Skip to content

Commit 0c4332d

Browse files
committed
Add message dialog and keyboard monitor APIs
Introduces message dialog and keyboard monitor support to the native API bindings, including Dart wrappers and platform integration. Adds MessageDialog and Dialog abstractions, updates bindings and platform source includes, and exposes new APIs in nativeapi.dart. Removes obsolete update_includes.py script and updates submodule and ffigen.yaml for new C API headers.
1 parent 6eda671 commit 0c4332d

13 files changed

Lines changed: 1099 additions & 142 deletions

File tree

packages/cnativeapi/.pubignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
src/libnativeapi/examples
1+
cxx_impl/.cursor
2+
cxx_impl/.github
3+
cxx_impl/examples
4+
codegen.py

packages/cnativeapi/codegen.py

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Script to regenerate bindings by:
4+
1. Pulling latest cxx_impl submodule
5+
2. Updating macos/Classes/cnativeapi.mm include statements
6+
3. Updating ios/Classes/cnativeapi.mm include statements
7+
4. Updating ffigen.yaml with all C API header files
8+
5. Generating bindings with ffigen
9+
"""
10+
11+
import os
12+
import re
13+
import subprocess
14+
import sys
15+
from pathlib import Path
16+
17+
18+
def run_command(cmd, cwd=None):
19+
"""Run a shell command and return success status."""
20+
print(f"Running: {' '.join(cmd)}")
21+
try:
22+
result = subprocess.run(
23+
cmd, cwd=cwd, check=True, capture_output=True, text=True
24+
)
25+
if result.stdout:
26+
print(result.stdout)
27+
return True
28+
except subprocess.CalledProcessError as e:
29+
print(f"Error: {e}")
30+
if e.stderr:
31+
print(f"Error output: {e.stderr}")
32+
return False
33+
34+
35+
def update_cxx_impl():
36+
"""Update cxx_impl submodule to latest."""
37+
print("\nStep 1/5: Updating cxx_impl submodule")
38+
print("Pulling latest changes from cxx_impl submodule...")
39+
40+
script_dir = Path(__file__).parent
41+
repo_root = script_dir.parent.parent
42+
43+
# Update submodule
44+
if not run_command(
45+
["git", "submodule", "update", "--remote", "packages/cnativeapi/cxx_impl"],
46+
cwd=repo_root,
47+
):
48+
print("Warning: Failed to update cxx_impl submodule")
49+
return False
50+
51+
print("Submodule updated successfully")
52+
return True
53+
54+
55+
def find_capi_headers(cxx_impl_dir):
56+
"""Find all C API header files (*_c.h) in cxx_impl/src/capi."""
57+
capi_dir = cxx_impl_dir / "src" / "capi"
58+
if not capi_dir.exists():
59+
return []
60+
61+
headers = []
62+
for header_file in capi_dir.glob("*_c.h"):
63+
# Use relative path from packages/cnativeapi
64+
rel_path = f"cxx_impl/src/capi/{header_file.name}"
65+
headers.append(rel_path)
66+
67+
return sorted(headers)
68+
69+
70+
def update_ffigen_yaml(ffigen_path, capi_headers):
71+
"""Update ffigen.yaml with all C API header files."""
72+
print("\nStep 4/5: Updating ffigen configuration")
73+
print(f"Found {len(capi_headers)} C API header files to process...")
74+
75+
with open(ffigen_path, "r", encoding="utf-8") as f:
76+
content = f.read()
77+
78+
# Find entry-points section
79+
# Format: entry-points:\n - "..."\n - "..."
80+
# Don't include the trailing newline and "include-directives:" in the match
81+
entry_points_pattern = (
82+
r"(entry-points:\n)((?: - .*\n)*)(?=\n include-directives:)"
83+
)
84+
entry_match = re.search(entry_points_pattern, content)
85+
86+
# Find include-directives section
87+
# Format: include-directives:\n - "..."\n - "..."\n\npreamble: |
88+
# Allow flexible whitespace between list and preamble
89+
include_directives_pattern = (
90+
r"(include-directives:\n)((?: - .*\n)+)(\n+preamble:)"
91+
)
92+
include_match = re.search(include_directives_pattern, content)
93+
94+
if not entry_match or not include_match:
95+
print("Error: Could not parse ffigen.yaml")
96+
if not entry_match:
97+
print(" - Missing 'entry-points' section")
98+
if not include_match:
99+
print(" - Missing 'include-directives' section")
100+
return False
101+
102+
# Generate new entries
103+
new_entries = []
104+
for header in capi_headers:
105+
new_entries.append(f' - "{header}"')
106+
107+
# Replace entry-points section: preserve the format with trailing newline
108+
# entry_match.group(2) may end with '\n' (from last list item)
109+
# We need to add a newline before " include-directives:"
110+
new_entry_section = entry_match.group(1) + "\n".join(new_entries) + "\n"
111+
112+
# Replace include-directives section: preserve the format with trailing newlines before preamble
113+
# group(2) ends with '\n' (from last list item), group(3) starts with '\n'
114+
# So we need to ensure the new list also ends with '\n' before appending group(3)
115+
# Build the list part only (without "include-directives:" header)
116+
new_include_list = "\n".join(new_entries) + "\n" + include_match.group(3)
117+
118+
# Replace sections
119+
# entry_match ends before "\n include-directives:", include_match starts at "include-directives:"
120+
# So we need to add "\n include-directives:" before the new list
121+
new_content = (
122+
content[: entry_match.start()]
123+
+ new_entry_section
124+
+ "\n include-directives:"
125+
+ "\n" # Add the " include-directives:" header with newline
126+
+ new_include_list # Add the list items and preamble
127+
+ content[include_match.end() :]
128+
)
129+
130+
with open(ffigen_path, "w", encoding="utf-8") as f:
131+
f.write(new_content)
132+
133+
print(f"Configuration updated with {len(capi_headers)} header files")
134+
for header in capi_headers:
135+
print(f" - {header}")
136+
return True
137+
138+
139+
def find_source_files(cxx_impl_dir, platform):
140+
"""Find all source files (.cpp and .mm) needed for the specified platform."""
141+
src_dir = cxx_impl_dir / "src"
142+
source_files = []
143+
144+
# Find all C++ and Objective-C++ files
145+
for ext in ["*.cpp", "*.mm"]:
146+
source_files.extend(src_dir.rglob(ext))
147+
148+
# Filter files based on platform
149+
filtered_files = []
150+
for file_path in source_files:
151+
file_str = str(file_path)
152+
153+
# Skip example directories
154+
if "examples" in file_str:
155+
continue
156+
157+
# Skip platform-specific files for other platforms
158+
if platform == "macos":
159+
if (
160+
"/platform/linux/" in file_str
161+
or "/platform/windows/" in file_str
162+
or "/platform/android/" in file_str
163+
or "/platform/ios/" in file_str
164+
or "/platform/ohos/" in file_str
165+
):
166+
continue
167+
elif platform == "ios":
168+
if (
169+
"/platform/linux/" in file_str
170+
or "/platform/windows/" in file_str
171+
or "/platform/android/" in file_str
172+
or "/platform/macos/" in file_str
173+
or "/platform/ohos/" in file_str
174+
):
175+
continue
176+
177+
filtered_files.append(file_path)
178+
179+
return filtered_files
180+
181+
182+
def update_nativeapi_mm(nativeapi_path, cxx_impl_dir, platform):
183+
"""Update cnativeapi.mm file with include statements."""
184+
source_files = find_source_files(cxx_impl_dir, platform)
185+
print(f"Processing {len(source_files)} source files for {platform}...")
186+
187+
# Read current file content
188+
with open(nativeapi_path, "r", encoding="utf-8") as f:
189+
content = f.read()
190+
191+
# Find the section with include statements
192+
# Look for the pattern that starts with "// Include source files"
193+
# This should match everything from "// Include source files" to the end of file
194+
include_pattern = r"(// Include source files\n)(.*?)(\Z)"
195+
196+
match = re.search(include_pattern, content, re.DOTALL)
197+
198+
if not match:
199+
print(
200+
f"Error: Could not find '// Include source files' marker in {nativeapi_path.name}"
201+
)
202+
return False
203+
204+
# Generate new include statements
205+
new_includes = []
206+
207+
# Categorize files for better organization
208+
capi_files = []
209+
platform_files = []
210+
core_files = []
211+
foundation_files = []
212+
213+
for file_path in source_files:
214+
file_str = str(file_path)
215+
216+
# Calculate relative path from cnativeapi.mm location
217+
# cnativeapi.mm is at packages/cnativeapi/{platform}/Classes/
218+
# cxx_impl is at packages/cnativeapi/cxx_impl/
219+
# So relative path should be ../../cxx_impl/src/...
220+
rel_path = os.path.relpath(file_path, nativeapi_path.parent)
221+
222+
if "/capi/" in file_str:
223+
capi_files.append(rel_path)
224+
elif f"/platform/{platform}/" in file_str:
225+
platform_files.append(rel_path)
226+
elif "/foundation/" in file_str:
227+
foundation_files.append(rel_path)
228+
else:
229+
core_files.append(rel_path)
230+
231+
# Add includes in organized order
232+
for file_path in (
233+
sorted(capi_files)
234+
+ sorted(platform_files)
235+
+ sorted(foundation_files)
236+
+ sorted(core_files)
237+
):
238+
new_includes.append(f'#include "{file_path}"')
239+
240+
# Print summary of what was included
241+
print(f" - C API files: {len(capi_files)}")
242+
print(f" - Platform-specific files: {len(platform_files)}")
243+
print(f" - Foundation files: {len(foundation_files)}")
244+
print(f" - Core files: {len(core_files)}")
245+
246+
# Build the new include section (no trailing newline, match original format)
247+
new_include_section = match.group(1) + "\n".join(new_includes)
248+
249+
# Replace the old include section with the new one
250+
new_content = content[: match.start()] + new_include_section
251+
252+
# Write back to file
253+
with open(nativeapi_path, "w", encoding="utf-8") as f:
254+
f.write(new_content)
255+
256+
return True
257+
258+
259+
def update_macos_mm(cnativeapi_dir, cxx_impl_dir):
260+
"""Update macos/Classes/cnativeapi.mm."""
261+
print("\nStep 2/5: Updating macOS platform bindings")
262+
263+
macos_mm_path = cnativeapi_dir / "macos/Classes/cnativeapi.mm"
264+
265+
if not macos_mm_path.exists():
266+
print(f"Error: {macos_mm_path} not found")
267+
return False
268+
269+
if update_nativeapi_mm(macos_mm_path, cxx_impl_dir, "macos"):
270+
print("macOS bindings updated successfully")
271+
return True
272+
else:
273+
print("Failed to update macOS bindings")
274+
return False
275+
276+
277+
def update_ios_mm(cnativeapi_dir, cxx_impl_dir):
278+
"""Update ios/Classes/cnativeapi.mm."""
279+
print("\nStep 3/5: Updating iOS platform bindings")
280+
281+
ios_mm_path = cnativeapi_dir / "ios/Classes/cnativeapi.mm"
282+
283+
if not ios_mm_path.exists():
284+
print(f"Error: {ios_mm_path} not found")
285+
return False
286+
287+
if update_nativeapi_mm(ios_mm_path, cxx_impl_dir, "ios"):
288+
print("iOS bindings updated successfully")
289+
return True
290+
else:
291+
print("Failed to update iOS bindings")
292+
return False
293+
294+
295+
def main():
296+
"""Main function to regenerate bindings."""
297+
cnativeapi_dir = Path(__file__).parent
298+
cxx_impl_dir = cnativeapi_dir / "cxx_impl"
299+
ffigen_path = cnativeapi_dir / "ffigen.yaml"
300+
301+
print("\nNative API Bindings Generator")
302+
print("This script will regenerate all platform bindings\n")
303+
304+
# Step 1: Update cxx_impl submodule
305+
if not update_cxx_impl():
306+
print("\nWarning: cxx_impl update failed, continuing anyway...")
307+
308+
# Verify cxx_impl exists
309+
if not cxx_impl_dir.exists():
310+
print(f"\nError: cxx_impl directory not found: {cxx_impl_dir}")
311+
return 1
312+
313+
# Step 2: Update macos/Classes/cnativeapi.mm
314+
if not update_macos_mm(cnativeapi_dir, cxx_impl_dir):
315+
print("\nError: macOS bindings update failed")
316+
return 1
317+
318+
# Step 3: Update ios/Classes/cnativeapi.mm
319+
if not update_ios_mm(cnativeapi_dir, cxx_impl_dir):
320+
print("\nError: iOS bindings update failed")
321+
return 1
322+
323+
# Step 4: Update ffigen.yaml
324+
capi_headers = find_capi_headers(cxx_impl_dir)
325+
if not capi_headers:
326+
print("\nWarning: No C API headers found in cxx_impl")
327+
else:
328+
if not update_ffigen_yaml(ffigen_path, capi_headers):
329+
print("\nError: Failed to update ffigen configuration")
330+
return 1
331+
332+
# Step 5: Generate bindings using ffigen
333+
print("\nStep 5/5: Generating Dart bindings")
334+
print("Running ffigen to generate Dart bindings from C headers...")
335+
336+
ffigen_cmd = ["dart", "run", "ffigen", "--config", "ffigen.yaml"]
337+
if not run_command(ffigen_cmd, cwd=cnativeapi_dir):
338+
print("\nWarning: Failed to generate bindings with ffigen")
339+
print("You can manually run: dart run ffigen --config ffigen.yaml")
340+
else:
341+
print("Dart bindings generated successfully")
342+
343+
print("\nAll steps completed successfully!")
344+
print("\nNext steps:")
345+
print(" 1. Review the changes: git status")
346+
print(" 2. Test the generated bindings")
347+
print(" 3. Commit your changes if everything looks good\n")
348+
349+
return 0
350+
351+
352+
if __name__ == "__main__":
353+
sys.exit(main())

packages/cnativeapi/ffigen.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ headers:
1313
- "cxx_impl/src/capi/display_manager_c.h"
1414
- "cxx_impl/src/capi/geometry_c.h"
1515
- "cxx_impl/src/capi/image_c.h"
16+
- "cxx_impl/src/capi/keyboard_monitor_c.h"
1617
- "cxx_impl/src/capi/menu_c.h"
18+
- "cxx_impl/src/capi/message_dialog_c.h"
1719
- "cxx_impl/src/capi/positioning_strategy_c.h"
1820
- "cxx_impl/src/capi/preferences_c.h"
21+
- "cxx_impl/src/capi/run_example_app_c.h"
1922
- "cxx_impl/src/capi/secure_storage_c.h"
2023
- "cxx_impl/src/capi/string_utils_c.h"
2124
- "cxx_impl/src/capi/tray_icon_c.h"
@@ -30,9 +33,12 @@ headers:
3033
- "cxx_impl/src/capi/display_manager_c.h"
3134
- "cxx_impl/src/capi/geometry_c.h"
3235
- "cxx_impl/src/capi/image_c.h"
36+
- "cxx_impl/src/capi/keyboard_monitor_c.h"
3337
- "cxx_impl/src/capi/menu_c.h"
38+
- "cxx_impl/src/capi/message_dialog_c.h"
3439
- "cxx_impl/src/capi/positioning_strategy_c.h"
3540
- "cxx_impl/src/capi/preferences_c.h"
41+
- "cxx_impl/src/capi/run_example_app_c.h"
3642
- "cxx_impl/src/capi/secure_storage_c.h"
3743
- "cxx_impl/src/capi/string_utils_c.h"
3844
- "cxx_impl/src/capi/tray_icon_c.h"

0 commit comments

Comments
 (0)