├── .clang-format ├── .clang-tidy ├── .clangd ├── .cz.toml ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .hookspace ├── .gitignore ├── .identifier └── workspace.json ├── .pre-commit-config.yaml ├── .quickjump.json ├── .root ├── LICENSE.md ├── Makefile ├── README.md ├── example_config.yaml ├── hyperenable.sublime-project ├── install.ps1 └── src ├── .gitignore ├── .ignore ├── .tokeignore ├── Makefile ├── argparse.hpp ├── config.cpp ├── config.hpp ├── keys.cpp ├── keys.hpp ├── main.cpp ├── main.hpp ├── pipe.cpp ├── pipe.hpp ├── squatter.cpp ├── squatter.hpp ├── wincon.cpp ├── wincon.hpp ├── yaml.cpp └── yaml.hpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | BreakBeforeBraces: Attach 5 | AlignAfterOpenBracket: AlwaysBreak 6 | AllowAllParametersOfDeclarationOnNextLine: true 7 | AlignArrayOfStructures: Right 8 | AllowShortBlocksOnASingleLine: Empty 9 | AllowShortFunctionsOnASingleLine: Inline 10 | BinPackArguments: true 11 | BinPackParameters: true 12 | BreakBeforeTernaryOperators: true 13 | BreakStringLiterals: true 14 | ColumnLimit: 80 15 | CompactNamespaces: false 16 | Cpp11BracedListStyle: true 17 | IndentWidth: 4 18 | IndentAccessModifiers: false 19 | AccessModifierOffset: -4 20 | DerivePointerAlignment: false 21 | PointerAlignment: Left 22 | --- 23 | Language: JavaScript 24 | BasedOnStyle: Google 25 | ColumnLimit: 80 26 | --- 27 | Language: Json 28 | ColumnLimit: 80 29 | --- 30 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | UseColor: true 2 | # NOTE Make sure all checks have trailing commas! 3 | Checks: > 4 | bugprone-*, 5 | clang-analyzer-*, 6 | cppcoreguidelines-*, 7 | google-*, 8 | hicpp-*, 9 | modernize-*, 10 | performance-*, 11 | portability-*, 12 | readability-*, 13 | -bugprone-easily-swappable-parameters, 14 | -cppcoreguidelines-avoid-c-arrays, 15 | -cppcoreguidelines-macro-usage, 16 | -cppcoreguidelines-pro-type-cstyle-cast, 17 | -google-readability-todo, 18 | -google-runtime-references, 19 | -hicpp-avoid-c-arrays, 20 | -hicpp-exception-baseclass, 21 | -hicpp-signed-bitwise, 22 | -modernize-avoid-c-arrays, 23 | -modernize-use-nullptr, 24 | -performance-no-int-to-ptr, 25 | -readability-avoid-const-params-in-decls, 26 | -readability-convert-member-functions-to-static, 27 | -readability-function-cognitive-complexity, 28 | -readability-identifier-length, 29 | -used-but-marked-unused, 30 | HeaderFilterRegex: "(^[\\/]*|.*[\\/]+)(argparse\.hpp|args\.hxx|yaml\.cpp|yaml\.hpp)([\\/]*$|[\\/]+.*)" 31 | -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: [-std=c++20] 3 | Remove: [-EH*, /EH*] 4 | -------------------------------------------------------------------------------- /.cz.toml: -------------------------------------------------------------------------------- 1 | [tool.commitizen] 2 | name = "cz_conventional_commits" 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_style = space 4 | indent_size = 4 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | guidelines = 80 8 | end_of_line = lf 9 | tab_width = 4 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{css,html,html.in,yml,yaml,rb}] 15 | indent_style = space 16 | indent_size = 2 17 | guidelines = 80 18 | 19 | [{CMakeLists.txt,*.cmake}] 20 | indent_style = space 21 | indent_size = 2 22 | guidelines = 80 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Git Line Endings # 3 | ############################### 4 | 5 | * text=auto eol=lf 6 | *.{cmd,[cC][mM][dD]} text eol=crlf 7 | *.{bat,[bB][aA][tT]} text eol=crlf 8 | *.{vcxproj,vcxproj.filters} text eol=crlf 9 | 10 | ############################### 11 | # Git Large File System (LFS) # 12 | ############################### 13 | 14 | # Archives 15 | #*.7z filter=lfs diff=lfs merge=lfs -text 16 | #*.br filter=lfs diff=lfs merge=lfs -text 17 | #*.gz filter=lfs diff=lfs merge=lfs -text 18 | #*.tar filter=lfs diff=lfs merge=lfs -text 19 | #*.zip filter=lfs diff=lfs merge=lfs -text 20 | 21 | # Documents 22 | #*.pdf filter=lfs diff=lfs merge=lfs -text 23 | 24 | # Images 25 | #*.gif filter=lfs diff=lfs merge=lfs -text 26 | #*.ico filter=lfs diff=lfs merge=lfs -text 27 | #*.jpg filter=lfs diff=lfs merge=lfs -text 28 | #*.pdf filter=lfs diff=lfs merge=lfs -text 29 | #*.png filter=lfs diff=lfs merge=lfs -text 30 | #*.psd filter=lfs diff=lfs merge=lfs -text 31 | #*.webp filter=lfs diff=lfs merge=lfs -text 32 | 33 | # Fonts 34 | #*.woff2 filter=lfs diff=lfs merge=lfs -text 35 | 36 | # Other 37 | #*.exe filter=lfs diff=lfs merge=lfs -text 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.hookspace/Session.vim 2 | /.hookspace/userdata.json 3 | /build 4 | /build-* 5 | /build_* 6 | /build_ 7 | /cmake-build-* 8 | /cmake-build 9 | /_build 10 | /cpm_modules 11 | /compile_flags.txt 12 | /compile_commands.json 13 | /.cache 14 | /venv 15 | /.venv 16 | /out 17 | /CMakeUserPresets.json 18 | /.idea/code-comments.xml 19 | _generated/ 20 | 21 | # Created by https://www.toptal.com/developers/gitignore/api/c,c++,vim,tags,conan,emacs,linux,macos,vcpkg,eclipse,windows,intellij,sublimetext,visualstudio,visualstudiocode,clion 22 | # Edit at https://www.toptal.com/developers/gitignore?templates=c,c++,vim,tags,conan,emacs,linux,macos,vcpkg,eclipse,windows,intellij,sublimetext,visualstudio,visualstudiocode,clion 23 | 24 | ### C ### 25 | # Prerequisites 26 | *.d 27 | 28 | # Object files 29 | *.o 30 | *.ko 31 | *.obj 32 | *.elf 33 | 34 | # Linker output 35 | *.ilk 36 | *.map 37 | *.exp 38 | 39 | # Precompiled Headers 40 | *.gch 41 | *.pch 42 | 43 | # Libraries 44 | *.lib 45 | *.a 46 | *.la 47 | *.lo 48 | 49 | # Shared objects (inc. Windows DLLs) 50 | *.dll 51 | *.so 52 | *.so.* 53 | *.dylib 54 | 55 | # Executables 56 | *.exe 57 | *.out 58 | *.app 59 | *.i*86 60 | *.x86_64 61 | *.hex 62 | 63 | # Debug files 64 | *.dSYM/ 65 | *.su 66 | *.idb 67 | *.pdb 68 | 69 | # Kernel Module Compile Results 70 | *.mod* 71 | *.cmd 72 | .tmp_versions/ 73 | modules.order 74 | Module.symvers 75 | Mkfile.old 76 | dkms.conf 77 | 78 | ### C++ ### 79 | # Prerequisites 80 | 81 | # Compiled Object files 82 | *.slo 83 | 84 | # Precompiled Headers 85 | 86 | # Compiled Dynamic libraries 87 | 88 | # Fortran module files 89 | *.mod 90 | *.smod 91 | 92 | # Compiled Static libraries 93 | *.lai 94 | 95 | # Executables 96 | 97 | ### CLion ### 98 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 99 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 100 | 101 | # User-specific stuff 102 | .idea/**/workspace.xml 103 | .idea/**/tasks.xml 104 | .idea/**/usage.statistics.xml 105 | .idea/**/dictionaries 106 | .idea/**/shelf 107 | 108 | # AWS User-specific 109 | .idea/**/aws.xml 110 | 111 | # Generated files 112 | .idea/**/contentModel.xml 113 | 114 | # Sensitive or high-churn files 115 | .idea/**/dataSources/ 116 | .idea/**/dataSources.ids 117 | .idea/**/dataSources.local.xml 118 | .idea/**/sqlDataSources.xml 119 | .idea/**/dynamic.xml 120 | .idea/**/uiDesigner.xml 121 | .idea/**/dbnavigator.xml 122 | 123 | # Gradle 124 | .idea/**/gradle.xml 125 | .idea/**/libraries 126 | 127 | # Gradle and Maven with auto-import 128 | # When using Gradle or Maven with auto-import, you should exclude module files, 129 | # since they will be recreated, and may cause churn. Uncomment if using 130 | # auto-import. 131 | # .idea/artifacts 132 | # .idea/compiler.xml 133 | # .idea/jarRepositories.xml 134 | # .idea/modules.xml 135 | # .idea/*.iml 136 | # .idea/modules 137 | # *.iml 138 | # *.ipr 139 | 140 | # CMake 141 | cmake-build-*/ 142 | 143 | # Mongo Explorer plugin 144 | .idea/**/mongoSettings.xml 145 | 146 | # File-based project format 147 | *.iws 148 | 149 | # IntelliJ 150 | out/ 151 | 152 | # mpeltonen/sbt-idea plugin 153 | .idea_modules/ 154 | 155 | # JIRA plugin 156 | atlassian-ide-plugin.xml 157 | 158 | # Cursive Clojure plugin 159 | .idea/replstate.xml 160 | 161 | # SonarLint plugin 162 | .idea/sonarlint/ 163 | 164 | # Crashlytics plugin (for Android Studio and IntelliJ) 165 | com_crashlytics_export_strings.xml 166 | crashlytics.properties 167 | crashlytics-build.properties 168 | fabric.properties 169 | 170 | # Editor-based Rest Client 171 | .idea/httpRequests 172 | 173 | # Android studio 3.1+ serialized cache file 174 | .idea/caches/build_file_checksums.ser 175 | 176 | ### CLion Patch ### 177 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 178 | 179 | # *.iml 180 | # modules.xml 181 | # .idea/misc.xml 182 | # *.ipr 183 | 184 | # Sonarlint plugin 185 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 186 | .idea/**/sonarlint/ 187 | 188 | # SonarQube Plugin 189 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 190 | .idea/**/sonarIssues.xml 191 | 192 | # Markdown Navigator plugin 193 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 194 | .idea/**/markdown-navigator.xml 195 | .idea/**/markdown-navigator-enh.xml 196 | .idea/**/markdown-navigator/ 197 | 198 | # Cache file creation bug 199 | # See https://youtrack.jetbrains.com/issue/JBR-2257 200 | .idea/$CACHE_FILE$ 201 | 202 | # CodeStream plugin 203 | # https://plugins.jetbrains.com/plugin/12206-codestream 204 | .idea/codestream.xml 205 | 206 | # Azure Toolkit for IntelliJ plugin 207 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij 208 | .idea/**/azureSettings.xml 209 | 210 | ### Conan ### 211 | # Conan build information 212 | conan.lock 213 | conanbuildinfo.* 214 | conaninfo.txt 215 | graph_info.json 216 | 217 | ## Various built-in generators 218 | # cmake_find_package generator https://docs.conan.io/en/latest/reference/generators/cmake_find_package.html 219 | Find*.cmake 220 | 221 | # cmake_paths generator https://docs.conan.io/en/1.4/integrations/cmake/cmake_paths_generator.html 222 | conan_paths.* 223 | 224 | # Environment activation scripts produced by https://docs.conan.io/en/latest/mastering/virtualenv.html#virtualenv-generator 225 | activate_run.ps1 226 | activate_run.sh 227 | deactivate_run.ps1 228 | deactivate_run.sh 229 | environment_run.ps1.env 230 | environment_run.sh.env 231 | 232 | ### Eclipse ### 233 | .metadata 234 | bin/ 235 | tmp/ 236 | *.tmp 237 | *.bak 238 | *.swp 239 | *~.nib 240 | local.properties 241 | .settings/ 242 | .loadpath 243 | .recommenders 244 | 245 | # External tool builders 246 | .externalToolBuilders/ 247 | 248 | # Locally stored "Eclipse launch configurations" 249 | *.launch 250 | 251 | # PyDev specific (Python IDE for Eclipse) 252 | *.pydevproject 253 | 254 | # CDT-specific (C/C++ Development Tooling) 255 | .cproject 256 | 257 | # CDT- autotools 258 | .autotools 259 | 260 | # Java annotation processor (APT) 261 | .factorypath 262 | 263 | # PDT-specific (PHP Development Tools) 264 | .buildpath 265 | 266 | # sbteclipse plugin 267 | .target 268 | 269 | # Tern plugin 270 | .tern-project 271 | 272 | # TeXlipse plugin 273 | .texlipse 274 | 275 | # STS (Spring Tool Suite) 276 | .springBeans 277 | 278 | # Code Recommenders 279 | .recommenders/ 280 | 281 | # Annotation Processing 282 | .apt_generated/ 283 | .apt_generated_test/ 284 | 285 | # Scala IDE specific (Scala & Java development for Eclipse) 286 | .cache-main 287 | .scala_dependencies 288 | .worksheet 289 | 290 | # Uncomment this line if you wish to ignore the project description file. 291 | # Typically, this file would be tracked if it contains build/dependency configurations: 292 | #.project 293 | 294 | ### Eclipse Patch ### 295 | # Spring Boot Tooling 296 | .sts4-cache/ 297 | 298 | ### Emacs ### 299 | # -*- mode: gitignore; -*- 300 | *~ 301 | \#*\# 302 | /.emacs.desktop 303 | /.emacs.desktop.lock 304 | *.elc 305 | auto-save-list 306 | tramp 307 | .\#* 308 | 309 | # Org-mode 310 | .org-id-locations 311 | *_archive 312 | 313 | # flymake-mode 314 | *_flymake.* 315 | 316 | # eshell files 317 | /eshell/history 318 | /eshell/lastdir 319 | 320 | # elpa packages 321 | /elpa/ 322 | 323 | # reftex files 324 | *.rel 325 | 326 | # AUCTeX auto folder 327 | /auto/ 328 | 329 | # cask packages 330 | .cask/ 331 | dist/ 332 | 333 | # Flycheck 334 | flycheck_*.el 335 | 336 | # server auth directory 337 | /server/ 338 | 339 | # projectiles files 340 | .projectile 341 | 342 | # directory configuration 343 | .dir-locals.el 344 | 345 | # network security 346 | /network-security.data 347 | 348 | 349 | ### Intellij ### 350 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 351 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 352 | 353 | # User-specific stuff 354 | 355 | # AWS User-specific 356 | 357 | # Generated files 358 | 359 | # Sensitive or high-churn files 360 | 361 | # Gradle 362 | 363 | # Gradle and Maven with auto-import 364 | # When using Gradle or Maven with auto-import, you should exclude module files, 365 | # since they will be recreated, and may cause churn. Uncomment if using 366 | # auto-import. 367 | # .idea/artifacts 368 | # .idea/compiler.xml 369 | # .idea/jarRepositories.xml 370 | # .idea/modules.xml 371 | # .idea/*.iml 372 | # .idea/modules 373 | # *.iml 374 | # *.ipr 375 | 376 | # CMake 377 | 378 | # Mongo Explorer plugin 379 | 380 | # File-based project format 381 | 382 | # IntelliJ 383 | 384 | # mpeltonen/sbt-idea plugin 385 | 386 | # JIRA plugin 387 | 388 | # Cursive Clojure plugin 389 | 390 | # SonarLint plugin 391 | 392 | # Crashlytics plugin (for Android Studio and IntelliJ) 393 | 394 | # Editor-based Rest Client 395 | 396 | # Android studio 3.1+ serialized cache file 397 | 398 | ### Intellij Patch ### 399 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 400 | 401 | # *.iml 402 | # modules.xml 403 | # .idea/misc.xml 404 | # *.ipr 405 | 406 | # Sonarlint plugin 407 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 408 | 409 | # SonarQube Plugin 410 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 411 | 412 | # Markdown Navigator plugin 413 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 414 | 415 | # Cache file creation bug 416 | # See https://youtrack.jetbrains.com/issue/JBR-2257 417 | 418 | # CodeStream plugin 419 | # https://plugins.jetbrains.com/plugin/12206-codestream 420 | 421 | # Azure Toolkit for IntelliJ plugin 422 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij 423 | 424 | ### Linux ### 425 | 426 | # temporary files which can be created if a process still has a handle open of a deleted file 427 | .fuse_hidden* 428 | 429 | # KDE directory preferences 430 | .directory 431 | 432 | # Linux trash folder which might appear on any partition or disk 433 | .Trash-* 434 | 435 | # .nfs files are created when an open file is removed but is still being accessed 436 | .nfs* 437 | 438 | ### macOS ### 439 | # General 440 | .DS_Store 441 | .AppleDouble 442 | .LSOverride 443 | 444 | # Icon must end with two \r 445 | Icon 446 | 447 | 448 | # Thumbnails 449 | ._* 450 | 451 | # Files that might appear in the root of a volume 452 | .DocumentRevisions-V100 453 | .fseventsd 454 | .Spotlight-V100 455 | .TemporaryItems 456 | .Trashes 457 | .VolumeIcon.icns 458 | .com.apple.timemachine.donotpresent 459 | 460 | # Directories potentially created on remote AFP share 461 | .AppleDB 462 | .AppleDesktop 463 | Network Trash Folder 464 | Temporary Items 465 | .apdisk 466 | 467 | ### macOS Patch ### 468 | # iCloud generated files 469 | *.icloud 470 | 471 | ### SublimeText ### 472 | # Cache files for Sublime Text 473 | *.tmlanguage.cache 474 | *.tmPreferences.cache 475 | *.stTheme.cache 476 | 477 | # Workspace files are user-specific 478 | *.sublime-workspace 479 | 480 | # Project files should be checked into the repository, unless a significant 481 | # proportion of contributors will probably not be using Sublime Text 482 | # *.sublime-project 483 | 484 | # SFTP configuration file 485 | sftp-config.json 486 | sftp-config-alt*.json 487 | 488 | # Package control specific files 489 | Package Control.last-run 490 | Package Control.ca-list 491 | Package Control.ca-bundle 492 | Package Control.system-ca-bundle 493 | Package Control.cache/ 494 | Package Control.ca-certs/ 495 | Package Control.merged-ca-bundle 496 | Package Control.user-ca-bundle 497 | oscrypto-ca-bundle.crt 498 | bh_unicode_properties.cache 499 | 500 | # Sublime-github package stores a github token in this file 501 | # https://packagecontrol.io/packages/sublime-github 502 | GitHub.sublime-settings 503 | 504 | ### Tags ### 505 | # Ignore tags created by etags, ctags, gtags (GNU global) and cscope 506 | TAGS 507 | .TAGS 508 | !TAGS/ 509 | tags 510 | .tags 511 | !tags/ 512 | gtags.files 513 | GTAGS 514 | GRTAGS 515 | GPATH 516 | GSYMS 517 | cscope.files 518 | cscope.out 519 | cscope.in.out 520 | cscope.po.out 521 | 522 | 523 | ### vcpkg ### 524 | # Vcpkg 525 | 526 | vcpkg-manifest-install.log 527 | 528 | ## Manifest Mode 529 | vcpkg_installed/ 530 | 531 | ### Vim ### 532 | # Swap 533 | [._]*.s[a-v][a-z] 534 | !*.svg # comment out if you don't need vector files 535 | [._]*.sw[a-p] 536 | [._]s[a-rt-v][a-z] 537 | [._]ss[a-gi-z] 538 | [._]sw[a-p] 539 | 540 | # Session 541 | Session.vim 542 | Sessionx.vim 543 | 544 | # Temporary 545 | .netrwhist 546 | # Auto-generated tag files 547 | # Persistent undo 548 | [._]*.un~ 549 | 550 | ### VisualStudioCode ### 551 | .vscode/* 552 | !.vscode/settings.json 553 | !.vscode/tasks.json 554 | !.vscode/launch.json 555 | !.vscode/extensions.json 556 | !.vscode/*.code-snippets 557 | 558 | # Local History for Visual Studio Code 559 | .history/ 560 | 561 | # Built Visual Studio Code Extensions 562 | *.vsix 563 | 564 | ### VisualStudioCode Patch ### 565 | # Ignore all local history of files 566 | .history 567 | .ionide 568 | 569 | ### Windows ### 570 | # Windows thumbnail cache files 571 | Thumbs.db 572 | Thumbs.db:encryptable 573 | ehthumbs.db 574 | ehthumbs_vista.db 575 | 576 | # Dump file 577 | *.stackdump 578 | 579 | # Folder config file 580 | [Dd]esktop.ini 581 | 582 | # Recycle Bin used on file shares 583 | $RECYCLE.BIN/ 584 | 585 | # Windows Installer files 586 | *.cab 587 | *.msi 588 | *.msix 589 | *.msm 590 | *.msp 591 | 592 | # Windows shortcuts 593 | *.lnk 594 | 595 | ### VisualStudio ### 596 | ## Ignore Visual Studio temporary files, build results, and 597 | ## files generated by popular Visual Studio add-ons. 598 | ## 599 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 600 | 601 | # User-specific files 602 | *.rsuser 603 | *.suo 604 | *.user 605 | *.userosscache 606 | *.sln.docstates 607 | 608 | # User-specific files (MonoDevelop/Xamarin Studio) 609 | *.userprefs 610 | 611 | # Mono auto generated files 612 | mono_crash.* 613 | 614 | # Build results 615 | [Dd]ebug/ 616 | [Dd]ebugPublic/ 617 | [Rr]elease/ 618 | [Rr]eleases/ 619 | x64/ 620 | x86/ 621 | [Ww][Ii][Nn]32/ 622 | [Aa][Rr][Mm]/ 623 | [Aa][Rr][Mm]64/ 624 | bld/ 625 | [Bb]in/ 626 | [Oo]bj/ 627 | [Ll]og/ 628 | [Ll]ogs/ 629 | 630 | # Visual Studio 2015/2017 cache/options directory 631 | .vs/ 632 | # Uncomment if you have tasks that create the project's static files in wwwroot 633 | #wwwroot/ 634 | 635 | # Visual Studio 2017 auto generated files 636 | Generated\ Files/ 637 | 638 | # MSTest test Results 639 | [Tt]est[Rr]esult*/ 640 | [Bb]uild[Ll]og.* 641 | 642 | # NUnit 643 | *.VisualState.xml 644 | TestResult.xml 645 | nunit-*.xml 646 | 647 | # Build Results of an ATL Project 648 | [Dd]ebugPS/ 649 | [Rr]eleasePS/ 650 | dlldata.c 651 | 652 | # Benchmark Results 653 | BenchmarkDotNet.Artifacts/ 654 | 655 | # .NET Core 656 | project.lock.json 657 | project.fragment.lock.json 658 | artifacts/ 659 | 660 | # ASP.NET Scaffolding 661 | ScaffoldingReadMe.txt 662 | 663 | # StyleCop 664 | StyleCopReport.xml 665 | 666 | # Files built by Visual Studio 667 | *_i.c 668 | *_p.c 669 | *_h.h 670 | *.meta 671 | *.iobj 672 | *.ipdb 673 | *.pgc 674 | *.pgd 675 | *.rsp 676 | *.sbr 677 | *.tlb 678 | *.tli 679 | *.tlh 680 | *.tmp_proj 681 | *_wpftmp.csproj 682 | *.log 683 | *.tlog 684 | *.vspscc 685 | *.vssscc 686 | .builds 687 | *.pidb 688 | *.svclog 689 | *.scc 690 | 691 | # Chutzpah Test files 692 | _Chutzpah* 693 | 694 | # Visual C++ cache files 695 | ipch/ 696 | *.aps 697 | *.ncb 698 | *.opendb 699 | *.opensdf 700 | *.sdf 701 | *.cachefile 702 | *.VC.db 703 | *.VC.VC.opendb 704 | 705 | # Visual Studio profiler 706 | *.psess 707 | *.vsp 708 | *.vspx 709 | *.sap 710 | 711 | # Visual Studio Trace Files 712 | *.e2e 713 | 714 | # TFS 2012 Local Workspace 715 | $tf/ 716 | 717 | # Guidance Automation Toolkit 718 | *.gpState 719 | 720 | # ReSharper is a .NET coding add-in 721 | _ReSharper*/ 722 | *.[Rr]e[Ss]harper 723 | *.DotSettings.user 724 | 725 | # TeamCity is a build add-in 726 | _TeamCity* 727 | 728 | # DotCover is a Code Coverage Tool 729 | *.dotCover 730 | 731 | # AxoCover is a Code Coverage Tool 732 | .axoCover/* 733 | !.axoCover/settings.json 734 | 735 | # Coverlet is a free, cross platform Code Coverage Tool 736 | coverage*.json 737 | coverage*.xml 738 | coverage*.info 739 | 740 | # Visual Studio code coverage results 741 | *.coverage 742 | *.coveragexml 743 | 744 | # NCrunch 745 | _NCrunch_* 746 | .*crunch*.local.xml 747 | nCrunchTemp_* 748 | 749 | # MightyMoose 750 | *.mm.* 751 | AutoTest.Net/ 752 | 753 | # Web workbench (sass) 754 | .sass-cache/ 755 | 756 | # Installshield output folder 757 | [Ee]xpress/ 758 | 759 | # DocProject is a documentation generator add-in 760 | DocProject/buildhelp/ 761 | DocProject/Help/*.HxT 762 | DocProject/Help/*.HxC 763 | DocProject/Help/*.hhc 764 | DocProject/Help/*.hhk 765 | DocProject/Help/*.hhp 766 | DocProject/Help/Html2 767 | DocProject/Help/html 768 | 769 | # Click-Once directory 770 | publish/ 771 | 772 | # Publish Web Output 773 | *.[Pp]ublish.xml 774 | *.azurePubxml 775 | # Note: Comment the next line if you want to checkin your web deploy settings, 776 | # but database connection strings (with potential passwords) will be unencrypted 777 | *.pubxml 778 | *.publishproj 779 | 780 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 781 | # checkin your Azure Web App publish settings, but sensitive information contained 782 | # in these scripts will be unencrypted 783 | PublishScripts/ 784 | 785 | # NuGet Packages 786 | *.nupkg 787 | # NuGet Symbol Packages 788 | *.snupkg 789 | # The packages folder can be ignored because of Package Restore 790 | **/[Pp]ackages/* 791 | # except build/, which is used as an MSBuild target. 792 | !**/[Pp]ackages/build/ 793 | # Uncomment if necessary however generally it will be regenerated when needed 794 | #!**/[Pp]ackages/repositories.config 795 | # NuGet v3's project.json files produces more ignorable files 796 | *.nuget.props 797 | *.nuget.targets 798 | 799 | # Microsoft Azure Build Output 800 | csx/ 801 | *.build.csdef 802 | 803 | # Microsoft Azure Emulator 804 | ecf/ 805 | rcf/ 806 | 807 | # Windows Store app package directories and files 808 | AppPackages/ 809 | BundleArtifacts/ 810 | Package.StoreAssociation.xml 811 | _pkginfo.txt 812 | *.appx 813 | *.appxbundle 814 | *.appxupload 815 | 816 | # Visual Studio cache files 817 | # files ending in .cache can be ignored 818 | *.[Cc]ache 819 | # but keep track of directories ending in .cache 820 | !?*.[Cc]ache/ 821 | 822 | # Others 823 | ClientBin/ 824 | ~$* 825 | *.dbmdl 826 | *.dbproj.schemaview 827 | *.jfm 828 | *.pfx 829 | *.publishsettings 830 | orleans.codegen.cs 831 | 832 | # Including strong name files can present a security risk 833 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 834 | #*.snk 835 | 836 | # Since there are multiple workflows, uncomment next line to ignore bower_components 837 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 838 | #bower_components/ 839 | 840 | # RIA/Silverlight projects 841 | Generated_Code/ 842 | 843 | # Backup & report files from converting an old project file 844 | # to a newer Visual Studio version. Backup files are not needed, 845 | # because we have git ;-) 846 | _UpgradeReport_Files/ 847 | Backup*/ 848 | UpgradeLog*.XML 849 | UpgradeLog*.htm 850 | ServiceFabricBackup/ 851 | *.rptproj.bak 852 | 853 | # SQL Server files 854 | *.mdf 855 | *.ldf 856 | *.ndf 857 | 858 | # Business Intelligence projects 859 | *.rdl.data 860 | *.bim.layout 861 | *.bim_*.settings 862 | *.rptproj.rsuser 863 | *- [Bb]ackup.rdl 864 | *- [Bb]ackup ([0-9]).rdl 865 | *- [Bb]ackup ([0-9][0-9]).rdl 866 | 867 | # Microsoft Fakes 868 | FakesAssemblies/ 869 | 870 | # GhostDoc plugin setting file 871 | *.GhostDoc.xml 872 | 873 | # Node.js Tools for Visual Studio 874 | .ntvs_analysis.dat 875 | node_modules/ 876 | 877 | # Visual Studio 6 build log 878 | *.plg 879 | 880 | # Visual Studio 6 workspace options file 881 | *.opt 882 | 883 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 884 | *.vbw 885 | 886 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 887 | *.vbp 888 | 889 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 890 | *.dsw 891 | *.dsp 892 | 893 | # Visual Studio 6 technical files 894 | 895 | # Visual Studio LightSwitch build output 896 | **/*.HTMLClient/GeneratedArtifacts 897 | **/*.DesktopClient/GeneratedArtifacts 898 | **/*.DesktopClient/ModelManifest.xml 899 | **/*.Server/GeneratedArtifacts 900 | **/*.Server/ModelManifest.xml 901 | _Pvt_Extensions 902 | 903 | # Paket dependency manager 904 | .paket/paket.exe 905 | paket-files/ 906 | 907 | # FAKE - F# Make 908 | .fake/ 909 | 910 | # CodeRush personal settings 911 | .cr/personal 912 | 913 | # Python Tools for Visual Studio (PTVS) 914 | __pycache__/ 915 | *.pyc 916 | 917 | # Cake - Uncomment if you are using it 918 | # tools/** 919 | # !tools/packages.config 920 | 921 | # Tabs Studio 922 | *.tss 923 | 924 | # Telerik's JustMock configuration file 925 | *.jmconfig 926 | 927 | # BizTalk build output 928 | *.btp.cs 929 | *.btm.cs 930 | *.odx.cs 931 | *.xsd.cs 932 | 933 | # OpenCover UI analysis results 934 | OpenCover/ 935 | 936 | # Azure Stream Analytics local run output 937 | ASALocalRun/ 938 | 939 | # MSBuild Binary and Structured Log 940 | *.binlog 941 | 942 | # NVidia Nsight GPU debugger configuration file 943 | *.nvuser 944 | 945 | # MFractors (Xamarin productivity tool) working folder 946 | .mfractor/ 947 | 948 | # Local History for Visual Studio 949 | .localhistory/ 950 | 951 | # Visual Studio History (VSHistory) files 952 | .vshistory/ 953 | 954 | # BeatPulse healthcheck temp database 955 | healthchecksdb 956 | 957 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 958 | MigrationBackup/ 959 | 960 | # Ionide (cross platform F# VS Code tools) working folder 961 | .ionide/ 962 | 963 | # Fody - auto-generated XML schema 964 | FodyWeavers.xsd 965 | 966 | # VS Code files for those working on multiple tools 967 | *.code-workspace 968 | 969 | # Local History for Visual Studio Code 970 | 971 | # Windows Installer files from build outputs 972 | 973 | # JetBrains Rider 974 | *.sln.iml 975 | 976 | ### VisualStudio Patch ### 977 | # Additional files built by Visual Studio 978 | 979 | # End of https://www.toptal.com/developers/gitignore/api/c,c++,vim,tags,conan,emacs,linux,macos,vcpkg,eclipse,windows,intellij,sublimetext,visualstudio,visualstudiocode,clion 980 | -------------------------------------------------------------------------------- /.hookspace/.gitignore: -------------------------------------------------------------------------------- 1 | /Session.vim 2 | /session.vim 3 | /trailblazer 4 | -------------------------------------------------------------------------------- /.hookspace/.identifier: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midrare/hyperenable/e3b4cff83c99847882d74e5096b92d6e356d1373/.hookspace/.identifier -------------------------------------------------------------------------------- /.hookspace/workspace.json: -------------------------------------------------------------------------------- 1 | {"name": "hyperenable", "created": 1682297244} 2 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | fail_fast: false 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.3.0 5 | hooks: 6 | - id: check-added-large-files 7 | - id: check-case-conflict 8 | - id: check-merge-conflict 9 | - id: mixed-line-ending 10 | - id: detect-private-key 11 | - repo: https://github.com/commitizen-tools/commitizen 12 | rev: v2.39.1 13 | hooks: 14 | - id: commitizen 15 | - id: commitizen-branch 16 | stages: [ push ] 17 | - repo: https://github.com/pocc/pre-commit-hooks 18 | rev: v1.3.5 19 | hooks: 20 | - id: clang-format 21 | -------------------------------------------------------------------------------- /.quickjump.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "source": "{filename}", 4 | "pattern": "(.+?)\\.c", 5 | "targets": { "header": [ "{$1}.h" ]} 6 | }, 7 | { 8 | "source": "{filename}", 9 | "pattern": "(.+?)\\.(cpp|cxx)", 10 | "targets": { "header": [ "{$1}.hpp", "{$1}.hxx" ]} 11 | }, 12 | { 13 | "source": "{filename}", 14 | "pattern": "(.+?)\\.h", 15 | "targets": { "source": [ "{$1}.c", "{$1}.cpp", "{$1}.cxx" ]} 16 | }, 17 | { 18 | "source": "{filename}", 19 | "pattern": "(.+?)\\.(hpp|hxx)", 20 | "targets": { "source": [ "{$1}.cpp", "{$1}.cxx" ]} 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /.root: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midrare/hyperenable/e3b4cff83c99847882d74e5096b92d6e356d1373/.root -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2023 midrare 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the “Software”), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL = cmd.exe 2 | RM = del -F -Q 3 | RMDIR = rmdir -S -Q 4 | MKDIR = mkdir 5 | COPY = xcopy /E /C /I /-I /Q /Y 6 | 7 | all: src 8 | .PHONY: all src clean 9 | 10 | src: 11 | $(MAKE) -C src 12 | 13 | clean: 14 | $(MAKE) -C src clean 15 | @echo > .dummy.tmp 16 | @$(RM) .dummy.tmp hyper*.exe hyper*.zip 17 | 18 | # tar is bundled with Windows 10+ 19 | release: src 20 | @$(COPY) src\\hyperenable.exe .\\ 21 | tar -a -c -f hyperenable-release.zip example_config.yaml hyperenable.exe install.ps1 LICENSE.md README.md 22 | @$(RM) hyperenable.exe 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hyperenable 2 | **hyperenable allows `ctrl+shift+alt+win` (i.e. the Office key, or the hyper key), which is normally reserved by Windows, to be freely rebound.** hyperenable does this by preemptively registering the hotkeys before `explorer.exe` can, and deregistering them afterwards. Run `hyperenable.exe --help` for cli options. 3 | 4 | ## Getting Started 5 | Start a Powershell instance with admin privileges. 6 | 7 | **To install** 8 | 9 | ```powershell 10 | # to install 11 | PS> Set-ExecutionPolicy Bypass -Scope Process -Force 12 | PS> ./install.ps1 -action install -exe "./hyperenable.exe" 13 | ``` 14 | 15 | **To uninstall** 16 | 17 | ```powershell 18 | # to uninstall 19 | PS> Set-ExecutionPolicy Bypass -Scope Process -Force 20 | PS> ./install.ps1 -action uninstall 21 | ``` 22 | 23 | **Options** 24 | hyperenable comes with CLI options that control some aspects of its behavior. These include: 25 | 26 | - `-c, --config PATH` 27 | Read given config file that controls which keybinds are affected. This is so you can choose which of Windows' default keybinds are allowed to go through and which ones hyperenables unbinds for you. See the included `example_config.yaml`. 28 | - `-r, --run PATH` 29 | Run a program after hyperenable has made sure it's safe to rebind the affected keyboard shortcuts. **I highly recommend putting this exe in the same folder as hyperenable.exe (i.e. `$ProgramFiles/hyperenable/hotkeys.ahk`), so it can be protected by admin rights.** Using this option prevents timing problems in which a later hotkey setup app fails to bind shortcuts because hyperenable hasn't finished running yet. 30 | 31 | To use any of these CLI options, you need to edit the registry key at `HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Userinit`. After installing hyperenable, you should see a `hyperenable.exe` entry appear in this value. Just add whatever CLI options you want, being careful to get the formatting right. Pay close attention to the delimiters: *the delimters are commas, not semicolons*. Also, *there's a final trailing comma at the end*. **Be sure to get this right, or Windows won't start properly.** 32 | 33 | ## Building 34 | 1. Install Visual Studio 2022. Make sure to include MSVC C++ x64/x86 build tools 35 | 2. Install [GNU Make](https://scoop.sh/) 36 | 3. Start Visual Studio 2022 developer prompt 37 | 4. `cd ~/project_dir` 38 | 5. `make` 39 | 40 | ## Acknowledgements 41 | Concept based on [OfficeKeyFix](https://github.com/anthonyheddings/OfficeKeyFix). 42 | 43 | -------------------------------------------------------------------------------- /example_config.yaml: -------------------------------------------------------------------------------- 1 | # see keys.hpp for possible values 2 | keyboard_shortcuts: 3 | - "ctrl+alt+shift+win+w" 4 | - "ctrl+alt+shift+win+t" 5 | - "ctrl+alt+shift+win+y" 6 | - "ctrl+alt+shift+win+o" 7 | - "ctrl+alt+shift+win+p" 8 | - "ctrl+alt+shift+win+d" 9 | - "ctrl+alt+shift+win+l" 10 | - "ctrl+alt+shift+win+x" 11 | - "ctrl+alt+shift+win+n" 12 | -------------------------------------------------------------------------------- /hyperenable.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "folder_exclude_patterns": [ 5 | ".idea", 6 | ".hookspace", 7 | "node_modules", 8 | "venv", 9 | ".venv", 10 | "__pycache__", 11 | ], 12 | "path": ".", 13 | "file_exclude_patterns": [ 14 | "*.bak", 15 | "tags", 16 | "*.tags", 17 | "~.*", 18 | "*.tmp", 19 | "*.temp", 20 | ".root", 21 | "*~", 22 | ] 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 7.2 2 | #Requires -RunAsAdministrator 3 | 4 | <# 5 | .SYNOPSIS 6 | Installs hyperenable 7 | .DESCRIPTION 8 | Copies hyperenable.exe to %ProgramFiles%, and edits relevant registry entries. 9 | .PARAMETER Action 10 | One of "install" or "uninstall" 11 | .PARAMETER Exe 12 | Path to hyperenable.exe file which will be installed 13 | .PARAMETER Destination 14 | Path to where hyperenable.exe will be installed to. Optional 15 | .EXAMPLE 16 | PS> ./install.ps1 -action install -exe "hyperenable.exe" 17 | PS> ./install.ps1 -action uninstall 18 | .LINK 19 | https://github.com/midrare/hyperenable 20 | .NOTES 21 | Author: midrare | License: MIT 22 | #> 23 | 24 | 25 | 26 | param( 27 | [parameter(Mandatory, HelpMessage="Either ""install"" or ""uninstall"".")] 28 | [validateset("install", "uninstall")] 29 | [string]$Action = $null, 30 | [parameter(HelpMessage='Path to hyperenable.exe file to install')] 31 | [string]$Exe = $null, 32 | [parameter(HelpMessage='Path to where hyperenable.exe will be installed')] 33 | [string]$Destination = "$Env:ProgramFiles\hyperenable\hyperenable.exe" 34 | ) 35 | 36 | 37 | 38 | function Get-ArrayItemIndex([string[]]$Array, [string]$Pattern) { 39 | For ($i = 0; $i -lt $Array.Length; $i++) { 40 | If ($Array[$i] -match "$Pattern") { 41 | Return $i 42 | } 43 | } 44 | 45 | Return -1 46 | } 47 | 48 | 49 | function Remove-ArrayItem([ref][string[]]$Array, [string]$Pattern) { 50 | $i = 0 51 | While ($i -lt $Array.Value.Length) { 52 | If ($Array.Value[$i] -match "$Pattern") { 53 | If ($i -le 0) { 54 | $Array.Value = @($Array.Value[($i + 1)..$Array.Value.Length]) 55 | } Else { 56 | $Array.Value = @($Array.Value[0..($i - 1)]) + @($Array.Value[($i + 1)..$Array.Value.Length]) 57 | } 58 | } Else { 59 | $i += 1 60 | } 61 | } 62 | } 63 | 64 | 65 | function Add-ArrayItem([ref][string[]]$Array, [int]$Idx, [string]$Value) { 66 | If ($idx -le 0) { 67 | $Array.Value = @($Value) + @($Array.Value[$Idx..$Array.Value.Length]) 68 | } Else { 69 | $Array.Value = @($Array.Value[0..($Idx - 1)]) + @($Value) + @($Array.Value[$Idx..$Array.Value.Length]) 70 | } 71 | } 72 | 73 | 74 | # Get-ScriptPath() is from https://stackoverflow.com/a/58768926 75 | # authored by Randy https://stackoverflow.com/users/1561650/randy 76 | # used here under CC BY-SA 4.0 77 | function Get-ScriptPath() { 78 | # If using PowerShell ISE 79 | if ($psISE) 80 | { 81 | $ScriptPath = Split-Path -Parent -Path $psISE.CurrentFile.FullPath 82 | } 83 | # If using PowerShell 3.0 or greater 84 | elseif($PSVersionTable.PSVersion.Major -gt 3) 85 | { 86 | $ScriptPath = $PSScriptRoot 87 | } 88 | # If using PowerShell 2.0 or lower 89 | else 90 | { 91 | $ScriptPath = split-path -parent $MyInvocation.MyCommand.Path 92 | } 93 | 94 | # If still not found 95 | # I found this can happen if running an exe created using PS2EXE module 96 | if(-not $ScriptPath) { 97 | $ScriptPath = [System.AppDomain]::CurrentDomain.BaseDirectory.TrimEnd('\') 98 | } 99 | 100 | # Return result 101 | return $ScriptPath 102 | } 103 | 104 | 105 | function Write-Userinit([string]$Exe = $null) { 106 | $Userinit = (Get-ItemProperty -Path $USERINIT_REG_KEY -Name $USERINIT_REG_VALUE).$USERINIT_REG_VALUE 107 | $UserinitArr = $Userinit -Split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ } 108 | $UserinitArr = @($UserinitArr) + "hyperenable.exe" 109 | Remove-ArrayItem ([ref]$UserinitArr) "hyperenable\.exe" 110 | 111 | If ($Exe) { 112 | $UserinitIdx = Get-ArrayItemIndex $UserinitArr "userinit\.exe" 113 | If ($UserinitIdx -lt 0) { 114 | Add-ArrayItem ([ref]$UserinitArr) 0 """$Exe"" start" 115 | } Else { 116 | Add-ArrayItem ([ref]$UserinitArr) $UserinitIdx """$Exe"" start" 117 | } 118 | } 119 | 120 | $Userinit = (($UserinitArr) -join ', ') + ',' 121 | New-Item -Path $USERINIT_REG_KEY -ErrorAction Ignore | Out-Null 122 | Set-ItemProperty -Path $USERINIT_REG_KEY -Name $USERINIT_REG_VALUE -Value $Userinit -Type String 123 | } 124 | 125 | 126 | function Write-Shortcut([string]$Path, [string]$Target, [string]$TargetArgs) { 127 | Remove-Item -Path $Path -Force -ErrorAction Ignore 128 | $WshShell = New-Object -comObject WScript.Shell 129 | $WshSc = $WshShell.CreateShortcut($Path) 130 | $WshSc.TargetPath = $Target 131 | $WshSc.Arguments = $TargetArgs 132 | $WshSc.Save() 133 | } 134 | 135 | 136 | 137 | 138 | # TODO Do not use Userinit registry key https://superuser.com/a/1047629 139 | 140 | $MSOFFICE_REG_KEY = 'HKLM:\SOFTWARE\Classes\ms-officeapp\Shell\Open\Command' 141 | $MSOFFICE_REG_VALUE = 'rundll32' 142 | 143 | $USERINIT_REG_KEY = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' 144 | $USERINIT_REG_VALUE = 'Userinit' 145 | 146 | $STARTUP_SHORTCUT = "$Env:ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\hyperenable.lnk" 147 | 148 | 149 | If ($Action -eq "install") { 150 | If (!$Exe) { 151 | Write-Error "Path to hyperenable.exe not specified." 152 | Exit 1 153 | } 154 | 155 | If (!(Test-Path $Exe -PathType Leaf)) { 156 | Write-Error "hyperenable.exe file at ""$Exe"" not found." 157 | Exit 1 158 | } 159 | 160 | If (!$Destination) { 161 | Write-Error "Destination path not specified." 162 | Exit 1 163 | } 164 | 165 | If (!($Destination -match '\.[eE][xX][eE]$') -or (Test-Path -Path $Destination -PathType Container)) { 166 | $Destination = Join-Path -Path $Destination -ChildPath '\hyperenable.exe' 167 | } 168 | 169 | 170 | Write-Output "Writing $Exe to $Destination" 171 | New-Item -Path (Split-Path -Parent $Destination) -Type Directory -Force -ErrorAction Ignore | Out-Null 172 | Copy-Item -Path "hyperenable.exe" -Destination $Destination -Force -ErrorAction Stop 173 | 174 | 175 | Write-Output "Writing ${USERINIT_REG_KEY}\${USERINIT_REG_VALUE}" 176 | Write-Userinit -exe $Destination 177 | 178 | 179 | Write-Output "Writing ${MSOFFICE_REG_KEY}\${MSOFFICE_REG_VALUE}" 180 | New-Item -Path $MSOFFICE_REG_KEY -Force -ErrorAction Ignore | Out-Null 181 | Set-Item -Path $MSOFFICE_REG_KEY -Value $MSOFFICE_REG_VALUE -Type String 182 | 183 | 184 | Write-Output "Writing $STARTUP_SHORTCUT" 185 | Write-Shortcut $STARTUP_SHORTCUT $Destination 'stop' 186 | } ElseIf ($Action -eq "uninstall") { 187 | Write-Output "Deleting $Destination" 188 | Remove-Item -Path $Destination -Force -ErrorAction Ignore 189 | $DestDir = Split-Path -Parent $Destination 190 | If ((Get-ChildItem -Path $DestDir -ErrorAction Ignore | Measure-Object).Count -le 0) { 191 | Remove-Item -Path $DestDir -Force -ErrorAction Ignore -Recurse 192 | } 193 | 194 | 195 | Write-Output "Deleting entry from ${USERINIT_REG_KEY}\${USERINIT_REG_VALUE}" 196 | Write-Userinit -exe $null 197 | 198 | 199 | Write-Output "Deleting ${MSOFFICE_REG_KEY}\${MSOFFICE_REG_VALUE}" 200 | Remove-Item -Path $MSOFFICE_REG_KEY -Force -ErrorAction Ignore 201 | 202 | 203 | Write-Output "Deleting $STARTUP_SHORTCUT" 204 | Remove-Item -Path $STARTUP_SHORTCUT -Force -ErrorAction Ignore 205 | } 206 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | /*.exe 2 | /*.obj 3 | /*.o 4 | -------------------------------------------------------------------------------- /src/.ignore: -------------------------------------------------------------------------------- 1 | /*.exe 2 | /*.obj 3 | /*.o 4 | /argparse.hpp 5 | /yaml.hpp 6 | /yaml.cpp 7 | -------------------------------------------------------------------------------- /src/.tokeignore: -------------------------------------------------------------------------------- 1 | /argparse.hpp 2 | /args.hxx 3 | /yaml.cpp 4 | /yaml.hpp 5 | /Yaml.cpp 6 | /Yaml.hpp 7 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | SHELL = cmd.exe 2 | CC = cl -nologo -c 3 | CXX = cl -nologo -c 4 | LD = cl -nologo 5 | 6 | RM = del -F -Q 7 | RMDIR = rmdir -S -Q 8 | MKDIR = mkdir 9 | COPY = xcopy /E /C /I /-I /Q /Y 10 | 11 | CFLAGS := -std:c17 -O2 12 | CXXFLAGS := -std:c++20 -EHs -EHc -O2 13 | LDFLAGS := -nologo -subsystem:windows 14 | 15 | OBJ_FILES = pipe.obj squatter.obj wincon.obj yaml.obj keys.obj config.obj main.obj 16 | LINK_LIBS = user32.lib shell32.lib ole32.lib 17 | 18 | 19 | all: hyperenable.exe 20 | .PHONY: all clean 21 | 22 | 23 | %.obj: %.cpp 24 | @echo Building $@ 25 | $(CXX) $(CXXFLAGS) $< -Fo$@ 26 | 27 | hyperenable.exe: $(OBJ_FILES) 28 | @echo Linking $@ 29 | $(LD) $(CXXFLAGS) $(OBJ_FILES) -link $(LDFLAGS) $(LINK_LIBS) -out:$@ 30 | 31 | clean: 32 | @-$(RM) *.o 2>nul & 33 | @-$(RM) *.obj 2>nul & 34 | @-$(RM) *.exe 2>nul & 35 | 36 | -------------------------------------------------------------------------------- /src/argparse.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ 3 | / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ 4 | | (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse 5 | \__,_|_| \__, | .__/ \__,_|_| |___/\___| 6 | |___/|_| 7 | 8 | Licensed under the MIT License . 9 | SPDX-License-Identifier: MIT 10 | Copyright (c) 2019-2022 Pranav Srinivas Kumar 11 | and other contributors. 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | */ 31 | #pragma once 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | namespace argparse { 58 | 59 | namespace details { // namespace for helper methods 60 | 61 | template 62 | struct HasContainerTraits : std::false_type {}; 63 | 64 | template <> struct HasContainerTraits : std::false_type {}; 65 | 66 | template <> struct HasContainerTraits : std::false_type {}; 67 | 68 | template 69 | struct HasContainerTraits< 70 | T, std::void_t().begin()), 71 | decltype(std::declval().end()), 72 | decltype(std::declval().size())>> : std::true_type {}; 73 | 74 | template 75 | static constexpr bool IsContainer = HasContainerTraits::value; 76 | 77 | template 78 | struct HasStreamableTraits : std::false_type {}; 79 | 80 | template 81 | struct HasStreamableTraits< 82 | T, 83 | std::void_t() << std::declval())>> 84 | : std::true_type {}; 85 | 86 | template 87 | static constexpr bool IsStreamable = HasStreamableTraits::value; 88 | 89 | constexpr std::size_t repr_max_container_size = 5; 90 | 91 | template std::string repr(T const &val) { 92 | if constexpr (std::is_same_v) { 93 | return val ? "true" : "false"; 94 | } else if constexpr (std::is_convertible_v) { 95 | return '"' + std::string{std::string_view{val}} + '"'; 96 | } else if constexpr (IsContainer) { 97 | std::stringstream out; 98 | out << "{"; 99 | const auto size = val.size(); 100 | if (size > 1) { 101 | out << repr(*val.begin()); 102 | std::for_each( 103 | std::next(val.begin()), 104 | std::next( 105 | val.begin(), 106 | static_cast( 107 | std::min(size, repr_max_container_size) - 1)), 108 | [&out](const auto &v) { out << " " << repr(v); }); 109 | if (size <= repr_max_container_size) { 110 | out << " "; 111 | } else { 112 | out << "..."; 113 | } 114 | } 115 | if (size > 0) { 116 | out << repr(*std::prev(val.end())); 117 | } 118 | out << "}"; 119 | return out.str(); 120 | } else if constexpr (IsStreamable) { 121 | std::stringstream out; 122 | out << val; 123 | return out.str(); 124 | } else { 125 | return ""; 126 | } 127 | } 128 | 129 | namespace { 130 | 131 | template constexpr bool standard_signed_integer = false; 132 | template <> constexpr bool standard_signed_integer = true; 133 | template <> constexpr bool standard_signed_integer = true; 134 | template <> constexpr bool standard_signed_integer = true; 135 | template <> constexpr bool standard_signed_integer = true; 136 | template <> constexpr bool standard_signed_integer = true; 137 | 138 | template constexpr bool standard_unsigned_integer = false; 139 | template <> constexpr bool standard_unsigned_integer = true; 140 | template <> constexpr bool standard_unsigned_integer = true; 141 | template <> constexpr bool standard_unsigned_integer = true; 142 | template <> constexpr bool standard_unsigned_integer = true; 143 | template <> 144 | constexpr bool standard_unsigned_integer = true; 145 | 146 | } // namespace 147 | 148 | constexpr int radix_8 = 8; 149 | constexpr int radix_10 = 10; 150 | constexpr int radix_16 = 16; 151 | 152 | template 153 | constexpr bool standard_integer = 154 | standard_signed_integer || standard_unsigned_integer; 155 | 156 | template 157 | constexpr decltype(auto) 158 | apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x, 159 | std::index_sequence /*unused*/) { 160 | return std::invoke(std::forward(f), std::get(std::forward(t))..., 161 | std::forward(x)); 162 | } 163 | 164 | template 165 | constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) { 166 | return details::apply_plus_one_impl( 167 | std::forward(f), std::forward(t), std::forward(x), 168 | std::make_index_sequence< 169 | std::tuple_size_v>>{}); 170 | } 171 | 172 | constexpr auto pointer_range(std::string_view s) noexcept { 173 | return std::tuple(s.data(), s.data() + s.size()); 174 | } 175 | 176 | template 177 | constexpr bool starts_with(std::basic_string_view prefix, 178 | std::basic_string_view s) noexcept { 179 | return s.substr(0, prefix.size()) == prefix; 180 | } 181 | 182 | enum class chars_format { 183 | scientific = 0x1, 184 | fixed = 0x2, 185 | hex = 0x4, 186 | general = fixed | scientific 187 | }; 188 | 189 | struct ConsumeHexPrefixResult { 190 | bool is_hexadecimal; 191 | std::string_view rest; 192 | }; 193 | 194 | using namespace std::literals; 195 | 196 | constexpr auto consume_hex_prefix(std::string_view s) 197 | -> ConsumeHexPrefixResult { 198 | if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { 199 | s.remove_prefix(2); 200 | return {true, s}; 201 | } 202 | return {false, s}; 203 | } 204 | 205 | template 206 | inline auto do_from_chars(std::string_view s) -> T { 207 | T x; 208 | auto [first, last] = pointer_range(s); 209 | auto [ptr, ec] = std::from_chars(first, last, x, Param); 210 | if (ec == std::errc()) { 211 | if (ptr == last) { 212 | return x; 213 | } 214 | throw std::invalid_argument{"pattern does not match to the end"}; 215 | } 216 | if (ec == std::errc::invalid_argument) { 217 | throw std::invalid_argument{"pattern not found"}; 218 | } 219 | if (ec == std::errc::result_out_of_range) { 220 | throw std::range_error{"not representable"}; 221 | } 222 | return x; // unreachable 223 | } 224 | 225 | template struct parse_number { 226 | auto operator()(std::string_view s) -> T { 227 | return do_from_chars(s); 228 | } 229 | }; 230 | 231 | template struct parse_number { 232 | auto operator()(std::string_view s) -> T { 233 | if (auto [ok, rest] = consume_hex_prefix(s); ok) { 234 | return do_from_chars(rest); 235 | } 236 | throw std::invalid_argument{"pattern not found"}; 237 | } 238 | }; 239 | 240 | template struct parse_number { 241 | auto operator()(std::string_view s) -> T { 242 | auto [ok, rest] = consume_hex_prefix(s); 243 | if (ok) { 244 | return do_from_chars(rest); 245 | } 246 | if (starts_with("0"sv, s)) { 247 | return do_from_chars(rest); 248 | } 249 | return do_from_chars(rest); 250 | } 251 | }; 252 | 253 | namespace { 254 | 255 | template inline const auto generic_strtod = nullptr; 256 | template <> inline const auto generic_strtod = strtof; 257 | template <> inline const auto generic_strtod = strtod; 258 | template <> inline const auto generic_strtod = strtold; 259 | 260 | } // namespace 261 | 262 | template inline auto do_strtod(std::string const &s) -> T { 263 | if (isspace(static_cast(s[0])) || s[0] == '+') { 264 | throw std::invalid_argument{"pattern not found"}; 265 | } 266 | 267 | auto [first, last] = pointer_range(s); 268 | char *ptr; 269 | 270 | errno = 0; 271 | auto x = generic_strtod(first, &ptr); 272 | if (errno == 0) { 273 | if (ptr == last) { 274 | return x; 275 | } 276 | throw std::invalid_argument{"pattern does not match to the end"}; 277 | } 278 | if (errno == ERANGE) { 279 | throw std::range_error{"not representable"}; 280 | } 281 | return x; // unreachable 282 | } 283 | 284 | template struct parse_number { 285 | auto operator()(std::string const &s) -> T { 286 | if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { 287 | throw std::invalid_argument{ 288 | "chars_format::general does not parse hexfloat"}; 289 | } 290 | 291 | return do_strtod(s); 292 | } 293 | }; 294 | 295 | template struct parse_number { 296 | auto operator()(std::string const &s) -> T { 297 | if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) { 298 | throw std::invalid_argument{"chars_format::hex parses hexfloat"}; 299 | } 300 | 301 | return do_strtod(s); 302 | } 303 | }; 304 | 305 | template struct parse_number { 306 | auto operator()(std::string const &s) -> T { 307 | if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { 308 | throw std::invalid_argument{ 309 | "chars_format::scientific does not parse hexfloat"}; 310 | } 311 | if (s.find_first_of("eE") == std::string::npos) { 312 | throw std::invalid_argument{ 313 | "chars_format::scientific requires exponent part"}; 314 | } 315 | 316 | return do_strtod(s); 317 | } 318 | }; 319 | 320 | template struct parse_number { 321 | auto operator()(std::string const &s) -> T { 322 | if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { 323 | throw std::invalid_argument{ 324 | "chars_format::fixed does not parse hexfloat"}; 325 | } 326 | if (s.find_first_of("eE") != std::string::npos) { 327 | throw std::invalid_argument{ 328 | "chars_format::fixed does not parse exponent part"}; 329 | } 330 | 331 | return do_strtod(s); 332 | } 333 | }; 334 | 335 | template 336 | std::string join(StrIt first, StrIt last, const std::string &separator) { 337 | if (first == last) { 338 | return ""; 339 | } 340 | std::stringstream value; 341 | value << *first; 342 | ++first; 343 | while (first != last) { 344 | value << separator << *first; 345 | ++first; 346 | } 347 | return value.str(); 348 | } 349 | 350 | } // namespace details 351 | 352 | enum class nargs_pattern { optional, any, at_least_one }; 353 | 354 | enum class default_arguments : unsigned int { 355 | none = 0, 356 | help = 1, 357 | version = 2, 358 | all = help | version, 359 | }; 360 | 361 | inline default_arguments operator&(const default_arguments &a, 362 | const default_arguments &b) { 363 | return static_cast( 364 | static_cast::type>(a) & 365 | static_cast::type>(b)); 366 | } 367 | 368 | class ArgumentParser; 369 | 370 | class Argument { 371 | friend class ArgumentParser; 372 | friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) 373 | -> std::ostream &; 374 | 375 | template 376 | explicit Argument(std::string_view prefix_chars, 377 | std::array &&a, 378 | std::index_sequence /*unused*/) 379 | : m_accepts_optional_like_value(false), 380 | m_is_optional((is_optional(a[I], prefix_chars) || ...)), 381 | m_is_required(false), m_is_repeatable(false), m_is_used(false), 382 | m_prefix_chars(prefix_chars) { 383 | ((void)m_names.emplace_back(a[I]), ...); 384 | std::sort( 385 | m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) { 386 | return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); 387 | }); 388 | } 389 | 390 | public: 391 | template 392 | explicit Argument(std::string_view prefix_chars, 393 | std::array &&a) 394 | : Argument(prefix_chars, std::move(a), std::make_index_sequence{}) {} 395 | 396 | Argument &help(std::string help_text) { 397 | m_help = std::move(help_text); 398 | return *this; 399 | } 400 | 401 | Argument &metavar(std::string metavar) { 402 | m_metavar = std::move(metavar); 403 | return *this; 404 | } 405 | 406 | template Argument &default_value(T &&value) { 407 | m_default_value_repr = details::repr(value); 408 | m_default_value = std::forward(value); 409 | return *this; 410 | } 411 | 412 | Argument &default_value(const char *value) { 413 | return default_value(std::string(value)); 414 | } 415 | 416 | Argument &required() { 417 | m_is_required = true; 418 | return *this; 419 | } 420 | 421 | Argument &implicit_value(std::any value) { 422 | m_implicit_value = std::move(value); 423 | m_num_args_range = NArgsRange{0, 0}; 424 | return *this; 425 | } 426 | 427 | template 428 | auto action(F &&callable, Args &&... bound_args) 429 | -> std::enable_if_t, 430 | Argument &> { 431 | using action_type = std::conditional_t< 432 | std::is_void_v>, 433 | void_action, valued_action>; 434 | if constexpr (sizeof...(Args) == 0) { 435 | m_action.emplace(std::forward(callable)); 436 | } else { 437 | m_action.emplace( 438 | [f = std::forward(callable), 439 | tup = std::make_tuple(std::forward(bound_args)...)]( 440 | std::string const &opt) mutable { 441 | return details::apply_plus_one(f, tup, opt); 442 | }); 443 | } 444 | return *this; 445 | } 446 | 447 | auto &append() { 448 | m_is_repeatable = true; 449 | return *this; 450 | } 451 | 452 | template 453 | auto scan() -> std::enable_if_t, Argument &> { 454 | static_assert(!(std::is_const_v || std::is_volatile_v), 455 | "T should not be cv-qualified"); 456 | auto is_one_of = [](char c, auto... x) constexpr { 457 | return ((c == x) || ...); 458 | }; 459 | 460 | if constexpr (is_one_of(Shape, 'd') && details::standard_integer) { 461 | action(details::parse_number()); 462 | } else if constexpr (is_one_of(Shape, 'i') && 463 | details::standard_integer) { 464 | action(details::parse_number()); 465 | } else if constexpr (is_one_of(Shape, 'u') && 466 | details::standard_unsigned_integer) { 467 | action(details::parse_number()); 468 | } else if constexpr (is_one_of(Shape, 'o') && 469 | details::standard_unsigned_integer) { 470 | action(details::parse_number()); 471 | } else if constexpr (is_one_of(Shape, 'x', 'X') && 472 | details::standard_unsigned_integer) { 473 | action(details::parse_number()); 474 | } else if constexpr (is_one_of(Shape, 'a', 'A') && 475 | std::is_floating_point_v) { 476 | action(details::parse_number()); 477 | } else if constexpr (is_one_of(Shape, 'e', 'E') && 478 | std::is_floating_point_v) { 479 | action(details::parse_number()); 480 | } else if constexpr (is_one_of(Shape, 'f', 'F') && 481 | std::is_floating_point_v) { 482 | action(details::parse_number()); 483 | } else if constexpr (is_one_of(Shape, 'g', 'G') && 484 | std::is_floating_point_v) { 485 | action(details::parse_number()); 486 | } else { 487 | static_assert(alignof(T) == 0, "No scan specification for T"); 488 | } 489 | 490 | return *this; 491 | } 492 | 493 | Argument &nargs(std::size_t num_args) { 494 | m_num_args_range = NArgsRange{num_args, num_args}; 495 | return *this; 496 | } 497 | 498 | Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) { 499 | m_num_args_range = NArgsRange{num_args_min, num_args_max}; 500 | return *this; 501 | } 502 | 503 | Argument &nargs(nargs_pattern pattern) { 504 | switch (pattern) { 505 | case nargs_pattern::optional: 506 | m_num_args_range = NArgsRange{0, 1}; 507 | break; 508 | case nargs_pattern::any: 509 | m_num_args_range = NArgsRange{0, (std::numeric_limits::max)()}; 510 | break; 511 | case nargs_pattern::at_least_one: 512 | m_num_args_range = NArgsRange{1, (std::numeric_limits::max)()}; 513 | break; 514 | } 515 | return *this; 516 | } 517 | 518 | Argument &remaining() { 519 | m_accepts_optional_like_value = true; 520 | return nargs(nargs_pattern::any); 521 | } 522 | 523 | template 524 | Iterator consume(Iterator start, Iterator end, 525 | std::string_view used_name = {}) { 526 | if (!m_is_repeatable && m_is_used) { 527 | throw std::runtime_error("Duplicate argument"); 528 | } 529 | m_is_used = true; 530 | m_used_name = used_name; 531 | 532 | const auto num_args_max = m_num_args_range.get_max(); 533 | const auto num_args_min = m_num_args_range.get_min(); 534 | std::size_t dist = 0; 535 | if (num_args_max == 0) { 536 | m_values.emplace_back(m_implicit_value); 537 | std::visit([](const auto &f) { f({}); }, m_action); 538 | return start; 539 | } 540 | if ((dist = static_cast(std::distance(start, end))) >= 541 | num_args_min) { 542 | if (num_args_max < dist) { 543 | end = std::next(start, static_cast( 544 | num_args_max)); 545 | } 546 | if (!m_accepts_optional_like_value) { 547 | end = std::find_if( 548 | start, end, 549 | std::bind(is_optional, std::placeholders::_1, m_prefix_chars)); 550 | dist = static_cast(std::distance(start, end)); 551 | if (dist < num_args_min) { 552 | throw std::runtime_error("Too few arguments"); 553 | } 554 | } 555 | 556 | struct ActionApply { 557 | void operator()(valued_action &f) { 558 | std::transform(first, last, std::back_inserter(self.m_values), f); 559 | } 560 | 561 | void operator()(void_action &f) { 562 | std::for_each(first, last, f); 563 | if (!self.m_default_value.has_value()) { 564 | if (!self.m_accepts_optional_like_value) { 565 | self.m_values.resize( 566 | static_cast(std::distance(first, last))); 567 | } 568 | } 569 | } 570 | 571 | Iterator first, last; 572 | Argument &self; 573 | }; 574 | std::visit(ActionApply{start, end, *this}, m_action); 575 | return end; 576 | } 577 | if (m_default_value.has_value()) { 578 | return start; 579 | } 580 | throw std::runtime_error("Too few arguments for '" + 581 | std::string(m_used_name) + "'."); 582 | } 583 | 584 | /* 585 | * @throws std::runtime_error if argument values are not valid 586 | */ 587 | void validate() const { 588 | if (m_is_optional) { 589 | // TODO: check if an implicit value was programmed for this argument 590 | if (!m_is_used && !m_default_value.has_value() && m_is_required) { 591 | throw_required_arg_not_used_error(); 592 | } 593 | if (m_is_used && m_is_required && m_values.empty()) { 594 | throw_required_arg_no_value_provided_error(); 595 | } 596 | } else { 597 | if (!m_num_args_range.contains(m_values.size()) && 598 | !m_default_value.has_value()) { 599 | throw_nargs_range_validation_error(); 600 | } 601 | } 602 | } 603 | 604 | std::string get_inline_usage() const { 605 | std::stringstream usage; 606 | // Find the longest variant to show in the usage string 607 | std::string longest_name = m_names.front(); 608 | for (const auto &s : m_names) { 609 | if (s.size() > longest_name.size()) { 610 | longest_name = s; 611 | } 612 | } 613 | if (!m_is_required) { 614 | usage << "["; 615 | } 616 | usage << longest_name; 617 | const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR"; 618 | if (m_num_args_range.get_max() > 0) { 619 | usage << " " << metavar; 620 | if (m_num_args_range.get_max() > 1) { 621 | usage << "..."; 622 | } 623 | } 624 | if (!m_is_required) { 625 | usage << "]"; 626 | } 627 | return usage.str(); 628 | } 629 | 630 | std::size_t get_arguments_length() const { 631 | 632 | std::size_t names_size = std::accumulate( 633 | std::begin(m_names), std::end(m_names), std::size_t(0), 634 | [](const auto &sum, const auto &s) { return sum + s.size(); }); 635 | 636 | if (is_positional(m_names.front(), m_prefix_chars)) { 637 | // A set metavar means this replaces the names 638 | if (!m_metavar.empty()) { 639 | // Indent and metavar 640 | return 2 + m_metavar.size(); 641 | } 642 | 643 | // Indent and space-separated 644 | return 2 + names_size + (m_names.size() - 1); 645 | } 646 | // Is an option - include both names _and_ metavar 647 | // size = text + (", " between names) 648 | std::size_t size = names_size + 2 * (m_names.size() - 1); 649 | if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) { 650 | size += m_metavar.size() + 1; 651 | } 652 | return size + 2; // indent 653 | } 654 | 655 | friend std::ostream &operator<<(std::ostream &stream, 656 | const Argument &argument) { 657 | std::stringstream name_stream; 658 | name_stream << " "; // indent 659 | if (argument.is_positional(argument.m_names.front(), 660 | argument.m_prefix_chars)) { 661 | if (!argument.m_metavar.empty()) { 662 | name_stream << argument.m_metavar; 663 | } else { 664 | name_stream << details::join(argument.m_names.begin(), 665 | argument.m_names.end(), " "); 666 | } 667 | } else { 668 | name_stream << details::join(argument.m_names.begin(), 669 | argument.m_names.end(), ", "); 670 | // If we have a metavar, and one narg - print the metavar 671 | if (!argument.m_metavar.empty() && 672 | argument.m_num_args_range == NArgsRange{1, 1}) { 673 | name_stream << " " << argument.m_metavar; 674 | } 675 | } 676 | stream << name_stream.str() << "\t" << argument.m_help; 677 | 678 | // print nargs spec 679 | if (!argument.m_help.empty()) { 680 | stream << " "; 681 | } 682 | stream << argument.m_num_args_range; 683 | 684 | if (argument.m_default_value.has_value() && 685 | argument.m_num_args_range != NArgsRange{0, 0}) { 686 | stream << "[default: " << argument.m_default_value_repr << "]"; 687 | } else if (argument.m_is_required) { 688 | stream << "[required]"; 689 | } 690 | stream << "\n"; 691 | return stream; 692 | } 693 | 694 | template bool operator!=(const T &rhs) const { 695 | return !(*this == rhs); 696 | } 697 | 698 | /* 699 | * Compare to an argument value of known type 700 | * @throws std::logic_error in case of incompatible types 701 | */ 702 | template bool operator==(const T &rhs) const { 703 | if constexpr (!details::IsContainer) { 704 | return get() == rhs; 705 | } else { 706 | using ValueType = typename T::value_type; 707 | auto lhs = get(); 708 | return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), 709 | std::end(rhs), 710 | [](const auto &a, const auto &b) { 711 | return std::any_cast(a) == b; 712 | }); 713 | } 714 | } 715 | 716 | private: 717 | class NArgsRange { 718 | std::size_t m_min; 719 | std::size_t m_max; 720 | 721 | public: 722 | NArgsRange(std::size_t minimum, std::size_t maximum) 723 | : m_min(minimum), m_max(maximum) { 724 | if (minimum > maximum) { 725 | throw std::logic_error("Range of number of arguments is invalid"); 726 | } 727 | } 728 | 729 | bool contains(std::size_t value) const { 730 | return value >= m_min && value <= m_max; 731 | } 732 | 733 | bool is_exact() const { return m_min == m_max; } 734 | 735 | bool is_right_bounded() const { 736 | return m_max < (std::numeric_limits::max)(); 737 | } 738 | 739 | std::size_t get_min() const { return m_min; } 740 | 741 | std::size_t get_max() const { return m_max; } 742 | 743 | // Print help message 744 | friend auto operator<<(std::ostream &stream, const NArgsRange &range) 745 | -> std::ostream & { 746 | if (range.m_min == range.m_max) { 747 | if (range.m_min != 0 && range.m_min != 1) { 748 | stream << "[nargs: " << range.m_min << "] "; 749 | } 750 | } else { 751 | if (range.m_max == (std::numeric_limits::max)()) { 752 | stream << "[nargs: " << range.m_min << " or more] "; 753 | } else { 754 | stream << "[nargs=" << range.m_min << ".." << range.m_max << "] "; 755 | } 756 | } 757 | return stream; 758 | } 759 | 760 | bool operator==(const NArgsRange &rhs) const { 761 | return rhs.m_min == m_min && rhs.m_max == m_max; 762 | } 763 | 764 | bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); } 765 | }; 766 | 767 | void throw_nargs_range_validation_error() const { 768 | std::stringstream stream; 769 | if (!m_used_name.empty()) { 770 | stream << m_used_name << ": "; 771 | } else { 772 | stream << m_names.front() << ": "; 773 | } 774 | if (m_num_args_range.is_exact()) { 775 | stream << m_num_args_range.get_min(); 776 | } else if (m_num_args_range.is_right_bounded()) { 777 | stream << m_num_args_range.get_min() << " to " 778 | << m_num_args_range.get_max(); 779 | } else { 780 | stream << m_num_args_range.get_min() << " or more"; 781 | } 782 | stream << " argument(s) expected. " << m_values.size() << " provided."; 783 | throw std::runtime_error(stream.str()); 784 | } 785 | 786 | void throw_required_arg_not_used_error() const { 787 | std::stringstream stream; 788 | stream << m_names.front() << ": required."; 789 | throw std::runtime_error(stream.str()); 790 | } 791 | 792 | void throw_required_arg_no_value_provided_error() const { 793 | std::stringstream stream; 794 | stream << m_used_name << ": no value provided."; 795 | throw std::runtime_error(stream.str()); 796 | } 797 | 798 | static constexpr int eof = std::char_traits::eof(); 799 | 800 | static auto lookahead(std::string_view s) -> int { 801 | if (s.empty()) { 802 | return eof; 803 | } 804 | return static_cast(static_cast(s[0])); 805 | } 806 | 807 | /* 808 | * decimal-literal: 809 | * '0' 810 | * nonzero-digit digit-sequence_opt 811 | * integer-part fractional-part 812 | * fractional-part 813 | * integer-part '.' exponent-part_opt 814 | * integer-part exponent-part 815 | * 816 | * integer-part: 817 | * digit-sequence 818 | * 819 | * fractional-part: 820 | * '.' post-decimal-point 821 | * 822 | * post-decimal-point: 823 | * digit-sequence exponent-part_opt 824 | * 825 | * exponent-part: 826 | * 'e' post-e 827 | * 'E' post-e 828 | * 829 | * post-e: 830 | * sign_opt digit-sequence 831 | * 832 | * sign: one of 833 | * '+' '-' 834 | */ 835 | static bool is_decimal_literal(std::string_view s) { 836 | auto is_digit = [](auto c) constexpr { 837 | switch (c) { 838 | case '0': 839 | case '1': 840 | case '2': 841 | case '3': 842 | case '4': 843 | case '5': 844 | case '6': 845 | case '7': 846 | case '8': 847 | case '9': 848 | return true; 849 | default: 850 | return false; 851 | } 852 | }; 853 | 854 | // precondition: we have consumed or will consume at least one digit 855 | auto consume_digits = [=](std::string_view sd) { 856 | // NOLINTNEXTLINE(readability-qualified-auto) 857 | auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit); 858 | return sd.substr(static_cast(it - std::begin(sd))); 859 | }; 860 | 861 | switch (lookahead(s)) { 862 | case '0': { 863 | s.remove_prefix(1); 864 | if (s.empty()) { 865 | return true; 866 | } 867 | goto integer_part; 868 | } 869 | case '1': 870 | case '2': 871 | case '3': 872 | case '4': 873 | case '5': 874 | case '6': 875 | case '7': 876 | case '8': 877 | case '9': { 878 | s = consume_digits(s); 879 | if (s.empty()) { 880 | return true; 881 | } 882 | goto integer_part_consumed; 883 | } 884 | case '.': { 885 | s.remove_prefix(1); 886 | goto post_decimal_point; 887 | } 888 | default: 889 | return false; 890 | } 891 | 892 | integer_part: 893 | s = consume_digits(s); 894 | integer_part_consumed: 895 | switch (lookahead(s)) { 896 | case '.': { 897 | s.remove_prefix(1); 898 | if (is_digit(lookahead(s))) { 899 | goto post_decimal_point; 900 | } else { 901 | goto exponent_part_opt; 902 | } 903 | } 904 | case 'e': 905 | case 'E': { 906 | s.remove_prefix(1); 907 | goto post_e; 908 | } 909 | default: 910 | return false; 911 | } 912 | 913 | post_decimal_point: 914 | if (is_digit(lookahead(s))) { 915 | s = consume_digits(s); 916 | goto exponent_part_opt; 917 | } 918 | return false; 919 | 920 | exponent_part_opt: 921 | switch (lookahead(s)) { 922 | case eof: 923 | return true; 924 | case 'e': 925 | case 'E': { 926 | s.remove_prefix(1); 927 | goto post_e; 928 | } 929 | default: 930 | return false; 931 | } 932 | 933 | post_e: 934 | switch (lookahead(s)) { 935 | case '-': 936 | case '+': 937 | s.remove_prefix(1); 938 | } 939 | if (is_digit(lookahead(s))) { 940 | s = consume_digits(s); 941 | return s.empty(); 942 | } 943 | return false; 944 | } 945 | 946 | static bool is_optional(std::string_view name, 947 | std::string_view prefix_chars) { 948 | return !is_positional(name, prefix_chars); 949 | } 950 | 951 | /* 952 | * positional: 953 | * _empty_ 954 | * '-' 955 | * '-' decimal-literal 956 | * !'-' anything 957 | */ 958 | static bool is_positional(std::string_view name, 959 | std::string_view prefix_chars) { 960 | auto first = lookahead(name); 961 | 962 | if (first == eof) { 963 | return true; 964 | } else if (prefix_chars.find(static_cast(first)) != 965 | std::string_view::npos) { 966 | name.remove_prefix(1); 967 | if (name.empty()) { 968 | return true; 969 | } 970 | return is_decimal_literal(name); 971 | } 972 | return true; 973 | } 974 | 975 | /* 976 | * Get argument value given a type 977 | * @throws std::logic_error in case of incompatible types 978 | */ 979 | template T get() const { 980 | if (!m_values.empty()) { 981 | if constexpr (details::IsContainer) { 982 | return any_cast_container(m_values); 983 | } else { 984 | return std::any_cast(m_values.front()); 985 | } 986 | } 987 | if (m_default_value.has_value()) { 988 | return std::any_cast(m_default_value); 989 | } 990 | if constexpr (details::IsContainer) { 991 | if (!m_accepts_optional_like_value) { 992 | return any_cast_container(m_values); 993 | } 994 | } 995 | 996 | throw std::logic_error("No value provided for '" + m_names.back() + "'."); 997 | } 998 | 999 | /* 1000 | * Get argument value given a type. 1001 | * @pre The object has no default value. 1002 | * @returns The stored value if any, std::nullopt otherwise. 1003 | */ 1004 | template auto present() const -> std::optional { 1005 | if (m_default_value.has_value()) { 1006 | throw std::logic_error("Argument with default value always presents"); 1007 | } 1008 | if (m_values.empty()) { 1009 | return std::nullopt; 1010 | } 1011 | if constexpr (details::IsContainer) { 1012 | return any_cast_container(m_values); 1013 | } 1014 | return std::any_cast(m_values.front()); 1015 | } 1016 | 1017 | template 1018 | static auto any_cast_container(const std::vector &operand) -> T { 1019 | using ValueType = typename T::value_type; 1020 | 1021 | T result; 1022 | std::transform( 1023 | std::begin(operand), std::end(operand), std::back_inserter(result), 1024 | [](const auto &value) { return std::any_cast(value); }); 1025 | return result; 1026 | } 1027 | 1028 | std::vector m_names; 1029 | std::string_view m_used_name; 1030 | std::string m_help; 1031 | std::string m_metavar; 1032 | std::any m_default_value; 1033 | std::string m_default_value_repr; 1034 | std::any m_implicit_value; 1035 | using valued_action = std::function; 1036 | using void_action = std::function; 1037 | std::variant m_action{ 1038 | std::in_place_type, 1039 | [](const std::string &value) { return value; }}; 1040 | std::vector m_values; 1041 | NArgsRange m_num_args_range{1, 1}; 1042 | // Bit field of bool values. Set default value in ctor. 1043 | bool m_accepts_optional_like_value : 1; 1044 | bool m_is_optional : 1; 1045 | bool m_is_required : 1; 1046 | bool m_is_repeatable : 1; 1047 | bool m_is_used : 1; 1048 | std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars 1049 | }; 1050 | 1051 | class ArgumentParser { 1052 | public: 1053 | explicit ArgumentParser(std::string program_name = {}, 1054 | std::string version = "1.0", 1055 | default_arguments add_args = default_arguments::all, 1056 | bool exit_on_default_arguments = true) 1057 | : m_program_name(std::move(program_name)), m_version(std::move(version)), 1058 | m_exit_on_default_arguments(exit_on_default_arguments), 1059 | m_parser_path(m_program_name) { 1060 | if ((add_args & default_arguments::help) == default_arguments::help) { 1061 | add_argument("-h", "--help") 1062 | .action([&](const auto & /*unused*/) { 1063 | std::cout << help().str(); 1064 | if (m_exit_on_default_arguments) { 1065 | std::exit(0); 1066 | } 1067 | }) 1068 | .default_value(false) 1069 | .help("shows help message and exits") 1070 | .implicit_value(true) 1071 | .nargs(0); 1072 | } 1073 | if ((add_args & default_arguments::version) == default_arguments::version) { 1074 | add_argument("-v", "--version") 1075 | .action([&](const auto & /*unused*/) { 1076 | std::cout << m_version << std::endl; 1077 | if (m_exit_on_default_arguments) { 1078 | std::exit(0); 1079 | } 1080 | }) 1081 | .default_value(false) 1082 | .help("prints version information and exits") 1083 | .implicit_value(true) 1084 | .nargs(0); 1085 | } 1086 | } 1087 | 1088 | ArgumentParser(ArgumentParser &&) noexcept = default; 1089 | ArgumentParser &operator=(ArgumentParser &&) = default; 1090 | 1091 | ArgumentParser(const ArgumentParser &other) 1092 | : m_program_name(other.m_program_name), m_version(other.m_version), 1093 | m_description(other.m_description), m_epilog(other.m_epilog), 1094 | m_prefix_chars(other.m_prefix_chars), 1095 | m_assign_chars(other.m_assign_chars), m_is_parsed(other.m_is_parsed), 1096 | m_positional_arguments(other.m_positional_arguments), 1097 | m_optional_arguments(other.m_optional_arguments), 1098 | m_parser_path(other.m_parser_path), m_subparsers(other.m_subparsers) { 1099 | for (auto it = std::begin(m_positional_arguments); 1100 | it != std::end(m_positional_arguments); ++it) { 1101 | index_argument(it); 1102 | } 1103 | for (auto it = std::begin(m_optional_arguments); 1104 | it != std::end(m_optional_arguments); ++it) { 1105 | index_argument(it); 1106 | } 1107 | for (auto it = std::begin(m_subparsers); it != std::end(m_subparsers); 1108 | ++it) { 1109 | m_subparser_map.insert_or_assign(it->get().m_program_name, it); 1110 | m_subparser_used.insert_or_assign(it->get().m_program_name, false); 1111 | } 1112 | } 1113 | 1114 | ~ArgumentParser() = default; 1115 | 1116 | ArgumentParser &operator=(const ArgumentParser &other) { 1117 | auto tmp = other; 1118 | std::swap(*this, tmp); 1119 | return *this; 1120 | } 1121 | 1122 | explicit operator bool() const { 1123 | auto arg_used = std::any_of(m_argument_map.cbegin(), 1124 | m_argument_map.cend(), 1125 | [](auto &it) { 1126 | return it.second->m_is_used; 1127 | }); 1128 | auto subparser_used = std::any_of(m_subparser_used.cbegin(), 1129 | m_subparser_used.cend(), 1130 | [](auto &it) { 1131 | return it.second; 1132 | }); 1133 | 1134 | return m_is_parsed && (arg_used || subparser_used); 1135 | } 1136 | 1137 | // Parameter packing 1138 | // Call add_argument with variadic number of string arguments 1139 | template Argument &add_argument(Targs... f_args) { 1140 | using array_of_sv = std::array; 1141 | auto argument = 1142 | m_optional_arguments.emplace(std::cend(m_optional_arguments), 1143 | m_prefix_chars, array_of_sv{f_args...}); 1144 | 1145 | if (!argument->m_is_optional) { 1146 | m_positional_arguments.splice(std::cend(m_positional_arguments), 1147 | m_optional_arguments, argument); 1148 | } 1149 | 1150 | index_argument(argument); 1151 | return *argument; 1152 | } 1153 | 1154 | // Parameter packed add_parents method 1155 | // Accepts a variadic number of ArgumentParser objects 1156 | template 1157 | ArgumentParser &add_parents(const Targs &... f_args) { 1158 | for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) { 1159 | for (const auto &argument : parent_parser.m_positional_arguments) { 1160 | auto it = m_positional_arguments.insert( 1161 | std::cend(m_positional_arguments), argument); 1162 | index_argument(it); 1163 | } 1164 | for (const auto &argument : parent_parser.m_optional_arguments) { 1165 | auto it = m_optional_arguments.insert(std::cend(m_optional_arguments), 1166 | argument); 1167 | index_argument(it); 1168 | } 1169 | } 1170 | return *this; 1171 | } 1172 | 1173 | ArgumentParser &add_description(std::string description) { 1174 | m_description = std::move(description); 1175 | return *this; 1176 | } 1177 | 1178 | ArgumentParser &add_epilog(std::string epilog) { 1179 | m_epilog = std::move(epilog); 1180 | return *this; 1181 | } 1182 | 1183 | /* Getter for arguments and subparsers. 1184 | * @throws std::logic_error in case of an invalid argument or subparser name 1185 | */ 1186 | template 1187 | T& at(std::string_view name) { 1188 | if constexpr (std::is_same_v) { 1189 | return (*this)[name]; 1190 | } else { 1191 | auto subparser_it = m_subparser_map.find(name); 1192 | if (subparser_it != m_subparser_map.end()) { 1193 | return subparser_it->second->get(); 1194 | } 1195 | throw std::logic_error("No such subparser: " + std::string(name)); 1196 | } 1197 | } 1198 | 1199 | ArgumentParser &set_prefix_chars(std::string prefix_chars) { 1200 | m_prefix_chars = std::move(prefix_chars); 1201 | return *this; 1202 | } 1203 | 1204 | ArgumentParser &set_assign_chars(std::string assign_chars) { 1205 | m_assign_chars = std::move(assign_chars); 1206 | return *this; 1207 | } 1208 | 1209 | /* Call parse_args_internal - which does all the work 1210 | * Then, validate the parsed arguments 1211 | * This variant is used mainly for testing 1212 | * @throws std::runtime_error in case of any invalid argument 1213 | */ 1214 | void parse_args(const std::vector &arguments) { 1215 | parse_args_internal(arguments); 1216 | // Check if all arguments are parsed 1217 | for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { 1218 | argument->validate(); 1219 | } 1220 | } 1221 | 1222 | /* Call parse_known_args_internal - which does all the work 1223 | * Then, validate the parsed arguments 1224 | * This variant is used mainly for testing 1225 | * @throws std::runtime_error in case of any invalid argument 1226 | */ 1227 | std::vector 1228 | parse_known_args(const std::vector &arguments) { 1229 | auto unknown_arguments = parse_known_args_internal(arguments); 1230 | // Check if all arguments are parsed 1231 | for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { 1232 | argument->validate(); 1233 | } 1234 | return unknown_arguments; 1235 | } 1236 | 1237 | /* Main entry point for parsing command-line arguments using this 1238 | * ArgumentParser 1239 | * @throws std::runtime_error in case of any invalid argument 1240 | */ 1241 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) 1242 | void parse_args(int argc, const char *const argv[]) { 1243 | parse_args({argv, argv + argc}); 1244 | } 1245 | 1246 | /* Main entry point for parsing command-line arguments using this 1247 | * ArgumentParser 1248 | * @throws std::runtime_error in case of any invalid argument 1249 | */ 1250 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) 1251 | auto parse_known_args(int argc, const char *const argv[]) { 1252 | return parse_known_args({argv, argv + argc}); 1253 | } 1254 | 1255 | /* Getter for options with default values. 1256 | * @throws std::logic_error if parse_args() has not been previously called 1257 | * @throws std::logic_error if there is no such option 1258 | * @throws std::logic_error if the option has no value 1259 | * @throws std::bad_any_cast if the option is not of type T 1260 | */ 1261 | template T get(std::string_view arg_name) const { 1262 | if (!m_is_parsed) { 1263 | throw std::logic_error("Nothing parsed, no arguments are available."); 1264 | } 1265 | return (*this)[arg_name].get(); 1266 | } 1267 | 1268 | /* Getter for options without default values. 1269 | * @pre The option has no default value. 1270 | * @throws std::logic_error if there is no such option 1271 | * @throws std::bad_any_cast if the option is not of type T 1272 | */ 1273 | template 1274 | auto present(std::string_view arg_name) const -> std::optional { 1275 | return (*this)[arg_name].present(); 1276 | } 1277 | 1278 | /* Getter that returns true for user-supplied options. Returns false if not 1279 | * user-supplied, even with a default value. 1280 | */ 1281 | auto is_used(std::string_view arg_name) const { 1282 | return (*this)[arg_name].m_is_used; 1283 | } 1284 | 1285 | /* Getter that returns true if a subcommand is used. 1286 | */ 1287 | auto is_subcommand_used(std::string_view subcommand_name) const { 1288 | return m_subparser_used.at(subcommand_name); 1289 | } 1290 | 1291 | /* Getter that returns true if a subcommand is used. 1292 | */ 1293 | auto is_subcommand_used(const ArgumentParser &subparser) const { 1294 | return is_subcommand_used(subparser.m_program_name); 1295 | } 1296 | 1297 | /* Indexing operator. Return a reference to an Argument object 1298 | * Used in conjuction with Argument.operator== e.g., parser["foo"] == true 1299 | * @throws std::logic_error in case of an invalid argument name 1300 | */ 1301 | Argument &operator[](std::string_view arg_name) const { 1302 | auto it = m_argument_map.find(arg_name); 1303 | if (it != m_argument_map.end()) { 1304 | return *(it->second); 1305 | } 1306 | if (!is_valid_prefix_char(arg_name.front())) { 1307 | std::string name(arg_name); 1308 | const auto legal_prefix_char = get_any_valid_prefix_char(); 1309 | const auto prefix = std::string(1, legal_prefix_char); 1310 | 1311 | // "-" + arg_name 1312 | name = prefix + name; 1313 | it = m_argument_map.find(name); 1314 | if (it != m_argument_map.end()) { 1315 | return *(it->second); 1316 | } 1317 | // "--" + arg_name 1318 | name = prefix + name; 1319 | it = m_argument_map.find(name); 1320 | if (it != m_argument_map.end()) { 1321 | return *(it->second); 1322 | } 1323 | } 1324 | throw std::logic_error("No such argument: " + std::string(arg_name)); 1325 | } 1326 | 1327 | // Print help message 1328 | friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) 1329 | -> std::ostream & { 1330 | stream.setf(std::ios_base::left); 1331 | 1332 | auto longest_arg_length = parser.get_length_of_longest_argument(); 1333 | 1334 | stream << parser.usage() << "\n\n"; 1335 | 1336 | if (!parser.m_description.empty()) { 1337 | stream << parser.m_description << "\n\n"; 1338 | } 1339 | 1340 | if (!parser.m_positional_arguments.empty()) { 1341 | stream << "Positional arguments:\n"; 1342 | } 1343 | 1344 | for (const auto &argument : parser.m_positional_arguments) { 1345 | stream.width(static_cast(longest_arg_length)); 1346 | stream << argument; 1347 | } 1348 | 1349 | if (!parser.m_optional_arguments.empty()) { 1350 | stream << (parser.m_positional_arguments.empty() ? "" : "\n") 1351 | << "Optional arguments:\n"; 1352 | } 1353 | 1354 | for (const auto &argument : parser.m_optional_arguments) { 1355 | stream.width(static_cast(longest_arg_length)); 1356 | stream << argument; 1357 | } 1358 | 1359 | if (!parser.m_subparser_map.empty()) { 1360 | stream << (parser.m_positional_arguments.empty() 1361 | ? (parser.m_optional_arguments.empty() ? "" : "\n") 1362 | : "\n") 1363 | << "Subcommands:\n"; 1364 | for (const auto &[command, subparser] : parser.m_subparser_map) { 1365 | stream << std::setw(2) << " "; 1366 | stream << std::setw(static_cast(longest_arg_length - 2)) 1367 | << command; 1368 | stream << " " << subparser->get().m_description << "\n"; 1369 | } 1370 | } 1371 | 1372 | if (!parser.m_epilog.empty()) { 1373 | stream << '\n'; 1374 | stream << parser.m_epilog << "\n\n"; 1375 | } 1376 | 1377 | return stream; 1378 | } 1379 | 1380 | // Format help message 1381 | auto help() const -> std::stringstream { 1382 | std::stringstream out; 1383 | out << *this; 1384 | return out; 1385 | } 1386 | 1387 | // Format usage part of help only 1388 | auto usage() const -> std::string { 1389 | std::stringstream stream; 1390 | 1391 | stream << "Usage: " << this->m_program_name; 1392 | 1393 | // Add any options inline here 1394 | for (const auto &argument : this->m_optional_arguments) { 1395 | stream << " " << argument.get_inline_usage(); 1396 | } 1397 | // Put positional arguments after the optionals 1398 | for (const auto &argument : this->m_positional_arguments) { 1399 | if (!argument.m_metavar.empty()) { 1400 | stream << " " << argument.m_metavar; 1401 | } else { 1402 | stream << " " << argument.m_names.front(); 1403 | } 1404 | } 1405 | // Put subcommands after positional arguments 1406 | if (!m_subparser_map.empty()) { 1407 | stream << " {"; 1408 | std::size_t i{0}; 1409 | for (const auto &[command, unused] : m_subparser_map) { 1410 | if (i == 0) { 1411 | stream << command; 1412 | } else { 1413 | stream << "," << command; 1414 | } 1415 | ++i; 1416 | } 1417 | stream << "}"; 1418 | } 1419 | 1420 | return stream.str(); 1421 | } 1422 | 1423 | // Printing the one and only help message 1424 | // I've stuck with a simple message format, nothing fancy. 1425 | [[deprecated("Use cout << program; instead. See also help().")]] std::string 1426 | print_help() const { 1427 | auto out = help(); 1428 | std::cout << out.rdbuf(); 1429 | return out.str(); 1430 | } 1431 | 1432 | void add_subparser(ArgumentParser &parser) { 1433 | parser.m_parser_path = m_program_name + " " + parser.m_program_name; 1434 | auto it = m_subparsers.emplace(std::cend(m_subparsers), parser); 1435 | m_subparser_map.insert_or_assign(parser.m_program_name, it); 1436 | m_subparser_used.insert_or_assign(parser.m_program_name, false); 1437 | } 1438 | 1439 | private: 1440 | bool is_valid_prefix_char(char c) const { 1441 | return m_prefix_chars.find(c) != std::string::npos; 1442 | } 1443 | 1444 | char get_any_valid_prefix_char() const { return m_prefix_chars[0]; } 1445 | 1446 | /* 1447 | * Pre-process this argument list. Anything starting with "--", that 1448 | * contains an =, where the prefix before the = has an entry in the 1449 | * options table, should be split. 1450 | */ 1451 | std::vector 1452 | preprocess_arguments(const std::vector &raw_arguments) const { 1453 | std::vector arguments{}; 1454 | for (const auto &arg : raw_arguments) { 1455 | 1456 | const auto argument_starts_with_prefix_chars = 1457 | [this](const std::string &a) -> bool { 1458 | if (!a.empty()) { 1459 | 1460 | const auto legal_prefix = [this](char c) -> bool { 1461 | return m_prefix_chars.find(c) != std::string::npos; 1462 | }; 1463 | 1464 | // Windows-style 1465 | // if '/' is a legal prefix char 1466 | // then allow single '/' followed by argument name, followed by an 1467 | // assign char, e.g., ':' e.g., 'test.exe /A:Foo' 1468 | const auto windows_style = legal_prefix('/'); 1469 | 1470 | if (windows_style) { 1471 | if (legal_prefix(a[0])) { 1472 | return true; 1473 | } 1474 | } else { 1475 | // Slash '/' is not a legal prefix char 1476 | // For all other characters, only support long arguments 1477 | // i.e., the argument must start with 2 prefix chars, e.g, 1478 | // '--foo' e,g, './test --foo=Bar -DARG=yes' 1479 | if (a.size() > 1) { 1480 | return (legal_prefix(a[0]) && legal_prefix(a[1])); 1481 | } 1482 | } 1483 | } 1484 | return false; 1485 | }; 1486 | 1487 | // Check that: 1488 | // - We don't have an argument named exactly this 1489 | // - The argument starts with a prefix char, e.g., "--" 1490 | // - The argument contains an assign char, e.g., "=" 1491 | auto assign_char_pos = arg.find_first_of(m_assign_chars); 1492 | 1493 | if (m_argument_map.find(arg) == m_argument_map.end() && 1494 | argument_starts_with_prefix_chars(arg) && 1495 | assign_char_pos != std::string::npos) { 1496 | // Get the name of the potential option, and check it exists 1497 | std::string opt_name = arg.substr(0, assign_char_pos); 1498 | if (m_argument_map.find(opt_name) != m_argument_map.end()) { 1499 | // This is the name of an option! Split it into two parts 1500 | arguments.push_back(std::move(opt_name)); 1501 | arguments.push_back(arg.substr(assign_char_pos + 1)); 1502 | continue; 1503 | } 1504 | } 1505 | // If we've fallen through to here, then it's a standard argument 1506 | arguments.push_back(arg); 1507 | } 1508 | return arguments; 1509 | } 1510 | 1511 | /* 1512 | * @throws std::runtime_error in case of any invalid argument 1513 | */ 1514 | void parse_args_internal(const std::vector &raw_arguments) { 1515 | auto arguments = preprocess_arguments(raw_arguments); 1516 | if (m_program_name.empty() && !arguments.empty()) { 1517 | m_program_name = arguments.front(); 1518 | } 1519 | auto end = std::end(arguments); 1520 | auto positional_argument_it = std::begin(m_positional_arguments); 1521 | for (auto it = std::next(std::begin(arguments)); it != end;) { 1522 | const auto ¤t_argument = *it; 1523 | if (Argument::is_positional(current_argument, m_prefix_chars)) { 1524 | if (positional_argument_it == std::end(m_positional_arguments)) { 1525 | 1526 | std::string_view maybe_command = current_argument; 1527 | 1528 | // Check sub-parsers 1529 | auto subparser_it = m_subparser_map.find(maybe_command); 1530 | if (subparser_it != m_subparser_map.end()) { 1531 | 1532 | // build list of remaining args 1533 | const auto unprocessed_arguments = 1534 | std::vector(it, end); 1535 | 1536 | // invoke subparser 1537 | m_is_parsed = true; 1538 | m_subparser_used[maybe_command] = true; 1539 | return subparser_it->second->get().parse_args( 1540 | unprocessed_arguments); 1541 | } 1542 | 1543 | throw std::runtime_error( 1544 | "Maximum number of positional arguments exceeded"); 1545 | } 1546 | auto argument = positional_argument_it++; 1547 | it = argument->consume(it, end); 1548 | continue; 1549 | } 1550 | 1551 | auto arg_map_it = m_argument_map.find(current_argument); 1552 | if (arg_map_it != m_argument_map.end()) { 1553 | auto argument = arg_map_it->second; 1554 | it = argument->consume(std::next(it), end, arg_map_it->first); 1555 | } else if (const auto &compound_arg = current_argument; 1556 | compound_arg.size() > 1 && 1557 | is_valid_prefix_char(compound_arg[0]) && 1558 | !is_valid_prefix_char(compound_arg[1])) { 1559 | ++it; 1560 | for (std::size_t j = 1; j < compound_arg.size(); j++) { 1561 | auto hypothetical_arg = std::string{'-', compound_arg[j]}; 1562 | auto arg_map_it2 = m_argument_map.find(hypothetical_arg); 1563 | if (arg_map_it2 != m_argument_map.end()) { 1564 | auto argument = arg_map_it2->second; 1565 | it = argument->consume(it, end, arg_map_it2->first); 1566 | } else { 1567 | throw std::runtime_error("Unknown argument: " + current_argument); 1568 | } 1569 | } 1570 | } else { 1571 | throw std::runtime_error("Unknown argument: " + current_argument); 1572 | } 1573 | } 1574 | m_is_parsed = true; 1575 | } 1576 | 1577 | /* 1578 | * Like parse_args_internal but collects unused args into a vector 1579 | */ 1580 | std::vector 1581 | parse_known_args_internal(const std::vector &raw_arguments) { 1582 | auto arguments = preprocess_arguments(raw_arguments); 1583 | 1584 | std::vector unknown_arguments{}; 1585 | 1586 | if (m_program_name.empty() && !arguments.empty()) { 1587 | m_program_name = arguments.front(); 1588 | } 1589 | auto end = std::end(arguments); 1590 | auto positional_argument_it = std::begin(m_positional_arguments); 1591 | for (auto it = std::next(std::begin(arguments)); it != end;) { 1592 | const auto ¤t_argument = *it; 1593 | if (Argument::is_positional(current_argument, m_prefix_chars)) { 1594 | if (positional_argument_it == std::end(m_positional_arguments)) { 1595 | 1596 | std::string_view maybe_command = current_argument; 1597 | 1598 | // Check sub-parsers 1599 | auto subparser_it = m_subparser_map.find(maybe_command); 1600 | if (subparser_it != m_subparser_map.end()) { 1601 | 1602 | // build list of remaining args 1603 | const auto unprocessed_arguments = 1604 | std::vector(it, end); 1605 | 1606 | // invoke subparser 1607 | m_is_parsed = true; 1608 | m_subparser_used[maybe_command] = true; 1609 | return subparser_it->second->get().parse_known_args_internal( 1610 | unprocessed_arguments); 1611 | } 1612 | 1613 | // save current argument as unknown and go to next argument 1614 | unknown_arguments.push_back(current_argument); 1615 | ++it; 1616 | } else { 1617 | // current argument is the value of a positional argument 1618 | // consume it 1619 | auto argument = positional_argument_it++; 1620 | it = argument->consume(it, end); 1621 | } 1622 | continue; 1623 | } 1624 | 1625 | auto arg_map_it = m_argument_map.find(current_argument); 1626 | if (arg_map_it != m_argument_map.end()) { 1627 | auto argument = arg_map_it->second; 1628 | it = argument->consume(std::next(it), end, arg_map_it->first); 1629 | } else if (const auto &compound_arg = current_argument; 1630 | compound_arg.size() > 1 && 1631 | is_valid_prefix_char(compound_arg[0]) && 1632 | !is_valid_prefix_char(compound_arg[1])) { 1633 | ++it; 1634 | for (std::size_t j = 1; j < compound_arg.size(); j++) { 1635 | auto hypothetical_arg = std::string{'-', compound_arg[j]}; 1636 | auto arg_map_it2 = m_argument_map.find(hypothetical_arg); 1637 | if (arg_map_it2 != m_argument_map.end()) { 1638 | auto argument = arg_map_it2->second; 1639 | it = argument->consume(it, end, arg_map_it2->first); 1640 | } else { 1641 | unknown_arguments.push_back(current_argument); 1642 | break; 1643 | } 1644 | } 1645 | } else { 1646 | // current argument is an optional-like argument that is unknown 1647 | // save it and move to next argument 1648 | unknown_arguments.push_back(current_argument); 1649 | ++it; 1650 | } 1651 | } 1652 | m_is_parsed = true; 1653 | return unknown_arguments; 1654 | } 1655 | 1656 | // Used by print_help. 1657 | std::size_t get_length_of_longest_argument() const { 1658 | if (m_argument_map.empty()) { 1659 | return 0; 1660 | } 1661 | std::size_t max_size = 0; 1662 | for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { 1663 | max_size = std::max(max_size, argument->get_arguments_length()); 1664 | } 1665 | for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) { 1666 | max_size = std::max(max_size, command.size()); 1667 | } 1668 | return max_size; 1669 | } 1670 | 1671 | using argument_it = std::list::iterator; 1672 | using argument_parser_it = 1673 | std::list>::iterator; 1674 | 1675 | void index_argument(argument_it it) { 1676 | for (const auto &name : std::as_const(it->m_names)) { 1677 | m_argument_map.insert_or_assign(name, it); 1678 | } 1679 | } 1680 | 1681 | std::string m_program_name; 1682 | std::string m_version; 1683 | std::string m_description; 1684 | std::string m_epilog; 1685 | bool m_exit_on_default_arguments = true; 1686 | std::string m_prefix_chars{"-"}; 1687 | std::string m_assign_chars{"="}; 1688 | bool m_is_parsed = false; 1689 | std::list m_positional_arguments; 1690 | std::list m_optional_arguments; 1691 | std::map m_argument_map; 1692 | std::string m_parser_path; 1693 | std::list> m_subparsers; 1694 | std::map m_subparser_map; 1695 | std::map m_subparser_used; 1696 | }; 1697 | 1698 | } // namespace argparse 1699 | -------------------------------------------------------------------------------- /src/config.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "config.hpp" 7 | #include "keys.hpp" 8 | #include "yaml.hpp" 9 | 10 | /* resplit() by https://stackoverflow.com/users/248823/marcin 11 | * https://stackoverflow.com/a/28142357 12 | * used under CC BY-SA 4.0 */ 13 | auto resplit(const std::string& s, const std::regex& sep) 14 | -> std::vector { 15 | std::sregex_token_iterator iter(s.begin(), s.end(), sep, -1); 16 | std::sregex_token_iterator end; 17 | return {iter, end}; 18 | } 19 | 20 | void to_lower(std::string& s) { 21 | std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { 22 | return std::tolower(c); 23 | }); 24 | } 25 | 26 | auto find(const std::multimap& m, std::string s) 27 | -> std::optional { 28 | to_lower(s); 29 | for (auto& [vkey, keystr] : m) { 30 | if (s == keystr) { 31 | return vkey; 32 | } 33 | } 34 | 35 | return {}; 36 | } 37 | 38 | auto parse_keybind(const std::string& s) -> std::optional> { 39 | if (s.empty()) { 40 | return {}; 41 | } 42 | 43 | int modifiers = 0; 44 | int keycode = 0; 45 | 46 | auto segs = resplit(s, std::regex("\\s*\\+\\s*")); 47 | 48 | for (auto& seg : segs) { 49 | auto modifier = find(modifier_keycode_to_str, seg); 50 | if (modifier.has_value()) { 51 | modifiers |= modifier.value(); 52 | continue; 53 | } 54 | 55 | auto key = find(keycode_to_str, seg); 56 | if (key.has_value()) { 57 | keycode = key.value(); 58 | continue; 59 | } 60 | } 61 | 62 | if (modifiers <= 0 or keycode <= 0) { 63 | return {}; 64 | } 65 | 66 | return std::make_pair(modifiers, keycode); 67 | } 68 | 69 | auto read_config(const std::filesystem::path& path) -> Config { 70 | Yaml::Node root; 71 | Yaml::Parse(root, std::filesystem::absolute(path).string().c_str()); 72 | 73 | Config conf; 74 | if (root.IsMap() && root["keyboard_shortcuts"].IsSequence()) { 75 | for (auto i = 0; i < root["keyboard_shortcuts"].Size(); i++) { 76 | auto& e = root["keyboard_shortcuts"][i]; 77 | auto s = e.As(); 78 | auto keybind = parse_keybind(s); 79 | if (keybind.has_value()) { 80 | conf.keyboard_shortcuts.emplace_back(keybind.value()); 81 | } 82 | } 83 | } 84 | 85 | return std::move(conf); 86 | } 87 | -------------------------------------------------------------------------------- /src/config.hpp: -------------------------------------------------------------------------------- 1 | #ifndef H4685028741 2 | #define H4685028741 3 | 4 | #include 5 | 6 | #include // must precede other windows headers 7 | 8 | #include "yaml.hpp" 9 | 10 | struct Config { 11 | std::vector> keyboard_shortcuts; 12 | }; 13 | 14 | auto parse_keybind(const std::string& s) -> std::optional>; 15 | auto read_config(const std::filesystem::path& path) -> Config; 16 | 17 | #endif // H4685028741 18 | -------------------------------------------------------------------------------- /src/keys.cpp: -------------------------------------------------------------------------------- 1 | #include "keys.hpp" 2 | -------------------------------------------------------------------------------- /src/keys.hpp: -------------------------------------------------------------------------------- 1 | #ifndef H6252715178 2 | #define H6252715178 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // windows.h must be before other windows headers 9 | #include 10 | 11 | #include 12 | 13 | // clang-format off 14 | // WARN be careful that your editor does not corrupt unicode characters! 15 | 16 | const std::multimap modifier_keycode_to_str = { 17 | { MOD_CONTROL, "^"}, 18 | // NOT a caret! 19 | { MOD_CONTROL, "⌃"}, 20 | { MOD_CONTROL, "ctrl"}, 21 | { MOD_CONTROL, "control"}, 22 | // alt on windows and linux, alt on mac 23 | { MOD_ALT, "⎇"}, 24 | // alt (?) on windows and linux, option on mac 25 | { MOD_ALT, "⌥"}, 26 | { MOD_ALT, "alt"}, 27 | { MOD_SHIFT, "⇧"}, 28 | { MOD_SHIFT, "shift"}, 29 | { MOD_WIN, "⊞"}, // windows key symbol 30 | { MOD_WIN, "⌘"}, // mac key symbol 31 | { MOD_WIN, "meta"}, 32 | { MOD_WIN, "logo"}, 33 | { MOD_WIN, "win"}, 34 | { MOD_WIN, "windows"}, 35 | { MOD_WIN, "mac"}, 36 | { MOD_WIN, "macos"}, 37 | { MOD_WIN, "osx"}, 38 | { MOD_WIN, "apple"}, 39 | }; 40 | 41 | const std::multimap keycode_to_str = { 42 | { VK_RETURN, "⌤"}, 43 | { VK_RETURN, "↩"}, 44 | { VK_RETURN, "⏎"}, 45 | { VK_RETURN, "enter"}, 46 | { VK_RETURN, "return"}, 47 | { VK_SHIFT, "⇧"}, 48 | { VK_SHIFT, "shift"}, 49 | { VK_BACK, "⌫"}, 50 | { VK_BACK, "bs"}, 51 | { VK_BACK, "back"}, 52 | { VK_BACK, "bspace"}, 53 | { VK_BACK, "backspace"}, 54 | { VK_BACK, "back-space"}, 55 | { VK_DELETE, "⌦"}, 56 | { VK_DELETE, "del"}, 57 | { VK_DELETE, "delete"}, 58 | { VK_DELETE, "fwddelete"}, 59 | { VK_DELETE, "fwd-delete"}, 60 | { VK_DELETE, "forwarddelete"}, 61 | { VK_DELETE, "forward-delete"}, 62 | { VK_PAUSE, "break"}, 63 | { VK_PAUSE, "pause"}, 64 | { VK_PAUSE, "breakpause"}, 65 | { VK_PAUSE, "pausebreak"}, 66 | { VK_PAUSE, "break-pause"}, 67 | { VK_PAUSE, "pause-break"}, 68 | { VK_CONTROL, "^"}, 69 | // NOT a caret! 70 | { VK_CONTROL, "⌃"}, 71 | { VK_CONTROL, "ctrl"}, 72 | { VK_CONTROL, "control"}, 73 | // alt on windows and linux, alt on mac 74 | { VK_MENU, "⎇"}, 75 | // alt (?) on windows and linux, option on mac 76 | { VK_MENU, "⌥"}, 77 | { VK_MENU, "alt"}, 78 | // also: tab left ⇤ 79 | { VK_TAB, "⇥"}, 80 | { VK_TAB, "tab"}, 81 | { VK_CAPITAL, "⇪"}, 82 | { VK_CAPITAL, "cap"}, 83 | { VK_CAPITAL, "capital"}, 84 | { VK_CAPITAL, "capitals"}, 85 | { VK_CAPITAL, "caps"}, 86 | { VK_CAPITAL, "caplock"}, 87 | { VK_CAPITAL, "capslock"}, 88 | { VK_CAPITAL, "cap-lock"}, 89 | { VK_CAPITAL, "caps-lock"}, 90 | { VK_ESCAPE, "⎋"}, 91 | { VK_ESCAPE, "esc"}, 92 | { VK_ESCAPE, "escape"}, 93 | { VK_SPACE, "␣"}, 94 | { VK_SPACE, "space"}, 95 | { VK_SPACE, "spacebar"}, 96 | { VK_SPACE, "space-bar"}, 97 | { VK_PRIOR, "⇞"}, 98 | { VK_PRIOR, "pgup"}, 99 | { VK_PRIOR, "pg-up"}, 100 | { VK_PRIOR, "pageup"}, 101 | { VK_PRIOR, "page-up"}, 102 | { VK_NEXT, "⇟"}, 103 | { VK_NEXT, "pgdn"}, 104 | { VK_NEXT, "pg-dn"}, 105 | { VK_NEXT, "pgdown"}, 106 | { VK_NEXT, "pg-down"}, 107 | { VK_NEXT, "pagedn"}, 108 | { VK_NEXT, "page-dn"}, 109 | { VK_NEXT, "pagedown"}, 110 | { VK_NEXT, "page-down"}, 111 | { VK_HOME, "↖"}, 112 | { VK_HOME, "home"}, 113 | { VK_END, "↘"}, 114 | { VK_END, "end"}, 115 | { VK_LEFT, "←"}, 116 | { VK_LEFT, "left"}, 117 | { VK_LEFT, "left-arrow"}, 118 | { VK_LEFT, "left-direction"}, 119 | { VK_LEFT, "left-direction-arrow"}, 120 | { VK_RIGHT, "→"}, 121 | { VK_RIGHT, "right"}, 122 | { VK_RIGHT, "right-arrow"}, 123 | { VK_RIGHT, "right-direction"}, 124 | { VK_RIGHT, "right-direction-arrow"}, 125 | { VK_UP, "↑"}, 126 | { VK_UP, "up"}, 127 | { VK_UP, "up-arrow"}, 128 | { VK_UP, "up-direction"}, 129 | { VK_UP, "up-direction-arrow"}, 130 | { VK_DOWN, "↓"}, 131 | { VK_DOWN, "down"}, 132 | { VK_DOWN, "down-arrow"}, 133 | { VK_DOWN, "down-direction"}, 134 | { VK_DOWN, "down-direction-arrow"}, 135 | { VK_OEM_MINUS, "minus"}, 136 | { VK_OEM_MINUS, "subtract"}, 137 | { VK_OEM_MINUS, "hyphen"}, 138 | { VK_OEM_MINUS, "dash"}, 139 | { VK_OEM_MINUS, "endash"}, 140 | { VK_OEM_MINUS, "en-dash"}, 141 | { VK_OEM_COMMA, "comma"}, 142 | {VK_OEM_PERIOD, "period"}, 143 | {VK_OEM_PERIOD, "dot"}, 144 | { VK_OEM_2, "slash"}, 145 | { VK_OEM_2, "forwardslash"}, 146 | { VK_OEM_2, "forward-slash"}, 147 | { VK_OEM_1, "semicolon"}, 148 | { VK_OEM_1, "semi-colon"}, 149 | { VK_OEM_PLUS, "equal"}, 150 | { VK_OEM_PLUS, "equals"}, 151 | { VK_OEM_5, "bslash"}, 152 | { VK_OEM_5, "backslash"}, 153 | { VK_OEM_5, "back-slash"}, 154 | { VK_OEM_4, "lbracket"}, 155 | { VK_OEM_4, "leftbracket"}, 156 | { VK_OEM_4, "left-bracket"}, 157 | { VK_OEM_6, "rbracket"}, 158 | { VK_OEM_6, "rightbracket"}, 159 | { VK_OEM_6, "right-bracket"}, 160 | { VK_CLEAR, "⌧"}, 161 | { VK_CLEAR, "clear"}, 162 | { VK_NUMPAD0, "num0"}, 163 | { VK_NUMPAD1, "num1"}, 164 | { VK_NUMPAD2, "num2"}, 165 | { VK_NUMPAD3, "num3"}, 166 | { VK_NUMPAD4, "num4"}, 167 | { VK_NUMPAD5, "num5"}, 168 | { VK_NUMPAD6, "num6"}, 169 | { VK_NUMPAD7, "num7"}, 170 | { VK_NUMPAD8, "num8"}, 171 | { VK_NUMPAD9, "num9"}, 172 | { VK_NUMLOCK, "⇭"}, 173 | { VK_NUMLOCK, "numlock"}, 174 | { VK_NUMLOCK, "num-lock"}, 175 | { VK_SCROLL, "scrolllock"}, 176 | { VK_SCROLL, "scroll-lock"}, 177 | { VK_PRINT, "print"}, 178 | { VK_PRINT, "printscreen"}, 179 | { VK_PRINT, "print-screen"}, 180 | { VK_INSERT, "insert"}, 181 | { VK_INSERT, "ins"}, 182 | { VK_LWIN, "⊞"}, // windows key symbol 183 | { VK_LWIN, "⌘"}, // mac key symbol 184 | { VK_LWIN, "meta"}, 185 | { VK_LWIN, "logo"}, 186 | { VK_LWIN, "win"}, 187 | { VK_LWIN, "windows"}, 188 | { VK_LWIN, "mac"}, 189 | { VK_LWIN, "macos"}, 190 | { VK_LWIN, "osx"}, 191 | { VK_LWIN, "apple"}, 192 | { VK_OEM_3, "backquote"}, 193 | { VK_OEM_3, "back-quote"}, 194 | { VK_OEM_7, "quote"}, 195 | { 0x30, "0"}, 196 | { 0x31, "1"}, 197 | { 0x32, "2"}, 198 | { 0x33, "3"}, 199 | { 0x34, "4"}, 200 | { 0x35, "5"}, 201 | { 0x36, "6"}, 202 | { 0x37, "7"}, 203 | { 0x38, "8"}, 204 | { 0x39, "9"}, 205 | { 0x41, "a"}, 206 | { 0x42, "b"}, 207 | { 0x43, "c"}, 208 | { 0x44, "d"}, 209 | { 0x45, "e"}, 210 | { 0x46, "f"}, 211 | { 0x47, "g"}, 212 | { 0x48, "h"}, 213 | { 0x49, "i"}, 214 | { 0x4a, "j"}, 215 | { 0x4b, "k"}, 216 | { 0x4c, "l"}, 217 | { 0x4d, "m"}, 218 | { 0x4e, "n"}, 219 | { 0x4f, "o"}, 220 | { 0x50, "p"}, 221 | { 0x51, "q"}, 222 | { 0x52, "r"}, 223 | { 0x53, "s"}, 224 | { 0x54, "t"}, 225 | { 0x55, "u"}, 226 | { 0x56, "v"}, 227 | { 0x57, "w"}, 228 | { 0x58, "x"}, 229 | { 0x59, "y"}, 230 | { 0x5a, "z"}, 231 | }; 232 | // clang-format on 233 | 234 | #endif // H6252715178 235 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // windows.h must come first 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "argparse.hpp" 21 | #include "config.hpp" 22 | #include "keys.hpp" 23 | #include "main.hpp" 24 | #include "pipe.hpp" 25 | #include "squatter.hpp" 26 | #include "wincon.hpp" 27 | #include "yaml.hpp" 28 | 29 | const std::string program_name = "hyperenable"; 30 | const std::string program_version = "0.1.3"; 31 | 32 | const LPCSTR window_taskbar = "Shell_TrayWnd"; 33 | 34 | // class that designates the desktop. this is the borderless window 35 | // that shows your recycling bin and desktop icons 36 | const LPCSTR window_desktop = "Progman"; 37 | 38 | const std::string action_start = "start"; 39 | const std::string action_stop = "stop"; 40 | 41 | constexpr int timeout_ms = 15000; 42 | constexpr int bufsize = 1024; 43 | constexpr int interval_ms = 100; 44 | constexpr int delay_ms = 0; 45 | 46 | constexpr int exit_ok = 0; 47 | constexpr int exit_timeout = 1; 48 | constexpr int exit_explorer_exists = 2; 49 | constexpr int exit_file_not_found = 3; 50 | constexpr int exit_connect_fail = 4; 51 | constexpr int exit_argparse = 5; 52 | 53 | auto is_window_active(const LPCSTR win, const bool req_text) -> bool { 54 | auto* explorer = FindWindow(win, NULL); 55 | if (explorer == NULL) { 56 | return false; 57 | } 58 | 59 | if (req_text) { 60 | std::size_t read_len = 0; 61 | std::array buf{}; 62 | read_len = GetWindowText(explorer, buf.data(), buf.size()); 63 | if (read_len <= 0) { 64 | return false; 65 | } 66 | } 67 | 68 | return true; 69 | } 70 | 71 | auto wait_until_window(const LPCSTR win, const int timeout_ms, const bool text) 72 | -> int { 73 | int waited_ms = 0; 74 | HWND explorer = NULL; 75 | 76 | do { 77 | explorer = FindWindow(win, NULL); 78 | if (explorer == NULL) { 79 | auto wait_ms = min(interval_ms, timeout_ms - waited_ms); 80 | Sleep(max(0, wait_ms)); 81 | waited_ms += wait_ms; 82 | } 83 | } while (explorer == NULL && (timeout_ms < 0 || waited_ms < timeout_ms)); 84 | 85 | if (explorer == NULL) { 86 | return -1; 87 | } 88 | 89 | if (text) { 90 | std::size_t read_len = 0; 91 | 92 | do { 93 | std::array buf{}; 94 | read_len = GetWindowText(explorer, buf.data(), buf.size()); 95 | 96 | if (read_len <= 0) { 97 | Sleep(max(0, min(interval_ms, timeout_ms - waited_ms))); 98 | waited_ms += interval_ms; 99 | } 100 | } while (read_len <= 0 && (timeout_ms < 0 || waited_ms < timeout_ms)); 101 | 102 | if (read_len <= 0) { 103 | return -1; 104 | } 105 | } 106 | 107 | return min((std::numeric_limits::max)(), waited_ms); 108 | } 109 | 110 | /* wtos() is from https://stackoverflow.com/a/67134110 111 | * https://stackoverflow.com/users/870239/liang-august-yuning 112 | * CC BY-SA 4.0 113 | */ 114 | std::wstring wtos(const std::string& value) { 115 | const size_t cSize = value.size() + 1; 116 | 117 | std::wstring wc; 118 | wc.resize(cSize); 119 | 120 | size_t cSize1; 121 | mbstowcs_s(&cSize1, (wchar_t*)&wc[0], cSize, value.c_str(), cSize); 122 | 123 | wc.pop_back(); 124 | 125 | return wc; 126 | } 127 | 128 | auto main(int argc, char* argv[]) -> int { 129 | argparse::ArgumentParser args_start(action_start); 130 | args_start.add_description("Block keybind registration attempts"); 131 | args_start.add_argument("-t", "--timeout") 132 | .help("max milliseconds to wait for explorer.exe to load. " 133 | "negative for infinite") 134 | .default_value(timeout_ms) 135 | .scan<'i', int>() 136 | .metavar("INT"); 137 | args_start.add_argument("-c", "--config") 138 | .help("read settings from file") 139 | .metavar("PATH"); 140 | args_start.add_argument("-r", "--run") 141 | .help("run a program afterwards. can " 142 | "be used to run a hotkey setup app") 143 | .metavar("PATH"); 144 | args_start.add_epilog("Keybind registration attempts will be " 145 | "blocked until terminated, timeout elapses, " 146 | "or stop signal is received, whichever happens " 147 | "first."); 148 | 149 | argparse::ArgumentParser args_stop(action_stop); 150 | args_stop.add_description("Unblock keybind registration attempts"); 151 | args_stop.add_epilog( 152 | "A signal will be sent to a running " 153 | "instance of " + 154 | program_name + 155 | " to stop " 156 | "blocking keybinds."); 157 | 158 | argparse::ArgumentParser args(program_name, program_version); 159 | args.add_subparser(args_start); 160 | args.add_subparser(args_stop); 161 | 162 | try { 163 | args.parse_args(argc, argv); 164 | } catch (const std::runtime_error& err) { 165 | std::cerr << err.what() << std::endl; 166 | return exit_argparse; 167 | } 168 | 169 | if (args.is_subcommand_used(args_start)) { 170 | if (is_window_active(window_desktop)) { 171 | std::cerr << "Can only block before explorer.exe " 172 | "is active." 173 | << std::endl; 174 | return exit_explorer_exists; 175 | } 176 | 177 | auto server = Listener::create(); 178 | if (!server) { 179 | std::cout << "Another instance is already blocking. " 180 | << "No need to block again." << std::endl; 181 | return exit_ok; 182 | } 183 | 184 | Config config; 185 | if (args_start.present("--config")) { 186 | std::filesystem::path path = args_start.get("--config"); 187 | if (!std::filesystem::exists(path)) { 188 | std::cerr << "File not found at \"" << path << "\"" 189 | << std::endl; 190 | return exit_file_not_found; 191 | } 192 | config = read_config(path); 193 | } 194 | 195 | if (config.keyboard_shortcuts.empty()) { 196 | config.keyboard_shortcuts.push_back( 197 | parse_keybind("ctrl+alt+shift+win+w").value()); 198 | config.keyboard_shortcuts.push_back( 199 | parse_keybind("ctrl+alt+shift+win+t").value()); 200 | config.keyboard_shortcuts.push_back( 201 | parse_keybind("ctrl+alt+shift+win+y").value()); 202 | config.keyboard_shortcuts.push_back( 203 | parse_keybind("ctrl+alt+shift+win+o").value()); 204 | config.keyboard_shortcuts.push_back( 205 | parse_keybind("ctrl+alt+shift+win+p").value()); 206 | config.keyboard_shortcuts.push_back( 207 | parse_keybind("ctrl+alt+shift+win+d").value()); 208 | config.keyboard_shortcuts.push_back( 209 | parse_keybind("ctrl+alt+shift+win+l").value()); 210 | config.keyboard_shortcuts.push_back( 211 | parse_keybind("ctrl+alt+shift+win+x").value()); 212 | config.keyboard_shortcuts.push_back( 213 | parse_keybind("ctrl+alt+shift+win+n").value()); 214 | } 215 | 216 | std::cout << "Blocking keybinds." << std::endl; 217 | auto blocker = Squatter::block(config.keyboard_shortcuts); 218 | 219 | auto timeout_ms = args_start.get("--timeout"); 220 | 221 | std::cout << "Waiting for explorer.exe" << std::endl; 222 | auto waited_ms = wait_until_window(window_desktop, timeout_ms); 223 | if (waited_ms < 0) { 224 | std::cerr << "Timed out while waiting for explorer.exe." 225 | << std::endl; 226 | return exit_timeout; 227 | } 228 | 229 | std::cout << "Waiting until release signal or timeout." << std::endl; 230 | while (waited_ms < timeout_ms && !server->poll()) { 231 | Sleep(max(0, min(timeout_ms - waited_ms, interval_ms))); 232 | waited_ms += interval_ms; 233 | } 234 | 235 | std::cout << "Unblocking keybinds." << std::endl; 236 | blocker.unblock(); 237 | 238 | if (args_start.present("--run")) { 239 | std::filesystem::path path = args_start.get("--run"); 240 | if (!std::filesystem::exists(path)) { 241 | std::cerr << "File not found at \"" << path << "\"" 242 | << std::endl; 243 | return exit_file_not_found; 244 | } 245 | 246 | std::cout << "Running program at " << path << std::endl; 247 | 248 | // always init COM before using ShellExecute() 249 | // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea#remarks 250 | CoInitializeEx( 251 | NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); 252 | auto status = ShellExecuteW( 253 | NULL, wtos("Open").c_str(), path.c_str(), NULL, NULL, SW_HIDE); 254 | } 255 | 256 | return exit_ok; 257 | } else if (args.is_subcommand_used(args_stop)) { 258 | auto client = Client(); 259 | if (!client.init()) { 260 | std::cout << "Not blocking; no need to release." << std::endl; 261 | return exit_ok; 262 | } 263 | 264 | if (!client.notify()) { 265 | std::cerr << "Failed to release keybinds. " 266 | "(Pipe communication failed.)" 267 | << std::endl; 268 | return exit_connect_fail; 269 | } 270 | 271 | return exit_ok; 272 | } else { 273 | std::cerr << "Unrecognized subcommand." << std::endl; 274 | return exit_argparse; 275 | } 276 | } 277 | 278 | _Use_decl_annotations_ auto WINAPI WinMain( 279 | HINSTANCE /*unused*/, HINSTANCE /*unused*/, PSTR /*unused*/, INT /*unused*/) 280 | -> INT { 281 | WinConsole console = WinConsole::attach(); 282 | 283 | // https://learn.microsoft.com/en-us/cpp/c-runtime-library/argc-argv-wargv 284 | return main(__argc, __argv); 285 | } 286 | -------------------------------------------------------------------------------- /src/main.hpp: -------------------------------------------------------------------------------- 1 | #ifndef H9138789750 2 | #define H9138789750 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // windows.h must come first 9 | #include 10 | 11 | auto is_window_active(const LPCSTR win, const bool req_text = true) -> bool; 12 | auto wait_until_window( 13 | const LPCSTR win, const int timeout_ms, const bool text = true) -> int; 14 | std::wstring wtos(const std::string& value); 15 | 16 | #endif // H9138789750 17 | -------------------------------------------------------------------------------- /src/pipe.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // windows.h must come before other windows headers 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "pipe.hpp" 13 | 14 | constexpr LPCSTR pipe_name = R"(\\.\pipe\e3f5a607-ec52-4841-886d-9502cf837302)"; 15 | constexpr char cmd_stop = 0x01; // arbitrary value 16 | 17 | auto Listener::create() -> std::optional { 18 | auto server = std::make_shared(); 19 | if (!server->init()) { 20 | return {}; 21 | } 22 | 23 | std::optional listener = Listener(); 24 | auto received = listener->received; 25 | 26 | listener->thread = std::make_shared( 27 | // NOLINTNEXTLINE(performance-unnecessary-value-param) 28 | std::jthread([server, received](const std::stop_token stop) { 29 | while (!stop.stop_requested()) { 30 | if (server->poll()) { 31 | received->store(true); 32 | break; 33 | } 34 | } 35 | })); 36 | 37 | return listener; 38 | } 39 | 40 | auto Listener::poll() -> bool { 41 | return received->load(); 42 | } 43 | 44 | auto Server::init() -> bool { 45 | if (pipe != INVALID_HANDLE_VALUE) { 46 | return true; 47 | } 48 | 49 | HANDLE existing = CreateFileA( 50 | pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 51 | NULL); 52 | if (existing != INVALID_HANDLE_VALUE) { 53 | CloseHandle(pipe); 54 | existing = INVALID_HANDLE_VALUE; 55 | return false; 56 | } 57 | 58 | pipe = CreateNamedPipeA( 59 | pipe_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, 60 | PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | 61 | PIPE_REJECT_REMOTE_CLIENTS, 62 | 1, bufsize, bufsize, NMPWAIT_USE_DEFAULT_WAIT, NULL); 63 | 64 | return pipe != INVALID_HANDLE_VALUE; 65 | }; 66 | 67 | void Server::deinit() { 68 | if (pipe != INVALID_HANDLE_VALUE) { 69 | CloseHandle(pipe); 70 | pipe = INVALID_HANDLE_VALUE; 71 | } 72 | } 73 | 74 | auto Server::poll() -> bool { 75 | if (pipe == INVALID_HANDLE_VALUE) { 76 | return false; 77 | } 78 | 79 | return read_into_buffer(); 80 | } 81 | 82 | auto Server::read_into_buffer() -> bool { 83 | buffer[0] = '\0'; 84 | buffer[bufsize] = '\0'; 85 | DWORD bytes_read = 0; 86 | BOOL status = FALSE; 87 | 88 | if (ConnectNamedPipe(pipe, NULL) != FALSE) { 89 | status = 90 | ReadFile(pipe, buffer.data(), buffer.size() - 1, &bytes_read, NULL); 91 | } 92 | 93 | DisconnectNamedPipe(pipe); 94 | return status != FALSE && bytes_read > 0; 95 | } 96 | 97 | auto Client::init() -> bool { 98 | if (pipe != INVALID_HANDLE_VALUE) { 99 | return true; 100 | } 101 | 102 | pipe = CreateFileA( 103 | pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 104 | NULL); 105 | 106 | return pipe != INVALID_HANDLE_VALUE; 107 | } 108 | 109 | void Client::deinit() { 110 | if (pipe != INVALID_HANDLE_VALUE) { 111 | CloseHandle(pipe); 112 | pipe = INVALID_HANDLE_VALUE; 113 | } 114 | } 115 | 116 | auto Client::notify() -> bool { 117 | if (pipe == INVALID_HANDLE_VALUE) { 118 | return false; 119 | } 120 | 121 | DWORD bytes_written = 0; 122 | std::array buf = {}; 123 | buf[bufsize] = '\0'; 124 | 125 | buf[0] = cmd_stop; 126 | buf[1] = '\0'; 127 | 128 | WriteFile(pipe, buf.data(), std::strlen(buf.data()), &bytes_written, NULL); 129 | 130 | return bytes_written > 0; 131 | } 132 | -------------------------------------------------------------------------------- /src/pipe.hpp: -------------------------------------------------------------------------------- 1 | #ifndef H6193606644 2 | #define H6193606644 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // windows.h must come before other windows headers 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | class Listener { 15 | public: 16 | static auto create() -> std::optional; 17 | 18 | Listener(const Listener& other) = delete; 19 | Listener(Listener&& other) noexcept 20 | : thread(std::move(other.thread)), received(std::move(other.received)) { 21 | other.thread = nullptr; 22 | other.received = nullptr; 23 | }; 24 | 25 | ~Listener() { 26 | if (thread) { 27 | thread->request_stop(); 28 | thread = nullptr; 29 | } 30 | } 31 | 32 | auto operator=(const Listener& other) -> Listener& = delete; 33 | auto operator=(Listener&& other) noexcept -> Listener& { 34 | thread = other.thread; 35 | received = other.received; 36 | other.thread = nullptr; 37 | other.received = nullptr; 38 | return *this; 39 | }; 40 | 41 | auto poll() -> bool; 42 | 43 | private: 44 | Listener() = default; 45 | 46 | std::shared_ptr thread = nullptr; 47 | std::shared_ptr received = 48 | std::make_shared(false); 49 | }; 50 | 51 | class Server { 52 | public: 53 | Server() = default; 54 | 55 | Server(const Server& other) = delete; 56 | Server(Server&& other) noexcept : pipe(other.pipe) { 57 | other.pipe = INVALID_HANDLE_VALUE; 58 | }; 59 | 60 | auto operator=(const Server& other) -> Server& = delete; 61 | auto operator=(Server&& other) noexcept -> Server& { 62 | pipe = other.pipe; 63 | other.pipe = INVALID_HANDLE_VALUE; 64 | return *this; 65 | }; 66 | 67 | ~Server() { deinit(); } 68 | 69 | auto init() -> bool; 70 | void deinit(); 71 | 72 | auto poll() -> bool; 73 | 74 | private: 75 | static const DWORD bufsize = 1024; 76 | static const int interval_ms = 100; 77 | 78 | auto read_into_buffer() -> bool; 79 | 80 | HANDLE pipe = INVALID_HANDLE_VALUE; 81 | std::array buffer = {}; 82 | }; 83 | 84 | class Client { 85 | public: 86 | Client() = default; 87 | 88 | Client(const Client& other) = delete; 89 | Client(Client&& other) noexcept : pipe(other.pipe) { 90 | other.pipe = INVALID_HANDLE_VALUE; 91 | } 92 | 93 | auto operator=(const Client& other) -> Client& = delete; 94 | auto operator=(Client&& other) noexcept -> Client& { 95 | pipe = other.pipe; 96 | other.pipe = INVALID_HANDLE_VALUE; 97 | return *this; 98 | } 99 | 100 | ~Client() { deinit(); } 101 | 102 | auto init() -> bool; 103 | void deinit(); 104 | 105 | auto notify() -> bool; 106 | 107 | private: 108 | static const DWORD bufsize = 1024; 109 | 110 | HANDLE pipe = INVALID_HANDLE_VALUE; 111 | }; 112 | 113 | #endif // H6193606644 114 | -------------------------------------------------------------------------------- /src/squatter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // windows.h must be before other windows headers 5 | #include 6 | 7 | #include "squatter.hpp" 8 | 9 | Squatter::Squatter(const std::vector>& keys) { 10 | for (auto& [mods, key] : keys) { 11 | block(mods, key); 12 | } 13 | } 14 | 15 | Squatter::~Squatter() { 16 | unblock(); 17 | } 18 | 19 | void Squatter::unblock() { 20 | while (!hotkeys.empty()) { 21 | auto hotkey_id = hotkeys.front(); 22 | hotkeys.pop_front(); 23 | UnregisterHotKey(NULL, hotkey_id); 24 | } 25 | } 26 | 27 | void Squatter::block(const int modifiers, const int key) { 28 | int hotkey_id = min((std::numeric_limits::max)(), hotkeys.size()); 29 | hotkeys.emplace_back(hotkey_id); 30 | RegisterHotKey(NULL, hotkey_id, modifiers | MOD_NOREPEAT, key); 31 | } 32 | -------------------------------------------------------------------------------- /src/squatter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef H3998501979 2 | #define H3998501979 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // windows.h must come before other windows headers 10 | #include 11 | 12 | class Squatter { 13 | public: 14 | Squatter(const Squatter& other) = delete; 15 | Squatter(Squatter&& other) noexcept { 16 | hotkeys = std::move(other.hotkeys); 17 | } 18 | 19 | ~Squatter(); 20 | 21 | auto operator=(const Squatter& other) -> Squatter& = delete; 22 | auto operator=(Squatter&& other) noexcept -> Squatter& { 23 | hotkeys = std::move(other.hotkeys); 24 | return *this; 25 | } 26 | 27 | static auto block(const std::vector>& keys) 28 | -> Squatter { 29 | Squatter squatter(keys); 30 | return std::move(squatter); 31 | }; 32 | 33 | auto unblock() -> void; 34 | 35 | private: 36 | Squatter(const std::vector>& keys); 37 | 38 | void block(const int modifiers, const int key); 39 | 40 | std::deque hotkeys; 41 | }; 42 | 43 | #endif // H3998501979 44 | -------------------------------------------------------------------------------- /src/wincon.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // windows.h must come before other windows headers 7 | #include 8 | 9 | #include "wincon.hpp" 10 | 11 | void WinConsole::attach_console() { 12 | if (this->attached) { 13 | throw std::runtime_error("console is already attached"); 14 | } 15 | 16 | // https://stackoverflow.com/a/26087606 17 | this->attached = static_cast(AttachConsole(ATTACH_PARENT_PROCESS)); 18 | if (this->attached) { 19 | assert(this->stdin_ == nullptr); 20 | assert(this->stdout_ == nullptr); 21 | assert(this->stderr_ == nullptr); 22 | 23 | _wfreopen_s(&this->stdin_, L"CONIN$", L"r", stdin); 24 | _wfreopen_s(&this->stdout_, L"CONOUT$", L"w", stdout); 25 | _wfreopen_s(&this->stderr_, L"CONOUT$", L"w", stderr); 26 | } 27 | } 28 | 29 | void WinConsole::detach_console() { 30 | // disabled cppcoreguidelines-owning-memory to avoid 31 | // depending on gsl lib. keeps this module drop in-able 32 | 33 | if (this->stdin_ != nullptr) { 34 | fflush(this->stdin_); 35 | // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) 36 | fclose(this->stdin_); 37 | this->stdin_ = nullptr; 38 | } 39 | 40 | if (this->stdout_ != nullptr) { 41 | fflush(this->stdout_); 42 | // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) 43 | fclose(this->stdout_); 44 | this->stdout_ = nullptr; 45 | } 46 | 47 | if (this->stderr_ != nullptr) { 48 | fflush(this->stderr_); 49 | // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) 50 | fclose(this->stderr_); 51 | this->stderr_ = nullptr; 52 | } 53 | 54 | if (static_cast(this->attached)) { 55 | FreeConsole(); 56 | this->attached = false; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/wincon.hpp: -------------------------------------------------------------------------------- 1 | #ifndef H5412315995 2 | #define H5412315995 3 | 4 | #include 5 | 6 | class WinConsole { 7 | public: 8 | WinConsole(WinConsole&& old) noexcept 9 | : attached(old.attached), stdin_(old.stdin_), stdout_(old.stdout_), 10 | stderr_(old.stderr_) { 11 | old.stdin_ = nullptr; 12 | old.stdout_ = nullptr; 13 | old.stderr_ = nullptr; 14 | old.attached = false; 15 | } 16 | 17 | WinConsole(const WinConsole&) = delete; 18 | ~WinConsole() { this->detach_console(); } 19 | 20 | auto operator=(const WinConsole&) = delete; 21 | auto operator=(WinConsole&) -> WinConsole& = delete; 22 | auto operator=(WinConsole&&) -> WinConsole& = delete; 23 | 24 | // static "constructor" to make it more obvious this is RAII 25 | [[nodiscard]] [[maybe_unused]] static auto attach() -> WinConsole { 26 | return {}; 27 | } 28 | 29 | private: 30 | bool attached = false; 31 | 32 | FILE* stdin_ = nullptr; 33 | FILE* stdout_ = nullptr; 34 | FILE* stderr_ = nullptr; 35 | 36 | [[nodiscard]] [[maybe_unused]] WinConsole() { this->attach_console(); } 37 | 38 | void attach_console(); 39 | void detach_console(); 40 | }; 41 | 42 | #endif // H5412315995 43 | -------------------------------------------------------------------------------- /src/yaml.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright(c) 2018 Jimmie Bergmann 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files(the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions : 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | /* 27 | YAML documentation: 28 | http://yaml.org/spec/1.0/index.html 29 | https://www.codeproject.com/Articles/28720/YAML-Parser-in-C 30 | */ 31 | 32 | #pragma once 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | /** 42 | * @breif Namespace wrapping mini-yaml classes. 43 | * 44 | */ 45 | namespace Yaml 46 | { 47 | 48 | /** 49 | * @breif Forward declarations. 50 | * 51 | */ 52 | class Node; 53 | 54 | 55 | /** 56 | * @breif Helper classes and functions 57 | * 58 | */ 59 | namespace impl 60 | { 61 | 62 | /** 63 | * @breif Helper functionality, converting string to any data type. 64 | * Strings are left untouched. 65 | * 66 | */ 67 | template 68 | struct StringConverter 69 | { 70 | static T Get(const std::string & data) 71 | { 72 | T type; 73 | std::stringstream ss(data); 74 | ss >> type; 75 | return type; 76 | } 77 | 78 | static T Get(const std::string & data, const T & defaultValue) 79 | { 80 | T type; 81 | std::stringstream ss(data); 82 | ss >> type; 83 | 84 | if(ss.fail()) 85 | { 86 | return defaultValue; 87 | } 88 | 89 | return type; 90 | } 91 | }; 92 | template<> 93 | struct StringConverter 94 | { 95 | static std::string Get(const std::string & data) 96 | { 97 | return data; 98 | } 99 | 100 | static std::string Get(const std::string & data, const std::string & defaultValue) 101 | { 102 | if(data.size() == 0) 103 | { 104 | return defaultValue; 105 | } 106 | return data; 107 | } 108 | }; 109 | 110 | template<> 111 | struct StringConverter 112 | { 113 | static bool Get(const std::string & data) 114 | { 115 | std::string tmpData = data; 116 | std::transform(tmpData.begin(), tmpData.end(), tmpData.begin(), ::tolower); 117 | if(tmpData == "true" || tmpData == "yes" || tmpData == "1") 118 | { 119 | return true; 120 | } 121 | 122 | return false; 123 | } 124 | 125 | static bool Get(const std::string & data, const bool & defaultValue) 126 | { 127 | if(data.size() == 0) 128 | { 129 | return defaultValue; 130 | } 131 | 132 | return Get(data); 133 | } 134 | }; 135 | 136 | } 137 | 138 | 139 | /** 140 | * @breif Exception class. 141 | * 142 | */ 143 | class Exception : public std::runtime_error 144 | { 145 | 146 | public: 147 | 148 | /** 149 | * @breif Enumeration of exception types. 150 | * 151 | */ 152 | enum eType 153 | { 154 | InternalError, ///< Internal error. 155 | ParsingError, ///< Invalid parsing data. 156 | OperationError ///< User operation error. 157 | }; 158 | 159 | /** 160 | * @breif Constructor. 161 | * 162 | * @param message Exception message. 163 | * @param type Type of exception. 164 | * 165 | */ 166 | Exception(const std::string & message, const eType type); 167 | 168 | /** 169 | * @breif Get type of exception. 170 | * 171 | */ 172 | eType Type() const; 173 | 174 | /** 175 | * @breif Get message of exception. 176 | * 177 | */ 178 | const char * Message() const; 179 | 180 | private: 181 | 182 | eType m_Type; ///< Type of exception. 183 | 184 | }; 185 | 186 | 187 | /** 188 | * @breif Internal exception class. 189 | * 190 | * @see Exception 191 | * 192 | */ 193 | class InternalException : public Exception 194 | { 195 | 196 | public: 197 | 198 | /** 199 | * @breif Constructor. 200 | * 201 | * @param message Exception message. 202 | * 203 | */ 204 | InternalException(const std::string & message); 205 | 206 | }; 207 | 208 | 209 | /** 210 | * @breif Parsing exception class. 211 | * 212 | * @see Exception 213 | * 214 | */ 215 | class ParsingException : public Exception 216 | { 217 | 218 | public: 219 | 220 | /** 221 | * @breif Constructor. 222 | * 223 | * @param message Exception message. 224 | * 225 | */ 226 | ParsingException(const std::string & message); 227 | 228 | }; 229 | 230 | 231 | /** 232 | * @breif Operation exception class. 233 | * 234 | * @see Exception 235 | * 236 | */ 237 | class OperationException : public Exception 238 | { 239 | 240 | public: 241 | 242 | /** 243 | * @breif Constructor. 244 | * 245 | * @param message Exception message. 246 | * 247 | */ 248 | OperationException(const std::string & message); 249 | 250 | }; 251 | 252 | 253 | /** 254 | * @breif Iterator class. 255 | * 256 | */ 257 | class Iterator 258 | { 259 | 260 | public: 261 | 262 | friend class Node; 263 | 264 | /** 265 | * @breif Default constructor. 266 | * 267 | */ 268 | Iterator(); 269 | 270 | /** 271 | * @breif Copy constructor. 272 | * 273 | */ 274 | Iterator(const Iterator & it); 275 | 276 | /** 277 | * @breif Assignment operator. 278 | * 279 | */ 280 | Iterator & operator = (const Iterator & it); 281 | 282 | /** 283 | * @breif Destructor. 284 | * 285 | */ 286 | ~Iterator(); 287 | 288 | /** 289 | * @breif Get node of iterator. 290 | * First pair item is the key of map value, empty if type is sequence. 291 | * 292 | */ 293 | std::pair operator *(); 294 | 295 | /** 296 | * @breif Post-increment operator. 297 | * 298 | */ 299 | Iterator & operator ++ (int); 300 | 301 | /** 302 | * @breif Post-decrement operator. 303 | * 304 | */ 305 | Iterator & operator -- (int); 306 | 307 | /** 308 | * @breif Check if iterator is equal to other iterator. 309 | * 310 | */ 311 | bool operator == (const Iterator & it); 312 | 313 | /** 314 | * @breif Check if iterator is not equal to other iterator. 315 | * 316 | */ 317 | bool operator != (const Iterator & it); 318 | 319 | private: 320 | 321 | enum eType 322 | { 323 | None, 324 | SequenceType, 325 | MapType 326 | }; 327 | 328 | eType m_Type; ///< Type of iterator. 329 | void * m_pImp; ///< Implementation of iterator class. 330 | 331 | }; 332 | 333 | 334 | /** 335 | * @breif Constant iterator class. 336 | * 337 | */ 338 | class ConstIterator 339 | { 340 | 341 | public: 342 | 343 | friend class Node; 344 | 345 | /** 346 | * @breif Default constructor. 347 | * 348 | */ 349 | ConstIterator(); 350 | 351 | /** 352 | * @breif Copy constructor. 353 | * 354 | */ 355 | ConstIterator(const ConstIterator & it); 356 | 357 | /** 358 | * @breif Assignment operator. 359 | * 360 | */ 361 | ConstIterator & operator = (const ConstIterator & it); 362 | 363 | /** 364 | * @breif Destructor. 365 | * 366 | */ 367 | ~ConstIterator(); 368 | 369 | /** 370 | * @breif Get node of iterator. 371 | * First pair item is the key of map value, empty if type is sequence. 372 | * 373 | */ 374 | std::pair operator *(); 375 | 376 | /** 377 | * @breif Post-increment operator. 378 | * 379 | */ 380 | ConstIterator & operator ++ (int); 381 | 382 | /** 383 | * @breif Post-decrement operator. 384 | * 385 | */ 386 | ConstIterator & operator -- (int); 387 | 388 | /** 389 | * @breif Check if iterator is equal to other iterator. 390 | * 391 | */ 392 | bool operator == (const ConstIterator & it); 393 | 394 | /** 395 | * @breif Check if iterator is not equal to other iterator. 396 | * 397 | */ 398 | bool operator != (const ConstIterator & it); 399 | 400 | private: 401 | 402 | enum eType 403 | { 404 | None, 405 | SequenceType, 406 | MapType 407 | }; 408 | 409 | eType m_Type; ///< Type of iterator. 410 | void * m_pImp; ///< Implementation of constant iterator class. 411 | 412 | }; 413 | 414 | 415 | /** 416 | * @breif Node class. 417 | * 418 | */ 419 | class Node 420 | { 421 | 422 | public: 423 | 424 | friend class Iterator; 425 | 426 | /** 427 | * @breif Enumeration of node types. 428 | * 429 | */ 430 | enum eType 431 | { 432 | None, 433 | SequenceType, 434 | MapType, 435 | ScalarType 436 | }; 437 | 438 | /** 439 | * @breif Default constructor. 440 | * 441 | */ 442 | Node(); 443 | 444 | /** 445 | * @breif Copy constructor. 446 | * 447 | */ 448 | Node(const Node & node); 449 | 450 | /** 451 | * @breif Assignment constructors. 452 | * Converts node to scalar type if needed. 453 | * 454 | */ 455 | Node(const std::string & value); 456 | Node(const char * value); 457 | 458 | /** 459 | * @breif Destructor. 460 | * 461 | */ 462 | ~Node(); 463 | 464 | /** 465 | * @breif Functions for checking type of node. 466 | * 467 | */ 468 | eType Type() const; 469 | bool IsNone() const; 470 | bool IsSequence() const; 471 | bool IsMap() const; 472 | bool IsScalar() const; 473 | 474 | /** 475 | * @breif Completely clear node. 476 | * 477 | */ 478 | void Clear(); 479 | 480 | /** 481 | * @breif Get node as given template type. 482 | * 483 | */ 484 | template 485 | T As() const 486 | { 487 | return impl::StringConverter::Get(AsString()); 488 | } 489 | 490 | /** 491 | * @breif Get node as given template type. 492 | * 493 | */ 494 | template 495 | T As(const T & defaultValue) const 496 | { 497 | return impl::StringConverter::Get(AsString(), defaultValue); 498 | } 499 | 500 | /** 501 | * @breif Get size of node. 502 | * Nodes of type None or Scalar will return 0. 503 | * 504 | */ 505 | size_t Size() const; 506 | 507 | // Sequence operators 508 | 509 | /** 510 | * @breif Insert sequence item at given index. 511 | * Converts node to sequence type if needed. 512 | * Adding new item to end of sequence if index is larger than sequence size. 513 | * 514 | */ 515 | Node & Insert(const size_t index); 516 | 517 | /** 518 | * @breif Add new sequence index to back. 519 | * Converts node to sequence type if needed. 520 | * 521 | */ 522 | Node & PushFront(); 523 | 524 | /** 525 | * @breif Add new sequence index to front. 526 | * Converts node to sequence type if needed. 527 | * 528 | */ 529 | Node & PushBack(); 530 | 531 | /** 532 | * @breif Get sequence/map item. 533 | * Converts node to sequence/map type if needed. 534 | * 535 | * @param index Sequence index. Returns None type Node if index is unknown. 536 | * @param key Map key. Creates a new node if key is unknown. 537 | * 538 | */ 539 | Node & operator [] (const size_t index); 540 | Node & operator [] (const std::string & key); 541 | 542 | /** 543 | * @breif Erase item. 544 | * No action if node is not a sequence or map. 545 | * 546 | */ 547 | void Erase(const size_t index); 548 | void Erase(const std::string & key); 549 | 550 | /** 551 | * @breif Assignment operators. 552 | * 553 | */ 554 | Node & operator = (const Node & node); 555 | Node & operator = (const std::string & value); 556 | Node & operator = (const char * value); 557 | 558 | /** 559 | * @breif Get start iterator. 560 | * 561 | */ 562 | Iterator Begin(); 563 | ConstIterator Begin() const; 564 | 565 | /** 566 | * @breif Get end iterator. 567 | * 568 | */ 569 | Iterator End(); 570 | ConstIterator End() const; 571 | 572 | 573 | private: 574 | 575 | /** 576 | * @breif Get as string. If type is scalar, else empty. 577 | * 578 | */ 579 | const std::string & AsString() const; 580 | 581 | void * m_pImp; ///< Implementation of node class. 582 | 583 | }; 584 | 585 | 586 | /** 587 | * @breif Parsing functions. 588 | * Population given root node with deserialized data. 589 | * 590 | * @param root Root node to populate. 591 | * @param filename Path of input file. 592 | * @param stream Input stream. 593 | * @param string String of input data. 594 | * @param buffer Char array of input data. 595 | * @param size Buffer size. 596 | * 597 | * @throw InternalException An internal error occurred. 598 | * @throw ParsingException Invalid input YAML data. 599 | * @throw OperationException If filename or buffer pointer is invalid. 600 | * 601 | */ 602 | void Parse(Node & root, const char * filename); 603 | void Parse(Node & root, std::iostream & stream); 604 | void Parse(Node & root, const std::string & string); 605 | void Parse(Node & root, const char * buffer, const size_t size); 606 | 607 | 608 | /** 609 | * @breif Serialization configuration structure, 610 | * describing output behavior. 611 | * 612 | */ 613 | struct SerializeConfig 614 | { 615 | 616 | /** 617 | * @breif Constructor. 618 | * 619 | * @param spaceIndentation Number of spaces per indentation. 620 | * @param scalarMaxLength Maximum length of scalars. Serialized as folder scalars if exceeded. 621 | * Ignored if equal to 0. 622 | * @param sequenceMapNewline Put maps on a new line if parent node is a sequence. 623 | * @param mapScalarNewline Put scalars on a new line if parent node is a map. 624 | * 625 | */ 626 | SerializeConfig(const size_t spaceIndentation = 2, 627 | const size_t scalarMaxLength = 64, 628 | const bool sequenceMapNewline = false, 629 | const bool mapScalarNewline = false); 630 | 631 | size_t SpaceIndentation; ///< Number of spaces per indentation. 632 | size_t ScalarMaxLength; ///< Maximum length of scalars. Serialized as folder scalars if exceeded. 633 | bool SequenceMapNewline; ///< Put maps on a new line if parent node is a sequence. 634 | bool MapScalarNewline; ///< Put scalars on a new line if parent node is a map. 635 | }; 636 | 637 | 638 | /** 639 | * @breif Serialization functions. 640 | * 641 | * @param root Root node to serialize. 642 | * @param filename Path of output file. 643 | * @param stream Output stream. 644 | * @param string String of output data. 645 | * @param config Serialization configurations. 646 | * 647 | * @throw InternalException An internal error occurred. 648 | * @throw OperationException If filename or buffer pointer is invalid. 649 | * If config is invalid. 650 | * 651 | */ 652 | void Serialize(const Node & root, const char * filename, const SerializeConfig & config = {2, 64, false, false}); 653 | void Serialize(const Node & root, std::iostream & stream, const SerializeConfig & config = {2, 64, false, false}); 654 | void Serialize(const Node & root, std::string & string, const SerializeConfig & config = {2, 64, false, false}); 655 | 656 | } 657 | --------------------------------------------------------------------------------