2014-01-21 80 views
10

我在寫a simple terminal使用openpty,NSTask和NSTextView。如何CtrlCCtrlD應該實施?如何用openpty實現Ctrl-C和Ctrl-D?

我開始這樣的外殼:

int amaster = 0, aslave = 0; 
if (openpty(&amaster, &aslave, NULL, 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]; 

然後我攔截按CtrlÇ和發送-[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

回答

4

我通過st加強(在suckless終端,它的代碼實際上是小和簡單到足以理解)在Linux上的gdb中發現,當你按Ctrl-CCtrl-D時,它分別將\003\004寫入進程。我在我的項目中嘗試了這一點,它的工作原理也一樣。

在上述我的代碼,用於處理每個熱鍵的溶液上下文

所以是這樣的:

  • Ctrl-C鍵:[masterHandle writeData:[NSData dataWithBytes:"\003" length:1]];
  • 按Ctrl-d:[masterHandle writeData:[NSData dataWithBytes:"\004" length:1]];
+0

不幸的是,出於某種原因,在我運行項目時,Ctrl-C對我無效([coolterm](https:/ /github.com/alltom/coolterm)。你有什麼想法,爲什麼會發生? –

+0

Ctrl-C既不啓動新行也不殺死子進程(如ping)。另外兩個詳細信息:Ctrl-D按預期工作,Ctrl-C不打印^ C。看起來像一些額外的代碼丟失,使其在10.11工作。 –

1

NSTask是指實際的bash,而不是它運行的命令。所以當你打電話給terminate時,它會發送信號給bash進程。您可以通過打印[task processIdentifier]來查看,並查看活動管理器中的PID。除非你找到一種方法來跟蹤任何新創建的進程的PID,否則你將很難殺死它們。

請參閱thisthis回答可能的跟蹤PID的方法。我看了一下你的項目,你可以通過改變你的方法來實現類似的東西。例如:

// [self writeCommand:input]; Take this out 
[self writeCommand:[NSString stringWithFormat:@"%@ & echo $! > /tmp/childpid\n", [input substringToIndex:[input length] - 2]]]; 

,然後從childpid文件,只要你想殺死孩子讀。臨時演員將出現在終端,但不是很好。

更好的選擇可能是爲每個進入的命令創建新的NSTasks(即不要將用戶輸入直接輸入到bash中),並將它們的輸出發送到同一個處理程序。然後您可以直接撥打terminate

當你CTRL-C的工作,可以實現按ctrl-d像這樣:

kill([task processIdentifier], SIGQUIT); 

Source

+1

感謝所有的技巧和引用(特別是最後一個),但是這似乎並不像正確的方式去。我相信其他終端應用程序不會這樣做,因爲它們可以與任何shell一起使用,而不僅僅是bash。 – alltom

+0

(我正在尋找一種方法來獲取進程的子進程。) – alltom

4

我也在俄羅斯Cocoa Developers Slack頻道詢問了這個問題,並從Dmitry Rodionov收到了答案。他用俄語回答了這個要求:ctrlc-ptty-nstask.markdown並且給了我批准在這裏發佈英文版本。

他的執行依據是什麼獄McPokerson建議,但更直接:他使用從Technical Q&A QA1123 Getting List of All Processes on Mac OS XGetBSDProcessList()獲得子進程的列表,併發送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); 
} 

這種方法完美的作品,然而還有兩個可能的關注(這是我個人不此刻關心我令狀我自己的玩具終端):

  1. 每次按下Ctrl-C時,通過所有進程枚舉是詳盡無遺的。也許有更好的方法找到子進程。
  2. 我和Dmitriy我們都不確定是否殺死所有子進程是Ctrl-C在真實終端中的工作方式。

下面的德米特里代碼的完整版如下:

- (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, 
        NULL, &length, 
        NULL, 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, (sizeof(name)/sizeof(*name)) - 1, 
         result, &length, 
         NULL, 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; 
}