diff --git a/.pql/pql-plan.json b/.pql/pql-plan.json index cc697c8a..b2205d20 100644 --- a/.pql/pql-plan.json +++ b/.pql/pql-plan.json @@ -1,5 +1,5 @@ { - "exported_at": "2026-05-03T19:51:59Z", + "exported_at": "2026-05-03T19:52:13Z", "decisions": [ { "id": "D-1", diff --git a/lib/main.dart b/lib/main.dart index 6cb65e13..1132d4b5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -78,22 +78,24 @@ Future main() async { preloadNamespaces: _tier0Namespaces, autoStartDaemonClient: false, toolchain: toolchain, - daemonClientFactory: kIsWeb ? null : (log, events) { - final dispatcher = DaemonDispatcher(); - final eventSink = _BusEventSink(events); - final filesService = FilesService.atCwd(events: eventSink); - final workRoot = filesService.root; - final paneRegistry = PaneRegistry(events: eventSink); - registerPaneCommands(dispatcher, paneRegistry); - registerFilesCommands(dispatcher, filesService); - final editorRegistry = EditorRegistry(events: eventSink, workspaceRoot: workRoot); - registerEditorCommands(dispatcher, editorRegistry); - final gitClient = GitClient(toolchain: toolchain, workDir: workRoot); - registerGitCommands(dispatcher, gitClient, eventSink); - final pql = PqlClient(workDir: workRoot, toolchain: toolchain); - registerPqlCommands(dispatcher, pql); - return InProcessClient(log: log, events: events, dispatcher: dispatcher); - }, + daemonClientFactory: kIsWeb + ? null + : (log, events) { + final dispatcher = DaemonDispatcher(); + final eventSink = _BusEventSink(events); + final filesService = FilesService.atCwd(events: eventSink); + final workRoot = filesService.root; + final paneRegistry = PaneRegistry(events: eventSink); + registerPaneCommands(dispatcher, paneRegistry); + registerFilesCommands(dispatcher, filesService); + final editorRegistry = EditorRegistry(events: eventSink, workspaceRoot: workRoot); + registerEditorCommands(dispatcher, editorRegistry); + final gitClient = GitClient(toolchain: toolchain, workDir: workRoot); + registerGitCommands(dispatcher, gitClient, eventSink); + final pql = PqlClient(workDir: workRoot, toolchain: toolchain); + registerPqlCommands(dispatcher, pql); + return InProcessClient(log: log, events: events, dispatcher: dispatcher); + }, ); // Register every built-in. Tier 0 activates only the four that do @@ -169,8 +171,13 @@ class _BusEventSink implements DaemonEventSink { Future _resolveAppDir() async { if (kIsWeb) return Directory('/clide-web-no-disk'); final home = Platform.environment['HOME'] ?? '/tmp'; - final xdg = Platform.environment['XDG_CONFIG_HOME'] ?? '$home/.config'; - final dir = Directory('$xdg/clide'); + final String base; + if (Platform.isMacOS) { + base = '$home/Library/Application Support'; + } else { + base = Platform.environment['XDG_CONFIG_HOME'] ?? '$home/.config'; + } + final dir = Directory('$base/clide'); await dir.create(recursive: true); return dir; } diff --git a/lib/src/panes/registry.dart b/lib/src/panes/registry.dart index 7d1ea9c0..e031414e 100644 --- a/lib/src/panes/registry.dart +++ b/lib/src/panes/registry.dart @@ -56,7 +56,6 @@ class PaneRegistry { 'COLORTERM': 'truecolor', 'LANG': 'en_US.UTF-8', 'LC_ALL': 'en_US.UTF-8', - 'TERMINFO': '/usr/share/terminfo', if (env != null) ...env, }; diff --git a/lib/src/pty/ffi/libc.dart b/lib/src/pty/ffi/libc.dart index 678edcb9..0063a868 100644 --- a/lib/src/pty/ffi/libc.dart +++ b/lib/src/pty/ffi/libc.dart @@ -16,7 +16,7 @@ import 'dart:io' show Platform; import 'package:ffi/ffi.dart' as pkg_ffi; // --------------------------------------------------------------------------- -// Constants (POSIX / Linux) +// Constants (POSIX — platform-dispatched where Linux/macOS diverge) // --------------------------------------------------------------------------- const int afUnix = 1; @@ -25,11 +25,11 @@ const int sockStream = 1; final int solSocket = Platform.isMacOS ? 0xffff : 1; final int scmRights = Platform.isMacOS ? 0x01 : 1; -const int fIoNonblock = 0x800; // O_NONBLOCK — 04000 octal +final int oNonblock = Platform.isMacOS ? 0x0004 : 0x0800; const int fGetFl = 3; const int fSetFl = 4; -const int tiocswinsz = 0x5414; // Linux x86_64; macOS differs +final int tiocswinsz = Platform.isMacOS ? 0x80087467 : 0x5414; // --------------------------------------------------------------------------- // Typedefs @@ -59,6 +59,17 @@ typedef _RecvmsgD = int Function( int flags, ); +typedef _RecvmsgDarwinC = ffi.IntPtr Function( + ffi.Int32 sockfd, + ffi.Pointer msg, + ffi.Int32 flags, +); +typedef _RecvmsgDarwinD = int Function( + int sockfd, + ffi.Pointer msg, + int flags, +); + typedef _ReadC = ffi.IntPtr Function( ffi.Int32 fd, ffi.Pointer buf, @@ -116,8 +127,8 @@ final class Iovec extends ffi.Struct { external int iov_len; } -/// POSIX `struct msghdr`. Field layout matches Linux/glibc; macOS is -/// byte-compatible here. +/// Linux `struct msghdr`. msg_iovlen/msg_controllen are size_t (8 bytes +/// on 64-bit). macOS uses int/socklen_t (4 bytes) — see MsghdrDarwin. final class Msghdr extends ffi.Struct { external ffi.Pointer msg_name; @ffi.Uint32() @@ -132,6 +143,22 @@ final class Msghdr extends ffi.Struct { external int msg_flags; } +/// macOS `struct msghdr`. msg_iovlen is int (4 bytes), msg_controllen +/// is socklen_t (4 bytes) — smaller than Linux's size_t fields. +final class MsghdrDarwin extends ffi.Struct { + external ffi.Pointer msg_name; + @ffi.Uint32() + external int msg_namelen; + external ffi.Pointer msg_iov; + @ffi.Int32() + external int msg_iovlen; + external ffi.Pointer msg_control; + @ffi.Uint32() + external int msg_controllen; + @ffi.Int32() + external int msg_flags; +} + /// POSIX `struct cmsghdr` prefix. We treat the rest of the control /// buffer as a raw byte region and compute offsets by hand. // On Linux, cmsg_len is size_t (8 bytes on 64-bit). @@ -183,11 +210,11 @@ ffi.DynamicLibrary _openLibc() { return ffi.DynamicLibrary.process(); } -final _SocketpairD socketpair = - _libc.lookupFunction<_SocketpairC, _SocketpairD>('socketpair'); +final _SocketpairD socketpair = _libc.lookupFunction<_SocketpairC, _SocketpairD>('socketpair'); -final _RecvmsgD recvmsg = - _libc.lookupFunction<_RecvmsgC, _RecvmsgD>('recvmsg'); +final _RecvmsgD recvmsgLinux = _libc.lookupFunction<_RecvmsgC, _RecvmsgD>('recvmsg'); + +final _RecvmsgDarwinD recvmsgDarwin = _libc.lookupFunction<_RecvmsgDarwinC, _RecvmsgDarwinD>('recvmsg'); final _ReadD read = _libc.lookupFunction<_ReadC, _ReadD>('read'); @@ -195,11 +222,9 @@ final _WriteD write = _libc.lookupFunction<_WriteC, _WriteD>('write'); final _CloseD close = _libc.lookupFunction<_CloseC, _CloseD>('close'); -final _IoctlPtrD ioctlWinsize = - _libc.lookupFunction<_IoctlPtrC, _IoctlPtrD>('ioctl'); +final _IoctlPtrD ioctlWinsize = _libc.lookupFunction<_IoctlPtrC, _IoctlPtrD>('ioctl'); -final _FcntlIntD fcntlInt = - _libc.lookupFunction<_FcntlIntC, _FcntlIntD>('fcntl'); +final _FcntlIntD fcntlInt = _libc.lookupFunction<_FcntlIntC, _FcntlIntD>('fcntl'); /// Resolve `errno` through the platform-appropriate thread-local /// accessor. glibc exposes `__errno_location`, musl the same, macOS @@ -238,8 +263,8 @@ T withBuffer(int bytes, T Function(ffi.Pointer) action) { bool setNonBlocking(int fd) { final flags = fcntlInt(fd, fGetFl, 0); if (flags < 0) return false; - if ((flags & fIoNonblock) != 0) return false; - fcntlInt(fd, fSetFl, flags | fIoNonblock); + if ((flags & oNonblock) != 0) return false; + fcntlInt(fd, fSetFl, flags | oNonblock); return true; } diff --git a/lib/src/pty/ffi/scm_rights.dart b/lib/src/pty/ffi/scm_rights.dart index 98763353..82bc981e 100644 --- a/lib/src/pty/ffi/scm_rights.dart +++ b/lib/src/pty/ffi/scm_rights.dart @@ -29,30 +29,61 @@ int recvFd(int socketFd) { final payload = pkg_ffi.calloc(payloadLen); final control = pkg_ffi.calloc(controlLen); final iov = pkg_ffi.calloc(); - final msg = pkg_ffi.calloc(); try { iov.ref.iov_base = payload; iov.ref.iov_len = payloadLen; - msg.ref.msg_name = ffi.nullptr; - msg.ref.msg_namelen = 0; - msg.ref.msg_iov = iov; - msg.ref.msg_iovlen = 1; - msg.ref.msg_control = control.cast(); - msg.ref.msg_controllen = controlLen; - msg.ref.msg_flags = 0; - int received; - while (true) { - received = libc.recvmsg(socketFd, msg, 0); - if (received >= 0) break; - final err = libc.errno; - if (err == 4 /* EINTR */) continue; - throw PtyException('recvmsg', 'recvmsg failed', errno: err); + int msgControllen; + + if (Platform.isMacOS) { + final msg = pkg_ffi.calloc(); + try { + msg.ref.msg_name = ffi.nullptr; + msg.ref.msg_namelen = 0; + msg.ref.msg_iov = iov; + msg.ref.msg_iovlen = 1; + msg.ref.msg_control = control.cast(); + msg.ref.msg_controllen = controlLen; + msg.ref.msg_flags = 0; + + while (true) { + received = libc.recvmsgDarwin(socketFd, msg, 0); + if (received >= 0) break; + final err = libc.errno; + if (err == 4 /* EINTR */) continue; + throw PtyException('recvmsg', 'recvmsg failed', errno: err); + } + msgControllen = msg.ref.msg_controllen; + } finally { + pkg_ffi.calloc.free(msg); + } + } else { + final msg = pkg_ffi.calloc(); + try { + msg.ref.msg_name = ffi.nullptr; + msg.ref.msg_namelen = 0; + msg.ref.msg_iov = iov; + msg.ref.msg_iovlen = 1; + msg.ref.msg_control = control.cast(); + msg.ref.msg_controllen = controlLen; + msg.ref.msg_flags = 0; + + while (true) { + received = libc.recvmsgLinux(socketFd, msg, 0); + if (received >= 0) break; + final err = libc.errno; + if (err == 4 /* EINTR */) continue; + throw PtyException('recvmsg', 'recvmsg failed', errno: err); + } + msgControllen = msg.ref.msg_controllen; + } finally { + pkg_ffi.calloc.free(msg); + } } - if (received == 0 || msg.ref.msg_controllen < 16) { + if (received == 0 || msgControllen < 16) { throw const PtyException( 'recvmsg', 'peer closed without sending ancillary data', @@ -84,7 +115,6 @@ int recvFd(int socketFd) { final fdPtr = (control + dataOffset).cast(); return fdPtr.value; } finally { - pkg_ffi.calloc.free(msg); pkg_ffi.calloc.free(iov); pkg_ffi.calloc.free(control); pkg_ffi.calloc.free(payload); diff --git a/lib/src/pty/native_pty.dart b/lib/src/pty/native_pty.dart index d113ebb9..59921ed2 100644 --- a/lib/src/pty/native_pty.dart +++ b/lib/src/pty/native_pty.dart @@ -49,42 +49,28 @@ ffi.DynamicLibrary _openLib() { return ffi.DynamicLibrary.open('libutil.so.1'); } -final _forkpty = _dl.lookupFunction< - ffi.Int32 Function(ffi.Pointer, ffi.Pointer, - ffi.Pointer, ffi.Pointer<_Winsize>), - int Function(ffi.Pointer, ffi.Pointer, - ffi.Pointer, ffi.Pointer<_Winsize>)>('forkpty'); +final _forkpty = _dl.lookupFunction, ffi.Pointer, ffi.Pointer, ffi.Pointer<_Winsize>), + int Function(ffi.Pointer, ffi.Pointer, ffi.Pointer, ffi.Pointer<_Winsize>)>('forkpty'); -final _execve = _dl.lookupFunction< - ffi.Int32 Function(ffi.Pointer, - ffi.Pointer>, ffi.Pointer>), - int Function(ffi.Pointer, ffi.Pointer>, - ffi.Pointer>)>('execve'); +final _execve = _dl.lookupFunction, ffi.Pointer>, ffi.Pointer>), + int Function(ffi.Pointer, ffi.Pointer>, ffi.Pointer>)>('execve'); -final _nativeWrite = ffi.DynamicLibrary.process().lookupFunction< - ffi.IntPtr Function(ffi.Int32, ffi.Pointer, ffi.IntPtr), - int Function(int, ffi.Pointer, int)>('write'); +final _nativeWrite = ffi.DynamicLibrary.process() + .lookupFunction, ffi.IntPtr), int Function(int, ffi.Pointer, int)>('write'); -final _nativeClose = ffi.DynamicLibrary.process() - .lookupFunction('close'); +final _nativeClose = ffi.DynamicLibrary.process().lookupFunction('close'); -final _ioctl = ffi.DynamicLibrary.process().lookupFunction< - ffi.Int32 Function(ffi.Int32, ffi.UnsignedLong, ffi.Pointer<_Winsize>), - int Function(int, int, ffi.Pointer<_Winsize>)>('ioctl'); +final _ioctl = ffi.DynamicLibrary.process() + .lookupFunction), int Function(int, int, ffi.Pointer<_Winsize>)>('ioctl'); -final _nativeKill = ffi.DynamicLibrary.process().lookupFunction< - ffi.Int32 Function(ffi.Int32, ffi.Int32), int Function(int, int)>('kill'); +final _nativeKill = ffi.DynamicLibrary.process().lookupFunction('kill'); -final _waitpid = ffi.DynamicLibrary.process().lookupFunction< - ffi.Int32 Function(ffi.Int32, ffi.Pointer, ffi.Int32), - int Function(int, ffi.Pointer, int)>('waitpid'); +final _waitpid = ffi.DynamicLibrary.process() + .lookupFunction, ffi.Int32), int Function(int, ffi.Pointer, int)>('waitpid'); -final _chdir = ffi.DynamicLibrary.process().lookupFunction< - ffi.Int32 Function(ffi.Pointer), - int Function(ffi.Pointer)>('chdir'); +final _chdir = ffi.DynamicLibrary.process().lookupFunction), int Function(ffi.Pointer)>('chdir'); -final _exit_ = ffi.DynamicLibrary.process().lookupFunction< - ffi.Void Function(ffi.Int32), void Function(int)>('_exit'); +final _exit_ = ffi.DynamicLibrary.process().lookupFunction('_exit'); final int _kTiocsWinsz = Platform.isMacOS ? 0x80087467 : 0x5414; const _kSighup = 1; @@ -153,15 +139,11 @@ class NativePty { final envList = environment.entries.toList(); final envpN = malloc>(envList.length + 1); for (var i = 0; i < envList.length; i++) { - envpN[i] = '${envList[i].key}=${envList[i].value}' - .toNativeUtf8(allocator: malloc) - .cast(); + envpN[i] = '${envList[i].key}=${envList[i].value}'.toNativeUtf8(allocator: malloc).cast(); } envpN[envList.length] = ffi.nullptr; - final wdN = (workingDirectory ?? '/') - .toNativeUtf8(allocator: malloc) - .cast(); + final wdN = (workingDirectory ?? '/').toNativeUtf8(allocator: malloc).cast(); final fdOut = calloc(); final ws = calloc<_Winsize>() ..ref.wsRow = rows @@ -171,8 +153,7 @@ class NativePty { final pid = _forkpty(fdOut, ffi.nullptr, ffi.nullptr, ws); if (pid == -1) { - _freeAll(shellN, argvN, allArgs.length, envpN, envList.length, wdN, - fdOut, ws); + _freeAll(shellN, argvN, allArgs.length, envpN, envList.length, wdN, fdOut, ws); throw StateError('forkpty() failed'); } @@ -185,8 +166,7 @@ class NativePty { // PARENT final fd = fdOut.value; - _freeAll(shellN, argvN, allArgs.length, envpN, envList.length, wdN, - fdOut, ws); + _freeAll(shellN, argvN, allArgs.length, envpN, envList.length, wdN, fdOut, ws); final pty = NativePty._(fd, pid); pty._spawnReader(); @@ -195,9 +175,13 @@ class NativePty { static void _freeAll( ffi.Pointer shell, - ffi.Pointer> argv, int argc, - ffi.Pointer> envp, int envc, - ffi.Pointer wd, ffi.Pointer fdOut, ffi.Pointer ws, + ffi.Pointer> argv, + int argc, + ffi.Pointer> envp, + int envc, + ffi.Pointer wd, + ffi.Pointer fdOut, + ffi.Pointer ws, ) { malloc.free(shell); for (var i = 0; i < argc; i++) malloc.free(argv[i]); @@ -229,12 +213,8 @@ class NativePty { static void _readLoop((SendPort, int) msg) { final (port, fd) = msg; final dl = ffi.DynamicLibrary.process(); - final rd = dl.lookupFunction< - ffi.IntPtr Function(ffi.Int32, ffi.Pointer, ffi.IntPtr), - int Function(int, ffi.Pointer, int)>('read'); - final poll = dl.lookupFunction< - ffi.Int32 Function(ffi.Pointer<_Pollfd>, ffi.Uint32, ffi.Int32), - int Function(ffi.Pointer<_Pollfd>, int, int)>('poll'); + final rd = dl.lookupFunction, ffi.IntPtr), int Function(int, ffi.Pointer, int)>('read'); + final poll = dl.lookupFunction, ffi.Uint32, ffi.Int32), int Function(ffi.Pointer<_Pollfd>, int, int)>('poll'); final buf = malloc(65536); final pfd = calloc<_Pollfd>(); @@ -276,9 +256,8 @@ class NativePty { final ws = calloc<_Winsize>() ..ref.wsRow = rows ..ref.wsCol = cols; - final rc = _ioctl(_fd, _kTiocsWinsz, ws); + _ioctl(_fd, _kTiocsWinsz, ws); calloc.free(ws); - print('[pty-resize] fd=$_fd cols=$cols rows=$rows ioctl=$rc pid=$pid'); // Explicitly signal the child to re-query its terminal size. _nativeKill(pid, 28); // SIGWINCH = 28 on macOS/Linux }