2014-01-21 13 views
10

Ich schreibe a simple terminal mit openpty, NSTask und NSTextView. Wie lautet CtrlC und CtrlD soll implementiert werden?Wie implementiert man Ctrl-C und Ctrl-D mit openpty?

ich eine Schale wie folgt beginnen:

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]; 

Dann abfangen ich CtrlC und -[interrupt] zum NSTask wie folgt senden:

- (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]; 
    } 
} 

Allerdings ist die Unterbrechung nicht scheinen zu töten, was auch immer Programm von der Shell ausgeführt wird. Wenn die Shell keinen Unterprozess hat, bricht der Interrupt die aktuelle Eingabezeile ab.

Ich habe keine Ahnung, wie zu implementieren CtrlD.

Antwort

4

Ich trat durch st (das suckless Terminal, dessen Code ist eigentlich klein und einfach genug zu verstehen) in gdb auf Linux zu finden, dass, wenn Sie drücken Ctrl-C und Ctrl-D, schreibt es \003 und zu dem jeweiligen Prozess. Ich habe das auf OS X in meinem Projekt versucht und es hat genauso gut funktioniert.

So im Rahmen meines obigen Code, die Lösung für jede der Hotkeys Handhabung ist dies:

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

Leider funktioniert Ctrl-C nicht für mich am 10.11, wenn ich dein Projekt ausführen: [coolterm] (https://github.com/alltom/coolterm). Hast du irgendwelche Ideen, warum es passieren kann? –

+0

Ctrl-C startet weder eine neue Zeile noch den Kindprozess (wie Ping). Zwei zusätzliche Details: Strg-D funktioniert wie erwartet, Strg-C druckt nicht^C. Sieht so aus, als ob ein zusätzlicher Code fehlt, damit er am 10.11 funktioniert. –

1

Die NSTask bezieht sich auf die tatsächliche bash, nicht die Befehle, die es ausführt. Wenn Sie also terminate aufrufen, sendet es dieses Signal an den Bash-Prozess. Sie können dies überprüfen, indem Sie [task processIdentifier] drucken und sich die PID im Aktivitätsmanager ansehen. Solange Sie keine Möglichkeit finden, die PID neu erstellter Prozesse zu verfolgen, werden Sie Schwierigkeiten haben, sie zu töten.

Siehe this oder this Antwort für mögliche Wege zur Verfolgung der PIDs. Ich habe mir Ihr Projekt angeschaut und Sie könnten etwas Ähnliches implementieren, indem Sie Ihre didChangeText Methode ändern. Zum Beispiel:

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

und dann von der childpid Datei lesen, wenn Sie die Kinder töten wollen. Die Extras erscheinen jedoch im Terminal, was nicht toll ist.

Eine bessere Option könnte sein, neue NSTasks für jeden eingehenden Befehl zu erstellen (d. H. Die Benutzereingabe nicht direkt an bash zu leiten) und ihre Ausgaben an denselben Handler zu senden. Dann können Sie terminate direkt auf sie anrufen.

Wenn Sie Strg-C zum Laufen zu bringen, können Sie wie so ctrl-d implementieren:

kill([task processIdentifier], SIGQUIT); 

Source

+1

Dank für alle Tipps und Hinweise (insbesondere die letzte), aber dies scheint nicht, wie der richtige Weg zu gehen . Ich bin sicher, dass andere Terminalanwendungen nichts dergleichen tun, weil sie mit jeder Shell arbeiten, nicht nur mit bash. – alltom

+0

(Ich bin jetzt auf der Suche nach einem Weg, um die Kinder eines Prozesses zu bekommen.) – alltom

4

I habe auch über diese Frage in Russian Cocoa Developers Slack-Kanal gefragt und erhielt die Antwort von Dmitry Rodionov. Er antwortete auf Russisch mit diesem Kern: ctrlc-ptty-nstask.markdown und gab mir die Genehmigung, die englische Version hier zu veröffentlichen.

Seine Implementierung basiert auf, was Pokey McPokerson vorgeschlagen, ist aber ganz einfach: er GetBSDProcessList() von Technical Q&A QA1123 Getting List of All Processes on Mac OS X verwendet die Liste der untergeordneten Prozesse zu erhalten und SIGINT zu jedem von ihnen zu senden:

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); 

Im Fall, wenn ein Prozess hat Prozesse kein Kind, das er direkt SIGINT zu diesem Prozess sendet:

if (hasChildren == NO) { 
    kill(task.processIdentifier, SIGINT); 
} 

Dieser Ansatz perfekt funktioniert jedoch gibt es zwei mögliche Bedenken (die ich über ich bin Schrift zur Zeit nicht persönlich kümmern ing mein eigenes Spielzeug Terminal):

  1. Es ist erschöpfend, alle Prozesse durch jedes Drücken von Ctrl-C aufzuzählen. Vielleicht gibt es eine bessere Möglichkeit, Kinderprozesse zu finden.
  2. Ich und Dmitriy sind uns beide nicht sicher, ob das Töten ALLER untergeordneten Prozesse die Art ist, wie Ctrl-C in realen Terminals funktioniert.

Unterhalb der Vollversion von Dmitriy Code folgt:

- (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; 
} 
Verwandte Themen