This commit is contained in:
rogier committed 2026-11-07 12:19:45 +01:00
commit d55d8bc32d
93 files changed
+13507

No files matched your search

@@ -0,0 +1,109 @@
---
name: Emoji stempel-tool
overview: "Voeg een Emoji-gereedschap toe aan de werkbalk van de editor. Je plaatst er een Twemoji-stempel mee op het knipsel. De kiezer werkt zoals emoji-mart: alle emoji, categorie-tabs, zoeken in het Nederlands en Engels, huidskleur en \"recent gebruikt\"."
todos:
- id: assets
content: Script tools/fetch-emoji.ps1 en Assets/Emoji (emoji.json, twemoji-64.png, cldr-nl.json) toevoegen als Resource in csproj
status: completed
- id: catalog
content: "EmojiCatalog: json/CLDR parsen, sheet croppen met cache, categorieën, zoeken, huidskleurvarianten"
status: completed
- id: model
content: ToolKind.Emoji, SkinTone, StyleSet.Emoji/SkinTone, StyleProps.Emoji, DefaultFor/PropsFor
status: completed
- id: annotation
content: EmojiAnnotation (Center, Bounds, Render met DrawImage en schaduw, Move)
status: completed
- id: picker
content: "EmojiPickerButton UserControl: zoeken, categorie-tabs, gevirtualiseerd raster, preview, huidskleur, recent"
status: completed
- id: editor
content: "EditorWindow: toolbarknop, EmojiGroup in PropsPanel, CreateAnnotation, stempel-case, sneltoets J, hint, stijl toepassen op selectie"
status: completed
- id: settings
content: AppSettings.RecentEmoji en controle van ToolbarPanel-volgorde voor het nieuwe item
status: completed
- id: verify
content: "Builden en testen: plaatsen, wisselen, huidskleur, zoeken NL/EN, export/kopiëren, undo"
status: completed
isProject: false
---
# Emoji-gereedschap met Twemoji-kiezer
## Aanpak
WPF tekent Segoe UI Emoji alleen zwart-wit. Daarom leveren we de **Twemoji-set van emoji-datasource** mee. Dat is dezelfde bron die emoji-mart gebruikt voor zijn "twitter"-set:
- `emoji.json`: namen, categorieën, sorteervolgorde, huidskleurvarianten en de positie van elke emoji in het sheet.
- `sheet_twitter_64.png`: één spritesheet waarin elke emoji 64 px groot is, met 1 px opvulling.
- CLDR `annotations/nl.json`: Nederlandse namen en trefwoorden, zodat je ook in het Nederlands kunt zoeken.
Een emoji wordt een stempel-annotatie, net als `StepAnnotation` en `CursorAnnotation`. Je klikt om hem te plaatsen, en daarna kun je hem verplaatsen, ongedaan maken en exporteren via de bestaande renderpijplijn.
```mermaid
flowchart LR
Assets["Assets/Emoji: json + sheet + nl"] --> Catalog[EmojiCatalog]
Catalog --> Picker[EmojiPickerButton]
Catalog --> Anno[EmojiAnnotation]
Picker -->|"gekozen emoji + huidskleur"| Style[StyleSet.Emoji]
Style --> Anno
Anno --> Surface[AnnotationSurface en ImageExporter]
```
## Bestanden
**Assets (nieuw):** `src/Kniip/Assets/Emoji/` met `emoji.json`, `twemoji-64.png` en `cldr-nl.json`. Deze komen in [src/Kniip/Kniip.csproj](src/Kniip/Kniip.csproj) als `<Resource Include="Assets\Emoji\*" />`. De bestanden worden eenmalig gedownload met een klein script, `tools/fetch-emoji.ps1`, dat vaste versies van emoji-datasource-twitter (15.x) en cldr-json binnenhaalt. De bestanden zelf worden ingecheckt, zodat de build offline blijft werken.
**`src/Kniip/Editor/Emoji/EmojiCatalog.cs` (nieuw)**
- Leest bij het eerste gebruik de json en het sheet (lazy, statisch). Emoji die niet in de Twitter-set zitten (`has_img_twitter`) en verouderde varianten (`obsoleted_by`) worden overgeslagen.
- Het model is `EmojiInfo` met deze velden: `Unified`, `Char`, `Name`, `ShortNames`, `Category`, `SortOrder`, `Keywords` (Engels plus Nederlands) en `SkinVariations` (een dictionary van toon naar sheetpositie).
- `ImageSource Get(string unified, SkinTone tone)`: een `CroppedBitmap` uit het sheet, in een cache en frozen.
- `IEnumerable<EmojiInfo> Search(string query)` zoekt in namen, shortcodes en trefwoorden, zonder op hoofdletters te letten.
- Categorieën in de volgorde van emoji-mart, met Nederlandse labels: Recent, Smileys en emotie, Mensen, Dieren en natuur, Eten en drinken, Activiteiten, Reizen en plaatsen, Objecten, Symbolen, Vlaggen.
**`src/Kniip/Editor/Annotations/EmojiAnnotation.cs` (nieuw)**
- Een stempel zoals `CursorAnnotation`: `Center` als `Point`, de grootte komt uit `Style.Size`, en `Bounds` is een vierkant rond `Center`.
- `Render` gebruikt `dc.DrawImage(EmojiCatalog.Get(Style.Emoji, Style.SkinTone), Bounds)`. Bij `Style.Shadow` komt er een schaduw onder, op dezelfde manier als in `RenderHelpers.DrawWithShadow`, maar dan voor een afbeelding (met een `DrawingGroup` en een `DropShadowEffect`-equivalent, of een offset-kopie met lage opaciteit).
- Er gebruikt `RenderOptions.SetBitmapScalingMode(HighQuality)` voor nette schaling.
**[src/Kniip/Editor/StyleSet.cs](src/Kniip/Editor/StyleSet.cs)**
- `ToolKind.Emoji` komt achter `Cursor`.
- Nieuwe enum `SkinTone { Default, Light, MediumLight, Medium, MediumDark, Dark }`.
- `StyleSet` krijgt `string Emoji = "1f44d"` (duim omhoog) en `SkinTone SkinTone`.
- Nieuwe `StyleProps.Emoji = 8192`.
- `DefaultFor`: `ToolKind.Emoji => new StyleSet { Size = 48, Shadow = false }`.
- `PropsFor`: `ToolKind.Emoji => StyleProps.Emoji | StyleProps.Size | StyleProps.Shadow`.
**`src/Kniip/Editor/EmojiPickerButton.xaml(.cs)` (nieuw)**
Dit volgt hetzelfde patroon als [ColorPickerButton](src/Kniip/Editor/ColorPickerButton.xaml.cs): een `ToggleButton` met een `Popup`, en het event `EmojiChanged`. De popup is ongeveer 350x420 en bevat:
- Een zoekveld bovenaan, dat focus krijgt zodra de popup opent.
- Een rij met categorie-tabs. Een klik scrollt naar de bijbehorende sectie.
- Een gevirtualiseerd raster: `ItemsControl` met `VirtualizingStackPanel` en per categorie een rij van negen emoji van 32 px.
- Onderaan een preview met de naam en `:shortcode:` van de emoji waar je met de muis boven hangt, plus een huidskleurkiezer met zes bolletjes.
- Na een keuze sluit de popup.
**[src/Kniip/Settings/AppSettings.cs](src/Kniip/Settings/AppSettings.cs)**
- Nieuwe instelling `List<string> RecentEmoji`, met maximaal 18 items en de laatst gekozen emoji vooraan. Deze lijst vult de tab "Recent".
**[src/Kniip/Editor/EditorWindow.xaml](src/Kniip/Editor/EditorWindow.xaml)**
- Een nieuwe `RadioButton Tag="Emoji"` achter `Cursor`, met `ItemId="Emoji"` en `Title="Emoji"`, tooltip "Emoji plaatsen (J)" en als icoon het Segoe Fluent-glyph `&#xE76E;` (Emoji2).
- In `PropsPanel` komt een `EmojiGroup` met `EmojiPickerButton`. `SizeGroup` en `ShadowGroup` worden hergebruikt.
**[src/Kniip/Editor/EditorWindow.xaml.cs](src/Kniip/Editor/EditorWindow.xaml.cs)**
- `CreateAnnotation`: `ToolKind.Emoji => new EmojiAnnotation { Center = p, Style = style }`.
- De stempel-case in `Surface_MouseLeftButtonDown` (rond regel 490) krijgt er `case ToolKind.Emoji:` bij.
- `CursorForTool`: `Emoji` gebruikt `Cursors.Hand`.
- Sneltoets `Key.J`: `SelectTool(ToolKind.Emoji)`.
- Hinttekst: "Klik om de gekozen emoji te plaatsen; kies een andere emoji in de balk eronder."
- `UpdatePropsPanel` en het toepassen van stijl op een geselecteerd object krijgen `StyleProps.Emoji` erbij. Zo kun je de emoji van een geplaatste stempel wisselen.
- De grootte-opties voor Emoji: 24, 32, 48, 64, 96 en 128. Boven 64 wordt het bitmap opgeschaald; dat is acceptabel.
**[src/Kniip/Editor/ToolbarPanel.cs](src/Kniip/Editor/ToolbarPanel.cs)**
- Controleren of een opgeslagen `ToolbarOrder` zonder "Emoji" het nieuwe item gewoon op de standaardplek toont. Is dat niet zo, dan wordt dat ingebouwd.
## Aandachtspunten
- De app wordt ongeveer 3 tot 8 MB groter door het sheet en de json. Het sheet wordt pas geladen als je het Emoji-gereedschap voor het eerst gebruikt.
- Twemoji valt onder CC-BY 4.0. De naamsvermelding komt in de README of het Over-scherm.
- Bestaande stijlen in de instellingen blijven compatibel, omdat de nieuwe velden standaardwaarden hebben.
@@ -0,0 +1,75 @@
---
name: Knip companion editor
overview: Een C# .NET 8 WPF-app die naast het Windows Knipprogramma draait, nieuwe knipsels oppikt (klembord, Screenshots-map, 'Openen met') en ze opent in een eigen editor met blur/pixelate, tekstkaders, stappen, cursor, highlight en vormen.
todos:
- id: skeleton
content: Solution + WPF .NET 8 project, tray-icoon, single-instance, editor die een afbeelding laadt
status: completed
- id: capture
content: ClipboardWatcher en FolderWatcher (Screenshots) met dedupe en instellingen
status: completed
- id: tools-shapes
content: "Annotatiemodel + tools: rechthoek/ellips met randen/kleuren, tekstkader, stappen, cursor, highlight"
status: completed
- id: blur
content: Blur- en pixelate-tool, undo/redo, selecteren/verplaatsen/resizen
status: completed
- id: export
content: Export naar klembord/bestand, 'Openen met'-registratie, autostart, installer
status: completed
isProject: false
---
# Knip Companion - WPF annotatie-editor naast het Knipprogramma
## Architectuur
```mermaid
flowchart LR
SnipTool["Knipprogramma (onaangetast)"] -->|kopieert| Clipboard
SnipTool -->|slaat op| ScreenshotsFolder["Afbeeldingen/Schermafbeeldingen"]
SnipTool -->|"Openen met"| CmdArgs["App gestart met bestandspad"]
Clipboard --> Watcher[CaptureWatcher]
ScreenshotsFolder --> Watcher
CmdArgs --> Editor
Watcher -->|toast/tray-melding of direct| Editor[EditorWindow]
Editor -->|Ctrl+C / Opslaan| Output["Klembord / PNG-bestand"]
```
- De app draait in de achtergrond met een tray-icoon (`NotifyIcon` via `H.NotifyIcon.Wpf`) en is single-instance (Mutex + named pipe om bestandspaden door te geven).
- Bij een nieuw knipsel opent de editor direct of na een melding (instelbaar).
## Projectstructuur
- [KnipPlus.sln](KnipPlus.sln)
- [src/KnipPlus/App.xaml.cs](src/KnipPlus/App.xaml.cs): opstarten, single-instance, tray, verwerking van commandline-argumenten
- [src/KnipPlus/Capture/ClipboardWatcher.cs](src/KnipPlus/Capture/ClipboardWatcher.cs): `AddClipboardFormatListener` + `WM_CLIPBOARDUPDATE`, controleert of het klembord een afbeelding bevat
- [src/KnipPlus/Capture/FolderWatcher.cs](src/KnipPlus/Capture/FolderWatcher.cs): `FileSystemWatcher` op `KnownFolder Screenshots`, met dedupe tegen het klembord (hash)
- [src/KnipPlus/Editor/EditorWindow.xaml](src/KnipPlus/Editor/EditorWindow.xaml): werkbalk (stijl zoals het Knipprogramma), canvas, eigenschappenpaneel (kleur, randdikte, stijl)
- [src/KnipPlus/Editor/Annotations/](src/KnipPlus/Editor/Annotations/): één klasse per tool, met een gemeenschappelijke basis `Annotation` (Render, HitTest, Move/Resize-handles)
- [src/KnipPlus/Editor/UndoStack.cs](src/KnipPlus/Editor/UndoStack.cs): undo/redo met commando's
- [src/KnipPlus/Settings/](src/KnipPlus/Settings/): JSON-instellingen in `%AppData%\KnipPlus`
## Tools (annotaties als bewerkbare objecten, pas plat bij export)
- **Rechthoek/ellips**: randkleur, vulkleur (optioneel/transparant), dikte, stijl (doorgetrokken/gestreept/gestippeld), afgeronde hoeken, schaduw
- **Tekst in kader**: tekst met achtergrondkader, padding, lettertype/-grootte, kleur
- **Stappen**: genummerde badges die automatisch ophogen; reeks kiezen: 1,2,3 / a,b,c / A,B,C / I,II,III
- **Cursor**: stempel van een pijl- of handcursor (meegeleverde vectoriconen)
- **Highlight**: semi-transparante markeerstift; met Shift een recht horizontale streep (standaard rechtgetrokken, instelbaar)
- **Blur / pixelate**: rechthoekig gebied; effect berekend op de onderliggende bitmap (Gaussian blur of pixelblokken, sterkte instelbaar)
- Extra: pijl, lijn, bijsnijden, selecteren/verplaatsen/verwijderen
## Export
- Ctrl+C: gerenderde PNG naar het klembord (gemarkeerd zodat de eigen watcher het niet opnieuw oppikt)
- Ctrl+S / Opslaan als: PNG/JPG
- Rendering via `RenderTargetBitmap` op de originele resolutie (rekening houden met DPI)
## Integratie met "Openen met"
- Registratie per gebruiker in `HKCU\Software\Classes\Applications\KnipPlus.exe` + `OpenWithProgids` voor `.png/.jpg`, zodat de app in het menu "Openen met" van het Knipprogramma staat (geen beheerdersrechten nodig)
- Optioneel: automatisch starten met Windows (`HKCU\...\Run`)
## Fasering
1. Skelet: solution, tray-app, single-instance, editor die een afbeelding toont
2. Capture: klembord- en mapwatcher, instellingen
3. Tools: vormen, tekstkader, stappen, highlight, cursor
4. Blur/pixelate, undo/redo, selecteren/aanpassen
5. Export, "Openen met"-registratie, autostart, installer (MSIX of Inno Setup)
@@ -0,0 +1,85 @@
---
name: Print Screen sneltoets
overview: Knip+ krijgt een instelbare sneltoets waarbij Print Screen gewoon gekozen kan worden. Die toets opent een eigen rechthoekselectie over het scherm; na loslaten gaat het knipsel naar de bestaande editor.
todos:
- id: hotkey-model
content: HotkeyGesture op AppSettings, standaard Print Screen, JSON blijft werken
status: completed
- id: global-hook
content: WH_KEYBOARD_LL-haak die Print Screen ziet, inslikt en de actie op de UI-thread start
status: completed
- id: settings-ui
content: Instellingenkaart om de toets te kiezen, inclusief Print Screen, Esc en wissen
status: completed
- id: region-overlay
content: Per-monitor rechthoekselectie en CopyFromScreen naar de editor
status: completed
- id: wire-up
content: StartNewSnip, systeemvak en opslaan van instellingen op de haak aansluiten
status: completed
isProject: false
---
# Print Screen als sneltoets voor eigen knipsel
Knip+ start nu het Windows Knipprogramma via `ms-screenclip:` ([App.xaml.cs](src/KnipPlus/App.xaml.cs) `StartNewSnip`). Er is geen sneltoets, en een gewone WPF-`KeyDown` ziet Print Screen niet: Windows eet die toets op voordat het venster hem krijgt. Daarom komt er een globale low-level toetsaanslaghaak, plus een selectievenster.
De schermafbeelding (opname, actief venster, GIF) is alleen het voorbeeld van de toets. Alleen rechthoekselectie komt erbij; `ScreenRecorderLib` blijft ongebruikt.
## Sneltoets vastleggen
Nieuw type `HotkeyGesture` (Ctrl, Alt, Shift, Win + virtuele toets) op [AppSettings](src/KnipPlus/Settings/AppSettings.cs), opgeslagen via de bestaande JSON-store. Standaard: **Print Screen** zonder modifiers (`VK_SNAPSHOT` = `0x2C`). Ontbreekt het veld in een bestaand `settings.json`, dan geldt diezelfde standaard.
In [SettingsWindow.xaml](src/KnipPlus/Settings/SettingsWindow.xaml) een kaart **Sneltoets**:
- Knop met de huidige combinatie, bijvoorbeeld `Print Screen` of `Ctrl + Shift + P`.
- Klikken zet de knop in luisterstand: de volgende toets (geen losse Ctrl/Alt/Shift/Win) wordt de sneltoets.
- Print Screen komt binnen via dezelfde haak, niet via WPF, zodat de knop hem echt kan tonen.
- Esc annuleert het kiezen. Delete of Backspace maakt de sneltoets leeg.
- Korte toelichting: als Print Screen is gekozen, vangt Knip+ die toets af. Het Windows-knipprogramma opent dan niet óók.
## Globale haak
Nieuwe `GlobalHotkeyHook` in `src/KnipPlus/Infrastructure/`, P/Invoke in [NativeMethods.cs](src/KnipPlus/Interop/NativeMethods.cs):
- `SetWindowsHookEx(WH_KEYBOARD_LL)` op de UI-thread, delegate vastgehouden tegen GC, `UnhookWindowsHookEx` bij afsluiten.
- Alleen key-down (`WM_KEYDOWN` / `WM_SYSKEYDOWN`), geen key-up.
- Modifiers via `GetAsyncKeyState` (inclusief linker en rechter Windows-toets).
- Bij een match: toets inslikken (niet doorgeven) en de actie met `BeginInvoke` starten, zodat de haak niet blokkeert.
- Tijdens het kiezen van een sneltoets slikt de haak die toets ook in, maar start geen knipsel.
`RegisterHotKey` is hier niet genoeg: Windows heeft Print Screen vaak al geregistreerd, en dan faalt de registratie stil.
## Rechthoekselectie
Nieuw overlayvenster per monitor (`src/KnipPlus/Capture/RegionCaptureWindow.xaml`). De app is al PerMonitorV2 ([app.manifest](src/KnipPlus/app.manifest)).
- Randloos, altijd bovenop, niet in de taakbalk, donkere laag over `Screen.Bounds`.
- Positie en grootte in fysieke pixels via `SetWindowPos`, daarna de WPF-grootte uit de DPI van dát scherm, zodat een scherm op 150% niet verschuift.
- Slepen tekent de rechthoek (uitsparing in de dimlaag plus maatlabel). Loslaten met een minimale grootte (ongeveer 3×3 px) maakt het knipsel.
- Esc of rechtermuisknop annuleert.
- Pixels via `Graphics.CopyFromScreen` op de fysieke rechthoek, omzetten naar een bevroren `BitmapSource` (96 DPI, Bgra32) met het bestaande patroon in [PixelUtil](src/KnipPlus/Infrastructure/PixelUtil.cs). Het klembord blijft onaangeroerd, zodat de klembordbewaking het knipsel niet nog eens aanbiedt.
Daarna direct `OpenEditor`. Geen toast: de gebruiker heeft zelf geknipt.
## Aansluiting
In [App.xaml.cs](src/KnipPlus/App.xaml.cs):
- Haak starten na het laden van de instellingen, en opnieuw zetten bij Opslaan.
- `StartNewSnip` opent de rechthoekselectie in plaats van `ms-screenclip:`. Daarmee gebruiken de knop **Nieuw** en Ctrl+N in de editor dezelfde selectie.
- Zolang de selectie open is, negeert de haak een tweede druk.
In [TrayIcon.cs](src/KnipPlus/Infrastructure/TrayIcon.cs) een menu-item **Rechthoek knippen**, zodat het ook zonder toets kan.
```mermaid
flowchart LR
Hook[GlobalHotkeyHook]
Settings[Instellingen]
Overlay[RegionCaptureWindow]
Editor[EditorWindow]
Settings -->|opslaan| Hook
Hook -->|"Print Screen of gekozen toets"| Overlay
Tray[Systeemvak en Nieuw] --> Overlay
Overlay -->|BitmapSource| Editor
```
@@ -0,0 +1,73 @@
---
name: Screencasts MP4 GIF
overview: "Knip+ krijgt een eigen schermopname: gebied kiezen via tray of sneltoets, opnemen met geluid, muisaanwijzer en klikmarkering, daarna begin en einde inkorten en opslaan als MP4 of GIF."
todos:
- id: spike
content: ScreenRecorderLib toevoegen, TFM naar windows10.0.19041.0, en controleren dat single-file publish met opname werkt
status: completed
- id: hotkey-tray
content: GlobalHotkey + tray-item + StartRecording/StopRecording in App
status: completed
- id: selector
content: RegionSelectorWindow (slepen, venster, heel scherm) en RegionFrameWindow
status: completed
- id: toolbar-session
content: RecordingToolbar en RecordingSession (audio, cursor, klikken, pauze, aftellen)
status: completed
- id: screencast-window
content: ScreencastWindow met preview en bereikschuif voor inkorten
status: completed
- id: export
content: VideoExporter (MP4 trim) en GifEncoder (frames via GetThumbnailsAsync, quantisatie, delta-frames)
status: completed
- id: settings
content: AppSettings-velden en sectie Screencasts in SettingsWindow
status: completed
- id: build
content: build.ps1/installer bijwerken en eind-tot-eind testen
status: completed
isProject: false
---
# Screencasts (MP4 en GIF) in Knip+
## Aanpak
Er komt één opnamepad: Knip+ neemt altijd eerst op als MP4 in een tijdelijk bestand (met geluid als dat aan staat). Pas na het stoppen kies je in een nabewerkingsvenster het bereik en het formaat. Zo hoef je vooraf niets te beslissen, en GIF en MP4 delen dezelfde opname en dezelfde inkortfunctie.
```mermaid
flowchart LR
trigger["Tray of sneltoets"] --> selector[RegionSelectorWindow]
selector --> toolbar["RecordingToolbar: start, pauze, stop"]
toolbar --> session["RecordingSession: tijdelijke MP4"]
session --> editor["ScreencastWindow: voorbeeld en inkorten"]
editor --> mp4["VideoExporter: MP4"]
editor --> gif["GifEncoder: GIF"]
```
## Techniek
- **Opnemen**: NuGet-pakket `ScreenRecorderLib` (MIT, gebruikt Windows Media Foundation, H.264/AAC). Dat regelt in één keer het gekozen gebied, de muisaanwijzer, de klikmarkering en het mixen van systeemgeluid en microfoon. Er is dan geen ffmpeg nodig.
- **Inkorten naar MP4**: `Windows.Media.Editing.MediaComposition` met `TrimTimeFromStart` en `TrimTimeFromEnd`, en daarna `RenderToFileAsync`. Daarvoor gaat het target framework in [src/KnipPlus/KnipPlus.csproj](src/KnipPlus/KnipPlus.csproj) van `net8.0-windows` naar `net8.0-windows10.0.19041.0`.
- **GIF**: frames uit de MP4 halen met `MediaComposition.GetThumbnailsAsync`, met een instelbare fps (standaard 10) en maximale breedte. Daarna codeert een eigen `GifEncoder` ze: per frame een palet van 256 kleuren (octree-quantisatie), framevertraging, oneindig herhalen (NETSCAPE-extensie) en ongewijzigde pixels transparant maken, zodat het bestand klein blijft. De ingebouwde `GifBitmapEncoder` van WPF kent geen framevertraging of herhalen en is daarom niet bruikbaar.
- **Risico bij single-file publish**: `ScreenRecorderLib` is een C++/CLI-assembly en die kan niet vanuit de bundel geladen worden. Daarom komt `-p:IncludeAllContentForSelfExtract=true` in [build.ps1](build.ps1) (of als fallback publiceren als map via de installer). Dit test ik direct als eerste stap.
## Nieuwe bestanden (`src/KnipPlus/Recording/`)
- `RegionSelectorWindow.xaml(.cs)`: een transparante overlay over alle schermen. Je sleept een rechthoek, klikt op een venster om dat te kiezen, of drukt op Enter voor het hele scherm. De selectie houdt rekening met DPI per monitor.
- `RegionFrameWindow.cs`: een rand rond het opnamegebied die klikken doorlaat en buiten het gebied valt, zodat hij niet in de opname komt.
- `RecordingToolbar.xaml(.cs)`: een kleine knoppenbalk die altijd bovenop blijft, naast het gebied. Daarop staan schakelaars voor systeemgeluid en microfoon, en knoppen voor start (met 3 seconden aftellen), pauze en stop, plus de verstreken tijd.
- `RecordingSession.cs`: een wrapper om `ScreenRecorderLib.Recorder` (opties, status, fouten) die een tijdelijk MP4-pad oplevert in `%TEMP%\KnipPlus`.
- `ScreencastWindow.xaml(.cs)`: voorbeeld van de opname via `MediaElement`, een bereikschuif met begin- en eindgreep en afspelen binnen het bereik. Verder knoppen "Opslaan als MP4", "Opslaan als GIF" (met fps en breedte) en "Kopiëren" (het bestand als FileDropList op het klembord). Bij het sluiten wordt het tijdelijke bestand opgeruimd.
- `VideoExporter.cs`: MP4 inkorten en renderen.
- `GifEncoder.cs`: GIF-encoder met quantisatie en het overslaan van ongewijzigde pixels per frame.
## Wijzigingen in bestaande bestanden
- [src/KnipPlus/Infrastructure/TrayIcon.cs](src/KnipPlus/Infrastructure/TrayIcon.cs): menu-item "Screencast opnemen..." en een event `RecordRequested`. Tijdens een opname wordt dit "Opname stoppen".
- `src/KnipPlus/Infrastructure/GlobalHotkey.cs` (nieuw): `RegisterHotKey` via een verborgen `HwndSource`. Dezelfde sneltoets start of stopt de opname (standaard Ctrl + Shift + R). Als de sneltoets al bezet is, verschijnt er een ballonmelding.
- [src/KnipPlus/Interop/NativeMethods.cs](src/KnipPlus/Interop/NativeMethods.cs): `RegisterHotKey`, `UnregisterHotKey` en `WindowFromPoint` (voor het kiezen van een venster).
- [src/KnipPlus/App.xaml.cs](src/KnipPlus/App.xaml.cs): `StartRecording()` en `StopRecording()`. Er draait maximaal één opname tegelijk. Tijdens de opname staat `Capture.Paused` aan, zodat er geen knipsels opgepikt worden.
- [src/KnipPlus/Settings/AppSettings.cs](src/KnipPlus/Settings/AppSettings.cs): `RecordHotkey`, `RecordSystemAudio`, `RecordMicrophone`, `RecordShowCursor`, `RecordHighlightClicks`, `RecordFps` (30), `GifFps` (10), `GifMaxWidth` (800) en `LastVideoFolder` (standaard `Video's\Knip+`).
- [src/KnipPlus/Settings/SettingsWindow.xaml](src/KnipPlus/Settings/SettingsWindow.xaml) en de bijbehorende `.cs`: een sectie "Screencasts" met de sneltoets, standaardinstellingen voor geluid, muisaanwijzer en klikken, en GIF-instellingen.
- [installer/KnipPlus.iss](installer/KnipPlus.iss): alleen aanpassen als de fallback (publiceren als map) nodig blijkt.
@@ -0,0 +1,97 @@
---
name: Zipline upload
overview: Een Zipline 4-uploadbestemming toevoegen die je configureert door het ShareX-bestand (.sxcu) of het Shell Script-bestand (.sh) uit Zipline te importeren. Kniip uploadt daarna zelf via HttpClient, met een aparte knop Uploaden in de editor en in het screencastvenster. De link komt op het klembord.
todos:
- id: config
content: UploaderConfig + UploaderImporter (sxcu- en curl/sh-parser) in src/Kniip/Upload
status: completed
- id: client
content: ZiplineUploader met HttpClient multipart, foutafhandeling en JSON-padlezer
status: completed
- id: settings
content: AppSettings.Uploader, DPAPI-versleuteling token in SettingsStore, tabblad Uploaden (importeren/testen/verwijderen)
status: completed
- id: clipboard
content: ClipboardWriter.SetText toevoegen
status: completed
- id: editor
content: Uploadknop + Ctrl+U in EditorWindow, zichtbaar alleen met config; ToolbarPanel-volgorde controleren
status: completed
- id: screencast
content: "Uploadknop in ScreencastWindow: export naar temp, upload met voortgang/annuleren, link naar klembord"
status: completed
- id: build
content: Bouwen en testen met een echte .sxcu en .sh uit Zipline 4
status: completed
isProject: false
---
# Upload naar Zipline 4
## Hoe het werkt
```mermaid
flowchart LR
sxcu["ShareX .sxcu"] --> importer[UploaderImporter]
sh["Shell script .sh"] --> importer
importer --> config["UploaderConfig in AppSettings"]
editorBtn["Editor: Uploaden"] --> client[ZiplineUploader]
castBtn["Screencast: Uploaden"] --> client
config --> client
client -->|"POST multipart"| server["Zipline /api/upload"]
server -->|"JSON files[0].url"| client
client --> clip["URL naar klembord + melding"]
```
Zipline 4 gebruikt een vast patroon: `POST https://host/api/upload` met de header `authorization: <token>`, extra headers zoals `x-zipline-format`, een multipart-veld `file`, en als antwoord `{"files":[{"url": ...}]}`. Beide exportbestanden bevatten precies die gegevens. Kniip leest ze daarom alleen in en voert niets uit.
## Nieuwe bestanden in `src/Kniip/Upload/`
- `UploaderConfig.cs` bevat de velden `Name`, `RequestUrl`, `Headers` (een dictionary), `FileFormName` (standaard `"file"`) en `UrlPath` (standaard `"files[0].url"`). Daarnaast staat er een eigenschap `IsConfigured`.
- `UploaderImporter.cs`:
- `FromSxcu(string json)`: leest met `System.Text.Json` de velden `RequestURL`, `Headers`, `FileFormName` en `URL` uit. Van `URL` wordt `{json:files[0].url}` omgezet naar het pad `files[0].url`. Een `.sxcu` met `Body` anders dan `MultipartFormData` wordt geweigerd.
- `FromShellScript(string text)`: zoekt de `curl`-regel. Daaruit haalt hij de `http(s)://`-URL, elke `-H "naam: waarde"` als header (behalve `content-type`), en de veldnaam uit `-F naam=@...`. Het antwoordpad komt uit `jq -r <pad>` als dat er staat, anders de standaardwaarde. Regelvervolgingen (`\` aan het einde van een regel) en aanhalingstekens worden gewoon afgehandeld.
- Kan het bestand niet worden gelezen, dan geeft de importer een duidelijke Nederlandse foutmelding.
- `ZiplineUploader.cs`: één statische `HttpClient` en de methode `Task<string> UploadAsync(UploaderConfig, Stream, string fileName, string contentType, IProgress<double>?, CancellationToken)`. Die bouwt de `MultipartFormDataContent`, voegt de headers toe en geeft bij een HTTP-fout de statuscode en de `error`-tekst van Zipline terug. Daarna leest hij de URL via een eenvoudige padlezer (`a.b[0].c`) uit het JSON-antwoord.
## Instellingen
- In [`src/Kniip/Settings/AppSettings.cs`](src/Kniip/Settings/AppSettings.cs) komt `public UploaderConfig? Uploader { get; set; }` bij.
- De `authorization`-header is een geheime token. Die wordt in de settings-JSON met DPAPI versleuteld voor de huidige gebruiker, via `ProtectedData.Protect` en het NuGet-pakket `System.Security.Cryptography.ProtectedData`. Dat gebeurt in [`src/Kniip/Settings/SettingsStore.cs`](src/Kniip/Settings/SettingsStore.cs) bij het opslaan en laden.
- In [`src/Kniip/Settings/SettingsWindow.xaml`](src/Kniip/Settings/SettingsWindow.xaml) komt een nieuw tabblad **Uploaden** met:
- Een knop "Importeren..." met een `OpenFileDialog` en het filter `ShareX-uploader (*.sxcu)|*.sxcu|Shell script (*.sh)|*.sh`.
- Een regel die de huidige bestemming toont: de servernaam uit de URL, zonder token.
- De knoppen "Testen" (uploadt een klein PNG'tje en toont de link) en "Verwijderen".
- Een hinttekst: "Maak het bestand in Zipline via Instellingen > Generate Uploaders (ShareX of Shell Script)."
- De afhandeling komt in [`src/Kniip/Settings/SettingsWindow.xaml.cs`](src/Kniip/Settings/SettingsWindow.xaml.cs). De nieuwe configuratie wordt pas bij Opslaan vastgelegd, net als bij de andere instellingen.
## Editor
- In [`src/Kniip/Editor/EditorWindow.xaml`](src/Kniip/Editor/EditorWindow.xaml) komt een nieuwe toolbarknop `ItemId="Upload"` (icoon `&#xE898;`, titel "Uploaden"), tussen Opslaan en Kopiëren.
- In [`src/Kniip/Editor/EditorWindow.xaml.cs`](src/Kniip/Editor/EditorWindow.xaml.cs) komt `Upload_Click` plus de sneltoets Ctrl+U:
- `CommitTextEdit()`, daarna `DocumentRenderer.Export(_doc)` en vervolgens `ImageExporter.EncodePng`.
- `UploadAsync` met de bestandsnaam `Knipsel yyyy-MM-dd HHmmss.png`.
- Het resultaat gaat naar `ClipboardWriter.SetText(url)` en `ShowTemporaryHint("Geüpload, link gekopieerd: ...")`.
- Tijdens het uploaden is de knop uitgeschakeld.
- De knop is ingeklapt zolang `Uploader` niet is ingesteld. Dat wordt opnieuw gecontroleerd in `OnActivated`, net zoals `ApplySaveButton` dat doet.
- Ik controleer in `ToolbarPanel` dat een nieuw `ItemId` ook verschijnt als er al een `ToolbarOrder` is opgeslagen. Zo niet, dan komen onbekende items achteraan.
## Screencast
- In [`src/Kniip/Recording/ScreencastWindow.xaml`](src/Kniip/Recording/ScreencastWindow.xaml) komt een knop `UploadButton` naast Kopiëren.
- In [`src/Kniip/Recording/ScreencastWindow.xaml.cs`](src/Kniip/Recording/ScreencastWindow.xaml.cs) komt `Upload_Click`. Dat volgt hetzelfde patroon als `Copy_Click`:
- Eerst `ExportAsync` naar een tijdelijke map.
- Daarna `UploadAsync` met `video/mp4` of `image/gif`. De voortgang loopt via dezelfde `Progress`-balk en annuleren gaat via `_export`.
- Daarna gaat de URL naar het klembord en toont `SavedText` de link.
- `SetBusy` schakelt ook `UploadButton` uit.
## Hulpfunctie
- In [`src/Kniip/Editor/ImageExporter.cs`](src/Kniip/Editor/ImageExporter.cs) krijgt `ClipboardWriter` de methode `SetText(string)`, met dezelfde aanpak voor een bezet klembord als `SetFile`.
## Buiten de scope
- Automatisch uploaden bij een nieuw knipsel.
- Een "opslaan en uploaden"-keuze voor de knop Opslaan.
- Het echt uitvoeren van het shellscript.
- Andere uploaders dan Zipline. De importer werkt in principe ook voor andere ShareX-configuraties met multipart, maar dat wordt niet getest.
@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "../.."
}
],
"settings": {}
}