如何使用Swift在控制台应用程序上进行非阻塞键盘输入?

时间:2019-04-29 15:36:28

标签: swift console

我想使用Swift 5制作一个简单的控制台游戏。我需要阅读键盘输入而又不阻止游戏动画(使用表情符号)。在没有键盘输入的情况下,游戏继续进行,但是如果有键盘输入,游戏将做出相应的反应。

我已经看到了如何使用其他语言(例如C和Python)执行此操作的示例。我知道Swift有提供许多POSIX功能的Darwin模块。但是,这些C代码似乎与Swift 5不兼容。

例如,如何将下面的C代码转换为Swift?达尔文模块中没有FD_ZERO也没有FD_SET

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>

struct termios orig_termios;

void reset_terminal_mode()
{
    tcsetattr(0, TCSANOW, &orig_termios);
}

void set_conio_terminal_mode()
{
    struct termios new_termios;

    /* take two copies - one for now, one for later */
    tcgetattr(0, &orig_termios);
    memcpy(&new_termios, &orig_termios, sizeof(new_termios));

    /* register cleanup handler, and set the new terminal mode */
    atexit(reset_terminal_mode);
    cfmakeraw(&new_termios);
    tcsetattr(0, TCSANOW, &new_termios);
}

int kbhit()
{
    struct timeval tv = { 0L, 0L };
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(0, &fds);
    return select(1, &fds, NULL, NULL, &tv);
}

int getch()
{
    int r;
    unsigned char c;
    if ((r = read(0, &c, sizeof(c))) < 0) {
        return r;
    } else {
        return c;
    }
}

int main(int argc, char *argv[])
{
    int key;

    printf("press a key: ");
    fflush(stdout);

    set_conio_terminal_mode();

    while (1) {
        if (kbhit()) {
            key = getch();

            if (key == 13) {
                printf("\n\r");
                break;
            } else if (key >= 20) {
                printf("%c, ", key);
                fflush(stdout);
            }
        }
        else {
            /* do some work */
            printf(".");
            usleep(10);
            printf(".");
            usleep(10);
            printf(".");
            usleep(10);
            printf("\e[3D");
            usleep(10);
        }
    }

    reset_terminal_mode();
}

我希望Swift的代码在Swift中能做同样的事情。

1 个答案:

答案 0 :(得分:0)

termios函数几乎一对一转换为Swift:

#if os(Linux)
import Glibc
#else
import Darwin
#endif

var orig_termios = termios()

func reset_terminal_mode() {
    tcsetattr(0, TCSANOW, &orig_termios)
}

func set_conio_terminal_mode() {
    tcgetattr(0, &orig_termios)
    var new_termios = orig_termios
    atexit(reset_terminal_mode)
    cfmakeraw(&new_termios)
    tcsetattr(0, TCSANOW, &new_termios)
}

set_conio_terminal_mode()

select()的问题在于,FD_ZERO等是“不平凡的”宏,没有导入到Swift中。但是您可以改用poll()

func kbhit() -> Bool {
    var fds = [ pollfd(fd: STDIN_FILENO, events: Int16(POLLIN), revents: 0) ]
    let res = poll(&fds, 1, 0)
    return res > 0
}

一种替代方法是使用Dispatch框架。这是一个简单的示例,可能会帮助您入门。 dispatch source用于异步等待可用输入,然后将其附加到数组,并在getch()函数中从该数组中检索该输入。串行队列用于同步对阵列的访问。

import Dispatch

let stdinQueue = DispatchQueue(label: "my.serial.queue")
var inputCharacters: [CChar] = []

let stdinSource = DispatchSource.makeReadSource(fileDescriptor: STDIN_FILENO, queue: stdinQueue)
stdinSource.setEventHandler(handler: {
    var c = CChar()
    if read(STDIN_FILENO, &c, 1) == 1 {
        inputCharacters.append(c)
    }
})
stdinSource.resume()

// Return next input character, or `nil` if there is none.
func getch() -> CChar? {
    return stdinQueue.sync {
        inputCharacters.isEmpty ? nil : inputCharacters.remove(at: 0)
    }
}