Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0
- uses: crate-ci/typos@bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # v1.47.2
- name: Reject incomplete implementation markers
Expand All @@ -38,8 +38,8 @@ jobs:
runs-on: windows-latest
timeout-minutes: 35
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
cache: true
Expand Down Expand Up @@ -97,8 +97,8 @@ jobs:
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
cache: true
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ jobs:
contents: read
security-events: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
cache: true
cache-dependency-path: "**/packages.lock.json"
- uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
- uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
languages: csharp
build-mode: manual
- run: dotnet restore Snaply.slnx --locked-mode
- run: >-
dotnet build src/Snaply.App/Snaply.App.csproj
-c Release -p:Platform=x64 --no-restore
- uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
- uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
category: /language:csharp

Expand All @@ -44,11 +44,11 @@ jobs:
contents: read
security-events: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
languages: actions
build-mode: none
- uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
- uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
category: /language:actions
2 changes: 1 addition & 1 deletion .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
fail-on-severity: moderate
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ jobs:
runs-on: ${{ matrix.runner }}
timeout-minutes: 90
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
architecture: ${{ matrix.architecture }}
cache: true
cache-dependency-path: "**/packages.lock.json"
- uses: microsoft/setup-WinAppCli@64bc6fd4c3e4c69e2de589dfc34684470891b8b7
- uses: microsoft/setup-WinAppCli@b93bbddc1f7abc061ca0d3a8119e3a0c7dd71495
with:
version: v0.3.1
- run: dotnet restore Snaply.slnx --locked-mode
Expand Down Expand Up @@ -104,8 +104,8 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
cache: true
Expand All @@ -131,8 +131,8 @@ jobs:
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
cache: true
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/nuget-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ jobs:
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
cache: true
Expand Down
24 changes: 12 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
if ([string]::IsNullOrWhiteSpace($env:MSIX_PUBLISHER)) {
throw "The MSIX_PUBLISHER repository variable is required."
}
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.REF }}
fetch-depth: 0
Expand All @@ -79,7 +79,7 @@ jobs:
if ("v$($props.Project.PropertyGroup.Version)" -ne $env:TAG) {
throw "The release tag does not match the project version."
}
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
cache: true
Expand All @@ -104,7 +104,7 @@ jobs:
-pv '${{ inputs.tag_name }}'.TrimStart('v') `
-ps P4suta `
-nsb https://github.com/P4suta/Snaply
- uses: taiki-e/install-action@4684b8405694ae9dd42c9f39ba901a70ae83f4a3 # v2.82.9
- uses: taiki-e/install-action@c44f6b046f1c29ae5918b1e0bfdbb2f1813836fd # v2.84.1
with:
tool: osv-scanner@2.3.6
- name: Scan release SBOM
Expand All @@ -130,10 +130,10 @@ jobs:
HAVE_SIGNING: ${{ secrets.ES_USERNAME != '' && secrets.ES_PASSWORD != '' && secrets.CREDENTIAL_ID != '' && secrets.ES_TOTP_SECRET != '' }}
SIGNER_SUBJECT_CONTAINS: ${{ vars.SIGNER_SUBJECT_CONTAINS }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.REF }}
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
cache: true
Expand Down Expand Up @@ -238,10 +238,10 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.REF }}
- uses: microsoft/setup-WinAppCli@64bc6fd4c3e4c69e2de589dfc34684470891b8b7
- uses: microsoft/setup-WinAppCli@b93bbddc1f7abc061ca0d3a8119e3a0c7dd71495
with:
version: v0.3.1
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
Expand Down Expand Up @@ -292,10 +292,10 @@ jobs:
SIGNER_SUBJECT_CONTAINS: ${{ vars.SIGNER_SUBJECT_CONTAINS }}
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.REF }}
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
cache: true
Expand Down Expand Up @@ -331,21 +331,21 @@ jobs:
}
}
- name: Attest build provenance
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0
with:
subject-path: |
build/release/package/*.zip
build/release/package/*.msixbundle
build/release/package/SHA256SUMS.txt
- name: Attest SBOM
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0
with:
subject-path: |
build/release/package/*.zip
build/release/package/*.msixbundle
sbom-path: build/release/package/snaply.spdx.json
- name: Publish release
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: ${{ inputs.tag_name }}
draft: false
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/sbom-monitor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download latest release SBOM
id: release
shell: bash
Expand All @@ -29,7 +29,7 @@ jobs:
gh release download "$tag" --pattern 'snaply.spdx.json' --dir sbom
echo "available=true" >> "$GITHUB_OUTPUT"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
- uses: taiki-e/install-action@4684b8405694ae9dd42c9f39ba901a70ae83f4a3 # v2.82.9
- uses: taiki-e/install-action@c44f6b046f1c29ae5918b1e0bfdbb2f1813836fd # v2.84.1
if: steps.release.outputs.available == 'true'
with:
tool: osv-scanner@2.3.6
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ jobs:
id-token: write
security-events: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
- uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
sarif_file: results.sarif
36 changes: 25 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
# Snaply

Snaply captures a region, window, or the complete virtual desktop and automatically presents it on a randomized, image-aware gradient background.
Snaply is a Windows screenshot tool. It captures a region, a window, or the
entire virtual desktop, then places the capture on a randomized, image-derived
gradient background.

After each capture, Snaply opens a preview, copies one PNG to the clipboard, and saves the same image to `Pictures\Screenshots\Snaply`. Copy, Save As, and Open Folder remain available from the preview.
After each capture, Snaply opens a preview, copies a PNG to the clipboard, and
saves the same PNG to `Pictures\Screenshots\Snaply`.

## Use
## Usage

1. Choose Region, Window, or Desktop from Capture.
2. For Region, drag across one or more displays. For Window, choose from the Windows picker.
3. Use Copy, Save As, Open Folder, Fit, or 100% as needed.

Keyboard shortcuts: `Ctrl+N`, `Ctrl+C`, `Ctrl+Shift+S`, `Ctrl+0`, `Ctrl+1`, and `Esc`.
1. Open the Capture menu and choose Region, Window, or Desktop.
2. For Region, drag to select across one or more displays. For Window, pick
from the system window picker.
3. In the preview, scroll to zoom, drag to pan, and double-tap to fit. Use
Open Folder to open the save location.

## Install

Download the signed MSIX bundle or the self-contained x64/ARM64 portable ZIP from [GitHub Releases](https://github.com/P4suta/Snaply/releases). Portable builds require no .NET or Windows App SDK installation; extract the ZIP and run `Snaply.exe`.
Download the signed MSIX bundle or the self-contained x64/ARM64 portable ZIP
from [GitHub Releases](https://github.com/P4suta/Snaply/releases). Portable
builds require no .NET or Windows App SDK installation: extract the ZIP and run
`Snaply.exe`.

Snaply runs on Windows 11 24H2 or later on x64 and ARM64, in English, Japanese,
and Simplified Chinese.

## Privacy

Snaply runs entirely on the local machine. It has no telemetry, network access,
background service, tray process, global hotkey, or updater.

Snaply supports Windows 11 24H2 or later on x64 and ARM64 in English, Japanese, and Simplified Chinese.
## License

Snaply works entirely on the local machine. It has no telemetry, network access, background service, tray process, global hotkey, or updater.
Apache-2.0. See [LICENSE](LICENSE).
22 changes: 10 additions & 12 deletions src/Snaply.App/Controls/ZoomableImage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,16 @@
namespace Snaply.Controls;

/// <summary>
/// A self-contained zoom/pan image viewer. The image is laid out <see cref="Stretch.Uniform"/>
/// so at scale <c>1.0</c> it already fits the viewport (the "fit" baseline); the element's
/// Composition <see cref="Visual.Scale"/>/<see cref="Visual.Offset"/> then scale/translate from
/// there. Mouse-wheel zooms about the cursor, left-drag pans, double-tap resets to fit, and a
/// floating fit-to-view button (bottom-right) resets the zoom.
/// A zoom/pan image viewer. The image is laid out <see cref="Stretch.Uniform"/>, so at scale
/// <c>1.0</c> it fits the viewport (the fit baseline and the scale floor); the element's Composition
/// <see cref="Visual.Scale"/>/<see cref="Visual.Offset"/> scale and translate from there. Mouse-wheel
/// zooms about the cursor, left-drag pans, and double-tap resets to fit.
/// </summary>
/// <remarks>
/// Zoom (wheel, Fit, double-tap) glides via GPU-composited spring animations, so rapid consecutive
/// notches retarget smoothly from the current in-flight value instead of snapping. Panning writes
/// the offset directly so the drag stays 1:1 with the cursor. The <c>_scale</c>/<c>_translateX</c>/
/// <c>_translateY</c> fields always hold the <em>target</em> state, so the cursor-anchor math stays
/// pixel-correct at rest.
/// Zoom glides via GPU-composited spring animations, so rapid consecutive notches retarget from the
/// in-flight value instead of snapping. Panning writes the offset directly to stay 1:1 with the
/// cursor. The <c>_scale</c>/<c>_translateX</c>/<c>_translateY</c> fields always hold the
/// <em>target</em> state, so the cursor-anchor math stays pixel-correct at rest.
/// </remarks>
internal sealed partial class ZoomableImage : UserControl
{
Expand All @@ -34,8 +32,8 @@ internal sealed partial class ZoomableImage : UserControl
typeof(ZoomableImage),
new PropertyMetadata(null, OnSourceChanged));

// Zoom limits and per-notch multiplier. Scale is relative to the fit baseline (1.0), which is
// also the floor: Fit is as small as it gets — the wheel only zooms in, never below Fit.
// Zoom limits. Scale is relative to the fit baseline (1.0), which is also the floor: the wheel
// only zooms in, never below fit.
private const double MinScale = 1.0;
private const double MaxScale = 8.0;
private const double ZoomStep = 1.1;
Expand Down
5 changes: 4 additions & 1 deletion src/Snaply.App/ImageExportService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ internal static async Task CopyAsync(RenderedImage image, CancellationToken canc
internal void OpenCaptureDirectory()
{
Directory.CreateDirectory(_captureDirectory);
Process.Start(new ProcessStartInfo("explorer.exe", _captureDirectory)
// Shell-execute the directory itself rather than passing it as an explorer.exe argument:
// an unquoted path that contains a space (e.g. a redirected Pictures folder) would be
// misparsed and open the wrong location.
Process.Start(new ProcessStartInfo(_captureDirectory)
{
UseShellExecute = true,
});
Expand Down
Loading
Loading