我使用openpty,NSTask和NSTextView来写
a simple terminal.如何实现CtrlC和CtrlD?
我启动一个这样的shell:
int amaster = 0,aslave = 0; if (openpty(&amaster,&aslave,NULL,NULL) == -1) { NSLog(@"openpty Failed"); return; } masterHandle = [[NSFileHandle alloc] initWithFileDescriptor:amaster cloSEOnDealloc:YES]; NSFileHandle *slaveHandle = [[NSFileHandle alloc] initWithFileDescriptor:aslave cloSEOnDealloc:YES]; NSTask *task = [NSTask new]; task.launchPath = @"/bin/bash"; task.arguments = @[@"-i",@"-l"]; task.standardInput = slaveHandle; task.standardOutput = slaveHandle; task.standardError = errorOutputPipe = [NSPipe pipe]; [task launch];
然后我截取CtrlC并发送 – [interrupt]到NSTask,如下所示:
- (void)keyDown:(NSEvent *)theEvent { NSUInteger flags = theEvent.modifierFlags; unsigned short keyCode = theEvent.keyCode; if ((flags & NSControlKeyMask) && keyCode == 8) { // ctrl-c [task interrupt]; // ??? } else if ((flags & NSControlKeyMask) && keyCode == 2) { // ctrl-d // ??? } else { [super keyDown:theEvent]; } }
但是,中断似乎并没有杀死shell执行的任何程序.如果shell没有子进程,则中断将取消当前输入行.
我不知道如何实现CtrlD.
解决方法
我也在俄罗斯可可开发商Slack频道中询问了这个问题,并从
Dmitry Rodionov收到了答案.他用这个要点:
ctrlc-ptty-nstask.markdown用俄语回答,并给予我批准在这里发布英文版本.
他的实现是基于Pokey McPokerson建议的,但更直接:他使用Technical Q&A QA1123
Getting List of All Processes on Mac OS X中的GetBSDProcessList()获取子进程的列表,并向其中的每个发送SIGINT:
kinfo_proc *procs = NULL; size_t count; if (0 != GetBSDProcessList(&procs,&count)) { return; } BOOL hasChildren = NO; for (size_t i = 0; i < count; i++) { // If the process if a child of our bash process we send SIGINT to it if (procs[i].kp_eproc.e_ppid == task.processIdentifier) { hasChildren = YES; kill(procs[i].kp_proc.p_pid,SIGINT); } } free(procs);
如果一个进程没有子进程,他会直接向该进程发送SIGINT:
if (hasChildren == NO) { kill(task.processIdentifier,SIGINT); }
这种方法是完美的,但有两个可能的问题(我本人不在乎我正在写我自己的玩具终端):
>每次按Ctrl-C时,通过所有进程枚举是非常详尽的.也许有更好的方法来找到子进程.
>我和Dmitriy我们都不确定是否杀死所有的子进程是Ctrl-C在真实终端中工作的方式.
下面是Dmitriy的完整版本代码:
- (void)keyDown:(NSEvent *)theEvent { NSUInteger flags = theEvent.modifierFlags; unsigned short keyCode = theEvent.keyCode; if ((flags & NSControlKeyMask) && keyCode == 8) { [self sendCtrlC]; } else if ((flags & NSControlKeyMask) && keyCode == 2) { [masterHandle writeData:[NSData dataWithBytes: "\004" length:1]]; } else if ((flags & NSDeviceIndependentModifierFlagsMask) == 0 && keyCode == 126) { NSLog(@"up"); } else if ((flags & NSDeviceIndependentModifierFlagsMask) == 0 && keyCode == 125) { NSLog(@"down"); } else { [super keyDown:theEvent]; } } // #include <sys/sysctl.h> // typedef struct kinfo_proc kinfo_proc; - (void)sendCtrlC { [masterHandle writeData:[NSData dataWithBytes: "\003" length:1]]; kinfo_proc *procs = NULL; size_t count; if (0 != GetBSDProcessList(&procs,&count)) { return; } BOOL hasChildren = NO; for (size_t i = 0; i < count; i++) { if (procs[i].kp_eproc.e_ppid == task.processIdentifier) { hasChildren = YES; kill(procs[i].kp_proc.p_pid,SIGINT); } } free(procs); if (hasChildren == NO) { kill(task.processIdentifier,SIGINT); } } static int GetBSDProcessList(kinfo_proc **procList,size_t *procCount) // Returns a list of all BSD processes on the system. This routine // allocates the list and puts it in *procList and a count of the // number of entries in *procCount. You are responsible for freeing // this list (use "free" from System framework). // On success,the function returns 0. // On error,the function returns a BSD errno value. { int err; kinfo_proc * result; bool done; static const int name[] = { CTL_KERN,KERN_PROC,KERN_PROC_ALL,0 }; // Declaring name as const requires us to cast it when passing it to // sysctl because the prototype doesn't include the const modifier. size_t length; assert( procList != NULL); assert(*procList == NULL); assert(procCount != NULL); *procCount = 0; // We start by calling sysctl with result == NULL and length == 0. // That will succeed,and set length to the appropriate length. // We then allocate a buffer of that size and call sysctl again // with that buffer. If that succeeds,we're done. If that fails // with ENOMEM,we have to throw away our buffer and loop. Note // that the loop causes use to call sysctl with NULL again; this // is necessary because the ENOMEM failure case sets length to // the amount of data returned,not the amount of data that // could have been returned. result = NULL; done = false; do { assert(result == NULL); // Call sysctl with a NULL buffer. length = 0; err = sysctl( (int *) name,(sizeof(name) / sizeof(*name)) - 1,&length,0); if (err == -1) { err = errno; } // Allocate an appropriately sized buffer based on the results // from the prevIoUs call. if (err == 0) { result = malloc(length); if (result == NULL) { err = ENOMEM; } } // Call sysctl again with the new buffer. If we get an ENOMEM // error,toss away our buffer and start again. if (err == 0) { err = sysctl( (int *) name,result,0); if (err == -1) { err = errno; } if (err == 0) { done = true; } else if (err == ENOMEM) { assert(result != NULL); free(result); result = NULL; err = 0; } } } while (err == 0 && ! done); // Clean up and establish post conditions. if (err != 0 && result != NULL) { free(result); result = NULL; } *procList = result; if (err == 0) { *procCount = length / sizeof(kinfo_proc); } assert( (err == 0) == (*procList != NULL) ); return err; }