Viewing is open to everyone. Downloading and starring need a GitHub sign-in.
Sign in with GitHub
README.md
v0.1.0-build.85
Latest
Commit ede38f5
Sign in with GitHub to download. Each download is recorded with your GitHub username.
| 1 | # CKey for .NET |
| 2 | |
| 3 | CKey optical codes for .NET. The package encodes payloads, renders native pixels (and exports PNG, SVG and PDF), and scans grayscale camera frames. It is a thin P/Invoke wrapper over the CKey C core and does not reimplement encoding, decoding or rendering. |
| 4 | |
| 5 | | Package | Target | Contents | |
| 6 | |---|---|---| |
| 7 | | `CKey` | `net8.0`, `netstandard2.0` | Headless core: encode, render, certify, scan, payload helpers, telemetry. No UI dependency. | |
| 8 | | `CKey.Wpf` | `net8.0-windows` | Optional adapter: `WriteableBitmap` output and `BitmapSource` scanning. | |
| 9 | |
| 10 | Native libraries ship inside the package as `runtimes/<rid>/native/` assets for `win-x64`, `linux-x64`, `osx-x64` and `osx-arm64`. On `net8.0` the bindings use `[LibraryImport]` and are trim- and AOT-compatible. On `netstandard2.0` they use `[DllImport]`. .NET Framework apps get `ckeynative.dll` (win-x64) copied by the package's build targets. |
| 11 | |
| 12 | ## Install |
| 13 | |
| 14 | ```sh |
| 15 | dotnet add package CKey |
| 16 | dotnet add package CKey.Wpf # optional, WPF only |
| 17 | ``` |
| 18 | |
| 19 | ```xml |
| 20 | <PackageReference Include="CKey" Version="0.1.0-build.85" /> |
| 21 | ``` |
| 22 | |
| 23 | The namespace is `OpenCKey`. |
| 24 | |
| 25 | ## Encode and render |
| 26 | |
| 27 | ```csharp |
| 28 | using OpenCKey; |
| 29 | |
| 30 | CKeySymbol symbol = CKeySymbol.Encode("https://openckey.org/hello"); // version 0 = smallest that fits |
| 31 | CKeySymbol big = CKeySymbol.Encode(bytes, version: 12, level: EccLevel.Q, layout: Layout.Print); |
| 32 | |
| 33 | // Native pixels: size x size RGBA8, straight alpha, no padding. |
| 34 | CKeyImage image = symbol.RenderRgba(512, RasterPolicy.Coverage, transparent: false, style: CKeyStyle.Concept); |
| 35 | byte[] rgba = image.Rgba; |
| 36 | |
| 37 | // Exports |
| 38 | byte[] png = symbol.ToPng(1024); |
| 39 | string svg = symbol.ToSvg(scale: 8, style: CKeyStyle.Plain); |
| 40 | byte[] pdf = symbol.ToPdf(widthPt: 144); // 2 inches wide, vector |
| 41 | CKeyGrayImage print = symbol.RenderPrint(1.0, 600); // 1 inch at 600 dpi, pure black/white |
| 42 | ``` |
| 43 | |
| 44 | `CKeySymbol` is a managed copy of the C symbol struct. It holds no native resources, so it needs no `Dispose`. |
| 45 | |
| 46 | ### Styles and optical safety |
| 47 | |
| 48 | ```csharp |
| 49 | var style = CKeyStyle.Concept; // or CKeyStyle.Plain, or new CKeyStyle() |
| 50 | style.Modules = ModuleShape.Circle; |
| 51 | style.Ink = CKeyColor.Parse("#1a1a2e"); |
| 52 | |
| 53 | if (!symbol.CheckStyle(style, out string why)) Console.WriteLine(why); |
| 54 | |
| 55 | // Certification renders the style, degrades it with a camera model and decodes it. |
| 56 | CKeyCertification c = symbol.Certify(style); |
| 57 | // A transparent symbol needs to know what it sits on: |
| 58 | c = symbol.Certify(null, transparent: true, backdropColours: new[] { CKeyColor.White }); |
| 59 | ``` |
| 60 | |
| 61 | Under the default `StylePolicy.Strict`, renderers refuse a style that fails the checks and throw `CKeyException`. With `StylePolicy.Adaptive`, the style is repaired instead (see `CKeyCertification.AdjustedStyle`). `StylePolicy.Override` draws the style as given. |
| 62 | |
| 63 | ### WPF |
| 64 | |
| 65 | ```csharp |
| 66 | using OpenCKey.Wpf; |
| 67 | |
| 68 | imageControl.Source = symbol.ToWriteableBitmap(512, transparent: true); |
| 69 | ``` |
| 70 | |
| 71 | ## Scan |
| 72 | |
| 73 | ```csharp |
| 74 | // gray: one 8-bit luma plane (for example the Y plane of NV12), any stride. |
| 75 | CKeyResult? result = CKeyDecoder.Decode(gray, width, height, stride); |
| 76 | if (result != null) |
| 77 | { |
| 78 | Console.WriteLine(result.ProfileName); // "Profile 0 · v3" |
| 79 | Console.WriteLine(result.Text ?? result.PayloadHex); |
| 80 | CKeyPayloadAction? action = result.Action; // e.g. Kind = Web, Label = "Open openckey.org" |
| 81 | // Show the payload and the action; act only when the user confirms. |
| 82 | } |
| 83 | ``` |
| 84 | |
| 85 | The pipeline is the iOS reader's. It finds up to six border candidates (`CKeyDecoder.FindBorders`). For each one it refines the quadrilateral to the Profile 0 field and decodes, then tries the Profile 1, 2 and 3 fields. Detection and decode are also available separately, via `FindBorders` and `DecodeCandidate`. Scanning never needs the renderer, telemetry or a network. |
| 86 | |
| 87 | For continuous camera scanning, use a session. It also drives the telemetry session counters: |
| 88 | |
| 89 | ```csharp |
| 90 | using var session = new CKeyScanSession(); // starts a scan session |
| 91 | foreach (Frame f in camera.Frames) |
| 92 | { |
| 93 | if (session.ProcessFrame(f.Gray, f.Width, f.Height, f.Stride) is { } r) { Show(r); break; } |
| 94 | } |
| 95 | // Dispose ends the session (failed if nothing decoded). Restart() is "scan again". |
| 96 | ``` |
| 97 | |
| 98 | `CKeyPayload.Classify(text)` maps text to an action using spec/CKey-Payload-Conventions.md. Only `https`/`http`, `mailto`, `tel`, `sms`, `geo`, `address:` and vCards are recognized; anything else is plain text. `CKeyPayload.ReadableText(bytes)` applies the convention's text rule. |
| 99 | |
| 100 | ## Safe Actions |
| 101 | |
| 102 | A Safe Action (`ckey-action:1:…`) is a signed code from a known site for this app. `CKeySafeActions` verifies it with the core and decides whether it runs; the app only pins keys and registers handlers. |
| 103 | |
| 104 | ```csharp |
| 105 | var actions = new CKeySafeActions("com.viadere") |
| 106 | .Trust("citeory.com", "2026-11", "<64 hex digits>", "https://citeory.com/api/ckey/requests/{request_id}") |
| 107 | .Register("open.library", new[] { "citeory.com" }, a => OpenItem(a.ParamsText)) |
| 108 | .Register("signin.request", new[] { "citeory.com" }, CKeySafeActionScope.CrossDevice, null, a => ApproveSignInAsync(a)); |
| 109 | |
| 110 | var outcome = await actions.HandleAsync(text); |
| 111 | switch (outcome.Kind) |
| 112 | { |
| 113 | case CKeySafeActionOutcomeKind.NotASafeAction: ShowPayload(text); break; // the ordinary payload rules |
| 114 | case CKeySafeActionOutcomeKind.Performed: break; // same-device: already ran |
| 115 | case CKeySafeActionOutcomeKind.NeedsConfirmation: |
| 116 | await OpenCKey.Wpf.CKeySafeActionPrompt.ShowAsync(window, outcome.Request!); break; // or CKeySafeActionConsolePrompt.AskAsync |
| 117 | case CKeySafeActionOutcomeKind.Rejected: ShowUnverified(outcome.Reason); break; |
| 118 | } |
| 119 | ``` |
| 120 | |
| 121 | Same-device actions run at once. Cross-device actions run only from an SDK prompt, which shows the verified issuer and who is asking; there is no other way to approve one. Anything that fails a check runs nothing; show that the code claims to be a Safe Action but could not be verified, with the reason. See [spec/CKey-Safe-Actions.md](../../spec/CKey-Safe-Actions.md). |
| 122 | |
| 123 | ## Telemetry |
| 124 | |
| 125 | The default mode is `1`. In mode 1 the SDK counts scan sessions, frames, decodes per profile and coarse durations. Roughly once an hour it sends one anonymous aggregate batch to `https://openckey.org/api/telemetry/v1/batch`. It never sends payloads, URLs, user or device identifiers, or location. Sending happens on a thread-pool timer and never blocks or delays scanning. Only `CKeyScanSession` records anything; `CKeyDecoder` and rendering do not. See [docs/adoption-and-telemetry.md](../../docs/adoption-and-telemetry.md). |
| 126 | |
| 127 | To opt out: |
| 128 | |
| 129 | ```csharp |
| 130 | CKey.Telemetry = 0; // off: nothing counted, nothing sent, counters purged |
| 131 | CKey.Telemetry = 2; // local only: counters via CKeyTelemetry.Snapshot(), never sent |
| 132 | CKey.Telemetry = 1; // default |
| 133 | ``` |
| 134 | |
| 135 | Any other value throws `ArgumentOutOfRangeException`, and the mode stays unchanged. |
| 136 | |
| 137 | `CKeyTelemetry.FlushAsync()` tries a send immediately, for example when your app is deactivated or moves to the background (the hourly limit still applies). `ExportState()` and `ImportState()` persist counters across restarts if you want that; persistence is off unless you store the state. A native library built with `-DCKEY_DOTNET_TELEMETRY=OFF` contains no telemetry code at all. In that case `CKeyTelemetry.IsAvailable` is false and the mode reads 0. |
| 138 | |
| 139 | Disclose telemetry according to your own app-store and legal requirements. |
| 140 | |
| 141 | ## Building from source |
| 142 | |
| 143 | ```sh |
| 144 | # Native library for this machine -> sdk/dotnet/artifacts/native/<rid>/ |
| 145 | cmake -S sdk/dotnet/native -B build-dotnet -DCMAKE_BUILD_TYPE=Release -DCKEY_DOTNET_RID=linux-x64 |
| 146 | cmake --build build-dotnet --config Release |
| 147 | |
| 148 | # Check the C struct layouts the C# interop mirrors |
| 149 | ./build-dotnet/ckey_layout_probe | diff sdk/dotnet/native/tools/layout_expected.txt - |
| 150 | |
| 151 | dotnet test sdk/dotnet/tests/CKey.Tests -p:CKeyHostRid=linux-x64 |
| 152 | dotnet pack sdk/dotnet/src/CKey -c Release # add -p:CKeyRequireAllNatives=true to insist on all four RIDs |
| 153 | ``` |
| 154 | |
| 155 | The package version comes from the repository's `VERSION` file. |
| 156 | |
| 157 | ## License |
| 158 | |
| 159 | MIT. |