diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..345c0cae9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,126 @@ +name: Build and Deploy + +on: + push: + branches: + - 'release/1.*' + +jobs: + build: + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '7.x' + + - name: Restore NuGet packages + run: msbuild src/Greenshot.sln /p:Configuration=Release /restore /t:PrepareForBuild + env: + Box13_ClientId: ${{ secrets.Box13_ClientId }} + Box13_ClientSecret: ${{ secrets.Box13_ClientSecret }} + DropBox13_ClientId: ${{ secrets.DropBox13_ClientId }} + DropBox13_ClientSecret: ${{ secrets.DropBox13_ClientSecret }} + Flickr_ClientId: ${{ secrets.Flickr_ClientId }} + Flickr_ClientSecret: ${{ secrets.Flickr_ClientSecret }} + Imgur13_ClientId: ${{ secrets.Imgur13_ClientId }} + Imgur13_ClientSecret: ${{ secrets.Imgur13_ClientSecret }} + Photobucket_ClientId: ${{ secrets.Photobucket_ClientId }} + Photobucket_ClientSecret: ${{ secrets.Photobucket_ClientSecret }} + Picasa_ClientId: ${{ secrets.Picasa_ClientId }} + Picasa_ClientSecret: ${{ secrets.Picasa_ClientSecret }} + + - name: Build and package + run: msbuild src/Greenshot.sln /p:Configuration=Release /t:Rebuild /v:normal + env: + Box13_ClientId: ${{ secrets.Box13_ClientId }} + Box13_ClientSecret: ${{ secrets.Box13_ClientSecret }} + DropBox13_ClientId: ${{ secrets.DropBox13_ClientId }} + DropBox13_ClientSecret: ${{ secrets.DropBox13_ClientSecret }} + Flickr_ClientId: ${{ secrets.Flickr_ClientId }} + Flickr_ClientSecret: ${{ secrets.Flickr_ClientSecret }} + Imgur13_ClientId: ${{ secrets.Imgur13_ClientId }} + Imgur13_ClientSecret: ${{ secrets.Imgur13_ClientSecret }} + Photobucket_ClientId: ${{ secrets.Photobucket_ClientId }} + Photobucket_ClientSecret: ${{ secrets.Photobucket_ClientSecret }} + Picasa_ClientId: ${{ secrets.Picasa_ClientId }} + Picasa_ClientSecret: ${{ secrets.Picasa_ClientSecret }} + + - name: Copy Files + run: | + mkdir -p ${{ github.workspace }}/artifacts + cp installer/Greenshot-INSTALLER-*.exe ${{ github.workspace }}/artifacts/ + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: drop + path: ${{ github.workspace }}/artifacts + +# deploy: +# runs-on: windows-latest +# needs: build +# +# steps: +# +# - name: Checkout repository +# uses: actions/checkout@v2 +# with: +# fetch-depth: 0 +# +# - name: Download build artifacts +# uses: actions/download-artifact@v4 +# with: +# name: drop Name of the artifact uploaded in previous steps +# path: drop Local folder where artifacts are downloaded +# +# - name: Extract version from file name +# id: extract_version +# run: | +# $file = Get-ChildItem drop -Filter "Greenshot-INSTALLER-*.exe" | Select-Object -First 1 +# if (-not $file) { +# throw "No matching file found in 'drop' directory." +# } +# if ($file.Name -match "Greenshot-INSTALLER-([\d\.]+).*\.exe") { +# echo "version=$($matches[1])" >> $Env:GITHUB_OUTPUT +# } else { +# throw "Version number could not be extracted from file name: $($file.Name)" +# } +# shell: pwsh +# +# - name: Create tag +# run: | +# git config user.name "github-actions[bot]" +# git config user.email "github-actions[bot]@users.noreply.github.com" +# git tag -a "v${{ steps.extract_version.outputs.version }}" -m "v${{ steps.extract_version.outputs.version }}" +# git push origin "v${{ steps.extract_version.outputs.version }}" +# +# - name: Create GitHub Release +# uses: softprops/action-gh-release@v2 +# with: +# name: "Greenshot ${{ steps.extract_version.outputs.version }} unstable" +# tag_name: "v${{ steps.extract_version.outputs.version }}" +# files: drop/*.exe +# generate_release_notes: true +# draft: true +# prerelease: true +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# +# - name: Trigger GitHub Pages rebuild +# shell: bash +# run: | +# curl -X POST \ +# -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ +# -H "Accept: application/vnd.github+json" \ +# https://api.github.com/repos/${{ github.repository }}/pages/builds + diff --git a/.github/workflows/update-gh-pages.yml b/.github/workflows/update-gh-pages.yml new file mode 100644 index 000000000..5026ffb18 --- /dev/null +++ b/.github/workflows/update-gh-pages.yml @@ -0,0 +1,18 @@ +name: Update GitHub Pages + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + update-gh-pages: + runs-on: ubuntu-latest + steps: + - name: Trigger GitHub Pages rebuild + shell: bash + run: | + curl -X POST \ + -H "Authorization: Bearer ${{ secrets.GH_PAGES_TOKEN }}" \ + -H "Accept: application/vnd.github+json" \ + https://api.github.com/repos/${{ github.repository }}/pages/builds diff --git a/.gitignore b/.gitignore index 3afd2d7ab..7e96d5501 100644 --- a/.gitignore +++ b/.gitignore @@ -214,4 +214,6 @@ ModelManifest.xml *.credentials.cs # Rider files -.idea \ No newline at end of file +.idea + +/installer/Greenshot-INSTALLER-*.exe diff --git a/README.md b/README.md index 1f47983e3..90fff9b6f 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,9 @@ Being easy to understand and configurable, Greenshot is an efficient tool for pr About this repository --------------------- This repository is for Greenshot 1.3, currently in development, but is the next planned release + +Releases +-------- + +You can find a list of all releases (stable and unstable) in the [Github releases](https://github.com/greenshot/greenshot/releases) or in the [version history on our website](https://getgreenshot.org/version-history/). +The [downloads page on our website](https://getgreenshot.org/downloads/) always links to the latest stable release. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..324758e42 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +If you think you found a security issue in Greenshot, please report it responsibly [in our security section](https://github.com/greenshot/greenshot/security). We try to look into it as soon as possible - please give us some time for reaction, though. diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 558d40760..000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,106 +0,0 @@ -# .NET Desktop -# Build and run tests for .NET Desktop or Windows classic desktop solutions. -# Add steps that publish symbols, save build artifacts, and more: -# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net - -trigger: - batch: true - branches: - include: - - 'release/1.*' - exclude: - - 'develop' - -stages: -- stage: Build - jobs: - - job: Build - variables: - - group: 'Plug-in Credentials' - - name: solution - value: 'src/Greenshot.sln' - - name: buildPlatform - value: 'Any CPU' - - name: buildConfiguration - value: 'Release' - - pool: - vmImage: 'Windows-latest' - - steps: - - task: MSBuild@1 - displayName: Restore nuget packages and generate credential templates - inputs: - solution: '$(solution)' - platform: $(buildPlatform) - configuration: $(buildConfiguration) - msbuildArguments: '/restore /t:PrepareForBuild' - - - task: MSBuild@1 - displayName: Build and package - inputs: - solution: '$(solution)' - platform: $(buildPlatform) - configuration: $(buildConfiguration) - env: - Box13_ClientId: $(Box13_ClientId) - Box13_ClientSecret: $(Box13_ClientSecret) - DropBox13_ClientId: $(DropBox13_ClientId) - DropBox13_ClientSecret: $(DropBox13_ClientSecret) - Flickr_ClientId: $(Flickr_ClientId) - Flickr_ClientSecret: $(Flickr_ClientSecret) - Imgur13_ClientId: $(Imgur13_ClientId) - Imgur13_ClientSecret: $(Imgur13_ClientSecret) - Photobucket_ClientId: $(Photobucket_ClientId) - Photobucket_ClientSecret: $(Photobucket_ClientSecret) - Picasa_ClientId: $(Picasa_ClientId) - Picasa_ClientSecret: $(Picasa_ClientSecret) - - - task: CopyFiles@2 - displayName: 'Copy Files to: $(build.artifactstagingdirectory)' - inputs: - SourceFolder: '$(Build.SourcesDirectory)\installer' - Contents: Greenshot-INSTALLER-*.exe - TargetFolder: '$(build.artifactstagingdirectory)' - flattenFolders: true - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: drop' - inputs: - PathtoPublish: '$(build.artifactstagingdirectory)' - -- stage: Deploy - jobs: - - deployment: GitHub_Release - pool: - vmImage: 'Windows-latest' - - environment: 'GitHub Release' - strategy: - # default deployment strategy - runOnce: - deploy: - steps: - - download: current - artifact: drop - - # Create a GitHub release - - task: GitHubRelease@0 - inputs: - gitHubConnection: GitHub Release - repositoryName: '$(Build.Repository.Name)' - action: 'create' # Options: create, edit, delete - target: '$(Build.SourceVersion)' # Required when action == Create || Action == Edit - tagSource: 'manual' # Required when action == Create# Options: auto, manual - tag: 'v$(Build.BuildNumber)' # Required when action == Edit || Action == Delete || TagSource == Manual - title: Greenshot $(Build.BuildNumber) unstable # Optional - #releaseNotesSource: 'file' # Optional. Options: file, input - #releaseNotesFile: # Optional - #releaseNotes: # Optional - assets: '$(Pipeline.Workspace)/drop/*.exe' - #assetUploadMode: 'delete' # Optional. Options: delete, replace - isDraft: true # Optional - isPreRelease: true # Optional - addChangeLog: true # Optional - #compareWith: 'lastFullRelease' # Required when addChangeLog == True. Options: lastFullRelease, lastRelease, lastReleaseByTag - #releaseTag: # Required when compareWith == LastReleaseByTag diff --git a/build-and-deploy.ps1 b/build-and-deploy.ps1 new file mode 100644 index 000000000..7645259c5 --- /dev/null +++ b/build-and-deploy.ps1 @@ -0,0 +1,149 @@ +# USAGE +# * Enable script execution in Powershell: 'Set-ExecutionPolicy RemoteSigned' +# * Create a GitHub personal access token (PAT) for greenshot repository +# * user must be owner of the repository +# * token needs read and write permissions ""for Contents"" and ""Pages"" +# * Execute the script and paste your token + +# Prompt the user to securely input the Github token +$SecureToken = Read-Host "Please enter your GitHub personal access token" -AsSecureString +$ReleaseToken = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureToken)) + +# Variables +$RepoPath = "." # Replace with your local repo path +$ArtifactsPath = "$RepoPath\artifacts" +$SolutionFile = "$RepoPath\src\Greenshot.sln" + +# Step 0: Update Local Repository +git pull + +# Step 1: Restore NuGet Packages +Write-Host "Restoring NuGet packages..." +msbuild "$SolutionFile" /p:Configuration=Release /restore /t:PrepareForBuild +if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to restore NuGet packages." + exit $LASTEXITCODE +} + +# Step 2: Build and Package +Write-Host "Building and packaging the solution..." +msbuild "$SolutionFile" /p:Configuration=Release /t:Rebuild /v:normal +if ($LASTEXITCODE -ne 0) { + Write-Error "Build failed." + exit $LASTEXITCODE +} + +# Step 3: Copy Installer Files +Write-Host "Copying installer files..." +if (-not (Test-Path $ArtifactsPath)) { + New-Item -ItemType Directory -Force -Path $ArtifactsPath +} +Copy-Item "$RepoPath\installer\Greenshot-INSTALLER-*.exe" -Destination $ArtifactsPath -Force +if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to copy installer files." + exit $LASTEXITCODE +} + +# Step 4: Extract Version from File Name +Write-Host "Extracting version from installer file name..." +$InstallerFile = Get-ChildItem $ArtifactsPath -Filter "Greenshot-INSTALLER-*.exe" | Select-Object -Last 1 +if (-not $InstallerFile) { + Write-Error "No matching installer file found in '$ArtifactsPath'." + exit 1 +} + +if ($InstallerFile.Name -match "Greenshot-INSTALLER-([\d\.]+).*\.exe") { + $Version = $matches[1] + Write-Host "Extracted version: $Version" +} else { + Write-Error "Version number could not be extracted from file name: $($InstallerFile.Name)" + exit 1 +} + +# Step 5: Create Git Tag +Write-Host "Creating Git tag..." +cd $RepoPath +#git config user.name "local-script" +#git config user.email "local-script@example.com" +git tag -a "v$Version" -m "v$Version" +git push origin "v$Version" +if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to create Git tag." + exit $LASTEXITCODE +} + +# Step 6: Create GitHub Release +Write-Host "Creating GitHub release..." +$Headers = @{ + Authorization = "Bearer $ReleaseToken" + Accept = "application/vnd.github+json" +} +$ReleaseData = @{ + tag_name = "v$Version" + name = "Greenshot $Version unstable" + body = "Pre-release of Greenshot $Version." + draft = $true + prerelease = $true + generate_release_notes = $true +} +$ReleaseResponse = Invoke-RestMethod ` + -Uri "https://api.github.com/repos/greenshot/greenshot/releases" ` + -Method POST ` + -Headers $Headers ` + -Body (ConvertTo-Json $ReleaseData -Depth 10) + +if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to create GitHub release." + exit $LASTEXITCODE +} + +Write-Host "Release created successfully." + +# Get the release ID from the response +$ReleaseId = $ReleaseResponse.id +Write-Host "Release ID: $ReleaseId" + +# Step 7: Upload .exe File to Release +Write-Host "Uploading .exe file to GitHub release..." +$ExeFilePath = "$ArtifactsPath\$($InstallerFile.Name)" +if (-Not (Test-Path $ExeFilePath)) { + Write-Error "Built .exe file not found: $ExeFilePath" + exit 1 +} + +# GitHub API for uploading release assets +$UploadUrl = $ReleaseResponse.upload_url -replace "{.*}", "" + +# Upload the file +$FileHeaders = @{ + Authorization = "Bearer $ReleaseToken" + ContentType = "application/octet-stream" +} +$FileName = [System.IO.Path]::GetFileName($ExeFilePath) + +Invoke-RestMethod ` + -Uri "$($UploadUrl)?name=$FileName" ` + -Method POST ` + -Headers $FileHeaders ` + -InFile $ExeFilePath ` + -ContentType "application/octet-stream" + +if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to upload .exe file to release." + exit $LASTEXITCODE +} + +Write-Host "File uploaded successfully: $FileName" + +# Step 7: Trigger GitHub Pages Rebuild +#Write-Host "Triggering GitHub Pages rebuild..." +#Invoke-RestMethod ` +# -Uri "https://api.github.com/repos/greenshot/greenshot/pages/builds" ` +# -Method POST ` +# -Headers $Headers +#if ($LASTEXITCODE -ne 0) { +# Write-Error "Failed to trigger GitHub Pages rebuild." +# exit $LASTEXITCODE +#} +# +#Write-Host "GitHub Pages rebuild triggered successfully." \ No newline at end of file diff --git a/installer/innosetup/setup.iss b/installer/innosetup/setup.iss index bf49ea5ed..fb77b0103 100644 --- a/installer/innosetup/setup.iss +++ b/installer/innosetup/setup.iss @@ -140,7 +140,8 @@ SetupIconFile=..\..\src\Greenshot\icons\applicationIcon\icon.ico ; SignTool=SignTool sign /debug /fd sha1 /tr https://time.certum.pl /td sha1 $f ; Append a SHA256 to the previous SHA1 signature (this is what as does) ; SignTool=SignTool sign /debug /as /fd sha256 /tr https://time.certum.pl /td sha256 $f -; SignedUninstaller=yes +SignTool=SignTool sign /sha1 "{#GetEnv('CertumThumbprint')}" /tr http://time.certum.pl /td sha256 /fd sha256 /v $f +;SignedUninstaller=yes UninstallDisplayIcon={app}\{#ExeName}.exe Uninstallable=true VersionInfoCompany={#ExeName} @@ -181,6 +182,8 @@ Root: HKCU; Subkey: Software\Classes\.greenshot; ValueType: string; ValueName: " Root: HKCU; Subkey: Software\Classes\Greenshot; ValueType: string; ValueName: ""; ValueData: "Greenshot File"; Permissions: users-modify; Flags: uninsdeletevalue noerror; Check: IsRegularUser Root: HKCU; Subkey: Software\Classes\Greenshot\DefaultIcon; ValueType: string; ValueName: ""; ValueData: """{app}\Greenshot.EXE,0"""; Permissions: users-modify; Flags: uninsdeletevalue noerror; Check: IsRegularUser Root: HKCU; Subkey: Software\Classes\Greenshot\shell\open\command; ValueType: string; ValueName: ""; ValueData: """{app}\Greenshot.EXE"" --openfile ""%1"""; Permissions: users-modify; Flags: uninsdeletevalue noerror; Check: IsRegularUser +; Disable the default PRTSCR Snipping Tool in Windows 11 +Root: HKCU; Subkey: Control Panel\Keyboard; ValueType: dword; ValueName: "PrintScreenKeyForSnippingEnabled"; ValueData: "0"; Flags: uninsdeletevalue; Check: ShouldDisableSnippingTool ; HKEY_LOCAL_MACHINE - for all users when admin Root: HKLM; Subkey: Software\Classes\.greenshot; ValueType: string; ValueName: ""; ValueData: "Greenshot"; Permissions: admins-modify; Flags: uninsdeletevalue noerror; Check: not IsRegularUser Root: HKLM; Subkey: Software\Classes\Greenshot; ValueType: string; ValueName: ""; ValueData: "Greenshot File"; Permissions: admins-modify; Flags: uninsdeletevalue noerror; Check: not IsRegularUser @@ -269,6 +272,7 @@ en.win10=Windows 10 plug-in en.UninstallIconDescription=Uninstall en.ShowLicense=Show license en.ShowReadme=Show Readme +en.disablewin11snippingtool=Disable Win11 default PrtScr snipping tool de.confluence=Confluence Plug-in de.default=Standard installation @@ -281,6 +285,7 @@ de.optimize=Optimierung der Leistung, kann etwas dauern. de.startgreenshot={#ExeName} starten de.startup={#ExeName} starten wenn Windows hochfährt de.win10=Windows 10 Plug-in +de.disablewin11snippingtool=Deaktiviere das Standard Windows 11 Snipping Tool auf "Druck" es.confluence=Extensión para Confluence es.default=${default} @@ -482,6 +487,7 @@ Name: "compact"; Description: "{code:CompactInstall}" Name: "custom"; Description: "{code:CustomInstall}"; Flags: iscustom [Components] +Name: "disablesnippingtool"; Description: {cm:disablewin11snippingtool}; Flags: disablenouninstallwarning; Types: default full custom; Check: IsWindows11OrNewer() Name: "greenshot"; Description: "Greenshot"; Types: default full compact custom; Flags: fixed ;Name: "plugins\networkimport"; Description: "Network Import Plugin"; Types: full Name: "plugins\box"; Description: {cm:box}; Types: full custom; Flags: disablenouninstallwarning @@ -531,6 +537,7 @@ Name: "languages\ukUA"; Description: {cm:ukUA}; Types: full custom; Flags: disab Name: "languages\viVN"; Description: {cm:viVN}; Types: full custom; Flags: disablenouninstallwarning; Check: hasLanguageGroup('e') Name: "languages\zhCN"; Description: {cm:zhCN}; Types: full custom; Flags: disablenouninstallwarning; Check: hasLanguageGroup('a') Name: "languages\zhTW"; Description: {cm:zhTW}; Types: full custom; Flags: disablenouninstallwarning; Check: hasLanguageGroup('9') + [Code] // Do we have a regular user trying to install this? function IsRegularUser(): Boolean; @@ -745,6 +752,19 @@ begin Result := IsWindowsVersionOrNewer(10, 0); end; +function IsWindows11OrNewer: Boolean; +var + WindowsVersion: TWindowsVersion; +begin + GetWindowsVersionEx(WindowsVersion); + Result := (WindowsVersion.Major >= 10) and (WindowsVersion.Build >= 22000); +end; + +function ShouldDisableSnippingTool: Boolean; +begin + Result := IsComponentSelected('disablesnippingtool'); +end; + [Run] Filename: "{app}\{#ExeName}.exe"; Description: "{cm:startgreenshot}"; Parameters: "{code:GetParamsForGS}"; WorkingDir: "{app}"; Flags: nowait postinstall runasoriginaluser Filename: "https://getgreenshot.org/thank-you/?language={language}&version={#Version}"; Flags: shellexec runasoriginaluser diff --git a/nuget.config b/nuget.config new file mode 100644 index 000000000..554c2f634 --- /dev/null +++ b/nuget.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index f2ce406a1..caa6a3db3 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,13 +1,65 @@ + + + + + + + + + + (); + foreach (var line in InputLines) + { + string text = line.ItemSpec; + foreach (var token in Tokens) + { + string tokenName = token.ItemSpec; + // Skip if tokenName is null or empty + if (string.IsNullOrEmpty(tokenName)) + continue; + + string replacementValue = token.GetMetadata("ReplacementValue"); + if (!string.IsNullOrEmpty(replacementValue)) + { + string placeholder = "$"+ "{"+tokenName+"}"; // Token-Format wie $(Box13_ClientId) + text = text.Replace(placeholder, replacementValue); + } + } + output.Add(new Microsoft.Build.Utilities.TaskItem(text)); + } + OutputLines = output.ToArray(); + ]]> + + + - $(PkgMSBuildTasks)\tools\ + $(NuGetPackageRoot)msbuildtasks/1.5.0.235/tools/ - - + + - - + + + + + + + + + + + + - \ No newline at end of file + + + + + diff --git a/src/Greenshot.Editor/Drawing/FreehandContainer.cs b/src/Greenshot.Editor/Drawing/FreehandContainer.cs index b8a66be4e..9c66e1a8e 100644 --- a/src/Greenshot.Editor/Drawing/FreehandContainer.cs +++ b/src/Greenshot.Editor/Drawing/FreehandContainer.cs @@ -1,323 +1,325 @@ -/* - * Greenshot - a free and open source screenshot tool - * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom - * - * For more information see: https://getgreenshot.org/ - * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 1 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Runtime.Serialization; -using Dapplo.Windows.Common.Structs; -using Greenshot.Base.Interfaces; -using Greenshot.Base.Interfaces.Drawing; -using Greenshot.Editor.Drawing.Fields; -using Greenshot.Editor.Helpers; - -namespace Greenshot.Editor.Drawing -{ - /// - /// Description of PathContainer. - /// - [Serializable] - public class FreehandContainer : DrawableContainer - { - private static readonly float[] PointOffset = - { - 0.5f, 0.25f, 0.75f - }; - - [NonSerialized] private GraphicsPath freehandPath = new GraphicsPath(); - private NativeRect myBounds = NativeRect.Empty; - private NativePoint lastMouse = NativePoint.Empty; - private readonly List capturePoints = new List(); - private bool isRecalculated; - - /// - /// Constructor - /// - public FreehandContainer(ISurface parent) : base(parent) - { - Width = parent.Image.Width; - Height = parent.Image.Height; - Top = 0; - Left = 0; - } - - protected override void InitializeFields() - { - AddField(GetType(), FieldType.LINE_THICKNESS, 3); - AddField(GetType(), FieldType.LINE_COLOR, Color.Red); - } - - public override void Transform(Matrix matrix) - { - Point[] points = capturePoints.ToArray(); - - matrix.TransformPoints(points); - capturePoints.Clear(); - capturePoints.AddRange(points); - RecalculatePath(); - } - - protected override void OnDeserialized(StreamingContext context) - { - RecalculatePath(); - } - - /// - /// This Dispose is called from the Dispose and the Destructor. - /// - /// When disposing==true all non-managed resources should be freed too! - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - freehandPath?.Dispose(); - } - - freehandPath = null; - } - - /// - /// Called from Surface (the parent) when the drawing begins (mouse-down) - /// - /// true if the surface doesn't need to handle the event - public override bool HandleMouseDown(int mouseX, int mouseY) - { - lastMouse = new Point(mouseX, mouseY); - capturePoints.Add(lastMouse); - return true; - } - - /// - /// Called from Surface (the parent) if a mouse move is made while drawing - /// - /// true if the surface doesn't need to handle the event - public override bool HandleMouseMove(int mouseX, int mouseY) - { - Point previousPoint = capturePoints[capturePoints.Count - 1]; - - if (GeometryHelper.Distance2D(previousPoint.X, previousPoint.Y, mouseX, mouseY) >= 2 * EditorConfig.FreehandSensitivity) - { - capturePoints.Add(new Point(mouseX, mouseY)); - } - - if (GeometryHelper.Distance2D(lastMouse.X, lastMouse.Y, mouseX, mouseY) < EditorConfig.FreehandSensitivity) - { - return true; - } - - //path.AddCurve(new Point[]{lastMouse, new Point(mouseX, mouseY)}); - lastMouse = new Point(mouseX, mouseY); - freehandPath.AddLine(lastMouse, new Point(mouseX, mouseY)); - // Only re-calculate the bounds & redraw when we added something to the path - myBounds = Rectangle.Round(freehandPath.GetBounds()); - - Invalidate(); - return true; - } - - /// - /// Called when the surface finishes drawing the element - /// - public override void HandleMouseUp(int mouseX, int mouseY) - { - // Make sure we don't loose the ending point - if (GeometryHelper.Distance2D(lastMouse.X, lastMouse.Y, mouseX, mouseY) >= EditorConfig.FreehandSensitivity) - { - capturePoints.Add(new Point(mouseX, mouseY)); - } - - RecalculatePath(); - } - - /// - /// Here we recalculate the freehand path by smoothing out the lines with Beziers. - /// - private void RecalculatePath() - { - // Store the previous path, to dispose it later when we are finished - var previousFreehandPath = freehandPath; - var newFreehandPath = new GraphicsPath(); - - // Here we can put some cleanup... like losing all the uninteresting points. - if (capturePoints.Count >= 3) - { - int index = 0; - while ((capturePoints.Count - 1) % 3 != 0) - { - // duplicate points, first at 50% than 25% than 75% - capturePoints.Insert((int) (capturePoints.Count * PointOffset[index]), capturePoints[(int) (capturePoints.Count * PointOffset[index++])]); - } - - newFreehandPath.AddBeziers(capturePoints.ToArray()); - } - else if (capturePoints.Count == 2) - { - newFreehandPath.AddLine(capturePoints[0], capturePoints[1]); - } - - // Recalculate the bounds - myBounds = Rectangle.Round(newFreehandPath.GetBounds()); - - // assign - isRecalculated = true; - freehandPath = newFreehandPath; - - // dispose previous - previousFreehandPath?.Dispose(); - } - - /// - /// Do the drawing of the freehand "stroke" - /// - /// - /// - public override void Draw(Graphics graphics, RenderMode renderMode) - { - graphics.SmoothingMode = SmoothingMode.HighQuality; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - - int lineThickness = GetFieldValueAsInt(FieldType.LINE_THICKNESS); - Color lineColor = GetFieldValueAsColor(FieldType.LINE_COLOR); - using var pen = new Pen(lineColor) - { - Width = lineThickness - }; - if (!(pen.Width > 0)) - { - return; - } - - // Make sure the lines are nicely rounded - pen.EndCap = LineCap.Round; - pen.StartCap = LineCap.Round; - pen.LineJoin = LineJoin.Round; - // Move to where we need to draw - graphics.TranslateTransform(Left, Top); - var currentFreehandPath = freehandPath; - if (currentFreehandPath != null) - { - if (isRecalculated && Selected && renderMode == RenderMode.EDIT) - { - isRecalculated = false; - DrawSelectionBorder(graphics, pen, currentFreehandPath); - } - - graphics.DrawPath(pen, currentFreehandPath); - } - - // Move back, otherwise everything is shifted - graphics.TranslateTransform(-Left, -Top); - } - - /// - /// Draw a selectionborder around the freehand path - /// - /// Graphics - /// Pen - /// GraphicsPath - protected static void DrawSelectionBorder(Graphics graphics, Pen linePen, GraphicsPath path) - { - using var selectionPen = (Pen) linePen.Clone(); - using var selectionPath = (GraphicsPath) path.Clone(); - selectionPen.Width += 5; - selectionPen.Color = Color.FromArgb(120, Color.LightSeaGreen); - graphics.DrawPath(selectionPen, selectionPath); - selectionPath.Widen(selectionPen); - selectionPen.DashPattern = new float[] - { - 2, 2 - }; - selectionPen.Color = Color.LightSeaGreen; - selectionPen.Width = 1; - graphics.DrawPath(selectionPen, selectionPath); - } - - /// - /// Get the bounds in which we have something drawn, plus safety margin, these are not the normal bounds... - /// - public override NativeRect DrawingBounds - { - get - { - if (!myBounds.IsEmpty) - { - int lineThickness = Math.Max(10, GetFieldValueAsInt(FieldType.LINE_THICKNESS)); - int safetyMargin = 10; - return new NativeRect(myBounds.Left + Left - (safetyMargin + lineThickness), myBounds.Top + Top - (safetyMargin + lineThickness), - myBounds.Width + 2 * (lineThickness + safetyMargin), myBounds.Height + 2 * (lineThickness + safetyMargin)); - } - - if (_parent?.Image is Image image) - { - return new NativeRect(0, 0, image.Width, image.Height); - } - - return NativeRect.Empty; - } - } - - /// - /// FreehandContainer are regarded equal if they are of the same type and their paths are equal. - /// - /// object - /// bool - public override bool Equals(object obj) - { - bool ret = false; - if (obj == null || GetType() != obj.GetType()) - { - return false; - } - - if (obj is FreehandContainer other && Equals(freehandPath, other.freehandPath)) - { - ret = true; - } - - return ret; - } - - public override int GetHashCode() - { - return freehandPath?.GetHashCode() ?? 0; - } - - public override bool ClickableAt(int x, int y) - { - bool returnValue = base.ClickableAt(x, y); - if (returnValue) - { - int lineThickness = GetFieldValueAsInt(FieldType.LINE_THICKNESS); - using var pen = new Pen(Color.White) - { - Width = lineThickness + 10 - }; - returnValue = freehandPath.IsOutlineVisible(x - Left, y - Top, pen); - } - - return returnValue; - } - } +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: https://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Runtime.Serialization; +using Dapplo.Windows.Common.Structs; +using Greenshot.Base.Interfaces; +using Greenshot.Base.Interfaces.Drawing; +using Greenshot.Editor.Drawing.Fields; +using Greenshot.Editor.Helpers; + +namespace Greenshot.Editor.Drawing +{ + /// + /// Description of PathContainer. + /// + [Serializable] + public class FreehandContainer : DrawableContainer + { + private static readonly float[] PointOffset = + { + 0.5f, 0.25f, 0.75f + }; + + [NonSerialized] + private GraphicsPath freehandPath = new GraphicsPath(); + + private Rectangle myBounds = NativeRect.Empty; + private Point lastMouse = NativePoint.Empty; + private List capturePoints = new List(); + private bool isRecalculated; + + /// + /// Constructor + /// + public FreehandContainer(ISurface parent) : base(parent) + { + Width = parent.Image.Width; + Height = parent.Image.Height; + Top = 0; + Left = 0; + } + + protected override void InitializeFields() + { + AddField(GetType(), FieldType.LINE_THICKNESS, 3); + AddField(GetType(), FieldType.LINE_COLOR, Color.Red); + } + + public override void Transform(Matrix matrix) + { + Point[] points = capturePoints.ToArray(); + + matrix.TransformPoints(points); + capturePoints.Clear(); + capturePoints.AddRange(points); + RecalculatePath(); + } + + protected override void OnDeserialized(StreamingContext context) + { + RecalculatePath(); + } + + /// + /// This Dispose is called from the Dispose and the Destructor. + /// + /// When disposing==true all non-managed resources should be freed too! + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + { + freehandPath?.Dispose(); + } + + freehandPath = null; + } + + /// + /// Called from Surface (the parent) when the drawing begins (mouse-down) + /// + /// true if the surface doesn't need to handle the event + public override bool HandleMouseDown(int mouseX, int mouseY) + { + lastMouse = new Point(mouseX, mouseY); + capturePoints.Add(lastMouse); + return true; + } + + /// + /// Called from Surface (the parent) if a mouse move is made while drawing + /// + /// true if the surface doesn't need to handle the event + public override bool HandleMouseMove(int mouseX, int mouseY) + { + Point previousPoint = capturePoints[capturePoints.Count - 1]; + + if (GeometryHelper.Distance2D(previousPoint.X, previousPoint.Y, mouseX, mouseY) >= 2 * EditorConfig.FreehandSensitivity) + { + capturePoints.Add(new Point(mouseX, mouseY)); + } + + if (GeometryHelper.Distance2D(lastMouse.X, lastMouse.Y, mouseX, mouseY) < EditorConfig.FreehandSensitivity) + { + return true; + } + + //path.AddCurve(new Point[]{lastMouse, new Point(mouseX, mouseY)}); + lastMouse = new Point(mouseX, mouseY); + freehandPath.AddLine(lastMouse, new Point(mouseX, mouseY)); + // Only re-calculate the bounds & redraw when we added something to the path + myBounds = Rectangle.Round(freehandPath.GetBounds()); + + Invalidate(); + return true; + } + + /// + /// Called when the surface finishes drawing the element + /// + public override void HandleMouseUp(int mouseX, int mouseY) + { + // Make sure we don't loose the ending point + if (GeometryHelper.Distance2D(lastMouse.X, lastMouse.Y, mouseX, mouseY) >= EditorConfig.FreehandSensitivity) + { + capturePoints.Add(new Point(mouseX, mouseY)); + } + + RecalculatePath(); + } + + /// + /// Here we recalculate the freehand path by smoothing out the lines with Beziers. + /// + private void RecalculatePath() + { + // Store the previous path, to dispose it later when we are finished + var previousFreehandPath = freehandPath; + var newFreehandPath = new GraphicsPath(); + + // Here we can put some cleanup... like losing all the uninteresting points. + if (capturePoints.Count >= 3) + { + int index = 0; + while ((capturePoints.Count - 1) % 3 != 0) + { + // duplicate points, first at 50% than 25% than 75% + capturePoints.Insert((int) (capturePoints.Count * PointOffset[index]), capturePoints[(int) (capturePoints.Count * PointOffset[index++])]); + } + + newFreehandPath.AddBeziers(capturePoints.ToArray()); + } + else if (capturePoints.Count == 2) + { + newFreehandPath.AddLine(capturePoints[0], capturePoints[1]); + } + + // Recalculate the bounds + myBounds = Rectangle.Round(newFreehandPath.GetBounds()); + + // assign + isRecalculated = true; + freehandPath = newFreehandPath; + + // dispose previous + previousFreehandPath?.Dispose(); + } + + /// + /// Do the drawing of the freehand "stroke" + /// + /// + /// + public override void Draw(Graphics graphics, RenderMode renderMode) + { + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + + int lineThickness = GetFieldValueAsInt(FieldType.LINE_THICKNESS); + Color lineColor = GetFieldValueAsColor(FieldType.LINE_COLOR); + using var pen = new Pen(lineColor) + { + Width = lineThickness + }; + if (!(pen.Width > 0)) + { + return; + } + + // Make sure the lines are nicely rounded + pen.EndCap = LineCap.Round; + pen.StartCap = LineCap.Round; + pen.LineJoin = LineJoin.Round; + // Move to where we need to draw + graphics.TranslateTransform(Left, Top); + var currentFreehandPath = freehandPath; + if (currentFreehandPath != null) + { + if (isRecalculated && Selected && renderMode == RenderMode.EDIT) + { + isRecalculated = false; + DrawSelectionBorder(graphics, pen, currentFreehandPath); + } + + graphics.DrawPath(pen, currentFreehandPath); + } + + // Move back, otherwise everything is shifted + graphics.TranslateTransform(-Left, -Top); + } + + /// + /// Draw a selectionborder around the freehand path + /// + /// Graphics + /// Pen + /// GraphicsPath + protected static void DrawSelectionBorder(Graphics graphics, Pen linePen, GraphicsPath path) + { + using var selectionPen = (Pen) linePen.Clone(); + using var selectionPath = (GraphicsPath) path.Clone(); + selectionPen.Width += 5; + selectionPen.Color = Color.FromArgb(120, Color.LightSeaGreen); + graphics.DrawPath(selectionPen, selectionPath); + selectionPath.Widen(selectionPen); + selectionPen.DashPattern = new float[] + { + 2, 2 + }; + selectionPen.Color = Color.LightSeaGreen; + selectionPen.Width = 1; + graphics.DrawPath(selectionPen, selectionPath); + } + + /// + /// Get the bounds in which we have something drawn, plus safety margin, these are not the normal bounds... + /// + public override NativeRect DrawingBounds + { + get + { + if (!myBounds.IsEmpty) + { + int lineThickness = Math.Max(10, GetFieldValueAsInt(FieldType.LINE_THICKNESS)); + int safetyMargin = 10; + return new NativeRect(myBounds.Left + Left - (safetyMargin + lineThickness), myBounds.Top + Top - (safetyMargin + lineThickness), + myBounds.Width + 2 * (lineThickness + safetyMargin), myBounds.Height + 2 * (lineThickness + safetyMargin)); + } + + if (_parent?.Image is Image image) + { + return new NativeRect(0, 0, image.Width, image.Height); + } + + return NativeRect.Empty; + } + } + + /// + /// FreehandContainer are regarded equal if they are of the same type and their paths are equal. + /// + /// object + /// bool + public override bool Equals(object obj) + { + bool ret = false; + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + + if (obj is FreehandContainer other && Equals(freehandPath, other.freehandPath)) + { + ret = true; + } + + return ret; + } + + public override int GetHashCode() + { + return freehandPath?.GetHashCode() ?? 0; + } + + public override bool ClickableAt(int x, int y) + { + bool returnValue = base.ClickableAt(x, y); + if (returnValue) + { + int lineThickness = GetFieldValueAsInt(FieldType.LINE_THICKNESS); + using var pen = new Pen(Color.White) + { + Width = lineThickness + 10 + }; + returnValue = freehandPath.IsOutlineVisible(x - Left, y - Top, pen); + } + + return returnValue; + } + } } \ No newline at end of file diff --git a/src/Greenshot.Editor/Drawing/SpeechbubbleContainer.cs b/src/Greenshot.Editor/Drawing/SpeechbubbleContainer.cs index 22dd13379..e1f37544b 100644 --- a/src/Greenshot.Editor/Drawing/SpeechbubbleContainer.cs +++ b/src/Greenshot.Editor/Drawing/SpeechbubbleContainer.cs @@ -39,10 +39,10 @@ namespace Greenshot.Editor.Drawing [Serializable] public class SpeechbubbleContainer : TextContainer { - private NativePoint _initialGripperPoint; + private Point _initialGripperPoint; // Only used for serializing the TargetGripper location - private NativePoint _storedTargetGripperLocation; + private Point _storedTargetGripperLocation; /// /// Store the current location of the target gripper @@ -120,7 +120,8 @@ namespace Greenshot.Editor.Drawing int xOffset = leftAligned ? -20 : 20; int yOffset = topAligned ? -20 : 20; - NativePoint newGripperLocation = _initialGripperPoint.Offset(xOffset, yOffset); + NativePoint initialGripperPoint = _initialGripperPoint; + NativePoint newGripperLocation = initialGripperPoint.Offset(xOffset, yOffset); if (TargetAdorner.Location != newGripperLocation) { diff --git a/src/Greenshot.Editor/Drawing/Surface.cs b/src/Greenshot.Editor/Drawing/Surface.cs index f050374a6..6d47a67a6 100644 --- a/src/Greenshot.Editor/Drawing/Surface.cs +++ b/src/Greenshot.Editor/Drawing/Surface.cs @@ -28,6 +28,7 @@ using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Runtime.Serialization.Formatters.Binary; +using System.ServiceModel.Security; using System.Windows.Forms; using Dapplo.Windows.Common.Extensions; using Dapplo.Windows.Common.Structs; @@ -40,6 +41,7 @@ using Greenshot.Base.Interfaces.Drawing; using Greenshot.Base.Interfaces.Drawing.Adorners; using Greenshot.Editor.Configuration; using Greenshot.Editor.Drawing.Fields; +using Greenshot.Editor.Helpers; using Greenshot.Editor.Memento; using log4net; @@ -722,6 +724,7 @@ namespace Greenshot.Editor.Drawing try { BinaryFormatter binaryRead = new BinaryFormatter(); + binaryRead.Binder = new BinaryFormatterHelper(); IDrawableContainerList loadedElements = (IDrawableContainerList) binaryRead.Deserialize(streamRead); loadedElements.Parent = this; // Make sure the steplabels are sorted according to their number @@ -731,6 +734,10 @@ namespace Greenshot.Editor.Drawing SelectElements(loadedElements); FieldAggregator.BindElements(loadedElements); } + catch (SecurityAccessDeniedException) + { + throw; + } catch (Exception e) { LOG.Error("Error serializing elements from stream.", e); diff --git a/src/Greenshot.Editor/Drawing/SvgContainer.cs b/src/Greenshot.Editor/Drawing/SvgContainer.cs index 283f755e8..85b8cb43d 100644 --- a/src/Greenshot.Editor/Drawing/SvgContainer.cs +++ b/src/Greenshot.Editor/Drawing/SvgContainer.cs @@ -21,6 +21,7 @@ using System; using System.Drawing; +using System.IO; using Dapplo.Windows.Common.Structs; using Greenshot.Base.Core; using Greenshot.Base.Interfaces; @@ -34,14 +35,32 @@ namespace Greenshot.Editor.Drawing [Serializable] public class SvgContainer : VectorGraphicsContainer { - private readonly SvgDocument _svgDocument; + private MemoryStream _svgContent; - public SvgContainer(SvgDocument svgDocument, ISurface parent) : base(parent) + [NonSerialized] + private SvgDocument _svgDocument; + + public SvgContainer(Stream stream, ISurface parent) : base(parent) { - _svgDocument = svgDocument; - Size = new Size((int)svgDocument.Width, (int)svgDocument.Height); + _svgContent = new MemoryStream(); + stream.CopyTo(_svgContent); + Init(); + Size = new Size((int)_svgDocument.Width, (int)_svgDocument.Height); } - + + protected override void Init() + { + base.Init(); + // Do nothing when there is no content + if (_svgContent == null) + { + return; + } + _svgContent.Position = 0; + + _svgDocument = SvgDocument.Open(_svgContent); + } + protected override Image ComputeBitmap() { //var image = ImageHelper.CreateEmpty(Width, Height, PixelFormat.Format32bppArgb, Color.Transparent); diff --git a/src/Greenshot.Editor/Drawing/VectorGraphicsContainer.cs b/src/Greenshot.Editor/Drawing/VectorGraphicsContainer.cs index 45238caaf..43da94c7d 100644 --- a/src/Greenshot.Editor/Drawing/VectorGraphicsContainer.cs +++ b/src/Greenshot.Editor/Drawing/VectorGraphicsContainer.cs @@ -47,7 +47,8 @@ namespace Greenshot.Editor.Drawing /// This is the cached version of the bitmap, pre-rendered to save performance /// Do not serialized, it can be rebuild with other information. /// - [NonSerialized] private Image _cachedImage; + [NonSerialized] + private Image _cachedImage; /// /// Constructor takes care of calling Init diff --git a/src/Greenshot.Editor/FileFormatHandlers/SvgFileFormatHandler.cs b/src/Greenshot.Editor/FileFormatHandlers/SvgFileFormatHandler.cs index 14a33246e..76c5e85b9 100644 --- a/src/Greenshot.Editor/FileFormatHandlers/SvgFileFormatHandler.cs +++ b/src/Greenshot.Editor/FileFormatHandlers/SvgFileFormatHandler.cs @@ -1,89 +1,89 @@ -/* - * Greenshot - a free and open source screenshot tool - * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom - * - * For more information see: https://getgreenshot.org/ - * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 1 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using Greenshot.Base.Interfaces; -using Greenshot.Base.Interfaces.Drawing; -using Greenshot.Base.Interfaces.Plugin; -using Greenshot.Editor.Drawing; -using log4net; -using Svg; - -namespace Greenshot.Editor.FileFormatHandlers -{ - /// - /// This handled the loading of SVG images to the editor - /// - public class SvgFileFormatHandler : AbstractFileFormatHandler, IFileFormatHandler - { - private static readonly ILog Log = LogManager.GetLogger(typeof(SvgFileFormatHandler)); - private readonly IReadOnlyCollection _ourExtensions = new[] { ".svg" }; - - public SvgFileFormatHandler() - { - SupportedExtensions[FileFormatHandlerActions.LoadDrawableFromStream] = _ourExtensions; - SupportedExtensions[FileFormatHandlerActions.LoadFromStream] = _ourExtensions; - } - - public override bool TryLoadFromStream(Stream stream, string extension, out Bitmap bitmap) - { - var svgDocument = SvgDocument.Open(stream); - - try - { - bitmap = svgDocument.Draw(); - return true; - } - catch (Exception ex) - { - Log.Error("Can't load SVG", ex); - } - bitmap = null; - return false; - } - - public override bool TrySaveToStream(Bitmap bitmap, Stream destination, string extension, ISurface surface = null, SurfaceOutputSettings surfaceOutputSettings = null) - { - // TODO: Implement this - return false; - } - - public override IEnumerable LoadDrawablesFromStream(Stream stream, string extension, ISurface parent = null) - { - SvgDocument svgDocument = null; - try - { - svgDocument = SvgDocument.Open(stream); - } - catch (Exception ex) - { - Log.Error("Can't load SVG", ex); - } - if (svgDocument != null) - { - yield return new SvgContainer(svgDocument, parent); - } - } - } -} +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: https://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using Greenshot.Base.Interfaces; +using Greenshot.Base.Interfaces.Drawing; +using Greenshot.Base.Interfaces.Plugin; +using Greenshot.Editor.Drawing; +using log4net; +using Svg; + +namespace Greenshot.Editor.FileFormatHandlers +{ + /// + /// This handled the loading of SVG images to the editor + /// + public class SvgFileFormatHandler : AbstractFileFormatHandler, IFileFormatHandler + { + private static readonly ILog Log = LogManager.GetLogger(typeof(SvgFileFormatHandler)); + private readonly IReadOnlyCollection _ourExtensions = new[] { ".svg" }; + + public SvgFileFormatHandler() + { + SupportedExtensions[FileFormatHandlerActions.LoadDrawableFromStream] = _ourExtensions; + SupportedExtensions[FileFormatHandlerActions.LoadFromStream] = _ourExtensions; + } + + public override bool TryLoadFromStream(Stream stream, string extension, out Bitmap bitmap) + { + var svgDocument = SvgDocument.Open(stream); + + try + { + bitmap = svgDocument.Draw(); + return true; + } + catch (Exception ex) + { + Log.Error("Can't load SVG", ex); + } + bitmap = null; + return false; + } + + public override bool TrySaveToStream(Bitmap bitmap, Stream destination, string extension, ISurface surface = null, SurfaceOutputSettings surfaceOutputSettings = null) + { + // TODO: Implement this + return false; + } + + public override IEnumerable LoadDrawablesFromStream(Stream stream, string extension, ISurface parent = null) + { + SvgContainer svgContainer = null; + try + { + svgContainer = new SvgContainer(stream, parent); + } + catch (Exception ex) + { + Log.Error("Can't load SVG", ex); + } + if (svgContainer != null) + { + yield return svgContainer; + } + } + } +} diff --git a/src/Greenshot.Editor/Helpers/BinaryFormatterHelper.cs b/src/Greenshot.Editor/Helpers/BinaryFormatterHelper.cs new file mode 100644 index 000000000..279e8b1a5 --- /dev/null +++ b/src/Greenshot.Editor/Helpers/BinaryFormatterHelper.cs @@ -0,0 +1,123 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: https://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.ServiceModel.Security; +using Greenshot.Base.Interfaces.Drawing; +using Greenshot.Editor.Drawing; +using Greenshot.Editor.Drawing.Fields; +using Greenshot.Editor.Drawing.Filters; +using log4net; +using static Greenshot.Editor.Drawing.FilterContainer; + +namespace Greenshot.Editor.Helpers +{ + /// + /// This helps to map the serialization of the old .greenshot file to the newer. + /// It also prevents misuse. + /// + internal class BinaryFormatterHelper : SerializationBinder + { + private static readonly ILog LOG = LogManager.GetLogger(typeof(BinaryFormatterHelper)); + private static readonly IDictionary TypeMapper = new Dictionary + { + {"System.Guid",typeof(Guid) }, + {"System.Drawing.Rectangle",typeof(System.Drawing.Rectangle) }, + {"System.Drawing.Point",typeof(System.Drawing.Point) }, + {"System.Drawing.Color",typeof(System.Drawing.Color) }, + {"System.Drawing.Bitmap",typeof(System.Drawing.Bitmap) }, + {"System.Drawing.Icon",typeof(System.Drawing.Icon) }, + {"System.Drawing.Size",typeof(System.Drawing.Size) }, + {"System.IO.MemoryStream",typeof(System.IO.MemoryStream) }, + {"System.Drawing.StringAlignment",typeof(System.Drawing.StringAlignment) }, + {"System.Collections.Generic.List`1[[Greenshot.Base.Interfaces.Drawing.IFieldHolder", typeof(List)}, + {"System.Collections.Generic.List`1[[Greenshot.Base.Interfaces.Drawing.IField", typeof(List)}, + {"System.Collections.Generic.List`1[[System.Drawing.Point", typeof(List)}, + {"Greenshot.Editor.Drawing.ArrowContainer", typeof(ArrowContainer) }, + {"Greenshot.Editor.Drawing.LineContainer", typeof(LineContainer) }, + {"Greenshot.Editor.Drawing.TextContainer", typeof(TextContainer) }, + {"Greenshot.Editor.Drawing.SpeechbubbleContainer", typeof(SpeechbubbleContainer) }, + {"Greenshot.Editor.Drawing.RectangleContainer", typeof(RectangleContainer) }, + {"Greenshot.Editor.Drawing.EllipseContainer", typeof(EllipseContainer) }, + {"Greenshot.Editor.Drawing.FreehandContainer", typeof(FreehandContainer) }, + {"Greenshot.Editor.Drawing.HighlightContainer", typeof(HighlightContainer) }, + {"Greenshot.Editor.Drawing.IconContainer", typeof(IconContainer) }, + {"Greenshot.Editor.Drawing.ObfuscateContainer", typeof(ObfuscateContainer) }, + {"Greenshot.Editor.Drawing.StepLabelContainer", typeof(StepLabelContainer) }, + {"Greenshot.Editor.Drawing.SvgContainer", typeof(SvgContainer) }, + {"Greenshot.Editor.Drawing.VectorGraphicsContainer", typeof(VectorGraphicsContainer) }, + {"Greenshot.Editor.Drawing.MetafileContainer", typeof(MetafileContainer) }, + {"Greenshot.Editor.Drawing.ImageContainer", typeof(ImageContainer) }, + {"Greenshot.Editor.Drawing.FilterContainer", typeof(FilterContainer) }, + {"Greenshot.Editor.Drawing.DrawableContainer", typeof(DrawableContainer) }, + {"Greenshot.Editor.Drawing.DrawableContainerList", typeof(DrawableContainerList) }, + {"Greenshot.Editor.Drawing.CursorContainer", typeof(CursorContainer) }, + {"Greenshot.Editor.Drawing.Filters.HighlightFilter", typeof(HighlightFilter) }, + {"Greenshot.Editor.Drawing.Filters.GrayscaleFilter", typeof(GrayscaleFilter) }, + {"Greenshot.Editor.Drawing.Filters.MagnifierFilter", typeof(MagnifierFilter) }, + {"Greenshot.Editor.Drawing.Filters.BrightnessFilter", typeof(BrightnessFilter) }, + {"Greenshot.Editor.Drawing.Filters.BlurFilter", typeof(BlurFilter) }, + {"Greenshot.Editor.Drawing.Filters.PixelizationFilter", typeof(PixelizationFilter) }, + {"Greenshot.Base.Interfaces.Drawing.IDrawableContainer", typeof(IDrawableContainer) }, + {"Greenshot.Base.Interfaces.Drawing.EditStatus", typeof(EditStatus) }, + {"Greenshot.Base.Interfaces.Drawing.IFieldHolder", typeof(IFieldHolder) }, + {"Greenshot.Base.Interfaces.Drawing.IField", typeof(IField) }, + {"Greenshot.Base.Interfaces.Drawing.FieldFlag", typeof(FieldFlag) }, + {"Greenshot.Editor.Drawing.Fields.Field", typeof(Field) }, + {"Greenshot.Editor.Drawing.Fields.FieldType", typeof(FieldType) }, + {"Greenshot.Editor.Drawing.FilterContainer+PreparedFilter", typeof(PreparedFilter) }, + }; + + /// + /// Do the type mapping + /// + /// Assembly for the type that was serialized + /// Type that was serialized + /// Type which was mapped + /// If something smells fishy + public override Type BindToType(string assemblyName, string typeName) + { + if (string.IsNullOrEmpty(typeName)) + { + return null; + } + var typeNameCommaLocation = typeName.IndexOf(","); + var comparingTypeName = typeName.Substring(0, typeNameCommaLocation > 0 ? typeNameCommaLocation : typeName.Length); + + // Correct wrong types + comparingTypeName = comparingTypeName.Replace("Greenshot.Drawing", "Greenshot.Editor.Drawing"); + comparingTypeName = comparingTypeName.Replace("Greenshot.Plugin.Drawing", "Greenshot.Base.Interfaces.Drawing"); + comparingTypeName = comparingTypeName.Replace("GreenshotPlugin.Interfaces.Drawing", "Greenshot.Base.Interfaces.Drawing"); + comparingTypeName = comparingTypeName.Replace("Greenshot.Drawing.Fields", "Greenshot.Editor.Drawing.Fields"); + comparingTypeName = comparingTypeName.Replace("Greenshot.Drawing.Filters", "Greenshot.Editor.Drawing.Filters"); + + if (TypeMapper.TryGetValue(comparingTypeName, out var returnType)) + { + LOG.Info($"Mapped {assemblyName} - {typeName} to {returnType.FullName}"); + return returnType; + } + LOG.Warn($"Unexpected Greenshot type in .greenshot file detected, maybe vulnerability attack created with ysoserial? Suspicious type: {assemblyName} - {typeName}"); + throw new SecurityAccessDeniedException($"Suspicious type in .greenshot file: {assemblyName} - {typeName}"); + } + } +} diff --git a/src/Greenshot.Plugin.Box/Greenshot.Plugin.Box.Credentials.template b/src/Greenshot.Plugin.Box/Greenshot.Plugin.Box.Credentials.template index df6eaa0bd..da2dbc7ae 100644 --- a/src/Greenshot.Plugin.Box/Greenshot.Plugin.Box.Credentials.template +++ b/src/Greenshot.Plugin.Box/Greenshot.Plugin.Box.Credentials.template @@ -1,4 +1,4 @@ -/* +/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom * diff --git a/src/Greenshot.Plugin.Office/OfficeUtils.cs b/src/Greenshot.Plugin.Office/OfficeUtils.cs index 52ca8d7cb..305611c5d 100644 --- a/src/Greenshot.Plugin.Office/OfficeUtils.cs +++ b/src/Greenshot.Plugin.Office/OfficeUtils.cs @@ -1,43 +1,45 @@ using System.Linq; using Microsoft.Win32; -namespace Greenshot.Plugin.Office; - -/// -/// A small utility class for helping with office -/// -internal static class OfficeUtils +namespace Greenshot.Plugin.Office { - private static readonly string[] OfficeRootKeys = { @"SOFTWARE\Microsoft\Office", @"SOFTWARE\WOW6432Node\Microsoft\Office" }; /// - /// Get the path to the office exe + /// A small utility class for helping with office /// - /// Name of the office executable - public static string GetOfficeExePath(string exeName) + internal static class OfficeUtils { - string strKeyName = exeName switch - { - "WINWORD.EXE" => "Word", - "EXCEL.EXE" => "Excel", - "POWERPNT.EXE" => "PowerPoint", - "OUTLOOK.EXE" => "Outlook", - "ONENOTE.EXE" => "OneNote", - _ => "" - }; + private static readonly string[] OfficeRootKeys = { @"SOFTWARE\Microsoft\Office", @"SOFTWARE\WOW6432Node\Microsoft\Office" }; - foreach (string strRootKey in OfficeRootKeys) + /// + /// Get the path to the office exe + /// + /// Name of the office executable + public static string GetOfficeExePath(string exeName) { - using RegistryKey rootKey = Registry.LocalMachine.OpenSubKey(strRootKey); - if (rootKey is null) continue; - - foreach (string officeVersion in rootKey.GetSubKeyNames().Where(r => r.Contains(".")).Reverse()) + string strKeyName = exeName switch { - using RegistryKey installRootKey = Registry.LocalMachine.OpenSubKey($@"{strRootKey}\{officeVersion}\{strKeyName}\InstallRoot"); - if (installRootKey == null) continue; - return $@"{installRootKey.GetValue("Path")}\{exeName}"; + "WINWORD.EXE" => "Word", + "EXCEL.EXE" => "Excel", + "POWERPNT.EXE" => "PowerPoint", + "OUTLOOK.EXE" => "Outlook", + "ONENOTE.EXE" => "OneNote", + _ => "" + }; + + foreach (string strRootKey in OfficeRootKeys) + { + using RegistryKey rootKey = Registry.LocalMachine.OpenSubKey(strRootKey); + if (rootKey is null) continue; + + foreach (string officeVersion in rootKey.GetSubKeyNames().Where(r => r.Contains(".")).Reverse()) + { + using RegistryKey installRootKey = Registry.LocalMachine.OpenSubKey($@"{strRootKey}\{officeVersion}\{strKeyName}\InstallRoot"); + if (installRootKey == null) continue; + return $@"{installRootKey.GetValue("Path")}\{exeName}"; + } } + return null; } - return null; } -} +} \ No newline at end of file diff --git a/src/Greenshot.sln b/src/Greenshot.sln index 91ddf4314..b71e79bd3 100644 --- a/src/Greenshot.sln +++ b/src/Greenshot.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29728.190 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34009.444 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Greenshot", "Greenshot\Greenshot.csproj", "{CD642BF4-D815-4D67-A0B5-C69F0B8231AF}" ProjectSection(ProjectDependencies) = postProject @@ -48,6 +48,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\azure-pipelines.yml = ..\azure-pipelines.yml Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets + ..\.github\workflows\release.yml = ..\.github\workflows\release.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Greenshot.Editor", "Greenshot.Editor\Greenshot.Editor.csproj", "{148D3C8B-D6EC-4A7D-80E9-243A81F19DD2}" diff --git a/src/Greenshot/Greenshot.csproj b/src/Greenshot/Greenshot.csproj index fc4dd90d1..334bfc0f0 100644 --- a/src/Greenshot/Greenshot.csproj +++ b/src/Greenshot/Greenshot.csproj @@ -17,6 +17,7 @@ + @@ -75,6 +76,7 @@ +