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

标签 swift console

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

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

比如下面的C代码如何转换成Swift? Darwin 模块中没有FD_ZEROFD_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();
}

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

最佳答案

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

关于swift - 如何使用 Swift 在控制台应用程序上进行非阻塞键盘输入?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55906580/

相关文章:

ios - 从 ViewController 向 View 传递数据时如何设置委托(delegate)?

ios - 使用布局约束动画 UIView 交换

c - Eclipse 在 C 的控制台上打印符号而不是 'chars'

javascript - 无法使用 JavaScript 控制台调用按钮类

ios - 无法在 Swift 中的 UIButton 上显示图像

swift - 快速在 collectionView 上像 facebook 一样分页

ios - Swift 2 到 Swift 3.0 运动管理器

c# - 从控制台删除特殊字符 - c#

c# - 在 Visual Studio 中时 AllocConsole 不打印

.net - 阻止 .net 控制台应用程序被关闭