Skip to content

Commit 1630105

Browse files
committed
Sync with core 3cc8b3d: cancellable window close and application quit (#65)
- Every binding gets EventRequest / EventDecision with asynchronous, retained delivery; Dart events cross native threads safely. - JS keeps deliveries alive through Promise completion and cancels its votes when its environment ends; Python pumps votes from asyncio. - Real Flutter exit-confirmation and multi-window close ownership tests (tools/gui), plus JS and Python event-loop regressions.
1 parent fc04bb6 commit 1630105

120 files changed

Lines changed: 5898 additions & 496 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/python-ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
- name: Build the library
4848
working-directory: bindings/python
4949
run: |
50-
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
50+
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DNATIVEAPI_PY_BUILD_TESTS=ON
5151
cmake --build build --config Release
5252
- name: Test
5353
working-directory: bindings/python

‎bindings/csharp/src/CNativeAPI/generated/Application.cs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@ public struct native_application_event_t
1818
public struct DataUnion
1919
{
2020
[FieldOffset(0)] public ExitingData exiting;
21+
[FieldOffset(0)] public QuitRequestedData quit_requested;
2122
}
2223

2324
[StructLayout(LayoutKind.Sequential)]
2425
public struct ExitingData
2526
{
2627
public int exit_code;
2728
}
29+
30+
[StructLayout(LayoutKind.Sequential)]
31+
public struct QuitRequestedData
32+
{
33+
public ulong request;
34+
}
2835
}
2936

3037
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// AUTO-GENERATED. DO NOT EDIT.
2+
// Any manual changes WILL BE LOST when this file is regenerated.
3+
#nullable enable
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Runtime.InteropServices;
8+
9+
namespace CNativeAPI;
10+
11+
public static partial class Interop
12+
{
13+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
14+
[return: MarshalAs(UnmanagedType.I1)]
15+
public static extern bool native_event_decision_accept(ulong self);
16+
17+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
18+
[return: MarshalAs(UnmanagedType.I1)]
19+
public static extern bool native_event_decision_cancel(ulong self);
20+
21+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
22+
[return: MarshalAs(UnmanagedType.I1)]
23+
public static extern bool native_event_decision_is_pending(ulong self);
24+
25+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
26+
[return: MarshalAs(UnmanagedType.I1)]
27+
public static extern bool native_event_request_cancel(ulong self);
28+
29+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
30+
[return: MarshalAs(UnmanagedType.I1)]
31+
public static extern bool native_event_request_is_cancelable(ulong self);
32+
33+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
34+
[return: MarshalAs(UnmanagedType.I1)]
35+
public static extern bool native_event_request_is_cancelled(ulong self);
36+
37+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
38+
[return: MarshalAs(UnmanagedType.I1)]
39+
public static extern bool native_event_request_is_pending(ulong self);
40+
41+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
42+
public static extern ulong native_event_request_defer(ulong self);
43+
44+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
45+
public static extern void native_event_decision_free(ulong handle);
46+
47+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
48+
public static extern void native_event_request_free(ulong handle);
49+
}
50+

‎bindings/csharp/src/CNativeAPI/generated/Window.cs‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public struct DataUnion
2020
{
2121
[FieldOffset(0)] public MovedData moved;
2222
[FieldOffset(0)] public ResizedData resized;
23+
[FieldOffset(0)] public CloseRequestedData close_requested;
2324
}
2425

2526
[StructLayout(LayoutKind.Sequential)]
@@ -33,6 +34,12 @@ public struct ResizedData
3334
{
3435
public native_size_t new_size;
3536
}
37+
38+
[StructLayout(LayoutKind.Sequential)]
39+
public struct CloseRequestedData
40+
{
41+
public ulong request;
42+
}
3643
}
3744

3845
[StructLayout(LayoutKind.Sequential)]
@@ -47,6 +54,10 @@ public struct native_window_list_t
4754

4855
public static partial class Interop
4956
{
57+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
58+
[return: MarshalAs(UnmanagedType.I1)]
59+
public static extern bool native_window_close(ulong self);
60+
5061
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
5162
[return: MarshalAs(UnmanagedType.I1)]
5263
public static extern bool native_window_has_shadow(ulong self);
@@ -63,6 +74,10 @@ public static partial class Interop
6374
[return: MarshalAs(UnmanagedType.I1)]
6475
public static extern bool native_window_is_closable(ulong self);
6576

77+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
78+
[return: MarshalAs(UnmanagedType.I1)]
79+
public static extern bool native_window_is_close_supported();
80+
6681
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
6782
[return: MarshalAs(UnmanagedType.I1)]
6883
public static extern bool native_window_is_content_protected(ulong self);
@@ -183,6 +198,10 @@ public static partial class Interop
183198
[return: MarshalAs(UnmanagedType.I1)]
184199
public static extern bool native_window_perform_title_bar_double_click(ulong self);
185200

201+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
202+
[return: MarshalAs(UnmanagedType.I1)]
203+
public static extern bool native_window_remove_listener(ulong self, ulong listenerId);
204+
186205
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
187206
[return: MarshalAs(UnmanagedType.I1)]
188207
public static extern bool native_window_reset_title_bar_colors(ulong self);
@@ -279,6 +298,9 @@ public static partial class Interop
279298
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
280299
public static extern uint native_window_get_id(ulong self);
281300

301+
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
302+
public static extern ulong native_window_add_listener(ulong self, WindowEventNativeCallback callback, IntPtr userData, ReleaseUserDataNativeCallback releaseUserData);
303+
282304
[DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
283305
public static extern ulong native_window_create();
284306

‎bindings/csharp/src/NativeAPI/Application.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public sealed record Started : ApplicationEvent;
2525
public sealed record Exiting(int ExitCode) : ApplicationEvent;
2626
public sealed record Activated : ApplicationEvent;
2727
public sealed record Deactivated : ApplicationEvent;
28-
public sealed record QuitRequested : ApplicationEvent;
28+
public sealed record QuitRequested(EventRequest Request) : ApplicationEvent;
2929

3030
internal static ApplicationEvent? FromRaw(in native_application_event_t raw)
3131
{
@@ -35,7 +35,7 @@ public sealed record QuitRequested : ApplicationEvent;
3535
case 1: return new Exiting(raw.data.exiting.exit_code);
3636
case 2: return new Activated();
3737
case 3: return new Deactivated();
38-
case 4: return new QuitRequested();
38+
case 4: return new QuitRequested(new EventRequest(raw.data.quit_requested.request, ownsHandle: false));
3939
default: return null;
4040
}
4141
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// AUTO-GENERATED. DO NOT EDIT.
2+
// Any manual changes WILL BE LOST when this file is regenerated.
3+
#nullable enable
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Runtime.InteropServices;
8+
using CNativeAPI;
9+
10+
namespace NativeAPI;
11+
12+
/// <summary>Owned handle to a native EventDecision.</summary>
13+
public sealed partial class EventDecision : IDisposable
14+
{
15+
public ulong NativeHandle { get; private set; }
16+
private readonly bool _ownsHandle;
17+
18+
public EventDecision(ulong nativeHandle, bool ownsHandle = true)
19+
{
20+
NativeHandle = nativeHandle;
21+
_ownsHandle = ownsHandle;
22+
}
23+
24+
~EventDecision() => ReleaseHandle();
25+
26+
public void Dispose()
27+
{
28+
ReleaseHandle();
29+
GC.SuppressFinalize(this);
30+
}
31+
32+
private void ReleaseHandle()
33+
{
34+
if (_ownsHandle && NativeHandle != 0)
35+
{
36+
Interop.native_event_decision_free(NativeHandle);
37+
NativeHandle = 0;
38+
}
39+
}
40+
41+
public bool Accept()
42+
{
43+
var rawResult = Interop.native_event_decision_accept(NativeHandle);
44+
return rawResult;
45+
}
46+
47+
public bool Cancel()
48+
{
49+
var rawResult = Interop.native_event_decision_cancel(NativeHandle);
50+
return rawResult;
51+
}
52+
53+
public bool IsPending
54+
{
55+
get
56+
{
57+
var rawResult = Interop.native_event_decision_is_pending(NativeHandle);
58+
return rawResult;
59+
}
60+
}
61+
62+
}
63+
64+
/// <summary>Owned handle to a native EventRequest.</summary>
65+
public sealed partial class EventRequest : IDisposable
66+
{
67+
public ulong NativeHandle { get; private set; }
68+
private readonly bool _ownsHandle;
69+
70+
public EventRequest(ulong nativeHandle, bool ownsHandle = true)
71+
{
72+
NativeHandle = nativeHandle;
73+
_ownsHandle = ownsHandle;
74+
}
75+
76+
~EventRequest() => ReleaseHandle();
77+
78+
public void Dispose()
79+
{
80+
ReleaseHandle();
81+
GC.SuppressFinalize(this);
82+
}
83+
84+
private void ReleaseHandle()
85+
{
86+
if (_ownsHandle && NativeHandle != 0)
87+
{
88+
Interop.native_event_request_free(NativeHandle);
89+
NativeHandle = 0;
90+
}
91+
}
92+
93+
public bool IsCancelable
94+
{
95+
get
96+
{
97+
var rawResult = Interop.native_event_request_is_cancelable(NativeHandle);
98+
return rawResult;
99+
}
100+
}
101+
102+
public bool IsCancelled
103+
{
104+
get
105+
{
106+
var rawResult = Interop.native_event_request_is_cancelled(NativeHandle);
107+
return rawResult;
108+
}
109+
}
110+
111+
public bool IsPending
112+
{
113+
get
114+
{
115+
var rawResult = Interop.native_event_request_is_pending(NativeHandle);
116+
return rawResult;
117+
}
118+
}
119+
120+
public bool Cancel()
121+
{
122+
var rawResult = Interop.native_event_request_cancel(NativeHandle);
123+
return rawResult;
124+
}
125+
126+
public EventDecision? Defer()
127+
{
128+
var rawResult = Interop.native_event_request_defer(NativeHandle);
129+
return rawResult == 0 ? null : new EventDecision(rawResult);
130+
}
131+
132+
}
133+

‎bindings/csharp/src/NativeAPI/Window.cs‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public sealed record Created(uint WindowId) : WindowEvent;
6363
public sealed record Closed(uint WindowId) : WindowEvent;
6464
public sealed record EnteredFullScreen(uint WindowId) : WindowEvent;
6565
public sealed record ExitedFullScreen(uint WindowId) : WindowEvent;
66+
public sealed record CloseRequested(uint WindowId, EventRequest Request) : WindowEvent;
6667

6768
internal static WindowEvent? FromRaw(in native_window_event_t raw)
6869
{
@@ -79,6 +80,7 @@ public sealed record ExitedFullScreen(uint WindowId) : WindowEvent;
7980
case 8: return new Closed(raw.window_id);
8081
case 9: return new EnteredFullScreen(raw.window_id);
8182
case 10: return new ExitedFullScreen(raw.window_id);
83+
case 11: return new CloseRequested(raw.window_id, new EventRequest(raw.data.close_requested.request, ownsHandle: false));
8284
default: return null;
8385
}
8486
}
@@ -127,6 +129,18 @@ private void ReleaseHandle()
127129
return handle == 0 ? null : new Window(handle);
128130
}
129131

132+
public static bool IsCloseSupported()
133+
{
134+
var rawResult = Interop.native_window_is_close_supported();
135+
return rawResult;
136+
}
137+
138+
public bool Close()
139+
{
140+
var rawResult = Interop.native_window_close(NativeHandle);
141+
return rawResult;
142+
}
143+
130144
public uint Id
131145
{
132146
get
@@ -849,5 +863,33 @@ public void StartResizing(ResizeEdge edge)
849863
/// <summary>Platform-specific native object behind this handle.</summary>
850864
public IntPtr NativeObject => Interop.native_window_get_native_object(NativeHandle);
851865

866+
/// <summary>Registers <paramref name="callback"/> for every WindowEvent this Window emits.</summary>
867+
/// <remarks>
868+
/// The delegate is kept alive until the listener is removed or its emitter
869+
/// destroyed; the core releases it then.
870+
/// </remarks>
871+
public ulong AddListener(Action<WindowEvent> callback)
872+
{
873+
WindowEventNativeCallback native = (evt, userData) =>
874+
{
875+
if (evt == IntPtr.Zero)
876+
{
877+
return;
878+
}
879+
var value = WindowEvent.FromRaw(Marshal.PtrToStructure<native_window_event_t>(evt));
880+
if (value is not null)
881+
{
882+
callback(value);
883+
}
884+
};
885+
return Interop.native_window_add_listener(NativeHandle, native, CallbackKeeper.Hold(native), CallbackKeeper.Release);
886+
}
887+
888+
/// <summary>Unregisters a listener. Returns false if unknown.</summary>
889+
public bool RemoveListener(ulong listenerId)
890+
{
891+
return Interop.native_window_remove_listener(NativeHandle, listenerId);
892+
}
893+
852894
}
853895

‎bindings/dart/README.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,19 @@ void main() => runNativeApp(app);
122122

123123
`app` must be a top-level or static function: it runs in an isolate of its own. On macOS that isolate lives on the process's first thread, the only one AppKit accepts, which the Dart VM otherwise keeps parked. The process ends when the app quits (`Application.instance.quit()`, Cmd+Q). See [`dart_view_example`](../../examples/dart_view_example) for a larger app built this way.
124124

125+
### Event listeners
126+
127+
Event listeners run on the isolate that registered them, including events emitted
128+
from a native UI thread. A listener may return a `Future<void>`; its event payload
129+
and borrowed objects remain valid until the callback finishes. Use an owned
130+
`EventDecision` when confirmation must continue after the callback returns.
131+
Removing a listener skips queued callbacks that have not started; an active
132+
callback can finish its asynchronous work. Method callback parameters retain
133+
their documented thread requirements.
134+
135+
The shared request primitives are available, but window close and application
136+
quit producers are not yet connected to them.
137+
125138
## Examples
126139

127140
The examples are the `flutter_*` directories in the repository's [`examples/`](../../examples), plus [`dart_view_example`](../../examples/dart_view_example), a plain Dart program (`dart run bin/main.dart`). Each Flutter one is an app for one module; they resolve through the pub workspace at the repository root:

‎bindings/dart/cnativeapi/CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## 0.4.1
22

3+
* Add raw window close capability, object listeners and close-request payloads.
4+
35
* Event loop shim for plain Dart programs (`src/event_loop*`, bound in
46
`lib/src/event_loop.dart`): `cnativeapi_run_ui_thread`,
57
`cnativeapi_start_event_loop`, `cnativeapi_pump_event_loop` and

0 commit comments

Comments
 (0)