cj-tauri 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -99
- package/bin/cj-tauri.js +287 -287
- package/cjpm.toml +63 -52
- package/cli/cjpm.toml +22 -22
- package/cli/src/doctor.cj +308 -306
- package/cli/src/main.cj +129 -129
- package/cli/src/manifest.cj +632 -558
- package/cli/src/manifest_test.cj +87 -0
- package/cli/src/project.cj +770 -770
- package/cli/src/resolve.cj +348 -348
- package/cli/src/scaffold.cj +369 -369
- package/cli/src/sync.cj +526 -526
- package/cli/src/util.cj +168 -168
- package/cli/templates/app/README.md +51 -48
- package/cli/templates/app/assets/capabilities/default.json +7 -7
- package/cli/templates/app/assets/ui/index.html +120 -118
- package/cli/templates/app/cj-tauri.toml +32 -22
- package/cli/templates/app/cjpm.toml +34 -34
- package/cli/templates/app/src/main.cj +101 -90
- package/cli/templates/app-react/README.md +56 -56
- package/cli/templates/app-react/assets/capabilities/default.json +7 -7
- package/cli/templates/app-react/assets/ui/index.html +25 -25
- package/cli/templates/app-react/assets/ui/package.json +19 -19
- package/cli/templates/app-react/assets/ui/src/App.css +67 -67
- package/cli/templates/app-react/assets/ui/src/App.jsx +124 -124
- package/cli/templates/app-react/assets/ui/src/main.jsx +6 -6
- package/cli/templates/app-react/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-react/cj-tauri.toml +33 -23
- package/cli/templates/app-react/cjpm.toml +34 -34
- package/cli/templates/app-react/src/main.cj +91 -91
- package/cli/templates/app-vue/README.md +56 -56
- package/cli/templates/app-vue/assets/capabilities/default.json +7 -7
- package/cli/templates/app-vue/assets/ui/index.html +25 -25
- package/cli/templates/app-vue/assets/ui/package.json +18 -18
- package/cli/templates/app-vue/assets/ui/src/App.vue +176 -176
- package/cli/templates/app-vue/assets/ui/src/main.js +4 -4
- package/cli/templates/app-vue/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-vue/cj-tauri.toml +33 -23
- package/cli/templates/app-vue/cjpm.toml +34 -34
- package/cli/templates/app-vue/src/main.cj +91 -91
- package/cli/templates/ohos-skeleton/AppScope/app.json5 +13 -13
- package/cli/templates/ohos-skeleton/AppScope/resources/base/element/string.json +8 -8
- package/cli/templates/ohos-skeleton/AppScope/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/README.md +84 -79
- package/cli/templates/ohos-skeleton/build-hap.sh +15 -0
- package/cli/templates/ohos-skeleton/build-profile.json5 +46 -46
- package/cli/templates/ohos-skeleton/entry/build-profile.json5 +26 -26
- package/cli/templates/ohos-skeleton/entry/cjpm.toml +51 -43
- package/cli/templates/ohos-skeleton/entry/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/entry/oh-package.json5 +9 -9
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/ability_stage.cj +17 -17
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/app_host.cj +94 -94
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -167
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/main_ability.cj +139 -139
- package/cli/templates/ohos-skeleton/entry/src/main/module.json5 +57 -49
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/color.json +8 -8
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/string.json +16 -16
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/profile/main_pages.json +2 -2
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/capabilities.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/index.html +92 -92
- package/cli/templates/ohos-skeleton/hvigor/hvigor-config.json5 +5 -5
- package/cli/templates/ohos-skeleton/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/oh-package.json5 +10 -10
- package/native/build_win.bat +62 -62
- package/native/core/bridge_core.c +1681 -1626
- package/native/core/bridge_core.h +675 -618
- package/native/core/bridge_js.h +108 -108
- package/native/platforms/harmonyos/README.md +115 -6
- package/native/platforms/linux/bridge_linux.c +2787 -2485
- package/native/platforms/windows/bridge_win.c +3740 -3248
- package/native/tests/gtk_threading_probe.c +143 -143
- package/native/tests/test_bridge_core.c +1339 -1228
- package/package.json +53 -53
- package/prebuilt/linux-x64/cj-tauri +0 -0
- package/src/api_system.cj +67 -67
- package/src/app.cj +325 -969
- package/src/app_events.cj +166 -0
- package/src/app_host_ops.cj +287 -0
- package/src/app_run.cj +283 -0
- package/src/app_spec.cj +76 -76
- package/src/arkweb_menu.cj +216 -0
- package/src/arkweb_script.cj +142 -142
- package/src/capability.cj +409 -409
- package/src/clipboard_bridge.cj +178 -0
- package/src/dialog_spec.cj +79 -0
- package/src/dragdrop.cj +26 -26
- package/src/embedded_assets.cj +98 -98
- package/src/host.cj +752 -607
- package/src/host_arkweb.cj +924 -778
- package/src/host_webkit.cj +663 -570
- package/src/host_webview2.cj +663 -570
- package/src/hotkey.cj +66 -66
- package/src/ipc_hub.cj +337 -337
- package/src/ipc_message.cj +286 -286
- package/src/launch.cj +62 -62
- package/src/logging.cj +127 -127
- package/src/menu.cj +197 -197
- package/src/platform/platform_app_context.cj +77 -77
- package/src/platform/platform_assets.cj +33 -33
- package/src/platform/platform_discover.cj +176 -176
- package/src/platform/platform_env.cj +29 -29
- package/src/platform/platform_feature.cj +31 -31
- package/src/platform/platform_page_source.cj +87 -87
- package/src/platform/platform_target_info.cj +32 -32
- package/src/platform_host_provider.cj +87 -87
- package/src/plugin.cj +333 -333
- package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
- package/src/plugins/dialog/plugin_dialog.cj +209 -209
- package/src/plugins/fs/plugin_fs.cj +105 -1
- package/src/plugins/http/http_backend.cj +131 -0
- package/src/plugins/http/http_desktop.cj +195 -0
- package/src/plugins/http/http_ohos.cj +204 -0
- package/src/plugins/http/plugin_http.cj +310 -459
- package/src/plugins/log/plugin_log.cj +105 -105
- package/src/plugins/menu/plugin_menu.cj +158 -158
- package/src/plugins/notification/plugin_notification.cj +154 -154
- package/src/plugins/serial/plugin_serial.cj +458 -454
- package/src/plugins/serial/serial_bridge.cj +194 -194
- package/src/plugins/serial/serial_manager_ohos.cj +92 -0
- package/src/plugins/serial/serial_ohos.cj +529 -0
- package/src/plugins/shell/plugin_shell.cj +258 -258
- package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
- package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
- package/src/plugins/ws/plugin_ws.cj +488 -470
- package/src/process_hidden.cj +174 -174
- package/src/scope.cj +391 -391
- package/src/shell_event.cj +69 -69
- package/src/tests/app_spec_test.cj +126 -127
- package/src/tests/capability_test.cj +243 -243
- package/src/tests/dialog_spec_test.cj +73 -0
- package/src/tests/fake_host.cj +201 -201
- package/src/tests/ipc_hub_test.cj +487 -487
- package/src/tests/logging_test.cj +62 -62
- package/src/tests/platform_contract_test.cj +251 -251
- package/src/tests/plugin_clipboard_test.cj +222 -222
- package/src/tests/plugin_dialog_test.cj +237 -237
- package/src/tests/plugin_fs_test.cj +207 -0
- package/src/tests/plugin_http_test.cj +124 -124
- package/src/tests/plugin_notification_test.cj +205 -205
- package/src/tests/plugin_serial_test.cj +48 -0
- package/src/tests/plugin_test.cj +605 -585
- package/src/tests/plugin_ws_test.cj +40 -40
- package/src/tests/process_hidden_test.cj +129 -129
- package/src/tests/scope_test.cj +350 -350
- package/src/tests/system_host_test.cj +99 -81
- package/src/tests/urlutil_test.cj +53 -53
- package/src/tests/version_test.cj +75 -75
- package/src/tests/window_registry_test.cj +123 -123
- package/src/tray.cj +90 -90
- package/src/urlutil.cj +159 -159
- package/src/version.cj +44 -44
- package/src/window.cj +51 -51
- package/src/window_registry.cj +116 -116
- package/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/plugins/serial/serial_ohos_stub.cj +0 -61
- package/src/plugins/sqlite/sqlite_ohos_stub.cj +0 -91
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
package cjTauri.plugins.serial
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ohos 侧 serial 真实现(批次 4,issue #5):NDK FFI 直连 musl libc 的 POSIX 串口面。
|
|
5
|
+
* 前身是 P-1 桩(serial_ohos_stub.cj,已删);写法照剪贴板那次的「NDK FFI 直连」模式
|
|
6
|
+
* (`src/clipboard_bridge.cj`),不经 C 桥、不加能力位。
|
|
7
|
+
*
|
|
8
|
+
* 为什么不引 serialport 式的现成库:std / stdx 都没有串口与 termios 绑定;同类项目 sscom
|
|
9
|
+
* 实测 serialport 依赖 nix/libudev 在 OHOS target 编不过,只能手写 POSIX——本文件同理,
|
|
10
|
+
* termios 结构体按 musl(aarch64 linux)布局手工按字节偏移读写,不依赖 @C 结构体映射。
|
|
11
|
+
*
|
|
12
|
+
* 边界与桌面两端的差异(对齐 RFC §4.6.4 P-2 的口径):
|
|
13
|
+
* - 桌面侧「真正的白名单在 C 桥 core」;ohos 没有 C 桥,本文件**就是**安全边界,
|
|
14
|
+
* open 前必须再过一次 `serialPathLooksAllowed`(仓颉侧镜像,plugin_serial.cj)——
|
|
15
|
+
* 页面可控字符串绝不能只靠上层命令层的校验。
|
|
16
|
+
* - 错误明细是包级共享槽(仓颉没有线程局部),Mutex 守着;并发失败时可能被覆盖,
|
|
17
|
+
* 代价只是错误文案不准,与桌面「线程局部」的语义差异如实记在这里。
|
|
18
|
+
* - 读超时用 poll(有数据才 read,与桌面 Linux 桥同口径);写按预算轮转。
|
|
19
|
+
* - 设备节点访问在真机上可能要 DDK/USB 权限甚至系统签名(sscom 同款风险),装不上 /
|
|
20
|
+
* 打不开时本实现如实报错,不伪装成功。
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
@When[env == "ohos"]
|
|
24
|
+
import std.collection.*
|
|
25
|
+
|
|
26
|
+
// std.sync 只被 ohos 门禁代码用(Mutex 状态槽),import 随之门禁——桌面侧整段摘除
|
|
27
|
+
@When[env == "ohos"]
|
|
28
|
+
import std.sync.*
|
|
29
|
+
|
|
30
|
+
@When[env == "ohos"]
|
|
31
|
+
import std.time.*
|
|
32
|
+
|
|
33
|
+
@When[env == "ohos"]
|
|
34
|
+
import std.fs.*
|
|
35
|
+
|
|
36
|
+
@When[env == "ohos"]
|
|
37
|
+
import std.sort.*
|
|
38
|
+
|
|
39
|
+
// ---- musl libc 的 POSIX 串口面(@C foreign 直连;符号都在 libc,无需 [links] 追加) ----
|
|
40
|
+
|
|
41
|
+
/** open 只按双参声明:aarch64(AAPCS64)上可变参与固定参的整型传参相同, musl 的 open 直接可用 */
|
|
42
|
+
@When[env == "ohos"]
|
|
43
|
+
@C
|
|
44
|
+
foreign func open(path: CString, flags: Int32): Int32
|
|
45
|
+
|
|
46
|
+
@When[env == "ohos"]
|
|
47
|
+
@C
|
|
48
|
+
foreign func close(fd: Int32): Int32
|
|
49
|
+
|
|
50
|
+
@When[env == "ohos"]
|
|
51
|
+
@C
|
|
52
|
+
foreign func read(fd: Int32, buf: CPointer<UInt8>, count: UInt64): Int64
|
|
53
|
+
|
|
54
|
+
@When[env == "ohos"]
|
|
55
|
+
@C
|
|
56
|
+
foreign func write(fd: Int32, buf: CPointer<UInt8>, count: UInt64): Int64
|
|
57
|
+
|
|
58
|
+
/** termios 按 64 字节裸缓冲传递(musl aarch64 布局,实际 60 字节) */
|
|
59
|
+
@When[env == "ohos"]
|
|
60
|
+
@C
|
|
61
|
+
foreign func tcgetattr(fd: Int32, termios: CPointer<UInt8>): Int32
|
|
62
|
+
|
|
63
|
+
@When[env == "ohos"]
|
|
64
|
+
@C
|
|
65
|
+
foreign func tcsetattr(fd: Int32, actions: Int32, termios: CPointer<UInt8>): Int32
|
|
66
|
+
|
|
67
|
+
@When[env == "ohos"]
|
|
68
|
+
@C
|
|
69
|
+
foreign func cfsetispeed(termios: CPointer<UInt8>, speed: UInt32): Int32
|
|
70
|
+
|
|
71
|
+
@When[env == "ohos"]
|
|
72
|
+
@C
|
|
73
|
+
foreign func cfsetospeed(termios: CPointer<UInt8>, speed: UInt32): Int32
|
|
74
|
+
|
|
75
|
+
/** pollfd 按 8 字节裸缓冲:fd(Int32) | events(Int16) | revents(Int16) */
|
|
76
|
+
@When[env == "ohos"]
|
|
77
|
+
@C
|
|
78
|
+
foreign func poll(fds: CPointer<UInt8>, nfds: UInt64, timeout: Int32): Int32
|
|
79
|
+
|
|
80
|
+
/** errno 与 strerror:给 serialLastError 一句人话(strerror 返回静态串,不释放) */
|
|
81
|
+
@When[env == "ohos"]
|
|
82
|
+
@C
|
|
83
|
+
foreign func __errno_location(): CPointer<Int32>
|
|
84
|
+
|
|
85
|
+
@When[env == "ohos"]
|
|
86
|
+
@C
|
|
87
|
+
foreign func strerror(errnum: Int32): CString
|
|
88
|
+
|
|
89
|
+
// ---- Linux/asm-generic 常量(与 sscom 的取值一字不差) ----
|
|
90
|
+
|
|
91
|
+
// open flags
|
|
92
|
+
const OHOS_O_RDWR: Int32 = 0x2
|
|
93
|
+
const OHOS_O_NOCTTY: Int32 = 0x100
|
|
94
|
+
|
|
95
|
+
// tcsetattr actions:TCSANOW
|
|
96
|
+
const OHOS_TCSANOW: Int32 = 0
|
|
97
|
+
|
|
98
|
+
// cflag 位
|
|
99
|
+
const OHOS_CSIZE: UInt32 = 0x30
|
|
100
|
+
const OHOS_CS8: UInt32 = 0x30
|
|
101
|
+
const OHOS_CSTOPB: UInt32 = 0x40
|
|
102
|
+
const OHOS_CREAD: UInt32 = 0x80
|
|
103
|
+
const OHOS_PARENB: UInt32 = 0x100
|
|
104
|
+
const OHOS_PARODD: UInt32 = 0x200
|
|
105
|
+
const OHOS_CLOCAL: UInt32 = 0x800
|
|
106
|
+
|
|
107
|
+
// iflag 原始位(cfmakeraw 要清的):IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON
|
|
108
|
+
const OHOS_IFLAG_RAW: UInt32 = 0x5eb
|
|
109
|
+
|
|
110
|
+
// lflag 原始位:ECHO|ICANON|ISIG|IEXTEN
|
|
111
|
+
const OHOS_LFLAG_RAW: UInt32 = 0x800b
|
|
112
|
+
|
|
113
|
+
// poll 事件
|
|
114
|
+
const OHOS_POLLIN: Int16 = 0x1
|
|
115
|
+
const OHOS_POLLOUT: Int16 = 0x4
|
|
116
|
+
// errno:EAGAIN
|
|
117
|
+
const OHOS_EAGAIN: Int32 = 11
|
|
118
|
+
|
|
119
|
+
/** termios 结构体里 speed 字段的字节偏移(musl aarch64:c_ispeed@52 / c_ospeed@56) */
|
|
120
|
+
const OHOS_TERMIOS_ISPEED: Int64 = 52
|
|
121
|
+
const OHOS_TERMIOS_OSPEED: Int64 = 56
|
|
122
|
+
|
|
123
|
+
/** c_cc 里 VTIME / VMIN 的下标(Linux 布局);poll+read 模式下两个都置 0 */
|
|
124
|
+
const OHOS_VTIME: Int64 = 5
|
|
125
|
+
const OHOS_VMIN: Int64 = 6
|
|
126
|
+
|
|
127
|
+
/** 波特率 → termios 速度常量(asm-generic:B38400 以下连续,往上 0x1000+n);
|
|
128
|
+
* 档位与 plugin_serial.cj 的 SERIAL_BAUDS 对齐(那层已拦过一次,这里兜底)。
|
|
129
|
+
* public:单测要直接构造(跨包可见性,AGENTS §2) */
|
|
130
|
+
public func serialOhosBaudConst(baud: Int32): Option<UInt32> {
|
|
131
|
+
let low: Option<UInt32> = match (baud) {
|
|
132
|
+
case 300 => Some(0x7)
|
|
133
|
+
case 600 => Some(0x8)
|
|
134
|
+
case 1200 => Some(0x9)
|
|
135
|
+
case 2400 => Some(0xb)
|
|
136
|
+
case 4800 => Some(0xc)
|
|
137
|
+
case 9600 => Some(0xd)
|
|
138
|
+
case 19200 => Some(0xe)
|
|
139
|
+
case 38400 => Some(0xf)
|
|
140
|
+
case _ => None
|
|
141
|
+
}
|
|
142
|
+
if (let Some(v) <- low) {
|
|
143
|
+
return low
|
|
144
|
+
}
|
|
145
|
+
match (baud) {
|
|
146
|
+
case 57600 => Some(0x1001)
|
|
147
|
+
case 115200 => Some(0x1002)
|
|
148
|
+
case 230400 => Some(0x1003)
|
|
149
|
+
case 460800 => Some(0x1004)
|
|
150
|
+
case 500000 => Some(0x1005)
|
|
151
|
+
case 921600 => Some(0x1006)
|
|
152
|
+
case 1000000 => Some(0x1008)
|
|
153
|
+
case _ => None
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---- 错误明细(包级共享槽,Mutex 守护;见文件头与桌面线程局部语义的差异说明) ----
|
|
158
|
+
// 引用 ohos 专属 foreign(strerror / __errno_location / poll)的辅助一律带门禁,
|
|
159
|
+
// 否则桌面构建在链接期找不到符号(AGENTS §4:跨平台文件别按单平台的 warning 清 import)
|
|
160
|
+
|
|
161
|
+
@When[env == "ohos"]
|
|
162
|
+
let OHOS_SERIAL_ERR_MUTEX = Mutex()
|
|
163
|
+
|
|
164
|
+
@When[env == "ohos"]
|
|
165
|
+
var ohosSerialLastError = ""
|
|
166
|
+
|
|
167
|
+
@When[env == "ohos"]
|
|
168
|
+
func serialOhosSetError(msg: String): Unit {
|
|
169
|
+
OHOS_SERIAL_ERR_MUTEX.lock()
|
|
170
|
+
ohosSerialLastError = msg
|
|
171
|
+
OHOS_SERIAL_ERR_MUTEX.unlock()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@When[env == "ohos"]
|
|
175
|
+
func serialOhosErrno(): Int32 {
|
|
176
|
+
unsafe {
|
|
177
|
+
let p = __errno_location()
|
|
178
|
+
if (p.isNull()) {
|
|
179
|
+
return 0
|
|
180
|
+
}
|
|
181
|
+
return p.read(0)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** 最近一次 errno 的人话(strerror 返回静态串,不释放);调用处无需 unsafe */
|
|
186
|
+
@When[env == "ohos"]
|
|
187
|
+
func serialOhosErrnoText(): String {
|
|
188
|
+
unsafe {
|
|
189
|
+
return strerror(serialOhosErrno()).toString()
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ---- termios 裸缓冲的读写辅助(aarch64 是小端;public 供单测直接构造,AGENTS §2) ----
|
|
194
|
+
|
|
195
|
+
public func serialOhosGetU32(t: CPointer<UInt8>, off: Int64): UInt32 {
|
|
196
|
+
unsafe {
|
|
197
|
+
let b0 = UInt32(t.read(off))
|
|
198
|
+
let b1 = UInt32(t.read(off + 1)) << 8
|
|
199
|
+
let b2 = UInt32(t.read(off + 2)) << 16
|
|
200
|
+
let b3 = UInt32(t.read(off + 3)) << 24
|
|
201
|
+
return b0 | b1 | b2 | b3
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public func serialOhosPutU32(t: CPointer<UInt8>, off: Int64, v: UInt32): Unit {
|
|
206
|
+
unsafe {
|
|
207
|
+
t.write(off, UInt8(v & 0xff))
|
|
208
|
+
t.write(off + 1, UInt8((v >> 8) & 0xff))
|
|
209
|
+
t.write(off + 2, UInt8((v >> 16) & 0xff))
|
|
210
|
+
t.write(off + 3, UInt8((v >> 24) & 0xff))
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** poll 单 fd 单事件:>0 就绪,0 超时,<0 出错(errno 已置) */
|
|
215
|
+
@When[env == "ohos"]
|
|
216
|
+
func serialOhosPollOne(fd: Int32, events: Int16, timeoutMs: Int32): Int32 {
|
|
217
|
+
let pfd = LibC.malloc<UInt8>(count: 8)
|
|
218
|
+
unsafe {
|
|
219
|
+
// fd 低 4 字节小端写入;events 高位偏移 4;revents(偏移 6)由内核填,预清零
|
|
220
|
+
pfd.write(0, UInt8(Int32(fd) & 0xff))
|
|
221
|
+
pfd.write(1, UInt8((fd >> 8) & 0xff))
|
|
222
|
+
pfd.write(2, UInt8((fd >> 16) & 0xff))
|
|
223
|
+
pfd.write(3, UInt8((fd >> 24) & 0xff))
|
|
224
|
+
pfd.write(4, UInt8(UInt16(events) & 0xff))
|
|
225
|
+
pfd.write(5, UInt8((UInt16(events) >> 8) & 0xff))
|
|
226
|
+
pfd.write(6, UInt8(0))
|
|
227
|
+
pfd.write(7, UInt8(0))
|
|
228
|
+
}
|
|
229
|
+
var r: Int32 = 0
|
|
230
|
+
unsafe {
|
|
231
|
+
r = poll(pfd, 1, timeoutMs)
|
|
232
|
+
}
|
|
233
|
+
unsafe {
|
|
234
|
+
LibC.free(pfd)
|
|
235
|
+
}
|
|
236
|
+
return r
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 打开并配置串口:成功返回 >0 句柄(fd+1,保证恒 >0),失败负码:
|
|
241
|
+
* -1 参数 / 白名单、-3 open 或 termios 配置失败(明细在 serialLastError())。
|
|
242
|
+
* 线程安全:fd 由调用方独占使用;重复 open 同一路径是两次独立连接(与桌面同形)。
|
|
243
|
+
*/
|
|
244
|
+
@When[env == "ohos"]
|
|
245
|
+
public func serialOpen(path: String, baud: Int32, dataBits: Int32, parity: Int32,
|
|
246
|
+
stopBits: Int32): Int64 {
|
|
247
|
+
// ohos 没有桌面 C 桥那层白名单,本函数就是边界:页面可控路径必须在这里再拦一次
|
|
248
|
+
if (!serialPathLooksAllowed(path)) {
|
|
249
|
+
serialOhosSetError("path \"${path}\" 不在允许的设备名单内(serialPathLooksAllowed)")
|
|
250
|
+
return -1
|
|
251
|
+
}
|
|
252
|
+
if (serialOhosBaudConst(baud).isNone()) {
|
|
253
|
+
serialOhosSetError("不支持的波特率 ${baud}")
|
|
254
|
+
return -1
|
|
255
|
+
}
|
|
256
|
+
if (dataBits < 5 || dataBits > 8) {
|
|
257
|
+
serialOhosSetError("dataBits 只收 5..8(拿到 ${dataBits})")
|
|
258
|
+
return -1
|
|
259
|
+
}
|
|
260
|
+
if (parity < 0 || parity > 2) {
|
|
261
|
+
serialOhosSetError("parity 只收 0|1|2(拿到 ${parity})")
|
|
262
|
+
return -1
|
|
263
|
+
}
|
|
264
|
+
if (stopBits != 1 && stopBits != 2) {
|
|
265
|
+
serialOhosSetError("stopBits 只收 1|2(拿到 ${stopBits})")
|
|
266
|
+
return -1
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
var fd: Int32 = 0
|
|
270
|
+
unsafe {
|
|
271
|
+
let cpath = LibC.mallocCString(path)
|
|
272
|
+
fd = open(cpath, OHOS_O_RDWR | OHOS_O_NOCTTY)
|
|
273
|
+
LibC.free(cpath)
|
|
274
|
+
}
|
|
275
|
+
if (fd < 0) {
|
|
276
|
+
// POSIX 打不开就走 serialManager(serial_manager_ohos.cj)。普通三方应用对 /dev/bus
|
|
277
|
+
// 整体不可达(SELinux,连 hdc shell 都进不去),seriaManager 由系统代管设备节点、
|
|
278
|
+
// 用户点一次授权即可——这是三方应用碰 USB 串口的正路,不是「降级兜底」。
|
|
279
|
+
let posixErr = serialOhosErrnoText()
|
|
280
|
+
let usbHandle = usbSerialOpenByPath(path, baud, dataBits, parity, stopBits)
|
|
281
|
+
if (usbHandle > 0) {
|
|
282
|
+
return usbHandle
|
|
283
|
+
}
|
|
284
|
+
// 两条路都失败:POSIX 的 errno 与 USB 侧原因一并写清,否则只看到「端口表里没有」
|
|
285
|
+
serialOhosSetError("open failed: POSIX ${posixErr};USB ${serialLastError()}")
|
|
286
|
+
return -3
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
let t = LibC.malloc<UInt8>(count: 64)
|
|
290
|
+
unsafe {
|
|
291
|
+
for (i in 0..64) {
|
|
292
|
+
t.write(i, 0)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
var rc: Int32 = 0
|
|
296
|
+
unsafe {
|
|
297
|
+
rc = tcgetattr(fd, t)
|
|
298
|
+
}
|
|
299
|
+
if (rc != 0) {
|
|
300
|
+
serialOhosSetError("tcgetattr failed: ${serialOhosErrnoText()}")
|
|
301
|
+
unsafe {
|
|
302
|
+
close(fd)
|
|
303
|
+
LibC.free(t)
|
|
304
|
+
}
|
|
305
|
+
return -3
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// cfmakeraw 等价:清 iflag/lflag 原始位,oflag/lflag 归零,cflag 重设数据位/校验/停止位
|
|
309
|
+
// (取反用「异或全 1 掩码」:仓颉没有 ~ 运算符,2026-10-24 编译实测)
|
|
310
|
+
let iflag = serialOhosGetU32(t, 0) & (OHOS_IFLAG_RAW ^ 0xFFFF_FFFF)
|
|
311
|
+
serialOhosPutU32(t, 0, iflag)
|
|
312
|
+
serialOhosPutU32(t, 4, 0)
|
|
313
|
+
var cflag = serialOhosGetU32(t, 8) & ((OHOS_CSIZE | OHOS_PARENB | OHOS_PARODD | OHOS_CSTOPB) ^ 0xFFFF_FFFF)
|
|
314
|
+
cflag |= OHOS_CREAD | OHOS_CLOCAL
|
|
315
|
+
// dataBits:CS5..CS8 = 0x00/0x10/0x20/0x30
|
|
316
|
+
cflag |= UInt32(dataBits - 5) << 4
|
|
317
|
+
if (parity == 1) {
|
|
318
|
+
cflag |= OHOS_PARODD | OHOS_PARENB
|
|
319
|
+
} else if (parity == 2) {
|
|
320
|
+
cflag |= OHOS_PARENB
|
|
321
|
+
}
|
|
322
|
+
if (stopBits == 2) {
|
|
323
|
+
cflag |= OHOS_CSTOPB
|
|
324
|
+
}
|
|
325
|
+
serialOhosPutU32(t, 8, cflag)
|
|
326
|
+
// lflag 清原始位(c_line@16 与 c_cc 其余位保留,buffer 已按 tcgetattr 的返回初始化)
|
|
327
|
+
serialOhosPutU32(t, 12, serialOhosGetU32(t, 12) & (OHOS_LFLAG_RAW ^ 0xFFFF_FFFF))
|
|
328
|
+
// VTIME / VMIN 置 0:read 不自带阻塞预算,超时由 poll 负责(与桌面 Linux 桥同口径)
|
|
329
|
+
unsafe {
|
|
330
|
+
t.write(17 + OHOS_VTIME, UInt8(0))
|
|
331
|
+
t.write(17 + OHOS_VMIN, UInt8(0))
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
match (serialOhosBaudConst(baud)) {
|
|
335
|
+
case Some(speed) =>
|
|
336
|
+
unsafe {
|
|
337
|
+
cfsetispeed(t, speed)
|
|
338
|
+
cfsetospeed(t, speed)
|
|
339
|
+
}
|
|
340
|
+
case None => () // 上面已拦,不可能走到
|
|
341
|
+
}
|
|
342
|
+
unsafe {
|
|
343
|
+
rc = tcsetattr(fd, OHOS_TCSANOW, t)
|
|
344
|
+
}
|
|
345
|
+
if (rc != 0) {
|
|
346
|
+
serialOhosSetError("tcsetattr failed: ${serialOhosErrnoText()}")
|
|
347
|
+
unsafe {
|
|
348
|
+
close(fd)
|
|
349
|
+
LibC.free(t)
|
|
350
|
+
}
|
|
351
|
+
return -3
|
|
352
|
+
}
|
|
353
|
+
unsafe {
|
|
354
|
+
LibC.free(t)
|
|
355
|
+
}
|
|
356
|
+
return Int64(fd) + 1
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** 最近一次失败的明细(包级共享槽;见文件头与桌面线程局部语义的差异说明) */
|
|
360
|
+
@When[env == "ohos"]
|
|
361
|
+
public func serialLastError(): String {
|
|
362
|
+
OHOS_SERIAL_ERR_MUTEX.lock()
|
|
363
|
+
let msg = ohosSerialLastError
|
|
364
|
+
OHOS_SERIAL_ERR_MUTEX.unlock()
|
|
365
|
+
return msg
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** ohos 不走桌面 C 桥的枚举(返回 None,list 命令走下面的 serialPortList 目录扫描) */
|
|
369
|
+
@When[env == "ohos"]
|
|
370
|
+
public func serialListC(): Option<ArrayList<String>> {
|
|
371
|
+
return None
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* 读:最多 maxBytes 字节、最多等 timeoutMs 毫秒(0 = 立即返回)。
|
|
376
|
+
* 返回实际读到的字节(空数组 = 超时无数据)。poll 有数据才 read 一次——
|
|
377
|
+
* 与桌面 Linux 桥「poll + read」同口径,不会在没数据时白等。
|
|
378
|
+
*/
|
|
379
|
+
@When[env == "ohos"]
|
|
380
|
+
public func serialRead(h: Int64, maxBytes: Int32, timeoutMs: Int32): Array<UInt8> {
|
|
381
|
+
if (usbSerialIsHandle(h)) {
|
|
382
|
+
return usbSerialReadHandle(h, maxBytes, timeoutMs)
|
|
383
|
+
}
|
|
384
|
+
let fd = Int32(h - 1)
|
|
385
|
+
let ready = serialOhosPollOne(fd, OHOS_POLLIN, timeoutMs)
|
|
386
|
+
if (ready <= 0) {
|
|
387
|
+
if (ready < 0) {
|
|
388
|
+
serialOhosSetError("poll failed: ${serialOhosErrnoText()}")
|
|
389
|
+
}
|
|
390
|
+
return Array<UInt8>(0, repeat: 0)
|
|
391
|
+
}
|
|
392
|
+
let cap = Int64(maxBytes)
|
|
393
|
+
let buf = LibC.malloc<UInt8>(count: cap)
|
|
394
|
+
var n: Int64 = 0
|
|
395
|
+
unsafe {
|
|
396
|
+
n = read(fd, buf, UInt64(cap))
|
|
397
|
+
}
|
|
398
|
+
var out = Array<UInt8>(0, repeat: 0)
|
|
399
|
+
if (n > 0) {
|
|
400
|
+
out = Array<UInt8>(n, repeat: 0)
|
|
401
|
+
unsafe {
|
|
402
|
+
for (i in 0..n) {
|
|
403
|
+
out[i] = buf.read(i)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
} else if (n < 0) {
|
|
407
|
+
serialOhosSetError("read failed: ${serialOhosErrnoText()}")
|
|
408
|
+
}
|
|
409
|
+
unsafe {
|
|
410
|
+
LibC.free(buf)
|
|
411
|
+
}
|
|
412
|
+
return out
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* 写:在 timeoutMs 预算内尽量写完,返回实际写入字节数(可能 < data.size;负码 = 失败)。
|
|
417
|
+
* EAGAIN 时 poll(POLLOUT) 等剩余预算,与桌面「预算内尽量写完」的口径一致。
|
|
418
|
+
*/
|
|
419
|
+
@When[env == "ohos"]
|
|
420
|
+
public func serialWrite(h: Int64, data: Array<UInt8>, timeoutMs: Int32): Int32 {
|
|
421
|
+
if (data.size == 0) {
|
|
422
|
+
return 0
|
|
423
|
+
}
|
|
424
|
+
if (usbSerialIsHandle(h)) {
|
|
425
|
+
return usbSerialWriteHandle(h, data, timeoutMs)
|
|
426
|
+
}
|
|
427
|
+
let fd = Int32(h - 1)
|
|
428
|
+
let n = data.size
|
|
429
|
+
let buf = LibC.malloc<UInt8>(count: Int64(n))
|
|
430
|
+
unsafe {
|
|
431
|
+
for (i in 0..n) {
|
|
432
|
+
buf.write(i, data[i])
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
let deadline = MonoTime.now() + Duration.millisecond * Int64(timeoutMs)
|
|
436
|
+
var total: Int64 = 0
|
|
437
|
+
while (total < n) {
|
|
438
|
+
var w: Int64 = 0
|
|
439
|
+
unsafe {
|
|
440
|
+
w = write(fd, buf + total, UInt64(n - total))
|
|
441
|
+
}
|
|
442
|
+
if (w > 0) {
|
|
443
|
+
total += w
|
|
444
|
+
continue
|
|
445
|
+
}
|
|
446
|
+
if (w < 0 && serialOhosErrno() != OHOS_EAGAIN) {
|
|
447
|
+
serialOhosSetError("write failed: ${serialOhosErrnoText()}")
|
|
448
|
+
unsafe {
|
|
449
|
+
LibC.free(buf)
|
|
450
|
+
}
|
|
451
|
+
return -1
|
|
452
|
+
}
|
|
453
|
+
let remainMs = (deadline - MonoTime.now()).toMilliseconds()
|
|
454
|
+
if (remainMs <= 0) {
|
|
455
|
+
break
|
|
456
|
+
}
|
|
457
|
+
let pr = serialOhosPollOne(fd, OHOS_POLLOUT, Int32(remainMs))
|
|
458
|
+
if (pr < 0) {
|
|
459
|
+
serialOhosSetError("poll failed: ${serialOhosErrnoText()}")
|
|
460
|
+
unsafe {
|
|
461
|
+
LibC.free(buf)
|
|
462
|
+
}
|
|
463
|
+
return -1
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
unsafe {
|
|
467
|
+
LibC.free(buf)
|
|
468
|
+
}
|
|
469
|
+
return Int32(total)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** 关闭:0 = 成功;fd 已关(EBADF)按桌面口径返回 -4(幂等拒绝重复关) */
|
|
473
|
+
@When[env == "ohos"]
|
|
474
|
+
public func serialClose(h: Int64): Int32 {
|
|
475
|
+
if (usbSerialIsHandle(h)) {
|
|
476
|
+
return usbSerialCloseHandle(h)
|
|
477
|
+
}
|
|
478
|
+
let fd = Int32(h - 1)
|
|
479
|
+
let rc = unsafe {
|
|
480
|
+
close(fd)
|
|
481
|
+
}
|
|
482
|
+
if (rc == 0) {
|
|
483
|
+
return 0
|
|
484
|
+
}
|
|
485
|
+
serialOhosSetError("close failed: ${serialOhosErrnoText()}")
|
|
486
|
+
return -4
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* 枚举本机串口(ohos 版):扫 /dev 顶层 + /dev/bus(鸿蒙特有的 USB 串口落点,sscom 同款),
|
|
491
|
+
* 按前缀过滤。拿不到目录就跳过;没有设备回空数组——空数组在页面侧语义就是「未发现端口」。
|
|
492
|
+
*/
|
|
493
|
+
@When[env == "ohos"]
|
|
494
|
+
public func serialPortList(): ArrayList<String> {
|
|
495
|
+
let prefixes = ["ttyUSB", "ttyACM", "ttyS", "ttyAMA", "ttyGS", "ttySC", "ttyPS", "ttyTHS", "tty.", "cu."]
|
|
496
|
+
let found = ArrayList<String>()
|
|
497
|
+
for (dir in ["/dev", "/dev/bus"]) {
|
|
498
|
+
if (!exists(dir)) {
|
|
499
|
+
continue
|
|
500
|
+
}
|
|
501
|
+
var entries: Array<FileInfo>
|
|
502
|
+
try {
|
|
503
|
+
entries = Directory.readFrom(dir)
|
|
504
|
+
} catch (e: Exception) {
|
|
505
|
+
continue
|
|
506
|
+
}
|
|
507
|
+
for (info in entries) {
|
|
508
|
+
if (info.isDirectory()) {
|
|
509
|
+
continue
|
|
510
|
+
}
|
|
511
|
+
let name = info.path.toString()
|
|
512
|
+
for (p in prefixes) {
|
|
513
|
+
if (name.contains(p)) {
|
|
514
|
+
found.add(name)
|
|
515
|
+
break
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// USB 串口只有 serialManager 认得(设备节点由系统代管,/dev 扫描看不见);
|
|
521
|
+
// 有权限时两边可能都出现同一个名字,去重后再排序
|
|
522
|
+
for (name in usbSerialListNames()) {
|
|
523
|
+
if (!found.contains(name)) {
|
|
524
|
+
found.add(name)
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
sort(found)
|
|
528
|
+
return found
|
|
529
|
+
}
|