├── .clang-format
├── .github
├── scripts
│ └── setenv.ps1
└── workflows
│ ├── compile.yml
│ └── release.yml
├── .gitignore
├── LICENSE-MIT
├── LICENSE-UNLICENSE
├── Makefile
├── README.md
├── build
├── build.ninja
├── build.ps1
├── build.zig
└── shim.vcxproj
├── compile_flags.txt
├── repshims.bat
├── shim.cpp
├── shim.manifest
└── version
/.clang-format:
--------------------------------------------------------------------------------
1 | AccessModifierOffset: -4
2 | AlignAfterOpenBracket: AlwaysBreak
3 | AlignConsecutiveAssignments: false
4 | AlignConsecutiveDeclarations: false
5 | AlignEscapedNewlines: DontAlign
6 | AlignTrailingComments: false
7 | AllowAllParametersOfDeclarationOnNextLine: false
8 | AllowShortFunctionsOnASingleLine: true
9 | AllowShortIfStatementsOnASingleLine: false
10 | AllowShortLoopsOnASingleLine: false
11 | AlwaysBreakAfterReturnType: None
12 | AlwaysBreakBeforeMultilineStrings: false
13 | AlwaysBreakTemplateDeclarations: true
14 | BinPackArguments: false
15 | BinPackParameters: false
16 | BreakBeforeBinaryOperators: None
17 |
18 | # Custom format to allow namespace and empty method compression.
19 | BreakBeforeBraces: Custom
20 | BraceWrapping:
21 | AfterClass: true
22 | AfterControlStatement: true
23 | AfterEnum : true
24 | AfterExternBlock: true
25 | AfterFunction: true
26 | AfterNamespace: false
27 | AfterStruct: true
28 | AfterUnion: true
29 | BeforeCatch: true
30 | BeforeElse: true
31 | IndentBraces: false
32 | SplitEmptyFunction: false
33 | SplitEmptyRecord: false
34 | SplitEmptyNamespace: false
35 |
36 | BreakBeforeTernaryOperators: false
37 | BreakConstructorInitializers: AfterColon
38 | BreakStringLiterals: true
39 | ColumnLimit: 160
40 | CompactNamespaces: true
41 | ConstructorInitializerAllOnOneLineOrOnePerLine: true
42 | ConstructorInitializerIndentWidth: 4
43 | ContinuationIndentWidth: 4
44 | Cpp11BracedListStyle: true
45 | DerivePointerBinding: false
46 | FixNamespaceComments: true
47 | IncludeBlocks: Preserve
48 | IndentCaseLabels: false
49 | IndentPPDirectives: None
50 | IndentWidth: 4
51 | IndentWrappedFunctionNames: false
52 | Language: Cpp
53 | MaxEmptyLinesToKeep: 1
54 | NamespaceIndentation: None
55 | PointerAlignment: Left
56 | ReflowComments: false
57 | SortIncludes: false
58 | SortUsingDeclarations: true
59 | SpaceAfterCStyleCast: false
60 | SpaceAfterTemplateKeyword: false
61 | SpaceBeforeAssignmentOperators: true
62 | SpaceBeforeCpp11BracedList: true
63 | SpaceBeforeParens: ControlStatements
64 | SpaceInEmptyParentheses: false
65 | SpacesBeforeTrailingComments: 1
66 | SpacesInAngles: false
67 | SpacesInCStyleCastParentheses: false
68 | SpacesInContainerLiterals: false
69 | SpacesInParentheses: false
70 | SpacesInSquareBrackets: false
71 | Standard: Cpp11
72 | TabWidth: 4
73 | UseTab: Never
74 | MacroBlockBegin: '^BEGIN_MODULE$|^BEGIN_TEST_CLASS$|^BEGIN_TEST_METHOD$|^BEGIN_COM_MAP$'
75 | MacroBlockEnd: '^END_MODULE$|^END_TEST_CLASS$|^END_TEST_METHOD$|^END_TEST_METHOD$|^END_COM_MAP$'
--------------------------------------------------------------------------------
/.github/scripts/setenv.ps1:
--------------------------------------------------------------------------------
1 | $_VsInstallerDir = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\";
2 | if (Test-Path "$_VsInstallerDir\vswhere.exe") {
3 | $env:PATH += ";$_VsInstallerDir";
4 | $installPath = vswhere -latest -property installationPath;
5 | Import-Module (Join-Path $installPath "Common7\Tools\Microsoft.VisualStudio.DevShell.dll") -Force;
6 | Enter-VsDevShell -VsInstallPath $installPath -SkipAutomaticLocation;
7 | }
8 |
--------------------------------------------------------------------------------
/.github/workflows/compile.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: windows-latest
8 | env:
9 | MSYS2_PATH_TYPE: inherit
10 | defaults:
11 | run:
12 | shell: pwsh
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - uses: msys2/setup-msys2@v2
17 | with:
18 | update: false
19 | release: false
20 | install: >-
21 | ninja
22 |
23 | - name: Build
24 | run: |
25 | .github/scripts/setenv.ps1
26 | ninja -C build shim.exe
27 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | schedule:
5 | - cron: "0 0 * * 0" # weekly
6 | workflow_dispatch:
7 | push:
8 | branches:
9 | - main
10 | paths:
11 | - 'version'
12 |
13 | jobs:
14 | build:
15 | runs-on: windows-latest
16 | env:
17 | MSYS2_PATH_TYPE: inherit
18 | defaults:
19 | run:
20 | shell: msys2 {0}
21 |
22 | steps:
23 | - uses: msys2/setup-msys2@v2
24 | with:
25 | update: false
26 | release: false
27 | install: >-
28 | zip
29 | ninja
30 | - uses: actions/checkout@v4
31 |
32 | - name: Get latest release
33 | continue-on-error: true
34 | id: cur_release
35 | uses: pozetroninc/github-action-get-latest-release@master
36 | with:
37 | repository: ${{ github.repository }}
38 | excludes: draft
39 |
40 | - name: Get version
41 | run: |
42 | base_ver=$(cat version)
43 | sha=${GITHUB_SHA::7}
44 | prerelease=$([[ "${{ github.event_name }}" == "schedule" || "${{ github.event_name }}" == "workflow_dispatch" ]] && echo true || echo false)
45 | version=$([[ $prerelease == true ]] && echo ${base_ver}.$sha || echo ${base_ver})
46 | cur_ver=$([[ ${{ steps.cur_release.outcome }} == failure ]] && echo v0 || echo ${{ steps.cur_release.outputs.release }})
47 | [[ v$version == $cur_ver ]] && exit 1
48 | echo "version=$version" >> $GITHUB_ENV
49 | echo "prerelease=$prerelease" >> $GITHUB_ENV
50 |
51 | - name: Setup environment
52 | shell: pwsh
53 | run: |
54 | .github/scripts/setenv.ps1
55 | echo $env:PATH | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
56 |
57 | - name: Build
58 | run: ninja -C build
59 |
60 | - name: Create release
61 | uses: ncipollo/release-action@v1
62 | with:
63 | tag: v${{ env.version }}
64 | name: ${{ env.version }}
65 | draft: false
66 | prerelease: ${{ env.prerelease }}
67 | artifactErrorsFailBuild: true
68 | artifacts: build/shimexe.zip
69 | artifactContentType: application/zip
70 | token: ${{ secrets.GITHUB_TOKEN }}
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | *.exe
3 | *.obj
4 | *.pdb
5 | *.ilk
6 | *.o
7 | checksum.sha*
8 | obj/
9 | bin/
10 | archive/
11 | out/
12 | .zig-cache/
13 | zig-out/
14 | .vscode/
15 | *.exe
16 | *.obj
17 | *.pdb
18 | *.ilk
19 | *.o
20 | checksum.sha*
21 | obj/
22 | bin/
23 | archive/
24 | out/
25 | .zig-cache/
26 | zig-out/
27 |
28 | # User-specific files
29 | *.rsuser
30 | *.suo
31 | *.user
32 | *.userosscache
33 | *.sln.docstates
34 |
35 | # Build results
36 | [Dd]ebug/
37 | [Dd]ebugPublic/
38 | [Rr]elease/
39 | [Rr]eleases/
40 | x64/
41 | x86/
42 | [Ww][Ii][Nn]32/
43 | [Aa][Rr][Mm]/
44 | [Aa][Rr][Mm]64/
45 | bld/
46 | [Bb]in/
47 | [Oo]bj/
48 | [Ll]og/
49 | [Ll]ogs/
50 |
51 | # Visual Studio cache/options directory
52 | .vs/
53 |
54 | # Visual C++ cache files
55 | ipch/
56 | *.aps
57 | *.ncb
58 | *.opendb
59 | *.opensdf
60 | *.sdf
61 | *.cachefile
62 | *.VC.db
63 | *.VC.VC.opendb
64 |
65 | # Visual Studio profiler
66 | *.psess
67 | *.vsp
68 | *.vspx
69 | *.sap
70 |
71 | # Visual Studio Trace Files
72 | *.e2e
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Grégoire Geis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/LICENSE-UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
4 |
5 | In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
6 | successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 |
10 | For more information, please refer to
11 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | CC=clang++.exe
2 | CFLAGS=-std=c++17 -m32
3 |
4 | ODIR = obj
5 | BDIR = bin
6 | ADIR = archive
7 |
8 | TARGET = $(BDIR)/shim.exe
9 | OBJ = shim.o
10 | OBJS = $(patsubst %,$(ODIR)/%,$(OBJ))
11 |
12 | $(TARGET): $(OBJS) | $(BDIR)
13 | $(CC) -o $(TARGET) $^ $(CFLAGS) -Ofast -static
14 | sha256sum $(TARGET) > $(BDIR)/checksum.sha256
15 | sha512sum $(TARGET) > $(BDIR)/checksum.sha512
16 |
17 | $(ODIR)/%.o: %.cpp | $(ODIR)
18 | $(CC) -c -o $@ $< $(CFLAGS) -Ofast -g
19 |
20 | $(ODIR):
21 | mkdir -p $(ODIR)
22 |
23 | $(BDIR):
24 | mkdir -p $(BDIR)
25 |
26 | .PHONY: clean debug zip
27 |
28 | clean:
29 | rm -f $(ODIR)/*.*
30 | rm -f $(BDIR)/*.*
31 |
32 | debug: $(OBJS) | $(BDIR)
33 | $(CC) -o $(BDIR)/shim.exe $^ $(CFLAGS) -g
34 |
35 | $(ADIR):
36 | mkdir -p $(ADIR)
37 |
38 | $(ADIR)/shimexe.zip: $(TARGET) | $(ADIR)
39 | cd $(ADIR) && zip -j -9 shimexe.zip ../$(BDIR)/*.*
40 |
41 | zip: $(ADIR)/shimexe.zip
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [Scoop](https://github.com/lukesampson/scoop), although very useful and nice to use,
2 | uses a (subjectively) terrible [`shim.exe`](https://github.com/lukesampson/scoop/blob/master/supporting/shimexe/shim.cs)
3 | file to redirect commands from `scoop\shims\app.exe` to `scoop\apps\app\current\app.exe`, because:
4 | 1. [It's made in C#](https://github.com/lukesampson/scoop/tree/master/supporting/shimexe),
5 | and thus requires an instantiation of a .NET command line app everytime it is started,
6 | which can make a command run much slower than if it had been ran directly;
7 | 2. [It](https://github.com/lukesampson/scoop/issues/2339) [does](https://github.com/lukesampson/scoop/issues/1896)
8 | [not](https://github.com/felixse/FluentTerminal/issues/221) handle Ctrl+C and other
9 | signals correctly, which can be quite infuriating.
10 |
11 | The last issue making interaction with REPLs and long-running apps practically impossible,
12 | and having never been fixed, I set out to improve them with this repository.
13 |
14 | [`shim.cpp`](./shim.cpp) is:
15 | - **Faster**, because it does not use the .NET Framework, and parses the `.shim` file in a simpler way.
16 | - **More efficient**, because by the time the target of the shim is started, all allocated memory will have been freed.
17 | - And more importantly, it **works better**:
18 | - Signals originating from pressing `Ctrl+C` are ignored, and therefore handled directly by the spawned child.
19 | Your processes and REPLs will no longer close when pressing `Ctrl+C`.
20 | - Children are automatically killed when the shim process is killed. No more orphaned processes and weird behaviors.
21 |
22 | > **Note**: This project is not affiliated with [Scoop](https://github.com/lukesampson/scoop).
23 |
24 | ## Installation
25 |
26 | - Building:
27 | - Build by `clang++`: `Makefile` is supported
28 | - Build by using `ninja`: `ninja -C build shim.exe`
29 | - Build by using `msbuild`: `msbuild /p:Configuration=Release .\build\shim.vcxproj`
30 |
31 | - Replace any `.exe` in `scoop\shims` by `shim.exe`.
32 |
33 | An additional script, `repshims.bat`, is provided. It will replace all `.exe`s in the user's Scoop directory
34 | by `shim.exe`.
35 |
36 |
37 | ## License
38 |
39 | `SPDX-License-Identifier: MIT OR Unlicense`
40 |
--------------------------------------------------------------------------------
/build/build.ninja:
--------------------------------------------------------------------------------
1 | cc = clang++.exe
2 | cflags = -std=c++20 -m32
3 |
4 | rule cc
5 | command = $cc -c -o $out $in $cflags -O2 -g
6 |
7 | rule link
8 | command = $cc -o $out $cflags -O2 -static -Wl,/manifest:embed -Wl,/manifestinput:$in
9 |
10 | rule link-debug
11 | command = $cc -o $out $cflags -O2 -static -fuse-ld=lld -g -Wl,/manifest:embed -Wl,/manifestinput:$in
12 |
13 | rule mkdir
14 | command = mkdir -p $in
15 |
16 | rule sha256sum
17 | command = sha256sum $in > $out
18 |
19 | rule sha512sum
20 | command = sha512sum $in > $out
21 |
22 | rule zip
23 | command = zip -j -9 $out $in
24 |
25 | build shim.o: cc ../shim.cpp
26 | build shim.exe: link ../shim.manifest shim.o
27 | build shimd.exe: link-debug ../shim.manifest shim.o
28 | build checksum.sha256: sha256sum shim.exe
29 | build checksum.sha512: sha512sum shim.exe
30 | build shimexe.zip: zip shim.exe checksum.sha256 checksum.sha512
31 |
32 | default shimexe.zip
33 |
--------------------------------------------------------------------------------
/build/build.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 | #Requires -Version 7
3 |
4 | <#
5 | .SYNOPSIS
6 | Build shim.exe using Zig.
7 | .PARAMETER BuildMode
8 | The build mode. Valid values are Debug, ReleaseSafe, ReleaseFast, ReleaseSmall
9 | Default is ReleaseSmall
10 | .PARAMETER Target
11 | The target architecture. Valid values are x86-windows-gnu, x86_64-windows-gnu, aarch64-windows-gnu
12 | Default is undefined (all valid targets)
13 | .PARAMETER Zip
14 | Generate checksums and pack the artifacts into a zip file for distribution
15 | #>
16 | param(
17 | [ValidateSet('Debug', 'ReleaseSafe', 'ReleaseFast', 'ReleaseSmall')]
18 | [string]$BuildMode = "ReleaseSmall",
19 | [ValidateSet('x86-windows-gnu', 'x86_64-windows-gnu', 'aarch64-windows-gnu')]
20 | [string]$Target,
21 | [switch]$Zip = $false
22 | )
23 |
24 | $oldErrorActionPreference = $ErrorActionPreference
25 | $ErrorActionPreference = "Stop"
26 |
27 | if (-not [bool](Get-Command zig -ErrorAction SilentlyContinue)) {
28 | Write-Host "Zig is not installed. Please install Zig before running this script." -ForegroundColor Yellow
29 | exit 1
30 | }
31 |
32 | Remove-Item -Path "$PSScriptRoot\zig-out" -Recurse -Force -ErrorAction SilentlyContinue
33 |
34 | Push-Location $PSScriptRoot
35 |
36 | if (-not $Target -or $Target -eq 'x86-windows-gnu') {
37 | Write-Host "Build shim.exe for x86-windows-gnu target..." -ForegroundColor Cyan
38 | Start-Process -FilePath "zig" -ArgumentList "build -Dtarget=x86-windows-gnu -Doptimize=$BuildMode" -Wait -NoNewWindow
39 | Rename-Item -Path "$PSScriptRoot\zig-out\bin\shim.exe" -NewName "$PSScriptRoot\zig-out\bin\shim-ia32.exe"
40 | }
41 |
42 | if (-not $Target -or $Target -eq 'x86_64-windows-gnu') {
43 | Write-Host "Build shim.exe for x86_64-windows-gnu target..." -ForegroundColor Cyan
44 | Start-Process -FilePath "zig" -ArgumentList "build -Dtarget=x86_64-windows-gnu -Doptimize=$BuildMode" -Wait -NoNewWindow
45 | Rename-Item -Path "$PSScriptRoot\zig-out\bin\shim.exe" -NewName "$PSScriptRoot\zig-out\bin\shim-amd64.exe"
46 | }
47 |
48 | if (-not $Target -or $Target -eq 'aarch64-windows-gnu') {
49 | Write-Host "Build shim.exe for aarch64-windows-gnu target..." -ForegroundColor Cyan
50 | Start-Process -FilePath "zig" -ArgumentList "build -Dtarget=aarch64-windows-gnu -Doptimize=$BuildMode" -Wait -NoNewWindow
51 | Rename-Item -Path "$PSScriptRoot\zig-out\bin\shim.exe" -NewName "$PSScriptRoot\zig-out\bin\shim-aarch64.exe"
52 | }
53 |
54 | if ($Zip) {
55 | Write-Host "Generate checksums..." -ForegroundColor Cyan
56 |
57 | # shim-ia32.exe
58 | if (-not $Target -or $Target -eq 'x86-windows-gnu') {
59 | $sha256 = (Get-FileHash "$PSScriptRoot\zig-out\bin\shim-ia32.exe" -Algorithm SHA256).Hash.ToLower()
60 | "$sha256 shim-ia32.exe" | Out-File "$PSScriptRoot\zig-out\bin\shim-ia32.exe.sha256"
61 | $sha512 = (Get-FileHash "$PSScriptRoot\zig-out\bin\shim-ia32.exe" -Algorithm SHA512).Hash.ToLower()
62 | "$sha512 shim-ia32.exe" | Out-File "$PSScriptRoot\zig-out\bin\shim-ia32.exe.sha512"
63 | }
64 |
65 | # shim-amd64.exe
66 | if (-not $Target -or $Target -eq 'x86_64-windows-gnu') {
67 | $sha256 = (Get-FileHash "$PSScriptRoot\zig-out\bin\shim-amd64.exe" -Algorithm SHA256).Hash.ToLower()
68 | "$sha256 shim-amd64.exe" | Out-File "$PSScriptRoot\zig-out\bin\shim-amd64.exe.sha256"
69 | $sha512 = (Get-FileHash "$PSScriptRoot\zig-out\bin\shim-amd64.exe" -Algorithm SHA512).Hash.ToLower()
70 | "$sha512 shim-amd64.exe" | Out-File "$PSScriptRoot\zig-out\bin\shim-amd64.exe.sha512"
71 | }
72 |
73 | # shim-aarch64.exe
74 | if (-not $Target -or $Target -eq 'aarch64-windows-gnu') {
75 | $sha256 = (Get-FileHash "$PSScriptRoot\zig-out\bin\shim-aarch64.exe" -Algorithm SHA256).Hash.ToLower()
76 | "$sha256 shim-aarch64.exe" | Out-File "$PSScriptRoot\zig-out\bin\shim-aarch64.exe.sha256"
77 | $sha512 = (Get-FileHash "$PSScriptRoot\zig-out\bin\shim-aarch64.exe" -Algorithm SHA512).Hash.ToLower()
78 | "$sha512 shim-aarch64.exe" | Out-File "$PSScriptRoot\zig-out\bin\shim-aarch64.exe.sha512"
79 | }
80 |
81 | Write-Host "Packaging..." -ForegroundColor Cyan
82 |
83 | $version = (Get-Content "$PSScriptRoot\..\version").Trim()
84 | Compress-Archive -Path "$PSScriptRoot\zig-out\bin\shim-*" -DestinationPath "$PSScriptRoot\zig-out\shimexe-$version.zip"
85 |
86 | $sha256 = (Get-FileHash "$PSScriptRoot\zig-out\shimexe-$version.zip" -Algorithm SHA256).Hash.ToLower()
87 | "$sha256 shimexe-$version.zip" | Out-File "$PSScriptRoot\zig-out\shimexe-$version.zip.sha256"
88 | }
89 |
90 | Write-Host "Artifacts available in $PSScriptRoot\zig-out" -ForegroundColor Green
91 |
92 | Pop-Location
93 |
94 | $ErrorActionPreference = $oldErrorActionPreference
95 |
--------------------------------------------------------------------------------
/build/build.zig:
--------------------------------------------------------------------------------
1 | const builtin = @import("builtin");
2 | const std = @import("std");
3 | const CrossTarget = std.Target.Query;
4 |
5 | // Usage:
6 | // zig build -Dtarget= -Doptimize=
7 | // Supported targets:
8 | // x86-windows-gnu
9 | // x86-windows-msvc
10 | // x86_64-windows-gnu
11 | // x86_64-windows-msvc
12 | // aarch64-windows-gnu
13 | // aarch64-windows-msvc
14 |
15 | const required_version = std.SemanticVersion.parse("0.13.0") catch unreachable;
16 | const compatible = builtin.zig_version.order(required_version) == .gt;
17 |
18 | pub fn build(b: *std.Build) void {
19 | if (!compatible) {
20 | std.log.err("Unsupported Zig compiler version", .{});
21 | return;
22 | }
23 |
24 | const optimize = b.standardOptimizeOption(.{});
25 | const target = b.standardTargetOptions(.{ .default_target = CrossTarget{
26 | .os_tag = .windows,
27 | .abi = .gnu,
28 | } });
29 |
30 | if (target.result.os.tag != .windows) {
31 | std.log.err("Non-Windows target is not supported", .{});
32 | return;
33 | }
34 |
35 | const exe = b.addExecutable(.{
36 | .name = "shim",
37 | .target = target,
38 | .optimize = optimize,
39 | .win32_manifest = b.path("../shim.manifest"),
40 | });
41 |
42 | exe.addCSourceFile(.{ .file = b.path("../shim.cpp"), .flags = &.{"-std=c++20"} });
43 | exe.linkSystemLibrary("shlwapi");
44 |
45 | if (target.result.abi == .msvc) {
46 | exe.linkLibC();
47 | } else {
48 | exe.linkLibCpp();
49 | exe.subsystem = .Console;
50 | // NOTE: This requires a recent Zig version (0.12.0-dev.3493+3661133f9 or later)
51 | exe.mingw_unicode_entry_point = true;
52 | }
53 |
54 | b.installArtifact(exe);
55 | }
56 |
--------------------------------------------------------------------------------
/build/shim.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 |
14 | 17.0
15 | Win32Proj
16 | shim
17 | 10.0
18 | {93C23795-D457-8055-262A-8D7686A0FB08}
19 |
20 |
21 | Application
22 | v143
23 | Unicode
24 | $(SolutionDir)out\$(Configuration)\bin\
25 | $(SolutionDir)out\$(Configuration)\obj\
26 |
27 |
28 |
29 | true
30 |
31 |
32 | false
33 | true
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | Level3
43 | true
44 | true
45 | stdcpplatest
46 | Size
47 |
48 |
49 | Console
50 | true
51 |
52 |
53 |
54 |
55 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
56 |
57 |
58 |
59 |
60 | true
61 | true
62 | Size
63 | true
64 | false
65 | false
66 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
67 | MinSpace
68 | true
69 | MultiThreaded
70 |
71 |
72 | true
73 | true
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/compile_flags.txt:
--------------------------------------------------------------------------------
1 | -std=c++20
2 |
--------------------------------------------------------------------------------
/repshims.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | if not defined SCOOP set SCOOP=%USERPROFILE%\scoop
4 |
5 | for %%x in ("%SCOOP%\shims\*.exe") do (
6 | echo Replacing %%x by new shim.
7 | del "%%~x"
8 | copy "%~dp0"\bin\shim.exe "%%~x"
9 | )
10 |
11 | if not defined SCOOP_GLOBAL set SCOOP_GLOBAL=%ProgramData%\scoop
12 |
13 | for %%x in ("%SCOOP_GLOBAL%\shims\*.exe") do (
14 | echo Replacing %%x by new shim.
15 | del "%%~x"
16 | copy "%~dp0"\bin\shim.exe "%%~x"
17 | )
18 |
--------------------------------------------------------------------------------
/shim.cpp:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #include
3 | #endif
4 | #pragma comment(lib, "SHLWAPI.LIB")
5 | #include
6 | #include
7 | #include
8 |
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #ifndef ERROR_ELEVATION_REQUIRED
17 | #define ERROR_ELEVATION_REQUIRED 740
18 | #endif
19 |
20 | using namespace std::string_view_literals;
21 |
22 | BOOL WINAPI CtrlHandler(DWORD ctrlType)
23 | {
24 | switch (ctrlType)
25 | {
26 | // Ignore all events, and let the child process
27 | // handle them.
28 | case CTRL_C_EVENT:
29 | case CTRL_CLOSE_EVENT:
30 | case CTRL_LOGOFF_EVENT:
31 | case CTRL_BREAK_EVENT:
32 | case CTRL_SHUTDOWN_EVENT:
33 | return TRUE;
34 |
35 | default:
36 | return FALSE;
37 | }
38 | }
39 |
40 | struct HandleDeleter
41 | {
42 | typedef HANDLE pointer;
43 | void operator() (HANDLE handle)
44 | {
45 | if (handle)
46 | {
47 | CloseHandle(handle);
48 | }
49 | }
50 | };
51 |
52 | namespace std
53 | {
54 | typedef unique_ptr unique_handle;
55 | typedef optional wstring_p;
56 | }
57 |
58 | struct ShimInfo
59 | {
60 | std::wstring_p path;
61 | std::wstring_p args;
62 | };
63 |
64 | std::wstring_view GetDirectory(std::wstring_view exe)
65 | {
66 | auto pos = exe.find_last_of(L"\\/");
67 | return exe.substr(0, pos);
68 | }
69 |
70 | std::wstring_p NormalizeArgs(std::wstring_p& args, std::wstring_view curDir)
71 | {
72 | static constexpr auto s_dirPlaceHolder = L"%~dp0"sv;
73 | if (!args)
74 | {
75 | return args;
76 | }
77 |
78 | auto pos = args->find(s_dirPlaceHolder);
79 | if (pos != std::wstring::npos)
80 | {
81 | args->replace(pos, s_dirPlaceHolder.size(), curDir.data(), curDir.size());
82 | }
83 |
84 | return args;
85 | }
86 |
87 | ShimInfo GetShimInfo()
88 | {
89 | // Find filename of current executable.
90 | wchar_t filename[MAX_PATH + 2];
91 | const auto filenameSize = GetModuleFileNameW(nullptr, filename, MAX_PATH);
92 |
93 | if (filenameSize >= MAX_PATH)
94 | {
95 | fprintf(stderr, "Shim: The filename of the program is too long to handle.\n");
96 | return {std::nullopt, std::nullopt};
97 | }
98 |
99 | // Use filename of current executable to find .shim
100 | wmemcpy(filename + filenameSize - 3, L"shim", 4U);
101 | filename[filenameSize + 1] = L'\0';
102 | FILE* fp = nullptr;
103 |
104 | if (_wfopen_s(&fp, filename, L"r,ccs=UTF-8") != 0)
105 | {
106 | fprintf(stderr, "Cannot open shim file for read.\n");
107 | return {std::nullopt, std::nullopt};
108 | }
109 |
110 | std::unique_ptr shimFile(fp, &fclose);
111 |
112 | // Read shim
113 | wchar_t linebuf[1<<14];
114 | std::wstring_p path;
115 | std::wstring_p args;
116 | while (true)
117 | {
118 | if (!fgetws(linebuf, ARRAYSIZE(linebuf), shimFile.get()))
119 | {
120 | break;
121 | }
122 |
123 | std::wstring_view line(linebuf);
124 |
125 | if ((line.size() < 7) || (line.substr(4, 3) != L" = "))
126 | {
127 | continue;
128 | }
129 |
130 | if (line.substr(0, 4) == L"path")
131 | {
132 | std::wstring_view line_substr = line.substr(7);
133 | if (line_substr.find(L" ") != std::wstring_view::npos && line_substr.front() != L'"')
134 | {
135 | path.emplace(L"\"");
136 | auto& path_value = path.value();
137 | path_value.append(line_substr.data(), line_substr.size() - (line.back() == L'\n' ? 1 : 0));
138 | path_value.push_back(L'"');
139 | }
140 | else
141 | {
142 | path.emplace(line_substr.data(), line_substr.size() - (line.back() == L'\n' ? 1 : 0));
143 | }
144 |
145 | continue;
146 | }
147 |
148 | if (line.substr(0, 4) == L"args")
149 | {
150 | args.emplace(line.data() + 7, line.size() - 7 - (line.back() == L'\n' ? 1 : 0));
151 | continue;
152 | }
153 | }
154 |
155 | return {path, NormalizeArgs(args, GetDirectory(filename))};
156 | }
157 |
158 | std::tuple MakeProcess(ShimInfo const& info)
159 | {
160 | // Start subprocess
161 | STARTUPINFOW si = {};
162 | PROCESS_INFORMATION pi = {};
163 |
164 | auto&& [path, args] = info;
165 | std::vector cmd(path->size() + args->size() + 2);
166 | wmemcpy(cmd.data(), path->c_str(), path->size());
167 | cmd[path->size()] = L' ';
168 | wmemcpy(cmd.data() + path->size() + 1, args->c_str(), args->size());
169 | cmd[path->size() + 1 + args->size()] = L'\0';
170 |
171 | std::unique_handle threadHandle;
172 | std::unique_handle processHandle;
173 |
174 | GetStartupInfoW(&si);
175 |
176 | if (CreateProcessW(nullptr, cmd.data(), nullptr, nullptr, TRUE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi))
177 | {
178 | threadHandle.reset(pi.hThread);
179 | processHandle.reset(pi.hProcess);
180 |
181 | ResumeThread(threadHandle.get());
182 | }
183 | else
184 | {
185 | if (GetLastError() == ERROR_ELEVATION_REQUIRED)
186 | {
187 | // We must elevate the process, which is (basically) impossible with
188 | // CreateProcess, and therefore we fallback to ShellExecuteEx,
189 | // which CAN create elevated processes, at the cost of opening a new separate
190 | // window.
191 | // Theorically, this could be fixed (or rather, worked around) using pipes
192 | // and IPC, but... this is a question for another day.
193 | SHELLEXECUTEINFOW sei = {};
194 |
195 | sei.cbSize = sizeof(SHELLEXECUTEINFOW);
196 | sei.fMask = SEE_MASK_NOCLOSEPROCESS;
197 | sei.lpFile = path->c_str();
198 | sei.lpParameters = args->c_str();
199 | sei.nShow = SW_SHOW;
200 |
201 | if (!ShellExecuteExW(&sei))
202 | {
203 | fprintf(stderr, "Shim: Unable to create elevated process: error %li.", GetLastError());
204 | return {std::move(processHandle), std::move(threadHandle)};
205 | }
206 |
207 | processHandle.reset(sei.hProcess);
208 | }
209 | else
210 | {
211 | fprintf(stderr, "Shim: Could not create process with command '%ls'.\n", cmd.data());
212 | return {std::move(processHandle), std::move(threadHandle)};
213 | }
214 | }
215 |
216 | // Ignore Ctrl-C and other signals
217 | if (!SetConsoleCtrlHandler(CtrlHandler, TRUE))
218 | {
219 | fprintf(stderr, "Shim: Could not set control handler; Ctrl-C behavior may be invalid.\n");
220 | }
221 |
222 | return {std::move(processHandle), std::move(threadHandle)};
223 | }
224 |
225 | int wmain(int argc, wchar_t* argv[])
226 | {
227 | auto [path, args] = GetShimInfo();
228 |
229 | if (!path)
230 | {
231 | fprintf(stderr, "Could not read shim file.\n");
232 | return 1;
233 | }
234 |
235 | if (!args)
236 | {
237 | args.emplace();
238 | }
239 |
240 | auto cmd = GetCommandLineW();
241 | if (cmd[0] == L'\"')
242 | {
243 | args->append(cmd + wcslen(argv[0]) + 2);
244 | }
245 | else
246 | {
247 | args->append(cmd + wcslen(argv[0]));
248 | }
249 |
250 | // Find out if the target program is a console app
251 |
252 | wchar_t unquotedPath[MAX_PATH] = {};
253 | wmemcpy(unquotedPath, path->c_str(), path->length());
254 | PathUnquoteSpacesW(unquotedPath);
255 | SHFILEINFOW sfi = {};
256 | const auto ret = SHGetFileInfoW(unquotedPath, -1, &sfi, sizeof(sfi), SHGFI_EXETYPE);
257 |
258 | if (ret == 0)
259 | {
260 | fprintf(stderr, "Shim: Could not determine if target is a GUI app. Assuming console.\n");
261 | }
262 |
263 | const auto isWindowsApp = HIWORD(ret) != 0;
264 |
265 | if (isWindowsApp)
266 | {
267 | // Unfortunately, this technique will still show a window for a fraction of time,
268 | // but there's just no workaround.
269 | FreeConsole();
270 | }
271 |
272 | // Create job object, which can be attached to child processes
273 | // to make sure they terminate when the parent terminates as well.
274 | std::unique_handle jobHandle(CreateJobObject(nullptr, nullptr));
275 | JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {};
276 |
277 | jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
278 | SetInformationJobObject(jobHandle.get(), JobObjectExtendedLimitInformation, &jeli, sizeof(jeli));
279 |
280 | auto [processHandle, threadHandle] = MakeProcess({path, args});
281 | if (processHandle)
282 | {
283 | AssignProcessToJobObject(jobHandle.get(), processHandle.get());
284 |
285 | // Wait till end of process
286 | WaitForSingleObject(processHandle.get(), INFINITE);
287 |
288 | DWORD exitCode = 0;
289 | GetExitCodeProcess(processHandle.get(), &exitCode);
290 |
291 | return exitCode;
292 | }
293 |
294 | return processHandle ? 0 : 1;
295 | }
296 |
--------------------------------------------------------------------------------
/shim.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/version:
--------------------------------------------------------------------------------
1 | 3.1.2
2 |
--------------------------------------------------------------------------------