Skip to content

Commit 8a84ed8

Browse files
committed
Add launch_at_login example and bindgen tweaks
Add a new Flutter example (examples/launch_at_login_example) demonstrating the LaunchAtLogin API with macOS project files, README, and a complete example UI. Update bindgen configuration to emit generated Dart into packages/nativeapi/lib/src, add string param setup/cleanup and param_name mapping, and adjust the dart-format target. Modify bindgen templates to: import and use cnativeApiBindings for global functions, special-case LaunchAtLogin constructors, marshal program/arguments arrays and returned string lists, and handle string params/returns correctly. Also update nativeapi package sources and cnativeapi bindings/stubs to align with the generated API and update pubspec accordingly.
1 parent 26127eb commit 8a84ed8

54 files changed

Lines changed: 2464 additions & 170 deletions

Some content is hidden

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

bindgen/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ cd nativeapi-flutter
1313
PYTHONPATH=packages/cnativeapi/cxx_impl/tools python3 -m bindgen \
1414
--config bindgen/config.yaml \
1515
--dump-ir bindgen/out/ir.json \
16-
--dump-context bindgen/out/context.json \
17-
--out bindgen/out
16+
--dump-context bindgen/out/context.json
1817
```
1918

2019
## Notes

bindgen/config.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
output_dir: packages/nativeapi/lib/src
2+
13
clang_flags:
24
- -x
35
- c++
46
- -std=c++17
57

68
entry_headers:
9+
- packages/cnativeapi/cxx_impl/src/launch_at_login.h
710
- packages/cnativeapi/cxx_impl/src/url_opener.h
811

912
mapping:
@@ -14,6 +17,7 @@ mapping:
1417
enum_value_name: camel_case
1518
method_name: camel_case
1619
field_name: camel_case
20+
param_name: camel_case
1721

1822
types:
1923
void: void
@@ -34,10 +38,12 @@ mapping:
3438
options:
3539
singleton_classes:
3640
- UrlOpener
41+
string_param_setup: "final {native_name} = {name}.toNativeUtf8().cast<ffi.Char>();"
42+
string_param_cleanup: "pkgffi.malloc.free({native_name});"
3743
formatters:
3844
- name: dart-format-generated
3945
cmd:
4046
- dart
4147
- format
42-
- "{out_dir}/src"
48+
- "{out_dir}"
4349
continue_on_error: true

bindgen/template/file/dart.j2

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44
{%- from 'partials/class.j2' import render_class -%}
55
{%- from 'partials/constant.j2' import render_constant -%}
66
{%- from 'partials/alias.j2' import render_alias -%}
7+
{%- set import_state = namespace(needs_global_bindings=has_functions) -%}
8+
{%- for item in classes -%}
9+
{%- for method in item.methods -%}
10+
{%- if method.static and not method.skip -%}
11+
{%- set import_state.needs_global_bindings = true -%}
12+
{%- endif -%}
13+
{%- endfor -%}
14+
{%- endfor -%}
715

816
// =============================================================================
917
// Auto-generated Dart wrapper for: {{ file_path }}
@@ -19,6 +27,9 @@ import 'dart:ui' as ui;
1927
{%- if uses_pkgffi %}
2028
import 'package:ffi/ffi.dart' as pkgffi;
2129
{%- endif %}
30+
{%- if import_state.needs_global_bindings %}
31+
import 'package:cnativeapi/cnativeapi.dart' show cnativeApiBindings;
32+
{%- endif %}
2233
import 'package:nativeapi/src/foundation/cnativeapi_bindings_mixin.dart';
2334
{%- if has_handle_classes %}
2435
import 'package:nativeapi/src/foundation/native_handle_wrapper.dart';

bindgen/template/partials/class.j2

Lines changed: 132 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{%- macro render_method_call(method) -%}
2-
bindings.{{ method.call_symbol }}(
2+
{{ 'cnativeApiBindings' if method.static else 'bindings' }}.{{ method.call_symbol }}(
33
{%- if method.needs_instance_handle %}
44
_nativeHandle{% if method.call_args|length > 0 %}, {% endif %}
55
{%- endif %}
@@ -9,8 +9,86 @@ bindings.{{ method.call_symbol }}(
99
)
1010
{%- endmacro -%}
1111

12+
{%- macro render_method_return_type(method) -%}
13+
{%- if method.call_symbol.endswith('_get_arguments') and method.return_bridge == 'string' -%}
14+
List<String>
15+
{%- else -%}
16+
{{ method.api_return_type }}
17+
{%- endif -%}
18+
{%- endmacro -%}
19+
20+
{%- macro render_method_param(method, param) -%}
21+
{%- if method.call_symbol.endswith('_set_program') and param.name == 'arguments' and param.api_type == 'String' -%}
22+
List<String> {{ param.name }}
23+
{%- else -%}
24+
{{ param.api_type }} {{ param.name }}
25+
{%- endif -%}
26+
{%- endmacro -%}
27+
1228
{%- macro render_method_body(method) -%}
13-
{%- if method.return_bridge == 'string' %}
29+
{%- if method.call_symbol.endswith('_get_arguments') and method.return_bridge == 'string' %}
30+
final outArguments = pkgffi.malloc<ffi.Pointer<ffi.Pointer<ffi.Char>>>();
31+
final outCount = pkgffi.malloc<ffi.Size>();
32+
try {
33+
final success = bindings.{{ method.call_symbol }}(
34+
_nativeHandle,
35+
outArguments,
36+
outCount,
37+
);
38+
if (!success || outArguments.value == ffi.nullptr) {
39+
return <String>[];
40+
}
41+
42+
final count = outCount.value;
43+
final nativeArguments = outArguments.value;
44+
final result = <String>[];
45+
for (var i = 0; i < count; i++) {
46+
final nativeArgument = nativeArguments[i];
47+
result.add(
48+
nativeArgument == ffi.nullptr
49+
? ''
50+
: nativeArgument.cast<pkgffi.Utf8>().toDartString(),
51+
);
52+
}
53+
54+
for (var i = 0; i < count; i++) {
55+
final nativeArgument = nativeArguments[i];
56+
if (nativeArgument != ffi.nullptr) {
57+
bindings.free_c_str(nativeArgument);
58+
}
59+
}
60+
pkgffi.malloc.free(nativeArguments);
61+
return result;
62+
} finally {
63+
pkgffi.malloc.free(outCount);
64+
pkgffi.malloc.free(outArguments);
65+
}
66+
{%- elif method.call_symbol.endswith('_set_program') and method.params|length == 2 %}
67+
final executablePathNative =
68+
executablePath.toNativeUtf8().cast<ffi.Char>();
69+
final argumentsNative = arguments.isEmpty
70+
? ffi.nullptr.cast<ffi.Pointer<ffi.Char>>()
71+
: pkgffi.malloc<ffi.Pointer<ffi.Char>>(arguments.length);
72+
try {
73+
for (var i = 0; i < arguments.length; i++) {
74+
argumentsNative[i] = arguments[i].toNativeUtf8().cast<ffi.Char>();
75+
}
76+
return bindings.{{ method.call_symbol }}(
77+
_nativeHandle,
78+
executablePathNative,
79+
argumentsNative,
80+
arguments.length,
81+
);
82+
} finally {
83+
for (var i = 0; i < arguments.length; i++) {
84+
pkgffi.malloc.free(argumentsNative[i]);
85+
}
86+
if (arguments.isNotEmpty) {
87+
pkgffi.malloc.free(argumentsNative);
88+
}
89+
pkgffi.malloc.free(executablePathNative);
90+
}
91+
{%- elif method.return_bridge == 'string' %}
1492
final cString = {{ render_method_call(method) }};
1593
if (cString == ffi.nullptr) return '';
1694
final value = cString.cast<pkgffi.Utf8>().toDartString();
@@ -50,6 +128,11 @@ bindings.{{ method.call_symbol }}(
50128
{%- endmacro -%}
51129

52130
{%- macro render_callable(method) -%}
131+
{%- if method.call_symbol.endswith('_get_arguments') and method.return_bridge == 'string' %}
132+
{{ render_method_body(method) }}
133+
{%- elif method.call_symbol.endswith('_set_program') and method.params|length == 2 %}
134+
{{ render_method_body(method) }}
135+
{%- else %}
53136
{%- for line in method.pre_call_lines %}
54137
{{ line }}
55138
{%- endfor %}
@@ -64,6 +147,7 @@ bindings.{{ method.call_symbol }}(
64147
{%- else %}
65148
{{ render_method_body(method) }}
66149
{%- endif %}
150+
{%- endif %}
67151
{%- endmacro -%}
68152

69153
{%- macro render_class(item, mapping) -%}
@@ -76,13 +160,13 @@ class {{ item.name }} with CNativeApiBindingsMixin {
76160
{%- for method in item.methods %}
77161
{%- if not method.skip %}
78162
{%- if method.is_property %}
79-
{{ 'static ' if method.static else '' }}{{ method.api_return_type }} get {{ method.property_name }} {
163+
{{ 'static ' if method.static else '' }}{{ render_method_return_type(method) }} get {{ method.property_name }} {
80164
{{ render_callable(method) }}
81165
}
82166
{%- else %}
83-
{{ 'static ' if method.static else '' }}{{ method.api_return_type }} {{ method.api_name }}(
167+
{{ 'static ' if method.static else '' }}{{ render_method_return_type(method) }} {{ method.api_name }}(
84168
{%- for param in method.params %}
85-
{{ param.api_type }} {{ param.name }}{% if not loop.last %}, {% endif %}
169+
{{ render_method_param(method, param) }}{% if not loop.last %}, {% endif %}
86170
{%- endfor %}
87171
) {
88172
{{ render_callable(method) }}
@@ -106,25 +190,65 @@ class {{ item.name }} with CNativeApiBindingsMixin implements NativeHandleWrappe
106190
late final {{ item.handle_alias }} _nativeHandle;
107191
final bool _ownsHandle;
108192

193+
{%- if item.name == 'LaunchAtLogin' %}
194+
{{ item.name }}({
195+
String? id,
196+
String? displayName,
197+
{{ item.handle_alias }}? nativeHandle,
198+
}) : _ownsHandle = nativeHandle == null {
199+
if (nativeHandle != null) {
200+
_nativeHandle = nativeHandle;
201+
return;
202+
}
203+
204+
if (id != null && displayName != null) {
205+
final idNative = id.toNativeUtf8().cast<ffi.Char>();
206+
final displayNameNative = displayName.toNativeUtf8().cast<ffi.Char>();
207+
try {
208+
_nativeHandle = bindings.{{ item.create_symbol }}_with_id_and_name(
209+
idNative,
210+
displayNameNative,
211+
);
212+
} finally {
213+
pkgffi.malloc.free(displayNameNative);
214+
pkgffi.malloc.free(idNative);
215+
}
216+
return;
217+
}
218+
219+
if (id != null) {
220+
final idNative = id.toNativeUtf8().cast<ffi.Char>();
221+
try {
222+
_nativeHandle = bindings.{{ item.create_symbol }}_with_id(idNative);
223+
} finally {
224+
pkgffi.malloc.free(idNative);
225+
}
226+
return;
227+
}
228+
229+
_nativeHandle = bindings.{{ item.create_symbol }}();
230+
}
231+
{%- else %}
109232
{{ item.name }}([{{ item.handle_alias }}? nativeHandle])
110233
: _ownsHandle = nativeHandle == null {
111234
_nativeHandle = nativeHandle ?? bindings.{{ item.create_symbol }}();
112235
}
113236
{%- endif %}
237+
{%- endif %}
114238

115239
@override
116240
{{ item.handle_alias }} get nativeHandle => _nativeHandle;
117241

118242
{%- for method in item.methods %}
119243
{%- if not method.skip %}
120244
{%- if method.is_property %}
121-
{{ 'static ' if method.static else '' }}{{ method.api_return_type }} get {{ method.property_name }} {
245+
{{ 'static ' if method.static else '' }}{{ render_method_return_type(method) }} get {{ method.property_name }} {
122246
{{ render_callable(method) }}
123247
}
124248
{%- else %}
125-
{{ 'static ' if method.static else '' }}{{ method.api_return_type }} {{ method.api_name }}(
249+
{{ 'static ' if method.static else '' }}{{ render_method_return_type(method) }} {{ method.api_name }}(
126250
{%- for param in method.params %}
127-
{{ param.api_type }} {{ param.name }}{% if not loop.last %}, {% endif %}
251+
{{ render_method_param(method, param) }}{% if not loop.last %}, {% endif %}
128252
{%- endfor %}
129253
) {
130254
{{ render_callable(method) }}

bindgen/template/partials/function.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{%- macro render_function_call(item) -%}
2-
bindings.{{ item.call_symbol }}(
2+
cnativeApiBindings.{{ item.call_symbol }}(
33
{%- for arg in item.call_args %}
44
{{ arg }}{% if not loop.last %}, {% endif %}
55
{%- endfor %}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Miscellaneous
2+
*.class
3+
*.log
4+
*.pyc
5+
*.swp
6+
.DS_Store
7+
.atom/
8+
.build/
9+
.buildlog/
10+
.history
11+
.svn/
12+
.swiftpm/
13+
migrate_working_dir/
14+
15+
# IntelliJ related
16+
*.iml
17+
*.ipr
18+
*.iws
19+
.idea/
20+
21+
# The .vscode folder contains launch configuration and tasks you configure in
22+
# VS Code which you may wish to be included in version control, so this line
23+
# is commented out by default.
24+
#.vscode/
25+
26+
# Flutter/Dart/Pub related
27+
**/doc/api/
28+
**/ios/Flutter/.last_build_id
29+
.dart_tool/
30+
.flutter-plugins-dependencies
31+
.pub-cache/
32+
.pub/
33+
/build/
34+
/coverage/
35+
36+
# Symbolication related
37+
app.*.symbols
38+
39+
# Obfuscation related
40+
app.*.map.json
41+
42+
# Android Studio will place build artifacts here
43+
/android/app/debug
44+
/android/app/profile
45+
/android/app/release
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This file tracks properties of this Flutter project.
2+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
3+
#
4+
# This file should be version controlled and should not be manually edited.
5+
6+
version:
7+
revision: "645a4e5fe50892fa3058d1e52bd405717bd00b74"
8+
channel: "main"
9+
10+
project_type: app
11+
12+
# Tracks metadata for the flutter migrate command
13+
migration:
14+
platforms:
15+
- platform: root
16+
create_revision: 645a4e5fe50892fa3058d1e52bd405717bd00b74
17+
base_revision: 645a4e5fe50892fa3058d1e52bd405717bd00b74
18+
- platform: macos
19+
create_revision: 645a4e5fe50892fa3058d1e52bd405717bd00b74
20+
base_revision: 645a4e5fe50892fa3058d1e52bd405717bd00b74
21+
22+
# User provided section
23+
24+
# List of Local paths (relative to this file) that should be
25+
# ignored by the migrate tool.
26+
#
27+
# Files that are not part of the templates will be ignored by default.
28+
unmanaged_files:
29+
- 'lib/main.dart'
30+
- 'ios/Runner.xcodeproj/project.pbxproj'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# launch_at_login_example
2+
3+
Flutter example demonstrating how to use the `LaunchAtLogin` API from `package:nativeapi`.
4+
5+
This example shows how to:
6+
7+
- Check if launch-at-login is supported on the current platform.
8+
- Create and configure a launch-at-login entry (display name, program, arguments).
9+
- Enable and disable launch-at-login for your application.
10+
- Read back the current launch-at-login state and configuration.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include: package:flutter_lints/flutter.yaml
2+
3+
linter:
4+
rules:

0 commit comments

Comments
 (0)