4 Commits
Author SHA1 Message Date
rogier 5bdc851fa0 1.1.3 2026-11-09 13:02:24 +01:00
rogierandCursor 9b9ac36261 1.1.2
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-09 00:38:41 +01:00
rogier ceec9ecdc0 Toon alleen de laatste versie. 2026-11-09 00:26:57 +01:00
rogierandCursor eb1fc40534 1.1.1
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-09 00:18:46 +01:00
15 changed files with 267 additions and 45 deletions

No files matched your search

+23
View File
@@ -2,6 +2,29 @@
Alle noemenswaardige wijzigingen in kniip staan in dit bestand. De nieuwste versie staat bovenaan.
## 1.1.3 - 2026-11-09
### Nieuw
- **Sneltoets voor het klembord.** In de tab **Sneltoetsen** kun je een combinatie zetten op **Vanaf klembord**. Die opent de afbeelding op het klembord in de editor. Standaard is er geen sneltoets. De gekozen combinatie staat ook bij **Bewerken vanaf klembord** in het systeemvakmenu.
- **Afbeelding naar de editor slepen.** Sleep een afbeeldingsbestand, of een afbeelding uit een ander programma, naar het kniip-venster. kniip vraagt daarna of je die afbeelding wilt openen, zodat een lopende bewerking niet zomaar verdwijnt.
## 1.1.2 - 2026-11-09
### Opgelost
- **Link kopiëren na uploaden.** Na het uploaden kon kniip de link soms niet op het klembord zetten en meldde dan dat het klembord bezet was. Dat gebeurde vooral als de klembordgeschiedenis van Windows (Win+V) of een ander klembordprogramma actief is. kniip zet de link nu op een directere manier op het klembord en probeert het langer opnieuw. Dit geldt ook voor het uploaden van screencasts en de test-upload in de instellingen.
## 1.1.1 - 2026-11-09
### Gewijzigd
- **Instellingen zonder scrollbalk.** Het instellingenvenster is iets hoger geworden, zodat elke tab, ook **Opslaan** met de nieuwe naamvelden, in één keer past. De changelog in de tab **Update** heeft daardoor ook meer ruimte.
### Opgelost
- De downloadlinks in de handleiding (README) wezen naar een verkeerde tag en gaven een foutmelding.
## 1.1.0 - 2026-11-09
### Nieuw
+5 -5
View File
@@ -24,9 +24,9 @@ kniip is een annotatie-editor die naast het Windows Knipprogramma werkt. Maak ee
| Versie | Installer |
| ------ | --------- |
| Nieuwste versie | [Alle releases](https://git.hibbit.net/rogier/kniip/releases) |
| 1.1.0 | [Kniip-Setup-1.1.0.exe](https://git.hibbit.net/rogier/kniip/releases/download/v1.1.0/Kniip-Setup-1.1.0.exe) |
| 1.0.0 | [Kniip-Setup-1.0.0.exe](https://git.hibbit.net/rogier/kniip/releases/download/v1.0.0/Kniip-Setup-1.0.0.exe) |
| 1.1.3 | [Kniip-Setup-1.1.3.exe](https://git.hibbit.net/rogier/kniip/releases/download/1.1.3/Kniip-Setup-1.1.3.exe) |
Oudere versies vind je bij [alle releases](https://git.hibbit.net/rogier/kniip/releases).
## Installeren
@@ -70,7 +70,7 @@ Vereisten:
Bouwen:
```powershell
.\build.ps1 # versie 1.1.0
.\build.ps1 # versie 1.1.3
.\build.ps1 -Version 1.2.0 # andere versie
```
@@ -85,7 +85,7 @@ Het script levert op:
1. Bouw met `.\build.ps1 -Version <versie>`.
2. Maak op [git.hibbit.net](https://git.hibbit.net/rogier/kniip/releases) een release met tag `v<versie>` en voeg `artifacts\Kniip-Setup-<versie>.exe` toe als bijlage.
3. Schrijf de wijzigingen in markdown in de beschrijving van de release. kniip toont die als changelog onder **Instellingen > Update**.
4. Voeg de nieuwe versie toe aan de tabel onder [Downloaden](#downloaden).
4. Vervang de versie en de link in de tabel onder [Downloaden](#downloaden) door de nieuwe versie.
### Updaten vanuit kniip
+1 -1
View File
@@ -7,7 +7,7 @@
# .\build.ps1 -Version 1.2.0
param(
[string]$Version = "1.1.0",
[string]$Version = "1.1.3",
[string]$Runtime = "win-x64"
)
+1 -1
View File
@@ -5,7 +5,7 @@
#define AppName "kniip"
#define AppExe "Kniip.exe"
#ifndef AppVersion
#define AppVersion "1.1.0"
#define AppVersion "1.1.3"
#endif
#ifndef PublishDir
#define PublishDir "..\artifacts\publish"
+2 -1
View File
@@ -298,11 +298,12 @@ public partial class App : Application
Bind(Settings.WindowHotkey, BeginWindowSnip);
Bind(Settings.RecordHotkey, () => Recorder.Toggle(ScreencastFormat.Mp4));
Bind(Settings.GifHotkey, () => Recorder.Toggle(ScreencastFormat.Gif));
Bind(Settings.ClipboardHotkey, OpenFromClipboard);
HotkeyHook.Bindings = bindings;
}
_tray?.SetHotkeys(HotkeyDisplay(Settings.SnipHotkey), HotkeyDisplay(Settings.WindowHotkey),
HotkeyDisplay(Settings.RecordHotkey), HotkeyDisplay(Settings.GifHotkey));
HotkeyDisplay(Settings.RecordHotkey), HotkeyDisplay(Settings.GifHotkey), HotkeyDisplay(Settings.ClipboardHotkey));
}
/// <summary>Start een screencast: eerst het gebied kiezen, daarna de knoppenbalk.</summary>
+63 -24
View File
@@ -41,33 +41,14 @@ internal static class ClipboardImageReader
return null;
}
if (includeFiles && TryReadFile(data) is { } fromFile)
if (ReadData(data, includeFiles) is { } image)
{
return fromFile;
return image;
}
if (data.GetDataPresent("PNG") && data.GetData("PNG") is MemoryStream png)
if (Clipboard.ContainsImage() && Clipboard.GetImage() is { } bitmap)
{
return PixelUtil.Decode(png);
}
if (data.GetDataPresent(DataFormats.Dib) && data.GetData(DataFormats.Dib) is MemoryStream dib)
{
BitmapSource? fromDib = FromDib(dib.ToArray());
if (fromDib != null)
{
return fromDib;
}
}
if (Clipboard.ContainsImage())
{
BitmapSource? image = Clipboard.GetImage();
if (image != null)
{
var copy = PixelUtil.CreateBitmap(PixelUtil.ToOpaqueBgra(image), image.PixelWidth, image.PixelHeight);
return copy;
}
return OpaqueCopy(bitmap);
}
}
catch (Exception ex) when (ex is COMException or ExternalException or NotSupportedException or FileFormatException)
@@ -78,6 +59,64 @@ internal static class ClipboardImageReader
return null;
}
/// <summary>Snelle controle tijdens het slepen, zonder te decoderen.</summary>
public static bool CanDrop(IDataObject data)
{
try
{
return data.GetDataPresent("PNG") || data.GetDataPresent(DataFormats.Dib) || data.GetDataPresent(DataFormats.Bitmap)
|| (data.GetData(DataFormats.FileDrop) is string[] files && Array.Exists(files, IsImageFile));
}
catch (Exception ex) when (ex is COMException or ExternalException)
{
return false;
}
}
/// <summary>Afbeelding uit iets dat in kniip is neergezet: een afbeeldingsbestand of afbeeldingsdata.</summary>
public static BitmapSource? FromDrop(IDataObject data)
{
try
{
if (ReadData(data, includeFiles: true) is { } image)
{
return image;
}
return data.GetData(DataFormats.Bitmap) is BitmapSource bitmap ? OpaqueCopy(bitmap) : null;
}
catch (Exception ex) when (ex is COMException or ExternalException or NotSupportedException or FileFormatException)
{
return null;
}
}
private static BitmapSource? ReadData(IDataObject data, bool includeFiles)
{
if (includeFiles && TryReadFile(data) is { } fromFile)
{
return fromFile;
}
if (data.GetDataPresent("PNG") && data.GetData("PNG") is MemoryStream png)
{
return PixelUtil.Decode(png);
}
if (data.GetDataPresent(DataFormats.Dib) && data.GetData(DataFormats.Dib) is MemoryStream dib)
{
return FromDib(dib.ToArray());
}
return null;
}
private static BitmapSource OpaqueCopy(BitmapSource image) =>
PixelUtil.CreateBitmap(PixelUtil.ToOpaqueBgra(image), image.PixelWidth, image.PixelHeight);
private static bool IsImageFile(string file) =>
Array.IndexOf(ImageExtensions, Path.GetExtension(file).ToLowerInvariant()) >= 0;
private static BitmapSource? TryReadFile(IDataObject data)
{
if (!data.GetDataPresent(DataFormats.FileDrop) || data.GetData(DataFormats.FileDrop) is not string[] files)
@@ -87,7 +126,7 @@ internal static class ClipboardImageReader
foreach (string file in files)
{
if (!File.Exists(file) || Array.IndexOf(ImageExtensions, Path.GetExtension(file).ToLowerInvariant()) < 0)
if (!File.Exists(file) || !IsImageFile(file))
{
continue;
}
+2 -1
View File
@@ -5,7 +5,8 @@
Title="kniip" Width="1180" Height="780" MinWidth="760" MinHeight="420"
WindowStartupLocation="CenterScreen" Background="{StaticResource WindowBrush}"
FontFamily="Segoe UI" FontSize="13" UseLayoutRounding="True"
Icon="pack://application:,,,/AppIcon.ico">
Icon="pack://application:,,,/AppIcon.ico"
AllowDrop="True" PreviewDragEnter="Window_PreviewDragOver" PreviewDragOver="Window_PreviewDragOver" PreviewDrop="Window_PreviewDrop">
<Window.Resources>
<local:CornerPreviewConverter x:Key="CornerPreview" />
<local:CursorIconConverter x:Key="CursorIcon" />
+30
View File
@@ -1595,6 +1595,36 @@ public partial class EditorWindow : Window
ReplaceWith(image, "Wil je doorgaan en de afbeelding van het klembord openen?");
}
private void Window_PreviewDragOver(object sender, DragEventArgs e)
{
if (!ClipboardImageReader.CanDrop(e.Data))
{
return;
}
e.Effects = DragDropEffects.Copy;
e.Handled = true;
}
private void Window_PreviewDrop(object sender, DragEventArgs e)
{
if (!ClipboardImageReader.CanDrop(e.Data))
{
return;
}
e.Handled = true;
BitmapSource? image = ClipboardImageReader.FromDrop(e.Data);
if (image == null)
{
ShowTemporaryHint("Deze afbeelding kon niet worden geopend.");
return;
}
// Pas na het slepen vragen: een dialoog tijdens Drop blokkeert Verkenner.
Dispatcher.BeginInvoke(() => ReplaceWith(image, "Wil je doorgaan en deze afbeelding openen?"));
}
private void OpenImageFile()
{
CommitTextEdit();
+84
View File
@@ -4,9 +4,11 @@ using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Kniip.Capture;
using Kniip.Interop;
namespace Kniip.Editor;
@@ -68,13 +70,95 @@ internal static class ClipboardWriter
return TrySet(data);
}
/// <summary>
/// Tekst gaat rechtstreeks via Win32 op het klembord. De OLE-route van WPF moet het klembord
/// daarna nog leegspoelen, en dat mislukt zodra een klembordbeheerder het meteen opent.
/// </summary>
public static bool SetText(string text)
{
if (TrySetUnicodeText(text))
{
return true;
}
var data = new DataObject();
data.SetText(text);
return TrySet(data);
}
private static HwndSource? _owner;
private static IntPtr OwnerHandle
{
get
{
// Een venster als eigenaar is verplicht: zonder eigenaar weigert SetClipboardData na EmptyClipboard.
_owner ??= new HwndSource(new HwndSourceParameters("KniipClipboardWriter")
{
ParentWindow = NativeMethods.HWND_MESSAGE,
WindowStyle = 0
});
return _owner.Handle;
}
}
private static bool TrySetUnicodeText(string text)
{
IntPtr owner = OwnerHandle;
for (int attempt = 0; attempt < 20; attempt++)
{
if (NativeMethods.OpenClipboard(owner))
{
try
{
return NativeMethods.EmptyClipboard() && PutUnicodeText(text);
}
finally
{
NativeMethods.CloseClipboard();
}
}
Thread.Sleep(50);
}
return false;
}
private static bool PutUnicodeText(string text)
{
IntPtr handle = NativeMethods.GlobalAlloc(NativeMethods.GMEM_MOVEABLE, (UIntPtr)((text.Length + 1) * 2));
if (handle == IntPtr.Zero)
{
return false;
}
IntPtr target = NativeMethods.GlobalLock(handle);
if (target == IntPtr.Zero)
{
NativeMethods.GlobalFree(handle);
return false;
}
try
{
Marshal.Copy(text.ToCharArray(), 0, target, text.Length);
Marshal.WriteInt16(target, text.Length * 2, 0);
}
finally
{
NativeMethods.GlobalUnlock(handle);
}
if (NativeMethods.SetClipboardData(NativeMethods.CF_UNICODETEXT, handle) == IntPtr.Zero)
{
NativeMethods.GlobalFree(handle);
return false;
}
return true;
}
private static bool TrySet(DataObject data)
{
for (int attempt = 0; attempt < 5; attempt++)
+5 -3
View File
@@ -9,6 +9,7 @@ internal sealed class TrayIcon : IDisposable
private readonly NotifyIcon _icon;
private const string RecordText = "Screencast opnemen (MP4)...";
private readonly ToolStripMenuItem _clipboardItem;
private readonly ToolStripMenuItem _pauseItem;
private readonly ToolStripMenuItem _snipItem;
private readonly ToolStripMenuItem _windowItem;
@@ -28,11 +29,11 @@ internal sealed class TrayIcon : IDisposable
public TrayIcon()
{
var menu = new ContextMenuStrip();
var openClipboard = new ToolStripMenuItem("Bewerken vanaf klembord", null, (_, _) => OpenFromClipboardRequested?.Invoke())
_clipboardItem = new ToolStripMenuItem("Bewerken vanaf klembord", null, (_, _) => OpenFromClipboardRequested?.Invoke())
{
Font = new Font(SystemFonts.MenuFont ?? Control.DefaultFont, FontStyle.Bold)
};
menu.Items.Add(openClipboard);
menu.Items.Add(_clipboardItem);
menu.Items.Add("Afbeelding openen...", null, (_, _) => OpenFileRequested?.Invoke());
menu.Items.Add(new ToolStripSeparator());
_snipItem = new ToolStripMenuItem("Rechthoek knippen", null, (_, _) => SnipRequested?.Invoke());
@@ -71,8 +72,9 @@ internal sealed class TrayIcon : IDisposable
public bool IsPaused => _pauseItem.Checked;
/// <summary>Toont de sneltoetsen naast de menu-items; null = geen sneltoets.</summary>
public void SetHotkeys(string? snip, string? window, string? record, string? gif)
public void SetHotkeys(string? snip, string? window, string? record, string? gif, string? clipboard)
{
_clipboardItem.ShortcutKeyDisplayString = clipboard;
_snipItem.ShortcutKeyDisplayString = snip;
_windowItem.ShortcutKeyDisplayString = window;
_recordItem.ShortcutKeyDisplayString = record;
+31
View File
@@ -26,6 +26,37 @@ internal static class NativeMethods
[DllImport("user32.dll")]
public static extern uint GetClipboardSequenceNumber();
public const uint CF_UNICODETEXT = 13;
public const uint GMEM_MOVEABLE = 0x0002;
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseClipboard();
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EmptyClipboard();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetClipboardData(uint format, IntPtr hMem);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GlobalAlloc(uint flags, UIntPtr bytes);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GlobalUnlock(IntPtr hMem);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GlobalFree(IntPtr hMem);
[DllImport("user32.dll")]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
+1 -1
View File
@@ -18,7 +18,7 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
<Product>kniip</Product>
<Description>Annotatie-editor naast het Windows Knipprogramma</Description>
<Version>1.1.0</Version>
<Version>1.1.3</Version>
<SatelliteResourceLanguages>nl;en</SatelliteResourceLanguages>
<!-- WPF regelt DPI via app.manifest; de WinForms-analyzer is hier niet van toepassing. -->
<NoWarn>$(NoWarn);WFAC010</NoWarn>
+3
View File
@@ -63,6 +63,9 @@ public sealed class AppSettings
/// <summary>Sneltoets voor een screencast die als GIF wordt aangeboden.</summary>
public string GifHotkey { get; set; } = "Ctrl+Shift+G";
/// <summary>Sneltoets om de afbeelding op het klembord in de editor te openen; standaard geen.</summary>
public string ClipboardHotkey { get; set; } = "";
public bool RecordSystemAudio { get; set; } = true;
public bool RecordMicrophone { get; set; }
public bool RecordShowCursor { get; set; } = true;
+12 -8
View File
@@ -1,7 +1,7 @@
<Window x:Class="Kniip.Settings.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="kniip instellingen" Width="720" Height="540" ResizeMode="NoResize"
Title="kniip instellingen" Width="720" Height="600" ResizeMode="NoResize"
WindowStartupLocation="CenterScreen" Background="{StaticResource WindowBrush}"
FontFamily="Segoe UI" FontSize="13" Icon="pack://application:,,,/AppIcon.ico">
<Window.Resources>
@@ -70,11 +70,8 @@
</Grid.ColumnDefinitions>
<TabPanel Grid.Column="0" IsItemsHost="True" VerticalAlignment="Top" Background="Transparent" />
<Border Grid.Column="1" Background="White" BorderBrush="#E5E5E5" BorderThickness="1"
CornerRadius="6" SnapsToDevicePixels="True">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
Padding="16,14">
<ContentPresenter ContentSource="SelectedContent" />
</ScrollViewer>
CornerRadius="6" SnapsToDevicePixels="True" Padding="16,14">
<ContentPresenter ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
@@ -130,13 +127,20 @@
<Button Tag="Record" Style="{StaticResource FlatButton}" Content="Geen" Click="ClearHotkey_Click" Margin="6,0,0,0"
ToolTip="Geen sneltoets gebruiken" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
<StackPanel Orientation="Horizontal" Margin="0,0,0,4">
<TextBlock Text="Screencast (GIF)" VerticalAlignment="Center" Width="150" />
<Button x:Name="GifHotkeyButton" Tag="Gif" Style="{StaticResource SecondaryButton}" Width="170" Margin="0"
Click="HotkeyButton_Click" ToolTip="Klik en druk daarna de gewenste toetscombinatie in" />
<Button Tag="Gif" Style="{StaticResource FlatButton}" Content="Geen" Click="ClearHotkey_Click" Margin="6,0,0,0"
ToolTip="Geen sneltoets gebruiken" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
<TextBlock Text="Vanaf klembord" VerticalAlignment="Center" Width="150" />
<Button x:Name="ClipboardHotkeyButton" Tag="Clipboard" Style="{StaticResource SecondaryButton}" Width="170" Margin="0"
Click="HotkeyButton_Click" ToolTip="Klik en druk daarna de gewenste toetscombinatie in; opent de afbeelding op het klembord" />
<Button Tag="Clipboard" Style="{StaticResource FlatButton}" Content="Geen" Click="ClearHotkey_Click" Margin="6,0,0,0"
ToolTip="Geen sneltoets gebruiken" />
</StackPanel>
<TextBlock x:Name="HotkeyHint" Style="{StaticResource Hint}" Margin="0,0,0,4" />
</StackPanel>
</TabItem>
@@ -267,7 +271,7 @@
</StackPanel>
<ProgressBar x:Name="DownloadProgress" Height="4" Minimum="0" Maximum="1" Margin="0,0,0,8"
Foreground="{StaticResource AccentBrush}" Visibility="Collapsed" />
<Border BorderBrush="{StaticResource DividerBrush}" BorderThickness="1" CornerRadius="4" Height="270">
<Border BorderBrush="{StaticResource DividerBrush}" BorderThickness="1" CornerRadius="4" Height="300">
<FlowDocumentScrollViewer x:Name="ChangelogViewer" VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled" IsToolBarVisible="False"
Zoom="100" MinZoom="100" MaxZoom="100" Focusable="False" />
@@ -26,6 +26,7 @@ public partial class SettingsWindow : Window
["Window"] = "venster knippen",
["Record"] = "screencast (MP4)",
["Gif"] = "screencast (GIF)",
["Clipboard"] = "bewerken vanaf klembord",
};
private readonly AppSettings _settings;
@@ -62,6 +63,7 @@ public partial class SettingsWindow : Window
["Window"] = WindowHotkeyButton,
["Record"] = RecordHotkeyButton,
["Gif"] = GifHotkeyButton,
["Clipboard"] = ClipboardHotkeyButton,
};
HotkeyHint.Text = App.Instance.HotkeyHook?.IsActive == true
@@ -105,6 +107,7 @@ public partial class SettingsWindow : Window
_hotkeys["Window"] = settings.WindowHotkey;
_hotkeys["Record"] = settings.RecordHotkey;
_hotkeys["Gif"] = settings.GifHotkey;
_hotkeys["Clipboard"] = settings.ClipboardHotkey;
foreach (string id in _hotkeys.Keys)
{
ShowHotkey(id);
@@ -438,6 +441,7 @@ public partial class SettingsWindow : Window
_settings.WindowHotkey = _hotkeys["Window"];
_settings.RecordHotkey = _hotkeys["Record"];
_settings.GifHotkey = _hotkeys["Gif"];
_settings.ClipboardHotkey = _hotkeys["Clipboard"];
_settings.RecordSystemAudio = SystemAudioBox.IsChecked == true;
_settings.RecordMicrophone = MicrophoneBox.IsChecked == true;
_settings.RecordShowCursor = ShowCursorBox.IsChecked == true;