EMACS-DOCUMENT

=============>随便,谢谢

Emacs, Dynamic Modules, and Joysticks « null program

Two months ago Emacs 25 was released and introduced a new dynamic module feature. Emacs can now load shared libraries built against Emacs’ 两个月前,Emacs 25发布了,并引入了一个新的动态模块特性。Emacs现在可以加载基于Emacs的共享库。 module API, defined in emacs-module.h. What’s interesting about this API is that it doesn’t require linking against Emacs or any sort of 模块API,在[[http://git.savannah.gnu.org/cgit/emacs.git/tree/src/emacs-module.h?这个API的有趣之处在于,它不需要链接到Emacs或其他任何类型 library. Instead, at run time Emacs supplies the module’s initialization function with function pointers for the entire API. 图书馆。相反,Emacs在运行时为模块的初始化函数提供整个API的函数指针。

As a demonstration, in this article I’ll build an Emacs joystick interface (Linux only) using a dynamic module. It will allow Emacs to 作为演示,在本文中,我将使用一个动态模块构建一个Emacs操纵杆接口(仅限Linux)。它将允许Emacs read events from any joystick on the system. All the source code is here: 从系统上的任何操纵杆读取事件。所有的源代码在这里:

2 [[https://github.com/skeeto/joymacs] [https://github.com/skeeto/joymacs]]

It includes a calibration interface (M-x joydemo) within Emacs: 它包括一个校准接口(M-x joydemo)内的Emacs:

joymacs.png joymacs.png

Currently, Emacs’ emacs-module.h header is the entirety of the module documentation. It’s a bit thin and leaves ambiguities that requires 目前,Emacs的Emacs -module.h头文件是整个模块文档。它有点薄,留下了需要的歧义 some reading of the Emacs source code. Even reading the source, it’s not clear which behaviors are a reliable part of the interface. For 一些Emacs源代码的阅读。即使阅读源代码,也不清楚哪些行为是接口的可靠部分。为 example, if there’s a pending non-local exit, it’s safe for a function to return NULL since the return value is never inspected (Emacs 例如,如果有一个挂起的非本地出口,函数返回NULL是安全的,因为返回值从未被检查(Emacs 25.1), but will this always be the case? While mistakes are unforgiving (a hard crash), the API is mostly intuitive and it’s been pretty 但是情况总是这样吗?虽然错误是不可原谅的(一个硬崩溃),但这个API基本上是直观的,而且它很漂亮 easy to feel my way around it. 很容易找到我的方法。

3 Dynamic Module Types

*动态模块类型

All Emacs values — integers, floats, cons cells, vectors, strings, etc. — are represented as the polymorphic, pointer-valued type, 所有的Emacs值——整数、浮点数、反转单元格、向量、字符串等等——都表示为多态指针值类型, emacsvalue. Despite being a pointer, NULL is not a valid value, as convenient as that would be. The API includes functions for creating emacsvalue。尽管NULL是一个指针,但它并不是一个有效的值,这很方便。该API包含用于创建的函数 and extracting the fundamental types: integers, floats, strings. Almost all other object types can only be accessed by making Lisp 提取基本类型:整数、浮点数、字符串。几乎所有其他对象类型都只能通过创建Lisp来访问 function calls to regular Emacs functions from the module. 从模块中调用常规Emacs函数。

Modules also introduce a brand new Emacs object type: a user pointer. These are non-readable, opaque pointer values returned by modules, 模块还引入了一个全新的Emacs对象类型:用户指针。这些是不可读,模块返回的不透明指针值, typically representing a handle to some resource, be it a memory block, database connection, or a joystick. These objects include a 通常表示某个资源的句柄,可以是内存块、数据库连接或操纵杆。这些对象包括 finalizer function pointer — which, surprisingly, is not permitted to be NULL — and their lifetime is managed by Emacs’ garbage collector. 终结器函数指针——令人惊讶的是,它不允许为空——它们的生命周期由Emacs的垃圾收集器管理。

User pointers are a somewhat dangerous feature since there’s little to stop Emacs Lisp code from misusing them. A Lisp program can take a 用户指针是一个有点危险的特性,因为没有什么可以阻止Emacs Lisp代码滥用它们。Lisp程序可以取A user pointer from one module and pass it to a function in a different module. Since it’s just a pointer, there’s no way to type check it. 用户指针从一个模块,并将其传递给另一个模块中的函数。因为它只是一个指针,没有办法类型检查它。 At best, a module could maintain a table of all its live pointers, checking all user pointer arguments against the table before 一个模块最多可以维护一个包含所有活动指针的表,检查所有用户指针参数 dereferencing. But I don’t expect this to be normal practice. 废弃。但我不认为这是正常的做法。

4 Module Initialization

*模块初始化

After loading the module through the platform’s mechanism, the first thing Emacs does is check for the symbol pluginisGPLcompatible. 通过平台机制加载模块之后,Emacs要做的第一件事是检查符号pluginisGPLcompatible。 While tacky, this is not surprising given the culture around Emacs. 虽然有点俗气,但考虑到Emacs的文化,这并不奇怪。

Next it calls emacsmoduleinit(), passing it the first function pointer. From this, the module can get a Lisp environment and start doing 接下来调用emacsmoduleinit(),将第一个函数指针传递给它。从这里,模块可以获得一个Lisp环境并开始执行 Emacs things, such as binding module functions to Lisp symbols. Emacs的东西,比如将模块函数绑定到Lisp符号。

Here’s a complete “Hello, world!” example: 这是一句完整的“你好,世界!””示例:

# + BEGIN_SRC c
#include "emacs-module.h"
# include“emacs-module.h”

int plugin_is_GPL_compatible;
int plugin_is_GPL_compatible;

int
int
emacs_module_init(struct emacs_runtime *ert)
emacs_module_init(struct emacs_runtime *ert)
{
emacs_env *env = ert->get_environment(ert);
emacs_env *env = ert->get_environment(ert);
emacs_value message = env->intern(env, "message");
emacs_value message = env->intern(env,“message”);
const char hi[] = "Hello, world!";
const char hi[] = "Hello, world!";
emacs_value string = env->make_string(env, hi, sizeof(hi) - 1);
emacs_value字符串= env->make_string(env, hi, sizeof(hi) - 1);
env->funcall(env, message, 1, &string);
env->函数(env, message, 1, &string);
return 0;
返回0;
}

In a real module, it’s common to create function objects for native functions, then fetch the fset symbol and make a Lisp call on it to 在实际的模块中,通常为本机函数创建函数对象,然后获取fset符号并对其进行Lisp调用 bind the newly-created function object to a name. You’ll see this in action later. 将新创建的函数对象绑定到名称。稍后您将看到它的实际应用。

5 Joystick API

*操纵杆API

The joystick API will closely resemble Linux’s own joystick API, making for a fairly thin wrapper. It’s so thin that Emacs almost doesn’t 操纵杆API将非常类似于Linux自己的操纵杆API,这是一个非常薄的包装。它很薄,Emacs几乎没有 even need a dynamic module. This is because, on Linux, joysticks are just files under dev/input. Want to see the input events on the 甚至需要一个动态模块。这是因为,在Linux上,操纵杆只是/dev/input/下的文件。要查看上的输入事件 first joystick? Just read /dev/input/js0. So Plan 9. 第一个操纵杆?刚读/dev/input/js0.所以计划9。

Emacs already knows how to read files, but these virtual files are a little too special for that. The header linux/joystick.h defines a Emacs已经知道如何读取文件,但是这些虚拟文件太特殊了。头文件linux/操纵杆.h定义了一个 struct jsevent: struct jsevent:

# + BEGIN_SRC c
struct js_event {
struct js_event {
uint32_t time;  /* event timestamp in milliseconds */
uint32_t时间;事件时间戳(以毫秒为单位)*/
int16_t value;
int16_t价值;
uint8_t type;
uint8_t类型;
uint8_t number; /* axis/button number */
uint8_t数量;/*轴/按钮编号*/
};

The idea is to read from the joystick device into this structure. The first several reads are initialization that define the axes and 这个想法是把操纵杆装置读入这个结构。前几次读取是定义轴和的初始化 buttons of the joystick and their initial state. Further events are queued up for the file descriptor. This all means that the file can’t 操纵杆的按钮及其初始状态。进一步的事件将排队等待文件描述符。这意味着文件不能 just be opened each time joystick input is needed. It has to be held open for the duration, and is typically configured non-blocking. 只需在每次需要操纵杆输入时打开即可。它必须在整个过程中保持打开状态,并且通常配置为非阻塞。

The Emacs package will be called joymacs and there will be three functions: Emacs包将被称为joymacs,将有三个功能:

# + BEGIN_SRC emacs lisp
(joymacs-open N)
(joymacs-open N)
(joymacs-close JOYSTICK)
(joymacs-close操纵杆)
(joymacs-read JOYSTICK EVENT-VECTOR)
(joymacs-read操纵杆EVENT-VECTOR)

5.1 joymacs-open

6 * joymacs-open

The joymacs-open function will take an integer, opening the Nth joystick (/dev/input/jsN). It will create a file descriptor for the joymacs-open函数将接受一个整数,打开第n个操纵杆(/dev/input/jsN)。的文件描述符 joystick device, returning it as a user pointer. Think of it as a sort of “joystick handle.” Now, it could instead return the file 操纵杆设备,作为用户指针返回。可以把它看作是一种“操纵杆手柄”。现在,它可以返回文件 descriptor as an integer, but the user pointer has two significant benefits: 描述符作为一个整数,但用户指针有两个重要的好处:

  1. The resource will be garbage collected. If the caller loses track of a file descriptor returned as an integer, the joystick device
  2. 资源将被垃圾回收。如果调用者丢失了作为整数返回的文件描述符的轨迹,则控制杆设备

will be held open until Emacs shuts down, using up one of Emacs’ file descriptors. By putting it in a user pointer, the garbage 将一直保持打开状态,直到Emacs关闭,这将使用完Emacs的一个文件描述符。通过把它放在一个用户指针,垃圾 collector will have the module to release the file descriptor if the user loses track of it. 如果用户失去对文件描述符的跟踪,collector将使用模块来释放文件描述符。

  1. It should be difficult for the user to make a dangerous call. Emacs Lisp can’t create user pointers — they only come from modules —
  2. 用户应该很难发出危险的呼叫。Emacs Lisp不能创建用户指针——它们只来自模块——

and so the module is less likely to get passed the wrong thing. In the case of joystick-close, the module will be calling close(2) on 所以模块不太可能传递错误的东西。在操纵杆关闭的情况下,模块将调用关闭(2) the argument. We definitely don’t want to make that system call on file descriptors owned by Emacs. Further, since user pointers are 这个论点。我们绝对不想对Emacs拥有的文件描述符进行系统调用。而且,因为用户指针是 mutable, the module can ensure it doesn’t call close(2) twice. 可变的,模块可以确保它不会两次调用close(2)。

Here’s the implementation for joymacs-open. I’ll over over each part in detail. 下面是joymacs-open的实现。我将详细介绍每一部分。

# + BEGIN_SRC c
static emacs_value
静态emacs_value
joymacs_open(emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
(emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
{
(void)ptr;
(空白)ptr;
(void)n;
(空白)n;
int id = env->extract_integer(env, args[0]);
int id = env->extract_integer(env, args[0]);
if (env->non_local_exit_check(env) != emacs_funcall_exit_return)
如果(env->non_local_exit_check(env) != emacs_funcall_exit_return)
return nil;
返回nil;
char buf[64];
字符缓冲区(64);
int buflen = sprintf(buf, "/dev/input/js%d", id);
int buflen = sprintf(buf, "/dev/input/js%d", id);
int fd = open(buf, O_RDONLY | O_NONBLOCK);
int fd = open(buf, O_RDONLY | O_NONBLOCK);
if (fd == -1) {
if (fd == -1) {
emacs_value signal = env->intern(env, "file-error");
emacs_value信号= env->实习生(env, "文件错误");
emacs_value message = env->make_string(env, buf, buflen);
emacs_value消息= env->make_string(env, buf, buflen);
env->non_local_exit_signal(env, signal, message);
env - > non_local_exit_signal (env、信号、消息);
return nil;
返回nil;
}
return env->make_user_ptr(env, fin_close, (void *)(intptr_t)fd);
返回env->make_user_ptr(env, fin_close, (void *)(intptr_t)fd);
}

The C function name doesn’t matter to Emacs. It’s static because it doesn’t even matter if the function visible to Emacs. It will get the 对于Emacs来说,C函数名并不重要。它是静态的,因为对于Emacs来说,函数是否可见并不重要。它会得到 function pointer later as part of initialization. 函数指针稍后作为初始化的一部分。

This is the prototype for all functions callable by Emacs Lisp, regardless of its arity. It has four arguments: 这是Emacs Lisp可以调用的所有函数的原型,不管它的特性如何。它有四个参数:

  1. It gets an environment, env, through which to call back into Emacs.
  2. 它获取一个环境env,通过它可以回调Emacs。
  3. It gets n, the number of arguments. This is guaranteed to be the correct number of arguments, as specified later when creating the
  4. 它有n个参数。这保证是正确的参数数量,稍后在创建时指定

function object, so only variadic functions need to inspect this argument. 函数对象,所以只有可变参数函数需要检查这个参数。

  1. The Lisp arguments are passed as an array of values, args. There’s no type declaration when declaring a function object, so these may

3.Lisp参数作为值args数组传递。在声明函数对象时没有类型声明,所以这些可能 be of the wrong type. I’ll go over how to deal with this. 属于错误的类型。我将详细说明如何处理这件事。

  1. Finally, it gets an arbitrary pointer, supplied at function object creation time. This allows the module to create closures, but will
  2. 最后,它获得一个在函数对象创建时提供的任意指针。这允许模块创建闭包

usually be ignored. 通常被忽略。

The first thing the function does is extract its integer argument. This is actually an intmaxt, but I don’t think anyone has that many 函数要做的第一件事是提取它的整型参数。这实际上是一个intmaxt,但我认为没有人有那么多 USB ports. An int will suffice. USB端口。一个整数就足够了。

# + BEGIN_SRC c
int id = env->extract_integer(env, args[0]);
int id = env->extract_integer(env, args[0]);
if (env->non_local_exit_check(env) != emacs_funcall_exit_return)
如果(env->non_local_exit_check(env) != emacs_funcall_exit_return)
return nil;
返回nil;

As for not underestimating fools, what if the user passed a value that isn’t an integer? Will the world come crashing down? Fortunately 至于不要低估愚人,如果用户传递的值不是整数呢?世界会崩溃吗?幸运的是 Emacs checks that in extractinteger and, if there’s a mismatch, sets a pending error signal in the environment. This is really great Emacs在extractinteger中检查它,如果不匹配,则在环境中设置一个挂起的错误信号。这真的很棒 because checking types directly in the module is a real pain the ass. So, before committing to anything further, such as opening a file, I 因为直接在模块中检查类型是一件非常麻烦的事情 check for this signal and bail out early if necessary. In Emacs 25.1 it’s safe to return NULL since the return value will be completely 检查此信号,如有必要,及早离开。在Emacs 25.1中,返回NULL是安全的,因为返回值是完全空的 ignored, but I’d rather hedge my bets. 被忽略了,但我宁愿两面下注。

By the way, the nil here is a global variable set in initialization. You don’t just get that for free! 顺便说一下,nil是初始化时设置的全局变量。你不可能免费得到的!

The next step is opening the joystick device, read-only and non-blocking. The non-blocking is vital because the module would otherwise 下一步是打开操纵杆装置,只读和非阻塞。非阻塞是至关重要的,因为模块否则会 hang Emacs later if there are no events (well, except for the read being quickly interrupted by a POSIX signal). 如果没有发生事件,稍后挂起Emacs(除了被POSIX信号快速中断的读之外)。

# + BEGIN_SRC c
char buf[64];
字符缓冲区(64);
int buflen = sprintf(buf, "/dev/input/js%d", id);
int buflen = sprintf(buf, "/dev/input/js%d", id);
int fd = open(buf, O_RDONLY | O_NONBLOCK);
int fd = open(buf, O_RDONLY | O_NONBLOCK);

If the joystick fails to open (e.g. it doesn’t exist, or the user lacks permission), manually set an error signal for a non-local exit. I 如果操纵杆打不开(例如它不存在,或者用户没有权限),手动设置一个非本地退出的错误信号。我 chose the file-error signal and I’m just using the filename as the signal data. 选择文件错误信号,我只是使用文件名作为信号数据。

# + BEGIN_SRC c
if (fd == -1) {
if (fd == -1) {
emacs_value signal = env->intern(env, "file-error");
emacs_value信号= env->实习生(env, "文件错误");
emacs_value message = env->make_string(env, buf, buflen);
emacs_value消息= env->make_string(env, buf, buflen);
env->non_local_exit_signal(env, signal, message);
env - > non_local_exit_signal (env、信号、消息);
return nil;
返回nil;
}

Otherwise create the user pointer. No need to allocate any memory; just stuff it in the pointer itself. If the user mistakenly passes it 否则创建用户指针。不需要分配任何内存;只是把它塞进指针本身。如果用户错误地传递它 to another module, it will sure be in for a surprise when it tries to dereference it. 对于另一个模块,当它试图取消对它的引用时,它肯定会大吃一惊。

# + BEGIN_SRC c
return env->make_user_ptr(env, fin_close, (void *)(intptr_t)fd);
返回env->make_user_ptr(env, fin_close, (void *)(intptr_t)fd);

The finclose() function is defined as: finclose()函数定义为:

# + BEGIN_SRC c
static void
静态的空白
fin_close(void *fdptr)
fin_close (void * fdptr)
{
int fd = (intptr_t)fdptr;
"特蕾莎银行"
if (fd != -1)
if (fd != -1)
close(fd);
关闭(fd);
}

The garbage collector will call this function when the user pointer is lost. If the user closes it early with joymacs-close, that function 当用户指针丢失时,垃圾收集器将调用这个函数。如果用户使用joymacs-close提前关闭该函数 will set the user pointer to -1, an invalid file descriptor, so that it doesn’t get closed a second time here. 将用户指针设置为-1,这是一个无效的文件描述符,因此在这里它不会第二次被关闭。

6.1 joymacs-close

7 * joymacs-close

Here’s joymacs-close, which is a bit simpler. 这是joymacs-close,比较简单。

# + BEGIN_SRC c
static emacs_value
静态emacs_value
joymacs_close(emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
@ joymacs_close(emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
{
(void)ptr;
(空白)ptr;
(void)n;
(空白)n;
int fd = (intptr_t)env->get_user_ptr(env, args[0]);
int . fd = rs / i . cnn (0):
if (env->non_local_exit_check(env) != emacs_funcall_exit_return)
如果(env->non_local_exit_check(env) != emacs_funcall_exit_return)
return nil;
返回nil;
if (fd != -1) {
if (fd != -1) {
close(fd);
关闭(fd);
env->set_user_ptr(env, args[0], (void *)(intptr_t)-1);
env - > set_user_ptr (args [0], env (void *) (intptr_t) 1);
}
return nil;
返回nil;
}

Again, it starts by extracting its argument, relying on Emacs to do the check: 同样,它首先提取自己的参数,然后依赖Emacs进行检查:

# + BEGIN_SRC c
int fd = (intptr_t)env->get_user_ptr(env, args[0]);
int . fd = rs / i . cnn (0):
if (env->non_local_exit_check(env) != emacs_funcall_exit_return)
如果(env->non_local_exit_check(env) != emacs_funcall_exit_return)
return nil;
返回nil;

If the user pointer hasn’t been closed yet, then close it and strip out the file descriptor to prevent further closes. 如果用户指针还没有关闭,那么关闭它并去掉文件描述符以防止进一步关闭。

# + BEGIN_SRC c
if (fd != -1) {
if (fd != -1) {
close(fd);
关闭(fd);
env->set_user_ptr(env, args[0], (void *)(intptr_t)-1);
env - > set_user_ptr (args [0], env (void *) (intptr_t) 1);
}

7.1 joymacs-read

8 * joymacs-read

The joymacs-read function is doing something a little unusual for an Emacs Lisp function. It takes two arguments: the joystick handle and 对于Emacs Lisp函数来说,乔伊斯-里德函数做了一些不太寻常的事情。它有两个参数:操纵杆手柄和 a 5-element vector. Instead of returning the event in some representation, it fills the vector with the event details. The are two reasons 5-element向量。它不是返回某个表示形式的事件,而是用事件细节填充向量。有两个原因 for this:



  1. The API has no function for creating vectors … though the module could get the make-symbol vector and call it to create a vector.
  2. 该API没有创建向量的功能……尽管模块可以获取make-symbol向量并调用它来创建向量。
  3. The idiom for event pumps is for the caller to supply a buffer to the pump. This has better performance by avoiding lots of
  4. 事件泵的习惯用法是调用者向泵提供一个缓冲区。这避免了很多问题,从而具有更好的性能

unnecessary allocations, especially since events tend to be message-like objects with a short, well-defined extent. 不必要的分配,特别是因为事件往往是具有短的、定义良好的范围的类似消息的对象。

Here’s the full definition: 以下是完整的定义:

# + BEGIN_SRC c
static emacs_value
静态emacs_value
joymacs_read(emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
@ joymacs_read(emacs_env *env, ptrdiff_t n, emacs_value *args, void *ptr)
{
(void)n;
(空白)n;
(void)ptr;
(空白)ptr;
int fd = (intptr_t)env->get_user_ptr(env, args[0]);
int . fd = rs / i . cnn (0):
if (env->non_local_exit_check(env) != emacs_funcall_exit_return)
如果(env->non_local_exit_check(env) != emacs_funcall_exit_return)
return nil;
返回nil;
struct js_event e;
struct js_event e;
int r = read(fd, &e, sizeof(e));
int r = read(fd, &e, sizeof(e));
if (r == -1 && errno == EAGAIN) {
if (r == -1 && errno == EAGAIN) {
/* No more events. */
/*没有其他活动。* /
return nil;
返回nil;
} else if (r == -1) {
} else if (r == -1) {
/* An actual read error (joystick unplugged, etc.). */
实际读取错误(游戏手柄未插拔等)。* /
emacs_value signal = env->intern(env, "file-error");
emacs_value信号= env->实习生(env, "文件错误");
const char *error = strerror(errno);
const char *error = strerror(errno);
size_t len = strlen(error);
size_t len = strlen(错误);
emacs_value message = env->make_string(env, error, len);
emacs_value消息= env->make_string(env,错误,len);
env->non_local_exit_signal(env, signal, message);
env - > non_local_exit_signal (env、信号、消息);
return nil;
返回nil;
} else {
其他}{
/* Fill out event vector. */
/*填写事件向量。* /
emacs_value v = args[1];
emacs_value v = args[1];
emacs_value type = e.type & JS_EVENT_BUTTON ? button : axis;
emacs_value类型= e。类型& JS_EVENT_BUTTON ?按钮:轴;
emacs_value value;
emacs_value价值;
if (type == button)
if (type == button)
value = e.value ? t : nil;
值= e。价值吗?t:零;
else
其他的
value =  env->make_float(env, e.value / (double)INT16_MAX);
值= env->make_float(env, e。价值/(双)INT16_MAX);
env->vec_set(env, v, 0, env->make_integer(env, e.time));
env->vec_set(env, v, 0, env->make_integer(env, e.time));
env->vec_set(env, v, 1, type);
env->vec_set(env, v, 1, type);
env->vec_set(env, v, 2, value);
env->vec_set(env, v, 2,值);
env->vec_set(env, v, 3, env->make_integer(env, e.number));
env->vec_set(env, v, 3, env->make_integer(env, e.number));
env->vec_set(env, v, 4, e.type & JS_EVENT_INIT ? t : nil);
env->vec_set(env, v, 4, e)类型& JS_EVENT_INIT ?t: nil);
return args[1];
返回参数[1];
}
}

As before, extract the first argument and check for a signal. Then call read(2) to get an event. If the read fails with EAGAIN, it’s not a 与前面一样,提取第一个参数并检查信号。然后调用read(2)获取一个事件。如果读取EAGAIN失败,它不是a real failure. There are just no more events, so return nil. 真正的失败。没有更多事件,返回nil。

# + BEGIN_SRC c
struct js_event e;
struct js_event e;
int r = read(fd, &e, sizeof(e));
int r = read(fd, &e, sizeof(e));
if (r == -1 && errno == EAGAIN) {
if (r == -1 && errno == EAGAIN) {
/* No more events. */
/*没有其他活动。* /
return nil;
返回nil;
}

If the read failed with something else — perhaps the joystick was unplugged — signal an error. The strerror(3) string is used for the 如果读操作失败——可能是操纵杆未插拔——则表示出错。方法使用strerror(3)字符串 signal data. 信号数据。

# + BEGIN_SRC c
if (r == -1) {
if (r == -1) {
/* An actual read error (joystick unplugged, etc.). */
实际读取错误(游戏手柄未插拔等)。* /
emacs_value signal = env->intern(env, "file-error");
emacs_value信号= env->实习生(env, "文件错误");
const char *error = strerror(errno);
const char *error = strerror(errno);
emacs_value message = env->make_string(env, error, strlen(error));
emacs_value消息= env->make_string(env,错误,strlen(错误));
env->non_local_exit_signal(env, signal, message);
env - > non_local_exit_signal (env、信号、消息);
return nil;
返回nil;
}

Otherwise fill out the event vector. If the second argument isn’t a vector, or if it’s too short, the signal will automatically get raised 否则填写事件向量。如果第二个参数不是一个向量,或者它太短,信号将自动被触发 by Emacs. The module can keep plowing through the vecset() calls safely since it’s not committing to anything. Emacs。模块可以安全地通过vecset()调用,因为它没有提交任何东西。

# + BEGIN_SRC c
/* Fill out event vector. */
/*填写事件向量。* /
emacs_value v = args[1];
emacs_value v = args[1];
emacs_value type = e.type & JS_EVENT_BUTTON ? button : axis;
emacs_value类型= e。类型& JS_EVENT_BUTTON ?按钮:轴;
emacs_value value;
emacs_value价值;
if (type == button)
if (type == button)
value = e.value ? t : nil;
值= e。价值吗?t:零;
else
其他的
value =  env->make_float(env, e.value / (double)INT16_MAX);
值= env->make_float(env, e。价值/(双)INT16_MAX);
env->vec_set(env, v, 0, env->make_integer(env, e.time));
env->vec_set(env, v, 0, env->make_integer(env, e.time));
env->vec_set(env, v, 1, type);
env->vec_set(env, v, 1, type);
env->vec_set(env, v, 2, value);
env->vec_set(env, v, 2,值);
env->vec_set(env, v, 3, env->make_integer(env, e.number));
env->vec_set(env, v, 3, env->make_integer(env, e.number));
env->vec_set(env, v, 4, e.type & JS_EVENT_INIT ? t : nil);
env->vec_set(env, v, 4, e)类型& JS_EVENT_INIT ?t: nil);
return args[1];
返回参数[1];

The Linux event struct has four fields and the function fills out five values of the vector. This is because the type field has a bit flag Linux事件结构有四个字段,该函数填入向量的五个值。这是因为type字段有一个位标志 indicating initialization events. This is split out into an extra t/nil value. It also normalizes axis values and converts button values 显示初始化事件。它被分割成一个额外的t/nil值。它还规范化轴值和转换按钮值 into t/nil, which makes more sense for Emacs Lisp. The event itself is returned since it’s a truthy value and it’s convenient for the 转换成t/nil,这对Emacs Lisp更有意义。事件本身被返回,因为它是一个真实的值,对于 caller. 调用者。

The astute programmer might notice that the negative side of the axis could go just below -1.0, since INT16MIN has one extra value over 精明的程序员可能会注意到,轴的负方向可能刚好低于-1.0,因为INT16MIN有一个额外的值 INT16MAX (two’s complement). It doesn’t seem to be documented, but the joystick drivers I’ve seen never exactly return INT16MIN, so this INT16MAX(二进制补码)。似乎没有记录,但我看到的操纵杆司机从来没有返回INT16MIN,所以这 is in fact the correct way to normalize it. 实际上是使它正常化的正确方法。

8.1 Initialization

9 *初始化

All that’s left is the initialization function. First declare some global variables to keep track of frequently-used symbols. 剩下的就是初始化函数了。首先声明一些全局变量来跟踪经常使用的符号。

# + BEGIN_SRC c
static emacs_value nil;
静态emacs_value零;
static emacs_value t;
静态emacs_value t;
static emacs_value button;
静态emacs_value按钮;
static emacs_value axis;
静态emacs_value轴;

These are interned at the very beginning of initialization. The symbols :button and :axis are given global references so that the garbage 它们在初始化的一开始就被搁置了。符号:button和:axis被赋予了全局引用,因此垃圾 collector doesn’t rip them out from under the module. It’s unclear from the API, but the makeglobalref() function returns the object 收集器不会把它们从模块下面取出来。从API中并不清楚,但是makeglobalref()函数返回了对象 being referenced. I trust that the t and nil symbols will never be garbage collected, so these don’t need global references. 被引用。我相信t和nil符号永远不会被垃圾回收,所以它们不需要全局引用。

# + BEGIN_SRC c
nil = env->intern(env, "nil");
nil = env->实习生(env,“nil”);
t = env->intern(env, "t");
实习(env, "t");
button = env->make_global_ref(env, env->intern(env, ":button"));
button = env->make_global_ref(env, env->实习生(env, ":button"));
axis = env->make_global_ref(env, env->intern(env, ":axis"));
axis = env->make_global_ref(env, env->实习生(env,“:axis”));

emacs_value fset = env->intern(env, "fset");
emacs_value fset = env->实习生(env, "fset");

It also grabs fset locally since it will soon be needed. 它还将抓取=fset=本地,因为很快就会需要它。

Finally, bind the functions. The second and third arguments to makefunction are the minimum and maximum number of arguments, which may 最后,绑定函数。makefunction的第二个和第三个参数是最小和最大参数数,它们是may look familiar. The last argument is that closure pointer I mentioned at the beginning. 看起来很熟悉。最后一个参数是我在开头提到的闭包指针。

# + BEGIN_SRC c
emacs_value args[2];
emacs_value args [2];
args[0] = env->intern(env, "joymacs-open");
args[0] = env->实习生(env, "joymacs-open");
args[1] = env->make_function(env, 1, 1, joymacs_open, doc, 0);
args[1] = env->make_function(env, 1,1, joymacs_open, doc, 0);
env->funcall(env, fset, 2, args);
env->函数(env, fset, 2, args);

If the module is to be loaded with require like any other package, it needs to provide: (provide 'joymacs). 如果要像其他包一样加载require模块,则需要提供:(提供'joymacs ')。

# + BEGIN_SRC c
emacs_value provide = env->intern(env, "provide");
emacs_value提供= env->实习生(env,“提供”);
emacs_value joymacs = env->intern(env, "joymacs");
emacs_value joymacs = env->实习生(env, "joymacs");
env->funcall(env, provide, 1, &joymacs);
env->函数(env, provide, 1, &joymacs);

And that’s it! 这是它!

The source repository now includes a port to Windows (XInput). If you’re on Linux or Windows, have Emacs 25 with modules enabled, and a 源存储库现在包括一个到Windows (XInput)的端口。如果您使用的是Linux或Windows,请启用模块的Emacs 25和a joystick is plugged in, then make run in the repository should bring up Emacs running a joystick calibration demonstration. The module 将操纵杆插入,然后使其在存储库中运行,应该会出现Emacs运行操纵杆校准演示。该模块 can’t poke at Emacs when events are ready, so instead there’s a timer that polls the module for events. 当事件准备好时,不能戳Emacs,因此有一个计时器轮询模块中的事件。

I’d like to someday see an Emacs Lisp game well-suited for a joystick. 我希望有一天能看到一个非常适合作为操纵杆的Emacs Lisp游戏。