Personal Nix flake
nixos home-manager nix
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: Merge pull request #28 from lpchaim/develop

chore: Merge branch 'develop' into main

authored by

Luna Perroni and committed by
GitHub
ba8af2ae d5ed37dd

+6112 -1892
+12 -10
.github/workflows/build.yml
··· 2 2 3 3 on: 4 4 workflow_dispatch: 5 - workflow_run: 6 - workflows: 7 - - Check 8 - types: 9 - - completed 5 + push: 6 + branches: 7 + - main 8 + - develop 9 + paths: 10 + - 'flake.nix' 11 + - 'flake.lock' 12 + - 'nix/**' 10 13 11 14 concurrency: 12 15 group: ${{ github.workflow }}-${{ github.ref_name }} ··· 15 18 jobs: 16 19 inventory: 17 20 name: Build inventory 18 - if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} 19 21 runs-on: ubuntu-24.04 20 22 outputs: 21 23 x86_64-linux: ${{ steps.matrix.outputs.x86_64-linux }} ··· 30 32 id: matrix 31 33 run: | 32 34 { 33 - echo "x86_64-linux=$(nix run .#generate-ci-matrix -- --system x86_64-linux --branch "$GITHUB_REF_NAME" --flatten)" 34 - echo "aarch64-linux=$(nix run .#generate-ci-matrix -- --system aarch64-linux --branch "$GITHUB_REF_NAME" --flatten)" 35 + echo "x86_64-linux=$(nix run --quiet .#generate-ci-matrix -- --system x86_64-linux --branch "$GITHUB_REF_NAME" --flatten)" 36 + echo "aarch64-linux=$(nix run --quiet .#generate-ci-matrix -- --system aarch64-linux --branch "$GITHUB_REF_NAME" --flatten)" 35 37 } >> "$GITHUB_OUTPUT" 36 38 37 39 build-x86_64-linux: ··· 51 53 cachixAuthToken: ${{ secrets.CACHIX_AUTH_TOKEN }} 52 54 makeSpace: ${{ matrix.output == 'nixosConfigurations' }} 53 55 - run: | 54 - nix build ${{ matrix.derivation }} 56 + nix build --quiet ${{ matrix.derivation }} 55 57 56 58 build-aarch64-linux: 57 59 name: Build ${{ matrix.output }}.${{ matrix.name }} ··· 70 72 cachixAuthToken: ${{ secrets.CACHIX_AUTH_TOKEN }} 71 73 makeSpace: ${{ matrix.output == 'nixosConfigurations' }} 72 74 - run: | 73 - nix build ${{ matrix.derivation }} 75 + nix build --quiet ${{ matrix.derivation }}
+2 -2
.github/workflows/check.yml
··· 16 16 cancel-in-progress: true 17 17 18 18 jobs: 19 - inventory: 19 + check: 20 20 name: Run flake checks 21 21 runs-on: ubuntu-24.04 22 22 steps: ··· 25 25 with: 26 26 cachixAuthToken: ${{ secrets.CACHIX_AUTH_TOKEN }} 27 27 - run: | 28 - nix flake check --all-systems --keep-going . 28 + nix flake check --quiet --all-systems --keep-going .
+2 -2
.github/workflows/update-flake.yml
··· 29 29 id: update 30 30 run: | 31 31 set -o pipefail 32 - nix flake update 2>&1 \ 32 + nix flake update --quiet 2>&1 \ 33 33 | tee -a "$GITHUB_STEP_SUMMARY" ./update.out 34 34 - name: Run flake checks 35 35 id: check 36 36 run: | 37 37 set -o pipefail 38 - nix flake check --all-systems --keep-going 2>&1 \ 38 + nix flake check --quiet --all-systems --keep-going 2>&1 \ 39 39 | tee -a "$GITHUB_STEP_SUMMARY" ./check.out 40 40 - name: Build PR body 41 41 id: vars
+13 -269
README.md
··· 1 - [caelestia]: https://github.com/caelestia-dots/shell 1 + [dms]: https://github.com/AvengeMedia/DankMaterialShell 2 2 [ez-configs]: https://github.com/ehllie/ez-configs/ 3 3 [flake-parts]: https://github.com/hercules-ci/flake-parts 4 4 [flake-schemas]: https://github.com/DeterminateSystems/flake-schemas 5 - [haumea]: https://github.com/nix-community/haumea 6 5 [rofi]: https://github.com/davatorium/rofi 7 6 [stylix]: https://github.com/danth/stylix 8 7 ··· 21 20 22 21 --- 23 22 24 - Welcome to my NixOS flake! It's mostly powered by [flake-parts], with some [haumea] sprinkled in for painless module loading here and there. 23 + Welcome to my Nix flake, powered by [flake-parts]! 25 24 26 - This is mainly for my NixOS configurations, but it also has a couple standalone Home Manager configs, development shells and NixOS/Home Manager modules. 25 + This is mainly for my NixOS configurations, but it also has a couple standalone Home Manager configs, packages, development shells and NixOS/Home Manager modules. 27 26 28 27 ## Design goals 29 28 ··· 42 41 As an example, this is a working NixOS configuration describing my main rig. 43 42 44 43 ```nix 45 - {inputs, ...}: let 46 - inherit (inputs.self.lib.config) name; 44 + {config, ...}: let 45 + inherit (config.my.config) name; 47 46 in { 48 47 imports = [ 49 48 ./hardware-configuration.nix ··· 51 50 ]; 52 51 53 52 my = { 53 + ci.build = true; 54 54 gaming.enable = true; 55 55 networking.tailscale.trusted = true; 56 + users.emily.enable = true; 56 57 profiles = { 57 58 formfactor.desktop = true; 58 59 hardware.gpu.nvidia = true; 59 60 hardware.rgb = true; 60 61 de.gnome = true; 61 62 de.hyprland = true; 63 + graphical = true; 62 64 }; 63 65 }; 64 66 65 67 networking.interfaces.enp6s0.wakeOnLan.enable = true; 66 68 69 + age.rekey.hostPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMNf+oynlWr+Xq3UYKpCy8ih/w9sT6IuIKAtYjo6sfJr"; 67 70 system.stateVersion = "23.11"; 68 71 home-manager.users.${name.user}.home.stateVersion = "24.11"; 69 72 } ··· 71 74 72 75 ## Look and feel 73 76 74 - I daily drive Hyprland with [caelestia] and [rofi]. 77 + I daily drive Hyprland with [dms] and [rofi]. 75 78 76 79 My systems wouldn't look even halfway as good without [stylix] doing all the heavy-lifting in my stead. 77 80 The color scheme used in my screenshots is `stella`. ··· 79 82 ## File structure 80 83 81 84 I'm hoping the file structure under `/nix` is mostly self-explanatory. That said, there are a couple that bear explaining: 82 - - `modules` for flake modules consumed by [flake-parts] 83 - - `schemas` for my custom [flake-schemas] definitions 84 - - `shared` for configuration and modules useful to both and NixOS and Home Manager 85 - 86 - <details> 87 - <summary>Tree view of the directory structure</summary> 88 - 89 - ```sh 90 - ./nix 91 - ├── apps 92 - │ ├── assets.nix 93 - │ ├── ci.nix 94 - │ └── default.nix 95 - ├── home 96 - │ ├── configs 97 - │ └── modules 98 - ├── lib 99 - │ ├── config.nix 100 - │ ├── default.nix 101 - │ ├── loaders.nix 102 - │ ├── storage 103 - │ └── strings.nix 104 - ├── modules 105 - │ ├── default.nix 106 - │ ├── ezConfigs.nix 107 - │ ├── gitHooks.nix 108 - │ └── just.nix 109 - ├── nixos 110 - │ ├── configs 111 - │ └── modules 112 - ├── overlays 113 - │ ├── default.nix 114 - │ ├── lix.nix 115 - │ ├── nixpkgsVersions.nix 116 - │ ├── nuInterpreterStdin.nix 117 - │ └── python.nix 118 - ├── packages 119 - │ ├── default.nix 120 - │ └── lichen 121 - ├── schemas 122 - │ ├── default.nix 123 - │ ├── lib.nix 124 - │ └── pkgs.nix 125 - ├── scripts 126 - │ ├── default.nix 127 - │ ├── lastdl.nix 128 - │ ├── leastspaces.nix 129 - │ ├── nu-generate-carapace-spec.nix 130 - │ ├── nu-generate-manpage.nix 131 - │ ├── nu-inspect.nix 132 - │ └── nu-parse-help.nix 133 - ├── shared 134 - │ ├── default.nix 135 - │ └── theming.nix 136 - └── shells 137 - ├── default.nix 138 - ├── deploy.nix 139 - ├── minimal.nix 140 - ├── nix.nix 141 - └── rust.nix 142 - ``` 143 - </details> 144 - 145 - ## Outputs 146 - 147 - If you're curious, this is what the flake actually outputs right now. 148 - Courtesy of [flake-schemas]' patches with my own lib/pkgs schemas on top. 149 - 150 - <details> 151 - <summary>Output of `nix flake show`</summary> 152 - 153 - ```sh 154 - git+file:///home/lpchaim/.config/nixos 155 - ├───apps 156 - │ ├───aarch64-linux 157 - │ │ ├───generate-assets: app 158 - │ │ ├───generate-ci-matrix: app 159 - │ │ └───render-readme: app 160 - │ └───x86_64-linux 161 - │ ├───generate-assets: app 162 - │ ├───generate-ci-matrix: app 163 - │ └───render-readme: app 164 - ├───checks 165 - │ ├───aarch64-linux 166 - │ │ ├───deploy-shell: CI test [nix-shell] 167 - │ │ ├───minimal-shell: CI test [nix-shell] 168 - │ │ ├───nix-shell: CI test [nix-shell] 169 - │ │ ├───pre-commit: CI test [pre-commit-run] 170 - │ │ └───rust-shell: CI test [nix-shell] 171 - │ └───x86_64-linux 172 - │ ├───deploy-shell: CI test [nix-shell] 173 - │ ├───minimal-shell: CI test [nix-shell] 174 - │ ├───nix-shell: CI test [nix-shell] 175 - │ ├───pre-commit: CI test [pre-commit-run] 176 - │ └───rust-shell: CI test [nix-shell] 177 - ├───darwinConfigurations 178 - ├───darwinModules 179 - ├───devShells 180 - │ ├───aarch64-linux 181 - │ │ ├───default: development environment [nix-shell] 182 - │ │ ├───deploy: development environment [deploy-shell] 183 - │ │ ├───minimal: development environment [minimal-shell] 184 - │ │ ├───nix: development environment [nix-shell] 185 - │ │ └───rust: development environment [rust-shell] 186 - │ └───x86_64-linux 187 - │ ├───default: development environment [nix-shell] 188 - │ ├───deploy: development environment [deploy-shell] 189 - │ ├───minimal: development environment [minimal-shell] 190 - │ ├───nix: development environment [nix-shell] 191 - │ └───rust: development environment [rust-shell] 192 - ├───formatter 193 - │ ├───aarch64-linux: formatter [alejandra-4.0.0] 194 - │ └───x86_64-linux: formatter [alejandra-4.0.0] 195 - ├───homeConfigurations 196 - │ ├───"cheina@pc082": Home Manager configuration [home-manager-generation] 197 - │ ├───"lpchaim@desktop": Home Manager configuration [home-manager-generation] 198 - │ ├───"lpchaim@laptop": Home Manager configuration [home-manager-generation] 199 - │ ├───"lpchaim@raspberrypi": Home Manager configuration [home-manager-generation] 200 - │ └───"lpchaim@steamdeck": Home Manager configuration [home-manager-generation] 201 - ├───homeModules 202 - │ ├───bars: Home Manager module 203 - │ ├───cli: Home Manager module 204 - │ ├───de: Home Manager module 205 - │ ├───default: Home Manager module 206 - │ ├───gui: Home Manager module 207 - │ ├───misc: Home Manager module 208 - │ ├───nix: Home Manager module 209 - │ ├───profiles: Home Manager module 210 - │ ├───scripts: Home Manager module 211 - │ ├───security: Home Manager module 212 - │ ├───syncthing: Home Manager module 213 - │ └───theming: Home Manager module 214 - ├───legacyPackages 215 - │ └───(skipped; use '--legacy' to show) 216 - ├───lib 217 - │ ├───config 218 - │ │ ├───email 219 - │ │ │ └───main: configuration constant 220 - │ │ ├───flake 221 - │ │ │ └───path: configuration constant 222 - │ │ ├───kb 223 - │ │ │ ├───br 224 - │ │ │ │ ├───layout: configuration constant 225 - │ │ │ │ ├───options: configuration constant 226 - │ │ │ │ └───variant: configuration constant 227 - │ │ │ ├───default 228 - │ │ │ │ ├───layout: configuration constant 229 - │ │ │ │ ├───options: configuration constant 230 - │ │ │ │ └───variant: configuration constant 231 - │ │ │ └───us 232 - │ │ │ ├───layout: configuration constant 233 - │ │ │ ├───options: configuration constant 234 - │ │ │ └───variant: configuration constant 235 - │ │ ├───name 236 - │ │ │ ├───full: configuration constant 237 - │ │ │ └───user: configuration constant 238 - │ │ ├───nix 239 - │ │ │ ├───pkgs 240 - │ │ │ │ └───config 241 - │ │ │ │ ├───allowUnfree: configuration constant 242 - │ │ │ │ └───permittedInsecurePackages: configuration constant 243 - │ │ │ └───settings 244 - │ │ │ ├───accept-flake-config: configuration constant 245 - │ │ │ ├───auto-optimise-store: configuration constant 246 - │ │ │ ├───builders-use-substitutes: configuration constant 247 - │ │ │ ├───extra-experimental-features: configuration constant 248 - │ │ │ ├───extra-substituters: configuration constant 249 - │ │ │ ├───extra-trusted-public-keys: configuration constant 250 - │ │ │ ├───keep-derivations: configuration constant 251 - │ │ │ ├───keep-outputs: configuration constant 252 - │ │ │ └───max-jobs: configuration constant 253 - │ │ ├───profilePicture: configuration constant 254 - │ │ ├───repo 255 - │ │ │ └───main: configuration constant 256 - │ │ ├───shell: configuration constant 257 - │ │ └───wallpaper: configuration constant 258 - │ ├───isNvidia: library function 259 - │ ├───loaders 260 - │ │ ├───callPackageDefault: library function 261 - │ │ ├───callPackageNonDefault: library function 262 - │ │ ├───importDefault: library function 263 - │ │ ├───importNonDefault: library function 264 - │ │ ├───list: library function 265 - │ │ ├───listDefault: library function 266 - │ │ ├───listDefaultRecursive: library function 267 - │ │ ├───listNonDefault: library function 268 - │ │ ├───listNonDefaultRecursive: library function 269 - │ │ ├───load: library function 270 - │ │ ├───loadDefault: library function 271 - │ │ ├───loadNonDefault: library function 272 - │ │ └───read: library function 273 - │ ├───mkPkgs: library function 274 - │ ├───storage 275 - │ │ ├───btrfs 276 - │ │ │ ├───mkSecondaryStorage: library function 277 - │ │ │ └───mkStorage: library function 278 - │ │ ├───mkSafePath: library function 279 - │ │ └───ntfs 280 - │ │ └───mkSecondaryStorage: library function 281 - │ └───strings 282 - │ └───replaceUsing: library function 283 - ├───nixosConfigurations 284 - │ ├───desktop: NixOS configuration [nixos-system-desktop-26.05.20260126.bfc1b8a] 285 - │ ├───laptop: NixOS configuration [nixos-system-laptop-26.05.20260126.bfc1b8a] 286 - │ ├───raspberrypi: NixOS configuration [nixos-system-raspberrypi-26.05.20260126.bfc1b8a] 287 - │ └───steamdeck: NixOS configuration [nixos-system-steamdeck-26.05.20260126.bfc1b8a] 288 - ├───nixosModules 289 - │ ├───boot: NixOS module 290 - │ ├───default: NixOS module 291 - │ ├───desktop: NixOS module 292 - │ ├───gaming: NixOS module 293 - │ ├───hardware: NixOS module 294 - │ ├───kdeconnect: NixOS module 295 - │ ├───locale: NixOS module 296 - │ ├───networking: NixOS module 297 - │ ├───nix: NixOS module 298 - │ ├───profiles: NixOS module 299 - │ ├───programs: NixOS module 300 - │ ├───secrets: NixOS module 301 - │ ├───secureboot: NixOS module 302 - │ ├───security: NixOS module 303 - │ ├───services: NixOS module 304 - │ ├───ssh: NixOS module 305 - │ ├───steamos: NixOS module 306 - │ ├───syncthing: NixOS module 307 - │ ├───tailscale: NixOS module 308 - │ ├───theming: NixOS module 309 - │ └───zram: NixOS module 310 - ├───overlays 311 - │ ├───external: Nixpkgs overlay 312 - │ ├───lix: Nixpkgs overlay 313 - │ ├───nixpkgsVersions: Nixpkgs overlay 314 - │ ├───nuInterpreterStdin: Nixpkgs overlay 315 - │ └───python: Nixpkgs overlay 316 - ├───packages 317 - │ ├───aarch64-linux 318 - │ │ └───lichen: package [lichen-0.22.0-unstable] 319 - │ └───x86_64-linux 320 - │ └───lichen: package [lichen-0.22.0-unstable] 321 - └───schemas 322 - ├───apps: flake schema 323 - ├───bundlers: flake schema 324 - ├───checks: flake schema 325 - ├───darwinConfigurations: flake schema 326 - ├───darwinModules: flake schema 327 - ├───devShells: flake schema 328 - ├───dockerImages: flake schema 329 - ├───formatter: flake schema 330 - ├───homeConfigurations: flake schema 331 - ├───homeModules: flake schema 332 - ├───hydraJobs: flake schema 333 - ├───legacyPackages: flake schema 334 - ├───lib: flake schema 335 - ├───nixosConfigurations: flake schema 336 - ├───nixosModules: flake schema 337 - ├───overlays: flake schema 338 - ├───packages: flake schema 339 - ├───pkgs: flake schema 340 - ├───schemas: flake schema 341 - └───templates: flake schema 342 - ``` 343 - </details> 85 + - `nix/flakeModules` for flake modules consumed by [flake-parts] 86 + - `nix/schemas` for my custom [flake-schemas] definitions 87 + - `nix/shared` for configuration and modules useful to both and NixOS and Home Manager
+7 -29
assets/README.md
··· 1 - [caelestia]: https://github.com/caelestia-dots/shell 1 + [dms]: https://github.com/AvengeMedia/DankMaterialShell 2 2 [ez-configs]: https://github.com/ehllie/ez-configs/ 3 3 [flake-parts]: https://github.com/hercules-ci/flake-parts 4 4 [flake-schemas]: https://github.com/DeterminateSystems/flake-schemas 5 - [haumea]: https://github.com/nix-community/haumea 6 5 [rofi]: https://github.com/davatorium/rofi 7 6 [stylix]: https://github.com/danth/stylix 8 7 ··· 21 20 22 21 --- 23 22 24 - Welcome to my NixOS flake! It's mostly powered by [flake-parts], with some [haumea] sprinkled in for painless module loading here and there. 23 + Welcome to my Nix flake, powered by [flake-parts]! 25 24 26 - This is mainly for my NixOS configurations, but it also has a couple standalone Home Manager configs, development shells and NixOS/Home Manager modules. 25 + This is mainly for my NixOS configurations, but it also has a couple standalone Home Manager configs, packages, development shells and NixOS/Home Manager modules. 27 26 28 27 ## Design goals 29 28 ··· 47 46 48 47 ## Look and feel 49 48 50 - I daily drive Hyprland with [caelestia] and [rofi]. 49 + I daily drive Hyprland with [dms] and [rofi]. 51 50 52 51 My systems wouldn't look even halfway as good without [stylix] doing all the heavy-lifting in my stead. 53 52 The color scheme used in my screenshots is `stella`. ··· 55 54 ## File structure 56 55 57 56 I'm hoping the file structure under `/nix` is mostly self-explanatory. That said, there are a couple that bear explaining: 58 - - `modules` for flake modules consumed by [flake-parts] 59 - - `schemas` for my custom [flake-schemas] definitions 60 - - `shared` for configuration and modules useful to both and NixOS and Home Manager 61 - 62 - <details> 63 - <summary>Tree view of the directory structure</summary> 64 - 65 - ```sh 66 - $filestructure 67 - ``` 68 - </details> 69 - 70 - ## Outputs 71 - 72 - If you're curious, this is what the flake actually outputs right now. 73 - Courtesy of [flake-schemas]' patches with my own lib/pkgs schemas on top. 74 - 75 - <details> 76 - <summary>Output of `nix flake show`</summary> 77 - 78 - ```sh 79 - $outputs 80 - ``` 81 - </details> 57 + - `nix/flakeModules` for flake modules consumed by [flake-parts] 58 + - `nix/schemas` for my custom [flake-schemas] definitions 59 + - `nix/shared` for configuration and modules useful to both and NixOS and Home Manager
-52
assets/readme/filestructure.txt
··· 1 - ./nix 2 - ├── apps 3 - │ ├── assets.nix 4 - │ ├── ci.nix 5 - │ └── default.nix 6 - ├── home 7 - │ ├── configs 8 - │ └── modules 9 - ├── lib 10 - │ ├── config.nix 11 - │ ├── default.nix 12 - │ ├── loaders.nix 13 - │ ├── storage 14 - │ └── strings.nix 15 - ├── modules 16 - │ ├── default.nix 17 - │ ├── ezConfigs.nix 18 - │ ├── gitHooks.nix 19 - │ └── just.nix 20 - ├── nixos 21 - │ ├── configs 22 - │ └── modules 23 - ├── overlays 24 - │ ├── default.nix 25 - │ ├── lix.nix 26 - │ ├── nixpkgsVersions.nix 27 - │ ├── nuInterpreterStdin.nix 28 - │ └── python.nix 29 - ├── packages 30 - │ ├── default.nix 31 - │ └── lichen 32 - ├── schemas 33 - │ ├── default.nix 34 - │ ├── lib.nix 35 - │ └── pkgs.nix 36 - ├── scripts 37 - │ ├── default.nix 38 - │ ├── lastdl.nix 39 - │ ├── leastspaces.nix 40 - │ ├── nu-generate-carapace-spec.nix 41 - │ ├── nu-generate-manpage.nix 42 - │ ├── nu-inspect.nix 43 - │ └── nu-parse-help.nix 44 - ├── shared 45 - │ ├── default.nix 46 - │ └── theming.nix 47 - └── shells 48 - ├── default.nix 49 - ├── deploy.nix 50 - ├── minimal.nix 51 - ├── nix.nix 52 - └── rust.nix
-188
assets/readme/outputs.txt
··· 1 - git+file:///home/lpchaim/.config/nixos 2 - ├───apps 3 - │ ├───aarch64-linux 4 - │ │ ├───generate-assets: app 5 - │ │ ├───generate-ci-matrix: app 6 - │ │ └───render-readme: app 7 - │ └───x86_64-linux 8 - │ ├───generate-assets: app 9 - │ ├───generate-ci-matrix: app 10 - │ └───render-readme: app 11 - ├───checks 12 - │ ├───aarch64-linux 13 - │ │ ├───deploy-shell: CI test [nix-shell] 14 - │ │ ├───minimal-shell: CI test [nix-shell] 15 - │ │ ├───nix-shell: CI test [nix-shell] 16 - │ │ ├───pre-commit: CI test [pre-commit-run] 17 - │ │ └───rust-shell: CI test [nix-shell] 18 - │ └───x86_64-linux 19 - │ ├───deploy-shell: CI test [nix-shell] 20 - │ ├───minimal-shell: CI test [nix-shell] 21 - │ ├───nix-shell: CI test [nix-shell] 22 - │ ├───pre-commit: CI test [pre-commit-run] 23 - │ └───rust-shell: CI test [nix-shell] 24 - ├───darwinConfigurations 25 - ├───darwinModules 26 - ├───devShells 27 - │ ├───aarch64-linux 28 - │ │ ├───default: development environment [nix-shell] 29 - │ │ ├───deploy: development environment [deploy-shell] 30 - │ │ ├───minimal: development environment [minimal-shell] 31 - │ │ ├───nix: development environment [nix-shell] 32 - │ │ └───rust: development environment [rust-shell] 33 - │ └───x86_64-linux 34 - │ ├───default: development environment [nix-shell] 35 - │ ├───deploy: development environment [deploy-shell] 36 - │ ├───minimal: development environment [minimal-shell] 37 - │ ├───nix: development environment [nix-shell] 38 - │ └───rust: development environment [rust-shell] 39 - ├───formatter 40 - │ ├───aarch64-linux: formatter [alejandra-4.0.0] 41 - │ └───x86_64-linux: formatter [alejandra-4.0.0] 42 - ├───homeConfigurations 43 - │ ├───"cheina@pc082": Home Manager configuration [home-manager-generation] 44 - │ ├───"lpchaim@desktop": Home Manager configuration [home-manager-generation] 45 - │ ├───"lpchaim@laptop": Home Manager configuration [home-manager-generation] 46 - │ ├───"lpchaim@raspberrypi": Home Manager configuration [home-manager-generation] 47 - │ └───"lpchaim@steamdeck": Home Manager configuration [home-manager-generation] 48 - ├───homeModules 49 - │ ├───bars: Home Manager module 50 - │ ├───cli: Home Manager module 51 - │ ├───de: Home Manager module 52 - │ ├───default: Home Manager module 53 - │ ├───gui: Home Manager module 54 - │ ├───misc: Home Manager module 55 - │ ├───nix: Home Manager module 56 - │ ├───profiles: Home Manager module 57 - │ ├───scripts: Home Manager module 58 - │ ├───security: Home Manager module 59 - │ ├───syncthing: Home Manager module 60 - │ └───theming: Home Manager module 61 - ├───legacyPackages 62 - │ └───(skipped; use '--legacy' to show) 63 - ├───lib 64 - │ ├───config 65 - │ │ ├───email 66 - │ │ │ └───main: configuration constant 67 - │ │ ├───flake 68 - │ │ │ └───path: configuration constant 69 - │ │ ├───kb 70 - │ │ │ ├───br 71 - │ │ │ │ ├───layout: configuration constant 72 - │ │ │ │ ├───options: configuration constant 73 - │ │ │ │ └───variant: configuration constant 74 - │ │ │ ├───default 75 - │ │ │ │ ├───layout: configuration constant 76 - │ │ │ │ ├───options: configuration constant 77 - │ │ │ │ └───variant: configuration constant 78 - │ │ │ └───us 79 - │ │ │ ├───layout: configuration constant 80 - │ │ │ ├───options: configuration constant 81 - │ │ │ └───variant: configuration constant 82 - │ │ ├───name 83 - │ │ │ ├───full: configuration constant 84 - │ │ │ └───user: configuration constant 85 - │ │ ├───nix 86 - │ │ │ ├───pkgs 87 - │ │ │ │ └───config 88 - │ │ │ │ ├───allowUnfree: configuration constant 89 - │ │ │ │ └───permittedInsecurePackages: configuration constant 90 - │ │ │ └───settings 91 - │ │ │ ├───accept-flake-config: configuration constant 92 - │ │ │ ├───auto-optimise-store: configuration constant 93 - │ │ │ ├───builders-use-substitutes: configuration constant 94 - │ │ │ ├───extra-experimental-features: configuration constant 95 - │ │ │ ├───extra-substituters: configuration constant 96 - │ │ │ ├───extra-trusted-public-keys: configuration constant 97 - │ │ │ ├───keep-derivations: configuration constant 98 - │ │ │ ├───keep-outputs: configuration constant 99 - │ │ │ └───max-jobs: configuration constant 100 - │ │ ├───profilePicture: configuration constant 101 - │ │ ├───repo 102 - │ │ │ └───main: configuration constant 103 - │ │ ├───shell: configuration constant 104 - │ │ └───wallpaper: configuration constant 105 - │ ├───isNvidia: library function 106 - │ ├───loaders 107 - │ │ ├───callPackageDefault: library function 108 - │ │ ├───callPackageNonDefault: library function 109 - │ │ ├───importDefault: library function 110 - │ │ ├───importNonDefault: library function 111 - │ │ ├───list: library function 112 - │ │ ├───listDefault: library function 113 - │ │ ├───listDefaultRecursive: library function 114 - │ │ ├───listNonDefault: library function 115 - │ │ ├───listNonDefaultRecursive: library function 116 - │ │ ├───load: library function 117 - │ │ ├───loadDefault: library function 118 - │ │ ├───loadNonDefault: library function 119 - │ │ └───read: library function 120 - │ ├───mkPkgs: library function 121 - │ ├───storage 122 - │ │ ├───btrfs 123 - │ │ │ ├───mkSecondaryStorage: library function 124 - │ │ │ └───mkStorage: library function 125 - │ │ ├───mkSafePath: library function 126 - │ │ └───ntfs 127 - │ │ └───mkSecondaryStorage: library function 128 - │ └───strings 129 - │ └───replaceUsing: library function 130 - ├───nixosConfigurations 131 - │ ├───desktop: NixOS configuration [nixos-system-desktop-26.05.20260126.bfc1b8a] 132 - │ ├───laptop: NixOS configuration [nixos-system-laptop-26.05.20260126.bfc1b8a] 133 - │ ├───raspberrypi: NixOS configuration [nixos-system-raspberrypi-26.05.20260126.bfc1b8a] 134 - │ └───steamdeck: NixOS configuration [nixos-system-steamdeck-26.05.20260126.bfc1b8a] 135 - ├───nixosModules 136 - │ ├───boot: NixOS module 137 - │ ├───default: NixOS module 138 - │ ├───desktop: NixOS module 139 - │ ├───gaming: NixOS module 140 - │ ├───hardware: NixOS module 141 - │ ├───kdeconnect: NixOS module 142 - │ ├───locale: NixOS module 143 - │ ├───networking: NixOS module 144 - │ ├───nix: NixOS module 145 - │ ├───profiles: NixOS module 146 - │ ├───programs: NixOS module 147 - │ ├───secrets: NixOS module 148 - │ ├───secureboot: NixOS module 149 - │ ├───security: NixOS module 150 - │ ├───services: NixOS module 151 - │ ├───ssh: NixOS module 152 - │ ├───steamos: NixOS module 153 - │ ├───syncthing: NixOS module 154 - │ ├───tailscale: NixOS module 155 - │ ├───theming: NixOS module 156 - │ └───zram: NixOS module 157 - ├───overlays 158 - │ ├───external: Nixpkgs overlay 159 - │ ├───lix: Nixpkgs overlay 160 - │ ├───nixpkgsVersions: Nixpkgs overlay 161 - │ ├───nuInterpreterStdin: Nixpkgs overlay 162 - │ └───python: Nixpkgs overlay 163 - ├───packages 164 - │ ├───aarch64-linux 165 - │ │ └───lichen: package [lichen-0.22.0-unstable] 166 - │ └───x86_64-linux 167 - │ └───lichen: package [lichen-0.22.0-unstable] 168 - └───schemas 169 - ├───apps: flake schema 170 - ├───bundlers: flake schema 171 - ├───checks: flake schema 172 - ├───darwinConfigurations: flake schema 173 - ├───darwinModules: flake schema 174 - ├───devShells: flake schema 175 - ├───dockerImages: flake schema 176 - ├───formatter: flake schema 177 - ├───homeConfigurations: flake schema 178 - ├───homeModules: flake schema 179 - ├───hydraJobs: flake schema 180 - ├───legacyPackages: flake schema 181 - ├───lib: flake schema 182 - ├───nixosConfigurations: flake schema 183 - ├───nixosModules: flake schema 184 - ├───overlays: flake schema 185 - ├───packages: flake schema 186 - ├───pkgs: flake schema 187 - ├───schemas: flake schema 188 - └───templates: flake schema
+200 -800
flake.lock
··· 4 4 "inputs": { 5 5 "darwin": "darwin", 6 6 "home-manager": "home-manager", 7 - "nixpkgs": "nixpkgs", 7 + "nixpkgs": [ 8 + "nixpkgs" 9 + ], 8 10 "systems": "systems" 9 11 }, 10 12 "locked": { ··· 32 34 "treefmt-nix": "treefmt-nix" 33 35 }, 34 36 "locked": { 35 - "lastModified": 1759699908, 36 - "narHash": "sha256-kYVGY8sAfqwpNch706Fy2+/b+xbtfidhXSnzvthAhIQ=", 37 + "lastModified": 1771524612, 38 + "narHash": "sha256-dCxFIwQi6rg5LTE4rUJxnLo9KWt1xN4pN8VPvC41Zas=", 37 39 "owner": "oddlama", 38 40 "repo": "agenix-rekey", 39 - "rev": "42362b12f59978aabf3ec3334834ce2f3662013d", 41 + "rev": "5a726968d5000c73518abc023311e0cd9991d16f", 40 42 "type": "github" 41 43 }, 42 44 "original": { ··· 45 47 "type": "github" 46 48 } 47 49 }, 48 - "aquamarine": { 49 - "inputs": { 50 - "hyprutils": [ 51 - "hyprland", 52 - "hyprutils" 53 - ], 54 - "hyprwayland-scanner": [ 55 - "hyprland", 56 - "hyprwayland-scanner" 57 - ], 58 - "nixpkgs": [ 59 - "hyprland", 60 - "nixpkgs" 61 - ], 62 - "systems": [ 63 - "hyprland", 64 - "systems" 65 - ] 66 - }, 67 - "locked": { 68 - "lastModified": 1770895474, 69 - "narHash": "sha256-JBcrq1Y0uw87VZdYsByVbv+GBuT6ECaCNb9txLX9UuU=", 70 - "owner": "hyprwm", 71 - "repo": "aquamarine", 72 - "rev": "a494d50d32b5567956b558437ceaa58a380712f7", 73 - "type": "github" 74 - }, 75 - "original": { 76 - "owner": "hyprwm", 77 - "repo": "aquamarine", 78 - "type": "github" 79 - } 80 - }, 81 50 "base16": { 82 51 "inputs": { 83 52 "fromYaml": "fromYaml" ··· 155 124 "quickshell": "quickshell" 156 125 }, 157 126 "locked": { 158 - "lastModified": 1770949235, 159 - "narHash": "sha256-OFeud9FjaOk6xHp/9igYl/+Zw6FJDyZNrIDNi47gsG0=", 127 + "lastModified": 1771725071, 128 + "narHash": "sha256-70QlV4vXdJfp+xBzM+JyzZLz9XFTBIWP369lb8YDn9Y=", 160 129 "owner": "caelestia-dots", 161 130 "repo": "shell", 162 - "rev": "93e8880842b03e251bf59d1ba316f2393c68574f", 131 + "rev": "dc7af39c909e47b19ac582a1eec39ee60f0dcce1", 163 132 "type": "github" 164 133 }, 165 134 "original": { ··· 179 148 ] 180 149 }, 181 150 "locked": { 182 - "lastModified": 1770345569, 183 - "narHash": "sha256-aXzEWD44Htg0kHdrT/j2Odxt1EXqdJR9s8fDpEAEZtY=", 151 + "lastModified": 1771641231, 152 + "narHash": "sha256-ztwtXtU3xKJhwr69N+tUbnMUv9Bo/p6kdogBo9Yd36s=", 184 153 "owner": "caelestia-dots", 185 154 "repo": "cli", 186 - "rev": "2395347d36dc12c4ad7471bcec030d75538c128c", 155 + "rev": "a6defd292136ac3a52fb0d39f045a0882dda6354", 187 156 "type": "github" 188 157 }, 189 158 "original": { ··· 192 161 "type": "github" 193 162 } 194 163 }, 164 + "colmena": { 165 + "inputs": { 166 + "flake-compat": "flake-compat_2", 167 + "flake-utils": "flake-utils", 168 + "nix-github-actions": "nix-github-actions", 169 + "nixpkgs": [ 170 + "nixpkgs" 171 + ], 172 + "stable": "stable" 173 + }, 174 + "locked": { 175 + "lastModified": 1762034856, 176 + "narHash": "sha256-QVey3iP3UEoiFVXgypyjTvCrsIlA4ecx6Acaz5C8/PQ=", 177 + "owner": "zhaofengli", 178 + "repo": "colmena", 179 + "rev": "349b035a5027f23d88eeb3bc41085d7ee29f18ed", 180 + "type": "github" 181 + }, 182 + "original": { 183 + "owner": "zhaofengli", 184 + "repo": "colmena", 185 + "type": "github" 186 + } 187 + }, 195 188 "crane": { 196 189 "locked": { 197 - "lastModified": 1770419512, 198 - "narHash": "sha256-o8Vcdz6B6bkiGUYkZqFwH3Pv1JwZyXht3dMtS7RchIo=", 190 + "lastModified": 1771121070, 191 + "narHash": "sha256-aIlv7FRXF9q70DNJPI237dEDAznSKaXmL5lfK/Id/bI=", 199 192 "owner": "ipetkov", 200 193 "repo": "crane", 201 - "rev": "2510f2cbc3ccd237f700bb213756a8f35c32d8d7", 194 + "rev": "a2812c19f1ed2e5ed5ce2ef7109798b575c180e1", 202 195 "type": "github" 203 196 }, 204 197 "original": { ··· 257 250 ] 258 251 }, 259 252 "locked": { 260 - "lastModified": 1769524058, 261 - "narHash": "sha256-zygdD6X1PcVNR2PsyK4ptzrVEiAdbMqLos7utrMDEWE=", 253 + "lastModified": 1771469470, 254 + "narHash": "sha256-GnqdqhrguKNN3HtVfl6z+zbV9R9jhHFm3Z8nu7R6ml0=", 262 255 "owner": "nix-community", 263 256 "repo": "disko", 264 - "rev": "71a3fc97d80881e91710fe721f1158d3b96ae14d", 257 + "rev": "4707eec8d1d2db5182ea06ed48c820a86a42dc13", 265 258 "type": "github" 266 259 }, 267 260 "original": { ··· 278 271 "quickshell": "quickshell_2" 279 272 }, 280 273 "locked": { 281 - "lastModified": 1768575133, 282 - "narHash": "sha256-P//moH3z9r4PXirTzXVsccQINsK5AIlF9RWOBwK3vLc=", 274 + "lastModified": 1771605734, 275 + "narHash": "sha256-qPm0HLKFIN8nBiTJwA4Jq5R3KPNoMHBxkpVJ4DQ9PBI=", 283 276 "owner": "AvengeMedia", 284 277 "repo": "DankMaterialShell", 285 - "rev": "a7cdb39b0b89b9af86160ad4e847a7d14ea44512", 278 + "rev": "03a8e1e0d5fee0e68e15c93e63ba4ebb967a8e0b", 286 279 "type": "github" 287 280 }, 288 281 "original": { ··· 350 343 "flake-compat_2": { 351 344 "flake": false, 352 345 "locked": { 353 - "lastModified": 1767039857, 354 - "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", 355 - "owner": "NixOS", 346 + "lastModified": 1650374568, 347 + "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", 348 + "owner": "edolstra", 356 349 "repo": "flake-compat", 357 - "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", 350 + "rev": "b4a34015c698c7793d592d66adbab377907a2be8", 358 351 "type": "github" 359 352 }, 360 353 "original": { 361 - "owner": "NixOS", 354 + "owner": "edolstra", 362 355 "repo": "flake-compat", 363 356 "type": "github" 364 357 } ··· 411 404 "type": "github" 412 405 } 413 406 }, 414 - "flake-compat_6": { 415 - "flake": false, 416 - "locked": { 417 - "lastModified": 1733328505, 418 - "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", 419 - "owner": "edolstra", 420 - "repo": "flake-compat", 421 - "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", 422 - "type": "github" 423 - }, 424 - "original": { 425 - "owner": "edolstra", 426 - "repo": "flake-compat", 427 - "type": "github" 428 - } 429 - }, 430 407 "flake-parts": { 431 408 "inputs": { 432 409 "nixpkgs-lib": [ ··· 435 412 ] 436 413 }, 437 414 "locked": { 438 - "lastModified": 1769996383, 439 - "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", 415 + "lastModified": 1733312601, 416 + "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", 440 417 "owner": "hercules-ci", 441 418 "repo": "flake-parts", 442 - "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", 419 + "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", 443 420 "type": "github" 444 421 }, 445 422 "original": { ··· 487 464 "flake-parts_4": { 488 465 "inputs": { 489 466 "nixpkgs-lib": [ 490 - "nixpkgs-schemas", 491 - "nixpkgs" 492 - ] 493 - }, 494 - "locked": { 495 - "lastModified": 1733312601, 496 - "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", 497 - "owner": "hercules-ci", 498 - "repo": "flake-parts", 499 - "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", 500 - "type": "github" 501 - }, 502 - "original": { 503 - "owner": "hercules-ci", 504 - "repo": "flake-parts", 505 - "type": "github" 506 - } 507 - }, 508 - "flake-parts_5": { 509 - "inputs": { 510 - "nixpkgs-lib": [ 511 467 "nixvim", 512 468 "nixpkgs" 513 469 ] ··· 526 482 "type": "github" 527 483 } 528 484 }, 529 - "flake-parts_6": { 485 + "flake-parts_5": { 530 486 "inputs": { 531 487 "nixpkgs-lib": [ 532 488 "nur", ··· 547 503 "type": "github" 548 504 } 549 505 }, 550 - "flake-parts_7": { 506 + "flake-parts_6": { 551 507 "inputs": { 552 508 "nixpkgs-lib": [ 553 509 "stylix", ··· 568 524 "type": "github" 569 525 } 570 526 }, 571 - "flake-parts_8": { 527 + "flake-parts_7": { 572 528 "inputs": { 573 529 "nixpkgs-lib": [ 574 530 "wayland-pipewire-idle-inhibit", ··· 605 561 } 606 562 }, 607 563 "flake-utils": { 564 + "locked": { 565 + "lastModified": 1659877975, 566 + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", 567 + "owner": "numtide", 568 + "repo": "flake-utils", 569 + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", 570 + "type": "github" 571 + }, 572 + "original": { 573 + "owner": "numtide", 574 + "repo": "flake-utils", 575 + "type": "github" 576 + } 577 + }, 578 + "flake-utils_2": { 608 579 "inputs": { 609 - "systems": "systems_3" 580 + "systems": "systems_2" 610 581 }, 611 582 "locked": { 612 583 "lastModified": 1689068808, ··· 640 611 }, 641 612 "git-hooks-nix": { 642 613 "inputs": { 643 - "flake-compat": "flake-compat_2", 614 + "flake-compat": "flake-compat_3", 644 615 "gitignore": "gitignore_2", 645 - "nixpkgs": "nixpkgs_2" 616 + "nixpkgs": "nixpkgs" 646 617 }, 647 618 "locked": { 648 619 "lastModified": 1770726378, ··· 658 629 "type": "github" 659 630 } 660 631 }, 661 - "git-hooks-nix_2": { 662 - "inputs": { 663 - "flake-compat": [ 664 - "nixpkgs-schemas" 665 - ], 666 - "gitignore": [ 667 - "nixpkgs-schemas" 668 - ], 669 - "nixpkgs": [ 670 - "nixpkgs-schemas", 671 - "nixpkgs" 672 - ], 673 - "nixpkgs-stable": [ 674 - "nixpkgs-schemas", 675 - "nixpkgs" 676 - ] 677 - }, 678 - "locked": { 679 - "lastModified": 1734279981, 680 - "narHash": "sha256-NdaCraHPp8iYMWzdXAt5Nv6sA3MUzlCiGiR586TCwo0=", 681 - "owner": "cachix", 682 - "repo": "git-hooks.nix", 683 - "rev": "aa9f40c906904ebd83da78e7f328cd8aeaeae785", 684 - "type": "github" 685 - }, 686 - "original": { 687 - "owner": "cachix", 688 - "repo": "git-hooks.nix", 689 - "type": "github" 690 - } 691 - }, 692 632 "gitignore": { 693 633 "inputs": { 694 634 "nixpkgs": [ ··· 735 675 "gitignore_3": { 736 676 "inputs": { 737 677 "nixpkgs": [ 738 - "hyprland", 739 - "pre-commit-hooks", 740 - "nixpkgs" 741 - ] 742 - }, 743 - "locked": { 744 - "lastModified": 1709087332, 745 - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 746 - "owner": "hercules-ci", 747 - "repo": "gitignore.nix", 748 - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 749 - "type": "github" 750 - }, 751 - "original": { 752 - "owner": "hercules-ci", 753 - "repo": "gitignore.nix", 754 - "type": "github" 755 - } 756 - }, 757 - "gitignore_4": { 758 - "inputs": { 759 - "nixpkgs": [ 760 678 "lanzaboote", 761 679 "pre-commit", 762 680 "nixpkgs" ··· 795 713 "type": "gitlab" 796 714 } 797 715 }, 798 - "haumea": { 799 - "inputs": { 800 - "nixpkgs": [ 801 - "nixpkgs" 802 - ] 803 - }, 804 - "locked": { 805 - "lastModified": 1685133229, 806 - "narHash": "sha256-FePm/Gi9PBSNwiDFq3N+DWdfxFq0UKsVVTJS3cQPn94=", 807 - "owner": "nix-community", 808 - "repo": "haumea", 809 - "rev": "34dd58385092a23018748b50f9b23de6266dffc2", 810 - "type": "github" 811 - }, 812 - "original": { 813 - "owner": "nix-community", 814 - "ref": "v0.2.2", 815 - "repo": "haumea", 816 - "type": "github" 817 - } 818 - }, 819 716 "home-manager": { 820 717 "inputs": { 821 718 "nixpkgs": [ ··· 824 721 ] 825 722 }, 826 723 "locked": { 827 - "lastModified": 1771037579, 828 - "narHash": "sha256-NX5XuhGcsmk0oEII2PEtMRgvh2KaAv3/WWQsOpxAgR4=", 724 + "lastModified": 1771851181, 725 + "narHash": "sha256-gFgE6mGUftwseV3DUENMb0k0EiHd739lZexPo5O/sdQ=", 829 726 "owner": "nix-community", 830 727 "repo": "home-manager", 831 - "rev": "05e6dc0f6ed936f918cb6f0f21f1dad1e4c53150", 728 + "rev": "9a4b494b1aa1b93d8edf167f46dc8e0c0011280c", 832 729 "type": "github" 833 730 }, 834 731 "original": { ··· 844 741 ] 845 742 }, 846 743 "locked": { 847 - "lastModified": 1771037579, 848 - "narHash": "sha256-NX5XuhGcsmk0oEII2PEtMRgvh2KaAv3/WWQsOpxAgR4=", 744 + "lastModified": 1771756436, 745 + "narHash": "sha256-Tl2I0YXdhSTufGqAaD1ySh8x+cvVsEI1mJyJg12lxhI=", 849 746 "owner": "nix-community", 850 747 "repo": "home-manager", 851 - "rev": "05e6dc0f6ed936f918cb6f0f21f1dad1e4c53150", 748 + "rev": "5bd3589390b431a63072868a90c0f24771ff4cbb", 852 749 "type": "github" 853 750 }, 854 751 "original": { ··· 857 754 "type": "github" 858 755 } 859 756 }, 860 - "hyprcursor": { 861 - "inputs": { 862 - "hyprlang": [ 863 - "hyprland", 864 - "hyprlang" 865 - ], 866 - "nixpkgs": [ 867 - "hyprland", 868 - "nixpkgs" 869 - ], 870 - "systems": [ 871 - "hyprland", 872 - "systems" 873 - ] 874 - }, 875 - "locked": { 876 - "lastModified": 1753964049, 877 - "narHash": "sha256-lIqabfBY7z/OANxHoPeIrDJrFyYy9jAM4GQLzZ2feCM=", 878 - "owner": "hyprwm", 879 - "repo": "hyprcursor", 880 - "rev": "44e91d467bdad8dcf8bbd2ac7cf49972540980a5", 881 - "type": "github" 882 - }, 883 - "original": { 884 - "owner": "hyprwm", 885 - "repo": "hyprcursor", 886 - "type": "github" 887 - } 888 - }, 889 - "hyprgraphics": { 890 - "inputs": { 891 - "hyprutils": [ 892 - "hyprland", 893 - "hyprutils" 894 - ], 895 - "nixpkgs": [ 896 - "hyprland", 897 - "nixpkgs" 898 - ], 899 - "systems": [ 900 - "hyprland", 901 - "systems" 902 - ] 903 - }, 904 - "locked": { 905 - "lastModified": 1770511807, 906 - "narHash": "sha256-suKmSbSk34uPOJDTg/GbPrKEJutzK08vj0VoTvAFBCA=", 907 - "owner": "hyprwm", 908 - "repo": "hyprgraphics", 909 - "rev": "7c75487edd43a71b61adb01cae8326d277aab683", 910 - "type": "github" 911 - }, 912 - "original": { 913 - "owner": "hyprwm", 914 - "repo": "hyprgraphics", 915 - "type": "github" 916 - } 917 - }, 918 - "hyprland": { 919 - "inputs": { 920 - "aquamarine": "aquamarine", 921 - "hyprcursor": "hyprcursor", 922 - "hyprgraphics": "hyprgraphics", 923 - "hyprland-guiutils": "hyprland-guiutils", 924 - "hyprland-protocols": "hyprland-protocols", 925 - "hyprlang": "hyprlang", 926 - "hyprutils": "hyprutils", 927 - "hyprwayland-scanner": "hyprwayland-scanner", 928 - "hyprwire": "hyprwire", 929 - "nixpkgs": "nixpkgs_3", 930 - "pre-commit-hooks": "pre-commit-hooks_2", 931 - "systems": "systems_2", 932 - "xdph": "xdph" 933 - }, 934 - "locked": { 935 - "lastModified": 1771026735, 936 - "narHash": "sha256-I06YSbOHSjYN6BiakRDrLNCcputm8EP69mJVfRgJqrQ=", 937 - "ref": "refs/heads/main", 938 - "rev": "e80f705d76d4dbe836e0f57aadea994a624ac63e", 939 - "revCount": 6888, 940 - "submodules": true, 941 - "type": "git", 942 - "url": "https://github.com/hyprwm/Hyprland" 943 - }, 944 - "original": { 945 - "submodules": true, 946 - "type": "git", 947 - "url": "https://github.com/hyprwm/Hyprland" 948 - } 949 - }, 950 - "hyprland-guiutils": { 951 - "inputs": { 952 - "aquamarine": [ 953 - "hyprland", 954 - "aquamarine" 955 - ], 956 - "hyprgraphics": [ 957 - "hyprland", 958 - "hyprgraphics" 959 - ], 960 - "hyprlang": [ 961 - "hyprland", 962 - "hyprlang" 963 - ], 964 - "hyprtoolkit": "hyprtoolkit", 965 - "hyprutils": [ 966 - "hyprland", 967 - "hyprutils" 968 - ], 969 - "hyprwayland-scanner": [ 970 - "hyprland", 971 - "hyprwayland-scanner" 972 - ], 973 - "nixpkgs": [ 974 - "hyprland", 975 - "nixpkgs" 976 - ], 977 - "systems": [ 978 - "hyprland", 979 - "systems" 980 - ] 981 - }, 982 - "locked": { 983 - "lastModified": 1767023960, 984 - "narHash": "sha256-R2HgtVS1G3KSIKAQ77aOZ+Q0HituOmPgXW9nBNkpp3Q=", 985 - "owner": "hyprwm", 986 - "repo": "hyprland-guiutils", 987 - "rev": "c2e906261142f5dd1ee0bfc44abba23e2754c660", 988 - "type": "github" 989 - }, 990 - "original": { 991 - "owner": "hyprwm", 992 - "repo": "hyprland-guiutils", 993 - "type": "github" 994 - } 995 - }, 996 - "hyprland-protocols": { 997 - "inputs": { 998 - "nixpkgs": [ 999 - "hyprland", 1000 - "nixpkgs" 1001 - ], 1002 - "systems": [ 1003 - "hyprland", 1004 - "systems" 1005 - ] 1006 - }, 1007 - "locked": { 1008 - "lastModified": 1765214753, 1009 - "narHash": "sha256-P9zdGXOzToJJgu5sVjv7oeOGPIIwrd9hAUAP3PsmBBs=", 1010 - "owner": "hyprwm", 1011 - "repo": "hyprland-protocols", 1012 - "rev": "3f3860b869014c00e8b9e0528c7b4ddc335c21ab", 1013 - "type": "github" 1014 - }, 1015 - "original": { 1016 - "owner": "hyprwm", 1017 - "repo": "hyprland-protocols", 1018 - "type": "github" 1019 - } 1020 - }, 1021 - "hyprlang": { 1022 - "inputs": { 1023 - "hyprutils": [ 1024 - "hyprland", 1025 - "hyprutils" 1026 - ], 1027 - "nixpkgs": [ 1028 - "hyprland", 1029 - "nixpkgs" 1030 - ], 1031 - "systems": [ 1032 - "hyprland", 1033 - "systems" 1034 - ] 1035 - }, 1036 - "locked": { 1037 - "lastModified": 1767983607, 1038 - "narHash": "sha256-8C2co8NYfR4oMOUEsPROOJ9JHrv9/ktbJJ6X1WsTbXc=", 1039 - "owner": "hyprwm", 1040 - "repo": "hyprlang", 1041 - "rev": "d4037379e6057246b408bbcf796cf3e9838af5b2", 1042 - "type": "github" 1043 - }, 1044 - "original": { 1045 - "owner": "hyprwm", 1046 - "repo": "hyprlang", 1047 - "type": "github" 1048 - } 1049 - }, 1050 - "hyprtoolkit": { 1051 - "inputs": { 1052 - "aquamarine": [ 1053 - "hyprland", 1054 - "hyprland-guiutils", 1055 - "aquamarine" 1056 - ], 1057 - "hyprgraphics": [ 1058 - "hyprland", 1059 - "hyprland-guiutils", 1060 - "hyprgraphics" 1061 - ], 1062 - "hyprlang": [ 1063 - "hyprland", 1064 - "hyprland-guiutils", 1065 - "hyprlang" 1066 - ], 1067 - "hyprutils": [ 1068 - "hyprland", 1069 - "hyprland-guiutils", 1070 - "hyprutils" 1071 - ], 1072 - "hyprwayland-scanner": [ 1073 - "hyprland", 1074 - "hyprland-guiutils", 1075 - "hyprwayland-scanner" 1076 - ], 1077 - "nixpkgs": [ 1078 - "hyprland", 1079 - "hyprland-guiutils", 1080 - "nixpkgs" 1081 - ], 1082 - "systems": [ 1083 - "hyprland", 1084 - "hyprland-guiutils", 1085 - "systems" 1086 - ] 1087 - }, 1088 - "locked": { 1089 - "lastModified": 1764592794, 1090 - "narHash": "sha256-7CcO+wbTJ1L1NBQHierHzheQGPWwkIQug/w+fhTAVuU=", 1091 - "owner": "hyprwm", 1092 - "repo": "hyprtoolkit", 1093 - "rev": "5cfe0743f0e608e1462972303778d8a0859ee63e", 1094 - "type": "github" 1095 - }, 1096 - "original": { 1097 - "owner": "hyprwm", 1098 - "repo": "hyprtoolkit", 1099 - "type": "github" 1100 - } 1101 - }, 1102 - "hyprutils": { 1103 - "inputs": { 1104 - "nixpkgs": [ 1105 - "hyprland", 1106 - "nixpkgs" 1107 - ], 1108 - "systems": [ 1109 - "hyprland", 1110 - "systems" 1111 - ] 1112 - }, 1113 - "locked": { 1114 - "lastModified": 1770139857, 1115 - "narHash": "sha256-bCqxcXjavgz5KBJ/1CBLqnagMMf9JvU1m9HmYVASKoc=", 1116 - "owner": "hyprwm", 1117 - "repo": "hyprutils", 1118 - "rev": "9038eec033843c289b06b83557a381a2648d8fa5", 1119 - "type": "github" 1120 - }, 1121 - "original": { 1122 - "owner": "hyprwm", 1123 - "repo": "hyprutils", 1124 - "type": "github" 1125 - } 1126 - }, 1127 - "hyprwayland-scanner": { 1128 - "inputs": { 1129 - "nixpkgs": [ 1130 - "hyprland", 1131 - "nixpkgs" 1132 - ], 1133 - "systems": [ 1134 - "hyprland", 1135 - "systems" 1136 - ] 1137 - }, 1138 - "locked": { 1139 - "lastModified": 1770501770, 1140 - "narHash": "sha256-NWRM6+YxTRv+bT9yvlhhJ2iLae1B1pNH3mAL5wi2rlQ=", 1141 - "owner": "hyprwm", 1142 - "repo": "hyprwayland-scanner", 1143 - "rev": "0bd8b6cde9ec27d48aad9e5b4deefb3746909d40", 1144 - "type": "github" 1145 - }, 1146 - "original": { 1147 - "owner": "hyprwm", 1148 - "repo": "hyprwayland-scanner", 1149 - "type": "github" 1150 - } 1151 - }, 1152 - "hyprwire": { 757 + "jovian": { 1153 758 "inputs": { 1154 - "hyprutils": [ 1155 - "hyprland", 1156 - "hyprutils" 1157 - ], 759 + "nix-github-actions": "nix-github-actions_2", 1158 760 "nixpkgs": [ 1159 - "hyprland", 1160 761 "nixpkgs" 1161 - ], 1162 - "systems": [ 1163 - "hyprland", 1164 - "systems" 1165 762 ] 1166 763 }, 1167 764 "locked": { 1168 - "lastModified": 1770203293, 1169 - "narHash": "sha256-PR/KER+yiHabFC/h1Wjb+9fR2Uy0lWM3Qld7jPVaWkk=", 1170 - "owner": "hyprwm", 1171 - "repo": "hyprwire", 1172 - "rev": "37bc90eed02b0c8b5a77a0b00867baf3005cfb98", 1173 - "type": "github" 1174 - }, 1175 - "original": { 1176 - "owner": "hyprwm", 1177 - "repo": "hyprwire", 1178 - "type": "github" 1179 - } 1180 - }, 1181 - "jovian": { 1182 - "inputs": { 1183 - "nix-github-actions": "nix-github-actions", 1184 - "nixpkgs": "nixpkgs_4" 1185 - }, 1186 - "locked": { 1187 - "lastModified": 1770915266, 1188 - "narHash": "sha256-2oD6ud7iathz3rb0TcZyMqAsG+u2KRpkcAY3CCBt5rs=", 765 + "lastModified": 1771587792, 766 + "narHash": "sha256-XGFLdlLOez7f0rmjlF+1TLXyBguy8gx2aBHx/Q5JXxs=", 1189 767 "owner": "Jovian-Experiments", 1190 768 "repo": "Jovian-NixOS", 1191 - "rev": "deee66bd287521aa4008f0bb616060f37c058980", 769 + "rev": "b49fc54950e251f166a2240799315033ab7a8916", 1192 770 "type": "github" 1193 771 }, 1194 772 "original": { ··· 1207 785 "rust-overlay": "rust-overlay" 1208 786 }, 1209 787 "locked": { 1210 - "lastModified": 1770734117, 1211 - "narHash": "sha256-PNXSnK507MRj+hYMgnUR7InNJzVCmOfsjHV4YXZgpwQ=", 788 + "lastModified": 1771492583, 789 + "narHash": "sha256-nQzvnU4BGu8dA6BsPPCqmVcab/3ebVmHtX3ZWbW3Hxc=", 1212 790 "owner": "nix-community", 1213 791 "repo": "lanzaboote", 1214 - "rev": "2038a9a19adb886eccba775321b055fdbdc5029d", 792 + "rev": "5e9380994665ef66c87ab8e22c913ff837174ce4", 1215 793 "type": "github" 1216 794 }, 1217 795 "original": { ··· 1257 835 "nix-gaming": { 1258 836 "inputs": { 1259 837 "flake-parts": "flake-parts_3", 1260 - "nixpkgs": "nixpkgs_5" 838 + "nixpkgs": "nixpkgs_2" 1261 839 }, 1262 840 "locked": { 1263 - "lastModified": 1771036369, 1264 - "narHash": "sha256-SzrzlEMdx0LfHbdWnFjaIPdUlKmEKjMlXx01w50nLtU=", 841 + "lastModified": 1771728260, 842 + "narHash": "sha256-WNa4vTrY1QdOciYsgOUpuzvWpRnTeiL71Q5Dz8OGXHI=", 1265 843 "owner": "fufexan", 1266 844 "repo": "nix-gaming", 1267 - "rev": "604bc7bad2f78a4616c7ba269902f4894a79ba73", 845 + "rev": "e43670cf52bdad1846e0b9b411c81776c0b2668f", 1268 846 "type": "github" 1269 847 }, 1270 848 "original": { ··· 1276 854 "nix-github-actions": { 1277 855 "inputs": { 1278 856 "nixpkgs": [ 857 + "colmena", 858 + "nixpkgs" 859 + ] 860 + }, 861 + "locked": { 862 + "lastModified": 1737420293, 863 + "narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=", 864 + "owner": "nix-community", 865 + "repo": "nix-github-actions", 866 + "rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9", 867 + "type": "github" 868 + }, 869 + "original": { 870 + "owner": "nix-community", 871 + "repo": "nix-github-actions", 872 + "type": "github" 873 + } 874 + }, 875 + "nix-github-actions_2": { 876 + "inputs": { 877 + "nixpkgs": [ 1279 878 "jovian", 1280 879 "nixpkgs" 1281 880 ] ··· 1295 894 "type": "github" 1296 895 } 1297 896 }, 1298 - "nix-github-actions_2": { 897 + "nix-github-actions_3": { 1299 898 "inputs": { 1300 899 "nixpkgs": [ 1301 900 "nixneovimplugins", ··· 1324 923 ] 1325 924 }, 1326 925 "locked": { 1327 - "lastModified": 1770315571, 1328 - "narHash": "sha256-hy0gcAgAcxrnSWKGuNO+Ob0x6jQ2xkR6hoaR0qJBHYs=", 926 + "lastModified": 1771734689, 927 + "narHash": "sha256-/phvMgr1yutyAMjKnZlxkVplzxHiz60i4rc+gKzpwhg=", 1329 928 "owner": "nix-community", 1330 929 "repo": "nix-index-database", 1331 - "rev": "2684bb8080a6f2ca5f9d494de5ef875bc1c4ecdb", 930 + "rev": "8f590b832326ab9699444f3a48240595954a4b10", 1332 931 "type": "github" 1333 932 }, 1334 933 "original": { ··· 1354 953 }, 1355 954 "nixneovimplugins": { 1356 955 "inputs": { 1357 - "flake-utils": "flake-utils", 956 + "flake-utils": "flake-utils_2", 1358 957 "nixpkgs": [ 1359 958 "nixpkgs" 1360 959 ], 1361 960 "poetry2nix": "poetry2nix" 1362 961 }, 1363 962 "locked": { 1364 - "lastModified": 1770908561, 1365 - "narHash": "sha256-cCEqomjZSTK30BfBhqjisSEyi7y5u8eDfDdaRObUTXM=", 963 + "lastModified": 1771513287, 964 + "narHash": "sha256-vqwmIyue66WUmAVL+kBo7IS7MldhRmYOLKh1lU/c9CM=", 1366 965 "owner": "jooooscha", 1367 966 "repo": "nixpkgs-vim-extra-plugins", 1368 - "rev": "5e3cf6c150d6e0709b04d333afe691551a9e58a6", 967 + "rev": "48e30712f6e7c259fc1ed2be9b08b34b3bd059cf", 1369 968 "type": "github" 1370 969 }, 1371 970 "original": { ··· 1376 975 }, 1377 976 "nixpkgs": { 1378 977 "locked": { 1379 - "lastModified": 1767313136, 1380 - "narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=", 1381 - "owner": "NixOS", 1382 - "repo": "nixpkgs", 1383 - "rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d", 1384 - "type": "github" 1385 - }, 1386 - "original": { 1387 - "owner": "NixOS", 1388 - "ref": "nixos-25.05", 1389 - "repo": "nixpkgs", 1390 - "type": "github" 1391 - } 1392 - }, 1393 - "nixpkgs-23-11": { 1394 - "locked": { 1395 - "lastModified": 1717159533, 1396 - "narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=", 978 + "lastModified": 1770073757, 979 + "narHash": "sha256-Vy+G+F+3E/Tl+GMNgiHl9Pah2DgShmIUBJXmbiQPHbI=", 1397 980 "owner": "NixOS", 1398 981 "repo": "nixpkgs", 1399 - "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", 982 + "rev": "47472570b1e607482890801aeaf29bfb749884f6", 1400 983 "type": "github" 1401 984 }, 1402 985 "original": { 1403 986 "owner": "NixOS", 987 + "ref": "nixpkgs-unstable", 1404 988 "repo": "nixpkgs", 1405 - "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", 1406 989 "type": "github" 1407 990 } 1408 991 }, 1409 992 "nixpkgs-hare": { 1410 993 "locked": { 1411 - "lastModified": 1769806382, 1412 - "narHash": "sha256-kk27xrW0HNtX5N1h5sbXE0k6A/OHETbau2WmL75gA94=", 994 + "lastModified": 1771360917, 995 + "narHash": "sha256-/Xszja7yoxRkLsG9nwumhmzLUXuGc4b47EU7N1sXNEA=", 1413 996 "owner": "lpchaim", 1414 997 "repo": "nixpkgs", 1415 - "rev": "e6c0f56a16c7deb4023f1d2a407ad1b68557c724", 998 + "rev": "450bc4b33978cadbdcd48012108a9b39dd9d2c2d", 1416 999 "type": "github" 1417 1000 }, 1418 1001 "original": { ··· 1452 1035 "type": "github" 1453 1036 } 1454 1037 }, 1455 - "nixpkgs-regression": { 1456 - "locked": { 1457 - "lastModified": 1643052045, 1458 - "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", 1459 - "owner": "NixOS", 1460 - "repo": "nixpkgs", 1461 - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", 1462 - "type": "github" 1463 - }, 1464 - "original": { 1465 - "owner": "NixOS", 1466 - "repo": "nixpkgs", 1467 - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", 1468 - "type": "github" 1469 - } 1470 - }, 1471 - "nixpkgs-schemas": { 1472 - "inputs": { 1473 - "flake-compat": "flake-compat_6", 1474 - "flake-parts": "flake-parts_4", 1475 - "git-hooks-nix": "git-hooks-nix_2", 1476 - "nixpkgs": "nixpkgs_6", 1477 - "nixpkgs-23-11": "nixpkgs-23-11", 1478 - "nixpkgs-regression": "nixpkgs-regression" 1479 - }, 1480 - "locked": { 1481 - "lastModified": 1741125032, 1482 - "narHash": "sha256-Yy1Cd3Xm4UJTctYsVQfD5jY5z7pVncvLu8cq0cjjYT4=", 1483 - "owner": "DeterminateSystems", 1484 - "repo": "nix-src", 1485 - "rev": "271926aa5997c3120c8ef0962ce1c7f29fee1a05", 1486 - "type": "github" 1487 - }, 1488 - "original": { 1489 - "owner": "DeterminateSystems", 1490 - "ref": "flake-schemas", 1491 - "repo": "nix-src", 1492 - "type": "github" 1493 - } 1494 - }, 1495 1038 "nixpkgs_2": { 1496 1039 "locked": { 1497 - "lastModified": 1770073757, 1498 - "narHash": "sha256-Vy+G+F+3E/Tl+GMNgiHl9Pah2DgShmIUBJXmbiQPHbI=", 1499 - "owner": "NixOS", 1500 - "repo": "nixpkgs", 1501 - "rev": "47472570b1e607482890801aeaf29bfb749884f6", 1502 - "type": "github" 1503 - }, 1504 - "original": { 1505 - "owner": "NixOS", 1506 - "ref": "nixpkgs-unstable", 1507 - "repo": "nixpkgs", 1508 - "type": "github" 1509 - } 1510 - }, 1511 - "nixpkgs_3": { 1512 - "locked": { 1513 - "lastModified": 1770841267, 1514 - "narHash": "sha256-9xejG0KoqsoKEGp2kVbXRlEYtFFcDTHjidiuX8hGO44=", 1515 - "owner": "NixOS", 1516 - "repo": "nixpkgs", 1517 - "rev": "ec7c70d12ce2fc37cb92aff673dcdca89d187bae", 1518 - "type": "github" 1519 - }, 1520 - "original": { 1521 - "owner": "NixOS", 1522 - "ref": "nixos-unstable", 1523 - "repo": "nixpkgs", 1524 - "type": "github" 1525 - } 1526 - }, 1527 - "nixpkgs_4": { 1528 - "locked": { 1529 - "lastModified": 1770562336, 1530 - "narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=", 1040 + "lastModified": 1771207753, 1041 + "narHash": "sha256-b9uG8yN50DRQ6A7JdZBfzq718ryYrlmGgqkRm9OOwCE=", 1531 1042 "owner": "NixOS", 1532 1043 "repo": "nixpkgs", 1533 - "rev": "d6c71932130818840fc8fe9509cf50be8c64634f", 1534 - "type": "github" 1535 - }, 1536 - "original": { 1537 - "owner": "NixOS", 1538 - "ref": "nixos-unstable", 1539 - "repo": "nixpkgs", 1540 - "type": "github" 1541 - } 1542 - }, 1543 - "nixpkgs_5": { 1544 - "locked": { 1545 - "lastModified": 1770537093, 1546 - "narHash": "sha256-pF1quXG5wsgtyuPOHcLfYg/ft/QMr8NnX0i6tW2187s=", 1547 - "owner": "NixOS", 1548 - "repo": "nixpkgs", 1549 - "rev": "fef9403a3e4d31b0a23f0bacebbec52c248fbb51", 1044 + "rev": "d1c15b7d5806069da59e819999d70e1cec0760bf", 1550 1045 "type": "github" 1551 1046 }, 1552 1047 "original": { ··· 1556 1051 "type": "github" 1557 1052 } 1558 1053 }, 1559 - "nixpkgs_6": { 1560 - "locked": { 1561 - "lastModified": 1734359947, 1562 - "narHash": "sha256-1Noao/H+N8nFB4Beoy8fgwrcOQLVm9o4zKW1ODaqK9E=", 1563 - "owner": "NixOS", 1564 - "repo": "nixpkgs", 1565 - "rev": "48d12d5e70ee91fe8481378e540433a7303dbf6a", 1566 - "type": "github" 1567 - }, 1568 - "original": { 1569 - "owner": "NixOS", 1570 - "ref": "release-24.11", 1571 - "repo": "nixpkgs", 1572 - "type": "github" 1573 - } 1574 - }, 1575 - "nixpkgs_7": { 1576 - "locked": { 1577 - "lastModified": 1770841267, 1578 - "narHash": "sha256-9xejG0KoqsoKEGp2kVbXRlEYtFFcDTHjidiuX8hGO44=", 1579 - "owner": "nixos", 1580 - "repo": "nixpkgs", 1581 - "rev": "ec7c70d12ce2fc37cb92aff673dcdca89d187bae", 1582 - "type": "github" 1583 - }, 1584 - "original": { 1585 - "owner": "nixos", 1586 - "ref": "nixos-unstable", 1587 - "repo": "nixpkgs", 1588 - "type": "github" 1589 - } 1590 - }, 1591 1054 "nixvim": { 1592 1055 "inputs": { 1593 - "flake-parts": "flake-parts_5", 1056 + "flake-parts": "flake-parts_4", 1594 1057 "nixpkgs": [ 1595 1058 "nixpkgs" 1596 1059 ], 1597 - "systems": "systems_4" 1060 + "systems": "systems_3" 1598 1061 }, 1599 1062 "locked": { 1600 - "lastModified": 1771023756, 1601 - "narHash": "sha256-sTj1hrPT7D4oGHaQQzwDeqyZBwnxYc+T7yceyQc4sy4=", 1063 + "lastModified": 1771135771, 1064 + "narHash": "sha256-wyvBIhDuyCRyjB3yPg77qoyxrlgQtBR1rVW3c9knV3E=", 1602 1065 "owner": "nix-community", 1603 1066 "repo": "nixvim", 1604 - "rev": "4c63aa76be59b49ae89892ae803005afd4a400cd", 1067 + "rev": "ed0424f0b08d303a7348f52f7850ad1b2704f9ba", 1605 1068 "type": "github" 1606 1069 }, 1607 1070 "original": { ··· 1612 1075 }, 1613 1076 "nur": { 1614 1077 "inputs": { 1615 - "flake-parts": "flake-parts_6", 1616 - "nixpkgs": "nixpkgs_7" 1078 + "flake-parts": "flake-parts_5", 1079 + "nixpkgs": [ 1080 + "nixpkgs" 1081 + ] 1617 1082 }, 1618 1083 "locked": { 1619 - "lastModified": 1771051892, 1620 - "narHash": "sha256-WrFPYVHrcL5860GpbtYIXhDb+Z4oZdwCvwysykCxbTQ=", 1084 + "lastModified": 1771759551, 1085 + "narHash": "sha256-6s6GPoexerGkUoCrdR+LSMjJnXoiYIV5Q0GVrqh7ZZs=", 1621 1086 "owner": "nix-community", 1622 1087 "repo": "NUR", 1623 - "rev": "3e8ceeafa58bab6c2de48831f6e75dd8fa401bdd", 1088 + "rev": "4eb896c52242049599e8a2c58aa185572db0d548", 1624 1089 "type": "github" 1625 1090 }, 1626 1091 "original": { ··· 1660 1125 "nixneovimplugins", 1661 1126 "flake-utils" 1662 1127 ], 1663 - "nix-github-actions": "nix-github-actions_2", 1128 + "nix-github-actions": "nix-github-actions_3", 1664 1129 "nixpkgs": [ 1665 1130 "nixneovimplugins", 1666 1131 "nixpkgs" ··· 1683 1148 "pre-commit": { 1684 1149 "inputs": { 1685 1150 "flake-compat": "flake-compat_4", 1686 - "gitignore": "gitignore_4", 1151 + "gitignore": "gitignore_3", 1687 1152 "nixpkgs": [ 1688 1153 "lanzaboote", 1689 1154 "nixpkgs" 1690 1155 ] 1691 1156 }, 1692 1157 "locked": { 1693 - "lastModified": 1769939035, 1694 - "narHash": "sha256-Fok2AmefgVA0+eprw2NDwqKkPGEI5wvR+twiZagBvrg=", 1158 + "lastModified": 1770726378, 1159 + "narHash": "sha256-kck+vIbGOaM/dHea7aTBxdFYpeUl/jHOy5W3eyRvVx8=", 1695 1160 "owner": "cachix", 1696 1161 "repo": "pre-commit-hooks.nix", 1697 - "rev": "a8ca480175326551d6c4121498316261cbb5b260", 1162 + "rev": "5eaaedde414f6eb1aea8b8525c466dc37bba95ae", 1698 1163 "type": "github" 1699 1164 }, 1700 1165 "original": { ··· 1726 1191 "type": "github" 1727 1192 } 1728 1193 }, 1729 - "pre-commit-hooks_2": { 1730 - "inputs": { 1731 - "flake-compat": "flake-compat_3", 1732 - "gitignore": "gitignore_3", 1733 - "nixpkgs": [ 1734 - "hyprland", 1735 - "nixpkgs" 1736 - ] 1737 - }, 1738 - "locked": { 1739 - "lastModified": 1770726378, 1740 - "narHash": "sha256-kck+vIbGOaM/dHea7aTBxdFYpeUl/jHOy5W3eyRvVx8=", 1741 - "owner": "cachix", 1742 - "repo": "git-hooks.nix", 1743 - "rev": "5eaaedde414f6eb1aea8b8525c466dc37bba95ae", 1744 - "type": "github" 1745 - }, 1746 - "original": { 1747 - "owner": "cachix", 1748 - "repo": "git-hooks.nix", 1749 - "type": "github" 1750 - } 1751 - }, 1752 1194 "quickshell": { 1753 1195 "inputs": { 1754 1196 "nixpkgs": [ ··· 1757 1199 ] 1758 1200 }, 1759 1201 "locked": { 1760 - "lastModified": 1769593411, 1761 - "narHash": "sha256-WW00FaBiUmQyxvSbefvgxIjwf/WmRrEGBbwMHvW/7uQ=", 1202 + "lastModified": 1770693276, 1203 + "narHash": "sha256-ngXnN5YXu+f45+QGYNN/VEBMQmcBCYGRCqwaK8cxY1s=", 1762 1204 "ref": "refs/heads/master", 1763 - "rev": "1e4d804e7f3fa7465811030e8da2bf10d544426a", 1764 - "revCount": 732, 1205 + "rev": "dacfa9de829ac7cb173825f593236bf2c21f637e", 1206 + "revCount": 735, 1765 1207 "type": "git", 1766 1208 "url": "https://git.outfoxxed.me/outfoxxed/quickshell" 1767 1209 }, ··· 1797 1239 "agenix": "agenix", 1798 1240 "agenix-rekey": "agenix-rekey", 1799 1241 "caelestia": "caelestia", 1242 + "colmena": "colmena", 1800 1243 "disko": "disko", 1801 1244 "dms": "dms", 1802 1245 "ez-configs": "ez-configs", 1803 1246 "flake-parts": "flake-parts_2", 1804 1247 "flake-schemas": "flake-schemas", 1805 1248 "git-hooks-nix": "git-hooks-nix", 1806 - "haumea": "haumea", 1807 1249 "home-manager": "home-manager_2", 1808 - "hyprland": "hyprland", 1809 1250 "jovian": "jovian", 1810 1251 "lanzaboote": "lanzaboote", 1811 1252 "make-shell": "make-shell", ··· 1818 1259 "unstable" 1819 1260 ], 1820 1261 "nixpkgs-hare": "nixpkgs-hare", 1821 - "nixpkgs-schemas": "nixpkgs-schemas", 1822 1262 "nixvim": "nixvim", 1823 1263 "nur": "nur", 1824 1264 "spicetify-nix": "spicetify-nix", 1825 - "stable": "stable", 1265 + "stable": "stable_2", 1826 1266 "stylix": "stylix", 1827 1267 "unstable": "unstable", 1828 1268 "wayland-pipewire-idle-inhibit": "wayland-pipewire-idle-inhibit" ··· 1836 1276 ] 1837 1277 }, 1838 1278 "locked": { 1839 - "lastModified": 1770520253, 1840 - "narHash": "sha256-6rWuHgSENXKnC6HGGAdRolQrnp/8IzscDn7FQEo1uEQ=", 1279 + "lastModified": 1771125043, 1280 + "narHash": "sha256-ldf/s49n6rOAxl7pYLJGGS1N/assoHkCOWdEdLyNZkc=", 1841 1281 "owner": "oxalica", 1842 1282 "repo": "rust-overlay", 1843 - "rev": "ebb8a141f60bb0ec33836333e0ca7928a072217f", 1283 + "rev": "4912f951a26dc8142b176be2c2ad834319dc06e8", 1844 1284 "type": "github" 1845 1285 }, 1846 1286 "original": { ··· 1854 1294 "nixpkgs": [ 1855 1295 "nixpkgs" 1856 1296 ], 1857 - "systems": "systems_5" 1297 + "systems": "systems_4" 1858 1298 }, 1859 1299 "locked": { 1860 - "lastModified": 1770846656, 1861 - "narHash": "sha256-wdYpo8++TqKp3GdRgLFykjuIVW1m9GlUnxID2FG74cE=", 1300 + "lastModified": 1771737804, 1301 + "narHash": "sha256-7wn9qbzIQQgH8tnq4VwzuWEqEWpekuymlLyhY3vM/j8=", 1862 1302 "owner": "Gerg-L", 1863 1303 "repo": "spicetify-nix", 1864 - "rev": "40e65cfc4608402674e1efaac3fccce20d2a72d3", 1304 + "rev": "6dd43010ac2458cc56a6ac5250349b9217a7a2ae", 1865 1305 "type": "github" 1866 1306 }, 1867 1307 "original": { ··· 1872 1312 }, 1873 1313 "stable": { 1874 1314 "locked": { 1875 - "lastModified": 1770770419, 1876 - "narHash": "sha256-iKZMkr6Cm9JzWlRYW/VPoL0A9jVKtZYiU4zSrVeetIs=", 1315 + "lastModified": 1767313136, 1316 + "narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=", 1877 1317 "owner": "NixOS", 1878 1318 "repo": "nixpkgs", 1879 - "rev": "6c5e707c6b5339359a9a9e215c5e66d6d802fd7a", 1319 + "rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d", 1320 + "type": "github" 1321 + }, 1322 + "original": { 1323 + "owner": "NixOS", 1324 + "ref": "nixos-25.05", 1325 + "repo": "nixpkgs", 1326 + "type": "github" 1327 + } 1328 + }, 1329 + "stable_2": { 1330 + "locked": { 1331 + "lastModified": 1771574726, 1332 + "narHash": "sha256-D1PA3xQv/s4W3lnR9yJFSld8UOLr0a/cBWMQMXS+1Qg=", 1333 + "owner": "NixOS", 1334 + "repo": "nixpkgs", 1335 + "rev": "c217913993d6c6f6805c3b1a3bda5e639adfde6d", 1880 1336 "type": "github" 1881 1337 }, 1882 1338 "original": { ··· 1893 1349 "base16-helix": "base16-helix", 1894 1350 "base16-vim": "base16-vim", 1895 1351 "firefox-gnome-theme": "firefox-gnome-theme", 1896 - "flake-parts": "flake-parts_7", 1352 + "flake-parts": "flake-parts_6", 1897 1353 "gnome-shell": "gnome-shell", 1898 1354 "nixpkgs": [ 1899 1355 "nixpkgs" 1900 1356 ], 1901 1357 "nur": "nur_2", 1902 - "systems": "systems_6", 1358 + "systems": "systems_5", 1903 1359 "tinted-foot": "tinted-foot", 1904 1360 "tinted-kitty": "tinted-kitty", 1905 1361 "tinted-schemes": "tinted-schemes", ··· 1907 1363 "tinted-zed": "tinted-zed" 1908 1364 }, 1909 1365 "locked": { 1910 - "lastModified": 1770914701, 1911 - "narHash": "sha256-QHFYyngohNhih4w+3IqQty5DV+p1txsx1kkk6XJWar8=", 1366 + "lastModified": 1771626923, 1367 + "narHash": "sha256-Mn6oeKrY+Sw6kH0jK+hp5QQH4MTcqwBRQL/ScZDNcz8=", 1912 1368 "owner": "danth", 1913 1369 "repo": "stylix", 1914 - "rev": "db03fed72e5ca02be34e1d24789345a943329738", 1370 + "rev": "b09847414b50c65788936199918272377f70fb91", 1915 1371 "type": "github" 1916 1372 }, 1917 1373 "original": { ··· 1937 1393 }, 1938 1394 "systems_2": { 1939 1395 "locked": { 1940 - "lastModified": 1689347949, 1941 - "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", 1942 - "owner": "nix-systems", 1943 - "repo": "default-linux", 1944 - "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", 1945 - "type": "github" 1946 - }, 1947 - "original": { 1948 - "owner": "nix-systems", 1949 - "repo": "default-linux", 1396 + "lastModified": 1681028828, 1397 + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 1398 + "owner": "nix-systems", 1399 + "repo": "default", 1400 + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 1401 + "type": "github" 1402 + }, 1403 + "original": { 1404 + "owner": "nix-systems", 1405 + "repo": "default", 1950 1406 "type": "github" 1951 1407 } 1952 1408 }, ··· 1996 1452 } 1997 1453 }, 1998 1454 "systems_6": { 1999 - "locked": { 2000 - "lastModified": 1681028828, 2001 - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 2002 - "owner": "nix-systems", 2003 - "repo": "default", 2004 - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 2005 - "type": "github" 2006 - }, 2007 - "original": { 2008 - "owner": "nix-systems", 2009 - "repo": "default", 2010 - "type": "github" 2011 - } 2012 - }, 2013 - "systems_7": { 2014 1455 "locked": { 2015 1456 "lastModified": 1689347949, 2016 1457 "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", ··· 2150 1591 }, 2151 1592 "unstable": { 2152 1593 "locked": { 2153 - "lastModified": 1770841267, 2154 - "narHash": "sha256-9xejG0KoqsoKEGp2kVbXRlEYtFFcDTHjidiuX8hGO44=", 1594 + "lastModified": 1771369470, 1595 + "narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=", 2155 1596 "owner": "NixOS", 2156 1597 "repo": "nixpkgs", 2157 - "rev": "ec7c70d12ce2fc37cb92aff673dcdca89d187bae", 1598 + "rev": "0182a361324364ae3f436a63005877674cf45efb", 2158 1599 "type": "github" 2159 1600 }, 2160 1601 "original": { ··· 2166 1607 }, 2167 1608 "wayland-pipewire-idle-inhibit": { 2168 1609 "inputs": { 2169 - "flake-parts": "flake-parts_8", 1610 + "flake-parts": "flake-parts_7", 2170 1611 "nixpkgs": [ 2171 1612 "nixpkgs" 2172 1613 ], 2173 - "systems": "systems_7", 1614 + "systems": "systems_6", 2174 1615 "treefmt-nix": "treefmt-nix_2" 2175 1616 }, 2176 1617 "locked": { ··· 2184 1625 "original": { 2185 1626 "owner": "rafaelrc7", 2186 1627 "repo": "wayland-pipewire-idle-inhibit", 2187 - "type": "github" 2188 - } 2189 - }, 2190 - "xdph": { 2191 - "inputs": { 2192 - "hyprland-protocols": [ 2193 - "hyprland", 2194 - "hyprland-protocols" 2195 - ], 2196 - "hyprlang": [ 2197 - "hyprland", 2198 - "hyprlang" 2199 - ], 2200 - "hyprutils": [ 2201 - "hyprland", 2202 - "hyprutils" 2203 - ], 2204 - "hyprwayland-scanner": [ 2205 - "hyprland", 2206 - "hyprwayland-scanner" 2207 - ], 2208 - "nixpkgs": [ 2209 - "hyprland", 2210 - "nixpkgs" 2211 - ], 2212 - "systems": [ 2213 - "hyprland", 2214 - "systems" 2215 - ] 2216 - }, 2217 - "locked": { 2218 - "lastModified": 1761431178, 2219 - "narHash": "sha256-xzjC1CV3+wpUQKNF+GnadnkeGUCJX+vgaWIZsnz9tzI=", 2220 - "owner": "hyprwm", 2221 - "repo": "xdg-desktop-portal-hyprland", 2222 - "rev": "4b8801228ff958d028f588f0c2b911dbf32297f9", 2223 - "type": "github" 2224 - }, 2225 - "original": { 2226 - "owner": "hyprwm", 2227 - "repo": "xdg-desktop-portal-hyprland", 2228 1628 "type": "github" 2229 1629 } 2230 1630 }
+31 -24
flake.nix
··· 6 6 {inherit inputs;} 7 7 ({flake-parts-lib, ...}: let 8 8 inherit (flake-parts-lib) importApply; 9 - import' = path: import path {inherit inputs;}; 10 - importApply' = path: importApply path {inherit inputs;}; 9 + inherit (inputs.nixpkgs) lib; 10 + specialArgs = {inherit inputs lib self;}; 11 + import' = path: import path specialArgs; 12 + importApply' = path: importApply path specialArgs; 11 13 systems = ["aarch64-linux" "x86_64-linux"]; 12 14 in { 13 15 inherit systems; ··· 25 27 system, 26 28 ... 27 29 }: { 28 - _module.args.pkgs = self.legacyPackages.${system}.pkgs; 30 + _module.args.pkgs = self.lib.mkPkgs {inherit system;}; 29 31 formatter = pkgs.alejandra; 30 32 legacyPackages.pkgs = self.lib.mkPkgs { 31 33 inherit system; ··· 46 48 nixpkgs.follows = "unstable"; 47 49 stable.url = "github:NixOS/nixpkgs/nixos-25.11"; 48 50 unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; 49 - nixpkgs-schemas.url = "github:DeterminateSystems/nix-src/flake-schemas"; 50 51 nixpkgs-hare.url = "github:lpchaim/nixpkgs/update-hare"; 51 52 52 - # Home Manager 53 - home-manager = { 54 - url = "github:nix-community/home-manager"; 55 - inputs.nixpkgs.follows = "nixpkgs"; 56 - }; 57 - nixneovimplugins = { 58 - url = "github:jooooscha/nixpkgs-vim-extra-plugins"; 59 - inputs.nixpkgs.follows = "nixpkgs"; 60 - }; 61 - nixvim = { 62 - url = "github:nix-community/nixvim"; 63 - inputs.nixpkgs.follows = "nixpkgs"; 64 - }; 65 - 66 53 # Hyprland 67 54 dms = { 68 55 url = "github:AvengeMedia/DankMaterialShell/stable"; ··· 72 59 url = "github:caelestia-dots/shell"; 73 60 inputs.nixpkgs.follows = "nixpkgs"; 74 61 }; 75 - hyprland.url = "git+https://github.com/hyprwm/Hyprland?submodules=1"; 76 62 77 63 # Misc 78 - agenix.url = "github:ryantm/agenix"; 64 + agenix = { 65 + url = "github:ryantm/agenix"; 66 + inputs.nixpkgs.follows = "nixpkgs"; 67 + }; 79 68 agenix-rekey = { 80 69 url = "github:oddlama/agenix-rekey"; 70 + inputs.nixpkgs.follows = "nixpkgs"; 71 + }; 72 + colmena = { 73 + url = "github:zhaofengli/colmena"; 81 74 inputs.nixpkgs.follows = "nixpkgs"; 82 75 }; 83 76 disko = { ··· 92 85 flake-parts.url = "github:hercules-ci/flake-parts"; 93 86 flake-schemas.url = "github:DeterminateSystems/flake-schemas"; 94 87 git-hooks-nix.url = "github:cachix/git-hooks.nix"; 95 - haumea = { 96 - url = "github:nix-community/haumea/v0.2.2"; 88 + home-manager = { 89 + url = "github:nix-community/home-manager"; 90 + inputs.nixpkgs.follows = "nixpkgs"; 91 + }; 92 + jovian = { 93 + url = "github:Jovian-Experiments/Jovian-NixOS"; 97 94 inputs.nixpkgs.follows = "nixpkgs"; 98 95 }; 99 - jovian.url = "github:Jovian-Experiments/Jovian-NixOS"; 100 96 lanzaboote = { 101 97 url = "github:nix-community/lanzaboote"; 102 98 inputs.nixpkgs.follows = "nixpkgs"; ··· 109 105 inputs.nixpkgs.follows = "nixpkgs"; 110 106 }; 111 107 nix-std.url = "github:chessai/nix-std"; 112 - nur.url = "github:nix-community/NUR"; 108 + nixneovimplugins = { 109 + url = "github:jooooscha/nixpkgs-vim-extra-plugins"; 110 + inputs.nixpkgs.follows = "nixpkgs"; 111 + }; 112 + nixvim = { 113 + url = "github:nix-community/nixvim"; 114 + inputs.nixpkgs.follows = "nixpkgs"; 115 + }; 116 + nur = { 117 + url = "github:nix-community/NUR"; 118 + inputs.nixpkgs.follows = "nixpkgs"; 119 + }; 113 120 spicetify-nix = { 114 121 url = "github:Gerg-L/spicetify-nix"; 115 122 inputs.nixpkgs.follows = "nixpkgs";
+7 -35
nix/apps/assets.nix
··· 1 1 {inputs, ...}: let 2 2 inherit (inputs) self; 3 3 in { 4 - perSystem = { 5 - inputs', 6 - lib, 7 - pkgs, 8 - ... 9 - }: { 10 - apps = let 11 - inherit (inputs'.nixpkgs-schemas.packages) nix; 12 - assetsRoot = "./assets/readme"; 13 - in { 14 - generate-assets = { 15 - meta.description = "Creates README.md assets"; 16 - program = pkgs.writeShellScriptBin "generate-assets" '' 17 - set -e 18 - 19 - ${lib.getExe pkgs.eza} --tree --level 2 ./nix \ 20 - | tee /dev/tty \ 21 - > '${assetsRoot}/filestructure.txt' 22 - 23 - ${lib.getExe nix} flake show --all-systems . \ 24 - --experimental-features 'flakes nix-command pipe-operators' \ 25 - | ${lib.getExe pkgs.ansifilter} \ 26 - | tee /dev/tty \ 27 - > '${assetsRoot}/outputs.txt' 28 - ''; 29 - }; 30 - render-readme = { 31 - meta.description = "Renders README.md"; 32 - program = pkgs.writeShellScriptBin "render-readme" '' 33 - export filestructure=$(cat '${assetsRoot}/filestructure.txt') 34 - export outputs=$(cat '${assetsRoot}/outputs.txt') 35 - export sampleconfig=$(cat '${self}/nix/nixos/configs/desktop/default.nix') 36 - envsubst < ./assets/README.md > ./README.md 37 - ''; 38 - }; 4 + perSystem = {pkgs, ...}: { 5 + apps.render-readme = { 6 + meta.description = "Renders README.md"; 7 + program = pkgs.writeShellScriptBin "render-readme" '' 8 + export sampleconfig=$(cat '${self}/nix/nixos/configs/desktop/default.nix') 9 + envsubst < ./assets/README.md > ./README.md 10 + ''; 39 11 }; 40 12 }; 41 13 }
+1 -1
nix/apps/ci.nix
··· 15 15 --branch: string # Only include the specified branch 16 16 --flatten # Output a single list 17 17 ]: nothing -> string { 18 - open "${self'.legacyPackages.ciMatrix}" 18 + open "${self'.legacyPackages.ci.matrix}" 19 19 | from json 20 20 | if $system != all { 21 21 filter-records { where system == $system }
+9 -2
nix/flakeModules/agenixRekey.nix
··· 1 - {inputs, ...}: { 1 + { 2 + inputs, 3 + self, 4 + ... 5 + }: let 6 + inherit (self.lib) getStandaloneHomeConfigurations; 7 + in { 2 8 imports = [ 3 9 inputs.agenix-rekey.flakeModule 4 10 ]; 5 11 6 12 perSystem = {pkgs, ...}: { 7 13 agenix-rekey = { 8 - inherit (inputs.self) homeConfigurations nixosConfigurations; 14 + inherit (self) nixosConfigurations; 9 15 agePackage = pkgs.rage; 16 + homeConfigurations = getStandaloneHomeConfigurations self; 10 17 }; 11 18 }; 12 19 }
+42
nix/flakeModules/colmena.nix
··· 1 + # Adapted from https://github.com/juspay/colmena-flake 2 + { 3 + inputs, 4 + lib, 5 + self, 6 + withSystem, 7 + ... 8 + }: { 9 + flake = withSystem "x86_64-linux" ({system, ...}: let 10 + nixosConfigurations = 11 + self.nixosConfigurations 12 + |> lib.filterAttrs (_: val: val.config.my.deploy.enable); 13 + hostConfigs = 14 + nixosConfigurations 15 + |> builtins.mapAttrs (name: value: { 16 + imports = 17 + value._module.args.modules 18 + ++ [ 19 + ({config, ...}: { 20 + deployment = let 21 + defaultNames = builtins.attrNames colmenaConfig.defaults.deployment; 22 + namesToIgnore = defaultNames ++ ["enable"]; 23 + in 24 + removeAttrs config.my.deploy namesToIgnore; 25 + }) 26 + ]; 27 + }); 28 + colmenaConfig = { 29 + meta = { 30 + nixpkgs = self.lib.mkPkgs {inherit system;}; 31 + nodeSpecialArgs = 32 + nixosConfigurations 33 + |> builtins.mapAttrs (_: value: value._module.specialArgs); 34 + }; 35 + defaults.deployment = { 36 + allowLocalDeployment = true; 37 + }; 38 + }; 39 + in { 40 + colmenaHive = inputs.colmena.lib.makeHive (colmenaConfig // hostConfigs); 41 + }); 42 + }
+1
nix/flakeModules/default.nix
··· 1 1 {...}: { 2 2 imports = [ 3 3 ./agenixRekey.nix 4 + ./colmena.nix 4 5 ./ezConfigs.nix 5 6 ./gitHooks.nix 6 7 ];
-8
nix/flakeModules/just.nix
··· 1 - {inputs, ...}: let 2 - inherit (inputs) self; 3 - inherit (inputs.nixpkgs) lib; 4 - in { 5 - imports = [ 6 - inputs.ez-configs.flakeModule 7 - ]; 8 - }
+5 -6
nix/home/modules/default.nix
··· 2 2 inputs, 3 3 lib, 4 4 ... 5 - }: let 6 - inherit (inputs) self; 7 - in { 5 + }: { 8 6 imports = 9 7 (with inputs; [ 10 8 agenix.homeManagerModules.default 11 - (agenix-rekey.homeManagerModules.default // {_class = "homeManager";}) # Don't ask 9 + agenix-rekey.homeManagerModules.default 12 10 caelestia.homeManagerModules.default 13 11 dms.homeModules.dank-material-shell 14 12 nix-index-database.homeModules.nix-index ··· 19 17 wayland-pipewire-idle-inhibit.homeModules.default 20 18 ]) 21 19 ++ [ 22 - "${self}/nix/shared" 20 + ../../shared 21 + ../profiles 23 22 ./bars 24 23 ./ci 25 24 ./cli 26 25 ./de 27 26 ./development 28 27 ./gui 28 + ./llm 29 29 ./misc 30 30 ./nix 31 31 ./scripts ··· 35 35 ./syncthing 36 36 ./theming 37 37 ./wayland 38 - ../profiles 39 38 ]; 40 39 41 40 my = {
+2 -2
nix/home/modules/development/nixd.nix
··· 15 15 16 16 config = lib.mkMerge [ 17 17 (lib.mkIf cfg.enable { 18 - home.packages = [pkgs.nixd-lix]; 18 + home.packages = [pkgs.nixd]; 19 19 }) 20 20 (lib.mkIf (cfg.lsp.enable && config.programs.helix.enable) { 21 21 programs.helix = { ··· 27 27 } 28 28 ]; 29 29 language-server.nixd = { 30 - command = "${lib.getExe pkgs.nixd-lix}"; 30 + command = "${lib.getExe pkgs.nixd}"; 31 31 args = ["--semantic-tokens=true"]; 32 32 config.nixd = let 33 33 inherit (config.my.config) flake;
+7 -3
nix/home/modules/misc/default.nix
··· 1 1 { 2 - imports = [ 3 - ./llm 4 - ]; 2 + lib, 3 + osConfig ? {}, 4 + ... 5 + }: { 6 + options.my.deprecated = 7 + lib.mkEnableOption "deprecation marker" 8 + // {default = osConfig.my.deprecated or false;}; 5 9 }
+2 -2
nix/home/modules/misc/llm/default.nix nix/home/modules/llm/default.nix
··· 8 8 inherit (inputs.home-manager.lib) hm; 9 9 inherit (inputs.nix-std.lib) serde; 10 10 inherit (lib) mkIf mkEnableOption mkOption; 11 - cfg = config.my.misc.llm; 11 + cfg = config.my.llm; 12 12 in { 13 - options.my.misc.llm = { 13 + options.my.llm = { 14 14 enable = mkEnableOption "LLM tools"; 15 15 defaultModel = mkOption { 16 16 description = "Which model to use by default";
+1 -1
nix/home/profiles/llm/high.nix
··· 6 6 cfg = config.my.profiles.llm.high; 7 7 in { 8 8 options.my.profiles.llm.high = lib.mkEnableOption "High LLM preset profile"; 9 - config.my.misc.llm = lib.mkIf cfg { 9 + config.my.llm = lib.mkIf cfg { 10 10 enable = true; 11 11 defaultModel = "llama3"; 12 12 };
+1 -1
nix/home/profiles/llm/low.nix
··· 6 6 cfg = config.my.profiles.llm.low; 7 7 in { 8 8 options.my.profiles.llm.low = lib.mkEnableOption "Low LLM preset profile"; 9 - config.my.misc.llm = lib.mkIf cfg { 9 + config.my.llm = lib.mkIf cfg { 10 10 enable = true; 11 11 defaultModel = "tinyllama"; 12 12 };
+1 -1
nix/home/profiles/llm/mid.nix
··· 6 6 cfg = config.my.profiles.llm.mid; 7 7 in { 8 8 options.my.profiles.llm.mid = lib.mkEnableOption "Mid LLM preset profile"; 9 - config.my.misc.llm = lib.mkIf cfg { 9 + config.my.llm = lib.mkIf cfg { 10 10 enable = true; 11 11 defaultModel = "phi2"; 12 12 };
+68 -73
nix/legacyPackages/ciMatrix.nix
··· 1 - {inputs, ...}: let 2 - inherit (inputs) self; 1 + { 2 + lib, 3 + self, 4 + writeText, 5 + ... 6 + }: let 3 7 inherit (self) systems; 4 - in { 5 - perSystem = { 6 - lib, 7 - pkgs, 8 - ... 9 - }: let 10 - getOutputInfo = mkDerivationPath: output: 11 - lib.mapAttrsToList (name: drv: { 12 - inherit name; 13 - derivation = 14 - name 15 - |> lib.strings.escapeNixIdentifier 16 - |> mkDerivationPath 17 - |> lib.escapeShellArg; 18 - system = drv.system or drv.pkgs.stdenv.hostPlatform.system; 19 - branch = drv.config.my.ci.branches or []; 20 - }) 21 - output; 22 - getPerSystemOutputInfo = mkDerivationPath: output: 23 - lib.concatMap (system: getOutputInfo (mkDerivationPath system) output.${system}) 24 - systems; 25 - filterToBuild = lib.filterAttrs (_: drv: drv.config.my.ci.build or false); 26 - filterPerSystemToBuild = lib.filterAttrsRecursive ( 27 - name: drv: 28 - !(lib.isDerivation drv) 29 - || ( 30 - (name != "default") 31 - && ( 32 - ((drv.passthru.my.ci.build or false) == true) 33 - || (builtins.elem drv.system (drv.passthru.my.ci.buildFor or [])) 34 - ) 8 + inherit (self.lib) getStandaloneHomeConfigurations; 9 + getOutputInfo = mkDerivationPath: output: 10 + lib.mapAttrsToList (name: drv: { 11 + inherit name; 12 + derivation = 13 + name 14 + |> lib.strings.escapeNixIdentifier 15 + |> mkDerivationPath 16 + |> lib.escapeShellArg; 17 + system = drv.system or drv.pkgs.stdenv.hostPlatform.system; 18 + branch = drv.config.my.ci.branches or []; 19 + }) 20 + output; 21 + getPerSystemOutputInfo = mkDerivationPath: output: 22 + lib.concatMap (system: getOutputInfo (mkDerivationPath system) output.${system}) 23 + systems; 24 + filterToBuild = lib.filterAttrs (_: drv: drv.config.my.ci.build or false); 25 + filterPerSystemToBuild = lib.filterAttrsRecursive ( 26 + name: drv: 27 + !(lib.isDerivation drv) 28 + || ( 29 + (name != "default") 30 + && ( 31 + ((drv.passthru.my.ci.build or false) == true) 32 + || (builtins.elem drv.system (drv.passthru.my.ci.buildFor or [])) 35 33 ) 36 - ); 37 - spreadBranchOrDefault = defaultBranches: infos: 38 - lib.concatMap 39 - (info: (lib.concatMap 40 - (branch: [(info // {inherit branch;})]) 41 - ( 42 - if (builtins.length info.branch > 0) 43 - then info.branch 44 - else defaultBranches 45 - ))) 46 - infos; 47 - ciInfo = { 48 - homeConfigurations = 49 - self.homeConfigurations 50 - |> lib.filterAttrs (_: home: home._module.specialArgs.osConfig == {}) # Standalone only 51 - |> filterToBuild 52 - |> getOutputInfo (name: ".#homeConfigurations.${name}.activationPackage") 53 - |> spreadBranchOrDefault []; 54 - nixosConfigurations = 55 - self.nixosConfigurations 56 - |> filterToBuild 57 - |> getOutputInfo (name: ".#nixosConfigurations.${name}.config.system.build.toplevel") 58 - |> spreadBranchOrDefault []; 59 - packages = 60 - self.packages 61 - |> filterPerSystemToBuild 62 - |> getPerSystemOutputInfo (system: name: ".#packages.${system}.${name}") 63 - |> spreadBranchOrDefault ["main" "develop"]; 64 - devShells = 65 - self.devShells 66 - |> filterPerSystemToBuild 67 - |> getPerSystemOutputInfo (system: name: ".#devShells.${system}.${name}") 68 - |> spreadBranchOrDefault ["main" "develop"]; 69 - }; 70 - in { 71 - legacyPackages.ciMatrix = 72 - ciInfo 73 - |> builtins.toJSON 74 - |> pkgs.writeText "ci-matrix"; 34 + ) 35 + ); 36 + spreadBranchOrDefault = defaultBranches: infos: 37 + lib.concatMap 38 + (info: (lib.concatMap 39 + (branch: [(info // {inherit branch;})]) 40 + ( 41 + if (builtins.length info.branch > 0) 42 + then info.branch 43 + else defaultBranches 44 + ))) 45 + infos; 46 + ciInfo = { 47 + homeConfigurations = 48 + (getStandaloneHomeConfigurations self) 49 + |> filterToBuild 50 + |> getOutputInfo (name: ".#homeConfigurations.${name}.activationPackage") 51 + |> spreadBranchOrDefault []; 52 + nixosConfigurations = 53 + self.nixosConfigurations 54 + |> filterToBuild 55 + |> getOutputInfo (name: ".#nixosConfigurations.${name}.config.system.build.toplevel") 56 + |> spreadBranchOrDefault []; 57 + packages = 58 + self.packages 59 + |> filterPerSystemToBuild 60 + |> getPerSystemOutputInfo (system: name: ".#packages.${system}.${name}") 61 + |> spreadBranchOrDefault ["main" "develop"]; 62 + devShells = 63 + self.devShells 64 + |> filterPerSystemToBuild 65 + |> getPerSystemOutputInfo (system: name: ".#devShells.${system}.${name}") 66 + |> spreadBranchOrDefault ["main" "develop"]; 75 67 }; 76 - } 68 + in 69 + ciInfo 70 + |> builtins.toJSON 71 + |> writeText "ci-matrix"
+16 -5
nix/legacyPackages/default.nix
··· 1 - args: { 2 - imports = [ 3 - ./ciMatrix.nix 4 - ./scripts 5 - ]; 1 + {inputs, ...} @ args: let 2 + inherit (inputs.self.lib) callPackageWith callPackageRecursiveWith; 3 + in { 4 + perSystem = { 5 + self', 6 + pkgs, 7 + ... 8 + }: let 9 + callPackage = callPackageWith pkgs; 10 + callPackageRecursive = callPackageRecursiveWith pkgs; 11 + in { 12 + legacyPackages = { 13 + ci.matrix = callPackage ./ciMatrix.nix {inherit (args.inputs) self;}; 14 + scripts = callPackageRecursive ./scripts {inherit (self'.legacyPackages.pkgs) writeNuScriptStdinBin;}; 15 + }; 16 + }; 6 17 }
-18
nix/legacyPackages/scripts/default.nix
··· 1 - args: let 2 - inherit ((import ../../lib args).loaders) loadNonDefault; 3 - in { 4 - # For reasons beyond my understanding, removing an argument from the attribute 5 - # set here stops it from propagating to the loaded files even if the whole 6 - # systemArgs is used as an argument 7 - perSystem = { 8 - self', 9 - pkgs, 10 - ... 11 - } @ systemArgs: { 12 - legacyPackages = let 13 - scripts = loadNonDefault ./. systemArgs; 14 - in 15 - scripts # Provide them at the top level as well so they're more convenient to run 16 - // {inherit scripts;}; 17 - }; 18 - }
+7 -3
nix/legacyPackages/scripts/lastdl.nix
··· 1 - {pkgs, ...}: 2 - pkgs.writeNuScriptStdinBin "lastdl" 1 + { 2 + writeNuScriptStdinBin, 3 + xdg-user-dirs, 4 + ... 5 + }: 6 + writeNuScriptStdinBin "lastdl" 3 7 # nu 4 8 '' 5 9 # Print the last downloaded file ··· 7 11 --short # Print the filename instead of full path 8 12 --quote # Print the raw path with added quotes 9 13 ]: nothing -> string { 10 - ls (${pkgs.xdg-user-dirs}/bin/xdg-user-dir DOWNLOAD) 14 + ls (${xdg-user-dirs}/bin/xdg-user-dir DOWNLOAD) 11 15 | where type == file 12 16 | sort-by modified 13 17 | last
+2 -2
nix/legacyPackages/scripts/leastspaces.nix
··· 1 - {pkgs, ...}: 2 - pkgs.writeNuScriptStdinBin "leastspaces" 1 + {writeNuScriptStdinBin, ...}: 2 + writeNuScriptStdinBin "leastspaces" 3 3 # nu 4 4 '' 5 5 # Computes the least prepending spaces present in any line of the supplied text
+2 -2
nix/legacyPackages/scripts/nu-generate-carapace-spec.nix
··· 1 - {pkgs, ...}: 2 - pkgs.writeNuScriptStdinBin "nu-generate-carapace-spec" 1 + {writeNuScriptStdinBin, ...}: 2 + writeNuScriptStdinBin "nu-generate-carapace-spec" 3 3 # nu 4 4 '' 5 5 # Generates carapace completion spec based on structured command metadata
-33
nix/legacyPackages/scripts/nu-generate-manpage.nix
··· 1 - { 2 - self', 3 - lib, 4 - pkgs, 5 - ... 6 - }: let 7 - inherit (self'.legacyPackages.scripts) nu-parse-help; 8 - in 9 - pkgs.writeNuScriptStdinBin "nu-generate-manpage" 10 - # nu 11 - '' 12 - # Converts the output of nu --help to a crude manpage 13 - def main []: string -> any { 14 - let sections = $in 15 - | ${lib.getExe nu-parse-help} 16 - | from json 17 - let name = [$sections.name $sections.description] 18 - | filter { $in != "" } 19 - | str join " - " 20 - let flags = $sections.flags 21 - | each { $"\t($in.switches)\n\t\t($in.description)" } 22 - | str join "\n"; 23 - 24 - $"($sections.name | str upcase)\(1) 25 - 26 - NAME 27 - \t($name) 28 - 29 - OPTIONS 30 - ($flags) 31 - " 32 - } 33 - ''
+4 -3
nix/legacyPackages/scripts/nu-inspect.nix
··· 1 1 { 2 2 lib, 3 - pkgs, 3 + nushell, 4 + writeNuScriptStdinBin, 4 5 ... 5 6 }: 6 - pkgs.writeNuScriptStdinBin "nu-inspect" 7 + writeNuScriptStdinBin "nu-inspect" 7 8 # nu 8 9 '' 9 10 # Outputs structured command data based on nushell script contents ··· 11 12 --name: string = "main" 12 13 ]: string -> string { 13 14 $in | save ./tmp.nu 14 - ${lib.getExe pkgs.nushell} --commands $" 15 + ${lib.getExe nushell} --commands $" 15 16 source './tmp.nu' 16 17 17 18 help commands
-66
nix/legacyPackages/scripts/nu-parse-help.nix
··· 1 - { 2 - self', 3 - lib, 4 - pkgs, 5 - ... 6 - }: let 7 - inherit (self'.legacyPackages.scripts) leastspaces; 8 - in 9 - pkgs.writeNuScriptStdinBin "nu-parse-help" 10 - # nu 11 - '' 12 - # Creates a record representation of a command's --help output 13 - def main []: string -> any { 14 - let sections = $in 15 - | split row --regex "\n\n" 16 - | where { str trim | $in != "" } 17 - | each { 18 - parse --regex '^(?<header>[^:]+:)?\n?(?<contents>.*(?:\n.*)*)?' 19 - | update header { default "" | str replace --regex ':$' "" | str downcase } 20 - | update contents { ${lib.getExe leastspaces} } 21 - } 22 - | where { $in.header != "" or $in.content != "" } 23 - | flatten 24 - let description = $sections 25 - | where header == "" 26 - | first 27 - | safeget contents "" 28 - let usage = $sections 29 - | where header =~ usage 30 - | first 31 - | safeget contents "" 32 - | str substring 2.. 33 - let name = $usage 34 - | parse --regex '(?<name>\w*) .*' 35 - | first 36 - | safeget name "" 37 - let flags = $sections 38 - | where header =~ flags 39 - | first 40 - | safeget contents "" 41 - | split row "\n" 42 - | each { 43 - split row ": " 44 - | { 45 - switches: $in.0 46 - description: $in.1 47 - }} 48 - 49 - { 50 - name: $name 51 - description: $description 52 - usage: $usage 53 - flags: $flags 54 - } 55 - | to json 56 - } 57 - 58 - def safeget [ 59 - field: string 60 - default: string 61 - ]: record -> string { 62 - $in 63 - | get --ignore-errors $field 64 - | default $default 65 - } 66 - ''
+26 -18
nix/lib/default.nix
··· 1 - {inputs, ...} @ topLevelArgs: let 2 - inherit (inputs.nixpkgs) lib; 3 - args = topLevelArgs // {inherit lib;}; 4 - overlays = builtins.attrValues inputs.self.overlays; 5 - in { 1 + { 2 + inputs, 3 + lib, 4 + self, 5 + ... 6 + } @ args: { 6 7 config = import ./config.nix args; 7 - loaders = import ./loaders.nix args; 8 + flake = import ./flake.nix args; 9 + packages = import ./packages.nix args; 8 10 secrets = import ./secrets.nix; 11 + services = import ./services.nix args; 9 12 storage = import ./storage args; 10 13 strings = import ./strings.nix args; 11 14 12 - mkPkgs = { 13 - system, 14 - nixpkgs ? inputs.nixpkgs, 15 - }: 16 - import nixpkgs { 17 - inherit system overlays; 18 - allowUnfree = true; 19 - }; 20 - isNvidia = config: let 21 - drivers = config.services.xserver.videoDrivers or []; 22 - in 23 - builtins.elem "nvidia" drivers; 15 + inherit 16 + (self.lib.flake) 17 + getStandaloneHomeConfigurations 18 + ; 19 + inherit 20 + (self.lib.packages) 21 + callPackageRecursiveWith 22 + callPackageWith 23 + mkPkgs 24 + ; 25 + 24 26 carapaceSpecFromNuScript = script: let 25 27 inherit (script) name system; 26 28 inherit (inputs.self.legacyPackages.${system}) scripts pkgs; ··· 39 41 | nu-generate-carapace-spec \ 40 42 > $out 41 43 ''; 44 + nixFilesToAttrs = path: 45 + builtins.readDir path 46 + |> lib.filterAttrs (name: type: type == "regular" && lib.hasSuffix ".nix" name && name != "default.nix") 47 + |> lib.concatMapAttrs (relativePath: _: { 48 + ${relativePath |> lib.removeSuffix ".nix"} = path + /${relativePath}; 49 + }); 42 50 }
+7
nix/lib/flake.nix
··· 1 + {lib, ...}: rec { 2 + getStandaloneHomeConfigurations = self: 3 + self.homeConfigurations 4 + |> lib.filterAttrs (name: _: isStandaloneHome self.nixosConfigurations name); 5 + isStandaloneHome = nixosConfigurations: name: 6 + !(nixosConfigurations ? ${name |> lib.splitString "@" |> lib.last}); 7 + }
-150
nix/lib/loaders.nix
··· 1 - { 2 - inputs, 3 - lib, 4 - ... 5 - }: rec { 6 - # Reads files similarly to builtins.readDir 7 - read = { 8 - path, 9 - filterFn ? (x: true), 10 - recursive ? false, 11 - }: 12 - path 13 - |> (path: 14 - if recursive 15 - then 16 - (inputs.haumea.lib.load { 17 - src = path; 18 - loader = inputs.haumea.lib.loaders.path; 19 - }) 20 - else 21 - ( 22 - path 23 - |> builtins.readDir 24 - |> builtins.mapAttrs (name: type: 25 - if (type == "directory" && builtins.pathExists (path + /${name}/default.nix)) 26 - then (path + /${name}/default.nix) 27 - else (path + /${name})) 28 - |> lib.concatMapAttrs (name: path: {${lib.removeSuffix ".nix" name} = path;}) 29 - )) 30 - |> (attr: removeAttrs attr ["default" "default.nix"]) 31 - |> lib.filterAttrsRecursive (_: path: filterFn path); 32 - 33 - # Lists files as paths 34 - list = { 35 - path, 36 - filterFn ? (x: true), 37 - recursive ? false, 38 - }: 39 - read {inherit path recursive;} 40 - |> lib.collect builtins.isPath 41 - |> builtins.filter (path: 42 - path 43 - |> toString 44 - |> filterFn) 45 - |> map ( 46 - path: 47 - path 48 - |> toString 49 - |> lib.removeSuffix "/default.nix" 50 - |> (x: /. + x) 51 - ); 52 - 53 - # Lists files ending in default.nix 54 - listDefault = path: 55 - list { 56 - inherit path; 57 - filterFn = lib.hasSuffix "default.nix"; 58 - }; 59 - 60 - # Lists files ending in default.nix recursively 61 - listDefaultRecursive = path: 62 - list { 63 - inherit path; 64 - filterFn = lib.hasSuffix "default.nix"; 65 - recursive = true; 66 - }; 67 - 68 - # Lists files not ending in default.nix 69 - listNonDefault = path: 70 - list { 71 - inherit path; 72 - filterFn = path: ! (lib.hasSuffix "default.nix" path); 73 - }; 74 - 75 - # Lists files not ending in default.nix 76 - listNonDefaultRecursive = path: 77 - list { 78 - inherit path; 79 - filterFn = path: ! (lib.hasSuffix "default.nix" path); 80 - recursive = true; 81 - }; 82 - 83 - # Imports modules ending in default.nix 84 - importDefault = path: args: 85 - path 86 - |> listDefault 87 - |> map (path: import path args); 88 - 89 - # Imports modules not ending in default.nix 90 - importNonDefault = path: args: 91 - path 92 - |> listNonDefault 93 - |> map (path: import path args); 94 - 95 - # Loads modules while preserving directory structure 96 - load = { 97 - path, 98 - args, 99 - filterFn ? (x: true), 100 - }: 101 - read { 102 - inherit path filterFn; 103 - recursive = true; 104 - } 105 - |> lib.mapAttrsRecursiveCond 106 - builtins.isAttrs 107 - (_: x: let 108 - mod = import x; 109 - in 110 - if builtins.isAttrs mod 111 - then mod 112 - else mod args) 113 - |> (attr: removeAttrs attr ["default"]); 114 - 115 - # Loads files ending in default.nix while preserving directory structure 116 - loadDefault = path: args: 117 - load { 118 - inherit path args; 119 - filterFn = lib.hasSuffix "default.nix"; 120 - }; 121 - 122 - # Loads files not ending in default.nix while preserving directory structure 123 - loadNonDefault = path: args: 124 - load { 125 - inherit path args; 126 - filterFn = path: ! (lib.hasSuffix "default.nix" path); 127 - }; 128 - 129 - # Runs callPackage on files ending in default.nix, always recursive 130 - callPackageDefault = path: pkgs: 131 - path 132 - |> (path: 133 - read { 134 - inherit path; 135 - filterFn = lib.hasSuffix "default.nix"; 136 - }) 137 - |> builtins.mapAttrs (_: path: 138 - lib.callPackageWith (removeAttrs pkgs ["root"]) path {}); 139 - 140 - # Runs callPackage on files not ending in default.nix, always recursive 141 - callPackageNonDefault = path: pkgs: 142 - path 143 - |> (path: 144 - read { 145 - inherit path; 146 - filterFn = path: ! (lib.hasSuffix "default.nix" path); 147 - }) 148 - |> builtins.mapAttrs (_: path: 149 - lib.callPackageWith (removeAttrs pkgs ["root"]) path {}); 150 - }
+33
nix/lib/packages.nix
··· 1 + { 2 + inputs, 3 + lib, 4 + ... 5 + }: let 6 + overlays = builtins.attrValues inputs.self.overlays; 7 + in { 8 + callPackageRecursiveWith = pkgs: path: extraArgs: 9 + lib.packagesFromDirectoryRecursive { 10 + callPackage = lib.callPackageWith (pkgs // extraArgs); 11 + directory = path; 12 + } 13 + |> lib.filterAttrsRecursive (name: _: name != "default"); 14 + callPackageWith = pkgs: path: extraArgs: 15 + lib.callPackageWith 16 + pkgs 17 + ( 18 + if (lib.pathIsDirectory path && builtins.pathExists "${path}/package.nix") 19 + then "${path}/package.nix" 20 + else if (lib.pathIsDirectory path && builtins.pathExists "${path}/default.nix") 21 + then "${path}/default.nix" 22 + else path 23 + ) 24 + extraArgs; 25 + mkPkgs = { 26 + system, 27 + nixpkgs ? inputs.nixpkgs, 28 + }: 29 + import nixpkgs { 30 + inherit system overlays; 31 + allowUnfree = true; 32 + }; 33 + }
+22
nix/lib/services.nix
··· 1 + {lib, ...}: { 2 + mkOneShotBootService = 3 + lib.recursiveUpdate 4 + { 5 + serviceConfig.Type = "oneshot"; 6 + wantedBy = ["default.target"]; 7 + }; 8 + mkOneShotSleepService = 9 + lib.recursiveUpdate 10 + { 11 + unitConfig = { 12 + DefaultDependencies = "no"; 13 + StopWhenUnneeded = "yes"; 14 + }; 15 + serviceConfig = { 16 + Type = "oneshot"; 17 + RemainAfterExit = "yes"; 18 + }; 19 + before = ["sleep.target"]; 20 + wantedBy = ["sleep.target"]; 21 + }; 22 + }
+1
nix/nixos/configs/steamdeck/default.nix
··· 14 14 ]; 15 15 16 16 my = { 17 + deprecated = true; 17 18 steamos.enable = true; 18 19 gaming.enable = false; 19 20 gaming.steam.enable = true;
+16 -17
nix/nixos/modules/default.nix
··· 3 3 lib, 4 4 pkgs, 5 5 ... 6 - }: let 7 - inherit (inputs) self; 8 - inherit (lib) mkDefault; 9 - in { 6 + }: { 10 7 imports = 11 8 (with inputs; [ 12 9 agenix.nixosModules.default ··· 21 18 stylix.nixosModules.stylix 22 19 ]) 23 20 ++ [ 24 - "${self}/nix/shared" 21 + ../../shared 22 + ../profiles 25 23 ./boot 26 24 ./ci 27 25 ./desktop 26 + ./deploy 28 27 ./gaming 29 28 ./gui 30 29 ./hardware 31 30 ./kdeconnect 32 31 ./kernel 33 32 ./locale 33 + ./misc 34 34 ./networking 35 35 ./nix 36 36 ./pipewire ··· 47 47 ./virtualization 48 48 ./wayland 49 49 ./zram 50 - ../profiles 51 50 ]; 52 51 53 52 my = { 54 - kernel.enable = mkDefault true; 55 - networking.tailscale.enable = mkDefault true; 56 - nix.enable = mkDefault true; 57 - pipewire.enable = mkDefault true; 58 - security.enable = mkDefault true; 59 - ssh.enable = mkDefault true; 60 - syncthing.enable = mkDefault true; 61 - theming.enable = mkDefault true; 62 - users.enable = mkDefault true; 63 - users.lpchaim.enable = mkDefault true; 64 - zram.enable = mkDefault true; 53 + kernel.enable = lib.mkDefault true; 54 + networking.tailscale.enable = lib.mkDefault true; 55 + nix.enable = lib.mkDefault true; 56 + pipewire.enable = lib.mkDefault true; 57 + security.enable = lib.mkDefault true; 58 + ssh.enable = lib.mkDefault true; 59 + syncthing.enable = lib.mkDefault true; 60 + theming.enable = lib.mkDefault true; 61 + users.enable = lib.mkDefault true; 62 + users.lpchaim.enable = lib.mkDefault true; 63 + zram.enable = lib.mkDefault true; 65 64 }; 66 65 67 66 environment.systemPackages = with pkgs; [
+20
nix/nixos/modules/deploy/default.nix
··· 1 + { 2 + config, 3 + inputs, 4 + lib, 5 + ... 6 + }: { 7 + options.my.deploy = let 8 + colmenaModule = inputs.colmena.nixosModules.deploymentOptions { 9 + inherit lib; 10 + name = config.networking.hostName; 11 + }; 12 + deployOptions = colmenaModule.options.deployment; 13 + in 14 + deployOptions 15 + // { 16 + enable = 17 + lib.mkEnableOption "deployment" 18 + // {default = !config.my.deprecated;}; 19 + }; 20 + }
+3
nix/nixos/modules/misc/default.nix
··· 1 + {lib, ...}: { 2 + options.my.deprecated = lib.mkEnableOption "deprecation marker"; 3 + }
+5 -3
nix/nixos/profiles/formfactor/desktop.nix
··· 1 1 # Desktop-specific configurations 2 2 { 3 3 config, 4 + inputs, 4 5 lib, 5 6 pkgs, 6 7 ... 7 8 }: let 9 + inherit (inputs.self.lib.services) mkOneShotBootService; 8 10 cfg = config.my.profiles.formfactor.desktop; 9 11 in { 10 12 options.my.profiles.formfactor.desktop = lib.mkEnableOption "desktop profile"; ··· 29 31 30 32 networking.firewall.allowedTCPPorts = [5900]; # Default VNC port 31 33 32 - powerManagement.powerUpCommands = '' 33 - ${pkgs.zsh}/bin/zsh -c "echo disabled > /sys/bus/usb/devices/*/power/wakeup" 34 - ''; 34 + systemd.services.usb-wakeup-disable = mkOneShotBootService { 35 + script = "echo disabled | tee /sys/bus/usb/devices/*/power/wakeup"; 36 + }; 35 37 }; 36 38 }
+7 -1
nix/nixos/profiles/formfactor/laptop.nix
··· 2 2 { 3 3 config, 4 4 lib, 5 + pkgs, 5 6 ... 6 7 }: let 7 8 cfg = config.my.profiles.formfactor.laptop; ··· 26 27 }; 27 28 }; 28 29 }; 29 - fprintd.enable = true; 30 + fprintd = { 31 + enable = true; 32 + package = pkgs.fprintd.override { 33 + libfprint = pkgs.libfprint-canvasbio-cb2000; 34 + }; 35 + }; 30 36 thermald.enable = true; 31 37 }; 32 38 };
+10 -7
nix/nixos/profiles/hardware/rgb.nix
··· 1 1 { 2 2 config, 3 + inputs, 3 4 lib, 4 5 pkgs, 5 6 ... 6 7 }: let 8 + inherit (inputs.self.lib.services) mkOneShotSleepService; 7 9 inherit (config.services.hardware.openrgb) package; 8 10 cfg = config.my.profiles.hardware.rgb; 9 11 in { ··· 15 17 package = pkgs.openrgb-with-all-plugins; 16 18 }; 17 19 services.udev.packages = [package]; 18 - powerManagement = { 19 - powerDownCommands = '' 20 - ${package}/bin/openrgb --color 000000 21 - ''; 22 - powerUpCommands = '' 23 - ${package}/bin/openrgb --color 000000 24 - ''; 20 + 21 + systemd.services.rgb-off = mkOneShotSleepService { 22 + serviceConfig = let 23 + command = "${package}/bin/openrgb --color 000000"; 24 + in { 25 + ExecStart = command; 26 + ExecStop = command; 27 + }; 25 28 }; 26 29 }; 27 30 }
+4 -9
nix/overlays/default.nix
··· 1 1 {inputs, ...} @ args: let 2 2 inherit (inputs.nixpkgs) lib; 3 - inherit (inputs.self.lib.loaders) loadNonDefault; 3 + inherit (inputs.self.lib) nixFilesToAttrs; 4 4 in { 5 5 flake.overlays = 6 - (loadNonDefault ./. args) 7 - // { 8 - external = lib.composeManyExtensions [ 9 - inputs.agenix-rekey.overlays.default 10 - inputs.nix-gaming.overlays.default 11 - inputs.nixneovimplugins.overlays.default 12 - ]; 13 - }; 6 + nixFilesToAttrs ./. 7 + |> lib.mapAttrs (_: path: import path args) 8 + |> (_: _ // {default = inputs.self.overlays.packages;}); 14 9 }
+7
nix/overlays/external.nix
··· 1 + {inputs, ...}: let 2 + inherit (inputs.nixpkgs) lib; 3 + in 4 + lib.composeManyExtensions [ 5 + inputs.agenix-rekey.overlays.default 6 + inputs.nix-gaming.overlays.default 7 + ]
+22 -5
nix/overlays/lix.nix
··· 1 1 # As instructed on https://lix.systems/add-to-config/ 2 - {...}: final: prev: let 2 + {inputs, ...}: final: prev: let 3 3 inherit (prev) lib; 4 + inherit (prev.stdenv.hostPlatform) system; 5 + rev = "stable"; 6 + nix = final.lixPackageSets.${rev}.lix; 4 7 in { 8 + # Workaround for infrec found here https://github.com/NixOS/nixpkgs/pull/445223#issuecomment-3330902652 9 + inherit nix; 10 + inherit (inputs.colmena.packages.${system}) colmena; 11 + nix-direnv = prev.nix-direnv.override {inherit nix;}; 5 12 inherit 6 - (prev.lixPackageSets.stable) 7 - nixpkgs-review 13 + (final.lixPackageSets.${rev}) 8 14 nix-eval-jobs 9 15 nix-fast-build 10 - colmena 16 + nixpkgs-review 17 + nix-serve-ng 11 18 ; 19 + lixPackageSets = prev.lixPackageSets.override { 20 + inherit 21 + (prev) 22 + colmena 23 + nix-direnv 24 + nix-fast-build 25 + nixpkgs-review 26 + nix-serve-ng 27 + ; 28 + }; 12 29 13 30 # Adapted from https://github.com/nix-community/nixd/issues/704#issuecomment-3688024705 14 - nixd-lix = prev.symlinkJoin { 31 + nixd = prev.symlinkJoin { 15 32 name = "nixd-lix"; 16 33 17 34 inherit (prev.nixd) meta;
+1 -2
nix/overlays/packages.nix
··· 1 1 {inputs, ...}: final: prev: let 2 - inherit (inputs) self; 3 2 inherit (prev.stdenv.hostPlatform) system; 4 3 in 5 - self.packages.${system} or {} 4 + inputs.self.packages.${system} or {}
+8 -15
nix/packages/default.nix
··· 1 - args: let 2 - inherit ((import ../lib args).loaders) callPackageDefault callPackageNonDefault; 1 + {inputs, ...}: let 2 + inherit (inputs.self.lib) callPackageWith; 3 3 in { 4 4 perSystem = { 5 5 inputs', 6 - self', 7 - lib, 6 + pkgs, 8 7 ... 9 8 }: let 10 - inherit (self'.legacyPackages) pkgs; 11 - callPackage = lib.callPackageWith pkgs; 9 + callPackage = callPackageWith pkgs; 12 10 in { 13 - packages = 14 - (callPackageDefault ./. pkgs) 15 - // (callPackageNonDefault ./. pkgs) 16 - // { 17 - lichen = 18 - callPackage 19 - ./lichen/package.nix 20 - {inherit (inputs'.nixpkgs-hare.legacyPackages) hare hareHook;}; 21 - }; 11 + packages = { 12 + libfprint-canvasbio-cb2000 = callPackage ./libfprint-canvasbio-cb2000 {}; 13 + lichen = callPackage ./lichen {inherit (inputs'.nixpkgs-hare.legacyPackages) hare hareHook;}; 14 + }; 22 15 }; 23 16 }
+5425
nix/packages/libfprint-canvasbio-cb2000/001-add-canvasbio.patch
··· 1 + diff --git a/libfprint/drivers/canvasbio_cb2000.c b/libfprint/drivers/canvasbio_cb2000.c 2 + new file mode 100644 3 + index 0000000..2492d32 4 + --- /dev/null 5 + +++ b/libfprint/drivers/canvasbio_cb2000.c 6 + @@ -0,0 +1,5353 @@ 7 + +/* 8 + + * CanvasBio CB2000 Fingerprint Reader Driver for libfprint 9 + + * 10 + + * Copyright (C) 2025 CanvasMancer Project 11 + + * Based on protocol analysis from libfprint issue #509 12 + + * https://gitlab.freedesktop.org/libfprint/libfprint/-/issues/509 13 + + * 14 + + * This library is free software; you can redistribute it and/or 15 + + * modify it under the terms of the GNU Lesser General Public 16 + + * License as published by the Free Software Foundation; either 17 + + * version 2.1 of the License, or (at your option) any later version. 18 + + * 19 + + * ============================================================================ 20 + + * DRIVER OVERVIEW 21 + + * ============================================================================ 22 + + * 23 + + * DRIVER VERSION TAG: V44 24 + + * Keep this tag updated at every iteration to make runtime/source identification 25 + + * unambiguous across logs, backups and manual syncs. 26 + + * 27 + + * Current Linux implementation uses the host-side matching path (MoH behavior): 28 + + * the sensor captures a raw grayscale image, and libfprint performs minutiae 29 + + * extraction/matching. A runtime-gated skeleton hook exists for future 30 + + * device-assisted verify experiments without changing the default behavior. 31 + + * 32 + + * Device specifications: 33 + + * - Vendor ID: 0x2DF0 (CanvasBio) 34 + + * - Product IDs: 0x0003, 0x0007 35 + + * - Image size: 80x64 pixels (5120 bytes) 36 + + * - Interface: USB bulk transfers + vendor control requests 37 + + * 38 + + * Architecture: Master cycle SSM + sub-SSMs per phase. 39 + + * First cycle does USB reset + full activation. Subsequent cycles do soft rearm: 40 + + * 41 + + * HARD_RESET -> INIT_CONFIG -> SETTLE_DELAY -> WAIT_FINGER -> 42 + + * CAPTURE_START -> READ_IMAGE -> FINALIZE -> VERIFY_RESULT_STATUS -> 43 + + * VERIFY_READY_QUERY_A -> VERIFY_READY_QUERY_B -> VERIFY_READY_DECIDE -> 44 + + * SUBMIT_IMAGE -> 45 + + * WAIT_REMOVAL -> REARM -> [next cycle] 46 + + * 47 + + * ============================================================================ 48 + + */ 49 + + 50 + +#define FP_COMPONENT "canvasbio_cb2000" 51 + +#define CB2000_DRIVER_VERSION_TAG "V44" 52 + + 53 + +#include "drivers_api.h" 54 + +#include <stdio.h> /* For raw dump file operations */ 55 + +#include <time.h> /* For timestamp in filename */ 56 + +#include <string.h> /* For memcpy/strlen */ 57 + +#include <math.h> /* For sqrt() in NCC */ 58 + +#include <gio/gio.h> 59 + + 60 + +/* Raw dump configuration for stride debugging */ 61 + +#define CB2000_RAW_DUMP_ENABLED 1 62 + + 63 + +/* ============================================================================ 64 + + * DEVICE CONSTANTS 65 + + * ============================================================================ */ 66 + + 67 + +/* USB Vendor and Product IDs */ 68 + +#define CB2000_VID 0x2df0 69 + +#define CB2000_PID_1 0x0003 70 + +#define CB2000_PID_2 0x0007 71 + + 72 + +/* USB Endpoints */ 73 + +#define CB2000_EP_OUT 0x01 74 + +#define CB2000_EP_IN 0x82 75 + + 76 + +/* Image dimensions */ 77 + +#define CB2000_IMG_WIDTH 80 78 + +#define CB2000_IMG_HEIGHT 64 79 + +#define CB2000_IMG_SIZE (CB2000_IMG_WIDTH * CB2000_IMG_HEIGHT) 80 + +#define CB2000_IMG_CHUNKS 20 81 + +#define CB2000_CHUNK_SIZE 320 82 + +#define CB2000_CHUNK_PAYLOAD_SIZE 256 83 + +#define CB2000_CHUNK_DEFAULT_PAYLOAD_OFFSET \ 84 + + (CB2000_CHUNK_SIZE - CB2000_CHUNK_PAYLOAD_SIZE) /* 64 bytes */ 85 + +/* Approx. sensor size: 15mm x 9mm => ~6.22 px/mm average. */ 86 + +#define CB2000_PPMM_DEFAULT 13.39 /* ~340 dpi - actual sensor resolution */ 87 + +/* CLAHE tile size (80/10=8, 64/8=8 → 10x8 grid of 8x8 tiles) */ 88 + +#define CB2000_CLAHE_TILE_W 8 89 + +#define CB2000_CLAHE_TILE_H 8 90 + +#define CB2000_CLAHE_CLIP_LIMIT 3.0 91 + + 92 + +/* Timing constants (milliseconds) */ 93 + +#define CB2000_TIMEOUT 5000 /* USB transfer timeout */ 94 + +#define CB2000_POLL_INTERVAL 16 /* Windows parity: ~16ms REQ_POLL cadence */ 95 + +#define CB2000_WAKEUP_DELAY 500 /* Post-init stabilization delay */ 96 + +#define CB2000_REMOVAL_INTERVAL 175 /* Finger removal poll interval */ 97 + + 98 + +/* Validation thresholds */ 99 + +#define CB2000_POLL_DEBOUNCE_COUNT 1 100 + +#define CB2000_POLL_STALE_TIMEOUT_MS 12000 /* ~12s max wait for finger */ 101 + +#define CB2000_EARLY_PLACEMENT_POLLS 0 /* disabled (Windows parity) */ 102 + +#define CB2000_EARLY_PLACEMENT_DELAY_MS 400 103 + +#define CB2000_REMOVAL_STABLE_OFF_COUNT 2 104 + +#define CB2000_REMOVAL_STALE_TIMEOUT_MS 10000 /* ~10s max wait for removal */ 105 + +#define CB2000_MIN_IMAGE_VARIANCE 3000.0 106 + +/* Background capture/quality heuristics */ 107 + +#define CB2000_BG_CAPTURE_MAX_VARIANCE 1800.0 108 + +#define CB2000_BLOCK_SIZE 8 109 + +#define CB2000_LOW_VAR_THRESHOLD 2000.0 110 + +#define CB2000_LOW_VAR_RATIO_MAX 0.15 111 + +/* Area thresholds per action (can be overridden by env). */ 112 + +#define CB2000_AREA_MIN_CAPTURE_PCT 85 113 + +#define CB2000_AREA_MIN_ENROLL_PCT 70 114 + +#define CB2000_AREA_MIN_VERIFY_PCT 70 115 + +/* Focus (Laplacian variance) threshold to avoid very blurry frames */ 116 + +#define CB2000_MIN_FOCUS_VARIANCE 500.0 117 + +/* Foreground threshold used for RE-style area/overlap heuristics. */ 118 + +#define CB2000_FOREGROUND_THRESHOLD 32 119 + +/* Some PCAPs show an alternate detect value for reg 0x51. */ 120 + +#define CB2000_USE_ALT_DETECT_51 0 121 + + 122 + +/* NCC correlation matching */ 123 + +#define CB2000_NR_ENROLL_STAGES 5 124 + +#define CB2000_NCC_MAX_SHIFT_DEFAULT 3 125 + +#define CB2000_NCC_MASK_THRESHOLD_DEFAULT 24 126 + +#define CB2000_NCC_MIN_SAMPLES_DEFAULT 700 127 + +/* 128 + + * Default NCC thresholds tuned for tiny 80x64 captures. 129 + + * Keep multi-sample support enabled to avoid easy false accepts. 130 + + */ 131 + +#define CB2000_NCC_MATCH_TOP1_DEFAULT 0.40 132 + +#define CB2000_NCC_MATCH_TOP2_DEFAULT 0.30 133 + +#define CB2000_NCC_MATCH_MEAN3_DEFAULT 0.30 134 + +#define CB2000_NCC_MATCH_GRAD1_DEFAULT 0.22 135 + +#define CB2000_NCC_SUPPORT_MIN_DEFAULT 2 136 + +#define CB2000_NCC_SUPPORT_INT_DEFAULT 0.28 137 + +#define CB2000_NCC_SUPPORT_GRAD_DEFAULT 0.20 138 + +/* Medium band for borderline same-finger attempts on tiny captures. */ 139 + +#define CB2000_NCC_MEDIUM_TOP1_DEFAULT 0.33 140 + +#define CB2000_NCC_MEDIUM_TOP2_DEFAULT 0.27 141 + +#define CB2000_NCC_MEDIUM_MEAN3_DEFAULT 0.24 142 + +#define CB2000_NCC_MEDIUM_GRAD1_DEFAULT 0.20 143 + +#define CB2000_NCC_MEDIUM_SUPPORT_MIN_DEFAULT 2 144 + +/* Dominant-match band: high top1 with coherent mean/gradient and minimum support. */ 145 + +#define CB2000_NCC_DOMINANT_TOP1_DEFAULT 0.44 146 + +#define CB2000_NCC_DOMINANT_TOP2_DEFAULT 0.20 147 + +#define CB2000_NCC_DOMINANT_MEAN3_DEFAULT 0.24 148 + +#define CB2000_NCC_DOMINANT_GRAD1_DEFAULT 0.24 149 + +#define CB2000_NCC_DOMINANT_SUPPORT_MIN_DEFAULT 1 150 + +/* 151 + + * Cross-sample consistency band: 152 + + * If at least 1 enrolled sample supports the captured frame, 153 + + * accept with lower per-score thresholds. 154 + + */ 155 + +#define CB2000_NCC_CONSISTENT_TOP1_DEFAULT 0.26 156 + +#define CB2000_NCC_CONSISTENT_MEAN3_DEFAULT 0.20 157 + +#define CB2000_NCC_CONSISTENT_GRAD1_DEFAULT 0.18 158 + +#define CB2000_NCC_CONSISTENT_SUPPORT_MIN_DEFAULT 2 159 + +#define CB2000_NCC_NOMATCH_TOP1_DEFAULT 0.22 160 + +/* 161 + + * Single-strong band: 162 + + * allow MATCH when one enrolled sample is clearly strong and stable. 163 + + */ 164 + +#define CB2000_NCC_SINGLE_TOP1_DEFAULT 0.42 165 + +#define CB2000_NCC_SINGLE_TOP2_DEFAULT 0.20 166 + +#define CB2000_NCC_SINGLE_MEAN3_DEFAULT 0.24 167 + +#define CB2000_NCC_SINGLE_GRAD1_DEFAULT 0.22 168 + +#define CB2000_NCC_SINGLE_SUPPORT_MIN_DEFAULT 1 169 + +/* 170 + + * Clear no-match band: 171 + + * avoid endless RETRY for clearly different fingers. 172 + + */ 173 + +#define CB2000_NCC_CLEAR_NOMATCH_TOP1_DEFAULT 0.25 174 + +#define CB2000_NCC_CLEAR_NOMATCH_MEAN3_DEFAULT 0.17 175 + +/* Adaptive thresholding based on enroll-template cohesion. */ 176 + +#define CB2000_NCC_ADAPTIVE_ENABLED_DEFAULT 1 177 + +#define CB2000_NCC_ADAPTIVE_MAX_RELAX_DEFAULT 0.08 178 + +#define CB2000_NCC_ADAPTIVE_TARGET_MEAN_DEFAULT 0.36 179 + +#define CB2000_NCC_ADAPTIVE_TARGET_MIN_DEFAULT 0.28 180 + +#define CB2000_NCC_ADAPTIVE_SUPPORT_RELAX_AT 0.045 181 + +#define CB2000_NCC_ADAPTIVE_RELAX_SUPPORT_DEFAULT 0 182 + +#define CB2000_PRINT_FORMAT "(uqq@ay)" 183 + + 184 + +/* ============================================================================ 185 + + * USB CONTROL TRANSFER REQUESTS 186 + + * ============================================================================ */ 187 + +#define REQ_CONFIG 202 /* 0xCA */ 188 + +#define REQ_STATUS 204 /* 0xCC */ 189 + +#define REQ_POLL 218 /* 0xDA */ 190 + +#define REQ_INIT 219 /* 0xDB */ 191 + + 192 + +#define CTRL_IN 0xC0 193 + +#define CTRL_OUT 0x40 194 + + 195 + +/* ============================================================================ 196 + + * STATE MACHINE DEFINITIONS 197 + + * ============================================================================ 198 + + * 199 + + * Architecture: One master cycle SSM controls the full capture flow. 200 + + * Sub-SSMs handle individual phases (activation, polling, image read). 201 + + * All polling lives inside sub-SSMs - no external timers except settle delay. 202 + + */ 203 + + 204 + +/* Master cycle SSM - one complete capture cycle */ 205 + +typedef enum { 206 + + CYCLE_RECOVERY, /* Explicit recovery state */ 207 + + CYCLE_HARD_RESET, /* USB reset + reclaim interface */ 208 + + CYCLE_INIT_CONFIG, /* Full activation via sub-SSM */ 209 + + CYCLE_SETTLE_DELAY, /* Post-init stabilization timer */ 210 + + CYCLE_WAIT_FINGER, /* Finger polling via sub-SSM */ 211 + + CYCLE_EARLY_PLACEMENT_DELAY, /* Extra delay if finger too early */ 212 + + CYCLE_CAPTURE_START, /* Capture start commands via sub-SSM */ 213 + + CYCLE_READ_IMAGE, /* Image read (20 chunks) via sub-SSM */ 214 + + CYCLE_FINALIZE, /* Capture finalize commands via sub-SSM */ 215 + + CYCLE_VERIFY_RESULT_STATUS, /* Verify-only status/result routing */ 216 + + CYCLE_VERIFY_READY_QUERY_A, /* Verify READY: complementary status query A */ 217 + + CYCLE_VERIFY_READY_QUERY_B, /* Verify READY: complementary status query B */ 218 + + CYCLE_VERIFY_READY_DECIDE, /* Verify READY: decide route from query */ 219 + + CYCLE_SUBMIT_IMAGE, /* Submit image to libfprint */ 220 + + CYCLE_WAIT_REMOVAL, /* Finger removal polling via sub-SSM */ 221 + + CYCLE_REARM, /* Soft rearm between captures */ 222 + + CYCLE_NUM_STATES, 223 + +} CycleState; 224 + + 225 + +/* Sub-SSM: finger polling loop */ 226 + +typedef enum { 227 + + POLL_FINGER_DELAY, /* Wait 16ms */ 228 + + POLL_FINGER_SEND, /* Submit REQ_POLL, callback decides */ 229 + + POLL_FINGER_NUM, 230 + +} PollFingerState; 231 + + 232 + +/* Sub-SSM: removal polling loop */ 233 + +typedef enum { 234 + + POLL_REMOVAL_DELAY, /* Wait 175ms */ 235 + + POLL_REMOVAL_SEND, /* Submit REQ_POLL, callback decides */ 236 + + POLL_REMOVAL_NUM, 237 + +} PollRemovalState; 238 + + 239 + +/* Sub-SSM: image read (chunk + padding loop) */ 240 + +typedef enum { 241 + + IMG_READ_CHUNK, /* Bulk read 320 bytes */ 242 + + IMG_WRITE_PADDING, /* Bulk write 256 zeros */ 243 + + IMG_READ_NUM, 244 + +} ImageReadState; 245 + + 246 + +/* Activation sub-SSM (existing, used by CYCLE_INIT_CONFIG) */ 247 + +typedef enum { 248 + + STATE_ACTIVATE_WAKE_SEQ, 249 + + STATE_ACTIVATE_REGS_SEQ, 250 + + STATE_ACTIVATE_TRIGGER_SEQ, 251 + + STATE_ACTIVATE_CAPTURE_SEQ, 252 + + STATE_ACTIVATE_FINALIZE_SEQ, 253 + + STATE_ACTIVATE_NUM_STATES, 254 + +} ActivateState; 255 + + 256 + +/* ============================================================================ 257 + + * COMMAND TABLE STRUCTURES 258 + + * ============================================================================ */ 259 + + 260 + +typedef enum { 261 + + CMD_END = 0, 262 + + CMD_BULK_OUT, 263 + + CMD_BULK_IN, 264 + + CMD_CTRL_OUT, 265 + + CMD_CTRL_IN, 266 + +} Cb2000CmdType; 267 + + 268 + +typedef struct { 269 + + Cb2000CmdType type; 270 + + guint8 request; 271 + + guint16 value; 272 + + guint16 index; 273 + + const guint8 *data; 274 + + gsize len; 275 + +} Cb2000Command; 276 + + 277 + +typedef struct { 278 + + const Cb2000Command *commands; 279 + + const char *seq_name; 280 + + gint cmd_index; 281 + + gint cmd_total; 282 + +} Cb2000CmdContext; 283 + + 284 + +typedef enum { 285 + + CB2000_RETRY_BACKGROUND_CAPTURE = 0, 286 + + CB2000_RETRY_AREA_GATE, 287 + + CB2000_RETRY_QUALITY_GATE, 288 + + CB2000_RETRY_MINUTIAE_PRECHECK, 289 + + CB2000_RETRY_DEVICE_STATUS, 290 + + CB2000_RETRY_CAUSE_COUNT, 291 + +} Cb2000RetryCause; 292 + + 293 + +typedef enum { 294 + + CB2000_VERIFY_ACK_UNKNOWN = 0, 295 + + CB2000_VERIFY_ACK_READY, 296 + + CB2000_VERIFY_ACK_RETRY, 297 + + CB2000_VERIFY_ACK_DEVICE_NOMATCH, 298 + +} Cb2000VerifyAckDecision; 299 + + 300 + +typedef enum { 301 + + CB2000_VERIFY_ROUTE_UNKNOWN = 0, 302 + + CB2000_VERIFY_ROUTE_HOST_NBIS, 303 + + CB2000_VERIFY_ROUTE_DEVICE_STATUS, 304 + + CB2000_VERIFY_ROUTE_DEVICE_NOMATCH, 305 + + CB2000_VERIFY_ROUTE_RETRY_GATE, 306 + +} Cb2000VerifyRoute; 307 + + 308 + +typedef enum { 309 + + CB2000_VERIFY_RESULT_CLASS_UNKNOWN = 0, 310 + + CB2000_VERIFY_RESULT_CLASS_READY, 311 + + CB2000_VERIFY_RESULT_CLASS_RETRY, 312 + + CB2000_VERIFY_RESULT_CLASS_DEVICE_NOMATCH, 313 + + CB2000_VERIFY_RESULT_CLASS_COUNT, 314 + +} Cb2000VerifyResultClass; 315 + + 316 + +typedef enum { 317 + + CB2000_VERIFY_READY_MATRIX_UNKNOWN = 0, 318 + + CB2000_VERIFY_READY_MATRIX_READY, 319 + + CB2000_VERIFY_READY_MATRIX_RETRY, 320 + + CB2000_VERIFY_READY_MATRIX_DEVICE_NOMATCH, 321 + +} Cb2000VerifyReadyMatrixDecision; 322 + + 323 + +/* ============================================================================ 324 + + * DEVICE STRUCTURE 325 + + * ============================================================================ */ 326 + +struct _FpiDeviceCanvasbioCb2000 { 327 + + FpDevice parent; 328 + + 329 + + /* Lifecycle */ 330 + + gboolean deactivating; 331 + + gboolean deactivation_in_progress; 332 + + 333 + + /* NCC enrollment state */ 334 + + guint8 *enroll_images[5]; 335 + + gint enroll_stage; 336 + + FpiMatchResult verify_result; 337 + + 338 + + /* Image buffer */ 339 + + guint8 *image_buffer; 340 + + guint8 *background_buffer; 341 + + guint8 *previous_norm_buffer; 342 + + gboolean background_valid; 343 + + gboolean previous_norm_valid; 344 + + gsize image_offset; 345 + + gint chunks_read; 346 + + 347 + + /* Polling counters (reset each sub-SSM) */ 348 + + gint poll_stable_hits; 349 + + gint no_finger_streak; 350 + + gint removal_poll_count; 351 + + gint removal_stable_off_hits; 352 + + gint poll_total_count; 353 + + gboolean early_placement_detected; 354 + + gint64 poll_start_us; 355 + + gint64 removal_start_us; 356 + + 357 + + /* SSM tracking */ 358 + + FpiSsm *cycle_ssm; /* Master cycle SSM (single reference) */ 359 + + guint settle_timeout_id; /* Only external timer */ 360 + + guint poll_timeout_id; 361 + + guint removal_timeout_id; 362 + + guint early_timeout_id; 363 + + guint deactivation_timeout_id; 364 + + gboolean force_recovery; 365 + + gboolean initial_activation_done; 366 + + 367 + + /* Runtime diagnostics for retry behavior by cause. */ 368 + + guint retry_total; 369 + + guint accepted_total; 370 + + guint retry_cause_count[CB2000_RETRY_CAUSE_COUNT]; 371 + + 372 + + /* Runtime telemetry by phase/action. */ 373 + + guint submit_capture_total; 374 + + guint submit_enroll_total; 375 + + guint submit_verify_total; 376 + + guint submit_identify_total; 377 + + 378 + + /* Verify template telemetry (host-side NBIS path). */ 379 + + guint verify_template_samples; 380 + + guint verify_template_subprints_last; 381 + + guint verify_template_subprints_min; 382 + + guint verify_template_subprints_max; 383 + + 384 + + /* Trilha B skeleton: runtime flag for device-assisted verify experiments. */ 385 + + gboolean experimental_device_assisted_verify; 386 + + 387 + + /* Trilha B etapa B2: telemetry for short status reads during verify. */ 388 + + guint verify_cmd_rx_total; 389 + + guint verify_cmd_rx_with_data; 390 + + gsize verify_cmd_rx_last_len; 391 + + guint8 verify_cmd_rx_last[8]; 392 + + 393 + + /* Last capture_finalize ACKs used as verify routing gate. */ 394 + + gsize finalize_ack1_len; 395 + + gsize finalize_ack2_len; 396 + + guint8 finalize_ack1[4]; 397 + + guint8 finalize_ack2[4]; 398 + + guint8 verify_ack_status_last; 399 + + Cb2000VerifyAckDecision verify_ack_decision_last; 400 + + guint verify_ack_ready_total; 401 + + guint verify_ack_retry_total; 402 + + guint verify_ack_nomatch_total; 403 + + guint verify_ack_unknown_total; 404 + + guint verify_ack_mismatch_total; 405 + + guint8 verify_status_code_last; 406 + + guint8 verify_result_code_last; 407 + + Cb2000VerifyRoute verify_route_last; 408 + + Cb2000VerifyResultClass verify_result_class_last; 409 + + guint verify_result_class_count[CB2000_VERIFY_RESULT_CLASS_COUNT]; 410 + + gboolean verify_device_status_short_circuit; 411 + + gboolean verify_ready_query_enabled; 412 + + gboolean verify_ready_query_b_enabled; 413 + + 414 + + /* READY-query telemetry: 415 + + * A = first complementary status query 416 + + * B = second complementary status query (V29 matrix decision) 417 + + */ 418 + + guint verify_ready_query_total; 419 + + guint8 verify_ready_query_status_last; 420 + + gsize verify_ready_query_len; 421 + + guint8 verify_ready_query_data[4]; 422 + + guint verify_ready_query_b_total; 423 + + guint8 verify_ready_query_b_status_last; 424 + + gsize verify_ready_query_b_len; 425 + + guint8 verify_ready_query_b_data[4]; 426 + + guint verify_ready_query_ready_total; 427 + + guint verify_ready_query_retry_total; 428 + + guint verify_ready_query_nomatch_total; 429 + + guint verify_ready_query_unknown_total; 430 + + Cb2000VerifyReadyMatrixDecision verify_ready_matrix_last; 431 + + guint verify_ready_matrix_ready_total; 432 + + guint verify_ready_matrix_retry_total; 433 + + guint verify_ready_matrix_nomatch_total; 434 + + guint verify_ready_matrix_unknown_total; 435 + + 436 + + /* Verify pre-capture status probe (Windows: ff:00:01:02 after a8:20). */ 437 + + guint verify_pre_capture_status_total; 438 + + gsize verify_pre_capture_status_len; 439 + + guint8 verify_pre_capture_status[4]; 440 + + 441 + + /* Deferred verify retry report (consumed in cycle_complete). */ 442 + + gboolean verify_retry_pending; 443 + + FpDeviceRetry verify_retry_error; 444 + + guint8 verify_retry_status_code; 445 + + guint8 verify_retry_result_code; 446 + + gchar verify_retry_message[128]; 447 + + 448 + + /* Deferred capture retry report (consumed in cycle_complete). */ 449 + + gboolean capture_retry_pending; 450 + + FpDeviceRetry capture_retry_error; 451 + + gchar capture_retry_message[128]; 452 + +}; 453 + + 454 + +G_DECLARE_FINAL_TYPE(FpiDeviceCanvasbioCb2000, fpi_device_canvasbio_cb2000, FPI, DEVICE_CANVASBIO_CB2000, FpDevice) 455 + +G_DEFINE_TYPE(FpiDeviceCanvasbioCb2000, fpi_device_canvasbio_cb2000, FP_TYPE_DEVICE) 456 + + 457 + +/* ============================================================================ 458 + + * COMMAND DATA BUFFERS 459 + + * ============================================================================ */ 460 + + 461 + +/* Wake/init commands */ 462 + +static const guint8 data_wake[] = {0x4f, 0x80}; 463 + +static const guint8 data_wake_ack[] = {0xa9, 0x4f, 0x80}; 464 + +static const guint8 data_config_read[] = {0xa8, 0xb9, 0x00}; 465 + +static const guint8 data_trigger[] = {0xa9, 0x09, 0x00, 0x00}; 466 + +static const guint8 data_read_start[] = {0xa9, 0x0c, 0x00}; 467 + +static const guint8 data_read_state[] = {0xa8, 0x20, 0x00, 0x00}; 468 + +static const guint8 data_stop[] = {0xa9, 0x04, 0x00}; 469 + + 470 + +/* Register configuration commands (15 registers) */ 471 + +static const guint8 data_reg_50[] = {0xa9, 0x50, 0x12, 0x00}; 472 + +static const guint8 data_reg_5f[] = {0xa9, 0x5f, 0x00, 0x00}; 473 + +static const guint8 data_reg_4e[] = {0xa9, 0x4e, 0x02, 0x00}; 474 + +static const guint8 data_reg_60[] = {0xa9, 0x60, 0x21, 0x00}; 475 + +static const guint8 data_reg_61[] = {0xa9, 0x61, 0x70, 0x00}; 476 + +static const guint8 data_reg_62[] = {0xa9, 0x62, 0x00, 0x21}; 477 + +static const guint8 data_reg_63[] = {0xa9, 0x63, 0x00, 0x21}; 478 + +static const guint8 data_reg_64[] = {0xa9, 0x64, 0x04, 0x08}; 479 + +static const guint8 data_reg_65[] = {0xa9, 0x65, 0x85, 0x08}; 480 + +static const guint8 data_reg_66[] = {0xa9, 0x66, 0x0d, 0x00}; 481 + +static const guint8 data_reg_67[] = {0xa9, 0x67, 0x10, 0x00}; 482 + +static const guint8 data_reg_68[] = {0xa9, 0x68, 0x00, 0x0c}; 483 + +static const guint8 data_reg_6b[] = {0xa9, 0x6b, 0x11, 0x70}; 484 + +static const guint8 data_reg_6c[] = {0xa9, 0x6c, 0x00, 0x0e}; 485 + + 486 + +/* Capture configuration commands */ 487 + +static const guint8 data_cap_03[] = {0xa9, 0x03, 0x00}; 488 + +static const guint8 data_cap_38[] = {0xa9, 0x38, 0x01, 0x00}; 489 + +static const guint8 data_cap_10[] = {0xa9, 0x10, 0x60, 0x00}; 490 + +static const guint8 data_cap_3b[] = {0xa9, 0x3b, 0x14, 0x00}; 491 + +static const guint8 data_cap_3d[] = {0xa9, 0x3d, 0xff, 0x0f}; 492 + +static const guint8 data_cap_26[] = {0xa9, 0x26, 0x30, 0x00}; 493 + +static const guint8 data_cap_2f[] = {0xa9, 0x2f, 0xf6, 0xff}; 494 + + 495 + +/* Capture start sequence commands */ 496 + +static const guint8 data_cap_read_08[] = {0xa8, 0x08, 0x00}; 497 + +static const guint8 data_cap_09[] = {0xa9, 0x09, 0x00, 0x00}; 498 + +static const guint8 data_cap_3e[] = {0xa8, 0x3e, 0x00, 0x00}; 499 + +static const guint8 data_cap_03_4b[] = {0xa9, 0x03, 0x00, 0x00}; 500 + +static const guint8 data_cap_20[] = {0xa8, 0x20, 0x00, 0x00}; 501 + +static const guint8 data_cap_0d[] = {0xa9, 0x0d, 0x00}; 502 + +static const guint8 data_cap_10_alt[] = {0xa9, 0x10, 0x00, 0x01}; 503 + +static const guint8 data_cap_26_4b[] = {0xa9, 0x26, 0x00, 0x00}; 504 + +static const guint8 data_cap_0c[] = {0xa9, 0x0c, 0x00}; 505 + +static const guint8 data_cap_04_4b[] = {0xa9, 0x04, 0x00, 0x00}; 506 + +static const guint8 data_cap_259[259] = {0xa8, 0x06}; 507 + + 508 + +/* Detection mode calibration values */ 509 + +static const guint8 data_cap_5d_detect[] = {0xa9, 0x5d, 0x3d, 0x00}; 510 + +static const guint8 data_cap_51_detect[] = {0xa9, 0x51, 0xa8, 0x01}; 511 + +static const guint8 data_cap_51_detect_alt[] = {0xa9, 0x51, 0x68, 0x01}; 512 + +/* Capture mode calibration values (post-detection) */ 513 + +static const guint8 data_cap_5d_capture[] = {0xa9, 0x5d, 0x4d, 0x00}; 514 + +static const guint8 data_cap_51_capture[] = {0xa9, 0x51, 0x88, 0x01}; 515 + +/* Chunk padding (256 zeros) */ 516 + +static const guint8 data_padding[256] = {0}; 517 + + 518 + +/* 519 + + * Verify READY complementary status query. 520 + + * This mirrors the RE "status execute/query then commit class" split: 521 + + * we ask one extra status sample after finalize before host matcher fallback. 522 + + */ 523 + +static const Cb2000Command verify_ready_query_a_cmds[] = { 524 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 525 + + {CMD_END, 0, 0, 0, NULL, 0} 526 + +}; 527 + + 528 + +static const Cb2000Command verify_ready_query_b_cmds[] = { 529 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 530 + + {CMD_END, 0, 0, 0, NULL, 0} 531 + +}; 532 + + 533 + +/* ============================================================================ 534 + + * COMMAND TABLES 535 + + * ============================================================================ */ 536 + + 537 + +/* Activation Phase 1: Wake sequence (PCAP-aligned) */ 538 + +static const Cb2000Command activation_wake_cmds[] = { 539 + + {CMD_CTRL_OUT, REQ_INIT, 0x0001, 1, NULL, 0}, 540 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 2, NULL, 0}, 541 + + {CMD_BULK_OUT, 0, 0, 0, data_wake, 2}, 542 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 543 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 544 + + {CMD_BULK_OUT, 0, 0, 0, data_wake_ack, 3}, 545 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 546 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 3, NULL, 0}, 547 + + {CMD_BULK_OUT, 0, 0, 0, data_config_read, 3}, 548 + + /* Windows emits a short 3-byte BULK_IN here before register setup starts. */ 549 + + {CMD_BULK_IN, 0, 0, 0, NULL, 3}, 550 + + {CMD_END, 0, 0, 0, NULL, 0} 551 + +}; 552 + + 553 + +/* Activation Phase 2: Register configuration (PCAP-aligned) */ 554 + +static const Cb2000Command activation_regs_cmds[] = { 555 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 556 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_50, 4}, 557 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 558 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 559 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_5f, 4}, 560 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 561 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 562 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_4e, 4}, 563 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 564 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 565 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_60, 4}, 566 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 567 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 568 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_61, 4}, 569 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 570 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 571 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_62, 4}, 572 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 573 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 574 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_63, 4}, 575 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 576 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 577 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_64, 4}, 578 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 579 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 580 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_65, 4}, 581 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 582 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 583 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_66, 4}, 584 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 585 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 586 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_67, 4}, 587 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 588 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 589 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_68, 4}, 590 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 591 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 592 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_6b, 4}, 593 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 594 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 595 + + {CMD_BULK_OUT, 0, 0, 0, data_reg_6c, 4}, 596 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 597 + + /* Keep trigger preamble in the next phase to avoid duplicating 0x0002:4. */ 598 + + {CMD_END, 0, 0, 0, NULL, 0} 599 + +}; 600 + + 601 + +/* Activation Phase 3: Init trigger (PCAP-aligned, 7 commands) */ 602 + +static const Cb2000Command activation_trigger_cmds[] = { 603 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 604 + + {CMD_BULK_OUT, 0, 0, 0, data_trigger, 4}, 605 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 606 + + {CMD_CTRL_OUT, REQ_INIT, 0x0001, 1, NULL, 0}, 607 + + {CMD_CTRL_IN, REQ_POLL, 0x00fe, 0, NULL, 2}, 608 + + {CMD_CTRL_IN, REQ_POLL, 0x00ff, 0, NULL, 2}, 609 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 610 + + {CMD_END, 0, 0, 0, NULL, 0} 611 + +}; 612 + + 613 + +/* Activation Phase 4: Capture configuration (PCAP-aligned, 27 commands) - detection mode */ 614 + +static const Cb2000Command activation_capture_cmds[] = { 615 + + /* activation_trigger already ends with REQ_CONFIG 0x0002 idx=4. */ 616 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_5d_detect, 4}, 617 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 618 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 619 + + {CMD_BULK_OUT, 0, 0, 0, 620 + + CB2000_USE_ALT_DETECT_51 ? data_cap_51_detect_alt : data_cap_51_detect, 4}, 621 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 622 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 623 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_03, 3}, 624 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 625 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 626 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_38, 4}, 627 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 628 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 629 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_10, 4}, 630 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 631 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 632 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_3b, 4}, 633 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 634 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 635 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_3d, 4}, 636 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 637 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 638 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_26, 4}, 639 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 640 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 641 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_2f, 4}, 642 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 643 + + {CMD_END, 0, 0, 0, NULL, 0} 644 + +}; 645 + + 646 + +/* Activation Phase 5: Finalize (PCAP-aligned, 11 commands) */ 647 + +static const Cb2000Command activation_finalize_cmds[] = { 648 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 649 + + {CMD_BULK_OUT, 0, 0, 0, data_trigger, 4}, 650 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 651 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 652 + + {CMD_BULK_OUT, 0, 0, 0, data_read_start, 3}, 653 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 654 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 4, NULL, 0}, 655 + + {CMD_BULK_OUT, 0, 0, 0, data_read_state, 4}, 656 + + /* Windows reads 4 bytes from BULK_IN before issuing the stop command. */ 657 + + {CMD_BULK_IN, 0, 0, 0, NULL, 4}, 658 + + {CMD_CTRL_OUT, REQ_INIT, 0x0007, 0, NULL, 0}, 659 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 660 + + {CMD_BULK_OUT, 0, 0, 0, data_stop, 3}, 661 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 662 + + {CMD_END, 0, 0, 0, NULL, 0} 663 + +}; 664 + + 665 + +/* Soft rearm between captures (PCAP-aligned). */ 666 + +static const Cb2000Command rearm_cmds[] = { 667 + + /* Windows verify traces end with REQ_INIT 0x0001 using wIndex=0. */ 668 + + {CMD_CTRL_OUT, REQ_INIT, 0x0001, 0, NULL, 0}, 669 + + 670 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 671 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_5d_detect, 4}, 672 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 673 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 674 + + {CMD_BULK_OUT, 0, 0, 0, 675 + + CB2000_USE_ALT_DETECT_51 ? data_cap_51_detect_alt : data_cap_51_detect, 4}, 676 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 677 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 678 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_03, 3}, 679 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 680 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 681 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_38, 4}, 682 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 683 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 684 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_10, 4}, 685 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 686 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 687 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_3b, 4}, 688 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 689 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 690 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_3d, 4}, 691 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 692 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 693 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_26, 4}, 694 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 695 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 696 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_2f, 4}, 697 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 698 + + 699 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 700 + + {CMD_BULK_OUT, 0, 0, 0, data_trigger, 4}, 701 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 702 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 703 + + {CMD_BULK_OUT, 0, 0, 0, data_read_start, 3}, 704 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 705 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 4, NULL, 0}, 706 + + {CMD_BULK_OUT, 0, 0, 0, data_read_state, 4}, 707 + + {CMD_CTRL_OUT, REQ_INIT, 0x0007, 0, NULL, 0}, 708 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 709 + + {CMD_BULK_OUT, 0, 0, 0, data_stop, 3}, 710 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 711 + + {CMD_END, 0, 0, 0, NULL, 0} 712 + +}; 713 + + 714 + +/* Capture start sequence (PCAP-aligned, double capture) - sent after finger detected. 715 + + * Phase 2: first capture trigger (a8:08) + quality check (a8:3e). 716 + + * Phase 3: post-quality config (a8:20 status reads). 717 + + * Phase 4: calibration + second capture trigger (idx=5123). */ 718 + +static const Cb2000Command capture_start_cmds[] = { 719 + + /* Phase 2: First capture trigger + quality response */ 720 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 3, NULL, 0}, 721 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_read_08, 3}, 722 + + {CMD_BULK_IN, 0, 0, 0, NULL, 3}, /* First capture quality: ff:00:XX */ 723 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_09, 4}, 724 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 725 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 4, NULL, 0}, 726 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_3e, 4}, 727 + + {CMD_BULK_IN, 0, 0, 0, NULL, 4}, /* Quality ACK: ff:00:YY:ZZ */ 728 + + /* Phase 3: Post-quality config */ 729 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 730 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_03_4b, 4}, 731 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 732 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 4, NULL, 0}, 733 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_20, 4}, 734 + + {CMD_BULK_IN, 0, 0, 0, NULL, 4}, /* Status #2: ff:00:01:07 */ 735 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 736 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_0d, 3}, 737 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 738 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 739 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_10_alt, 4}, 740 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 741 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 742 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_26_4b, 4}, 743 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 744 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 745 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_09, 4}, 746 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 747 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 748 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_0c, 3}, 749 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 750 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 4, NULL, 0}, 751 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_20, 4}, 752 + + {CMD_BULK_IN, 0, 0, 0, NULL, 4}, /* Status #3: ff:00:01:02 */ 753 + + /* Phase 4: Calibration + second capture trigger */ 754 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 755 + + /* Switch to CAPTURE mode values (post-detection) */ 756 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_5d_capture, 4}, 757 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 758 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 759 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_51_capture, 4}, 760 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 761 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 762 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_04_4b, 4}, 763 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 764 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 765 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_09, 4}, 766 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 767 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 5123, NULL, 0}, 768 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_259, 259}, 769 + + {CMD_END, 0, 0, 0, NULL, 0} 770 + +}; 771 + + 772 + +/* 773 + + * Verify-specific start sequence. 774 + + * Kept as a dedicated table (even if currently equal to capture_start_cmds) 775 + + * so we can evolve verify 1:1 without impacting enroll/capture flow. 776 + + */ 777 + +static const Cb2000Command verify_start_cmds[] = { 778 + + /* Phase 2: First capture trigger + quality response */ 779 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 3, NULL, 0}, 780 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_read_08, 3}, 781 + + {CMD_BULK_IN, 0, 0, 0, NULL, 3}, /* First capture quality: ff:00:XX */ 782 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_09, 4}, 783 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 784 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 4, NULL, 0}, 785 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_3e, 4}, 786 + + {CMD_BULK_IN, 0, 0, 0, NULL, 4}, /* Quality ACK: ff:00:YY:ZZ */ 787 + + /* Phase 3: Post-quality config */ 788 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 789 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_03_4b, 4}, 790 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 791 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 4, NULL, 0}, 792 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_20, 4}, 793 + + {CMD_BULK_IN, 0, 0, 0, NULL, 4}, /* Status #2: ff:00:01:07 */ 794 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 795 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_0d, 3}, 796 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 797 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 798 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_10_alt, 4}, 799 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 800 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 801 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_26_4b, 4}, 802 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 803 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 804 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_09, 4}, 805 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 806 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 807 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_0c, 3}, 808 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 809 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 4, NULL, 0}, 810 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_20, 4}, 811 + + {CMD_BULK_IN, 0, 0, 0, NULL, 4}, /* Status #3: ff:00:01:02 */ 812 + + /* Phase 4: Calibration + second capture trigger */ 813 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 814 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_5d_capture, 4}, 815 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 816 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 817 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_51_capture, 4}, 818 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 819 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 820 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_04_4b, 4}, 821 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 822 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 823 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_09, 4}, 824 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 825 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 5123, NULL, 0}, 826 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_259, 259}, 827 + + {CMD_END, 0, 0, 0, NULL, 0} 828 + +}; 829 + + 830 + +/* Capture finalize sequence (PCAP-aligned) */ 831 + +static const Cb2000Command capture_finalize_cmds[] = { 832 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 833 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_09, 4}, 834 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 835 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 4, NULL, 0}, 836 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_3e, 4}, 837 + + {CMD_BULK_IN, 0, 0, 0, NULL, 4}, 838 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 4, NULL, 0}, 839 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_3e, 4}, 840 + + {CMD_BULK_IN, 0, 0, 0, NULL, 4}, 841 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 842 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_0d, 3}, 843 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 844 + + {CMD_END, 0, 0, 0, NULL, 0} 845 + +}; 846 + + 847 + +/* 848 + + * Verify-specific finalize sequence. 849 + + * Dedicated table to isolate verify evolution from enroll/capture behavior. 850 + + */ 851 + +static const Cb2000Command verify_finalize_cmds[] = { 852 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 4, NULL, 0}, 853 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_09, 4}, 854 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 855 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 4, NULL, 0}, 856 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_3e, 4}, 857 + + {CMD_BULK_IN, 0, 0, 0, NULL, 4}, 858 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0003, 4, NULL, 0}, 859 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_3e, 4}, 860 + + {CMD_BULK_IN, 0, 0, 0, NULL, 4}, 861 + + {CMD_CTRL_OUT, REQ_CONFIG, 0x0002, 3, NULL, 0}, 862 + + {CMD_BULK_OUT, 0, 0, 0, data_cap_0d, 3}, 863 + + {CMD_CTRL_IN, REQ_STATUS, 0x0000, 0, NULL, 4}, 864 + + {CMD_END, 0, 0, 0, NULL, 0} 865 + +}; 866 + + 867 + +/* ============================================================================ 868 + + * SEQUENTIAL COMMAND EXECUTOR 869 + + * ============================================================================ */ 870 + + 871 + +/* Forward declarations */ 872 + +static void cmd_sequence_run(FpiSsm *ssm, FpDevice *dev); 873 + +static void cmd_transfer_cb(FpiUsbTransfer *transfer, FpDevice *dev, 874 + + gpointer user_data, GError *error); 875 + +static const char *cb2000_cmd_type_to_str(Cb2000CmdType type); 876 + +static gboolean cb2000_is_verify_phase_action(FpiDeviceAction action); 877 + +static const char *cb2000_capture_start_seq_name_for_action(FpiDeviceAction action); 878 + +static const char *cb2000_capture_finalize_seq_name_for_action(FpiDeviceAction action); 879 + +static gboolean cb2000_seq_is_capture_finalize(const char *seq_name); 880 + +static const char *cb2000_verify_ack_decision_label(Cb2000VerifyAckDecision decision); 881 + +static const char *cb2000_verify_result_class_label(Cb2000VerifyResultClass klass); 882 + +static Cb2000VerifyResultClass 883 + +cb2000_classify_verify_result_codes(guint8 status_code, guint8 result_code); 884 + +static const char *cb2000_verify_ready_query_class_label(guint8 status0); 885 + +static const char *cb2000_verify_ready_matrix_label(Cb2000VerifyReadyMatrixDecision decision); 886 + +static Cb2000VerifyReadyMatrixDecision 887 + +cb2000_decide_ready_query_matrix(guint8 status_a, gboolean has_b, guint8 status_b); 888 + +static Cb2000VerifyAckDecision 889 + +cb2000_classify_finalize_ack(FpiDeviceCanvasbioCb2000 *self, guint8 *status_hint, gboolean *mismatch_out); 890 + +static void cb2000_log_cmd_rx_data(FpDevice *dev, 891 + + const Cb2000CmdContext *ctx, 892 + + const Cb2000Command *cmd, 893 + + const FpiUsbTransfer *transfer); 894 + +static gboolean cb2000_verify_finalize_ack_gate(FpDevice *dev, 895 + + FpiDeviceCanvasbioCb2000 *self); 896 + +static void complete_deactivation(FpDevice *dev); 897 + +static gboolean deactivation_force_cb(gpointer user_data); 898 + +static void start_new_cycle(FpDevice *dev); 899 + + 900 + +/* Invert grayscale image in-place (sensor outputs inverted polarity). */ 901 + +static void G_GNUC_UNUSED 902 + +invert_image_u8(guint8 *data, gsize len) 903 + +{ 904 + + for (gsize i = 0; i < len; i++) 905 + + data[i] = 0xff - data[i]; 906 + +} 907 + + 908 + +static gboolean 909 + +cb2000_dump_binarized_enabled(void) 910 + +{ 911 + + const gchar *v = g_getenv("CB2000_DUMP_BIN"); 912 + + if (!v) 913 + + return FALSE; 914 + + return g_ascii_strcasecmp(v, "1") == 0 || 915 + + g_ascii_strcasecmp(v, "true") == 0 || 916 + + g_ascii_strcasecmp(v, "yes") == 0; 917 + +} 918 + + 919 + +/* 920 + + * Parse a boolean env var with explicit defaults. 921 + + * Accepted true values: 1,true,yes,on 922 + + * Accepted false values: 0,false,no,off 923 + + */ 924 + +static gboolean 925 + +cb2000_get_env_bool(const gchar *name, gboolean default_value) 926 + +{ 927 + + const gchar *v = g_getenv(name); 928 + + if (!v || !*v) 929 + + return default_value; 930 + + 931 + + if (g_ascii_strcasecmp(v, "1") == 0 || 932 + + g_ascii_strcasecmp(v, "true") == 0 || 933 + + g_ascii_strcasecmp(v, "yes") == 0 || 934 + + g_ascii_strcasecmp(v, "on") == 0) 935 + + return TRUE; 936 + + 937 + + if (g_ascii_strcasecmp(v, "0") == 0 || 938 + + g_ascii_strcasecmp(v, "false") == 0 || 939 + + g_ascii_strcasecmp(v, "no") == 0 || 940 + + g_ascii_strcasecmp(v, "off") == 0) 941 + + return FALSE; 942 + + 943 + + return default_value; 944 + +} 945 + + 946 + +static int 947 + +cb2000_parse_upscale_value(const gchar *v, int default_value) 948 + +{ 949 + + if (!v || !*v) 950 + + return default_value; 951 + + 952 + + int f = atoi(v); 953 + + if (f < 1) 954 + + f = 1; 955 + + if (f > 8) 956 + + f = 8; 957 + + return f; 958 + +} 959 + + 960 + +static int 961 + +cb2000_get_env_int(const gchar *name, 962 + + int default_value, 963 + + int min_value, 964 + + int max_value) 965 + +{ 966 + + const gchar *v = g_getenv(name); 967 + + int out; 968 + + 969 + + out = default_value; 970 + + if (v && *v) 971 + + out = atoi(v); 972 + + 973 + + if (out < min_value) 974 + + out = min_value; 975 + + if (out > max_value) 976 + + out = max_value; 977 + + 978 + + return out; 979 + +} 980 + + 981 + +static const gchar * 982 + +cb2000_retry_cause_label(Cb2000RetryCause cause) 983 + +{ 984 + + switch (cause) { 985 + + case CB2000_RETRY_BACKGROUND_CAPTURE: 986 + + return "background_capture"; 987 + + case CB2000_RETRY_AREA_GATE: 988 + + return "area_gate"; 989 + + case CB2000_RETRY_QUALITY_GATE: 990 + + return "quality_gate"; 991 + + case CB2000_RETRY_MINUTIAE_PRECHECK: 992 + + return "minutiae_precheck"; 993 + + case CB2000_RETRY_DEVICE_STATUS: 994 + + return "device_status"; 995 + + case CB2000_RETRY_CAUSE_COUNT: 996 + + return "count_sentinel"; 997 + + default: 998 + + return "unknown"; 999 + + } 1000 + +} 1001 + + 1002 + +/* 1003 + + * Retry-class status pairs observed in Windows captures. 1004 + + * We keep this conservative: unknown values never become hard decisions. 1005 + + */ 1006 + +static gboolean 1007 + +cb2000_is_verify_retry_status_pair(guint8 status_code, guint8 result_code) 1008 + +{ 1009 + + if (status_code == 0x00 && result_code == 0x00) 1010 + + return TRUE; 1011 + + if (status_code == 0x0f && result_code == 0x00) 1012 + + return TRUE; 1013 + + if (status_code == 0xff && result_code == 0x0c) 1014 + + return TRUE; 1015 + + 1016 + + /* New retry-class ACKs seen in 2026-02-16 Windows verify error captures. */ 1017 + + if (status_code == 0xf0 && result_code == 0x0f) 1018 + + return TRUE; 1019 + + if (status_code == 0xf3 && result_code == 0x0f) 1020 + + return TRUE; 1021 + + if (status_code == 0x88 && result_code == 0x08) 1022 + + return TRUE; 1023 + + if (status_code == 0x11 && result_code == 0x01) 1024 + + return TRUE; 1025 + + if (status_code == 0x08 && result_code == 0x00) 1026 + + return TRUE; 1027 + + if (status_code == 0x20 && result_code == 0x0f) 1028 + + return TRUE; 1029 + + if (status_code == 0x00 && result_code == 0x08) 1030 + + return TRUE; 1031 + + if (status_code == 0x11 && result_code == 0x00) 1032 + + return TRUE; 1033 + + if (status_code == 0x30 && result_code == 0x0f) 1034 + + return TRUE; 1035 + + 1036 + + return FALSE; 1037 + +} 1038 + + 1039 + +static FpDeviceRetry 1040 + +cb2000_retry_error_from_verify_status_pair(guint8 status_code, guint8 result_code) 1041 + +{ 1042 + + /* 1043 + + * Direction/placement-like hints from Windows Hello feedback: 1044 + + * - 20:0f -> move up 1045 + + * - 11:00 -> move right 1046 + + * - 88:08 -> move left 1047 + + */ 1048 + + if ((status_code == 0x20 && result_code == 0x0f) || 1049 + + (status_code == 0x11 && result_code == 0x00) || 1050 + + (status_code == 0x88 && result_code == 0x08)) 1051 + + return FP_DEVICE_RETRY_CENTER_FINGER; 1052 + + 1053 + + /* Generic placement instability buckets observed in retry-only cycles. */ 1054 + + if ((status_code == 0x11 && result_code == 0x01) || 1055 + + (status_code == 0xf0 && result_code == 0x0f) || 1056 + + (status_code == 0xf3 && result_code == 0x0f)) 1057 + + return FP_DEVICE_RETRY_CENTER_FINGER; 1058 + + 1059 + + /* Lift/replace style hint for unstable edge-contact / contamination states. */ 1060 + + if ((status_code == 0x08 && result_code == 0x00) || 1061 + + (status_code == 0x00 && result_code == 0x08)) 1062 + + return FP_DEVICE_RETRY_REMOVE_FINGER; 1063 + + 1064 + + /* "Try a different finger" in Windows; keep as generic retry. */ 1065 + + if (status_code == 0x30 && result_code == 0x0f) 1066 + + return FP_DEVICE_RETRY_GENERAL; 1067 + + 1068 + + return FP_DEVICE_RETRY_GENERAL; 1069 + +} 1070 + + 1071 + +static const gchar * 1072 + +cb2000_retry_message_from_verify_status_pair(guint8 status_code, guint8 result_code) 1073 + +{ 1074 + + if (status_code == 0x20 && result_code == 0x0f) 1075 + + return "Move finger slightly up and try again."; 1076 + + if (status_code == 0x11 && result_code == 0x00) 1077 + + return "Move finger slightly right and try again."; 1078 + + if (status_code == 0x88 && result_code == 0x08) 1079 + + return "Move finger slightly left and try again."; 1080 + + if (status_code == 0x00 && result_code == 0x08) 1081 + + return "Clean the sensor and try again."; 1082 + + if (status_code == 0x30 && result_code == 0x0f) 1083 + + return "Try a different finger."; 1084 + + if (status_code == 0x08 && result_code == 0x00) 1085 + + return "Lift and place finger again."; 1086 + + if ((status_code == 0xf0 && result_code == 0x0f) || 1087 + + (status_code == 0xf3 && result_code == 0x0f) || 1088 + + (status_code == 0x11 && result_code == 0x01)) 1089 + + return "Adjust finger placement and try again."; 1090 + + 1091 + + return NULL; 1092 + +} 1093 + + 1094 + +static const gchar * 1095 + +cb2000_retry_error_label(FpDeviceRetry retry_error) 1096 + +{ 1097 + + switch (retry_error) { 1098 + + case FP_DEVICE_RETRY_GENERAL: 1099 + + return "GENERAL"; 1100 + + case FP_DEVICE_RETRY_TOO_SHORT: 1101 + + return "TOO_SHORT"; 1102 + + case FP_DEVICE_RETRY_CENTER_FINGER: 1103 + + return "CENTER_FINGER"; 1104 + + case FP_DEVICE_RETRY_REMOVE_FINGER: 1105 + + return "REMOVE_FINGER"; 1106 + + case FP_DEVICE_RETRY_TOO_FAST: 1107 + + return "TOO_FAST"; 1108 + + default: 1109 + + return "UNKNOWN"; 1110 + + } 1111 + +} 1112 + + 1113 + +static int 1114 + +cb2000_get_upscale_factor(FpiDeviceAction action) 1115 + +{ 1116 + + const gchar *v_phase = NULL; 1117 + + const gchar *v_global = g_getenv("CB2000_UPSCALE"); 1118 + + int action_default = 1; 1119 + + 1120 + + /* 1121 + + * Keep preprocess parity between template generation and matching: 1122 + + * enroll/verify/identify default to 2x, capture remains 1x. 1123 + + */ 1124 + + switch (action) { 1125 + + case FPI_DEVICE_ACTION_NONE: 1126 + + case FPI_DEVICE_ACTION_PROBE: 1127 + + case FPI_DEVICE_ACTION_OPEN: 1128 + + case FPI_DEVICE_ACTION_CLOSE: 1129 + + case FPI_DEVICE_ACTION_LIST: 1130 + + case FPI_DEVICE_ACTION_DELETE: 1131 + + case FPI_DEVICE_ACTION_CLEAR_STORAGE: 1132 + + break; 1133 + + case FPI_DEVICE_ACTION_VERIFY: 1134 + + action_default = 2; 1135 + + v_phase = g_getenv("CB2000_VERIFY_UPSCALE"); 1136 + + break; 1137 + + case FPI_DEVICE_ACTION_IDENTIFY: 1138 + + action_default = 2; 1139 + + v_phase = g_getenv("CB2000_IDENTIFY_UPSCALE"); 1140 + + break; 1141 + + case FPI_DEVICE_ACTION_ENROLL: 1142 + + action_default = 2; 1143 + + v_phase = g_getenv("CB2000_ENROLL_UPSCALE"); 1144 + + break; 1145 + + case FPI_DEVICE_ACTION_CAPTURE: 1146 + + v_phase = g_getenv("CB2000_CAPTURE_UPSCALE"); 1147 + + break; 1148 + + default: 1149 + + break; 1150 + + } 1151 + + 1152 + + if (v_phase && *v_phase) 1153 + + return cb2000_parse_upscale_value(v_phase, action_default); 1154 + + 1155 + + return cb2000_parse_upscale_value(v_global, action_default); 1156 + +} 1157 + + 1158 + +/* 1159 + + * Background subtraction helps remove fixed-pattern noise in enroll/capture. 1160 + + * Default is configured per action via env knobs, with verify/identify mapped 1161 + + * by CB2000_VERIFY_BG_SUBTRACT and others by CB2000_BG_SUBTRACT. 1162 + + */ 1163 + +static gboolean 1164 + +cb2000_should_apply_bg_subtraction(FpiDeviceAction action) 1165 + +{ 1166 + + /* Keep verify/identify aligned with enroll defaults for NBIS matching. */ 1167 + + if (action == FPI_DEVICE_ACTION_VERIFY) 1168 + + return cb2000_get_env_bool("CB2000_VERIFY_BG_SUBTRACT", TRUE); 1169 + + 1170 + + if (action == FPI_DEVICE_ACTION_IDENTIFY) { 1171 + + const gchar *v_identify = g_getenv("CB2000_IDENTIFY_BG_SUBTRACT"); 1172 + + if (v_identify && *v_identify) 1173 + + return cb2000_get_env_bool("CB2000_IDENTIFY_BG_SUBTRACT", TRUE); 1174 + + return cb2000_get_env_bool("CB2000_VERIFY_BG_SUBTRACT", TRUE); 1175 + + } 1176 + + 1177 + + if (action == FPI_DEVICE_ACTION_ENROLL) { 1178 + + const gchar *v_enroll = g_getenv("CB2000_ENROLL_BG_SUBTRACT"); 1179 + + if (v_enroll && *v_enroll) 1180 + + return cb2000_get_env_bool("CB2000_ENROLL_BG_SUBTRACT", TRUE); 1181 + + return cb2000_get_env_bool("CB2000_BG_SUBTRACT", TRUE); 1182 + + } 1183 + + 1184 + + if (action == FPI_DEVICE_ACTION_CAPTURE) { 1185 + + const gchar *v_capture = g_getenv("CB2000_CAPTURE_BG_SUBTRACT"); 1186 + + if (v_capture && *v_capture) 1187 + + return cb2000_get_env_bool("CB2000_CAPTURE_BG_SUBTRACT", TRUE); 1188 + + } 1189 + + 1190 + + return cb2000_get_env_bool("CB2000_BG_SUBTRACT", TRUE); 1191 + +} 1192 + + 1193 + +static double 1194 + +cb2000_get_ppmm(void) 1195 + +{ 1196 + + const char *v_ppmm = g_getenv("CB2000_PPMM"); 1197 + + if (v_ppmm && *v_ppmm) { 1198 + + double ppmm = g_ascii_strtod(v_ppmm, NULL); 1199 + + if (ppmm > 0.0) 1200 + + return ppmm; 1201 + + } 1202 + + 1203 + + const char *v_dpi = g_getenv("CB2000_DPI"); 1204 + + if (v_dpi && *v_dpi) { 1205 + + double dpi = g_ascii_strtod(v_dpi, NULL); 1206 + + if (dpi > 0.0) 1207 + + return dpi / 25.4; 1208 + + } 1209 + + 1210 + + return CB2000_PPMM_DEFAULT; 1211 + +} 1212 + + 1213 + +static gboolean 1214 + +cb2000_should_precheck_minutiae(FpiDeviceAction action) 1215 + +{ 1216 + + const char *v = g_getenv("CB2000_PRECHECK_MINUTIAE"); 1217 + + if (v && *v && strcmp(v, "0") != 0) 1218 + + return TRUE; 1219 + + 1220 + + if (action == FPI_DEVICE_ACTION_VERIFY) 1221 + + return cb2000_get_env_bool("CB2000_PRECHECK_VERIFY", FALSE); 1222 + + 1223 + + if (action == FPI_DEVICE_ACTION_IDENTIFY) 1224 + + return cb2000_get_env_bool("CB2000_PRECHECK_IDENTIFY", FALSE); 1225 + + 1226 + + if (action == FPI_DEVICE_ACTION_ENROLL) 1227 + + return cb2000_get_env_bool("CB2000_PRECHECK_ENROLL", FALSE); 1228 + + 1229 + + if (action == FPI_DEVICE_ACTION_CAPTURE) 1230 + + return cb2000_get_env_bool("CB2000_PRECHECK_CAPTURE", FALSE); 1231 + + 1232 + + return FALSE; 1233 + +} 1234 + + 1235 + +static const gchar * 1236 + +cb2000_action_label(FpiDeviceAction action) 1237 + +{ 1238 + + switch (action) { 1239 + + case FPI_DEVICE_ACTION_NONE: 1240 + + return "none"; 1241 + + case FPI_DEVICE_ACTION_PROBE: 1242 + + return "probe"; 1243 + + case FPI_DEVICE_ACTION_OPEN: 1244 + + return "open"; 1245 + + case FPI_DEVICE_ACTION_CLOSE: 1246 + + return "close"; 1247 + + case FPI_DEVICE_ACTION_CAPTURE: 1248 + + return "capture"; 1249 + + case FPI_DEVICE_ACTION_LIST: 1250 + + return "list"; 1251 + + case FPI_DEVICE_ACTION_DELETE: 1252 + + return "delete"; 1253 + + case FPI_DEVICE_ACTION_CLEAR_STORAGE: 1254 + + return "clear_storage"; 1255 + + case FPI_DEVICE_ACTION_ENROLL: 1256 + + return "enroll"; 1257 + + case FPI_DEVICE_ACTION_VERIFY: 1258 + + return "verify"; 1259 + + case FPI_DEVICE_ACTION_IDENTIFY: 1260 + + return "identify"; 1261 + + } 1262 + + 1263 + + return "capture"; 1264 + +} 1265 + + 1266 + +/* 1267 + + * Persist last frame per action for fast offline inspection even when the 1268 + + * higher-level example does not emit a PGM (e.g. verify ending in retry). 1269 + + * Files are written to $HOME by default. 1270 + + */ 1271 + +static gboolean 1272 + +cb2000_should_save_debug_pgm(FpiDeviceAction action) 1273 + +{ 1274 + + gboolean global_default = cb2000_get_env_bool("CB2000_DEBUG_SAVE_PGM", TRUE); 1275 + + 1276 + + switch (action) { 1277 + + case FPI_DEVICE_ACTION_NONE: 1278 + + case FPI_DEVICE_ACTION_PROBE: 1279 + + case FPI_DEVICE_ACTION_OPEN: 1280 + + case FPI_DEVICE_ACTION_CLOSE: 1281 + + case FPI_DEVICE_ACTION_LIST: 1282 + + case FPI_DEVICE_ACTION_DELETE: 1283 + + case FPI_DEVICE_ACTION_CLEAR_STORAGE: 1284 + + return global_default; 1285 + + case FPI_DEVICE_ACTION_VERIFY: 1286 + + return cb2000_get_env_bool("CB2000_DEBUG_SAVE_VERIFY", global_default); 1287 + + case FPI_DEVICE_ACTION_IDENTIFY: 1288 + + return cb2000_get_env_bool("CB2000_DEBUG_SAVE_IDENTIFY", global_default); 1289 + + case FPI_DEVICE_ACTION_ENROLL: 1290 + + return cb2000_get_env_bool("CB2000_DEBUG_SAVE_ENROLL", global_default); 1291 + + case FPI_DEVICE_ACTION_CAPTURE: 1292 + + return cb2000_get_env_bool("CB2000_DEBUG_SAVE_CAPTURE", global_default); 1293 + + default: 1294 + + return global_default; 1295 + + } 1296 + +} 1297 + + 1298 + +static const gchar * 1299 + +cb2000_debug_pgm_name(FpiDeviceAction action) 1300 + +{ 1301 + + switch (action) { 1302 + + case FPI_DEVICE_ACTION_NONE: 1303 + + case FPI_DEVICE_ACTION_PROBE: 1304 + + case FPI_DEVICE_ACTION_OPEN: 1305 + + case FPI_DEVICE_ACTION_CLOSE: 1306 + + case FPI_DEVICE_ACTION_LIST: 1307 + + case FPI_DEVICE_ACTION_DELETE: 1308 + + case FPI_DEVICE_ACTION_CLEAR_STORAGE: 1309 + + return "canvasbio_debug.pgm"; 1310 + + case FPI_DEVICE_ACTION_CAPTURE: 1311 + + return "finger.pgm"; 1312 + + case FPI_DEVICE_ACTION_ENROLL: 1313 + + return "enrolled.pgm"; 1314 + + case FPI_DEVICE_ACTION_VERIFY: 1315 + + case FPI_DEVICE_ACTION_IDENTIFY: 1316 + + return "verify.pgm"; 1317 + + default: 1318 + + return "canvasbio_debug.pgm"; 1319 + + } 1320 + +} 1321 + + 1322 + +static const gchar * 1323 + +cb2000_get_output_dir(void) 1324 + +{ 1325 + + const gchar *out_dir = g_getenv("CB2000_OUTPUT_DIR"); 1326 + + const gchar *home; 1327 + + 1328 + + if (out_dir && *out_dir) 1329 + + return out_dir; 1330 + + 1331 + + home = g_get_home_dir(); 1332 + + if (home && *home) 1333 + + return home; 1334 + + 1335 + + return "."; 1336 + +} 1337 + + 1338 + +static void 1339 + +cb2000_save_debug_pgm(FpImage *img, FpiDeviceAction action) 1340 + +{ 1341 + + const gchar *out_dir; 1342 + + g_autofree gchar *pgm_path = NULL; 1343 + + g_autofree gchar *header = NULL; 1344 + + g_autofree guint8 *pgm_data = NULL; 1345 + + gsize pixels; 1346 + + gsize header_len; 1347 + + gsize total_len; 1348 + + g_autoptr(GError) error = NULL; 1349 + + 1350 + + if (!img || !cb2000_should_save_debug_pgm(action)) 1351 + + return; 1352 + + 1353 + + out_dir = cb2000_get_output_dir(); 1354 + + g_mkdir_with_parents(out_dir, 0755); 1355 + + pgm_path = g_build_filename(out_dir, cb2000_debug_pgm_name(action), NULL); 1356 + + 1357 + + pixels = (gsize)img->width * (gsize)img->height; 1358 + + header = g_strdup_printf("P5\n%d %d\n255\n", img->width, img->height); 1359 + + header_len = strlen(header); 1360 + + total_len = header_len + pixels; 1361 + + pgm_data = g_malloc(total_len); 1362 + + 1363 + + memcpy(pgm_data, header, header_len); 1364 + + memcpy(pgm_data + header_len, img->data, pixels); 1365 + + 1366 + + if (!g_file_set_contents(pgm_path, (const gchar *)pgm_data, total_len, &error)) { 1367 + + fp_warn("Failed to save debug PGM %s: %s", pgm_path, 1368 + + error ? error->message : "unknown"); 1369 + + return; 1370 + + } 1371 + + 1372 + + fp_dbg("Saved debug PGM: %s (%dx%d)", pgm_path, img->width, img->height); 1373 + +} 1374 + + 1375 + +static int 1376 + +cb2000_get_area_min_pct(FpiDeviceAction action) 1377 + +{ 1378 + + int action_default = CB2000_AREA_MIN_CAPTURE_PCT; 1379 + + int global_min = cb2000_get_env_int("CB2000_AREA_MIN", 1380 + + CB2000_AREA_MIN_CAPTURE_PCT, 1381 + + 1, 100); 1382 + + int capture_min = 0; 1383 + + 1384 + + /* Non-image actions still need explicit handling to keep -Wswitch-enum clean. */ 1385 + + capture_min = cb2000_get_env_int("CB2000_AREA_MIN_CAPTURE", global_min, 1, 100); 1386 + + 1387 + + switch (action) { 1388 + + case FPI_DEVICE_ACTION_NONE: 1389 + + case FPI_DEVICE_ACTION_PROBE: 1390 + + case FPI_DEVICE_ACTION_OPEN: 1391 + + case FPI_DEVICE_ACTION_CLOSE: 1392 + + case FPI_DEVICE_ACTION_LIST: 1393 + + case FPI_DEVICE_ACTION_DELETE: 1394 + + case FPI_DEVICE_ACTION_CLEAR_STORAGE: 1395 + + return capture_min; 1396 + + case FPI_DEVICE_ACTION_ENROLL: 1397 + + action_default = CB2000_AREA_MIN_ENROLL_PCT; 1398 + + global_min = cb2000_get_env_int("CB2000_AREA_MIN", 1399 + + action_default, 1, 100); 1400 + + return cb2000_get_env_int("CB2000_AREA_MIN_ENROLL", global_min, 1, 100); 1401 + + case FPI_DEVICE_ACTION_VERIFY: 1402 + + case FPI_DEVICE_ACTION_IDENTIFY: 1403 + + action_default = CB2000_AREA_MIN_VERIFY_PCT; 1404 + + global_min = cb2000_get_env_int("CB2000_AREA_MIN", 1405 + + action_default, 1, 100); 1406 + + return cb2000_get_env_int("CB2000_AREA_MIN_VERIFY", global_min, 1, 100); 1407 + + case FPI_DEVICE_ACTION_CAPTURE: 1408 + + default: 1409 + + return capture_min; 1410 + + } 1411 + +} 1412 + + 1413 + +/* Internal helper: read internal NBIS subprint cardinality for telemetry only. */ 1414 + +static guint 1415 + +cb2000_get_subprint_count(FpPrint *print) 1416 + +{ 1417 + + GPtrArray *prints = NULL; 1418 + + 1419 + + if (!print) 1420 + + return 0; 1421 + + 1422 + + if (!g_object_class_find_property(G_OBJECT_GET_CLASS(print), "fpi-prints")) 1423 + + return 0; 1424 + + 1425 + + g_object_get(print, "fpi-prints", &prints, NULL); 1426 + + if (!prints) 1427 + + return 0; 1428 + + 1429 + + /* 1430 + + * `fpi-prints` is exposed as G_PARAM_POINTER in libfprint (private API). 1431 + + * Ownership stays with FpPrint; unref'ing here can free internal storage. 1432 + + */ 1433 + + return prints->len; 1434 + +} 1435 + + 1436 + +static void G_GNUC_UNUSED 1437 + +cb2000_log_preprocess_profile(FpiDeviceAction action) 1438 + +{ 1439 + + fp_info("[ PROFILE ] action=%s bg_subtract=%d upscale=%dx precheck=%d area_min=%d", 1440 + + cb2000_action_label(action), 1441 + + cb2000_should_apply_bg_subtraction(action), 1442 + + cb2000_get_upscale_factor(action), 1443 + + cb2000_should_precheck_minutiae(action), 1444 + + cb2000_get_area_min_pct(action)); 1445 + +} 1446 + + 1447 + +static void 1448 + +cb2000_update_submit_telemetry(FpiDeviceCanvasbioCb2000 *self, 1449 + + FpDevice *dev, 1450 + + FpiDeviceAction action) 1451 + +{ 1452 + + switch (action) { 1453 + + case FPI_DEVICE_ACTION_NONE: 1454 + + case FPI_DEVICE_ACTION_PROBE: 1455 + + case FPI_DEVICE_ACTION_OPEN: 1456 + + case FPI_DEVICE_ACTION_CLOSE: 1457 + + case FPI_DEVICE_ACTION_LIST: 1458 + + case FPI_DEVICE_ACTION_DELETE: 1459 + + case FPI_DEVICE_ACTION_CLEAR_STORAGE: 1460 + + break; 1461 + + case FPI_DEVICE_ACTION_CAPTURE: 1462 + + self->submit_capture_total++; 1463 + + break; 1464 + + case FPI_DEVICE_ACTION_ENROLL: 1465 + + self->submit_enroll_total++; 1466 + + break; 1467 + + case FPI_DEVICE_ACTION_VERIFY: { 1468 + + FpPrint *template = NULL; 1469 + + guint subprints = 0; 1470 + + 1471 + + self->submit_verify_total++; 1472 + + fpi_device_get_verify_data(dev, &template); 1473 + + subprints = cb2000_get_subprint_count(template); 1474 + + 1475 + + self->verify_template_subprints_last = subprints; 1476 + + if (self->verify_template_samples == 0) { 1477 + + self->verify_template_subprints_min = subprints; 1478 + + self->verify_template_subprints_max = subprints; 1479 + + } else { 1480 + + self->verify_template_subprints_min = 1481 + + MIN(self->verify_template_subprints_min, subprints); 1482 + + self->verify_template_subprints_max = 1483 + + MAX(self->verify_template_subprints_max, subprints); 1484 + + } 1485 + + self->verify_template_samples++; 1486 + + 1487 + + fp_info("[ VERIFY ] attempt=%u template_subprints=%u (min=%u max=%u samples=%u)", 1488 + + self->submit_verify_total, 1489 + + subprints, 1490 + + self->verify_template_subprints_min, 1491 + + self->verify_template_subprints_max, 1492 + + self->verify_template_samples); 1493 + + break; 1494 + + } 1495 + + case FPI_DEVICE_ACTION_IDENTIFY: 1496 + + self->submit_identify_total++; 1497 + + break; 1498 + + default: 1499 + + break; 1500 + + } 1501 + +} 1502 + + 1503 + +/* 1504 + + * Trilha B skeleton: 1505 + + * runtime gate for future verify path that uses device-assisted matching. 1506 + + * Current behavior intentionally falls back to host-side NBIS matching. 1507 + + */ 1508 + +static gboolean G_GNUC_UNUSED 1509 + +cb2000_try_device_assisted_verify(FpiDeviceCanvasbioCb2000 *self, 1510 + + FpDevice *dev, 1511 + + FpImage *img) 1512 + +{ 1513 + + (void) img; 1514 + + 1515 + + if (!self->experimental_device_assisted_verify) 1516 + + return FALSE; 1517 + + 1518 + + switch (self->verify_ack_decision_last) { 1519 + + case CB2000_VERIFY_ACK_DEVICE_NOMATCH: 1520 + + fp_warn("[ EXP ] device-assisted verify: finalize ACK indicates no-match (status=0x%02x)", 1521 + + self->verify_ack_status_last); 1522 + + fpi_device_verify_report(dev, FPI_MATCH_FAIL, NULL, NULL); 1523 + + return TRUE; 1524 + + case CB2000_VERIFY_ACK_READY: 1525 + + fp_info("[ EXP ] device-assisted verify: ACK ready (status=0x%02x), host NBIS fallback remains active", 1526 + + self->verify_ack_status_last); 1527 + + return FALSE; 1528 + + case CB2000_VERIFY_ACK_RETRY: 1529 + + fp_info("[ EXP ] device-assisted verify: ACK retry already handled by gate"); 1530 + + return FALSE; 1531 + + case CB2000_VERIFY_ACK_UNKNOWN: 1532 + + default: 1533 + + fp_info("[ EXP ] device-assisted verify: ACK unknown (status=0x%02x), using host NBIS fallback", 1534 + + self->verify_ack_status_last); 1535 + + return FALSE; 1536 + + } 1537 + + 1538 + + return FALSE; 1539 + +} 1540 + + 1541 + +static void 1542 + +cb2000_retry_scan_with_cause(FpDevice *dev, 1543 + + Cb2000RetryCause cause, 1544 + + const gchar *detail) 1545 + +{ 1546 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 1547 + + FpiDeviceAction action = fpi_device_get_current_action(dev); 1548 + + FpDeviceRetry retry_error = FP_DEVICE_RETRY_GENERAL; 1549 + + const gchar *retry_msg = NULL; 1550 + + guint cause_count = 0; 1551 + + 1552 + + self->retry_total++; 1553 + + if (cause >= 0 && cause < CB2000_RETRY_CAUSE_COUNT) { 1554 + + self->retry_cause_count[cause]++; 1555 + + cause_count = self->retry_cause_count[cause]; 1556 + + } 1557 + + 1558 + + if (cause == CB2000_RETRY_DEVICE_STATUS) { 1559 + + if (detail && g_strstr_len(detail, -1, "verify finalize ack gate")) { 1560 + + retry_error = cb2000_retry_error_from_verify_status_pair( 1561 + + self->verify_status_code_last, 1562 + + self->verify_result_code_last); 1563 + + retry_msg = cb2000_retry_message_from_verify_status_pair( 1564 + + self->verify_status_code_last, 1565 + + self->verify_result_code_last); 1566 + + } else if (detail && g_strstr_len(detail, -1, "verify ready matrix retry")) { 1567 + + retry_error = FP_DEVICE_RETRY_CENTER_FINGER; 1568 + + retry_msg = "Adjust finger placement and try again."; 1569 + + } 1570 + + 1571 + + fp_info("[ RETRY ] action=%s cause=%s total=%u cause_count=%u retry_hint=%s status=0x%02x result=0x%02x msg=\"%s\" %s", 1572 + + cb2000_action_label(action), 1573 + + cb2000_retry_cause_label(cause), 1574 + + self->retry_total, 1575 + + cause_count, 1576 + + cb2000_retry_error_label(retry_error), 1577 + + self->verify_status_code_last, 1578 + + self->verify_result_code_last, 1579 + + retry_msg ? retry_msg : "", 1580 + + detail ? detail : ""); 1581 + + } else { 1582 + + if (cause == CB2000_RETRY_AREA_GATE) 1583 + + retry_msg = "Place finger fully on sensor and try again."; 1584 + + else if (cause == CB2000_RETRY_QUALITY_GATE) 1585 + + retry_msg = "Image quality too low, adjust finger and try again."; 1586 + + else if (cause == CB2000_RETRY_MINUTIAE_PRECHECK) 1587 + + retry_msg = "Adjust finger and try again."; 1588 + + 1589 + + fp_info("[ RETRY ] action=%s cause=%s total=%u cause_count=%u retry_hint=%s msg=\"%s\" %s", 1590 + + cb2000_action_label(action), 1591 + + cb2000_retry_cause_label(cause), 1592 + + self->retry_total, 1593 + + cause_count, 1594 + + cb2000_retry_error_label(retry_error), 1595 + + retry_msg ? retry_msg : "", 1596 + + detail ? detail : ""); 1597 + + } 1598 + + 1599 + + if (cb2000_is_verify_phase_action(action)) { 1600 + + self->verify_retry_pending = TRUE; 1601 + + self->verify_retry_error = retry_error; 1602 + + self->verify_retry_status_code = self->verify_status_code_last; 1603 + + self->verify_retry_result_code = self->verify_result_code_last; 1604 + + if (retry_msg) { 1605 + + g_strlcpy(self->verify_retry_message, retry_msg, 1606 + + sizeof(self->verify_retry_message)); 1607 + + } else { 1608 + + self->verify_retry_message[0] = '\0'; 1609 + + } 1610 + + self->verify_result = FPI_MATCH_ERROR; 1611 + + return; 1612 + + } 1613 + + 1614 + + if (action == FPI_DEVICE_ACTION_CAPTURE) { 1615 + + self->capture_retry_pending = TRUE; 1616 + + self->capture_retry_error = retry_error; 1617 + + if (retry_msg) { 1618 + + g_strlcpy(self->capture_retry_message, retry_msg, 1619 + + sizeof(self->capture_retry_message)); 1620 + + } else { 1621 + + self->capture_retry_message[0] = '\0'; 1622 + + } 1623 + + return; 1624 + + } 1625 + + 1626 + + if (action == FPI_DEVICE_ACTION_ENROLL) { 1627 + + GError *err = NULL; 1628 + + if (retry_msg) { 1629 + + err = fpi_device_retry_new_msg(retry_error, "%s", retry_msg); 1630 + + } else { 1631 + + err = fpi_device_retry_new(retry_error); 1632 + + } 1633 + + fpi_device_enroll_progress(dev, self->enroll_stage, NULL, err); 1634 + + } 1635 + +} 1636 + + 1637 + +typedef struct { 1638 + + FpDevice *dev; 1639 + + FpiSsm *ssm; 1640 + + FpImage *img; 1641 + + FpiDeviceAction action; 1642 + +} Cb2000MinutiaePrecheckCtx; 1643 + + 1644 + +static void 1645 + +cb2000_minutiae_precheck_cb(GObject *source, GAsyncResult *res, gpointer user_data) 1646 + +{ 1647 + + Cb2000MinutiaePrecheckCtx *ctx = user_data; 1648 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(ctx->dev); 1649 + + g_autoptr(GError) error = NULL; 1650 + + 1651 + + if (!fp_image_detect_minutiae_finish(FP_IMAGE(source), res, &error)) { 1652 + + fp_warn("Image screening failed with error %s", 1653 + + error ? error->message : "unknown"); 1654 + + fp_warn("Reject! Image quality too bad. (minutiae precheck)"); 1655 + + g_object_unref(ctx->img); 1656 + + cb2000_retry_scan_with_cause(ctx->dev, 1657 + + CB2000_RETRY_MINUTIAE_PRECHECK, 1658 + + "(precheck)"); 1659 + + fpi_ssm_next_state(ctx->ssm); 1660 + + g_free(ctx); 1661 + + return; 1662 + + } 1663 + + 1664 + + self->accepted_total++; 1665 + + if (ctx->action == FPI_DEVICE_ACTION_CAPTURE) { 1666 + + /* Capture action must complete explicitly after async precheck. */ 1667 + + fpi_device_capture_complete(ctx->dev, ctx->img, NULL); 1668 + + fpi_ssm_mark_completed(ctx->ssm); 1669 + + g_free(ctx); 1670 + + return; 1671 + + } 1672 + + 1673 + + /* For other actions, precheck is disabled by default in V33. */ 1674 + + g_object_unref(ctx->img); 1675 + + fpi_ssm_next_state(ctx->ssm); 1676 + + g_free(ctx); 1677 + +} 1678 + + 1679 + +static void 1680 + +cb2000_dump_binarized_cb(GObject *source, GAsyncResult *res, gpointer user_data) 1681 + +{ 1682 + + FpImage *img = FP_IMAGE(source); 1683 + + g_autoptr(GError) error = NULL; 1684 + + g_autofree gchar *label = user_data; 1685 + + 1686 + + if (!fp_image_detect_minutiae_finish(img, res, &error)) { 1687 + + fp_warn("Binarized dump: minutiae detect failed: %s", 1688 + + error ? error->message : "unknown"); 1689 + + g_object_unref(img); 1690 + + return; 1691 + + } 1692 + + 1693 + + gsize len = 0; 1694 + + const guchar *bin = fp_image_get_binarized(img, &len); 1695 + + if (!bin || len != (gsize)(img->width * img->height)) { 1696 + + fp_warn("Binarized dump: invalid buffer"); 1697 + + g_object_unref(img); 1698 + + return; 1699 + + } 1700 + + 1701 + + g_autofree gchar *dump_dir = g_build_filename( 1702 + + g_get_user_special_dir(G_USER_DIRECTORY_DOWNLOAD), 1703 + + "canvasbio_bin", NULL); 1704 + + g_mkdir_with_parents(dump_dir, 0755); 1705 + + 1706 + + time_t raw = time(NULL); 1707 + + struct tm *tm_info = localtime(&raw); 1708 + + char timestamp[32]; 1709 + + strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", tm_info); 1710 + + 1711 + + g_autofree gchar *dump_name = 1712 + + g_strdup_printf("canvasbio_%s_%s_bin.pgm", label, timestamp); 1713 + + g_autofree gchar *dump_path = g_build_filename(dump_dir, dump_name, NULL); 1714 + + 1715 + + g_autofree guint8 *pgm = g_malloc(len); 1716 + + for (gsize i = 0; i < len; i++) 1717 + + pgm[i] = bin[i] ? 0x00 : 0xff; /* 1=ridge => black, 0=valley => white */ 1718 + + 1719 + + g_autofree gchar *header = 1720 + + g_strdup_printf("P5\n%u %u\n255\n", img->width, img->height); 1721 + + gsize header_len = strlen(header); 1722 + + g_autofree gchar *out = g_malloc(header_len + len); 1723 + + memcpy(out, header, header_len); 1724 + + memcpy(out + header_len, pgm, len); 1725 + + 1726 + + if (g_file_set_contents(dump_path, out, header_len + len, NULL)) 1727 + + fp_info("BIN DUMP: %zu bytes -> %s", header_len + len, dump_path); 1728 + + else 1729 + + fp_warn("BIN DUMP: failed to save %s", dump_path); 1730 + + 1731 + + g_object_unref(img); 1732 + +} 1733 + + 1734 + +/* 1735 + + * Run a table-driven USB command sequence inside a sub-SSM. 1736 + + * The sub-SSM advances one command per callback and completes at CMD_END. 1737 + + */ 1738 + +static void 1739 + +run_command_sequence(FpiSsm *parent_ssm, FpDevice *dev, 1740 + + const char *seq_name, 1741 + + const Cb2000Command *cmds) 1742 + +{ 1743 + + FpiSsm *subsm; 1744 + + Cb2000CmdContext *ctx; 1745 + + gint total = 0; 1746 + + 1747 + + ctx = g_new0(Cb2000CmdContext, 1); 1748 + + ctx->commands = cmds; 1749 + + ctx->seq_name = seq_name ? seq_name : "unnamed_seq"; 1750 + + ctx->cmd_index = 0; 1751 + + while (cmds[total].type != CMD_END) 1752 + + total++; 1753 + + ctx->cmd_total = total; 1754 + + 1755 + + subsm = fpi_ssm_new(dev, cmd_sequence_run, 1); 1756 + + fpi_ssm_set_data(subsm, ctx, g_free); 1757 + + fpi_ssm_start_subsm(parent_ssm, subsm); 1758 + +} 1759 + + 1760 + +static const char * 1761 + +cb2000_cmd_type_to_str(Cb2000CmdType type) 1762 + +{ 1763 + + switch (type) { 1764 + + case CMD_END: 1765 + + return "END"; 1766 + + case CMD_BULK_OUT: 1767 + + return "BULK_OUT"; 1768 + + case CMD_BULK_IN: 1769 + + return "BULK_IN"; 1770 + + case CMD_CTRL_OUT: 1771 + + return "CTRL_OUT"; 1772 + + case CMD_CTRL_IN: 1773 + + return "CTRL_IN"; 1774 + + default: 1775 + + return "UNKNOWN"; 1776 + + } 1777 + +} 1778 + + 1779 + +static gboolean 1780 + +cb2000_is_verify_phase_action(FpiDeviceAction action) 1781 + +{ 1782 + + return action == FPI_DEVICE_ACTION_VERIFY || 1783 + + action == FPI_DEVICE_ACTION_IDENTIFY; 1784 + +} 1785 + + 1786 + +static const char * 1787 + +cb2000_capture_start_seq_name_for_action(FpiDeviceAction action) 1788 + +{ 1789 + + if (cb2000_is_verify_phase_action(action)) 1790 + + return "verify_start"; 1791 + + return "capture_start_enroll"; 1792 + +} 1793 + + 1794 + +static const char * 1795 + +cb2000_capture_finalize_seq_name_for_action(FpiDeviceAction action) 1796 + +{ 1797 + + if (cb2000_is_verify_phase_action(action)) 1798 + + return "verify_finalize"; 1799 + + return "capture_finalize_enroll"; 1800 + +} 1801 + + 1802 + +static const Cb2000Command * 1803 + +cb2000_capture_start_cmds_for_action(FpiDeviceAction action) 1804 + +{ 1805 + + if (cb2000_is_verify_phase_action(action)) 1806 + + return verify_start_cmds; 1807 + + return capture_start_cmds; 1808 + +} 1809 + + 1810 + +static const Cb2000Command * 1811 + +cb2000_capture_finalize_cmds_for_action(FpiDeviceAction action) 1812 + +{ 1813 + + if (cb2000_is_verify_phase_action(action)) 1814 + + return verify_finalize_cmds; 1815 + + return capture_finalize_cmds; 1816 + +} 1817 + + 1818 + +static gboolean 1819 + +cb2000_seq_is_capture_finalize(const char *seq_name) 1820 + +{ 1821 + + return seq_name != NULL && 1822 + + (g_str_has_prefix(seq_name, "capture_finalize") || 1823 + + g_str_has_prefix(seq_name, "verify_finalize")); 1824 + +} 1825 + + 1826 + +/* 1827 + + * Log short response payloads from command-sequence reads. 1828 + + * This is used to correlate verify status bytes with RE/Windows traces. 1829 + + */ 1830 + +static void 1831 + +cb2000_log_cmd_rx_data(FpDevice *dev, 1832 + + const Cb2000CmdContext *ctx, 1833 + + const Cb2000Command *cmd, 1834 + + const FpiUsbTransfer *transfer) 1835 + +{ 1836 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 1837 + + FpiDeviceAction action = fpi_device_get_current_action(dev); 1838 + + gchar hex[3 * 8 + 1]; 1839 + + gsize max_bytes; 1840 + + gsize i; 1841 + + gsize p = 0; 1842 + + 1843 + + if (cmd->type != CMD_BULK_IN && cmd->type != CMD_CTRL_IN) 1844 + + return; 1845 + + if (!cb2000_get_env_bool("CB2000_DEBUG_CMD_RX", TRUE)) 1846 + + return; 1847 + + 1848 + + self->verify_cmd_rx_total++; 1849 + + if (transfer->actual_length == 0) { 1850 + + fp_dbg("[%s] cmd %d/%d %s rx len=0", 1851 + + ctx->seq_name, 1852 + + ctx->cmd_index + 1, 1853 + + ctx->cmd_total, 1854 + + cb2000_cmd_type_to_str(cmd->type)); 1855 + + return; 1856 + + } 1857 + + 1858 + + self->verify_cmd_rx_with_data++; 1859 + + self->verify_cmd_rx_last_len = MIN(transfer->actual_length, sizeof(self->verify_cmd_rx_last)); 1860 + + memcpy(self->verify_cmd_rx_last, transfer->buffer, self->verify_cmd_rx_last_len); 1861 + + 1862 + + max_bytes = MIN(transfer->actual_length, (gsize)8); 1863 + + for (i = 0; i < max_bytes; i++) { 1864 + + if (i > 0 && p < sizeof(hex)) 1865 + + hex[p++] = ':'; 1866 + + if (p + 2 <= sizeof(hex)) 1867 + + p += g_snprintf(hex + p, sizeof(hex) - p, "%02x", transfer->buffer[i]); 1868 + + } 1869 + + hex[MIN(p, sizeof(hex) - 1)] = '\0'; 1870 + + 1871 + + fp_dbg("[%s] cmd %d/%d %s rx len=%zu data=%s", 1872 + + ctx->seq_name, 1873 + + ctx->cmd_index + 1, 1874 + + ctx->cmd_total, 1875 + + cb2000_cmd_type_to_str(cmd->type), 1876 + + transfer->actual_length, 1877 + + hex); 1878 + + 1879 + + if (cb2000_is_verify_phase_action(action) && 1880 + + cb2000_seq_is_capture_finalize(ctx->seq_name) && 1881 + + (ctx->cmd_index == 5 || ctx->cmd_index == 8)) { 1882 + + guint8 *dst = (ctx->cmd_index == 5) ? self->finalize_ack1 : self->finalize_ack2; 1883 + + gsize *dst_len = (ctx->cmd_index == 5) ? &self->finalize_ack1_len : &self->finalize_ack2_len; 1884 + + gsize copy_len = MIN(transfer->actual_length, (gsize)4); 1885 + + memset(dst, 0, 4); 1886 + + memcpy(dst, transfer->buffer, copy_len); 1887 + + *dst_len = copy_len; 1888 + + 1889 + + fp_info("[ VERIFY_STATUS ] finalize_ack_%u len=%zu data=%s", 1890 + + (ctx->cmd_index == 5) ? 1U : 2U, 1891 + + transfer->actual_length, 1892 + + hex); 1893 + + } 1894 + + 1895 + + if (cb2000_is_verify_phase_action(action) && 1896 + + g_str_has_prefix(ctx->seq_name, "verify_ready_query_a") && 1897 + + ctx->cmd_index == 0) { 1898 + + gsize copy_len = MIN(transfer->actual_length, (gsize)4); 1899 + + memset(self->verify_ready_query_data, 0, sizeof(self->verify_ready_query_data)); 1900 + + memcpy(self->verify_ready_query_data, transfer->buffer, copy_len); 1901 + + self->verify_ready_query_len = copy_len; 1902 + + self->verify_ready_query_status_last = (copy_len > 0) ? transfer->buffer[0] : 0x00; 1903 + + self->verify_ready_query_total++; 1904 + + 1905 + + fp_info("[ VERIFY_READY_QUERY_A ] len=%zu data=%s class=%s", 1906 + + transfer->actual_length, 1907 + + hex, 1908 + + cb2000_verify_ready_query_class_label(self->verify_ready_query_status_last)); 1909 + + } 1910 + + 1911 + + if (cb2000_is_verify_phase_action(action) && 1912 + + g_str_has_prefix(ctx->seq_name, "verify_ready_query_b") && 1913 + + ctx->cmd_index == 0) { 1914 + + gsize copy_len = MIN(transfer->actual_length, (gsize)4); 1915 + + memset(self->verify_ready_query_b_data, 0, sizeof(self->verify_ready_query_b_data)); 1916 + + memcpy(self->verify_ready_query_b_data, transfer->buffer, copy_len); 1917 + + self->verify_ready_query_b_len = copy_len; 1918 + + self->verify_ready_query_b_status_last = (copy_len > 0) ? transfer->buffer[0] : 0x00; 1919 + + self->verify_ready_query_b_total++; 1920 + + 1921 + + fp_info("[ VERIFY_READY_QUERY_B ] len=%zu data=%s class=%s", 1922 + + transfer->actual_length, 1923 + + hex, 1924 + + cb2000_verify_ready_query_class_label(self->verify_ready_query_b_status_last)); 1925 + + } 1926 + + 1927 + + if (cb2000_is_verify_phase_action(action) && 1928 + + g_str_has_prefix(ctx->seq_name, "verify_start") && 1929 + + cmd->type == CMD_BULK_IN && 1930 + + transfer->actual_length == 4 && 1931 + + self->verify_pre_capture_status_len == 0) { 1932 + + self->verify_pre_capture_status_len = 4; 1933 + + memcpy(self->verify_pre_capture_status, transfer->buffer, 4); 1934 + + self->verify_pre_capture_status_total++; 1935 + + fp_info("[ VERIFY_PRE_CAPTURE ] len=%zu data=%s", 1936 + + transfer->actual_length, hex); 1937 + + } 1938 + +} 1939 + + 1940 + +static const char * 1941 + +cb2000_verify_ack_decision_label(Cb2000VerifyAckDecision decision) 1942 + +{ 1943 + + switch (decision) { 1944 + + case CB2000_VERIFY_ACK_READY: 1945 + + return "READY"; 1946 + + case CB2000_VERIFY_ACK_RETRY: 1947 + + return "RETRY"; 1948 + + case CB2000_VERIFY_ACK_DEVICE_NOMATCH: 1949 + + return "DEVICE_NOMATCH"; 1950 + + case CB2000_VERIFY_ACK_UNKNOWN: 1951 + + default: 1952 + + return "UNKNOWN"; 1953 + + } 1954 + +} 1955 + + 1956 + +static const char * 1957 + +cb2000_verify_route_label(Cb2000VerifyRoute route) 1958 + +{ 1959 + + switch (route) { 1960 + + case CB2000_VERIFY_ROUTE_HOST_NBIS: 1961 + + return "HOST_NCC"; 1962 + + case CB2000_VERIFY_ROUTE_DEVICE_STATUS: 1963 + + return "DEVICE_STATUS"; 1964 + + case CB2000_VERIFY_ROUTE_DEVICE_NOMATCH: 1965 + + return "DEVICE_NOMATCH"; 1966 + + case CB2000_VERIFY_ROUTE_RETRY_GATE: 1967 + + return "RETRY_GATE"; 1968 + + case CB2000_VERIFY_ROUTE_UNKNOWN: 1969 + + default: 1970 + + return "UNKNOWN"; 1971 + + } 1972 + +} 1973 + + 1974 + +static const char * 1975 + +cb2000_verify_result_class_label(Cb2000VerifyResultClass klass) 1976 + +{ 1977 + + switch (klass) { 1978 + + case CB2000_VERIFY_RESULT_CLASS_READY: 1979 + + return "READY"; 1980 + + case CB2000_VERIFY_RESULT_CLASS_RETRY: 1981 + + return "RETRY"; 1982 + + case CB2000_VERIFY_RESULT_CLASS_DEVICE_NOMATCH: 1983 + + return "DEVICE_NOMATCH"; 1984 + + case CB2000_VERIFY_RESULT_CLASS_COUNT: 1985 + + case CB2000_VERIFY_RESULT_CLASS_UNKNOWN: 1986 + + default: 1987 + + return "UNKNOWN"; 1988 + + } 1989 + +} 1990 + + 1991 + +/* 1992 + + * Normalize verify status/result byte pairs into a stable class. 1993 + + * This class is used for routing telemetry and for optional short-circuit 1994 + + * handling when device status is explicit enough to skip host NBIS. 1995 + + */ 1996 + +static Cb2000VerifyResultClass 1997 + +cb2000_classify_verify_result_codes(guint8 status_code, guint8 result_code) 1998 + +{ 1999 + + if (status_code == 0xff && result_code == 0x0f) 2000 + + return CB2000_VERIFY_RESULT_CLASS_READY; 2001 + + if (cb2000_is_verify_retry_status_pair(status_code, result_code)) 2002 + + return CB2000_VERIFY_RESULT_CLASS_RETRY; 2003 + + if (status_code == 0xff && result_code == 0x0b) 2004 + + return CB2000_VERIFY_RESULT_CLASS_DEVICE_NOMATCH; 2005 + + return CB2000_VERIFY_RESULT_CLASS_UNKNOWN; 2006 + +} 2007 + + 2008 + +static const char * 2009 + +cb2000_verify_ready_query_class_label(guint8 status0) 2010 + +{ 2011 + + switch (status0) { 2012 + + case 0x00: 2013 + + return "READY"; 2014 + + case 0x02: 2015 + + return "RETRY_02"; 2016 + + case 0x0b: 2017 + + return "NOMATCH_0B"; 2018 + + default: 2019 + + return "UNKNOWN"; 2020 + + } 2021 + +} 2022 + + 2023 + +static const char * 2024 + +cb2000_verify_ready_matrix_label(Cb2000VerifyReadyMatrixDecision decision) 2025 + +{ 2026 + + switch (decision) { 2027 + + case CB2000_VERIFY_READY_MATRIX_READY: 2028 + + return "READY_FALLBACK_HOST"; 2029 + + case CB2000_VERIFY_READY_MATRIX_RETRY: 2030 + + return "RETRY"; 2031 + + case CB2000_VERIFY_READY_MATRIX_DEVICE_NOMATCH: 2032 + + return "DEVICE_NOMATCH"; 2033 + + case CB2000_VERIFY_READY_MATRIX_UNKNOWN: 2034 + + default: 2035 + + return "UNKNOWN_FALLBACK_HOST"; 2036 + + } 2037 + +} 2038 + + 2039 + +/* 2040 + + * V29 READY matrix decision. 2041 + + * Priority is conservative: 2042 + + * 1) any 0x02 -> RETRY 2043 + + * 2) any 0x0b -> DEVICE_NOMATCH 2044 + + * 3) A=0x00 and (B missing or B=0x00) -> READY fallback to host NBIS 2045 + + * 4) otherwise -> UNKNOWN fallback to host NBIS 2046 + + */ 2047 + +static Cb2000VerifyReadyMatrixDecision 2048 + +cb2000_decide_ready_query_matrix(guint8 status_a, gboolean has_b, guint8 status_b) 2049 + +{ 2050 + + if (status_a == 0x02 || (has_b && status_b == 0x02)) 2051 + + return CB2000_VERIFY_READY_MATRIX_RETRY; 2052 + + if (status_a == 0x0b || (has_b && status_b == 0x0b)) 2053 + + return CB2000_VERIFY_READY_MATRIX_DEVICE_NOMATCH; 2054 + + if (status_a == 0x00 && (!has_b || status_b == 0x00)) 2055 + + return CB2000_VERIFY_READY_MATRIX_READY; 2056 + + return CB2000_VERIFY_READY_MATRIX_UNKNOWN; 2057 + +} 2058 + + 2059 + +/* 2060 + + * Map finalize ACKs to a verify routing decision. 2061 + + * Runtime evidence so far: 2062 + + * ff:00:ff:0f -> capture ready, proceed to host matcher 2063 + + * ff:00:00:00 -> retry 2064 + + * ff:00:ff:0c -> retry (observed in poor/partial captures) 2065 + + * ff:00:f0:0f, ff:00:f3:0f, ff:00:88:08, ff:00:11:01, 2066 + + * ff:00:08:00, ff:00:20:0f, ff:00:00:08, ff:00:11:00, 2067 + + * ff:00:30:0f -> retry (Windows verify failures, 2026-02-16) 2068 + + * 2069 + + * Hypothesis from Windows RE: 2070 + + * ff:00:ff:0b -> explicit device no-match status 2071 + + */ 2072 + +static Cb2000VerifyAckDecision 2073 + +cb2000_classify_finalize_ack(FpiDeviceCanvasbioCb2000 *self, 2074 + + guint8 *status_hint, 2075 + + gboolean *mismatch_out) 2076 + +{ 2077 + + gboolean mismatch = FALSE; 2078 + + guint8 status = 0x00; 2079 + + guint8 result = 0x00; 2080 + + Cb2000VerifyResultClass klass; 2081 + + 2082 + + if (self->finalize_ack1_len < 4 || self->finalize_ack2_len < 4) 2083 + + return CB2000_VERIFY_ACK_UNKNOWN; 2084 + + 2085 + + mismatch = memcmp(self->finalize_ack1, self->finalize_ack2, 4) != 0; 2086 + + if (mismatch) { 2087 + + if (mismatch_out) 2088 + + *mismatch_out = TRUE; 2089 + + return CB2000_VERIFY_ACK_RETRY; 2090 + + } 2091 + + 2092 + + status = self->finalize_ack1[2]; 2093 + + result = self->finalize_ack1[3]; 2094 + + if (status_hint) 2095 + + *status_hint = status; 2096 + + if (mismatch_out) 2097 + + *mismatch_out = FALSE; 2098 + + 2099 + + if (self->finalize_ack1[0] != 0xff || self->finalize_ack1[1] != 0x00) 2100 + + return CB2000_VERIFY_ACK_UNKNOWN; 2101 + + 2102 + + klass = cb2000_classify_verify_result_codes(status, result); 2103 + + switch (klass) { 2104 + + case CB2000_VERIFY_RESULT_CLASS_READY: 2105 + + return CB2000_VERIFY_ACK_READY; 2106 + + case CB2000_VERIFY_RESULT_CLASS_RETRY: 2107 + + return CB2000_VERIFY_ACK_RETRY; 2108 + + case CB2000_VERIFY_RESULT_CLASS_DEVICE_NOMATCH: 2109 + + return CB2000_VERIFY_ACK_DEVICE_NOMATCH; 2110 + + case CB2000_VERIFY_RESULT_CLASS_COUNT: 2111 + + case CB2000_VERIFY_RESULT_CLASS_UNKNOWN: 2112 + + default: 2113 + + break; 2114 + + } 2115 + + 2116 + + return CB2000_VERIFY_ACK_UNKNOWN; 2117 + +} 2118 + + 2119 + +/* 2120 + + * Gate verify/identify routing using capture_finalize ACK bytes. 2121 + + * This performs only "early exits" (retry). Match/no-match decisions for 2122 + + * experimental device-assisted flow are handled later by 2123 + + * cb2000_try_device_assisted_verify(). 2124 + + */ 2125 + +static gboolean 2126 + +cb2000_verify_finalize_ack_gate(FpDevice *dev, 2127 + + FpiDeviceCanvasbioCb2000 *self) 2128 + +{ 2129 + + Cb2000VerifyAckDecision decision; 2130 + + Cb2000VerifyResultClass result_class = CB2000_VERIFY_RESULT_CLASS_UNKNOWN; 2131 + + guint8 status_hint = 0x00; 2132 + + gboolean ack_mismatch = FALSE; 2133 + + 2134 + + if (self->finalize_ack1_len < 4 || self->finalize_ack2_len < 4) { 2135 + + fp_info("[ VERIFY_GATE ] ack incomplete (ack1_len=%zu ack2_len=%zu) -> allow matcher", 2136 + + self->finalize_ack1_len, self->finalize_ack2_len); 2137 + + self->verify_ack_decision_last = CB2000_VERIFY_ACK_UNKNOWN; 2138 + + self->verify_ack_status_last = 0x00; 2139 + + self->verify_status_code_last = 0x00; 2140 + + self->verify_result_code_last = 0x00; 2141 + + self->verify_route_last = CB2000_VERIFY_ROUTE_UNKNOWN; 2142 + + self->verify_result_class_last = CB2000_VERIFY_RESULT_CLASS_UNKNOWN; 2143 + + self->verify_result_class_count[CB2000_VERIFY_RESULT_CLASS_UNKNOWN]++; 2144 + + self->verify_ack_unknown_total++; 2145 + + return FALSE; 2146 + + } 2147 + + 2148 + + self->verify_status_code_last = self->finalize_ack1[2]; 2149 + + self->verify_result_code_last = self->finalize_ack1[3]; 2150 + + result_class = cb2000_classify_verify_result_codes(self->verify_status_code_last, 2151 + + self->verify_result_code_last); 2152 + + self->verify_result_class_last = result_class; 2153 + + self->verify_result_class_count[result_class]++; 2154 + + 2155 + + decision = cb2000_classify_finalize_ack(self, &status_hint, &ack_mismatch); 2156 + + self->verify_ack_decision_last = decision; 2157 + + self->verify_ack_status_last = status_hint; 2158 + + 2159 + + switch (decision) { 2160 + + case CB2000_VERIFY_ACK_READY: 2161 + + self->verify_ack_ready_total++; 2162 + + break; 2163 + + case CB2000_VERIFY_ACK_RETRY: 2164 + + self->verify_ack_retry_total++; 2165 + + break; 2166 + + case CB2000_VERIFY_ACK_DEVICE_NOMATCH: 2167 + + self->verify_ack_nomatch_total++; 2168 + + break; 2169 + + case CB2000_VERIFY_ACK_UNKNOWN: 2170 + + default: 2171 + + self->verify_ack_unknown_total++; 2172 + + break; 2173 + + } 2174 + + 2175 + + if (ack_mismatch) 2176 + + self->verify_ack_mismatch_total++; 2177 + + 2178 + + fp_info("[ VERIFY_GATE ] ack1=%02x:%02x:%02x:%02x ack2=%02x:%02x:%02x:%02x decision=%s result_class=%s status=0x%02x result=0x%02x mismatch=%d", 2179 + + self->finalize_ack1[0], self->finalize_ack1[1], 2180 + + self->finalize_ack1[2], self->finalize_ack1[3], 2181 + + self->finalize_ack2[0], self->finalize_ack2[1], 2182 + + self->finalize_ack2[2], self->finalize_ack2[3], 2183 + + cb2000_verify_ack_decision_label(decision), 2184 + + cb2000_verify_result_class_label(self->verify_result_class_last), 2185 + + status_hint, 2186 + + self->verify_result_code_last, 2187 + + ack_mismatch); 2188 + + 2189 + + if (decision == CB2000_VERIFY_ACK_RETRY) { 2190 + + cb2000_retry_scan_with_cause(dev, CB2000_RETRY_DEVICE_STATUS, 2191 + + "(verify finalize ack gate)"); 2192 + + return TRUE; 2193 + + } 2194 + + 2195 + + fp_info("[ VERIFY_GATE ] non-retry decision -> allow matcher/device-assisted flow"); 2196 + + return FALSE; 2197 + +} 2198 + + 2199 + +/* 2200 + + * Execute the next command in the sequence by mapping each entry to a 2201 + + * libfprint USB transfer. The callback advances the state machine. 2202 + + */ 2203 + +static void 2204 + +cmd_sequence_run(FpiSsm *ssm, FpDevice *dev) 2205 + +{ 2206 + + Cb2000CmdContext *ctx = fpi_ssm_get_data(ssm); 2207 + + const Cb2000Command *cmd = &ctx->commands[ctx->cmd_index]; 2208 + + FpiUsbTransfer *transfer; 2209 + + 2210 + + if (cmd->type == CMD_END) { 2211 + + fp_dbg("Command sequence complete (%d commands)", ctx->cmd_total); 2212 + + fpi_ssm_mark_completed(ssm); 2213 + + return; 2214 + + } 2215 + + 2216 + + transfer = fpi_usb_transfer_new(dev); 2217 + + transfer->ssm = ssm; 2218 + + 2219 + + switch (cmd->type) { 2220 + + case CMD_BULK_OUT: 2221 + + if (cmd->data != NULL && cmd->len >= 4) { 2222 + + fp_dbg("[%s] cmd %d/%d %s len=%zu data=%02x:%02x:%02x:%02x", 2223 + + ctx->seq_name, 2224 + + ctx->cmd_index + 1, 2225 + + ctx->cmd_total, 2226 + + cb2000_cmd_type_to_str(cmd->type), 2227 + + cmd->len, 2228 + + cmd->data[0], cmd->data[1], cmd->data[2], cmd->data[3]); 2229 + + } else if (cmd->data != NULL && cmd->len == 3) { 2230 + + fp_dbg("[%s] cmd %d/%d %s len=%zu data=%02x:%02x:%02x", 2231 + + ctx->seq_name, 2232 + + ctx->cmd_index + 1, 2233 + + ctx->cmd_total, 2234 + + cb2000_cmd_type_to_str(cmd->type), 2235 + + cmd->len, 2236 + + cmd->data[0], cmd->data[1], cmd->data[2]); 2237 + + } else if (cmd->data != NULL && cmd->len == 2) { 2238 + + fp_dbg("[%s] cmd %d/%d %s len=%zu data=%02x:%02x", 2239 + + ctx->seq_name, 2240 + + ctx->cmd_index + 1, 2241 + + ctx->cmd_total, 2242 + + cb2000_cmd_type_to_str(cmd->type), 2243 + + cmd->len, 2244 + + cmd->data[0], cmd->data[1]); 2245 + + } else if (cmd->data != NULL && cmd->len == 1) { 2246 + + fp_dbg("[%s] cmd %d/%d %s len=%zu data=%02x", 2247 + + ctx->seq_name, 2248 + + ctx->cmd_index + 1, 2249 + + ctx->cmd_total, 2250 + + cb2000_cmd_type_to_str(cmd->type), 2251 + + cmd->len, 2252 + + cmd->data[0]); 2253 + + } else { 2254 + + fp_dbg("[%s] cmd %d/%d %s len=%zu", 2255 + + ctx->seq_name, 2256 + + ctx->cmd_index + 1, 2257 + + ctx->cmd_total, 2258 + + cb2000_cmd_type_to_str(cmd->type), 2259 + + cmd->len); 2260 + + } 2261 + + fpi_usb_transfer_fill_bulk_full(transfer, CB2000_EP_OUT, 2262 + + (guint8 *)cmd->data, cmd->len, NULL); 2263 + + transfer->short_is_error = TRUE; 2264 + + break; 2265 + + case CMD_BULK_IN: 2266 + + fp_dbg("[%s] cmd %d/%d %s len=%zu", 2267 + + ctx->seq_name, 2268 + + ctx->cmd_index + 1, 2269 + + ctx->cmd_total, 2270 + + cb2000_cmd_type_to_str(cmd->type), 2271 + + cmd->len); 2272 + + fpi_usb_transfer_fill_bulk(transfer, CB2000_EP_IN, cmd->len); 2273 + + break; 2274 + + case CMD_CTRL_OUT: 2275 + + fp_dbg("[%s] cmd %d/%d %s req=0x%02x value=0x%04x index=%u", 2276 + + ctx->seq_name, 2277 + + ctx->cmd_index + 1, 2278 + + ctx->cmd_total, 2279 + + cb2000_cmd_type_to_str(cmd->type), 2280 + + cmd->request, cmd->value, cmd->index); 2281 + + fpi_usb_transfer_fill_control(transfer, 2282 + + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, 2283 + + G_USB_DEVICE_REQUEST_TYPE_VENDOR, 2284 + + G_USB_DEVICE_RECIPIENT_DEVICE, 2285 + + cmd->request, cmd->value, cmd->index, 0); 2286 + + break; 2287 + + case CMD_CTRL_IN: 2288 + + fp_dbg("[%s] cmd %d/%d %s req=0x%02x value=0x%04x index=%u len=%zu", 2289 + + ctx->seq_name, 2290 + + ctx->cmd_index + 1, 2291 + + ctx->cmd_total, 2292 + + cb2000_cmd_type_to_str(cmd->type), 2293 + + cmd->request, cmd->value, cmd->index, cmd->len); 2294 + + fpi_usb_transfer_fill_control(transfer, 2295 + + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, 2296 + + G_USB_DEVICE_REQUEST_TYPE_VENDOR, 2297 + + G_USB_DEVICE_RECIPIENT_DEVICE, 2298 + + cmd->request, cmd->value, cmd->index, 2299 + + cmd->len); 2300 + + break; 2301 + + case CMD_END: 2302 + + g_assert_not_reached(); 2303 + + return; 2304 + + } 2305 + + 2306 + + fpi_usb_transfer_submit(transfer, CB2000_TIMEOUT, NULL, 2307 + + cmd_transfer_cb, NULL); 2308 + +} 2309 + + 2310 + +/* 2311 + + * Command transfer completion: on success, advance to the next command; 2312 + + * on failure, abort the sequence and bubble up the error. 2313 + + */ 2314 + +static void 2315 + +cmd_transfer_cb(FpiUsbTransfer *transfer, 2316 + + FpDevice *dev, 2317 + + gpointer user_data, 2318 + + GError *error) 2319 + +{ 2320 + + Cb2000CmdContext *ctx; 2321 + + const Cb2000Command *cmd; 2322 + + 2323 + + if (error) { 2324 + + fp_warn("Command transfer failed: %s", error->message); 2325 + + fpi_ssm_mark_failed(transfer->ssm, error); 2326 + + return; 2327 + + } 2328 + + 2329 + + ctx = fpi_ssm_get_data(transfer->ssm); 2330 + + cmd = &ctx->commands[ctx->cmd_index]; 2331 + + cb2000_log_cmd_rx_data(dev, ctx, cmd, transfer); 2332 + + ctx->cmd_index++; 2333 + + fpi_ssm_jump_to_state(transfer->ssm, 0); 2334 + +} 2335 + + 2336 + +/* ============================================================================ 2337 + + * USB TRANSFER HELPERS 2338 + + * ============================================================================ */ 2339 + + 2340 + +/* 2341 + + * Submit a vendor control IN request and bind it to an SSM. 2342 + + * The SSM will keep ownership of the transfer lifecycle. 2343 + + */ 2344 + +static void 2345 + +cb2000_ctrl_in(FpDevice *dev, 2346 + + FpiSsm *ssm, 2347 + + guint8 request, 2348 + + guint16 value, 2349 + + guint16 index, 2350 + + gsize len, 2351 + + FpiUsbTransferCallback callback, 2352 + + gpointer user_data) 2353 + +{ 2354 + + FpiUsbTransfer *transfer = fpi_usb_transfer_new(dev); 2355 + + 2356 + + fpi_usb_transfer_fill_control(transfer, 2357 + + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, 2358 + + G_USB_DEVICE_REQUEST_TYPE_VENDOR, 2359 + + G_USB_DEVICE_RECIPIENT_DEVICE, 2360 + + request, value, index, len); 2361 + + transfer->ssm = ssm; 2362 + + fpi_usb_transfer_submit(transfer, CB2000_TIMEOUT, NULL, 2363 + + callback, user_data); 2364 + +} 2365 + + 2366 + +/* 2367 + + * Submit a bulk read for a fixed-length chunk from the sensor. 2368 + + */ 2369 + +static void 2370 + +cb2000_bulk_read(FpDevice *dev, 2371 + + FpiSsm *ssm, 2372 + + gsize len, 2373 + + FpiUsbTransferCallback callback, 2374 + + gpointer user_data) 2375 + +{ 2376 + + FpiUsbTransfer *transfer = fpi_usb_transfer_new(dev); 2377 + + 2378 + + fpi_usb_transfer_fill_bulk(transfer, CB2000_EP_IN, len); 2379 + + transfer->ssm = ssm; 2380 + + fpi_usb_transfer_submit(transfer, CB2000_TIMEOUT, NULL, 2381 + + callback, user_data); 2382 + +} 2383 + + 2384 + +/* 2385 + + * Submit a bulk write. Used for padding "keepalive" writes between reads. 2386 + + */ 2387 + +static void 2388 + +cb2000_bulk_write(FpDevice *dev, 2389 + + FpiSsm *ssm, 2390 + + const guint8 *data, 2391 + + gsize len, 2392 + + FpiUsbTransferCallback callback, 2393 + + gpointer user_data) 2394 + +{ 2395 + + FpiUsbTransfer *transfer = fpi_usb_transfer_new(dev); 2396 + + 2397 + + fpi_usb_transfer_fill_bulk_full(transfer, CB2000_EP_OUT, 2398 + + (guint8 *)data, len, NULL); 2399 + + transfer->ssm = ssm; 2400 + + transfer->short_is_error = TRUE; 2401 + + fpi_usb_transfer_submit(transfer, CB2000_TIMEOUT, NULL, 2402 + + callback, user_data); 2403 + +} 2404 + + 2405 + +/* ============================================================================ 2406 + + * ACTIVATION SUB-SSM 2407 + + * ============================================================================ */ 2408 + + 2409 + +static void 2410 + +activate_run_state(FpiSsm *ssm, FpDevice *dev) 2411 + +{ 2412 + + switch (fpi_ssm_get_cur_state(ssm)) { 2413 + + case STATE_ACTIVATE_WAKE_SEQ: 2414 + + fp_dbg("Activation: WAKE_SEQ (9 commands)"); 2415 + + run_command_sequence(ssm, dev, "activation_wake", activation_wake_cmds); 2416 + + break; 2417 + + case STATE_ACTIVATE_REGS_SEQ: 2418 + + fp_dbg("Activation: REGS_SEQ (42 commands)"); 2419 + + run_command_sequence(ssm, dev, "activation_regs", activation_regs_cmds); 2420 + + break; 2421 + + case STATE_ACTIVATE_TRIGGER_SEQ: 2422 + + fp_dbg("Activation: TRIGGER_SEQ (7 commands)"); 2423 + + run_command_sequence(ssm, dev, "activation_trigger", activation_trigger_cmds); 2424 + + break; 2425 + + case STATE_ACTIVATE_CAPTURE_SEQ: 2426 + + fp_dbg("Activation: CAPTURE_SEQ (27 commands)"); 2427 + + run_command_sequence(ssm, dev, "activation_capture", activation_capture_cmds); 2428 + + break; 2429 + + case STATE_ACTIVATE_FINALIZE_SEQ: 2430 + + fp_dbg("Activation: FINALIZE_SEQ (11 commands)"); 2431 + + run_command_sequence(ssm, dev, "activation_finalize", activation_finalize_cmds); 2432 + + break; 2433 + + } 2434 + +} 2435 + + 2436 + +/* ============================================================================ 2437 + + * FINGER POLLING SUB-SSM 2438 + + * ============================================================================ */ 2439 + + 2440 + +static void poll_finger_run_state(FpiSsm *ssm, FpDevice *dev); 2441 + + 2442 + +/* 2443 + + * Delay step for finger polling. This keeps the polling period stable and 2444 + + * avoids hammering the USB control endpoint. 2445 + + */ 2446 + +static gboolean 2447 + +poll_finger_delay_cb(gpointer user_data) 2448 + +{ 2449 + + FpiSsm *ssm = user_data; 2450 + + FpDevice *dev = fpi_ssm_get_device(ssm); 2451 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 2452 + + 2453 + + self->poll_timeout_id = 0; 2454 + + 2455 + + if (self->deactivating || self->deactivation_in_progress) { 2456 + + return G_SOURCE_REMOVE; 2457 + + } 2458 + + 2459 + + fpi_ssm_next_state(ssm); 2460 + + return G_SOURCE_REMOVE; 2461 + +} 2462 + + 2463 + +/* 2464 + + * Poll response handler. Implements debounce and stale-timeout detection. 2465 + + * The device reports finger presence via two status bytes; we treat either 2466 + + * bit as a valid presence signal for safety. 2467 + + */ 2468 + +static void 2469 + +poll_finger_result_cb(FpiUsbTransfer *transfer, 2470 + + FpDevice *dev, 2471 + + gpointer user_data, 2472 + + GError *error) 2473 + +{ 2474 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 2475 + + gint64 now_us = g_get_monotonic_time(); 2476 + + 2477 + + if (self->deactivating || self->deactivation_in_progress) { 2478 + + return; 2479 + + } 2480 + + if (error) { 2481 + + fpi_ssm_mark_failed(transfer->ssm, error); 2482 + + return; 2483 + + } 2484 + + 2485 + + if (transfer->actual_length < 2) { 2486 + + fp_dbg("Poll: short read (%zu bytes), retrying", 2487 + + transfer->actual_length); 2488 + + fpi_ssm_jump_to_state(transfer->ssm, POLL_FINGER_DELAY); 2489 + + return; 2490 + + } 2491 + + 2492 + + /* The device can signal presence via either byte. */ 2493 + + gboolean finger_present = (transfer->buffer[1] != 0) || 2494 + + (transfer->buffer[0] == 0x01); 2495 + + self->poll_total_count++; 2496 + + 2497 + + fp_dbg("Poll response: len=%zu [0]=%02x [1]=%02x (hits=%d streak=%d)", 2498 + + transfer->actual_length, 2499 + + transfer->buffer[0], transfer->buffer[1], 2500 + + self->poll_stable_hits, self->no_finger_streak); 2501 + + if (self->poll_total_count == 0) { 2502 + + fp_dbg("WAIT_FINGER started: t0=%" G_GINT64_FORMAT "us", 2503 + + self->poll_start_us); 2504 + + } 2505 + + 2506 + + if (self->poll_total_count > 0 && (self->poll_total_count % 10) == 0) { 2507 + + fp_dbg("WAIT_FINGER timing: poll=%u t=+%" G_GINT64_FORMAT "us", 2508 + + self->poll_total_count, (now_us - self->poll_start_us)); 2509 + + } 2510 + + 2511 + + if ((now_us - self->poll_start_us) > 2512 + + (gint64)CB2000_POLL_STALE_TIMEOUT_MS * 1000) { 2513 + + fp_warn("WAIT_FINGER stale (>%dms) - triggering recovery", 2514 + + CB2000_POLL_STALE_TIMEOUT_MS); 2515 + + fpi_ssm_mark_failed(transfer->ssm, 2516 + + fpi_device_error_new_msg(FP_DEVICE_ERROR_PROTO, 2517 + + "Finger polling stale")); 2518 + + return; 2519 + + } 2520 + + 2521 + + if (finger_present) { 2522 + + self->no_finger_streak = 0; 2523 + + self->poll_stable_hits++; 2524 + + 2525 + + /* Disabled for Windows parity (no early-placement delay). */ 2526 + + if (self->poll_total_count <= CB2000_EARLY_PLACEMENT_POLLS) { 2527 + + fp_dbg("Early placement gate disabled (poll=%d)", 2528 + + self->poll_total_count); 2529 + + } 2530 + + 2531 + + if (self->poll_stable_hits >= CB2000_POLL_DEBOUNCE_COUNT) { 2532 + + fp_dbg("Finger detected (stable, hits=%d)", self->poll_stable_hits); 2533 + + fpi_ssm_mark_completed(transfer->ssm); 2534 + + return; 2535 + + } 2536 + + 2537 + + fp_dbg("Finger detected but debouncing (%d/%d)", 2538 + + self->poll_stable_hits, CB2000_POLL_DEBOUNCE_COUNT); 2539 + + } else { 2540 + + self->poll_stable_hits = 0; 2541 + + self->no_finger_streak++; 2542 + + } 2543 + + 2544 + + /* Loop: back to delay */ 2545 + + fpi_ssm_jump_to_state(transfer->ssm, POLL_FINGER_DELAY); 2546 + +} 2547 + + 2548 + +/* 2549 + + * Polling sub-SSM entry point. Alternates between a fixed delay and a 2550 + + * control IN request. 2551 + + */ 2552 + +static void 2553 + +poll_finger_run_state(FpiSsm *ssm, FpDevice *dev) 2554 + +{ 2555 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 2556 + + 2557 + + if (self->deactivating || self->deactivation_in_progress) { 2558 + + return; 2559 + + } 2560 + + 2561 + + switch (fpi_ssm_get_cur_state(ssm)) { 2562 + + case POLL_FINGER_DELAY: 2563 + + self->poll_timeout_id = g_timeout_add(CB2000_POLL_INTERVAL, 2564 + + poll_finger_delay_cb, ssm); 2565 + + break; 2566 + + case POLL_FINGER_SEND: 2567 + + fp_dbg("Polling REQ_POLL (wValue=0x0007 wIndex=0 len=2)"); 2568 + + cb2000_ctrl_in(dev, ssm, 2569 + + REQ_POLL, 0x0007, 0, 2, 2570 + + poll_finger_result_cb, NULL); 2571 + + break; 2572 + + } 2573 + +} 2574 + + 2575 + +/* ============================================================================ 2576 + + * REMOVAL POLLING SUB-SSM 2577 + + * ============================================================================ */ 2578 + + 2579 + +static void poll_removal_run_state(FpiSsm *ssm, FpDevice *dev); 2580 + + 2581 + +/* 2582 + + * Delay step for finger removal polling. Uses a longer interval to reduce 2583 + + * USB traffic once an image has already been captured. 2584 + + */ 2585 + +static gboolean 2586 + +poll_removal_delay_cb(gpointer user_data) 2587 + +{ 2588 + + FpiSsm *ssm = user_data; 2589 + + FpDevice *dev = fpi_ssm_get_device(ssm); 2590 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 2591 + + 2592 + + self->removal_timeout_id = 0; 2593 + + 2594 + + if (self->deactivating || self->deactivation_in_progress) { 2595 + + return G_SOURCE_REMOVE; 2596 + + } 2597 + + 2598 + + fpi_ssm_next_state(ssm); 2599 + + return G_SOURCE_REMOVE; 2600 + +} 2601 + + 2602 + +/* 2603 + + * Removal response handler. Requires N consecutive "no finger" readings 2604 + + * to declare removal, to avoid flicker. 2605 + + */ 2606 + +static void 2607 + +poll_removal_result_cb(FpiUsbTransfer *transfer, 2608 + + FpDevice *dev, 2609 + + gpointer user_data, 2610 + + GError *error) 2611 + +{ 2612 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 2613 + + gint64 now_us = g_get_monotonic_time(); 2614 + + 2615 + + if (self->deactivating || self->deactivation_in_progress) { 2616 + + return; 2617 + + } 2618 + + if (error) { 2619 + + fpi_ssm_mark_failed(transfer->ssm, error); 2620 + + return; 2621 + + } 2622 + + 2623 + + if (transfer->actual_length < 2) { 2624 + + fpi_ssm_jump_to_state(transfer->ssm, POLL_REMOVAL_DELAY); 2625 + + return; 2626 + + } 2627 + + 2628 + + gboolean finger_present = (transfer->buffer[1] != 0); 2629 + + fp_dbg("Removal poll: [0]=%02x [1]=%02x finger_present=%d", 2630 + + transfer->buffer[0], transfer->buffer[1], finger_present); 2631 + + 2632 + + self->removal_poll_count++; 2633 + + 2634 + + if ((now_us - self->removal_start_us) > 2635 + + (gint64)CB2000_REMOVAL_STALE_TIMEOUT_MS * 1000) { 2636 + + fp_warn("WAIT_REMOVAL stale (>%dms) - triggering recovery", 2637 + + CB2000_REMOVAL_STALE_TIMEOUT_MS); 2638 + + fpi_ssm_mark_failed(transfer->ssm, 2639 + + fpi_device_error_new_msg(FP_DEVICE_ERROR_PROTO, 2640 + + "Removal polling stale")); 2641 + + return; 2642 + + } 2643 + + 2644 + + if (finger_present) { 2645 + + self->removal_stable_off_hits = 0; 2646 + + fpi_ssm_jump_to_state(transfer->ssm, POLL_REMOVAL_DELAY); 2647 + + } else { 2648 + + self->removal_stable_off_hits++; 2649 + + if (self->removal_stable_off_hits < CB2000_REMOVAL_STABLE_OFF_COUNT) { 2650 + + fp_dbg("Removal debounce: off hits %d/%d", 2651 + + self->removal_stable_off_hits, 2652 + + CB2000_REMOVAL_STABLE_OFF_COUNT); 2653 + + fpi_ssm_jump_to_state(transfer->ssm, POLL_REMOVAL_DELAY); 2654 + + return; 2655 + + } 2656 + + 2657 + + fp_dbg("Finger removed, reporting status"); 2658 + + if (!self->deactivating && !self->deactivation_in_progress) 2659 + + fpi_ssm_mark_completed(transfer->ssm); 2660 + + } 2661 + +} 2662 + + 2663 + +/* 2664 + + * Removal polling sub-SSM entry point. 2665 + + */ 2666 + +static void 2667 + +poll_removal_run_state(FpiSsm *ssm, FpDevice *dev) 2668 + +{ 2669 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 2670 + + 2671 + + if (self->deactivating || self->deactivation_in_progress) { 2672 + + return; 2673 + + } 2674 + + 2675 + + switch (fpi_ssm_get_cur_state(ssm)) { 2676 + + case POLL_REMOVAL_DELAY: 2677 + + self->removal_timeout_id = g_timeout_add(CB2000_REMOVAL_INTERVAL, 2678 + + poll_removal_delay_cb, ssm); 2679 + + break; 2680 + + case POLL_REMOVAL_SEND: 2681 + + cb2000_ctrl_in(dev, ssm, 2682 + + REQ_POLL, 0x0007, 0, 2, 2683 + + poll_removal_result_cb, NULL); 2684 + + break; 2685 + + } 2686 + +} 2687 + + 2688 + +/* ============================================================================ 2689 + + * IMAGE READ SUB-SSM 2690 + + * ============================================================================ */ 2691 + + 2692 + +static void image_read_run_state(FpiSsm *ssm, FpDevice *dev); 2693 + + 2694 + +/* 2695 + + * CLAHE (Contrast Limited Adaptive Histogram Equalization). 2696 + + * Enhances local contrast per tile, so ridge/valley boundaries are sharpened 2697 + + * without the global flattening that a full-image equalization would cause. 2698 + + * 2699 + + * Applied on the RAW 80x64 image BEFORE upscale. This is the single most 2700 + + * important preprocessing step for NBIS minutiae detection on small sensors: 2701 + + * it makes ridges locally contrasty so MINDTCT can compute direction maps. 2702 + + * 2703 + + * Grid: tiles_x * tiles_y tiles of tile_w * tile_h pixels each. 2704 + + * clip_limit controls how much contrast amplification is allowed per tile 2705 + + * (higher = more contrast, but also more noise amplification). 2706 + + * Bilinear interpolation between neighboring tile histograms avoids 2707 + + * visible tile boundary artifacts. 2708 + + */ 2709 + +static void 2710 + +apply_clahe(guint8 *data, gint width, gint height, 2711 + + gint tile_w, gint tile_h, double clip_limit) 2712 + +{ 2713 + + gint tiles_x = width / tile_w; 2714 + + gint tiles_y = height / tile_h; 2715 + + 2716 + + if (tiles_x < 2 || tiles_y < 2) 2717 + + return; 2718 + + 2719 + + gint num_tiles = tiles_x * tiles_y; 2720 + + gint tile_size = tile_w * tile_h; 2721 + + 2722 + + /* Build per-tile CDF lookup tables */ 2723 + + guint8 *lut = g_malloc(num_tiles * 256); 2724 + + 2725 + + for (gint ty = 0; ty < tiles_y; ty++) { 2726 + + for (gint tx = 0; tx < tiles_x; tx++) { 2727 + + gint hist[256] = {0}; 2728 + + gint tile_idx = ty * tiles_x + tx; 2729 + + 2730 + + /* Compute histogram for this tile */ 2731 + + for (gint dy = 0; dy < tile_h; dy++) { 2732 + + gint y = ty * tile_h + dy; 2733 + + for (gint dx = 0; dx < tile_w; dx++) { 2734 + + gint x = tx * tile_w + dx; 2735 + + hist[data[y * width + x]]++; 2736 + + } 2737 + + } 2738 + + 2739 + + /* Clip histogram (redistribute excess above clip threshold) */ 2740 + + gint clip_threshold = (gint)(clip_limit * tile_size / 256); 2741 + + if (clip_threshold < 1) 2742 + + clip_threshold = 1; 2743 + + 2744 + + gint excess = 0; 2745 + + for (gint i = 0; i < 256; i++) { 2746 + + if (hist[i] > clip_threshold) { 2747 + + excess += hist[i] - clip_threshold; 2748 + + hist[i] = clip_threshold; 2749 + + } 2750 + + } 2751 + + /* Redistribute excess evenly */ 2752 + + gint per_bin = excess / 256; 2753 + + gint remainder = excess - per_bin * 256; 2754 + + for (gint i = 0; i < 256; i++) 2755 + + hist[i] += per_bin; 2756 + + /* Spread remainder across bins */ 2757 + + for (gint i = 0; i < remainder; i++) 2758 + + hist[i]++; 2759 + + 2760 + + /* Build CDF -> LUT mapping */ 2761 + + gint cumulative = 0; 2762 + + for (gint i = 0; i < 256; i++) { 2763 + + cumulative += hist[i]; 2764 + + lut[tile_idx * 256 + i] = 2765 + + (guint8)((cumulative * 255) / tile_size); 2766 + + } 2767 + + } 2768 + + } 2769 + + 2770 + + /* Apply with bilinear interpolation between tiles */ 2771 + + guint8 *out = g_malloc(width * height); 2772 + + 2773 + + for (gint y = 0; y < height; y++) { 2774 + + for (gint x = 0; x < width; x++) { 2775 + + guint8 pixel = data[y * width + x]; 2776 + + 2777 + + /* Find which tile center this pixel is nearest to */ 2778 + + double fx = ((double)x - tile_w * 0.5) / tile_w; 2779 + + double fy = ((double)y - tile_h * 0.5) / tile_h; 2780 + + 2781 + + gint tx0 = (gint)fx; 2782 + + gint ty0 = (gint)fy; 2783 + + 2784 + + if (tx0 < 0) tx0 = 0; 2785 + + if (ty0 < 0) ty0 = 0; 2786 + + if (tx0 >= tiles_x - 1) tx0 = tiles_x - 2; 2787 + + if (ty0 >= tiles_y - 1) ty0 = tiles_y - 2; 2788 + + 2789 + + gint tx1 = tx0 + 1; 2790 + + gint ty1 = ty0 + 1; 2791 + + 2792 + + double ax = fx - tx0; 2793 + + double ay = fy - ty0; 2794 + + if (ax < 0.0) ax = 0.0; 2795 + + if (ax > 1.0) ax = 1.0; 2796 + + if (ay < 0.0) ay = 0.0; 2797 + + if (ay > 1.0) ay = 1.0; 2798 + + 2799 + + /* Bilinear interpolation of 4 neighboring tile LUTs */ 2800 + + double v00 = lut[(ty0 * tiles_x + tx0) * 256 + pixel]; 2801 + + double v10 = lut[(ty0 * tiles_x + tx1) * 256 + pixel]; 2802 + + double v01 = lut[(ty1 * tiles_x + tx0) * 256 + pixel]; 2803 + + double v11 = lut[(ty1 * tiles_x + tx1) * 256 + pixel]; 2804 + + 2805 + + double val = v00 * (1 - ax) * (1 - ay) + 2806 + + v10 * ax * (1 - ay) + 2807 + + v01 * (1 - ax) * ay + 2808 + + v11 * ax * ay; 2809 + + 2810 + + out[y * width + x] = (guint8)(val + 0.5); 2811 + + } 2812 + + } 2813 + + 2814 + + memcpy(data, out, width * height); 2815 + + g_free(out); 2816 + + g_free(lut); 2817 + + 2818 + + fp_dbg("CLAHE applied: %dx%d tiles, clip=%.1f", 2819 + + tiles_x, tiles_y, clip_limit); 2820 + +} 2821 + + 2822 + +/* 2823 + + * Smart levels: percentile-based contrast stretch. 2824 + + * Clips bottom/top 5% of histogram before stretching, so outlier pixels 2825 + + * (stuck pixels, noise spikes) don't compress the useful ridge contrast. 2826 + + */ 2827 + +static void 2828 + +normalize_contrast(guint8 *data, gint width, gint height) 2829 + +{ 2830 + + gint size = width * height; 2831 + + gint hist[256] = {0}; 2832 + + 2833 + + for (gint i = 0; i < size; i++) 2834 + + hist[data[i]]++; 2835 + + 2836 + + /* Find 5th and 95th percentile values */ 2837 + + gint clip_lo_count = (gint)(size * 0.05); 2838 + + gint clip_hi_count = (gint)(size * 0.05); 2839 + + guint8 lo = 0, hi = 255; 2840 + + gint cumulative = 0; 2841 + + 2842 + + for (gint v = 0; v < 256; v++) { 2843 + + cumulative += hist[v]; 2844 + + if (cumulative > clip_lo_count) { 2845 + + lo = (guint8)v; 2846 + + break; 2847 + + } 2848 + + } 2849 + + 2850 + + cumulative = 0; 2851 + + for (gint v = 255; v >= 0; v--) { 2852 + + cumulative += hist[v]; 2853 + + if (cumulative > clip_hi_count) { 2854 + + hi = (guint8)v; 2855 + + break; 2856 + + } 2857 + + } 2858 + + 2859 + + if (hi <= lo) 2860 + + return; 2861 + + 2862 + + fp_dbg("Smart levels: lo=%d hi=%d (clipped 5%%/95%%)", lo, hi); 2863 + + 2864 + + double range = (double)(hi - lo); 2865 + + for (gint i = 0; i < size; i++) { 2866 + + if (data[i] <= lo) 2867 + + data[i] = 0; 2868 + + else if (data[i] >= hi) 2869 + + data[i] = 255; 2870 + + else 2871 + + data[i] = (guint8)(((data[i] - lo) * 255.0) / range); 2872 + + } 2873 + +} 2874 + + 2875 + +/* 2876 + + * 3x3 Gaussian blur (sigma ~0.85) to smooth bilinear upscale artifacts. 2877 + + * Kernel: [1 2 1; 2 4 2; 1 2 1] / 16 2878 + + * Only applied to upscaled images - smooths blocky pixel grid into 2879 + + * more natural ridge contours for NBIS. 2880 + + */ 2881 + +static void 2882 + +gaussian_blur_3x3(guint8 *data, gint width, gint height) 2883 + +{ 2884 + + guint8 *tmp = g_malloc(width * height); 2885 + + 2886 + + memcpy(tmp, data, width * height); 2887 + + 2888 + + for (gint y = 1; y < height - 1; y++) { 2889 + + for (gint x = 1; x < width - 1; x++) { 2890 + + gint sum = 2891 + + 1 * tmp[(y - 1) * width + (x - 1)] + 2892 + + 2 * tmp[(y - 1) * width + x] + 2893 + + 1 * tmp[(y - 1) * width + (x + 1)] + 2894 + + 2 * tmp[y * width + (x - 1)] + 2895 + + 4 * tmp[y * width + x] + 2896 + + 2 * tmp[y * width + (x + 1)] + 2897 + + 1 * tmp[(y + 1) * width + (x - 1)] + 2898 + + 2 * tmp[(y + 1) * width + x] + 2899 + + 1 * tmp[(y + 1) * width + (x + 1)]; 2900 + + data[y * width + x] = (guint8)(sum / 16); 2901 + + } 2902 + + } 2903 + + 2904 + + g_free(tmp); 2905 + +} 2906 + + 2907 + +static double 2908 + +calculate_variance(const guint8 *data, gint width, gint height) 2909 + +{ 2910 + + gint size = width * height; 2911 + + double mean = 0.0; 2912 + + 2913 + + for (gint i = 0; i < size; i++) 2914 + + mean += data[i]; 2915 + + mean /= size; 2916 + + 2917 + + double var = 0.0; 2918 + + for (gint i = 0; i < size; i++) { 2919 + + double diff = data[i] - mean; 2920 + + var += diff * diff; 2921 + + } 2922 + + 2923 + + return var / size; 2924 + +} 2925 + + 2926 + +static double 2927 + +calculate_block_low_var_ratio(const guint8 *data, 2928 + + gint width, 2929 + + gint height, 2930 + + gint block_size, 2931 + + double var_threshold) 2932 + +{ 2933 + + gint blocks_x = width / block_size; 2934 + + gint blocks_y = height / block_size; 2935 + + gint total_blocks = blocks_x * blocks_y; 2936 + + gint low_blocks = 0; 2937 + + 2938 + + if (total_blocks == 0) 2939 + + return 0.0; 2940 + + 2941 + + for (gint by = 0; by < blocks_y; by++) { 2942 + + for (gint bx = 0; bx < blocks_x; bx++) { 2943 + + double sum = 0.0; 2944 + + double sum2 = 0.0; 2945 + + for (gint y = 0; y < block_size; y++) { 2946 + + gint row = (by * block_size + y) * width; 2947 + + for (gint x = 0; x < block_size; x++) { 2948 + + guint8 v = data[row + (bx * block_size + x)]; 2949 + + sum += v; 2950 + + sum2 += (double)v * (double)v; 2951 + + } 2952 + + } 2953 + + double n = (double)block_size * (double)block_size; 2954 + + double mean = sum / n; 2955 + + double var = (sum2 / n) - (mean * mean); 2956 + + if (var < var_threshold) 2957 + + low_blocks++; 2958 + + } 2959 + + } 2960 + + 2961 + + return (double)low_blocks / (double)total_blocks; 2962 + +} 2963 + + 2964 + +static double 2965 + +calculate_laplacian_variance(const guint8 *data, gint width, gint height) 2966 + +{ 2967 + + double sum = 0.0; 2968 + + double sum2 = 0.0; 2969 + + gint count = 0; 2970 + + 2971 + + if (width < 3 || height < 3) 2972 + + return 0.0; 2973 + + 2974 + + for (gint y = 1; y < height - 1; y++) { 2975 + + for (gint x = 1; x < width - 1; x++) { 2976 + + gint idx = y * width + x; 2977 + + gint l = data[idx - width] + data[idx + width] + 2978 + + data[idx - 1] + data[idx + 1] - 2979 + + 4 * data[idx]; 2980 + + sum += l; 2981 + + sum2 += (double)l * (double)l; 2982 + + count++; 2983 + + } 2984 + + } 2985 + + 2986 + + if (count == 0) 2987 + + return 0.0; 2988 + + 2989 + + double mean = sum / count; 2990 + + return (sum2 / count) - (mean * mean); 2991 + +} 2992 + + 2993 + +/* 2994 + + * Approximate fingerprint coverage area (0.0..1.0). 2995 + + * This is a host-side proxy for the DLL notion of "fingerprint_area": 2996 + + * blocks with enough local variance are considered fingerprint foreground. 2997 + + */ 2998 + +static double 2999 + +calculate_fingerprint_area_ratio(const guint8 *data, gint width, gint height) 3000 + +{ 3001 + + double low_var_ratio = calculate_block_low_var_ratio(data, width, height, 3002 + + CB2000_BLOCK_SIZE, 3003 + + CB2000_LOW_VAR_THRESHOLD); 3004 + + return CLAMP(1.0 - low_var_ratio, 0.0, 1.0); 3005 + +} 3006 + + 3007 + +/* 3008 + + * Approximate overlap percentage between consecutive normalized frames. 3009 + + * The metric is intentionally simple and only used as a retry heuristic log. 3010 + + * Returns -1 when overlap is undefined (no previous foreground data). 3011 + + */ 3012 + +static int 3013 + +calculate_overlap_percent(const guint8 *curr, const guint8 *prev, gint size) 3014 + +{ 3015 + + gint curr_fg = 0; 3016 + + gint prev_fg = 0; 3017 + + gint inter = 0; 3018 + + 3019 + + for (gint i = 0; i < size; i++) { 3020 + + gboolean c = curr[i] > CB2000_FOREGROUND_THRESHOLD; 3021 + + gboolean p = prev[i] > CB2000_FOREGROUND_THRESHOLD; 3022 + + if (c) 3023 + + curr_fg++; 3024 + + if (p) 3025 + + prev_fg++; 3026 + + if (c && p) 3027 + + inter++; 3028 + + } 3029 + + 3030 + + gint denom = MIN(curr_fg, prev_fg); 3031 + + if (denom <= 0) 3032 + + return -1; 3033 + + 3034 + + return (inter * 100) / denom; 3035 + +} 3036 + + 3037 + +/* 3038 + + * Coarse quality score (0..100) to mirror DLL-style host quality diagnostics. 3039 + + * The score blends global variance, focus (Laplacian variance), and area ratio. 3040 + + */ 3041 + +static int 3042 + +calculate_quality_score(double variance_norm, double focus_var, double area_ratio) 3043 + +{ 3044 + + double var_n = CLAMP(variance_norm / 5000.0, 0.0, 1.0); 3045 + + double focus_n = CLAMP(focus_var / 1500.0, 0.0, 1.0); 3046 + + double area_n = CLAMP(area_ratio, 0.0, 1.0); 3047 + + double score = (0.40 * var_n + 0.35 * focus_n + 0.25 * area_n) * 100.0; 3048 + + return (int)CLAMP(score, 0.0, 100.0); 3049 + +} 3050 + + 3051 + +static void 3052 + +apply_background_subtraction(guint8 *data, 3053 + + const guint8 *bg, 3054 + + gint width, 3055 + + gint height) 3056 + +{ 3057 + + gint size = width * height; 3058 + + for (gint i = 0; i < size; i++) { 3059 + + if (data[i] > bg[i]) 3060 + + data[i] = (guint8)(data[i] - bg[i]); 3061 + + else 3062 + + data[i] = 0; 3063 + + } 3064 + +} 3065 + + 3066 + +/* 3067 + + * Read one image chunk. 3068 + + * 3069 + + * Windows captures show mixed packet lengths depending on transport mode: 3070 + + * - legacy framing: 320-byte chunk carrying 256-byte payload at offset 64 3071 + + * - compact framing: mostly 256-byte chunks, with a trailing 259-byte chunk 3072 + + * 3073 + + * In compact framing, the first chunk can start with a 3-byte marker 3074 + + * (typically ff 00 06). When present, those bytes are framing metadata and 3075 + + * must be skipped to keep image assembly aligned with Windows. 3076 + + * 3077 + + * CB2000_CHUNK_PAYLOAD_OFFSET can still force a static offset for experiments. 3078 + + */ 3079 + +static void 3080 + +image_chunk_cb(FpiUsbTransfer *transfer, 3081 + + FpDevice *dev, 3082 + + gpointer user_data, 3083 + + GError *error) 3084 + +{ 3085 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 3086 + + 3087 + + if (error) { 3088 + + fp_warn("Image chunk read error: %s", error->message); 3089 + + fpi_ssm_mark_failed(transfer->ssm, error); 3090 + + return; 3091 + + } 3092 + + 3093 + + guint8 *src = transfer->buffer; 3094 + + gsize len = transfer->actual_length; 3095 + + int chunk_index = self->chunks_read; 3096 + + gboolean first_chunk_header = 3097 + + (chunk_index == 0 && len >= 3 && 3098 + + src[0] == 0xff && src[1] == 0x00 && src[2] == 0x06); 3099 + + int default_payload_offset; 3100 + + gsize payload_offset; 3101 + + gsize payload_len; 3102 + + gsize remaining; 3103 + + gsize to_copy; 3104 + + 3105 + + if (len < CB2000_CHUNK_PAYLOAD_SIZE) { 3106 + + fp_warn("Chunk %d too short: got %zu, need >= %d", 3107 + + self->chunks_read, len, CB2000_CHUNK_PAYLOAD_SIZE); 3108 + + fpi_ssm_mark_failed(transfer->ssm, 3109 + + fpi_device_error_new(FP_DEVICE_ERROR_PROTO)); 3110 + + return; 3111 + + } 3112 + + 3113 + + /* Prefer Windows-aligned defaults: 3114 + + * - 320-byte framing -> payload at offset 64 3115 + + * - first compact chunk with ff 00 06 -> skip 3-byte marker 3116 + + * - other compact chunks (including 259-byte tail) -> payload at 0 */ 3117 + + if (len == CB2000_CHUNK_SIZE) { 3118 + + default_payload_offset = CB2000_CHUNK_DEFAULT_PAYLOAD_OFFSET; 3119 + + } else if (first_chunk_header) { 3120 + + default_payload_offset = 3; 3121 + + } else { 3122 + + default_payload_offset = 0; 3123 + + } 3124 + + payload_offset = (gsize) cb2000_get_env_int("CB2000_CHUNK_PAYLOAD_OFFSET", 3125 + + default_payload_offset, 3126 + + 0, 3127 + + (int)len - 1); 3128 + + if (payload_offset >= len) { 3129 + + fp_warn("Invalid payload window: off=%zu len=%zu chunk=%zu", 3130 + + payload_offset, len, len); 3131 + + fpi_ssm_mark_failed(transfer->ssm, 3132 + + fpi_device_error_new(FP_DEVICE_ERROR_PROTO)); 3133 + + return; 3134 + + } 3135 + + payload_len = len - payload_offset; 3136 + + 3137 + + if (self->chunks_read == 0 && len >= 3) { 3138 + + fp_dbg("Chunk 0 prefix=%02x %02x %02x payload_off=%zu payload_len=%zu", 3139 + + src[0], src[1], src[2], payload_offset, payload_len); 3140 + + } 3141 + + 3142 + + remaining = CB2000_IMG_SIZE - self->image_offset; 3143 + + to_copy = MIN(payload_len, remaining); 3144 + + 3145 + + if (to_copy > 0) { 3146 + + memcpy(self->image_buffer + self->image_offset, 3147 + + src + payload_offset, 3148 + + to_copy); 3149 + + self->image_offset += to_copy; 3150 + + } 3151 + + 3152 + + self->chunks_read++; 3153 + + 3154 + + fp_dbg("Chunk %d/%d: read %zu payload_off=%zu payload_len=%zu copied %zu -> total %zu/%d", 3155 + + self->chunks_read, CB2000_IMG_CHUNKS, 3156 + + transfer->actual_length, payload_offset, payload_len, to_copy, 3157 + + self->image_offset, CB2000_IMG_SIZE); 3158 + + 3159 + + if (self->chunks_read >= CB2000_IMG_CHUNKS) { 3160 + + fp_dbg("Image complete: %d chunks read", CB2000_IMG_CHUNKS); 3161 + + fpi_ssm_mark_completed(transfer->ssm); 3162 + + } else { 3163 + + fpi_ssm_next_state(transfer->ssm); 3164 + + } 3165 + +} 3166 + + 3167 + +/* 3168 + + * Write padding between chunk reads. This mirrors Windows behavior and keeps 3169 + + * the device in sync. 3170 + + */ 3171 + +static void 3172 + +image_padding_cb(FpiUsbTransfer *transfer, 3173 + + FpDevice *dev, 3174 + + gpointer user_data, 3175 + + GError *error) 3176 + +{ 3177 + + if (error) { 3178 + + fp_warn("Padding write error: %s", error->message); 3179 + + fpi_ssm_mark_failed(transfer->ssm, error); 3180 + + return; 3181 + + } 3182 + + 3183 + + fpi_ssm_jump_to_state(transfer->ssm, IMG_READ_CHUNK); 3184 + +} 3185 + + 3186 + +/* 3187 + + * Image read sub-SSM entry point. Alternates between read and padding writes. 3188 + + */ 3189 + +static void 3190 + +image_read_run_state(FpiSsm *ssm, FpDevice *dev) 3191 + +{ 3192 + + switch (fpi_ssm_get_cur_state(ssm)) { 3193 + + case IMG_READ_CHUNK: 3194 + + cb2000_bulk_read(dev, ssm, CB2000_CHUNK_SIZE, image_chunk_cb, NULL); 3195 + + break; 3196 + + case IMG_WRITE_PADDING: 3197 + + cb2000_bulk_write(dev, ssm, data_padding, CB2000_CHUNK_PAYLOAD_SIZE, 3198 + + image_padding_cb, NULL); 3199 + + break; 3200 + + } 3201 + +} 3202 + + 3203 + +/* ============================================================================ 3204 + + * NCC CORRELATION MATCHER (V33 real, robust decision) 3205 + + * ============================================================================ 3206 + + * Match strategy: 3207 + + * 1) Intensity NCC over small 2D shifts on foreground-only mask. 3208 + + * 2) Gradient-magnitude NCC over the same masked support. 3209 + + * 3) Conservative decision matrix using top1/top2/mean(top3)/support_count. 3210 + + */ 3211 + + 3212 + +typedef enum { 3213 + + CB2000_NCC_DECISION_MATCH = 0, 3214 + + CB2000_NCC_DECISION_NO_MATCH, 3215 + + CB2000_NCC_DECISION_RETRY, 3216 + + CB2000_NCC_DECISION_ERROR, 3217 + +} Cb2000NccDecision; 3218 + + 3219 + +typedef struct { 3220 + + gdouble best_intensity; 3221 + + gdouble second_intensity; 3222 + + gdouble mean_top3_intensity; 3223 + + gdouble grad_for_best_intensity; 3224 + + guint support_count; 3225 + + guint compared_images; 3226 + + gdouble enroll_pair_mean_intensity; 3227 + + gdouble enroll_pair_min_intensity; 3228 + + gdouble enroll_pair_mean_gradient; 3229 + + guint enroll_pair_count; 3230 + + gdouble adaptive_relax; 3231 + +} Cb2000NccTelemetry; 3232 + + 3233 + +static const char * 3234 + +cb2000_ncc_decision_label(Cb2000NccDecision decision) 3235 + +{ 3236 + + switch (decision) { 3237 + + case CB2000_NCC_DECISION_MATCH: 3238 + + return "MATCH"; 3239 + + case CB2000_NCC_DECISION_NO_MATCH: 3240 + + return "NO_MATCH"; 3241 + + case CB2000_NCC_DECISION_RETRY: 3242 + + return "RETRY"; 3243 + + case CB2000_NCC_DECISION_ERROR: 3244 + + return "ERROR"; 3245 + + default: 3246 + + return "UNKNOWN"; 3247 + + } 3248 + +} 3249 + + 3250 + +static gdouble 3251 + +cb2000_ncc_env_double(const char *name, 3252 + + gdouble fallback, 3253 + + gdouble min, 3254 + + gdouble max) 3255 + +{ 3256 + + const char *env = g_getenv(name); 3257 + + gdouble val; 3258 + + 3259 + + if (!env || !*env) 3260 + + return fallback; 3261 + + 3262 + + val = g_ascii_strtod(env, NULL); 3263 + + if (val < min || val > max) 3264 + + return fallback; 3265 + + return val; 3266 + +} 3267 + + 3268 + +static guint 3269 + +cb2000_ncc_env_uint(const char *name, guint fallback, guint min, guint max) 3270 + +{ 3271 + + const char *env = g_getenv(name); 3272 + + char *end = NULL; 3273 + + unsigned long val; 3274 + + 3275 + + if (!env || !*env) 3276 + + return fallback; 3277 + + 3278 + + val = strtoul(env, &end, 10); 3279 + + if (end == env || *end != '\0') 3280 + + return fallback; 3281 + + if (val < min || val > max) 3282 + + return fallback; 3283 + + return (guint) val; 3284 + +} 3285 + + 3286 + +static guint 3287 + +cb2000_build_foreground_mask(const guint8 *img, 3288 + + guint8 *mask, 3289 + + gint width, 3290 + + gint height, 3291 + + guint8 threshold) 3292 + +{ 3293 + + guint active = 0; 3294 + + gint x, y; 3295 + + 3296 + + for (y = 0; y < height; y++) { 3297 + + for (x = 0; x < width; x++) { 3298 + + gint idx = y * width + x; 3299 + + guint8 on = img[idx] >= threshold ? 1 : 0; 3300 + + mask[idx] = on; 3301 + + active += on; 3302 + + } 3303 + + } 3304 + + return active; 3305 + +} 3306 + + 3307 + +static void 3308 + +cb2000_compute_gradient_magnitude(const guint8 *img, 3309 + + gdouble *grad, 3310 + + gint width, 3311 + + gint height) 3312 + +{ 3313 + + gint x, y; 3314 + + 3315 + + memset(grad, 0, sizeof(*grad) * width * height); 3316 + + for (y = 1; y < height - 1; y++) { 3317 + + for (x = 1; x < width - 1; x++) { 3318 + + gint idx = y * width + x; 3319 + + gint gx = 3320 + + -img[(y - 1) * width + (x - 1)] + img[(y - 1) * width + (x + 1)] + 3321 + + -2 * img[y * width + (x - 1)] + 2 * img[y * width + (x + 1)] + 3322 + + -img[(y + 1) * width + (x - 1)] + img[(y + 1) * width + (x + 1)]; 3323 + + gint gy = 3324 + + -img[(y - 1) * width + (x - 1)] - 2 * img[(y - 1) * width + x] - img[(y - 1) * width + (x + 1)] + 3325 + + img[(y + 1) * width + (x - 1)] + 2 * img[(y + 1) * width + x] + img[(y + 1) * width + (x + 1)]; 3326 + + grad[idx] = sqrt((gdouble) gx * gx + (gdouble) gy * gy); 3327 + + } 3328 + + } 3329 + +} 3330 + + 3331 + +static gdouble 3332 + +cb2000_ncc_match_u8_masked(const guint8 *a, 3333 + + const guint8 *b, 3334 + + const guint8 *mask_a, 3335 + + const guint8 *mask_b, 3336 + + gint width, 3337 + + gint height, 3338 + + gint max_shift, 3339 + + guint min_samples) 3340 + +{ 3341 + + gdouble best = -2.0; 3342 + + gint dx, dy, x, y; 3343 + + 3344 + + for (dy = -max_shift; dy <= max_shift; dy++) { 3345 + + gint y0 = MAX(0, -dy); 3346 + + gint y1 = MIN(height, height - dy); 3347 + + if (y0 >= y1) 3348 + + continue; 3349 + + 3350 + + for (dx = -max_shift; dx <= max_shift; dx++) { 3351 + + gint x0 = MAX(0, -dx); 3352 + + gint x1 = MIN(width, width - dx); 3353 + + guint n = 0; 3354 + + gdouble sum_a = 0.0, sum_b = 0.0; 3355 + + gdouble mean_a, mean_b; 3356 + + gdouble cov = 0.0, var_a = 0.0, var_b = 0.0; 3357 + + gdouble score; 3358 + + 3359 + + if (x0 >= x1) 3360 + + continue; 3361 + + 3362 + + for (y = y0; y < y1; y++) { 3363 + + for (x = x0; x < x1; x++) { 3364 + + gint ia = y * width + x; 3365 + + gint ib = (y + dy) * width + (x + dx); 3366 + + if (!mask_a[ia] || !mask_b[ib]) 3367 + + continue; 3368 + + sum_a += a[ia]; 3369 + + sum_b += b[ib]; 3370 + + n++; 3371 + + } 3372 + + } 3373 + + 3374 + + if (n < min_samples) 3375 + + continue; 3376 + + 3377 + + mean_a = sum_a / n; 3378 + + mean_b = sum_b / n; 3379 + + 3380 + + for (y = y0; y < y1; y++) { 3381 + + for (x = x0; x < x1; x++) { 3382 + + gint ia = y * width + x; 3383 + + gint ib = (y + dy) * width + (x + dx); 3384 + + gdouble da, db; 3385 + + if (!mask_a[ia] || !mask_b[ib]) 3386 + + continue; 3387 + + da = a[ia] - mean_a; 3388 + + db = b[ib] - mean_b; 3389 + + cov += da * db; 3390 + + var_a += da * da; 3391 + + var_b += db * db; 3392 + + } 3393 + + } 3394 + + 3395 + + score = (var_a < 1.0 || var_b < 1.0) ? 0.0 : cov / sqrt(var_a * var_b); 3396 + + if (score > best) 3397 + + best = score; 3398 + + } 3399 + + } 3400 + + 3401 + + if (best < -1.0) 3402 + + return 0.0; 3403 + + return best; 3404 + +} 3405 + + 3406 + +static gdouble 3407 + +cb2000_ncc_match_f64_masked(const gdouble *a, 3408 + + const gdouble *b, 3409 + + const guint8 *mask_a, 3410 + + const guint8 *mask_b, 3411 + + gint width, 3412 + + gint height, 3413 + + gint max_shift, 3414 + + guint min_samples) 3415 + +{ 3416 + + gdouble best = -2.0; 3417 + + gint dx, dy, x, y; 3418 + + 3419 + + for (dy = -max_shift; dy <= max_shift; dy++) { 3420 + + gint y0 = MAX(0, -dy); 3421 + + gint y1 = MIN(height, height - dy); 3422 + + if (y0 >= y1) 3423 + + continue; 3424 + + 3425 + + for (dx = -max_shift; dx <= max_shift; dx++) { 3426 + + gint x0 = MAX(0, -dx); 3427 + + gint x1 = MIN(width, width - dx); 3428 + + guint n = 0; 3429 + + gdouble sum_a = 0.0, sum_b = 0.0; 3430 + + gdouble mean_a, mean_b; 3431 + + gdouble cov = 0.0, var_a = 0.0, var_b = 0.0; 3432 + + gdouble score; 3433 + + 3434 + + if (x0 >= x1) 3435 + + continue; 3436 + + 3437 + + for (y = y0; y < y1; y++) { 3438 + + for (x = x0; x < x1; x++) { 3439 + + gint ia = y * width + x; 3440 + + gint ib = (y + dy) * width + (x + dx); 3441 + + if (!mask_a[ia] || !mask_b[ib]) 3442 + + continue; 3443 + + sum_a += a[ia]; 3444 + + sum_b += b[ib]; 3445 + + n++; 3446 + + } 3447 + + } 3448 + + 3449 + + if (n < min_samples) 3450 + + continue; 3451 + + 3452 + + mean_a = sum_a / n; 3453 + + mean_b = sum_b / n; 3454 + + 3455 + + for (y = y0; y < y1; y++) { 3456 + + for (x = x0; x < x1; x++) { 3457 + + gint ia = y * width + x; 3458 + + gint ib = (y + dy) * width + (x + dx); 3459 + + gdouble da, db; 3460 + + if (!mask_a[ia] || !mask_b[ib]) 3461 + + continue; 3462 + + da = a[ia] - mean_a; 3463 + + db = b[ib] - mean_b; 3464 + + cov += da * db; 3465 + + var_a += da * da; 3466 + + var_b += db * db; 3467 + + } 3468 + + } 3469 + + 3470 + + score = (var_a < 1.0 || var_b < 1.0) ? 0.0 : cov / sqrt(var_a * var_b); 3471 + + if (score > best) 3472 + + best = score; 3473 + + } 3474 + + } 3475 + + 3476 + + if (best < -1.0) 3477 + + return 0.0; 3478 + + return best; 3479 + +} 3480 + + 3481 + +static gdouble 3482 + +cb2000_ncc_compute_adaptive_relax(const Cb2000NccTelemetry *tel) 3483 + +{ 3484 + + guint adaptive_enabled = cb2000_ncc_env_uint("CB2000_NCC_ADAPTIVE", 3485 + + CB2000_NCC_ADAPTIVE_ENABLED_DEFAULT, 3486 + + 0, 1); 3487 + + gdouble max_relax = cb2000_ncc_env_double("CB2000_NCC_ADAPTIVE_MAX_RELAX", 3488 + + CB2000_NCC_ADAPTIVE_MAX_RELAX_DEFAULT, 3489 + + 0.0, 0.20); 3490 + + gdouble target_mean = cb2000_ncc_env_double("CB2000_NCC_ADAPTIVE_TARGET_MEAN", 3491 + + CB2000_NCC_ADAPTIVE_TARGET_MEAN_DEFAULT, 3492 + + 0.0, 1.0); 3493 + + gdouble target_min = cb2000_ncc_env_double("CB2000_NCC_ADAPTIVE_TARGET_MIN", 3494 + + CB2000_NCC_ADAPTIVE_TARGET_MIN_DEFAULT, 3495 + + 0.0, 1.0); 3496 + + gdouble deficit_mean; 3497 + + gdouble deficit_min; 3498 + + gdouble relax; 3499 + + 3500 + + if (!adaptive_enabled || tel->enroll_pair_count == 0) 3501 + + return 0.0; 3502 + + 3503 + + deficit_mean = MAX(0.0, target_mean - tel->enroll_pair_mean_intensity); 3504 + + deficit_min = MAX(0.0, target_min - tel->enroll_pair_min_intensity); 3505 + + 3506 + + /* Weighted deficits: broad cohesion (mean) + worst-case spread (min). */ 3507 + + relax = deficit_mean * 0.60 + deficit_min * 0.40; 3508 + + if (relax > max_relax) 3509 + + relax = max_relax; 3510 + + if (relax < 0.0) 3511 + + relax = 0.0; 3512 + + return relax; 3513 + +} 3514 + + 3515 + +static void 3516 + +cb2000_ncc_compute_enroll_cohesion(const guint8 *images_data, 3517 + + gint n_images, 3518 + + guint8 mask_threshold, 3519 + + guint min_samples, 3520 + + gint max_shift, 3521 + + Cb2000NccTelemetry *tel) 3522 + +{ 3523 + + guint8 masks[CB2000_NR_ENROLL_STAGES][CB2000_IMG_SIZE]; 3524 + + gdouble grads[CB2000_NR_ENROLL_STAGES][CB2000_IMG_SIZE]; 3525 + + const guint8 *images[CB2000_NR_ENROLL_STAGES]; 3526 + + gdouble sum_int = 0.0; 3527 + + gdouble sum_grad = 0.0; 3528 + + gdouble min_int = 1.0; 3529 + + guint pairs = 0; 3530 + + gint i, j; 3531 + + 3532 + + tel->enroll_pair_mean_intensity = 0.0; 3533 + + tel->enroll_pair_min_intensity = 0.0; 3534 + + tel->enroll_pair_mean_gradient = 0.0; 3535 + + tel->enroll_pair_count = 0; 3536 + + 3537 + + if (n_images < 2) 3538 + + return; 3539 + + 3540 + + n_images = MIN(n_images, CB2000_NR_ENROLL_STAGES); 3541 + + 3542 + + for (i = 0; i < n_images; i++) { 3543 + + images[i] = images_data + (i * CB2000_IMG_SIZE); 3544 + + cb2000_build_foreground_mask(images[i], masks[i], 3545 + + CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT, 3546 + + mask_threshold); 3547 + + cb2000_compute_gradient_magnitude(images[i], grads[i], 3548 + + CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT); 3549 + + } 3550 + + 3551 + + for (i = 0; i < n_images; i++) { 3552 + + for (j = i + 1; j < n_images; j++) { 3553 + + gdouble int_pair = cb2000_ncc_match_u8_masked(images[i], images[j], 3554 + + masks[i], masks[j], 3555 + + CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT, 3556 + + max_shift, min_samples); 3557 + + gdouble grad_pair = cb2000_ncc_match_f64_masked(grads[i], grads[j], 3558 + + masks[i], masks[j], 3559 + + CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT, 3560 + + max_shift, min_samples); 3561 + + sum_int += int_pair; 3562 + + sum_grad += grad_pair; 3563 + + min_int = MIN(min_int, int_pair); 3564 + + pairs++; 3565 + + fp_dbg("[ NCC ] cohesion pair=%d-%d int=%.4f grad=%.4f", 3566 + + i, j, int_pair, grad_pair); 3567 + + } 3568 + + } 3569 + + 3570 + + if (pairs == 0) 3571 + + return; 3572 + + 3573 + + tel->enroll_pair_count = pairs; 3574 + + tel->enroll_pair_mean_intensity = sum_int / pairs; 3575 + + tel->enroll_pair_min_intensity = min_int; 3576 + + tel->enroll_pair_mean_gradient = sum_grad / pairs; 3577 + +} 3578 + + 3579 + +static Cb2000NccDecision 3580 + +cb2000_ncc_classify(const Cb2000NccTelemetry *tel) 3581 + +{ 3582 + + gdouble match_top1 = cb2000_ncc_env_double("CB2000_NCC_MATCH_TOP1", 3583 + + CB2000_NCC_MATCH_TOP1_DEFAULT, 3584 + + 0.0, 1.0); 3585 + + gdouble match_top2 = cb2000_ncc_env_double("CB2000_NCC_MATCH_TOP2", 3586 + + CB2000_NCC_MATCH_TOP2_DEFAULT, 3587 + + 0.0, 1.0); 3588 + + gdouble match_mean3 = cb2000_ncc_env_double("CB2000_NCC_MATCH_MEAN3", 3589 + + CB2000_NCC_MATCH_MEAN3_DEFAULT, 3590 + + 0.0, 1.0); 3591 + + gdouble match_grad1 = cb2000_ncc_env_double("CB2000_NCC_MATCH_GRAD1", 3592 + + CB2000_NCC_MATCH_GRAD1_DEFAULT, 3593 + + 0.0, 1.0); 3594 + + guint support_min = cb2000_ncc_env_uint("CB2000_NCC_SUPPORT_MIN", 3595 + + CB2000_NCC_SUPPORT_MIN_DEFAULT, 3596 + + 1, CB2000_NR_ENROLL_STAGES); 3597 + + gdouble medium_top1 = cb2000_ncc_env_double("CB2000_NCC_MEDIUM_TOP1", 3598 + + CB2000_NCC_MEDIUM_TOP1_DEFAULT, 3599 + + 0.0, 1.0); 3600 + + gdouble medium_top2 = cb2000_ncc_env_double("CB2000_NCC_MEDIUM_TOP2", 3601 + + CB2000_NCC_MEDIUM_TOP2_DEFAULT, 3602 + + 0.0, 1.0); 3603 + + gdouble medium_mean3 = cb2000_ncc_env_double("CB2000_NCC_MEDIUM_MEAN3", 3604 + + CB2000_NCC_MEDIUM_MEAN3_DEFAULT, 3605 + + 0.0, 1.0); 3606 + + gdouble medium_grad1 = cb2000_ncc_env_double("CB2000_NCC_MEDIUM_GRAD1", 3607 + + CB2000_NCC_MEDIUM_GRAD1_DEFAULT, 3608 + + 0.0, 1.0); 3609 + + guint medium_support_min = cb2000_ncc_env_uint("CB2000_NCC_MEDIUM_SUPPORT_MIN", 3610 + + CB2000_NCC_MEDIUM_SUPPORT_MIN_DEFAULT, 3611 + + 1, CB2000_NR_ENROLL_STAGES); 3612 + + gdouble dominant_top1 = cb2000_ncc_env_double("CB2000_NCC_DOMINANT_TOP1", 3613 + + CB2000_NCC_DOMINANT_TOP1_DEFAULT, 3614 + + 0.0, 1.0); 3615 + + gdouble dominant_top2 = cb2000_ncc_env_double("CB2000_NCC_DOMINANT_TOP2", 3616 + + CB2000_NCC_DOMINANT_TOP2_DEFAULT, 3617 + + 0.0, 1.0); 3618 + + gdouble dominant_mean3 = cb2000_ncc_env_double("CB2000_NCC_DOMINANT_MEAN3", 3619 + + CB2000_NCC_DOMINANT_MEAN3_DEFAULT, 3620 + + 0.0, 1.0); 3621 + + gdouble dominant_grad1 = cb2000_ncc_env_double("CB2000_NCC_DOMINANT_GRAD1", 3622 + + CB2000_NCC_DOMINANT_GRAD1_DEFAULT, 3623 + + 0.0, 1.0); 3624 + + guint dominant_support_min = cb2000_ncc_env_uint("CB2000_NCC_DOMINANT_SUPPORT_MIN", 3625 + + CB2000_NCC_DOMINANT_SUPPORT_MIN_DEFAULT, 3626 + + 1, CB2000_NR_ENROLL_STAGES); 3627 + + gdouble consistent_top1 = cb2000_ncc_env_double("CB2000_NCC_CONSISTENT_TOP1", 3628 + + CB2000_NCC_CONSISTENT_TOP1_DEFAULT, 3629 + + 0.0, 1.0); 3630 + + gdouble consistent_mean3 = cb2000_ncc_env_double("CB2000_NCC_CONSISTENT_MEAN3", 3631 + + CB2000_NCC_CONSISTENT_MEAN3_DEFAULT, 3632 + + 0.0, 1.0); 3633 + + gdouble consistent_grad1 = cb2000_ncc_env_double("CB2000_NCC_CONSISTENT_GRAD1", 3634 + + CB2000_NCC_CONSISTENT_GRAD1_DEFAULT, 3635 + + 0.0, 1.0); 3636 + + guint consistent_support_min = cb2000_ncc_env_uint("CB2000_NCC_CONSISTENT_SUPPORT_MIN", 3637 + + CB2000_NCC_CONSISTENT_SUPPORT_MIN_DEFAULT, 3638 + + 1, CB2000_NR_ENROLL_STAGES); 3639 + + gdouble single_top1 = cb2000_ncc_env_double("CB2000_NCC_SINGLE_TOP1", 3640 + + CB2000_NCC_SINGLE_TOP1_DEFAULT, 3641 + + 0.0, 1.0); 3642 + + gdouble single_top2 = cb2000_ncc_env_double("CB2000_NCC_SINGLE_TOP2", 3643 + + CB2000_NCC_SINGLE_TOP2_DEFAULT, 3644 + + 0.0, 1.0); 3645 + + gdouble single_mean3 = cb2000_ncc_env_double("CB2000_NCC_SINGLE_MEAN3", 3646 + + CB2000_NCC_SINGLE_MEAN3_DEFAULT, 3647 + + 0.0, 1.0); 3648 + + gdouble single_grad1 = cb2000_ncc_env_double("CB2000_NCC_SINGLE_GRAD1", 3649 + + CB2000_NCC_SINGLE_GRAD1_DEFAULT, 3650 + + 0.0, 1.0); 3651 + + guint single_support_min = cb2000_ncc_env_uint("CB2000_NCC_SINGLE_SUPPORT_MIN", 3652 + + CB2000_NCC_SINGLE_SUPPORT_MIN_DEFAULT, 3653 + + 1, CB2000_NR_ENROLL_STAGES); 3654 + + gdouble nomatch_top1 = cb2000_ncc_env_double("CB2000_NCC_NOMATCH_TOP1", 3655 + + CB2000_NCC_NOMATCH_TOP1_DEFAULT, 3656 + + 0.0, 1.0); 3657 + + gdouble clear_nomatch_top1 = cb2000_ncc_env_double("CB2000_NCC_CLEAR_NOMATCH_TOP1", 3658 + + CB2000_NCC_CLEAR_NOMATCH_TOP1_DEFAULT, 3659 + + 0.0, 1.0); 3660 + + gdouble clear_nomatch_mean3 = cb2000_ncc_env_double("CB2000_NCC_CLEAR_NOMATCH_MEAN3", 3661 + + CB2000_NCC_CLEAR_NOMATCH_MEAN3_DEFAULT, 3662 + + 0.0, 1.0); 3663 + + guint adaptive_relax_support = cb2000_ncc_env_uint("CB2000_NCC_ADAPTIVE_RELAX_SUPPORT", 3664 + + CB2000_NCC_ADAPTIVE_RELAX_SUPPORT_DEFAULT, 3665 + + 0, 1); 3666 + + gdouble relax = cb2000_ncc_compute_adaptive_relax(tel); 3667 + + 3668 + + if (relax > 0.0) { 3669 + + match_top1 = MAX(0.0, match_top1 - relax); 3670 + + match_top2 = MAX(0.0, match_top2 - (relax * 0.75)); 3671 + + match_mean3 = MAX(0.0, match_mean3 - (relax * 0.75)); 3672 + + match_grad1 = MAX(0.0, match_grad1 - (relax * 0.50)); 3673 + + 3674 + + medium_top1 = MAX(0.0, medium_top1 - relax); 3675 + + medium_top2 = MAX(0.0, medium_top2 - (relax * 0.80)); 3676 + + medium_mean3 = MAX(0.0, medium_mean3 - (relax * 0.80)); 3677 + + medium_grad1 = MAX(0.0, medium_grad1 - (relax * 0.50)); 3678 + + 3679 + + dominant_top1 = MAX(0.0, dominant_top1 - (relax * 0.40)); 3680 + + dominant_top2 = MAX(0.0, dominant_top2 - (relax * 0.25)); 3681 + + dominant_mean3 = MAX(0.0, dominant_mean3 - (relax * 0.50)); 3682 + + dominant_grad1 = MAX(0.0, dominant_grad1 - (relax * 0.40)); 3683 + + 3684 + + consistent_top1 = MAX(0.0, consistent_top1 - (relax * 0.60)); 3685 + + consistent_mean3 = MAX(0.0, consistent_mean3 - (relax * 0.50)); 3686 + + consistent_grad1 = MAX(0.0, consistent_grad1 - (relax * 0.40)); 3687 + + single_top1 = MAX(0.0, single_top1 - (relax * 0.20)); 3688 + + single_top2 = MAX(0.0, single_top2 - (relax * 0.10)); 3689 + + single_mean3 = MAX(0.0, single_mean3 - (relax * 0.20)); 3690 + + single_grad1 = MAX(0.0, single_grad1 - (relax * 0.20)); 3691 + + 3692 + + if (adaptive_relax_support) { 3693 + + if (support_min > 1 && relax >= CB2000_NCC_ADAPTIVE_SUPPORT_RELAX_AT) 3694 + + support_min--; 3695 + + if (medium_support_min > 1 && relax >= CB2000_NCC_ADAPTIVE_SUPPORT_RELAX_AT) 3696 + + medium_support_min--; 3697 + + if (consistent_support_min > 1 && relax >= CB2000_NCC_ADAPTIVE_SUPPORT_RELAX_AT) 3698 + + consistent_support_min--; 3699 + + if (single_support_min > 1 && relax >= CB2000_NCC_ADAPTIVE_SUPPORT_RELAX_AT) 3700 + + single_support_min--; 3701 + + } 3702 + + } 3703 + + 3704 + + fp_dbg("[ NCC ] classify thresholds: strong(top1>=%.3f top2>=%.3f mean3>=%.3f grad1>=%.3f support>=%u) " 3705 + + "medium(top1>=%.3f top2>=%.3f mean3>=%.3f grad1>=%.3f support>=%u) " 3706 + + "dominant(top1>=%.3f top2>=%.3f mean3>=%.3f grad1>=%.3f support>=%u) " 3707 + + "consistent(top1>=%.3f mean3>=%.3f grad1>=%.3f support>=%u) " 3708 + + "single(top1>=%.3f top2>=%.3f mean3>=%.3f grad1>=%.3f support>=%u) " 3709 + + "clear_nomatch(support==0 && top1<%.3f && mean3<%.3f) " 3710 + + "nomatch(top1<%.3f) " 3711 + + "adaptive(relax=%.3f relax_support=%u cohesion_mean=%.3f cohesion_min=%.3f pairs=%u)", 3712 + + match_top1, match_top2, match_mean3, match_grad1, support_min, 3713 + + medium_top1, medium_top2, medium_mean3, medium_grad1, medium_support_min, 3714 + + dominant_top1, dominant_top2, dominant_mean3, dominant_grad1, dominant_support_min, 3715 + + consistent_top1, consistent_mean3, consistent_grad1, consistent_support_min, 3716 + + single_top1, single_top2, single_mean3, single_grad1, single_support_min, 3717 + + clear_nomatch_top1, clear_nomatch_mean3, 3718 + + nomatch_top1, 3719 + + relax, 3720 + + adaptive_relax_support, 3721 + + tel->enroll_pair_mean_intensity, 3722 + + tel->enroll_pair_min_intensity, 3723 + + tel->enroll_pair_count); 3724 + + 3725 + + if (tel->compared_images == 0) 3726 + + return CB2000_NCC_DECISION_ERROR; 3727 + + 3728 + + if (tel->support_count >= support_min && 3729 + + tel->best_intensity >= match_top1 && 3730 + + tel->second_intensity >= match_top2 && 3731 + + tel->mean_top3_intensity >= match_mean3 && 3732 + + tel->grad_for_best_intensity >= match_grad1) 3733 + + return CB2000_NCC_DECISION_MATCH; 3734 + + 3735 + + if (tel->support_count >= medium_support_min && 3736 + + tel->best_intensity >= medium_top1 && 3737 + + tel->second_intensity >= medium_top2 && 3738 + + tel->mean_top3_intensity >= medium_mean3 && 3739 + + tel->grad_for_best_intensity >= medium_grad1) 3740 + + return CB2000_NCC_DECISION_MATCH; 3741 + + 3742 + + if (tel->support_count >= dominant_support_min && 3743 + + tel->best_intensity >= dominant_top1 && 3744 + + tel->second_intensity >= dominant_top2 && 3745 + + tel->mean_top3_intensity >= dominant_mean3 && 3746 + + tel->grad_for_best_intensity >= dominant_grad1) 3747 + + return CB2000_NCC_DECISION_MATCH; 3748 + + 3749 + + if (tel->support_count >= consistent_support_min && 3750 + + tel->best_intensity >= consistent_top1 && 3751 + + tel->mean_top3_intensity >= consistent_mean3 && 3752 + + tel->grad_for_best_intensity >= consistent_grad1) 3753 + + return CB2000_NCC_DECISION_MATCH; 3754 + + 3755 + + if (tel->support_count >= single_support_min && 3756 + + tel->best_intensity >= single_top1 && 3757 + + tel->second_intensity >= single_top2 && 3758 + + tel->mean_top3_intensity >= single_mean3 && 3759 + + tel->grad_for_best_intensity >= single_grad1) 3760 + + return CB2000_NCC_DECISION_MATCH; 3761 + + 3762 + + if (tel->support_count == 0 && 3763 + + tel->best_intensity < clear_nomatch_top1 && 3764 + + tel->mean_top3_intensity < clear_nomatch_mean3) 3765 + + return CB2000_NCC_DECISION_NO_MATCH; 3766 + + 3767 + + if (tel->best_intensity < nomatch_top1) 3768 + + return CB2000_NCC_DECISION_NO_MATCH; 3769 + + 3770 + + return CB2000_NCC_DECISION_RETRY; 3771 + +} 3772 + + 3773 + +/* ============================================================================ 3774 + + * GVARIANT PRINT STORAGE (V33) 3775 + + * ============================================================================ 3776 + + * Enrollment stores preprocessed 80x64 images in GVariant format. 3777 + + * Format: (uqqay) = (version, width, height, concatenated_images) 3778 + + */ 3779 + + 3780 + +/** 3781 + + * pack_enrollment_data - Store enrollment images into a print 3782 + + * @self: Device instance with enrollment images 3783 + + * @print: FpPrint to store data into 3784 + + * 3785 + + * Packs all enrollment images into a GVariant and sets it on the print. 3786 + + */ 3787 + +static void 3788 + +pack_enrollment_data(FpiDeviceCanvasbioCb2000 *self, FpPrint *print) 3789 + +{ 3790 + + gsize total_size = CB2000_NR_ENROLL_STAGES * CB2000_IMG_SIZE; 3791 + + guint8 *all_images = g_malloc(total_size); 3792 + + gint i; 3793 + + GVariant *images_var; 3794 + + GVariant *data; 3795 + + 3796 + + for (i = 0; i < CB2000_NR_ENROLL_STAGES; i++) 3797 + + memcpy(all_images + i * CB2000_IMG_SIZE, 3798 + + self->enroll_images[i], CB2000_IMG_SIZE); 3799 + + 3800 + + images_var = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, 3801 + + all_images, total_size, 1); 3802 + + 3803 + + data = g_variant_new(CB2000_PRINT_FORMAT, 3804 + + (guint32) 1, 3805 + + (guint16) CB2000_IMG_WIDTH, 3806 + + (guint16) CB2000_IMG_HEIGHT, 3807 + + images_var); 3808 + + 3809 + + fpi_print_set_type(print, FPI_PRINT_RAW); 3810 + + g_object_set(print, "fpi-data", data, NULL); 3811 + + 3812 + + g_free(all_images); 3813 + + 3814 + + fp_info("Packed %d enrollment images (%zu bytes total)", 3815 + + CB2000_NR_ENROLL_STAGES, total_size); 3816 + +} 3817 + + 3818 + +/** 3819 + + * unpack_and_match - Unpack enrolled images and run robust NCC telemetry 3820 + + * @print: Enrolled FpPrint containing stored images 3821 + + * @captured: Captured image to match against (CB2000_IMG_SIZE bytes) 3822 + + * @tel: Output telemetry for decision matrix 3823 + + * 3824 + + * Returns TRUE if unpack succeeded, FALSE on format error. 3825 + + */ 3826 + +static gboolean 3827 + +unpack_and_match(FpPrint *print, const guint8 *captured, Cb2000NccTelemetry *tel) 3828 + +{ 3829 + + g_autoptr(GVariant) print_data = NULL; 3830 + + g_autoptr(GVariant) images_var = NULL; 3831 + + gdouble captured_grad[CB2000_IMG_SIZE]; 3832 + + guint8 captured_mask[CB2000_IMG_SIZE]; 3833 + + gdouble top_scores[CB2000_NR_ENROLL_STAGES] = {0}; 3834 + + guint32 version; 3835 + + guint16 width, height; 3836 + + const guint8 *images_data; 3837 + + gsize images_len; 3838 + + guint mask_threshold; 3839 + + guint min_samples; 3840 + + guint support_min; 3841 + + gint max_shift; 3842 + + gdouble support_int; 3843 + + gdouble support_grad; 3844 + + gdouble support_int_effective; 3845 + + gdouble support_grad_effective; 3846 + + gint n_images; 3847 + + gint i; 3848 + + guint top_n; 3849 + + 3850 + + g_object_get(print, "fpi-data", &print_data, NULL); 3851 + + if (!print_data) { 3852 + + fp_warn("No print data found"); 3853 + + return FALSE; 3854 + + } 3855 + + 3856 + + if (!g_variant_check_format_string(print_data, CB2000_PRINT_FORMAT, FALSE)) { 3857 + + fp_warn("Invalid print data format"); 3858 + + return FALSE; 3859 + + } 3860 + + 3861 + + g_variant_get(print_data, CB2000_PRINT_FORMAT, 3862 + + &version, &width, &height, &images_var); 3863 + + 3864 + + if (version != 1 || width != CB2000_IMG_WIDTH || height != CB2000_IMG_HEIGHT) { 3865 + + fp_warn("Print data version/size mismatch: v=%u w=%u h=%u", 3866 + + version, width, height); 3867 + + return FALSE; 3868 + + } 3869 + + 3870 + + images_data = g_variant_get_fixed_array(images_var, &images_len, 1); 3871 + + n_images = images_len / CB2000_IMG_SIZE; 3872 + + 3873 + + if (n_images < 1 || images_len % CB2000_IMG_SIZE != 0) { 3874 + + fp_warn("Invalid image data length: %zu", images_len); 3875 + + return FALSE; 3876 + + } 3877 + + 3878 + + memset(tel, 0, sizeof(*tel)); 3879 + + tel->best_intensity = -1.0; 3880 + + tel->second_intensity = -1.0; 3881 + + tel->mean_top3_intensity = -1.0; 3882 + + tel->grad_for_best_intensity = 0.0; 3883 + + 3884 + + mask_threshold = cb2000_ncc_env_uint("CB2000_NCC_MASK_THRESH", 3885 + + CB2000_NCC_MASK_THRESHOLD_DEFAULT, 3886 + + 1, 254); 3887 + + min_samples = cb2000_ncc_env_uint("CB2000_NCC_MIN_SAMPLES", 3888 + + CB2000_NCC_MIN_SAMPLES_DEFAULT, 3889 + + 64, CB2000_IMG_SIZE); 3890 + + support_min = cb2000_ncc_env_uint("CB2000_NCC_SUPPORT_MIN", 3891 + + CB2000_NCC_SUPPORT_MIN_DEFAULT, 3892 + + 1, CB2000_NR_ENROLL_STAGES); 3893 + + support_int = cb2000_ncc_env_double("CB2000_NCC_SUPPORT_INT", 3894 + + CB2000_NCC_SUPPORT_INT_DEFAULT, 3895 + + 0.0, 1.0); 3896 + + support_grad = cb2000_ncc_env_double("CB2000_NCC_SUPPORT_GRAD", 3897 + + CB2000_NCC_SUPPORT_GRAD_DEFAULT, 3898 + + 0.0, 1.0); 3899 + + max_shift = (gint) cb2000_ncc_env_uint("CB2000_NCC_MAX_SHIFT", 3900 + + CB2000_NCC_MAX_SHIFT_DEFAULT, 3901 + + 0, 8); 3902 + + 3903 + + cb2000_ncc_compute_enroll_cohesion(images_data, n_images, 3904 + + (guint8) mask_threshold, 3905 + + min_samples, 3906 + + max_shift, 3907 + + tel); 3908 + + tel->adaptive_relax = cb2000_ncc_compute_adaptive_relax(tel); 3909 + + support_int_effective = MAX(0.0, support_int - (tel->adaptive_relax * 0.50)); 3910 + + support_grad_effective = MAX(0.0, support_grad - (tel->adaptive_relax * 0.35)); 3911 + + 3912 + + fp_info("[ NCC ] enroll cohesion: pairs=%u mean_int=%.4f min_int=%.4f mean_grad=%.4f relax=%.4f", 3913 + + tel->enroll_pair_count, 3914 + + tel->enroll_pair_mean_intensity, 3915 + + tel->enroll_pair_min_intensity, 3916 + + tel->enroll_pair_mean_gradient, 3917 + + tel->adaptive_relax); 3918 + + 3919 + + cb2000_build_foreground_mask(captured, captured_mask, 3920 + + CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT, 3921 + + (guint8) mask_threshold); 3922 + + cb2000_compute_gradient_magnitude(captured, captured_grad, 3923 + + CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT); 3924 + + 3925 + + for (i = 0; i < n_images; i++) { 3926 + + const guint8 *enrolled = images_data + i * CB2000_IMG_SIZE; 3927 + + gdouble enrolled_grad[CB2000_IMG_SIZE]; 3928 + + guint8 enrolled_mask[CB2000_IMG_SIZE]; 3929 + + gdouble int_score; 3930 + + gdouble grad_score; 3931 + + 3932 + + cb2000_build_foreground_mask(enrolled, enrolled_mask, 3933 + + CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT, 3934 + + (guint8) mask_threshold); 3935 + + cb2000_compute_gradient_magnitude(enrolled, enrolled_grad, 3936 + + CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT); 3937 + + 3938 + + int_score = cb2000_ncc_match_u8_masked(captured, enrolled, 3939 + + captured_mask, enrolled_mask, 3940 + + CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT, 3941 + + max_shift, min_samples); 3942 + + grad_score = cb2000_ncc_match_f64_masked(captured_grad, enrolled_grad, 3943 + + captured_mask, enrolled_mask, 3944 + + CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT, 3945 + + max_shift, min_samples); 3946 + + 3947 + + top_scores[i] = int_score; 3948 + + tel->compared_images++; 3949 + + 3950 + + if (int_score > tel->best_intensity) { 3951 + + tel->second_intensity = tel->best_intensity; 3952 + + tel->best_intensity = int_score; 3953 + + tel->grad_for_best_intensity = grad_score; 3954 + + } else if (int_score > tel->second_intensity) { 3955 + + tel->second_intensity = int_score; 3956 + + } 3957 + + 3958 + + if (int_score >= support_int_effective && grad_score >= support_grad_effective) 3959 + + tel->support_count++; 3960 + + 3961 + + fp_dbg("[ NCC ] gallery=%d int=%.4f grad=%.4f", i, int_score, grad_score); 3962 + + } 3963 + + 3964 + + top_n = MIN((guint) n_images, (guint) 3); 3965 + + for (i = 0; i < n_images; i++) { 3966 + + gint j; 3967 + + for (j = i + 1; j < n_images; j++) { 3968 + + if (top_scores[j] > top_scores[i]) { 3969 + + gdouble tmp = top_scores[i]; 3970 + + top_scores[i] = top_scores[j]; 3971 + + top_scores[j] = tmp; 3972 + + } 3973 + + } 3974 + + } 3975 + + if (top_n > 0) { 3976 + + gdouble sum = 0.0; 3977 + + for (i = 0; i < (gint) top_n; i++) 3978 + + sum += top_scores[i]; 3979 + + tel->mean_top3_intensity = sum / top_n; 3980 + + } else { 3981 + + tel->mean_top3_intensity = 0.0; 3982 + + } 3983 + + 3984 + + if (tel->second_intensity < 0.0) 3985 + + tel->second_intensity = 0.0; 3986 + + if (tel->best_intensity < 0.0) 3987 + + tel->best_intensity = 0.0; 3988 + + 3989 + + fp_info("[ NCC ] top1=%.4f top2=%.4f mean3=%.4f grad1=%.4f support=%u/%u " 3990 + + "mask_thresh=%u min_samples=%u max_shift=%d support_min=%u " 3991 + + "support_int=%.3f(%.3f) support_grad=%.3f(%.3f) adapt=%.3f", 3992 + + tel->best_intensity, 3993 + + tel->second_intensity, 3994 + + tel->mean_top3_intensity, 3995 + + tel->grad_for_best_intensity, 3996 + + tel->support_count, 3997 + + tel->compared_images, 3998 + + mask_threshold, 3999 + + min_samples, 4000 + + max_shift, 4001 + + support_min, 4002 + + support_int, 4003 + + support_int_effective, 4004 + + support_grad, 4005 + + support_grad_effective, 4006 + + tel->adaptive_relax); 4007 + + 4008 + + return TRUE; 4009 + +} 4010 + + 4011 + +/* ============================================================================ 4012 + + * MASTER CYCLE SSM 4013 + + * ============================================================================ 4014 + + * 4015 + + * Complete capture cycle matching Windows 1:1: 4016 + + * HARD_RESET -> INIT_CONFIG -> SETTLE_DELAY -> WAIT_FINGER -> 4017 + + * CAPTURE_START -> READ_IMAGE -> FINALIZE -> VERIFY_RESULT_STATUS -> 4018 + + * VERIFY_READY_QUERY -> VERIFY_READY_DECIDE -> SUBMIT_IMAGE -> 4019 + + * WAIT_REMOVAL -> REARM -> [cycle_complete -> start_new_cycle] 4020 + + */ 4021 + + 4022 + +static void cycle_run_state(FpiSsm *ssm, FpDevice *dev); 4023 + + 4024 + +/* 4025 + + * Post-activation settle delay. The device needs a short stabilization window 4026 + + * after configuration. 4027 + + */ 4028 + +static gboolean 4029 + +settle_delay_cb(gpointer user_data) 4030 + +{ 4031 + + FpiSsm *ssm = user_data; 4032 + + FpDevice *dev = fpi_ssm_get_device(ssm); 4033 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 4034 + + 4035 + + self->settle_timeout_id = 0; 4036 + + 4037 + + if (self->deactivating) { 4038 + + fpi_ssm_mark_completed(ssm); 4039 + + return G_SOURCE_REMOVE; 4040 + + } 4041 + + 4042 + + fpi_ssm_next_state(ssm); 4043 + + return G_SOURCE_REMOVE; 4044 + +} 4045 + + 4046 + +/* Disabled: early placement delay (kept for reference, Windows parity). */ 4047 + +#if 0 4048 + +static gboolean 4049 + +early_placement_delay_cb(gpointer user_data) 4050 + +{ 4051 + + FpiSsm *ssm = user_data; 4052 + + FpDevice *dev = fpi_ssm_get_device(ssm); 4053 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 4054 + + 4055 + + self->early_timeout_id = 0; 4056 + + self->early_placement_detected = FALSE; 4057 + + 4058 + + if (self->deactivating) { 4059 + + fpi_ssm_mark_completed(ssm); 4060 + + return G_SOURCE_REMOVE; 4061 + + } 4062 + + 4063 + + fpi_ssm_jump_to_state(ssm, CYCLE_WAIT_FINGER); 4064 + + return G_SOURCE_REMOVE; 4065 + +} 4066 + +#endif 4067 + + 4068 + +/* 4069 + + * Master cycle state machine. This is the authoritative control flow for 4070 + + * capture, and is designed to stay as close to Windows behavior as possible. 4071 + + */ 4072 + +static void 4073 + +cycle_run_state(FpiSsm *ssm, FpDevice *dev) 4074 + +{ 4075 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 4076 + + 4077 + + /* Check deactivation at every state entry */ 4078 + + if (self->deactivating || self->deactivation_in_progress) { 4079 + + return; 4080 + + } 4081 + + 4082 + + switch (fpi_ssm_get_cur_state(ssm)) { 4083 + + 4084 + + case CYCLE_RECOVERY: 4085 + + if (self->force_recovery) { 4086 + + fp_warn("Cycle: RECOVERY (hard reset + re-init)"); 4087 + + } else { 4088 + + fp_dbg("Cycle: RECOVERY (no-op)"); 4089 + + } 4090 + + fpi_ssm_next_state(ssm); 4091 + + break; 4092 + + 4093 + + case CYCLE_HARD_RESET: 4094 + + { 4095 + + GUsbDevice *usb = fpi_device_get_usb_device(dev); 4096 + + GError *err = NULL; 4097 + + 4098 + + if (self->initial_activation_done && !self->force_recovery) { 4099 + + fp_dbg("Cycle: HARD_RESET skipped (rearm mode)"); 4100 + + fpi_ssm_jump_to_state(ssm, CYCLE_WAIT_FINGER); 4101 + + break; 4102 + + } 4103 + + 4104 + + fp_dbg("Cycle: HARD_RESET (USB reset + reclaim)"); 4105 + + 4106 + + /* Release interface before reset */ 4107 + + g_usb_device_release_interface(usb, 0, 0, &err); 4108 + + g_clear_error(&err); 4109 + + 4110 + + /* USB device reset */ 4111 + + g_usb_device_reset(usb, &err); 4112 + + if (err) { 4113 + + fp_warn("USB reset: %s", err->message); 4114 + + g_clear_error(&err); 4115 + + } 4116 + + 4117 + + /* Reclaim interface */ 4118 + + if (!g_usb_device_claim_interface(usb, 0, 4119 + + G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, &err)) { 4120 + + fpi_ssm_mark_failed(ssm, err); 4121 + + return; 4122 + + } 4123 + + 4124 + + fpi_ssm_next_state(ssm); 4125 + + break; 4126 + + } 4127 + + 4128 + + case CYCLE_INIT_CONFIG: 4129 + + fp_dbg("Cycle: INIT_CONFIG (activation sub-SSM)"); 4130 + + { 4131 + + FpiSsm *subsm = fpi_ssm_new(dev, activate_run_state, 4132 + + STATE_ACTIVATE_NUM_STATES); 4133 + + fpi_ssm_start_subsm(ssm, subsm); 4134 + + } 4135 + + break; 4136 + + 4137 + + case CYCLE_SETTLE_DELAY: 4138 + + if (!self->initial_activation_done) { 4139 + + self->initial_activation_done = TRUE; 4140 + + } 4141 + + if (self->force_recovery) { 4142 + + self->force_recovery = FALSE; 4143 + + } 4144 + + fp_dbg("Cycle: SETTLE_DELAY (%dms)", CB2000_WAKEUP_DELAY); 4145 + + self->settle_timeout_id = g_timeout_add(CB2000_WAKEUP_DELAY, 4146 + + settle_delay_cb, ssm); 4147 + + break; 4148 + + 4149 + + case CYCLE_WAIT_FINGER: 4150 + + fp_dbg("Cycle: WAIT_FINGER (polling sub-SSM, interval=%dms)", 4151 + + CB2000_POLL_INTERVAL); 4152 + + /* Reset counters for a new detection window. */ 4153 + + self->poll_stable_hits = 0; 4154 + + self->no_finger_streak = 0; 4155 + + self->poll_total_count = 0; 4156 + + self->early_placement_detected = FALSE; 4157 + + self->poll_start_us = g_get_monotonic_time(); 4158 + + { 4159 + + FpiSsm *subsm = fpi_ssm_new(dev, poll_finger_run_state, 4160 + + POLL_FINGER_NUM); 4161 + + fpi_ssm_start_subsm(ssm, subsm); 4162 + + } 4163 + + break; 4164 + + 4165 + + case CYCLE_EARLY_PLACEMENT_DELAY: 4166 + + /* Disabled for Windows parity (no early-placement delay). */ 4167 + + fpi_ssm_next_state(ssm); 4168 + + break; 4169 + + 4170 + + case CYCLE_CAPTURE_START: 4171 + + { 4172 + + FpiDeviceAction action = fpi_device_get_current_action(dev); 4173 + + const char *seq_name = cb2000_capture_start_seq_name_for_action(action); 4174 + + const Cb2000Command *seq_cmds = cb2000_capture_start_cmds_for_action(action); 4175 + + const char *phase_name = cb2000_is_verify_phase_action(action) 4176 + + ? "verify_like" 4177 + + : "capture_enroll"; 4178 + + 4179 + + self->finalize_ack1_len = 0; 4180 + + self->finalize_ack2_len = 0; 4181 + + memset(self->finalize_ack1, 0, sizeof(self->finalize_ack1)); 4182 + + memset(self->finalize_ack2, 0, sizeof(self->finalize_ack2)); 4183 + + self->verify_ack_status_last = 0x00; 4184 + + self->verify_ack_decision_last = CB2000_VERIFY_ACK_UNKNOWN; 4185 + + self->verify_status_code_last = 0x00; 4186 + + self->verify_result_code_last = 0x00; 4187 + + self->verify_route_last = CB2000_VERIFY_ROUTE_UNKNOWN; 4188 + + self->verify_result_class_last = CB2000_VERIFY_RESULT_CLASS_UNKNOWN; 4189 + + self->verify_ready_query_len = 0; 4190 + + memset(self->verify_ready_query_data, 0, sizeof(self->verify_ready_query_data)); 4191 + + self->verify_ready_query_status_last = 0x00; 4192 + + self->verify_ready_query_b_len = 0; 4193 + + memset(self->verify_ready_query_b_data, 0, sizeof(self->verify_ready_query_b_data)); 4194 + + self->verify_ready_query_b_status_last = 0x00; 4195 + + self->verify_ready_matrix_last = CB2000_VERIFY_READY_MATRIX_UNKNOWN; 4196 + + self->verify_pre_capture_status_len = 0; 4197 + + memset(self->verify_pre_capture_status, 0, sizeof(self->verify_pre_capture_status)); 4198 + + 4199 + + fp_info("[ SSM_ROUTE ] action=%s phase=%s state=CAPTURE_START seq=%s", 4200 + + cb2000_action_label(action), phase_name, seq_name); 4201 + + run_command_sequence(ssm, dev, seq_name, seq_cmds); 4202 + + break; 4203 + + } 4204 + + 4205 + + case CYCLE_READ_IMAGE: 4206 + + fp_dbg("Cycle: READ_IMAGE (20 chunks)"); 4207 + + self->chunks_read = 0; 4208 + + self->image_offset = 0; 4209 + + memset(self->image_buffer, 0, CB2000_IMG_SIZE); 4210 + + { 4211 + + FpiSsm *subsm = fpi_ssm_new(dev, image_read_run_state, 4212 + + IMG_READ_NUM); 4213 + + fpi_ssm_start_subsm(ssm, subsm); 4214 + + } 4215 + + break; 4216 + + 4217 + + case CYCLE_FINALIZE: 4218 + + { 4219 + + FpiDeviceAction action = fpi_device_get_current_action(dev); 4220 + + const char *seq_name = cb2000_capture_finalize_seq_name_for_action(action); 4221 + + const Cb2000Command *seq_cmds = cb2000_capture_finalize_cmds_for_action(action); 4222 + + const char *phase_name = cb2000_is_verify_phase_action(action) 4223 + + ? "verify_like" 4224 + + : "capture_enroll"; 4225 + + 4226 + + fp_info("[ SSM_ROUTE ] action=%s phase=%s state=FINALIZE seq=%s", 4227 + + cb2000_action_label(action), phase_name, seq_name); 4228 + + run_command_sequence(ssm, dev, seq_name, seq_cmds); 4229 + + break; 4230 + + } 4231 + + 4232 + + case CYCLE_VERIFY_RESULT_STATUS: 4233 + + { 4234 + + FpiDeviceAction action = fpi_device_get_current_action(dev); 4235 + + guint attempt = self->submit_verify_total + 1; 4236 + + gboolean gate_retry = FALSE; 4237 + + 4238 + + if (!cb2000_is_verify_phase_action(action)) { 4239 + + fpi_ssm_jump_to_state(ssm, CYCLE_SUBMIT_IMAGE); 4240 + + break; 4241 + + } 4242 + + 4243 + + gate_retry = cb2000_verify_finalize_ack_gate(dev, self); 4244 + + 4245 + + if (gate_retry) { 4246 + + self->verify_route_last = CB2000_VERIFY_ROUTE_RETRY_GATE; 4247 + + fp_info("[ VERIFY_RESULT ] attempt=%u decision=%s result_class=%s status=0x%02x result=0x%02x route=%s experimental=%d status_short=%d", 4248 + + attempt, 4249 + + cb2000_verify_ack_decision_label(self->verify_ack_decision_last), 4250 + + cb2000_verify_result_class_label(self->verify_result_class_last), 4251 + + self->verify_status_code_last, 4252 + + self->verify_result_code_last, 4253 + + cb2000_verify_route_label(self->verify_route_last), 4254 + + self->experimental_device_assisted_verify, 4255 + + self->verify_device_status_short_circuit); 4256 + + fpi_ssm_jump_to_state(ssm, CYCLE_WAIT_REMOVAL); 4257 + + break; 4258 + + } 4259 + + 4260 + + switch (self->verify_ack_decision_last) { 4261 + + case CB2000_VERIFY_ACK_RETRY: 4262 + + /* Retry already handled by finalize_ack_gate; fall through to NBIS. */ 4263 + + 4264 + + case CB2000_VERIFY_ACK_DEVICE_NOMATCH: 4265 + + if (self->verify_device_status_short_circuit || 4266 + + self->experimental_device_assisted_verify) { 4267 + + self->verify_route_last = CB2000_VERIFY_ROUTE_DEVICE_NOMATCH; 4268 + + fp_info("[ VERIFY_RESULT ] attempt=%u decision=%s result_class=%s status=0x%02x result=0x%02x route=%s experimental=%d status_short=%d", 4269 + + attempt, 4270 + + cb2000_verify_ack_decision_label(self->verify_ack_decision_last), 4271 + + cb2000_verify_result_class_label(self->verify_result_class_last), 4272 + + self->verify_status_code_last, 4273 + + self->verify_result_code_last, 4274 + + cb2000_verify_route_label(self->verify_route_last), 4275 + + self->experimental_device_assisted_verify, 4276 + + self->verify_device_status_short_circuit); 4277 + + fpi_device_verify_report(dev, FPI_MATCH_FAIL, NULL, NULL); 4278 + + fpi_ssm_jump_to_state(ssm, CYCLE_WAIT_REMOVAL); 4279 + + break; 4280 + + } 4281 + + self->verify_route_last = CB2000_VERIFY_ROUTE_HOST_NBIS; 4282 + + fp_info("[ VERIFY_RESULT ] attempt=%u decision=%s result_class=%s status=0x%02x result=0x%02x route=%s experimental=%d status_short=%d", 4283 + + attempt, 4284 + + cb2000_verify_ack_decision_label(self->verify_ack_decision_last), 4285 + + cb2000_verify_result_class_label(self->verify_result_class_last), 4286 + + self->verify_status_code_last, 4287 + + self->verify_result_code_last, 4288 + + cb2000_verify_route_label(self->verify_route_last), 4289 + + self->experimental_device_assisted_verify, 4290 + + self->verify_device_status_short_circuit); 4291 + + fpi_ssm_next_state(ssm); 4292 + + break; 4293 + + 4294 + + case CB2000_VERIFY_ACK_READY: 4295 + + if (self->experimental_device_assisted_verify) { 4296 + + self->verify_route_last = CB2000_VERIFY_ROUTE_DEVICE_STATUS; 4297 + + } else { 4298 + + self->verify_route_last = CB2000_VERIFY_ROUTE_HOST_NBIS; 4299 + + } 4300 + + fp_info("[ VERIFY_RESULT ] attempt=%u decision=%s result_class=%s status=0x%02x result=0x%02x route=%s experimental=%d status_short=%d", 4301 + + attempt, 4302 + + cb2000_verify_ack_decision_label(self->verify_ack_decision_last), 4303 + + cb2000_verify_result_class_label(self->verify_result_class_last), 4304 + + self->verify_status_code_last, 4305 + + self->verify_result_code_last, 4306 + + cb2000_verify_route_label(self->verify_route_last), 4307 + + self->experimental_device_assisted_verify, 4308 + + self->verify_device_status_short_circuit); 4309 + + if (self->verify_ready_query_enabled) { 4310 + + fp_info("[ VERIFY_RESULT ] READY -> entering complementary status query"); 4311 + + fpi_ssm_jump_to_state(ssm, CYCLE_VERIFY_READY_QUERY_A); 4312 + + } else { 4313 + + fpi_ssm_jump_to_state(ssm, CYCLE_SUBMIT_IMAGE); 4314 + + } 4315 + + break; 4316 + + 4317 + + case CB2000_VERIFY_ACK_UNKNOWN: 4318 + + default: 4319 + + self->verify_route_last = CB2000_VERIFY_ROUTE_UNKNOWN; 4320 + + fp_info("[ VERIFY_RESULT ] attempt=%u decision=%s result_class=%s status=0x%02x result=0x%02x route=%s experimental=%d status_short=%d", 4321 + + attempt, 4322 + + cb2000_verify_ack_decision_label(self->verify_ack_decision_last), 4323 + + cb2000_verify_result_class_label(self->verify_result_class_last), 4324 + + self->verify_status_code_last, 4325 + + self->verify_result_code_last, 4326 + + cb2000_verify_route_label(self->verify_route_last), 4327 + + self->experimental_device_assisted_verify, 4328 + + self->verify_device_status_short_circuit); 4329 + + fpi_ssm_next_state(ssm); 4330 + + break; 4331 + + } 4332 + + break; 4333 + + } 4334 + + 4335 + + case CYCLE_VERIFY_READY_QUERY_A: 4336 + + { 4337 + + FpiDeviceAction action = fpi_device_get_current_action(dev); 4338 + + 4339 + + if (!cb2000_is_verify_phase_action(action) || !self->verify_ready_query_enabled) { 4340 + + fpi_ssm_jump_to_state(ssm, CYCLE_SUBMIT_IMAGE); 4341 + + break; 4342 + + } 4343 + + 4344 + + self->verify_ready_query_len = 0; 4345 + + memset(self->verify_ready_query_data, 0, sizeof(self->verify_ready_query_data)); 4346 + + self->verify_ready_query_status_last = 0x00; 4347 + + 4348 + + fp_info("[ SSM_ROUTE ] action=%s phase=verify_like state=VERIFY_READY_QUERY_A", 4349 + + cb2000_action_label(action)); 4350 + + run_command_sequence(ssm, dev, "verify_ready_query_a", verify_ready_query_a_cmds); 4351 + + break; 4352 + + } 4353 + + 4354 + + case CYCLE_VERIFY_READY_QUERY_B: 4355 + + { 4356 + + FpiDeviceAction action = fpi_device_get_current_action(dev); 4357 + + 4358 + + if (!cb2000_is_verify_phase_action(action) || !self->verify_ready_query_enabled) { 4359 + + fpi_ssm_jump_to_state(ssm, CYCLE_SUBMIT_IMAGE); 4360 + + break; 4361 + + } 4362 + + 4363 + + if (!self->verify_ready_query_b_enabled) { 4364 + + fp_dbg("[ VERIFY_READY_QUERY_B ] disabled -> skipping"); 4365 + + fpi_ssm_next_state(ssm); 4366 + + break; 4367 + + } 4368 + + 4369 + + self->verify_ready_query_b_len = 0; 4370 + + memset(self->verify_ready_query_b_data, 0, sizeof(self->verify_ready_query_b_data)); 4371 + + self->verify_ready_query_b_status_last = 0x00; 4372 + + 4373 + + fp_info("[ SSM_ROUTE ] action=%s phase=verify_like state=VERIFY_READY_QUERY_B", 4374 + + cb2000_action_label(action)); 4375 + + run_command_sequence(ssm, dev, "verify_ready_query_b", verify_ready_query_b_cmds); 4376 + + break; 4377 + + } 4378 + + 4379 + + case CYCLE_VERIFY_READY_DECIDE: 4380 + + { 4381 + + FpiDeviceAction action = fpi_device_get_current_action(dev); 4382 + + guint8 status_a = self->verify_ready_query_status_last; 4383 + + guint8 status_b = self->verify_ready_query_b_status_last; 4384 + + gboolean has_b = self->verify_ready_query_b_enabled && 4385 + + (self->verify_ready_query_b_len > 0); 4386 + + Cb2000VerifyReadyMatrixDecision matrix; 4387 + + 4388 + + if (!cb2000_is_verify_phase_action(action) || !self->verify_ready_query_enabled) { 4389 + + fpi_ssm_jump_to_state(ssm, CYCLE_SUBMIT_IMAGE); 4390 + + break; 4391 + + } 4392 + + 4393 + + matrix = cb2000_decide_ready_query_matrix(status_a, has_b, status_b); 4394 + + self->verify_ready_matrix_last = matrix; 4395 + + 4396 + + if (has_b) { 4397 + + fp_info("[ VERIFY_READY_MATRIX ] A=0x%02x(%s) B=0x%02x(%s) => %s", 4398 + + status_a, 4399 + + cb2000_verify_ready_query_class_label(status_a), 4400 + + status_b, 4401 + + cb2000_verify_ready_query_class_label(status_b), 4402 + + cb2000_verify_ready_matrix_label(matrix)); 4403 + + } else { 4404 + + fp_info("[ VERIFY_READY_MATRIX ] A=0x%02x(%s) B=N/A => %s", 4405 + + status_a, 4406 + + cb2000_verify_ready_query_class_label(status_a), 4407 + + cb2000_verify_ready_matrix_label(matrix)); 4408 + + } 4409 + + 4410 + + switch (matrix) { 4411 + + case CB2000_VERIFY_READY_MATRIX_READY: 4412 + + self->verify_ready_query_ready_total++; 4413 + + self->verify_ready_matrix_ready_total++; 4414 + + /* Keep conservative fallback to NBIS until MoC decision table is closed. */ 4415 + + if (self->verify_route_last == CB2000_VERIFY_ROUTE_DEVICE_STATUS) 4416 + + self->verify_route_last = CB2000_VERIFY_ROUTE_HOST_NBIS; 4417 + + fpi_ssm_next_state(ssm); 4418 + + break; 4419 + + 4420 + + case CB2000_VERIFY_READY_MATRIX_RETRY: 4421 + + self->verify_ready_query_retry_total++; 4422 + + self->verify_ready_matrix_retry_total++; 4423 + + cb2000_retry_scan_with_cause(dev, CB2000_RETRY_DEVICE_STATUS, 4424 + + "(verify ready matrix retry)"); 4425 + + fpi_ssm_jump_to_state(ssm, CYCLE_WAIT_REMOVAL); 4426 + + break; 4427 + + 4428 + + case CB2000_VERIFY_READY_MATRIX_DEVICE_NOMATCH: 4429 + + self->verify_ready_query_nomatch_total++; 4430 + + self->verify_ready_matrix_nomatch_total++; 4431 + + if (self->verify_device_status_short_circuit) { 4432 + + self->verify_route_last = CB2000_VERIFY_ROUTE_DEVICE_NOMATCH; 4433 + + fpi_device_verify_report(dev, FPI_MATCH_FAIL, NULL, NULL); 4434 + + fpi_ssm_jump_to_state(ssm, CYCLE_WAIT_REMOVAL); 4435 + + } else { 4436 + + fpi_ssm_next_state(ssm); 4437 + + } 4438 + + break; 4439 + + 4440 + + case CB2000_VERIFY_READY_MATRIX_UNKNOWN: 4441 + + default: 4442 + + self->verify_ready_query_unknown_total++; 4443 + + self->verify_ready_matrix_unknown_total++; 4444 + + fpi_ssm_next_state(ssm); 4445 + + break; 4446 + + } 4447 + + break; 4448 + + } 4449 + + 4450 + + case CYCLE_SUBMIT_IMAGE: 4451 + + fp_dbg("Cycle: SUBMIT_IMAGE (got %zu/%d bytes)", 4452 + + self->image_offset, CB2000_IMG_SIZE); 4453 + + 4454 + + /* === RAW DUMP === 4455 + + * Enabled only for debugging and protocol validation. */ 4456 + + #if CB2000_RAW_DUMP_ENABLED 4457 + + { 4458 + + time_t now = time(NULL); 4459 + + struct tm *tm_info = localtime(&now); 4460 + + char timestamp[32]; 4461 + + strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", tm_info); 4462 + + const gchar *out_dir = cb2000_get_output_dir(); 4463 + + g_autofree gchar *dump_name = g_strdup_printf("canvasbio_%s.raw", timestamp); 4464 + + g_autofree gchar *dump_path = NULL; 4465 + + 4466 + + g_mkdir_with_parents(out_dir, 0755); 4467 + + dump_path = g_build_filename(out_dir, dump_name, NULL); 4468 + + 4469 + + if (g_file_set_contents(dump_path, 4470 + + (const gchar *)self->image_buffer, 4471 + + self->image_offset, NULL)) { 4472 + + fp_info("RAW DUMP: %zu bytes -> %s", self->image_offset, dump_path); 4473 + + } else { 4474 + + fp_warn("RAW DUMP: failed to save %s", dump_path); 4475 + + } 4476 + + } 4477 + + #endif 4478 + + 4479 + + /* Create and submit image to libfprint for MoH processing. */ 4480 + + { 4481 + + FpImage *img = fp_image_new(CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT); 4482 + + img->ppmm = cb2000_get_ppmm(); 4483 + + 4484 + + gsize copy_len = MIN(self->image_offset, CB2000_IMG_SIZE); 4485 + + memcpy(img->data, self->image_buffer, copy_len); 4486 + + 4487 + + if (copy_len < CB2000_IMG_SIZE) { 4488 + + memset(img->data + copy_len, 0x00, 4489 + + CB2000_IMG_SIZE - copy_len); 4490 + + } 4491 + + 4492 + + double variance = calculate_variance(img->data, 4493 + + CB2000_IMG_WIDTH, 4494 + + CB2000_IMG_HEIGHT); 4495 + + double low_var_ratio = calculate_block_low_var_ratio( 4496 + + img->data, CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT, 4497 + + CB2000_BLOCK_SIZE, CB2000_LOW_VAR_THRESHOLD); 4498 + + double focus_var = calculate_laplacian_variance( 4499 + + img->data, CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT); 4500 + + fp_info("Image variance: %.1f low_var_ratio=%.2f focus=%.1f", 4501 + + variance, low_var_ratio, focus_var); 4502 + + fp_info("Detect reg51=%s, Capture reg51=%s", 4503 + + CB2000_USE_ALT_DETECT_51 ? "0x68" : "0xA8", 4504 + + "0x88"); 4505 + + 4506 + + FpiDeviceAction action = fpi_device_get_current_action(dev); 4507 + + gboolean use_bg_subtract = cb2000_should_apply_bg_subtraction(action); 4508 + + 4509 + + if (cb2000_is_verify_phase_action(action)) { 4510 + + fp_info("[ SSM_ROUTE ] action=%s phase=verify_like state=SUBMIT_IMAGE route=%s", 4511 + + cb2000_action_label(action), 4512 + + cb2000_verify_route_label(self->verify_route_last)); 4513 + + } else { 4514 + + fp_dbg("[ SSM_ROUTE ] action=%s phase=capture_enroll state=SUBMIT_IMAGE route=HOST_PREPROCESS", 4515 + + cb2000_action_label(action)); 4516 + + } 4517 + + 4518 + + /* 4519 + + * Background bootstrap is useful for capture/enroll only. 4520 + + * In verify/identify, Windows parity relies on finalize ACK routing, 4521 + + * so a low-variance frame should not be re-labeled as background. 4522 + + */ 4523 + + if (!cb2000_is_verify_phase_action(action) && 4524 + + !self->background_valid && 4525 + + variance < CB2000_BG_CAPTURE_MAX_VARIANCE) { 4526 + + memcpy(self->background_buffer, img->data, CB2000_IMG_SIZE); 4527 + + self->background_valid = TRUE; 4528 + + fp_info("Background captured (variance %.1f) - retrying capture", 4529 + + variance); 4530 + + g_object_unref(img); 4531 + + cb2000_retry_scan_with_cause(dev, 4532 + + CB2000_RETRY_BACKGROUND_CAPTURE, 4533 + + "(capture background)"); 4534 + + fpi_ssm_next_state(ssm); 4535 + + return; 4536 + + } 4537 + + 4538 + + if (self->background_valid && use_bg_subtract) { 4539 + + apply_background_subtraction(img->data, 4540 + + self->background_buffer, 4541 + + CB2000_IMG_WIDTH, 4542 + + CB2000_IMG_HEIGHT); 4543 + + } 4544 + + 4545 + + /* CLAHE: local contrast enhancement on raw image before upscale. 4546 + + * This is the key preprocessing for NBIS on small sensors. */ 4547 + + apply_clahe(img->data, CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT, 4548 + + CB2000_CLAHE_TILE_W, CB2000_CLAHE_TILE_H, 4549 + + CB2000_CLAHE_CLIP_LIMIT); 4550 + + 4551 + + normalize_contrast(img->data, CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT); 4552 + + /* Polarity: keep raw sensor orientation (white ridges on black). */ 4553 + + /* invert_image_u8(img->data, CB2000_IMG_SIZE); */ 4554 + + 4555 + + double variance_norm = calculate_variance(img->data, 4556 + + CB2000_IMG_WIDTH, 4557 + + CB2000_IMG_HEIGHT); 4558 + + double low_var_ratio_norm = calculate_block_low_var_ratio( 4559 + + img->data, CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT, 4560 + + CB2000_BLOCK_SIZE, CB2000_LOW_VAR_THRESHOLD); 4561 + + fp_info("Image variance (normalized): %.1f low_var_ratio=%.2f", 4562 + + variance_norm, low_var_ratio_norm); 4563 + + 4564 + + const int area_min_pct = cb2000_get_area_min_pct(action); 4565 + + 4566 + + double area_ratio_norm = calculate_fingerprint_area_ratio( 4567 + + img->data, CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT); 4568 + + int area_pct = (int)(area_ratio_norm * 100.0 + 0.5); 4569 + + int overlap_pct = -1; 4570 + + if (self->previous_norm_valid && self->previous_norm_buffer) { 4571 + + overlap_pct = calculate_overlap_percent(img->data, 4572 + + self->previous_norm_buffer, 4573 + + CB2000_IMG_SIZE); 4574 + + } 4575 + + int quality_score = calculate_quality_score(variance_norm, 4576 + + focus_var, 4577 + + area_ratio_norm); 4578 + + fp_info("[ IMAGE ] quality = %d, area = %d, overlap = %d", 4579 + + quality_score, area_pct, overlap_pct); 4580 + + 4581 + + gboolean fail_area = (area_pct < area_min_pct); 4582 + + gboolean fail_variance = (variance_norm < CB2000_MIN_IMAGE_VARIANCE); 4583 + + gboolean fail_focus = (focus_var < CB2000_MIN_FOCUS_VARIANCE); 4584 + + 4585 + + if (fail_area) { 4586 + + fp_warn("Reject! quality.fingerprint_area %d < %d (threshold)", 4587 + + area_pct, area_min_pct); 4588 + + } 4589 + + 4590 + + if (fail_variance || fail_area || fail_focus) { 4591 + + fp_warn("Reject! Image quality too bad. var=%.1f area=%d focus=%.1f overlap=%d", 4592 + + variance_norm, area_pct, focus_var, overlap_pct); 4593 + + fp_info("[ GATE ] action=%s min_area=%d fail_var=%d fail_area=%d fail_focus=%d", 4594 + + cb2000_action_label(action), area_min_pct, 4595 + + fail_variance, fail_area, fail_focus); 4596 + + if (self->previous_norm_buffer) { 4597 + + memcpy(self->previous_norm_buffer, img->data, CB2000_IMG_SIZE); 4598 + + self->previous_norm_valid = TRUE; 4599 + + } 4600 + + g_object_unref(img); 4601 + + /* Avoid misleading "finger not centered" when failure is just quality/coverage. */ 4602 + + cb2000_retry_scan_with_cause(dev, 4603 + + fail_area 4604 + + ? CB2000_RETRY_AREA_GATE 4605 + + : CB2000_RETRY_QUALITY_GATE, 4606 + + "(quality gate)"); 4607 + + fpi_ssm_next_state(ssm); 4608 + + return; 4609 + + } 4610 + + 4611 + + /* Let libfprint invert for matching (see fp-image.c). */ 4612 + + img->flags = FPI_IMAGE_COLORS_INVERTED; 4613 + + 4614 + + /* Optional upscale to improve NBIS minutiae extraction on small images. */ 4615 + + int upscale = cb2000_get_upscale_factor(action); 4616 + + fp_dbg("Preprocess profile: action=%s bg_subtract=%d upscale=%dx", 4617 + + cb2000_action_label(action), use_bg_subtract, upscale); 4618 + + if (upscale > 1) { 4619 + + FpImage *resized = fpi_image_resize(img, upscale, upscale); 4620 + + if (resized) { 4621 + + resized->flags = img->flags; 4622 + + resized->ppmm = img->ppmm * upscale; 4623 + + /* Smooth bilinear upscale artifacts for NBIS ridge detection. */ 4624 + + gaussian_blur_3x3(resized->data, resized->width, 4625 + + resized->height); 4626 + + g_object_unref(img); 4627 + + img = resized; 4628 + + } 4629 + + } 4630 + + 4631 + + /* Save action-scoped PGM for debug (latest frame always available). */ 4632 + + cb2000_save_debug_pgm(img, action); 4633 + + 4634 + + if (cb2000_dump_binarized_enabled()) { 4635 + + FpImage *dbg = fp_image_new(CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT); 4636 + + dbg->ppmm = img->ppmm; 4637 + + dbg->flags = img->flags; 4638 + + memcpy(dbg->data, img->data, CB2000_IMG_SIZE); 4639 + + 4640 + + g_autofree gchar *label = 4641 + + g_strdup(cb2000_action_label(action)); 4642 + + fp_image_detect_minutiae(dbg, NULL, cb2000_dump_binarized_cb, 4643 + + g_steal_pointer(&label)); 4644 + + } 4645 + + 4646 + + fp_info("Captured fingerprint: %zu bytes (%dx%d)", 4647 + + self->image_offset, img->width, img->height); 4648 + + 4649 + + if (self->previous_norm_buffer) { 4650 + + memcpy(self->previous_norm_buffer, img->data, CB2000_IMG_SIZE); 4651 + + self->previous_norm_valid = TRUE; 4652 + + } 4653 + + 4654 + + gboolean precheck_enabled = cb2000_should_precheck_minutiae(action); 4655 + + fp_dbg("Precheck profile: action=%s precheck=%d", 4656 + + cb2000_action_label(action), precheck_enabled); 4657 + + 4658 + + if (precheck_enabled) { 4659 + + Cb2000MinutiaePrecheckCtx *ctx = g_new0(Cb2000MinutiaePrecheckCtx, 1); 4660 + + ctx->dev = dev; 4661 + + ctx->ssm = ssm; 4662 + + ctx->img = img; 4663 + + ctx->action = action; 4664 + + fp_image_detect_minutiae(img, NULL, cb2000_minutiae_precheck_cb, ctx); 4665 + + return; 4666 + + } 4667 + + 4668 + + cb2000_update_submit_telemetry(self, dev, action); 4669 + + self->accepted_total++; 4670 + + 4671 + + /* Action-specific handling (V33 FpDevice + NCC) */ 4672 + + { 4673 + + if (action == FPI_DEVICE_ACTION_CAPTURE) { 4674 + + fpi_device_capture_complete(dev, img, NULL); 4675 + + fpi_ssm_mark_completed(ssm); 4676 + + return; 4677 + + 4678 + + } else if (action == FPI_DEVICE_ACTION_ENROLL) { 4679 + + guint8 *norm = g_malloc(CB2000_IMG_SIZE); 4680 + + memcpy(norm, img->data, CB2000_IMG_SIZE); 4681 + + normalize_contrast(norm, CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT); 4682 + + g_clear_pointer(&self->enroll_images[self->enroll_stage], g_free); 4683 + + self->enroll_images[self->enroll_stage] = norm; 4684 + + self->enroll_stage++; 4685 + + g_object_unref(img); 4686 + + 4687 + + fp_info("Enrollment stage %d/%d complete", 4688 + + self->enroll_stage, CB2000_NR_ENROLL_STAGES); 4689 + + 4690 + + if (self->enroll_stage >= CB2000_NR_ENROLL_STAGES) { 4691 + + fpi_ssm_mark_completed(ssm); 4692 + + } else { 4693 + + fpi_device_enroll_progress(dev, self->enroll_stage, NULL, NULL); 4694 + + fpi_ssm_next_state(ssm); /* -> WAIT_REMOVAL -> REARM -> new cycle */ 4695 + + } 4696 + + return; 4697 + + 4698 + + } else if (action == FPI_DEVICE_ACTION_VERIFY || 4699 + + action == FPI_DEVICE_ACTION_IDENTIFY) { 4700 + + FpPrint *enrolled = NULL; 4701 + + guint8 norm[CB2000_IMG_SIZE]; 4702 + + Cb2000NccTelemetry ncc_tel; 4703 + + Cb2000NccDecision ncc_decision = CB2000_NCC_DECISION_ERROR; 4704 + + 4705 + + memcpy(norm, img->data, CB2000_IMG_SIZE); 4706 + + normalize_contrast(norm, CB2000_IMG_WIDTH, CB2000_IMG_HEIGHT); 4707 + + g_object_unref(img); 4708 + + 4709 + + fpi_device_get_verify_data(dev, &enrolled); 4710 + + if (enrolled && unpack_and_match(enrolled, norm, &ncc_tel)) { 4711 + + ncc_decision = cb2000_ncc_classify(&ncc_tel); 4712 + + switch (ncc_decision) { 4713 + + case CB2000_NCC_DECISION_MATCH: 4714 + + self->verify_result = FPI_MATCH_SUCCESS; 4715 + + break; 4716 + + case CB2000_NCC_DECISION_NO_MATCH: 4717 + + self->verify_result = FPI_MATCH_FAIL; 4718 + + break; 4719 + + case CB2000_NCC_DECISION_RETRY: 4720 + + self->verify_result = FPI_MATCH_ERROR; 4721 + + break; 4722 + + case CB2000_NCC_DECISION_ERROR: 4723 + + default: 4724 + + self->verify_result = FPI_MATCH_ERROR; 4725 + + break; 4726 + + } 4727 + + } else { 4728 + + self->verify_result = FPI_MATCH_ERROR; 4729 + + } 4730 + + 4731 + + fp_info("[ NCC ] decision=%s -> result=%s", 4732 + + cb2000_ncc_decision_label(ncc_decision), 4733 + + self->verify_result == FPI_MATCH_SUCCESS ? "MATCH" : 4734 + + self->verify_result == FPI_MATCH_FAIL ? "NO MATCH" : "ERROR"); 4735 + + 4736 + + fpi_ssm_mark_completed(ssm); 4737 + + return; 4738 + + 4739 + + } else { 4740 + + g_object_unref(img); 4741 + + fpi_ssm_next_state(ssm); 4742 + + return; 4743 + + } 4744 + + } 4745 + + } 4746 + + break; 4747 + + 4748 + + case CYCLE_WAIT_REMOVAL: 4749 + + fp_dbg("Cycle: WAIT_REMOVAL (polling sub-SSM)"); 4750 + + self->removal_poll_count = 0; 4751 + + self->removal_stable_off_hits = 0; 4752 + + self->removal_start_us = g_get_monotonic_time(); 4753 + + { 4754 + + FpiSsm *subsm = fpi_ssm_new(dev, poll_removal_run_state, 4755 + + POLL_REMOVAL_NUM); 4756 + + fpi_ssm_start_subsm(ssm, subsm); 4757 + + } 4758 + + break; 4759 + + 4760 + + case CYCLE_REARM: 4761 + + fp_dbg("Cycle: REARM (soft reset + detection rearm)"); 4762 + + run_command_sequence(ssm, dev, "rearm", rearm_cmds); 4763 + + break; 4764 + + } 4765 + +} 4766 + + 4767 + +/* 4768 + + * Cycle completion handler. Cancels any pending timers, handles recovery, 4769 + + * and starts the next cycle unless deactivation is in progress. 4770 + + */ 4771 + +static void 4772 + +cycle_complete(FpiSsm *ssm, FpDevice *dev, GError *error) 4773 + +{ 4774 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 4775 + + self->cycle_ssm = NULL; 4776 + + 4777 + + /* Cancel settle timer if pending */ 4778 + + if (self->settle_timeout_id > 0) { 4779 + + g_source_remove(self->settle_timeout_id); 4780 + + self->settle_timeout_id = 0; 4781 + + } 4782 + + if (self->poll_timeout_id > 0) { 4783 + + g_source_remove(self->poll_timeout_id); 4784 + + self->poll_timeout_id = 0; 4785 + + } 4786 + + if (self->removal_timeout_id > 0) { 4787 + + g_source_remove(self->removal_timeout_id); 4788 + + self->removal_timeout_id = 0; 4789 + + } 4790 + + if (self->early_timeout_id > 0) { 4791 + + g_source_remove(self->early_timeout_id); 4792 + + self->early_timeout_id = 0; 4793 + + } 4794 + + if (self->deactivation_timeout_id > 0) { 4795 + + g_source_remove(self->deactivation_timeout_id); 4796 + + self->deactivation_timeout_id = 0; 4797 + + } 4798 + + 4799 + + if (self->deactivating) { 4800 + + g_clear_error(&error); 4801 + + complete_deactivation(dev); 4802 + + return; 4803 + + } 4804 + + 4805 + + if (error) { 4806 + + fp_warn("Cycle failed: %s - starting new cycle (recovery)", 4807 + + error->message); 4808 + + g_error_free(error); 4809 + + self->force_recovery = TRUE; 4810 + + start_new_cycle(dev); 4811 + + return; 4812 + + } 4813 + + 4814 + + fp_dbg("[ STATS ] accepted=%u retry_total=%u bg=%u area=%u quality=%u precheck=%u dev_status=%u " 4815 + + "submit_cap=%u submit_enroll=%u submit_verify=%u submit_identify=%u " 4816 + + "verify_subprints_last=%u min=%u max=%u verify_rx=%u rx_nonzero=%u " 4817 + + "ack1=%02x:%02x:%02x:%02x ack2=%02x:%02x:%02x:%02x " 4818 + + "ack_ready=%u ack_retry=%u ack_nomatch=%u ack_unknown=%u ack_mismatch=%u " 4819 + + "verify_status=0x%02x verify_result=0x%02x verify_route=%s " 4820 + + "result_class=%s rc_ready=%u rc_retry=%u rc_nomatch=%u rc_unknown=%u status_short=%d " 4821 + + "ready_query_en=%d ready_query_total=%u ready_query_status=0x%02x " 4822 + + "ready_query_b_en=%d ready_query_b_total=%u ready_query_b_status=0x%02x " 4823 + + "rq_ready=%u rq_retry=%u rq_nomatch=%u rq_unknown=%u " 4824 + + "rq_matrix=%s m_ready=%u m_retry=%u m_nomatch=%u m_unknown=%u " 4825 + + "pre_cap_total=%u pre_cap_len=%zu pre_cap=%02x:%02x:%02x:%02x", 4826 + + self->accepted_total, 4827 + + self->retry_total, 4828 + + self->retry_cause_count[CB2000_RETRY_BACKGROUND_CAPTURE], 4829 + + self->retry_cause_count[CB2000_RETRY_AREA_GATE], 4830 + + self->retry_cause_count[CB2000_RETRY_QUALITY_GATE], 4831 + + self->retry_cause_count[CB2000_RETRY_MINUTIAE_PRECHECK], 4832 + + self->retry_cause_count[CB2000_RETRY_DEVICE_STATUS], 4833 + + self->submit_capture_total, 4834 + + self->submit_enroll_total, 4835 + + self->submit_verify_total, 4836 + + self->submit_identify_total, 4837 + + self->verify_template_subprints_last, 4838 + + self->verify_template_subprints_min, 4839 + + self->verify_template_subprints_max, 4840 + + self->verify_cmd_rx_total, 4841 + + self->verify_cmd_rx_with_data, 4842 + + self->finalize_ack1[0], self->finalize_ack1[1], 4843 + + self->finalize_ack1[2], self->finalize_ack1[3], 4844 + + self->finalize_ack2[0], self->finalize_ack2[1], 4845 + + self->finalize_ack2[2], self->finalize_ack2[3], 4846 + + self->verify_ack_ready_total, 4847 + + self->verify_ack_retry_total, 4848 + + self->verify_ack_nomatch_total, 4849 + + self->verify_ack_unknown_total, 4850 + + self->verify_ack_mismatch_total, 4851 + + self->verify_status_code_last, 4852 + + self->verify_result_code_last, 4853 + + cb2000_verify_route_label(self->verify_route_last), 4854 + + cb2000_verify_result_class_label(self->verify_result_class_last), 4855 + + self->verify_result_class_count[CB2000_VERIFY_RESULT_CLASS_READY], 4856 + + self->verify_result_class_count[CB2000_VERIFY_RESULT_CLASS_RETRY], 4857 + + self->verify_result_class_count[CB2000_VERIFY_RESULT_CLASS_DEVICE_NOMATCH], 4858 + + self->verify_result_class_count[CB2000_VERIFY_RESULT_CLASS_UNKNOWN], 4859 + + self->verify_device_status_short_circuit, 4860 + + self->verify_ready_query_enabled, 4861 + + self->verify_ready_query_total, 4862 + + self->verify_ready_query_status_last, 4863 + + self->verify_ready_query_b_enabled, 4864 + + self->verify_ready_query_b_total, 4865 + + self->verify_ready_query_b_status_last, 4866 + + self->verify_ready_query_ready_total, 4867 + + self->verify_ready_query_retry_total, 4868 + + self->verify_ready_query_nomatch_total, 4869 + + self->verify_ready_query_unknown_total, 4870 + + cb2000_verify_ready_matrix_label(self->verify_ready_matrix_last), 4871 + + self->verify_ready_matrix_ready_total, 4872 + + self->verify_ready_matrix_retry_total, 4873 + + self->verify_ready_matrix_nomatch_total, 4874 + + self->verify_ready_matrix_unknown_total, 4875 + + self->verify_pre_capture_status_total, 4876 + + self->verify_pre_capture_status_len, 4877 + + self->verify_pre_capture_status[0], 4878 + + self->verify_pre_capture_status[1], 4879 + + self->verify_pre_capture_status[2], 4880 + + self->verify_pre_capture_status[3]); 4881 + + fp_dbg("Cycle complete, checking action-specific completion"); 4882 + + 4883 + + { 4884 + + FpiDeviceAction action = fpi_device_get_current_action(dev); 4885 + + 4886 + + /* Action-specific completion for FpDevice */ 4887 + + if (action == FPI_DEVICE_ACTION_ENROLL) { 4888 + + if (self->enroll_stage >= CB2000_NR_ENROLL_STAGES) { 4889 + + /* All stages done - pack and complete */ 4890 + + FpPrint *print = NULL; 4891 + + fpi_device_get_enroll_data(dev, &print); 4892 + + pack_enrollment_data(self, print); 4893 + + fp_info("Enrollment complete (%d stages)", CB2000_NR_ENROLL_STAGES); 4894 + + fpi_device_enroll_complete(dev, g_object_ref(print), NULL); 4895 + + return; 4896 + + } 4897 + + /* More stages: fall through to start_new_cycle */ 4898 + + } else if (action == FPI_DEVICE_ACTION_VERIFY || 4899 + + action == FPI_DEVICE_ACTION_IDENTIFY) { 4900 + + if (self->verify_retry_pending) { 4901 + + GError *err = NULL; 4902 + + if (self->verify_retry_message[0] != '\0') { 4903 + + err = fpi_device_retry_new_msg(self->verify_retry_error, 4904 + + "%s", 4905 + + self->verify_retry_message); 4906 + + } else { 4907 + + err = fpi_device_retry_new(self->verify_retry_error); 4908 + + } 4909 + + fpi_device_verify_report(dev, FPI_MATCH_ERROR, NULL, err); 4910 + + self->verify_retry_pending = FALSE; 4911 + + self->verify_retry_message[0] = '\0'; 4912 + + fpi_device_verify_complete(dev, NULL); 4913 + + return; 4914 + + } 4915 + + 4916 + + if (self->verify_result == FPI_MATCH_ERROR) { 4917 + + GError *err = fpi_device_retry_new(FP_DEVICE_RETRY_GENERAL); 4918 + + fpi_device_verify_report(dev, FPI_MATCH_ERROR, NULL, err); 4919 + + } else { 4920 + + fpi_device_verify_report(dev, self->verify_result, NULL, NULL); 4921 + + } 4922 + + fpi_device_verify_complete(dev, NULL); 4923 + + return; 4924 + + } else if (action == FPI_DEVICE_ACTION_CAPTURE) { 4925 + + if (self->capture_retry_pending) { 4926 + + GError *err = NULL; 4927 + + if (self->capture_retry_message[0] != '\0') { 4928 + + err = fpi_device_retry_new_msg(self->capture_retry_error, 4929 + + "%s", 4930 + + self->capture_retry_message); 4931 + + } else { 4932 + + err = fpi_device_retry_new(self->capture_retry_error); 4933 + + } 4934 + + fpi_device_capture_complete(dev, NULL, err); 4935 + + self->capture_retry_pending = FALSE; 4936 + + self->capture_retry_message[0] = '\0'; 4937 + + return; 4938 + + } 4939 + + /* capture_complete already called in SUBMIT_IMAGE success path. */ 4940 + + return; 4941 + + } 4942 + + } 4943 + + 4944 + + fp_dbg("Starting next cycle"); 4945 + + start_new_cycle(dev); 4946 + +} 4947 + + 4948 + +/* 4949 + + * Start a fresh master cycle SSM if the device is active. 4950 + + */ 4951 + +static void 4952 + +start_new_cycle(FpDevice *dev) 4953 + +{ 4954 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 4955 + + 4956 + + if (self->deactivating) 4957 + + return; 4958 + + 4959 + + self->cycle_ssm = fpi_ssm_new(dev, cycle_run_state, 4960 + + CYCLE_NUM_STATES); 4961 + + fpi_ssm_start(self->cycle_ssm, cycle_complete); 4962 + +} 4963 + + 4964 + +/* ============================================================================ 4965 + + * DEVICE OPERATIONS 4966 + + * ============================================================================ */ 4967 + + 4968 + +/* 4969 + + * Open the USB device, claim interface, and initialize driver state. 4970 + + */ 4971 + +static void 4972 + +dev_open(FpDevice *device) 4973 + +{ 4974 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(device); 4975 + + GUsbDevice *usb_dev = fpi_device_get_usb_device(device); 4976 + + GError *error = NULL; 4977 + + 4978 + + fp_info("Opening CanvasBio CB2000 device"); 4979 + + 4980 + + if (!g_usb_device_set_configuration(usb_dev, 1, &error)) { 4981 + + fp_dbg("set_configuration: %s (may be OK if already configured)", 4982 + + error ? error->message : "unknown"); 4983 + + g_clear_error(&error); 4984 + + } 4985 + + 4986 + + if (!g_usb_device_claim_interface(usb_dev, 0, 4987 + + G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, 4988 + + &error)) { 4989 + + fp_err("Failed to claim USB interface: %s", error->message); 4990 + + fpi_device_open_complete(device, error); 4991 + + return; 4992 + + } 4993 + + 4994 + + self->image_buffer = g_malloc0(CB2000_IMG_SIZE); 4995 + + self->background_buffer = g_malloc0(CB2000_IMG_SIZE); 4996 + + self->previous_norm_buffer = g_malloc0(CB2000_IMG_SIZE); 4997 + + self->background_valid = FALSE; 4998 + + self->previous_norm_valid = FALSE; 4999 + + self->deactivating = FALSE; 5000 + + self->deactivation_in_progress = FALSE; 5001 + + if (self->deactivation_timeout_id > 0) { 5002 + + g_source_remove(self->deactivation_timeout_id); 5003 + + self->deactivation_timeout_id = 0; 5004 + + } 5005 + + self->image_offset = 0; 5006 + + self->chunks_read = 0; 5007 + + self->poll_stable_hits = 0; 5008 + + self->no_finger_streak = 0; 5009 + + self->removal_poll_count = 0; 5010 + + self->removal_stable_off_hits = 0; 5011 + + self->poll_total_count = 0; 5012 + + self->early_placement_detected = FALSE; 5013 + + self->poll_start_us = 0; 5014 + + self->removal_start_us = 0; 5015 + + self->cycle_ssm = NULL; 5016 + + self->settle_timeout_id = 0; 5017 + + self->poll_timeout_id = 0; 5018 + + self->removal_timeout_id = 0; 5019 + + self->early_timeout_id = 0; 5020 + + self->force_recovery = FALSE; 5021 + + self->retry_total = 0; 5022 + + self->accepted_total = 0; 5023 + + memset(self->retry_cause_count, 0, sizeof(self->retry_cause_count)); 5024 + + self->submit_capture_total = 0; 5025 + + self->submit_enroll_total = 0; 5026 + + self->submit_verify_total = 0; 5027 + + self->submit_identify_total = 0; 5028 + + self->verify_template_samples = 0; 5029 + + self->verify_template_subprints_last = 0; 5030 + + self->verify_template_subprints_min = 0; 5031 + + self->verify_template_subprints_max = 0; 5032 + + self->experimental_device_assisted_verify = FALSE; 5033 + + self->verify_cmd_rx_total = 0; 5034 + + self->verify_cmd_rx_with_data = 0; 5035 + + self->verify_cmd_rx_last_len = 0; 5036 + + memset(self->verify_cmd_rx_last, 0, sizeof(self->verify_cmd_rx_last)); 5037 + + self->finalize_ack1_len = 0; 5038 + + self->finalize_ack2_len = 0; 5039 + + memset(self->finalize_ack1, 0, sizeof(self->finalize_ack1)); 5040 + + memset(self->finalize_ack2, 0, sizeof(self->finalize_ack2)); 5041 + + self->verify_ack_status_last = 0x00; 5042 + + self->verify_ack_decision_last = CB2000_VERIFY_ACK_UNKNOWN; 5043 + + self->verify_ack_ready_total = 0; 5044 + + self->verify_ack_retry_total = 0; 5045 + + self->verify_ack_nomatch_total = 0; 5046 + + self->verify_ack_unknown_total = 0; 5047 + + self->verify_ack_mismatch_total = 0; 5048 + + self->verify_status_code_last = 0x00; 5049 + + self->verify_result_code_last = 0x00; 5050 + + self->verify_route_last = CB2000_VERIFY_ROUTE_UNKNOWN; 5051 + + self->verify_result_class_last = CB2000_VERIFY_RESULT_CLASS_UNKNOWN; 5052 + + memset(self->verify_result_class_count, 0, sizeof(self->verify_result_class_count)); 5053 + + self->verify_device_status_short_circuit = TRUE; 5054 + + self->verify_ready_query_enabled = TRUE; 5055 + + self->verify_ready_query_b_enabled = TRUE; 5056 + + self->verify_ready_query_total = 0; 5057 + + self->verify_ready_query_status_last = 0x00; 5058 + + self->verify_ready_query_len = 0; 5059 + + memset(self->verify_ready_query_data, 0, sizeof(self->verify_ready_query_data)); 5060 + + self->verify_ready_query_b_total = 0; 5061 + + self->verify_ready_query_b_status_last = 0x00; 5062 + + self->verify_ready_query_b_len = 0; 5063 + + memset(self->verify_ready_query_b_data, 0, sizeof(self->verify_ready_query_b_data)); 5064 + + self->verify_ready_query_ready_total = 0; 5065 + + self->verify_ready_query_retry_total = 0; 5066 + + self->verify_ready_query_nomatch_total = 0; 5067 + + self->verify_ready_query_unknown_total = 0; 5068 + + self->verify_ready_matrix_last = CB2000_VERIFY_READY_MATRIX_UNKNOWN; 5069 + + self->verify_ready_matrix_ready_total = 0; 5070 + + self->verify_ready_matrix_retry_total = 0; 5071 + + self->verify_ready_matrix_nomatch_total = 0; 5072 + + self->verify_ready_matrix_unknown_total = 0; 5073 + + self->verify_pre_capture_status_total = 0; 5074 + + self->verify_pre_capture_status_len = 0; 5075 + + memset(self->verify_pre_capture_status, 0, sizeof(self->verify_pre_capture_status)); 5076 + + self->verify_retry_pending = FALSE; 5077 + + self->verify_retry_error = FP_DEVICE_RETRY_GENERAL; 5078 + + self->verify_retry_status_code = 0x00; 5079 + + self->verify_retry_result_code = 0x00; 5080 + + self->verify_retry_message[0] = '\0'; 5081 + + self->capture_retry_pending = FALSE; 5082 + + self->capture_retry_error = FP_DEVICE_RETRY_GENERAL; 5083 + + self->capture_retry_message[0] = '\0'; 5084 + + 5085 + + fp_dbg("Device opened successfully"); 5086 + + fpi_device_open_complete(device, NULL); 5087 + +} 5088 + + 5089 + +/* 5090 + + * Release USB interface and free resources. 5091 + + */ 5092 + +static void 5093 + +dev_close(FpDevice *device) 5094 + +{ 5095 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(device); 5096 + + GError *error = NULL; 5097 + + int i; 5098 + + 5099 + + fp_info("Closing CanvasBio CB2000 device"); 5100 + + 5101 + + if (self->settle_timeout_id > 0) { 5102 + + g_source_remove(self->settle_timeout_id); 5103 + + self->settle_timeout_id = 0; 5104 + + } 5105 + + if (self->poll_timeout_id > 0) { 5106 + + g_source_remove(self->poll_timeout_id); 5107 + + self->poll_timeout_id = 0; 5108 + + } 5109 + + if (self->removal_timeout_id > 0) { 5110 + + g_source_remove(self->removal_timeout_id); 5111 + + self->removal_timeout_id = 0; 5112 + + } 5113 + + if (self->early_timeout_id > 0) { 5114 + + g_source_remove(self->early_timeout_id); 5115 + + self->early_timeout_id = 0; 5116 + + } 5117 + + 5118 + + g_clear_pointer(&self->image_buffer, g_free); 5119 + + g_clear_pointer(&self->background_buffer, g_free); 5120 + + g_clear_pointer(&self->previous_norm_buffer, g_free); 5121 + + 5122 + + for (i = 0; i < 5; i++) 5123 + + g_clear_pointer(&self->enroll_images[i], g_free); 5124 + + 5125 + + g_usb_device_release_interface(fpi_device_get_usb_device(device), 5126 + + 0, 0, &error); 5127 + + 5128 + + fpi_device_close_complete(device, error); 5129 + +} 5130 + + 5131 + +/* 5132 + + * Common initialization shared by all action entry points. 5133 + + */ 5134 + +static void 5135 + +dev_action_common_init(FpiDeviceCanvasbioCb2000 *self) 5136 + +{ 5137 + + self->deactivating = FALSE; 5138 + + self->deactivation_in_progress = FALSE; 5139 + + self->initial_activation_done = FALSE; 5140 + + self->force_recovery = FALSE; 5141 + + self->previous_norm_valid = FALSE; 5142 + + self->retry_total = 0; 5143 + + self->accepted_total = 0; 5144 + + memset(self->retry_cause_count, 0, sizeof(self->retry_cause_count)); 5145 + + self->submit_capture_total = 0; 5146 + + self->submit_enroll_total = 0; 5147 + + self->submit_verify_total = 0; 5148 + + self->submit_identify_total = 0; 5149 + + self->verify_template_samples = 0; 5150 + + self->verify_template_subprints_last = 0; 5151 + + self->verify_template_subprints_min = 0; 5152 + + self->verify_template_subprints_max = 0; 5153 + + self->verify_cmd_rx_total = 0; 5154 + + self->verify_cmd_rx_with_data = 0; 5155 + + self->verify_cmd_rx_last_len = 0; 5156 + + memset(self->verify_cmd_rx_last, 0, sizeof(self->verify_cmd_rx_last)); 5157 + + self->finalize_ack1_len = 0; 5158 + + self->finalize_ack2_len = 0; 5159 + + memset(self->finalize_ack1, 0, sizeof(self->finalize_ack1)); 5160 + + memset(self->finalize_ack2, 0, sizeof(self->finalize_ack2)); 5161 + + self->verify_ack_status_last = 0x00; 5162 + + self->verify_ack_decision_last = CB2000_VERIFY_ACK_UNKNOWN; 5163 + + self->verify_ack_ready_total = 0; 5164 + + self->verify_ack_retry_total = 0; 5165 + + self->verify_ack_nomatch_total = 0; 5166 + + self->verify_ack_unknown_total = 0; 5167 + + self->verify_ack_mismatch_total = 0; 5168 + + self->verify_status_code_last = 0x00; 5169 + + self->verify_result_code_last = 0x00; 5170 + + self->verify_route_last = CB2000_VERIFY_ROUTE_UNKNOWN; 5171 + + self->verify_result_class_last = CB2000_VERIFY_RESULT_CLASS_UNKNOWN; 5172 + + memset(self->verify_result_class_count, 0, sizeof(self->verify_result_class_count)); 5173 + + self->verify_device_status_short_circuit = TRUE; 5174 + + self->experimental_device_assisted_verify = 5175 + + cb2000_get_env_bool("CB2000_EXPERIMENTAL_DEVICE_ASSISTED_VERIFY", FALSE); 5176 + + self->verify_device_status_short_circuit = 5177 + + cb2000_get_env_bool("CB2000_VERIFY_DEVICE_STATUS_SHORT_CIRCUIT", TRUE); 5178 + + self->verify_ready_query_enabled = 5179 + + cb2000_get_env_bool("CB2000_VERIFY_READY_QUERY", TRUE); 5180 + + self->verify_ready_query_b_enabled = 5181 + + cb2000_get_env_bool("CB2000_VERIFY_READY_QUERY_B", TRUE); 5182 + + self->verify_ready_query_total = 0; 5183 + + self->verify_ready_query_status_last = 0x00; 5184 + + self->verify_ready_query_len = 0; 5185 + + memset(self->verify_ready_query_data, 0, sizeof(self->verify_ready_query_data)); 5186 + + self->verify_ready_query_b_total = 0; 5187 + + self->verify_ready_query_b_status_last = 0x00; 5188 + + self->verify_ready_query_b_len = 0; 5189 + + memset(self->verify_ready_query_b_data, 0, sizeof(self->verify_ready_query_b_data)); 5190 + + self->verify_ready_query_ready_total = 0; 5191 + + self->verify_ready_query_retry_total = 0; 5192 + + self->verify_ready_query_nomatch_total = 0; 5193 + + self->verify_ready_query_unknown_total = 0; 5194 + + self->verify_ready_matrix_last = CB2000_VERIFY_READY_MATRIX_UNKNOWN; 5195 + + self->verify_ready_matrix_ready_total = 0; 5196 + + self->verify_ready_matrix_retry_total = 0; 5197 + + self->verify_ready_matrix_nomatch_total = 0; 5198 + + self->verify_ready_matrix_unknown_total = 0; 5199 + + self->verify_pre_capture_status_total = 0; 5200 + + self->verify_pre_capture_status_len = 0; 5201 + + memset(self->verify_pre_capture_status, 0, sizeof(self->verify_pre_capture_status)); 5202 + + self->verify_retry_pending = FALSE; 5203 + + self->verify_retry_error = FP_DEVICE_RETRY_GENERAL; 5204 + + self->verify_retry_status_code = 0x00; 5205 + + self->verify_retry_result_code = 0x00; 5206 + + self->verify_retry_message[0] = '\0'; 5207 + + self->capture_retry_pending = FALSE; 5208 + + self->capture_retry_error = FP_DEVICE_RETRY_GENERAL; 5209 + + self->capture_retry_message[0] = '\0'; 5210 + +} 5211 + + 5212 + +static void 5213 + +dev_enroll(FpDevice *device) 5214 + +{ 5215 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(device); 5216 + + gint i; 5217 + + 5218 + + fp_info("Starting enrollment (%d stages)", CB2000_NR_ENROLL_STAGES); 5219 + + dev_action_common_init(self); 5220 + + self->enroll_stage = 0; 5221 + + for (i = 0; i < CB2000_NR_ENROLL_STAGES; i++) 5222 + + g_clear_pointer(&self->enroll_images[i], g_free); 5223 + + 5224 + + start_new_cycle(device); 5225 + +} 5226 + + 5227 + +static void 5228 + +dev_verify(FpDevice *device) 5229 + +{ 5230 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(device); 5231 + + 5232 + + fp_info("Starting verification (NCC)"); 5233 + + dev_action_common_init(self); 5234 + + self->verify_result = FPI_MATCH_FAIL; 5235 + + 5236 + + start_new_cycle(device); 5237 + +} 5238 + + 5239 + +static void 5240 + +dev_capture(FpDevice *device) 5241 + +{ 5242 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(device); 5243 + + 5244 + + fp_info("Starting image capture"); 5245 + + dev_action_common_init(self); 5246 + + 5247 + + start_new_cycle(device); 5248 + +} 5249 + + 5250 + +/* 5251 + + * Deactivation control request completion. 5252 + + * In FpDevice mode, this is just a cleanup callback - no deactivate_complete needed. 5253 + + */ 5254 + +static void 5255 + +deactivate_ctrl_cb(FpiUsbTransfer *transfer, 5256 + + FpDevice *dev, 5257 + + gpointer user_data, 5258 + + GError *error) 5259 + +{ 5260 + + if (error) { 5261 + + fp_warn("Deactivation command error: %s", error->message); 5262 + + g_error_free(error); 5263 + + } 5264 + + 5265 + + fp_dbg("Deactivation USB command complete"); 5266 + +} 5267 + + 5268 + +/* 5269 + + * Issue the vendor deactivation control request for cleanup. 5270 + + * In FpDevice mode, there is no deactivate_complete to call. 5271 + + */ 5272 + +static void 5273 + +complete_deactivation(FpDevice *dev) 5274 + +{ 5275 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 5276 + + FpiUsbTransfer *transfer; 5277 + + 5278 + + if (self->deactivation_in_progress) { 5279 + + fp_dbg("Deactivation already in progress, skipping duplicate"); 5280 + + return; 5281 + + } 5282 + + self->deactivation_in_progress = TRUE; 5283 + + if (self->deactivation_timeout_id > 0) { 5284 + + g_source_remove(self->deactivation_timeout_id); 5285 + + self->deactivation_timeout_id = 0; 5286 + + } 5287 + + 5288 + + fp_dbg("Sending deactivation command (REQ_INIT 0x0001 idx=0)"); 5289 + + 5290 + + transfer = fpi_usb_transfer_new(dev); 5291 + + fpi_usb_transfer_fill_control(transfer, 5292 + + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, 5293 + + G_USB_DEVICE_REQUEST_TYPE_VENDOR, 5294 + + G_USB_DEVICE_RECIPIENT_DEVICE, 5295 + + REQ_INIT, 0x0001, 0, 0); 5296 + + fpi_usb_transfer_submit(transfer, CB2000_TIMEOUT, NULL, 5297 + + deactivate_ctrl_cb, NULL); 5298 + +} 5299 + + 5300 + +/* 5301 + + * Safety timeout: if the cycle is still running after a short delay, force 5302 + + * completion to honor cancellation (e.g. Ctrl+C). 5303 + + */ 5304 + +static gboolean G_GNUC_UNUSED 5305 + +deactivation_force_cb(gpointer user_data) 5306 + +{ 5307 + + FpDevice *dev = FP_DEVICE(user_data); 5308 + + FpiDeviceCanvasbioCb2000 *self = FPI_DEVICE_CANVASBIO_CB2000(dev); 5309 + + 5310 + + self->deactivation_timeout_id = 0; 5311 + + 5312 + + if (!self->deactivating || self->deactivation_in_progress) 5313 + + return G_SOURCE_REMOVE; 5314 + + 5315 + + if (self->cycle_ssm) { 5316 + + /* Still busy: force completion to honor cancel/ctrl+c */ 5317 + + fpi_ssm_mark_completed(self->cycle_ssm); 5318 + + } else { 5319 + + complete_deactivation(dev); 5320 + + } 5321 + + 5322 + + return G_SOURCE_REMOVE; 5323 + +} 5324 + + 5325 + +/* ============================================================================ 5326 + + * DRIVER REGISTRATION 5327 + + * ============================================================================ */ 5328 + + 5329 + +static const FpIdEntry id_table[] = { 5330 + + { .vid = CB2000_VID, .pid = CB2000_PID_1 }, 5331 + + { .vid = CB2000_VID, .pid = CB2000_PID_2 }, 5332 + + { .vid = 0, .pid = 0 }, 5333 + +}; 5334 + + 5335 + +static void 5336 + +fpi_device_canvasbio_cb2000_init(FpiDeviceCanvasbioCb2000 *self) 5337 + +{ 5338 + +} 5339 + + 5340 + +static void 5341 + +fpi_device_canvasbio_cb2000_class_init(FpiDeviceCanvasbioCb2000Class *klass) 5342 + +{ 5343 + + FpDeviceClass *dev_class = FP_DEVICE_CLASS(klass); 5344 + + 5345 + + dev_class->id = "canvasbio-cb2000"; 5346 + + dev_class->full_name = "CanvasBio CB2000"; 5347 + + dev_class->type = FP_DEVICE_TYPE_USB; 5348 + + dev_class->id_table = id_table; 5349 + + dev_class->scan_type = FP_SCAN_TYPE_PRESS; 5350 + + 5351 + + dev_class->open = dev_open; 5352 + + dev_class->close = dev_close; 5353 + + dev_class->enroll = dev_enroll; 5354 + + dev_class->verify = dev_verify; 5355 + + dev_class->capture = dev_capture; 5356 + + dev_class->nr_enroll_stages = CB2000_NR_ENROLL_STAGES; 5357 + + 5358 + + fpi_device_class_auto_initialize_features(dev_class); 5359 + +} 5360 + diff --git a/libfprint/drivers/meson.build b/libfprint/drivers/meson.build 5361 + new file mode 100644 5362 + index 0000000..cfad60d 5363 + --- /dev/null 5364 + +++ b/libfprint/drivers/meson.build 5365 + @@ -0,0 +1,13 @@ 5366 + +# CanvasBio CB2000 driver for libfprint 5367 + +# 5368 + +# To integrate with libfprint: 5369 + +# 1. Copy this folder to libfprint/libfprint/drivers/canvasbio_cb2000/ 5370 + +# 2. Add 'canvasbio_cb2000' to driver_sources dict in libfprint/libfprint/meson.build 5371 + +# 3. Add 'canvasbio_cb2000' to default_drivers list in libfprint/meson.build 5372 + +# 4. Rebuild libfprint 5373 + +# 5374 + +# IMPORTANT: Use underscore (_) not hyphen (-) in driver name! 5375 + + 5376 + +canvasbio_cb2000_sources = files( 5377 + + 'canvasbio_cb2000.c', 5378 + +) 5379 + \ No newline at end of file 5380 + diff --git a/libfprint/meson.build b/libfprint/meson.build 5381 + index ae0f6e2..5dbe9a4 100644 5382 + --- a/libfprint/meson.build 5383 + +++ b/libfprint/meson.build 5384 + @@ -87,6 +87,8 @@ nbis_sources = [ 5385 + ] 5386 + 5387 + driver_sources = { 5388 + + 'canvasbio_cb2000' : 5389 + + [ 'drivers/canvasbio_cb2000.c' ], 5390 + 'upekts' : 5391 + [ 'drivers/upekts.c', 'drivers/upek_proto.c' ], 5392 + 'upektc' : 5393 + diff --git a/meson.build b/meson.build 5394 + index 14fb11f..6fd1820 100644 5395 + --- a/meson.build 5396 + +++ b/meson.build 5397 + @@ -115,6 +115,7 @@ virtual_drivers = [ 5398 + ] 5399 + 5400 + default_drivers = [ 5401 + + 'canvasbio_cb2000', 5402 + 'upektc_img', 5403 + 'vfs5011', 5404 + 'vfs7552', 5405 + diff --git a/tests/meson.build b/tests/meson.build 5406 + index 07c924b..395c5d9 100644 5407 + --- a/tests/meson.build 5408 + +++ b/tests/meson.build 5409 + @@ -321,16 +321,6 @@ foreach test_name: unit_tests 5410 + ) 5411 + endforeach 5412 + 5413 + -# Run udev rule generator with fatal warnings 5414 + -envs.set('UDEV_HWDB', udev_hwdb.full_path()) 5415 + -envs.set('UDEV_HWDB_CHECK_CONTENTS', default_drivers_are_enabled ? '1' : '0') 5416 + -envs.set('G_MESSAGES_DEBUG', '') 5417 + -test('udev-hwdb', 5418 + - find_program('test-generated-hwdb.sh'), 5419 + - depends: udev_hwdb, 5420 + - suite: ['data', 'no-valgrind'], 5421 + - env: envs) 5422 + - 5423 + appstreamcli = find_program('appstreamcli', required: false) 5424 + if appstreamcli.found() 5425 + test('metainfo-validate',
+6
nix/packages/libfprint-canvasbio-cb2000/package.nix
··· 1 + {libfprint, ...}: 2 + libfprint.overrideAttrs (oldAttrs: { 3 + patches = 4 + (oldAttrs.patches or []) 5 + ++ [./001-add-canvasbio.patch]; 6 + })
+20
nix/schemas/agenixRekey.nix
··· 1 + {inputs, ...}: let 2 + inherit (inputs.nixpkgs) lib; 3 + in { 4 + version = 1; 5 + doc = '' 6 + The `agenix-rekey` flake output defines executables used by agenix-rekey 7 + ''; 8 + inventory = output: let 9 + recurse = attrs: { 10 + children = builtins.mapAttrs (attrName: attr: 11 + if lib.isDerivation attr 12 + then { 13 + what = "agenix-rekey executable"; 14 + } 15 + else recurse attr) 16 + attrs; 17 + }; 18 + in 19 + recurse output; 20 + }
+2 -1
nix/schemas/default.nix
··· 1 1 args: { 2 + agenix-rekey = import ./agenixRekey.nix args; 2 3 lib = import ./lib.nix args; 3 - pkgs = import ./pkgs.nix args; 4 + systems = import ./systems.nix args; 4 5 }
+4 -1
nix/schemas/lib.nix
··· 9 9 recurse = attrs: { 10 10 children = builtins.mapAttrs (attrName: attr: 11 11 if builtins.isAttrs attr 12 - then recurse attr 12 + then { 13 + what = "library namespace"; 14 + } 13 15 else if builtins.isFunction attr 14 16 then { 15 17 what = "library function"; ··· 17 19 } 18 20 else { 19 21 what = "configuration constant"; 22 + evalChecks.camelCase = (lib.strings.toCamelCase attrName) == attrName; 20 23 }) 21 24 attrs; 22 25 };
-19
nix/schemas/pkgs.nix
··· 1 - {systems, ...}: { 2 - version = 1; 3 - doc = '' 4 - The `pkgs` flake output defines fixed nixpkgs instances. 5 - ''; 6 - inventory = output: let 7 - recurse = attrs: { 8 - children = builtins.mapAttrs (attrName: attr: 9 - if builtins.isAttrs attr 10 - then { 11 - what = "nixpkgs instance"; 12 - evalChecks.supportedSystem = builtins.elem attrName systems; 13 - } 14 - else throw "unsupported 'pkgs' system") 15 - attrs; 16 - }; 17 - in 18 - recurse output; 19 - }
+17
nix/schemas/systems.nix
··· 1 + {inputs, ...}: let 2 + inherit (inputs.nixpkgs) lib; 3 + in { 4 + version = 1; 5 + doc = '' 6 + The `systems` flake output defines the systems supported by this flake. 7 + ''; 8 + inventory = output: let 9 + recurse = list: { 10 + children = lib.genAttrs list (attrName: { 11 + what = "supported system"; 12 + evalChecks.supportedSystem = lib.systems.flakeExposed ? ${attrName}; 13 + }); 14 + }; 15 + in 16 + recurse output; 17 + }
+1 -1
nix/shells/maintenance.nix
··· 22 22 packages = 23 23 (with pkgs; [ 24 24 age-plugin-yubikey 25 - agenix-rekey 25 + colmena 26 26 just 27 27 rage 28 28 ])
secrets/rekeyed/laptop/436ec0f4a8351cc704f0e9341a61bcf9-user.emily.password.age

This is a binary file and will not be displayed.

secrets/rekeyed/raspberrypi/df6b34c90fcbdf12dccd55c27804ca79-user.emily.password.age

This is a binary file and will not be displayed.