Skip to content

Instantly share code, notes, and snippets.

@rodionovd
Last active October 23, 2015 14:16
Show Gist options
  • Save rodionovd/958263b5d469a7525492 to your computer and use it in GitHub Desktop.
Save rodionovd/958263b5d469a7525492 to your computer and use it in GitHub Desktop.

Проблема в том, что ^C (aka ETX) это просто символ, который никак сам по себе не влияет на выполнение процесса. Терминал просто перехватывает его нажатие и посылает SIGINT (interruption/прерывание) текущему процессу.

Дело за малым: получить PID текущего процесса. Увы, task.processIdentifier вернёт лишь идентификатор шелла (/bin/bash), а не какого-нибудь ping’а, работающего в данный момент. Соответственно, любые попытки послать ETX шеллу никак не отразятся на ping’е.

К сожалению, прямого пути получения списка дочерних PID’ов в Си нет, поэтому прийдётся просматривать все процессы руками. Сделать это можно при помощи функции GetBSDProcessList(), исходники которой можно найти здесь: https://developer.apple.com/legacy/library/qa/qa2001/qa1123.html (либо ниже по коду):

    kinfo_proc *procs = NULL;
    size_t count;
    if (0 != GetBSDProcessList(&procs, &count)) {
        return;
    }
    BOOL hasChildren = NO;
    for (size_t i = 0; i < count; i++) {
        // Ищем процессы, предок которого — запущенный нами bash
        if (procs[i].kp_eproc.e_ppid == task.processIdentifier) {
            hasChildren = YES;
            // и отправляем им SIGIN
            kill(procs[i].kp_proc.p_pid, SIGINT);
        }
    }
    free(procs);

ВНИМАНИЕ: я не специалист, поэтому не знаю точно, но что-то мне подсказывает, что убивать нужно не всех потомков сразу…

Разумеется, бывает так, что у шелла нет никаких потомков, а Ctrl-C пользователь всё же жмёт. В этом случае пошлём SIGINT самому шеллу:

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

Это далеко не самый оптимальный способ: при каждом Ctrl-C приходится вручную перебирать все запущенные процессы. В идеале при запуске дочерних процессов можно было бы просто запоминать их PID’ы и затем лишь проходить по этому небольшому списку.

Итого:

CLTTerminal.m из https://github.com/alltom/coolterm

- (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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment