요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
=============
uinput module
=============
Introduction
============
uinput is a kernel module that makes it possible to emulate input devices
from userspace. By writing to /dev/uinput (or /dev/input/uinput) device, a
process can create a virtual input device with specific capabilities. Once
this virtual device is created, the process can send events through it,
that will be delivered to userspace and in-kernel consumers.
Interface
=========
::
linux/uinput.h
The uinput header defines ioctls to create, set up, and destroy virtual
devices.
libevdev
========
libevdev is a wrapper library for evdev devices that provides interfaces to
create uinput devices and send events. libevdev is less error-prone than
accessing uinput directly, and should be considered for new software.
For examples and more information about libevdev:
https://www.freedesktop.org/software/libevdev/doc/latest/
Examples
========
Keyboard events
---------------
This first example shows how to create a new virtual device, and how to
send a key event. All default imports and error handlers were removed for
the sake of simplicity.
.. code-block:: c
#include <linux/uinput.h>
void emit(int fd, int type, int code, int val)
{
struct input_event ie;
ie.type = type;
ie.code = code;
ie.value = val;
/* timestamp values below are ignored */
ie.time.tv_sec = 0;
ie.time.tv_usec = 0;
write(fd, &ie, sizeof(ie));
}
int main(void)
{
struct uinput_setup usetup;
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
/*
* The ioctls below will enable the device that is about to be
* created, to pass key events, in this case the space key.
*/
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, KEY_SPACE);
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234; /* sample vendor */
usetup.id.product = 0x5678; /* sample product */
strcpy(usetup.name, "Example device");
ioctl(fd, UI_DEV_SETUP, &usetup);
ioctl(fd, UI_DEV_CREATE);
/*
* On UI_DEV_CREATE the kernel will create the device node for this
* device. We are inserting a pause here so that userspace has time
* to detect, initialize the new device, and can start listening to
* the event, otherwise it will not notice the event we are about
* to send. This pause is only needed in our example code!
*/
sleep(1);
/* Key press, report the event, send key release, and report again */
emit(fd, EV_KEY, KEY_SPACE, 1);
emit(fd, EV_SYN, SYN_REPORT, 0);
emit(fd, EV_KEY, KEY_SPACE, 0);
emit(fd, EV_SYN, SYN_REPORT, 0);
/*
* Give userspace some time to read the events before we destroy the
* device with UI_DEV_DESTROY.
*/
sleep(1);
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 0;
}
Mouse movements
---------------
This example shows how to create a virtual device that behaves like a physical
mouse.
.. code-block:: c
#include <linux/uinput.h>
/* emit function is identical to of the first example */
int main(void)
{
struct uinput_setup usetup;
int i = 50;
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
/* enable mouse button left and relative events */
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);
ioctl(fd, UI_SET_EVBIT, EV_REL);
ioctl(fd, UI_SET_RELBIT, REL_X);
ioctl(fd, UI_SET_RELBIT, REL_Y);
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234; /* sample vendor */
usetup.id.product = 0x5678; /* sample product */
strcpy(usetup.name, "Example device");
ioctl(fd, UI_DEV_SETUP, &usetup);
ioctl(fd, UI_DEV_CREATE);
/*
* On UI_DEV_CREATE the kernel will create the device node for this
* device. We are inserting a pause here so that userspace has time
* to detect, initialize the new device, and can start listening to
* the event, otherwise it will not notice the event we are about
* to send. This pause is only needed in our example code!
*/
sleep(1);
/* Move the mouse diagonally, 5 units per axis */
while (i--) {
emit(fd, EV_REL, REL_X, 5);
emit(fd, EV_REL, REL_Y, 5);
emit(fd, EV_SYN, SYN_REPORT, 0);
usleep(15000);
}
/*
* Give userspace some time to read the events before we destroy the
* device with UI_DEV_DESTROY.
*/
sleep(1);
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 0;
}
uinput old interface
--------------------
Before uinput version 5, there wasn't a dedicated ioctl to set up a virtual
device. Programs supporting older versions of uinput interface need to fill
a uinput_user_dev structure and write it to the uinput file descriptor to
configure the new uinput device. New code should not use the old interface
but interact with uinput via ioctl calls, or use libevdev.
.. code-block:: c
#include <linux/uinput.h>
/* emit function is identical to of the first example */
int main(void)
{
struct uinput_user_dev uud;
int version, rc, fd;
fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
rc = ioctl(fd, UI_GET_VERSION, &version);
if (rc == 0 && version >= 5) {
/* use UI_DEV_SETUP */
return 0;
}
/*
* The ioctls below will enable the device that is about to be
* created, to pass key events, in this case the space key.
*/
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, KEY_SPACE);
memset(&uud, 0, sizeof(uud));
snprintf(uud.name, UINPUT_MAX_NAME_SIZE, "uinput old interface");
write(fd, &uud, sizeof(uud));
ioctl(fd, UI_DEV_CREATE);
/*
* On UI_DEV_CREATE the kernel will create the device node for this
* device. We are inserting a pause here so that userspace has time
* to detect, initialize the new device, and can start listening to
* the event, otherwise it will not notice the event we are about
* to send. This pause is only needed in our example code!
*/
sleep(1);
/* Key press, report the event, send key release, and report again */
emit(fd, EV_KEY, KEY_SPACE, 1);
emit(fd, EV_SYN, SYN_REPORT, 0);
emit(fd, EV_KEY, KEY_SPACE, 0);
emit(fd, EV_SYN, SYN_REPORT, 0);
/*
* Give userspace some time to read the events before we destroy the
* device with UI_DEV_DESTROY.
*/
sleep(1);
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 0;
}
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
소개, 인터페이스와 libevdev
1-33`uinput`은 사용자 공간에서 입력 장치를 흉내 낼 수 있게 하는 커널 모듈입니다. 프로세스가 `/dev/uinput` 또는 `/dev/input/uinput`에 기록하면 원하는 capability를 가진 가상 입력 장치를 만들 수 있습니다. 장치가 만들어진 뒤 프로세스가 보내는 event는 사용자 공간 소비자와 커널 내부 소비자 모두에게 전달됩니다.
인터페이스 선언은 `linux/uinput.h`에 있습니다. 이 header는 가상 장치를 만들고 설정하고 제거하는 ioctl을 정의합니다.
`libevdev`는 evdev 장치를 감싸는 library이며 uinput 장치 생성과 event 전송 인터페이스를 제공합니다. uinput을 직접 다루는 것보다 오류 가능성이 낮으므로 새 software에서는 `libevdev` 사용을 고려해야 합니다. 예제와 최신 정보는 `https://www.freedesktop.org/software/libevdev/doc/latest/`에서 확인할 수 있습니다.
가상 입력 장치가 사용자 공간과 input subsystem 사이에서 맡는 역할입니다.
capability를 정한 뒤 장치를 공개하고 event를 전송합니다.
=============
uinput module
=============
Introduction
============
uinput is a kernel module that makes it possible to emulate input devices
from userspace. By writing to /dev/uinput (or /dev/input/uinput) device, a
process can create a virtual input device with specific capabilities. Once
this virtual device is created, the process can send events through it,
that will be delivered to userspace and in-kernel consumers.
Interface
=========
::
linux/uinput.h
The uinput header defines ioctls to create, set up, and destroy virtual
devices.
libevdev
========
libevdev is a wrapper library for evdev devices that provides interfaces to
create uinput devices and send events. libevdev is less error-prone than
accessing uinput directly, and should be considered for new software.
For examples and more information about libevdev:
https://www.freedesktop.org/software/libevdev/doc/latest/
가상 keyboard와 key event 예제
34-111첫 번째 예제는 새 가상 장치를 만들고 key event를 보내는 방법을 보여 줍니다. 흐름을 분명히 하기 위해 기본 include와 error handler는 생략되었습니다. 실제 program에서는 모든 system call과 ioctl의 반환값을 검사해야 합니다.
`emit()`은 `struct input_event`의 `type`, `code`, `value`를 채운 뒤 descriptor에 구조체 전체를 기록합니다. 아래 timestamp 값은 uinput에서 무시되므로 `tv_sec`과 `tv_usec`를 0으로 둡니다.
한 input event를 구성하는 핵심 field입니다.
`main()`은 `/dev/uinput`을 `O_WRONLY | O_NONBLOCK`으로 엽니다. `UI_SET_EVBIT`으로 `EV_KEY`를, `UI_SET_KEYBIT`으로 `KEY_SPACE`를 활성화하여 이 장치가 space key event를 전달할 수 있게 합니다.
`struct uinput_setup`을 0으로 초기화한 뒤 bus type은 `BUS_USB`, sample vendor와 product ID는 각각 `0x1234`, `0x5678`, 이름은 `Example device`로 설정합니다. `UI_DEV_SETUP`으로 이 정보를 적용하고 `UI_DEV_CREATE`로 장치를 만듭니다.
`UI_DEV_CREATE`가 반환되면 kernel은 장치 node를 만듭니다. 예제의 `sleep(1)`은 사용자 공간이 새 장치를 발견하고 초기화하여 event를 듣기 시작할 시간을 주기 위한 것입니다. 이 pause는 예제에만 필요하며 일반적인 protocol 요구 사항은 아닙니다.
누름과 놓음은 각각 `SYN_REPORT`로 끝나는 별도 frame입니다.
마지막 pause는 사용자 공간이 event를 읽을 시간을 주기 위한 것입니다. 그 뒤 `UI_DEV_DESTROY`로 가상 장치를 없애고 `close(fd)`로 descriptor를 닫습니다.
capability 설정에서 장치 제거까지의 호출 목적입니다.
Examples
========
Keyboard events
---------------
This first example shows how to create a new virtual device, and how to
send a key event. All default imports and error handlers were removed for
the sake of simplicity.
.. code-block:: c
#include <linux/uinput.h>
void emit(int fd, int type, int code, int val)
{
struct input_event ie;
ie.type = type;
ie.code = code;
ie.value = val;
/* timestamp values below are ignored */
ie.time.tv_sec = 0;
ie.time.tv_usec = 0;
write(fd, &ie, sizeof(ie));
}
int main(void)
{
struct uinput_setup usetup;
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
/*
* The ioctls below will enable the device that is about to be
* created, to pass key events, in this case the space key.
*/
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, KEY_SPACE);
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234; /* sample vendor */
usetup.id.product = 0x5678; /* sample product */
strcpy(usetup.name, "Example device");
ioctl(fd, UI_DEV_SETUP, &usetup);
ioctl(fd, UI_DEV_CREATE);
/*
* On UI_DEV_CREATE the kernel will create the device node for this
* device. We are inserting a pause here so that userspace has time
* to detect, initialize the new device, and can start listening to
* the event, otherwise it will not notice the event we are about
* to send. This pause is only needed in our example code!
*/
sleep(1);
/* Key press, report the event, send key release, and report again */
emit(fd, EV_KEY, KEY_SPACE, 1);
emit(fd, EV_SYN, SYN_REPORT, 0);
emit(fd, EV_KEY, KEY_SPACE, 0);
emit(fd, EV_SYN, SYN_REPORT, 0);
/*
* Give userspace some time to read the events before we destroy the
* device with UI_DEV_DESTROY.
*/
sleep(1);
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 0;
}
가상 mouse와 상대 이동 예제
112-177두 번째 예제는 물리 mouse처럼 동작하는 가상 장치를 만듭니다. `emit()` 함수는 첫 번째 예제와 같습니다.
왼쪽 button을 위해 `EV_KEY`와 `BTN_LEFT`를 활성화하고 상대 이동을 위해 `EV_REL`, `REL_X`, `REL_Y`를 활성화합니다. 장치 identity와 이름은 keyboard 예제와 같은 `struct uinput_setup` 절차로 설정한 뒤 `UI_DEV_CREATE`를 호출합니다.
button과 두 상대 좌표축을 함께 선언합니다.
장치 생성 뒤의 `sleep(1)`은 앞 예제와 마찬가지로 사용자 공간 listener가 장치를 발견할 시간을 주는 예제용 pause입니다.
반복문은 50회 실행됩니다. 각 회마다 `REL_X`와 `REL_Y`에 5를 보내고 `SYN_REPORT`로 한 frame을 완료하므로 pointer는 대각선으로 이동합니다. frame 사이에는 `usleep(15000)`으로 15 ms를 둡니다.
각 반복은 X·Y delta를 하나의 동기화 frame으로 묶습니다.
event 소비 시간을 위한 마지막 pause 뒤 `UI_DEV_DESTROY`를 호출하고 descriptor를 닫아 장치를 정리합니다.
Mouse movements
---------------
This example shows how to create a virtual device that behaves like a physical
mouse.
.. code-block:: c
#include <linux/uinput.h>
/* emit function is identical to of the first example */
int main(void)
{
struct uinput_setup usetup;
int i = 50;
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
/* enable mouse button left and relative events */
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);
ioctl(fd, UI_SET_EVBIT, EV_REL);
ioctl(fd, UI_SET_RELBIT, REL_X);
ioctl(fd, UI_SET_RELBIT, REL_Y);
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234; /* sample vendor */
usetup.id.product = 0x5678; /* sample product */
strcpy(usetup.name, "Example device");
ioctl(fd, UI_DEV_SETUP, &usetup);
ioctl(fd, UI_DEV_CREATE);
/*
* On UI_DEV_CREATE the kernel will create the device node for this
* device. We are inserting a pause here so that userspace has time
* to detect, initialize the new device, and can start listening to
* the event, otherwise it will not notice the event we are about
* to send. This pause is only needed in our example code!
*/
sleep(1);
/* Move the mouse diagonally, 5 units per axis */
while (i--) {
emit(fd, EV_REL, REL_X, 5);
emit(fd, EV_REL, REL_Y, 5);
emit(fd, EV_SYN, SYN_REPORT, 0);
usleep(15000);
}
/*
* Give userspace some time to read the events before we destroy the
* device with UI_DEV_DESTROY.
*/
sleep(1);
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 0;
}
uinput v5 이전의 구형 인터페이스
178-245uinput version 5 이전에는 가상 장치를 설정하는 전용 ioctl이 없었습니다. 구형 interface까지 지원하는 program은 `struct uinput_user_dev`를 채운 뒤 uinput file descriptor에 직접 기록하여 새 장치를 구성해야 합니다. 새 code는 이 구형 interface를 사용하지 말고 ioctl 또는 `libevdev`를 사용해야 합니다.
예제는 `UI_GET_VERSION`으로 version을 읽습니다. 호출이 성공하고 version이 5 이상이면 `UI_DEV_SETUP` 경로를 사용할 수 있으므로 구형 설정을 수행하지 않습니다.
version 5를 경계로 장치 설정 방법이 달라집니다.
구형 경로에서도 `UI_SET_EVBIT`과 `UI_SET_KEYBIT`으로 `EV_KEY`와 `KEY_SPACE`를 활성화합니다. `struct uinput_user_dev`를 0으로 초기화하고 `UINPUT_MAX_NAME_SIZE` 한도 안에서 이름을 `uinput old interface`로 쓴 다음 구조체 전체를 descriptor에 기록합니다.
`UI_GET_VERSION` 결과에 따라 현대 설정과 구조체 기록 방식을 나눕니다.
장치 생성 후에는 현대 예제와 같은 이유로 잠시 기다린 뒤 space key 누름과 놓음을 각각 `SYN_REPORT`로 보고합니다. 사용자 공간이 읽을 시간을 준 다음 `UI_DEV_DESTROY`를 호출하고 descriptor를 닫습니다.
uinput old interface
--------------------
Before uinput version 5, there wasn't a dedicated ioctl to set up a virtual
device. Programs supporting older versions of uinput interface need to fill
a uinput_user_dev structure and write it to the uinput file descriptor to
configure the new uinput device. New code should not use the old interface
but interact with uinput via ioctl calls, or use libevdev.
.. code-block:: c
#include <linux/uinput.h>
/* emit function is identical to of the first example */
int main(void)
{
struct uinput_user_dev uud;
int version, rc, fd;
fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
rc = ioctl(fd, UI_GET_VERSION, &version);
if (rc == 0 && version >= 5) {
/* use UI_DEV_SETUP */
return 0;
}
/*
* The ioctls below will enable the device that is about to be
* created, to pass key events, in this case the space key.
*/
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, KEY_SPACE);
memset(&uud, 0, sizeof(uud));
snprintf(uud.name, UINPUT_MAX_NAME_SIZE, "uinput old interface");
write(fd, &uud, sizeof(uud));
ioctl(fd, UI_DEV_CREATE);
/*
* On UI_DEV_CREATE the kernel will create the device node for this
* device. We are inserting a pause here so that userspace has time
* to detect, initialize the new device, and can start listening to
* the event, otherwise it will not notice the event we are about
* to send. This pause is only needed in our example code!
*/
sleep(1);
/* Key press, report the event, send key release, and report again */
emit(fd, EV_KEY, KEY_SPACE, 1);
emit(fd, EV_SYN, SYN_REPORT, 0);
emit(fd, EV_KEY, KEY_SPACE, 0);
emit(fd, EV_SYN, SYN_REPORT, 0);
/*
* Give userspace some time to read the events before we destroy the
* device with UI_DEV_DESTROY.
*/
sleep(1);
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 0;
}
요약·해설
uinput.rst:1-245uinput은 사용자 공간 process가 capability와 identity를 정한 가상 input device를 만들고 event를 kernel input subsystem에 주입하는 interface입니다. 새 code는 `UI_DEV_SETUP` 또는 `libevdev`를 사용해야 하며 v5 이전의 `uinput_user_dev` 기록 방식은 호환 목적으로만 필요합니다.
문서의 세 예제와 권장 interface를 요약합니다.