Merge branch 'release/1.3' into bug/457_larger_steplabel

This commit is contained in:
jklingen 2025-05-21 07:38:40 +02:00 committed by GitHub
commit a12a1c040e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 1002 additions and 566 deletions

126
.github/workflows/release.yml vendored Normal file
View file

@ -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

18
.github/workflows/update-gh-pages.yml vendored Normal file
View file

@ -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

4
.gitignore vendored
View file

@ -214,4 +214,6 @@ ModelManifest.xml
*.credentials.cs *.credentials.cs
# Rider files # Rider files
.idea .idea
/installer/Greenshot-INSTALLER-*.exe

View file

@ -22,3 +22,9 @@ Being easy to understand and configurable, Greenshot is an efficient tool for pr
About this repository About this repository
--------------------- ---------------------
This repository is for Greenshot 1.3, currently in development, but is the next planned release 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.

5
SECURITY.md Normal file
View file

@ -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.

View file

@ -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

149
build-and-deploy.ps1 Normal file
View file

@ -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."

View file

@ -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 ; 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) ; 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 ; 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 UninstallDisplayIcon={app}\{#ExeName}.exe
Uninstallable=true Uninstallable=true
VersionInfoCompany={#ExeName} 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; 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\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 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 ; 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"; 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 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.UninstallIconDescription=Uninstall
en.ShowLicense=Show license en.ShowLicense=Show license
en.ShowReadme=Show Readme en.ShowReadme=Show Readme
en.disablewin11snippingtool=Disable Win11 default PrtScr snipping tool
de.confluence=Confluence Plug-in de.confluence=Confluence Plug-in
de.default=Standard installation de.default=Standard installation
@ -281,6 +285,7 @@ de.optimize=Optimierung der Leistung, kann etwas dauern.
de.startgreenshot={#ExeName} starten de.startgreenshot={#ExeName} starten
de.startup={#ExeName} starten wenn Windows hochfährt de.startup={#ExeName} starten wenn Windows hochfährt
de.win10=Windows 10 Plug-in de.win10=Windows 10 Plug-in
de.disablewin11snippingtool=Deaktiviere das Standard Windows 11 Snipping Tool auf "Druck"
es.confluence=Extensión para Confluence es.confluence=Extensión para Confluence
es.default=${default} es.default=${default}
@ -482,6 +487,7 @@ Name: "compact"; Description: "{code:CompactInstall}"
Name: "custom"; Description: "{code:CustomInstall}"; Flags: iscustom Name: "custom"; Description: "{code:CustomInstall}"; Flags: iscustom
[Components] [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: "greenshot"; Description: "Greenshot"; Types: default full compact custom; Flags: fixed
;Name: "plugins\networkimport"; Description: "Network Import Plugin"; Types: full ;Name: "plugins\networkimport"; Description: "Network Import Plugin"; Types: full
Name: "plugins\box"; Description: {cm:box}; Types: full custom; Flags: disablenouninstallwarning 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\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\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') Name: "languages\zhTW"; Description: {cm:zhTW}; Types: full custom; Flags: disablenouninstallwarning; Check: hasLanguageGroup('9')
[Code] [Code]
// Do we have a regular user trying to install this? // Do we have a regular user trying to install this?
function IsRegularUser(): Boolean; function IsRegularUser(): Boolean;
@ -745,6 +752,19 @@ begin
Result := IsWindowsVersionOrNewer(10, 0); Result := IsWindowsVersionOrNewer(10, 0);
end; 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] [Run]
Filename: "{app}\{#ExeName}.exe"; Description: "{cm:startgreenshot}"; Parameters: "{code:GetParamsForGS}"; WorkingDir: "{app}"; Flags: nowait postinstall runasoriginaluser 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 Filename: "https://getgreenshot.org/thank-you/?language={language}&version={#Version}"; Flags: shellexec runasoriginaluser

6
nuget.config Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View file

@ -1,13 +1,65 @@
<Project> <Project>
<UsingTask TaskName="ApplyTokenReplacements" TaskFactory="CodeTaskFactory" AssemblyName="Microsoft.Build.Tasks.Core">
<ParameterGroup>
<InputLines ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<Tokens ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<OutputLines ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Text.RegularExpressions" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var output = new List<ITaskItem>();
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();
]]>
</Code>
</Task>
</UsingTask>
<PropertyGroup Condition="Exists('$(ProjectDir)$(ProjectName).Credentials.template')"> <PropertyGroup Condition="Exists('$(ProjectDir)$(ProjectName).Credentials.template')">
<MSBuildCommunityTasksPath>$(PkgMSBuildTasks)\tools\</MSBuildCommunityTasksPath> <MSBuildCommunityTasksPath>$(NuGetPackageRoot)msbuildtasks/1.5.0.235/tools/</MSBuildCommunityTasksPath>
</PropertyGroup> </PropertyGroup>
<Import Project="$(MSBuildCommunityTasksPath)MSBuild.Community.Tasks.Targets" Condition="Exists('$(ProjectDir)$(ProjectName).Credentials.template')"/> <Import Project="$(MSBuildCommunityTasksPath)MSBuild.Community.Tasks.Targets" Condition="Exists('$(ProjectDir)$(ProjectName).Credentials.template')" />
<Target Name="ProcessTemplates" BeforeTargets="PrepareForBuild" Condition="Exists('$(ProjectDir)$(ProjectName).Credentials.template')"> <Target Name="ProcessTemplates" BeforeTargets="PrepareForBuild" Condition="Exists('$(ProjectDir)$(ProjectName).Credentials.template')">
<Message Text="Processing: $(ProjectDir)$(ProjectName).Credentials.template" Importance="high"/> <Message Text="Processing: $(ProjectDir)$(ProjectName).Credentials.template" Importance="high"/>
<TemplateFile Template="$(ProjectDir)$(ProjectName).Credentials.template" OutputFilename="$(ProjectDir)$(ProjectName).Credentials.cs" Tokens="@(Tokens)" />
<ReadLinesFromFile File="$(ProjectDir)$(ProjectName).Credentials.template">
<Output TaskParameter="Lines" ItemName="TemplateLines" />
</ReadLinesFromFile>
<ApplyTokenReplacements InputLines="@(TemplateLines)" Tokens="@(Tokens)">
<Output TaskParameter="OutputLines" ItemName="ProcessedLines" />
</ApplyTokenReplacements>
<WriteLinesToFile
File="$(ProjectDir)$(ProjectName).Credentials.cs"
Lines="@(ProcessedLines)"
Overwrite="true" />
</Target> </Target>
</Project> </Project>

View file

@ -1,323 +1,325 @@
/* /*
* Greenshot - a free and open source screenshot tool * Greenshot - a free and open source screenshot tool
* Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom
* *
* For more information see: https://getgreenshot.org/ * For more information see: https://getgreenshot.org/
* The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 1 of the License, or * the Free Software Foundation, either version 1 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using Dapplo.Windows.Common.Structs; using Dapplo.Windows.Common.Structs;
using Greenshot.Base.Interfaces; using Greenshot.Base.Interfaces;
using Greenshot.Base.Interfaces.Drawing; using Greenshot.Base.Interfaces.Drawing;
using Greenshot.Editor.Drawing.Fields; using Greenshot.Editor.Drawing.Fields;
using Greenshot.Editor.Helpers; using Greenshot.Editor.Helpers;
namespace Greenshot.Editor.Drawing namespace Greenshot.Editor.Drawing
{ {
/// <summary> /// <summary>
/// Description of PathContainer. /// Description of PathContainer.
/// </summary> /// </summary>
[Serializable] [Serializable]
public class FreehandContainer : DrawableContainer public class FreehandContainer : DrawableContainer
{ {
private static readonly float[] PointOffset = private static readonly float[] PointOffset =
{ {
0.5f, 0.25f, 0.75f 0.5f, 0.25f, 0.75f
}; };
[NonSerialized] private GraphicsPath freehandPath = new GraphicsPath(); [NonSerialized]
private NativeRect myBounds = NativeRect.Empty; private GraphicsPath freehandPath = new GraphicsPath();
private NativePoint lastMouse = NativePoint.Empty;
private readonly List<Point> capturePoints = new List<Point>(); private Rectangle myBounds = NativeRect.Empty;
private bool isRecalculated; private Point lastMouse = NativePoint.Empty;
private List<Point> capturePoints = new List<Point>();
/// <summary> private bool isRecalculated;
/// Constructor
/// </summary> /// <summary>
public FreehandContainer(ISurface parent) : base(parent) /// Constructor
{ /// </summary>
Width = parent.Image.Width; public FreehandContainer(ISurface parent) : base(parent)
Height = parent.Image.Height; {
Top = 0; Width = parent.Image.Width;
Left = 0; Height = parent.Image.Height;
} Top = 0;
Left = 0;
protected override void InitializeFields() }
{
AddField(GetType(), FieldType.LINE_THICKNESS, 3); protected override void InitializeFields()
AddField(GetType(), FieldType.LINE_COLOR, Color.Red); {
} AddField(GetType(), FieldType.LINE_THICKNESS, 3);
AddField(GetType(), FieldType.LINE_COLOR, Color.Red);
public override void Transform(Matrix matrix) }
{
Point[] points = capturePoints.ToArray(); public override void Transform(Matrix matrix)
{
matrix.TransformPoints(points); Point[] points = capturePoints.ToArray();
capturePoints.Clear();
capturePoints.AddRange(points); matrix.TransformPoints(points);
RecalculatePath(); capturePoints.Clear();
} capturePoints.AddRange(points);
RecalculatePath();
protected override void OnDeserialized(StreamingContext context) }
{
RecalculatePath(); protected override void OnDeserialized(StreamingContext context)
} {
RecalculatePath();
/// <summary> }
/// This Dispose is called from the Dispose and the Destructor.
/// </summary> /// <summary>
/// <param name="disposing">When disposing==true all non-managed resources should be freed too!</param> /// This Dispose is called from the Dispose and the Destructor.
protected override void Dispose(bool disposing) /// </summary>
{ /// <param name="disposing">When disposing==true all non-managed resources should be freed too!</param>
base.Dispose(disposing); protected override void Dispose(bool disposing)
if (disposing) {
{ base.Dispose(disposing);
freehandPath?.Dispose(); if (disposing)
} {
freehandPath?.Dispose();
freehandPath = null; }
}
freehandPath = null;
/// <summary> }
/// Called from Surface (the parent) when the drawing begins (mouse-down)
/// </summary> /// <summary>
/// <returns>true if the surface doesn't need to handle the event</returns> /// Called from Surface (the parent) when the drawing begins (mouse-down)
public override bool HandleMouseDown(int mouseX, int mouseY) /// </summary>
{ /// <returns>true if the surface doesn't need to handle the event</returns>
lastMouse = new Point(mouseX, mouseY); public override bool HandleMouseDown(int mouseX, int mouseY)
capturePoints.Add(lastMouse); {
return true; lastMouse = new Point(mouseX, mouseY);
} capturePoints.Add(lastMouse);
return true;
/// <summary> }
/// Called from Surface (the parent) if a mouse move is made while drawing
/// </summary> /// <summary>
/// <returns>true if the surface doesn't need to handle the event</returns> /// Called from Surface (the parent) if a mouse move is made while drawing
public override bool HandleMouseMove(int mouseX, int mouseY) /// </summary>
{ /// <returns>true if the surface doesn't need to handle the event</returns>
Point previousPoint = capturePoints[capturePoints.Count - 1]; public override bool HandleMouseMove(int mouseX, int mouseY)
{
if (GeometryHelper.Distance2D(previousPoint.X, previousPoint.Y, mouseX, mouseY) >= 2 * EditorConfig.FreehandSensitivity) Point previousPoint = capturePoints[capturePoints.Count - 1];
{
capturePoints.Add(new Point(mouseX, mouseY)); 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; 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)); //path.AddCurve(new Point[]{lastMouse, new Point(mouseX, mouseY)});
// Only re-calculate the bounds & redraw when we added something to the path lastMouse = new Point(mouseX, mouseY);
myBounds = Rectangle.Round(freehandPath.GetBounds()); freehandPath.AddLine(lastMouse, new Point(mouseX, mouseY));
// Only re-calculate the bounds & redraw when we added something to the path
Invalidate(); myBounds = Rectangle.Round(freehandPath.GetBounds());
return true;
} Invalidate();
return true;
/// <summary> }
/// Called when the surface finishes drawing the element
/// </summary> /// <summary>
public override void HandleMouseUp(int mouseX, int mouseY) /// Called when the surface finishes drawing the element
{ /// </summary>
// Make sure we don't loose the ending point public override void HandleMouseUp(int mouseX, int mouseY)
if (GeometryHelper.Distance2D(lastMouse.X, lastMouse.Y, mouseX, mouseY) >= EditorConfig.FreehandSensitivity) {
{ // Make sure we don't loose the ending point
capturePoints.Add(new Point(mouseX, mouseY)); if (GeometryHelper.Distance2D(lastMouse.X, lastMouse.Y, mouseX, mouseY) >= EditorConfig.FreehandSensitivity)
} {
capturePoints.Add(new Point(mouseX, mouseY));
RecalculatePath(); }
}
RecalculatePath();
/// <summary> }
/// Here we recalculate the freehand path by smoothing out the lines with Beziers.
/// </summary> /// <summary>
private void RecalculatePath() /// Here we recalculate the freehand path by smoothing out the lines with Beziers.
{ /// </summary>
// Store the previous path, to dispose it later when we are finished private void RecalculatePath()
var previousFreehandPath = freehandPath; {
var newFreehandPath = new GraphicsPath(); // Store the previous path, to dispose it later when we are finished
var previousFreehandPath = freehandPath;
// Here we can put some cleanup... like losing all the uninteresting points. var newFreehandPath = new GraphicsPath();
if (capturePoints.Count >= 3)
{ // Here we can put some cleanup... like losing all the uninteresting points.
int index = 0; if (capturePoints.Count >= 3)
while ((capturePoints.Count - 1) % 3 != 0) {
{ int index = 0;
// duplicate points, first at 50% than 25% than 75% while ((capturePoints.Count - 1) % 3 != 0)
capturePoints.Insert((int) (capturePoints.Count * PointOffset[index]), capturePoints[(int) (capturePoints.Count * PointOffset[index++])]); {
} // 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.AddBeziers(capturePoints.ToArray());
{ }
newFreehandPath.AddLine(capturePoints[0], capturePoints[1]); else if (capturePoints.Count == 2)
} {
newFreehandPath.AddLine(capturePoints[0], capturePoints[1]);
// Recalculate the bounds }
myBounds = Rectangle.Round(newFreehandPath.GetBounds());
// Recalculate the bounds
// assign myBounds = Rectangle.Round(newFreehandPath.GetBounds());
isRecalculated = true;
freehandPath = newFreehandPath; // assign
isRecalculated = true;
// dispose previous freehandPath = newFreehandPath;
previousFreehandPath?.Dispose();
} // dispose previous
previousFreehandPath?.Dispose();
/// <summary> }
/// Do the drawing of the freehand "stroke"
/// </summary> /// <summary>
/// <param name="graphics"></param> /// Do the drawing of the freehand "stroke"
/// <param name="renderMode"></param> /// </summary>
public override void Draw(Graphics graphics, RenderMode renderMode) /// <param name="graphics"></param>
{ /// <param name="renderMode"></param>
graphics.SmoothingMode = SmoothingMode.HighQuality; public override void Draw(Graphics graphics, RenderMode renderMode)
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; {
graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.CompositingQuality = CompositingQuality.HighQuality;
int lineThickness = GetFieldValueAsInt(FieldType.LINE_THICKNESS); graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
Color lineColor = GetFieldValueAsColor(FieldType.LINE_COLOR);
using var pen = new Pen(lineColor) int lineThickness = GetFieldValueAsInt(FieldType.LINE_THICKNESS);
{ Color lineColor = GetFieldValueAsColor(FieldType.LINE_COLOR);
Width = lineThickness using var pen = new Pen(lineColor)
}; {
if (!(pen.Width > 0)) Width = lineThickness
{ };
return; if (!(pen.Width > 0))
} {
return;
// Make sure the lines are nicely rounded }
pen.EndCap = LineCap.Round;
pen.StartCap = LineCap.Round; // Make sure the lines are nicely rounded
pen.LineJoin = LineJoin.Round; pen.EndCap = LineCap.Round;
// Move to where we need to draw pen.StartCap = LineCap.Round;
graphics.TranslateTransform(Left, Top); pen.LineJoin = LineJoin.Round;
var currentFreehandPath = freehandPath; // Move to where we need to draw
if (currentFreehandPath != null) graphics.TranslateTransform(Left, Top);
{ var currentFreehandPath = freehandPath;
if (isRecalculated && Selected && renderMode == RenderMode.EDIT) if (currentFreehandPath != null)
{ {
isRecalculated = false; if (isRecalculated && Selected && renderMode == RenderMode.EDIT)
DrawSelectionBorder(graphics, pen, currentFreehandPath); {
} isRecalculated = false;
DrawSelectionBorder(graphics, pen, currentFreehandPath);
graphics.DrawPath(pen, currentFreehandPath); }
}
graphics.DrawPath(pen, currentFreehandPath);
// Move back, otherwise everything is shifted }
graphics.TranslateTransform(-Left, -Top);
} // Move back, otherwise everything is shifted
graphics.TranslateTransform(-Left, -Top);
/// <summary> }
/// Draw a selectionborder around the freehand path
/// </summary> /// <summary>
/// <param name="graphics">Graphics</param> /// Draw a selectionborder around the freehand path
/// <param name="linePen">Pen</param> /// </summary>
/// <param name="path">GraphicsPath</param> /// <param name="graphics">Graphics</param>
protected static void DrawSelectionBorder(Graphics graphics, Pen linePen, GraphicsPath path) /// <param name="linePen">Pen</param>
{ /// <param name="path">GraphicsPath</param>
using var selectionPen = (Pen) linePen.Clone(); protected static void DrawSelectionBorder(Graphics graphics, Pen linePen, GraphicsPath path)
using var selectionPath = (GraphicsPath) path.Clone(); {
selectionPen.Width += 5; using var selectionPen = (Pen) linePen.Clone();
selectionPen.Color = Color.FromArgb(120, Color.LightSeaGreen); using var selectionPath = (GraphicsPath) path.Clone();
graphics.DrawPath(selectionPen, selectionPath); selectionPen.Width += 5;
selectionPath.Widen(selectionPen); selectionPen.Color = Color.FromArgb(120, Color.LightSeaGreen);
selectionPen.DashPattern = new float[] graphics.DrawPath(selectionPen, selectionPath);
{ selectionPath.Widen(selectionPen);
2, 2 selectionPen.DashPattern = new float[]
}; {
selectionPen.Color = Color.LightSeaGreen; 2, 2
selectionPen.Width = 1; };
graphics.DrawPath(selectionPen, selectionPath); selectionPen.Color = Color.LightSeaGreen;
} selectionPen.Width = 1;
graphics.DrawPath(selectionPen, selectionPath);
/// <summary> }
/// Get the bounds in which we have something drawn, plus safety margin, these are not the normal bounds...
/// </summary> /// <summary>
public override NativeRect DrawingBounds /// Get the bounds in which we have something drawn, plus safety margin, these are not the normal bounds...
{ /// </summary>
get public override NativeRect DrawingBounds
{ {
if (!myBounds.IsEmpty) get
{ {
int lineThickness = Math.Max(10, GetFieldValueAsInt(FieldType.LINE_THICKNESS)); if (!myBounds.IsEmpty)
int safetyMargin = 10; {
return new NativeRect(myBounds.Left + Left - (safetyMargin + lineThickness), myBounds.Top + Top - (safetyMargin + lineThickness), int lineThickness = Math.Max(10, GetFieldValueAsInt(FieldType.LINE_THICKNESS));
myBounds.Width + 2 * (lineThickness + safetyMargin), myBounds.Height + 2 * (lineThickness + safetyMargin)); 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); if (_parent?.Image is Image image)
} {
return new NativeRect(0, 0, image.Width, image.Height);
return NativeRect.Empty; }
}
} return NativeRect.Empty;
}
/// <summary> }
/// FreehandContainer are regarded equal if they are of the same type and their paths are equal.
/// </summary> /// <summary>
/// <param name="obj">object</param> /// FreehandContainer are regarded equal if they are of the same type and their paths are equal.
/// <returns>bool</returns> /// </summary>
public override bool Equals(object obj) /// <param name="obj">object</param>
{ /// <returns>bool</returns>
bool ret = false; public override bool Equals(object obj)
if (obj == null || GetType() != obj.GetType()) {
{ bool ret = false;
return false; if (obj == null || GetType() != obj.GetType())
} {
return false;
if (obj is FreehandContainer other && Equals(freehandPath, other.freehandPath)) }
{
ret = true; if (obj is FreehandContainer other && Equals(freehandPath, other.freehandPath))
} {
ret = true;
return ret; }
}
return ret;
public override int GetHashCode() }
{
return freehandPath?.GetHashCode() ?? 0; public override int GetHashCode()
} {
return freehandPath?.GetHashCode() ?? 0;
public override bool ClickableAt(int x, int y) }
{
bool returnValue = base.ClickableAt(x, y); public override bool ClickableAt(int x, int y)
if (returnValue) {
{ bool returnValue = base.ClickableAt(x, y);
int lineThickness = GetFieldValueAsInt(FieldType.LINE_THICKNESS); if (returnValue)
using var pen = new Pen(Color.White) {
{ int lineThickness = GetFieldValueAsInt(FieldType.LINE_THICKNESS);
Width = lineThickness + 10 using var pen = new Pen(Color.White)
}; {
returnValue = freehandPath.IsOutlineVisible(x - Left, y - Top, pen); Width = lineThickness + 10
} };
returnValue = freehandPath.IsOutlineVisible(x - Left, y - Top, pen);
return returnValue; }
}
} return returnValue;
}
}
} }

View file

@ -39,10 +39,10 @@ namespace Greenshot.Editor.Drawing
[Serializable] [Serializable]
public class SpeechbubbleContainer : TextContainer public class SpeechbubbleContainer : TextContainer
{ {
private NativePoint _initialGripperPoint; private Point _initialGripperPoint;
// Only used for serializing the TargetGripper location // Only used for serializing the TargetGripper location
private NativePoint _storedTargetGripperLocation; private Point _storedTargetGripperLocation;
/// <summary> /// <summary>
/// Store the current location of the target gripper /// Store the current location of the target gripper
@ -120,7 +120,8 @@ namespace Greenshot.Editor.Drawing
int xOffset = leftAligned ? -20 : 20; int xOffset = leftAligned ? -20 : 20;
int yOffset = topAligned ? -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) if (TargetAdorner.Location != newGripperLocation)
{ {

View file

@ -28,6 +28,7 @@ using System.Drawing.Imaging;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization.Formatters.Binary;
using System.ServiceModel.Security;
using System.Windows.Forms; using System.Windows.Forms;
using Dapplo.Windows.Common.Extensions; using Dapplo.Windows.Common.Extensions;
using Dapplo.Windows.Common.Structs; using Dapplo.Windows.Common.Structs;
@ -40,6 +41,7 @@ using Greenshot.Base.Interfaces.Drawing;
using Greenshot.Base.Interfaces.Drawing.Adorners; using Greenshot.Base.Interfaces.Drawing.Adorners;
using Greenshot.Editor.Configuration; using Greenshot.Editor.Configuration;
using Greenshot.Editor.Drawing.Fields; using Greenshot.Editor.Drawing.Fields;
using Greenshot.Editor.Helpers;
using Greenshot.Editor.Memento; using Greenshot.Editor.Memento;
using log4net; using log4net;
@ -722,6 +724,7 @@ namespace Greenshot.Editor.Drawing
try try
{ {
BinaryFormatter binaryRead = new BinaryFormatter(); BinaryFormatter binaryRead = new BinaryFormatter();
binaryRead.Binder = new BinaryFormatterHelper();
IDrawableContainerList loadedElements = (IDrawableContainerList) binaryRead.Deserialize(streamRead); IDrawableContainerList loadedElements = (IDrawableContainerList) binaryRead.Deserialize(streamRead);
loadedElements.Parent = this; loadedElements.Parent = this;
// Make sure the steplabels are sorted according to their number // Make sure the steplabels are sorted according to their number
@ -731,6 +734,10 @@ namespace Greenshot.Editor.Drawing
SelectElements(loadedElements); SelectElements(loadedElements);
FieldAggregator.BindElements(loadedElements); FieldAggregator.BindElements(loadedElements);
} }
catch (SecurityAccessDeniedException)
{
throw;
}
catch (Exception e) catch (Exception e)
{ {
LOG.Error("Error serializing elements from stream.", e); LOG.Error("Error serializing elements from stream.", e);

View file

@ -21,6 +21,7 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.IO;
using Dapplo.Windows.Common.Structs; using Dapplo.Windows.Common.Structs;
using Greenshot.Base.Core; using Greenshot.Base.Core;
using Greenshot.Base.Interfaces; using Greenshot.Base.Interfaces;
@ -34,14 +35,32 @@ namespace Greenshot.Editor.Drawing
[Serializable] [Serializable]
public class SvgContainer : VectorGraphicsContainer 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; _svgContent = new MemoryStream();
Size = new Size((int)svgDocument.Width, (int)svgDocument.Height); 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<SvgDocument>(_svgContent);
}
protected override Image ComputeBitmap() protected override Image ComputeBitmap()
{ {
//var image = ImageHelper.CreateEmpty(Width, Height, PixelFormat.Format32bppArgb, Color.Transparent); //var image = ImageHelper.CreateEmpty(Width, Height, PixelFormat.Format32bppArgb, Color.Transparent);

View file

@ -47,7 +47,8 @@ namespace Greenshot.Editor.Drawing
/// This is the cached version of the bitmap, pre-rendered to save performance /// This is the cached version of the bitmap, pre-rendered to save performance
/// Do not serialized, it can be rebuild with other information. /// Do not serialized, it can be rebuild with other information.
/// </summary> /// </summary>
[NonSerialized] private Image _cachedImage; [NonSerialized]
private Image _cachedImage;
/// <summary> /// <summary>
/// Constructor takes care of calling Init /// Constructor takes care of calling Init

View file

@ -1,89 +1,89 @@
/* /*
* Greenshot - a free and open source screenshot tool * Greenshot - a free and open source screenshot tool
* Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom
* *
* For more information see: https://getgreenshot.org/ * For more information see: https://getgreenshot.org/
* The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 1 of the License, or * the Free Software Foundation, either version 1 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using Greenshot.Base.Interfaces; using Greenshot.Base.Interfaces;
using Greenshot.Base.Interfaces.Drawing; using Greenshot.Base.Interfaces.Drawing;
using Greenshot.Base.Interfaces.Plugin; using Greenshot.Base.Interfaces.Plugin;
using Greenshot.Editor.Drawing; using Greenshot.Editor.Drawing;
using log4net; using log4net;
using Svg; using Svg;
namespace Greenshot.Editor.FileFormatHandlers namespace Greenshot.Editor.FileFormatHandlers
{ {
/// <summary> /// <summary>
/// This handled the loading of SVG images to the editor /// This handled the loading of SVG images to the editor
/// </summary> /// </summary>
public class SvgFileFormatHandler : AbstractFileFormatHandler, IFileFormatHandler public class SvgFileFormatHandler : AbstractFileFormatHandler, IFileFormatHandler
{ {
private static readonly ILog Log = LogManager.GetLogger(typeof(SvgFileFormatHandler)); private static readonly ILog Log = LogManager.GetLogger(typeof(SvgFileFormatHandler));
private readonly IReadOnlyCollection<string> _ourExtensions = new[] { ".svg" }; private readonly IReadOnlyCollection<string> _ourExtensions = new[] { ".svg" };
public SvgFileFormatHandler() public SvgFileFormatHandler()
{ {
SupportedExtensions[FileFormatHandlerActions.LoadDrawableFromStream] = _ourExtensions; SupportedExtensions[FileFormatHandlerActions.LoadDrawableFromStream] = _ourExtensions;
SupportedExtensions[FileFormatHandlerActions.LoadFromStream] = _ourExtensions; SupportedExtensions[FileFormatHandlerActions.LoadFromStream] = _ourExtensions;
} }
public override bool TryLoadFromStream(Stream stream, string extension, out Bitmap bitmap) public override bool TryLoadFromStream(Stream stream, string extension, out Bitmap bitmap)
{ {
var svgDocument = SvgDocument.Open<SvgDocument>(stream); var svgDocument = SvgDocument.Open<SvgDocument>(stream);
try try
{ {
bitmap = svgDocument.Draw(); bitmap = svgDocument.Draw();
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error("Can't load SVG", ex); Log.Error("Can't load SVG", ex);
} }
bitmap = null; bitmap = null;
return false; return false;
} }
public override bool TrySaveToStream(Bitmap bitmap, Stream destination, string extension, ISurface surface = null, SurfaceOutputSettings surfaceOutputSettings = null) public override bool TrySaveToStream(Bitmap bitmap, Stream destination, string extension, ISurface surface = null, SurfaceOutputSettings surfaceOutputSettings = null)
{ {
// TODO: Implement this // TODO: Implement this
return false; return false;
} }
public override IEnumerable<IDrawableContainer> LoadDrawablesFromStream(Stream stream, string extension, ISurface parent = null) public override IEnumerable<IDrawableContainer> LoadDrawablesFromStream(Stream stream, string extension, ISurface parent = null)
{ {
SvgDocument svgDocument = null; SvgContainer svgContainer = null;
try try
{ {
svgDocument = SvgDocument.Open<SvgDocument>(stream); svgContainer = new SvgContainer(stream, parent);
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error("Can't load SVG", ex); Log.Error("Can't load SVG", ex);
} }
if (svgDocument != null) if (svgContainer != null)
{ {
yield return new SvgContainer(svgDocument, parent); yield return svgContainer;
} }
} }
} }
} }

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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
{
/// <summary>
/// This helps to map the serialization of the old .greenshot file to the newer.
/// It also prevents misuse.
/// </summary>
internal class BinaryFormatterHelper : SerializationBinder
{
private static readonly ILog LOG = LogManager.GetLogger(typeof(BinaryFormatterHelper));
private static readonly IDictionary<string, Type> TypeMapper = new Dictionary<string, Type>
{
{"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<IFieldHolder>)},
{"System.Collections.Generic.List`1[[Greenshot.Base.Interfaces.Drawing.IField", typeof(List<IField>)},
{"System.Collections.Generic.List`1[[System.Drawing.Point", typeof(List<System.Drawing.Point>)},
{"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) },
};
/// <summary>
/// Do the type mapping
/// </summary>
/// <param name="assemblyName">Assembly for the type that was serialized</param>
/// <param name="typeName">Type that was serialized</param>
/// <returns>Type which was mapped</returns>
/// <exception cref="SecurityAccessDeniedException">If something smells fishy</exception>
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}");
}
}
}

View file

@ -1,4 +1,4 @@
/* /*
* Greenshot - a free and open source screenshot tool * Greenshot - a free and open source screenshot tool
* Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom
* *

View file

@ -1,43 +1,45 @@
using System.Linq; using System.Linq;
using Microsoft.Win32; using Microsoft.Win32;
namespace Greenshot.Plugin.Office; namespace Greenshot.Plugin.Office
/// <summary>
/// A small utility class for helping with office
/// </summary>
internal static class OfficeUtils
{ {
private static readonly string[] OfficeRootKeys = { @"SOFTWARE\Microsoft\Office", @"SOFTWARE\WOW6432Node\Microsoft\Office" };
/// <summary> /// <summary>
/// Get the path to the office exe /// A small utility class for helping with office
/// </summary> /// </summary>
/// <param name="exeName">Name of the office executable</param> internal static class OfficeUtils
public static string GetOfficeExePath(string exeName)
{ {
string strKeyName = exeName switch private static readonly string[] OfficeRootKeys = { @"SOFTWARE\Microsoft\Office", @"SOFTWARE\WOW6432Node\Microsoft\Office" };
{
"WINWORD.EXE" => "Word",
"EXCEL.EXE" => "Excel",
"POWERPNT.EXE" => "PowerPoint",
"OUTLOOK.EXE" => "Outlook",
"ONENOTE.EXE" => "OneNote",
_ => ""
};
foreach (string strRootKey in OfficeRootKeys) /// <summary>
/// Get the path to the office exe
/// </summary>
/// <param name="exeName">Name of the office executable</param>
public static string GetOfficeExePath(string exeName)
{ {
using RegistryKey rootKey = Registry.LocalMachine.OpenSubKey(strRootKey); string strKeyName = exeName switch
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"); "WINWORD.EXE" => "Word",
if (installRootKey == null) continue; "EXCEL.EXE" => "Excel",
return $@"{installRootKey.GetValue("Path")}\{exeName}"; "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;
} }
} }

View file

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 17
VisualStudioVersion = 16.0.29728.190 VisualStudioVersion = 17.7.34009.444
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Greenshot", "Greenshot\Greenshot.csproj", "{CD642BF4-D815-4D67-A0B5-C69F0B8231AF}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Greenshot", "Greenshot\Greenshot.csproj", "{CD642BF4-D815-4D67-A0B5-C69F0B8231AF}"
ProjectSection(ProjectDependencies) = postProject ProjectSection(ProjectDependencies) = postProject
@ -48,6 +48,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
..\azure-pipelines.yml = ..\azure-pipelines.yml ..\azure-pipelines.yml = ..\azure-pipelines.yml
Directory.Build.props = Directory.Build.props Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets Directory.Build.targets = Directory.Build.targets
..\.github\workflows\release.yml = ..\.github\workflows\release.yml
EndProjectSection EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Greenshot.Editor", "Greenshot.Editor\Greenshot.Editor.csproj", "{148D3C8B-D6EC-4A7D-80E9-243A81F19DD2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Greenshot.Editor", "Greenshot.Editor\Greenshot.Editor.csproj", "{148D3C8B-D6EC-4A7D-80E9-243A81F19DD2}"

View file

@ -17,6 +17,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.13.9" />
<PackageReference Include="Tools.InnoSetup" version="6.2.1" GeneratePathProperty="true" /> <PackageReference Include="Tools.InnoSetup" version="6.2.1" GeneratePathProperty="true" />
</ItemGroup> </ItemGroup>
@ -75,6 +76,7 @@
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)' == 'Release'"> <Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)' == 'Release'">
<SetEnvironmentVariableTask Name="BuildVersionSimple" Value="$(BuildVersionSimple)" /> <SetEnvironmentVariableTask Name="BuildVersionSimple" Value="$(BuildVersionSimple)" />
<SetEnvironmentVariableTask Name="AssemblyInformationalVersion" Value="$(AssemblyInformationalVersion)" /> <SetEnvironmentVariableTask Name="AssemblyInformationalVersion" Value="$(AssemblyInformationalVersion)" />
<Exec Command='signtool.exe sign /sha1 "$(CertumThumbprint)" /tr http://time.certum.pl /td sha256 /fd sha256 /v "$(OutDir)Greenshot.exe"' />
<Exec Command="$(PkgTools_InnoSetup)\tools\ISCC.exe $(SolutionDir)\..\installer\innosetup\setup.iss" /> <Exec Command="$(PkgTools_InnoSetup)\tools\ISCC.exe $(SolutionDir)\..\installer\innosetup\setup.iss" />
</Target> </Target>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'"> <PropertyGroup Condition="'$(Configuration)' == 'Debug'">