瀏覽代碼

Introduce Virtual Input Device Driver

Add the new section of input device driver, vinput[1].

Also, update the Acknowledgements.

[1] https://github.com/sysprog21/vinput
linD026 3 年之前
父節點
當前提交
636c1e702d
共有 11 個文件被更改,包括 656 次插入2 次删除
  1. 1 0
      .ci/non-working
  2. 2 1
      .mailmap
  3. 7 0
      contrib.tex
  4. 4 0
      examples/.clang-format
  5. 2 0
      examples/Makefile
  6. 398 0
      examples/vinput.c
  7. 45 0
      examples/vinput.h
  8. 106 0
      examples/vkbd.c
  9. 82 0
      lkmpg.tex
  10. 7 0
      scripts/Contributors
  11. 2 1
      scripts/Include

+ 1 - 0
.ci/non-working

@@ -1,2 +1,3 @@
 bottomhalf
 intrpt
+vkbd

+ 2 - 1
.mailmap

@@ -5,4 +5,5 @@ Jim Huang   <jserv.tw@gmail.com> Jim Huang  <jserv@ccns.ncku.edu.tw>
 Jim Huang   <jserv.tw@gmail.com> Jim Huang  <jserv@biilabs.io>
 linD026     <shiyn.lin@gmail.com> linD026   <66012716+linD026@users.noreply.github.com>
 linD026     <shiyn.lin@gmail.com> linD026   <0086d026@email.ntou.edu.tw>
-linD026     <shiyn.lin@gmail.com> linzhien  <0086d026@email.ntou.edu.tw>
+linD026     <shiyn.lin@gmail.com> linzhien  <0086d026@email.ntou.edu.tw>
+mengxinayan <31788564+mengxinayan@users.noreply.github.com> 萌新阿岩 <31788564+mengxinayan@users.noreply.github.com>

+ 7 - 0
contrib.tex

@@ -3,9 +3,11 @@
 Arush Sharma,                  % <46960231+arushsharma24@users.noreply.github.com>
 asas1asas200,                  % <asas1asas200@gmail.com>
 Benno Bielmeier,               % <32938211+bbenno@users.noreply.github.com>
+Bob Lee,                       % <defru04002@gmail.com>
 Brad Baker,                    % <brad@brdbkr.com>
 ccs100203,                     % <ccs100203@gmail.com>
 Chih-Yu Chen,                  % <34228283+chihyu1206@users.noreply.github.com>
+Ching-Hua (Vivian) Lin,        % <jkrvivian@gmail.com>
 ChinYikMing,                   % <yikming2222@gmail.com>
 Cyril Brulebois,               % <cyril@debamax.com>
 Daniele Paolo Scarpazza,       % <>
@@ -22,10 +24,15 @@ Hsin-Hsiang Peng,              % <hsinspeng@gmail.com>
 Ignacio Martin,                % <>
 JianXing Wu,                   % <fdgkhdkgh@gmail.com>
 linD026,                       % <shiyn.lin@gmail.com>
+lyctw,                         % <lyctw.ee@gmail.com>
+manbing,                       % <manbing3@gmail.com>
 Marconi Jiang,                 % <marconi1964@yahoo.com>
+mengxinayan,                   % <31788564+mengxinayan@users.noreply.github.com>
 RinHizakura,                   % <s921975628@gmail.com>
 Roman Lakeev,                  % <>
 Stacy Prowell,                 % <sprowell@gmail.com>
+Steven Lung,                   % <1030steven@gmail.com>
+Tristan Lelong,                % <tristan.lelong@blunderer.org>
 Tucker Polomik,                % <tucker.polomik@inficon.com>
 VxTeemo,                       % <tcccvvv123@gmail.com>
 Wei-Lun Tsai,                  % <alan23273850@gmail.com>

+ 4 - 0
examples/.clang-format

@@ -56,6 +56,10 @@ DisableFormat: false
 ExperimentalAutoDetectBinPacking: false
 FixNamespaceComments: false
 
+ForEachMacros:
+  - 'list_for_each'
+  - 'list_for_each_safe'
+
 IncludeBlocks: Preserve
 IncludeCategories:
   - Regex: '.*'

+ 2 - 0
examples/Makefile

@@ -29,6 +29,8 @@ obj-m += example_atomic.o
 obj-m += example_mutex.o
 obj-m += bottomhalf.o
 obj-m += ioctl.o
+obj-m += vinput.o
+obj-m += vkbd.o
 
 PWD := $(CURDIR)
 

+ 398 - 0
examples/vinput.c

@@ -0,0 +1,398 @@
+#include <linux/cdev.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <asm/uaccess.h>
+
+#include "vinput.h"
+
+#define DRIVER_NAME "vinput"
+
+#define dev_to_vinput(dev) container_of(dev, struct vinput, dev)
+
+static DECLARE_BITMAP(vinput_ids, VINPUT_MINORS);
+
+static LIST_HEAD(vinput_devices);
+static LIST_HEAD(vinput_vdevices);
+
+static int vinput_dev;
+static struct spinlock vinput_lock;
+static struct class vinput_class;
+
+/* Search the name of vinput device in the vinput_devices linked list,
+ * which added at vinput_register().
+ */
+static struct vinput_device *vinput_get_device_by_type(const char *type)
+{
+    int found = 0;
+    struct vinput_device *vinput;
+    struct list_head *curr;
+
+    spin_lock(&vinput_lock);
+    list_for_each (curr, &vinput_devices) {
+        vinput = list_entry(curr, struct vinput_device, list);
+        if (vinput && strncmp(type, vinput->name, strlen(vinput->name)) == 0) {
+            found = 1;
+            break;
+        }
+    }
+    spin_unlock(&vinput_lock);
+
+    if (found)
+        return vinput;
+    return ERR_PTR(-ENODEV);
+}
+
+/* Search the id of virtual device in the vinput_vdevices linked list,
+ * which added at vinput_alloc_vdevice().
+ */
+static struct vinput *vinput_get_vdevice_by_id(long id)
+{
+    struct vinput *vinput = NULL;
+    struct list_head *curr;
+
+    spin_lock(&vinput_lock);
+    list_for_each (curr, &vinput_vdevices) {
+        vinput = list_entry(curr, struct vinput, list);
+        if (vinput && vinput->id == id)
+            break;
+    }
+    spin_unlock(&vinput_lock);
+
+    if (vinput && vinput->id == id)
+        return vinput;
+    return ERR_PTR(-ENODEV);
+}
+
+static int vinput_open(struct inode *inode, struct file *file)
+{
+    int err = 0;
+    struct vinput *vinput = NULL;
+
+    vinput = vinput_get_vdevice_by_id(iminor(inode));
+
+    if (IS_ERR(vinput))
+        err = PTR_ERR(vinput);
+    else
+        file->private_data = vinput;
+
+    return err;
+}
+
+static int vinput_release(struct inode *inode, struct file *file)
+{
+    return 0;
+}
+
+static ssize_t vinput_read(struct file *file, char __user *buffer, size_t count,
+                           loff_t *offset)
+{
+    int len;
+    char buff[VINPUT_MAX_LEN + 1];
+    struct vinput *vinput = file->private_data;
+
+    len = vinput->type->ops->read(vinput, buff, count);
+
+    if (*offset > len)
+        count = 0;
+    else if (count + *offset > VINPUT_MAX_LEN)
+        count = len - *offset;
+
+    if (raw_copy_to_user(buffer, buff + *offset, count))
+        count = -EFAULT;
+
+    *offset += count;
+
+    return count;
+}
+
+static ssize_t vinput_write(struct file *file, const char __user *buffer,
+                            size_t count, loff_t *offset)
+{
+    char buff[VINPUT_MAX_LEN + 1];
+    struct vinput *vinput = file->private_data;
+
+    memset(buff, 0, sizeof(char) * (VINPUT_MAX_LEN + 1));
+
+    if (count > VINPUT_MAX_LEN) {
+        dev_warn(&vinput->dev, "Too long. %d bytes allowed\n", VINPUT_MAX_LEN);
+        return -EINVAL;
+    }
+
+    if (raw_copy_from_user(buff, buffer, count))
+        return -EFAULT;
+
+    return vinput->type->ops->send(vinput, buff, count);
+}
+
+static const struct file_operations vinput_fops = {
+    .owner = THIS_MODULE,
+    .open = vinput_open,
+    .release = vinput_release,
+    .read = vinput_read,
+    .write = vinput_write,
+};
+
+static void vinput_unregister_vdevice(struct vinput *vinput)
+{
+    input_unregister_device(vinput->input);
+    if (vinput->type->ops->kill)
+        vinput->type->ops->kill(vinput);
+}
+
+static void vinput_destroy_vdevice(struct vinput *vinput)
+{
+    /* Remove from the list first */
+    spin_lock(&vinput_lock);
+    list_del(&vinput->list);
+    clear_bit(vinput->id, vinput_ids);
+    spin_unlock(&vinput_lock);
+
+    module_put(THIS_MODULE);
+
+    kfree(vinput);
+}
+
+static void vinput_release_dev(struct device *dev)
+{
+    struct vinput *vinput = dev_to_vinput(dev);
+    int id = vinput->id;
+
+    vinput_destroy_vdevice(vinput);
+
+    pr_debug("released vinput%d.\n", id);
+}
+
+static struct vinput *vinput_alloc_vdevice(void)
+{
+    int err;
+    struct vinput *vinput = kzalloc(sizeof(struct vinput), GFP_KERNEL);
+
+    try_module_get(THIS_MODULE);
+
+    memset(vinput, 0, sizeof(struct vinput));
+
+    spin_lock_init(&vinput->lock);
+
+    spin_lock(&vinput_lock);
+    vinput->id = find_first_zero_bit(vinput_ids, VINPUT_MINORS);
+    if (vinput->id >= VINPUT_MINORS) {
+        err = -ENOBUFS;
+        goto fail_id;
+    }
+    set_bit(vinput->id, vinput_ids);
+    list_add(&vinput->list, &vinput_vdevices);
+    spin_unlock(&vinput_lock);
+
+    /* allocate the input device */
+    vinput->input = input_allocate_device();
+    if (vinput->input == NULL) {
+        pr_err("vinput: Cannot allocate vinput input device\n");
+        err = -ENOMEM;
+        goto fail_input_dev;
+    }
+
+    /* initialize device */
+    vinput->dev.class = &vinput_class;
+    vinput->dev.release = vinput_release_dev;
+    vinput->dev.devt = MKDEV(vinput_dev, vinput->id);
+    dev_set_name(&vinput->dev, DRIVER_NAME "%lu", vinput->id);
+
+    return vinput;
+
+fail_input_dev:
+    spin_lock(&vinput_lock);
+    list_del(&vinput->list);
+fail_id:
+    spin_unlock(&vinput_lock);
+    module_put(THIS_MODULE);
+    kfree(vinput);
+
+    return ERR_PTR(err);
+}
+
+static int vinput_register_vdevice(struct vinput *vinput)
+{
+    int err = 0;
+
+    /* register the input device */
+    vinput->input->name = vinput->type->name;
+    vinput->input->phys = "vinput";
+    vinput->input->dev.parent = &vinput->dev;
+
+    vinput->input->id.bustype = BUS_VIRTUAL;
+    vinput->input->id.product = 0x0000;
+    vinput->input->id.vendor = 0x0000;
+    vinput->input->id.version = 0x0000;
+
+    err = vinput->type->ops->init(vinput);
+
+    if (err == 0)
+        dev_info(&vinput->dev, "Registered virtual input %s %ld\n",
+                 vinput->type->name, vinput->id);
+
+    return err;
+}
+
+static ssize_t export_store(struct class *class, struct class_attribute *attr,
+                            const char *buf, size_t len)
+{
+    int err;
+    struct vinput *vinput;
+    struct vinput_device *device;
+
+    device = vinput_get_device_by_type(buf);
+    if (IS_ERR(device)) {
+        pr_info("vinput: This virtual device isn't registered\n");
+        err = PTR_ERR(device);
+        goto fail;
+    }
+
+    vinput = vinput_alloc_vdevice();
+    if (IS_ERR(vinput)) {
+        err = PTR_ERR(vinput);
+        goto fail;
+    }
+
+    vinput->type = device;
+    err = device_register(&vinput->dev);
+    if (err < 0)
+        goto fail_register;
+
+    err = vinput_register_vdevice(vinput);
+    if (err < 0)
+        goto fail_register_vinput;
+
+    return len;
+
+fail_register_vinput:
+    device_unregister(&vinput->dev);
+fail_register:
+    vinput_destroy_vdevice(vinput);
+fail:
+    return err;
+}
+/* This macro generates class_attr_export structure and export_store() */
+static CLASS_ATTR_WO(export);
+
+static ssize_t unexport_store(struct class *class, struct class_attribute *attr,
+                              const char *buf, size_t len)
+{
+    int err;
+    unsigned long id;
+    struct vinput *vinput;
+
+    err = kstrtol(buf, 10, &id);
+    if (err) {
+        err = -EINVAL;
+        goto failed;
+    }
+
+    vinput = vinput_get_vdevice_by_id(id);
+    if (IS_ERR(vinput)) {
+        pr_err("vinput: No such vinput device %ld\n", id);
+        err = PTR_ERR(vinput);
+        goto failed;
+    }
+
+    vinput_unregister_vdevice(vinput);
+    device_unregister(&vinput->dev);
+
+    return len;
+failed:
+    return err;
+}
+/* This macro generates class_attr_unexport structure and unexport_store() */
+static CLASS_ATTR_WO(unexport);
+
+static struct attribute *vinput_class_attrs[] = {
+    &class_attr_export.attr,
+    &class_attr_unexport.attr,
+    NULL,
+};
+
+/* This macro generates vinput_class_groups structure */
+ATTRIBUTE_GROUPS(vinput_class);
+
+static struct class vinput_class = {
+    .name = "vinput",
+    .owner = THIS_MODULE,
+    .class_groups = vinput_class_groups,
+};
+
+int vinput_register(struct vinput_device *dev)
+{
+    spin_lock(&vinput_lock);
+    list_add(&dev->list, &vinput_devices);
+    spin_unlock(&vinput_lock);
+
+    pr_info("vinput: registered new virtual input device '%s'\n", dev->name);
+
+    return 0;
+}
+EXPORT_SYMBOL(vinput_register);
+
+void vinput_unregister(struct vinput_device *dev)
+{
+    struct list_head *curr, *next;
+
+    /* Remove from the list first */
+    spin_lock(&vinput_lock);
+    list_del(&dev->list);
+    spin_unlock(&vinput_lock);
+
+    /* unregister all devices of this type */
+    list_for_each_safe (curr, next, &vinput_vdevices) {
+        struct vinput *vinput = list_entry(curr, struct vinput, list);
+        if (vinput && vinput->type == dev) {
+            vinput_unregister_vdevice(vinput);
+            device_unregister(&vinput->dev);
+        }
+    }
+
+    pr_info("vinput: unregistered virtual input device '%s'\n", dev->name);
+}
+EXPORT_SYMBOL(vinput_unregister);
+
+static int __init vinput_init(void)
+{
+    int err = 0;
+
+    pr_info("vinput: Loading virtual input driver\n");
+
+    vinput_dev = register_chrdev(0, DRIVER_NAME, &vinput_fops);
+    if (vinput_dev < 0) {
+        pr_err("vinput: Unable to allocate char dev region\n");
+        goto failed_alloc;
+    }
+
+    spin_lock_init(&vinput_lock);
+
+    err = class_register(&vinput_class);
+    if (err < 0) {
+        pr_err("vinput: Unable to register vinput class\n");
+        goto failed_class;
+    }
+
+    return 0;
+failed_class:
+    class_unregister(&vinput_class);
+failed_alloc:
+    return err;
+}
+
+static void __exit vinput_end(void)
+{
+    pr_info("vinput: Unloading virtual input driver\n");
+
+    unregister_chrdev(vinput_dev, DRIVER_NAME);
+    class_unregister(&vinput_class);
+}
+
+module_init(vinput_init);
+module_exit(vinput_end);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Emulate input events");

+ 45 - 0
examples/vinput.h

@@ -0,0 +1,45 @@
+#ifndef VINPUT_H
+#define VINPUT_H
+
+#include <linux/input.h>
+#include <linux/spinlock.h>
+
+#define VINPUT_MAX_LEN 128
+#define MAX_VINPUT 32
+#define VINPUT_MINORS MAX_VINPUT
+
+#define dev_to_vinput(dev) container_of(dev, struct vinput, dev)
+
+struct vinput_device;
+
+struct vinput {
+    long id;
+    long devno;
+    long last_entry;
+    spinlock_t lock;
+
+    void *priv_data;
+
+    struct device dev;
+    struct list_head list;
+    struct input_dev *input;
+    struct vinput_device *type;
+};
+
+struct vinput_ops {
+    int (*init)(struct vinput *);
+    int (*kill)(struct vinput *);
+    int (*send)(struct vinput *, char *, int);
+    int (*read)(struct vinput *, char *, int);
+};
+
+struct vinput_device {
+    char name[16];
+    struct list_head list;
+    struct vinput_ops *ops;
+};
+
+int vinput_register(struct vinput_device *dev);
+void vinput_unregister(struct vinput_device *dev);
+
+#endif

+ 106 - 0
examples/vkbd.c

@@ -0,0 +1,106 @@
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+
+#include "vinput.h"
+
+#define VINPUT_KBD "vkbd"
+#define VINPUT_RELEASE 0
+#define VINPUT_PRESS 1
+
+static unsigned short vkeymap[KEY_MAX];
+
+static int vinput_vkbd_init(struct vinput *vinput)
+{
+    int i;
+
+    /* Set up the input bitfield */
+    vinput->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+    vinput->input->keycodesize = sizeof(unsigned short);
+    vinput->input->keycodemax = KEY_MAX;
+    vinput->input->keycode = vkeymap;
+
+    for (i = 0; i < KEY_MAX; i++)
+        set_bit(vkeymap[i], vinput->input->keybit);
+
+    /* vinput will help us allocate new input device structure via
+     * input_allocate_device(). So, we can register it straightforwardly.
+     */
+    return input_register_device(vinput->input);
+}
+
+static int vinput_vkbd_read(struct vinput *vinput, char *buff, int len)
+{
+    spin_lock(&vinput->lock);
+    len = snprintf(buff, len, "%+ld\n", vinput->last_entry);
+    spin_unlock(&vinput->lock);
+
+    return len;
+}
+
+static int vinput_vkbd_send(struct vinput *vinput, char *buff, int len)
+{
+    int ret;
+    long key = 0;
+    short type = VINPUT_PRESS;
+
+    /* Determine which event was received (press or release)
+     * and store the state.
+     */
+    if (buff[0] == '+')
+        ret = kstrtol(buff + 1, 10, &key);
+    else
+        ret = kstrtol(buff, 10, &key);
+    if (ret)
+        dev_err(&vinput->dev, "error during kstrtol: -%d\n", ret);
+    spin_lock(&vinput->lock);
+    vinput->last_entry = key;
+    spin_unlock(&vinput->lock);
+
+    if (key < 0) {
+        type = VINPUT_RELEASE;
+        key = -key;
+    }
+
+    dev_info(&vinput->dev, "Event %s code %ld\n",
+             (type == VINPUT_RELEASE) ? "VINPUT_RELEASE" : "VINPUT_PRESS", key);
+
+    /* Report the state received to input subsystem. */
+    input_report_key(vinput->input, key, type);
+    /* Tell input subsystem that it finished the report. */
+    input_sync(vinput->input);
+
+    return len;
+}
+
+static struct vinput_ops vkbd_ops = {
+    .init = vinput_vkbd_init,
+    .send = vinput_vkbd_send,
+    .read = vinput_vkbd_read,
+};
+
+static struct vinput_device vkbd_dev = {
+    .name = VINPUT_KBD,
+    .ops = &vkbd_ops,
+};
+
+static int __init vkbd_init(void)
+{
+    int i;
+
+    for (i = 0; i < KEY_MAX; i++)
+        vkeymap[i] = i;
+    return vinput_register(&vkbd_dev);
+}
+
+static void __exit vkbd_end(void)
+{
+    vinput_unregister(&vkbd_dev);
+}
+
+module_init(vkbd_init);
+module_exit(vkbd_end);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Emulate keyboard input events through /dev/vinput");

+ 82 - 0
lkmpg.tex

@@ -1857,6 +1857,88 @@ Here is an example of symmetrically encrypting a string using the AES algorithm
 
 \samplec{examples/cryptosk.c}
 
+\section{Virtual Input Device Driver}
+\label{sec:vinput}
+The input device driver is a module that provides a way to communicate with the interaction device via the event.
+For example, the keyboard can send the press or release event to tell the kernel what we want to do.
+The input device driver will allocate a new input structure with \cpp|input_allocate_device()| and sets up input bitfields, device id, version, etc.
+After that, registers it by calling \cpp|input_register_device()|.
+
+Here is an example, vinput,
+It is an API to allow easy development of virtual input drivers.
+The drivers needs to export a \cpp|vinput_device()| that contains the virtual device name and \cpp|vinput_ops| structure that describes:
+
+\begin{itemize}
+    \item the init function: \cpp|init()|
+    \item the input event injection function: \cpp|send()|
+    \item the readback function: \cpp|read()|
+\end{itemize}
+
+Then using \cpp|vinput_register_device()| and \cpp|vinput_unregister_device()| will add a new device to the list of support virtual input devices.
+
+\begin{code}
+int init(struct vinput *);
+\end{code}
+
+This function is passed a \cpp|struct vinput| already initialized with an allocated \cpp|struct input_dev|.
+The \cpp|init()| function is responsible for initializing the capabilities of the input device and register it.
+
+\begin{code}
+int send(struct vinput *, char *, int);
+\end{code}
+
+This function will receive a user string to interpret and inject the event using the \cpp|input_report_XXXX| or \cpp|input_event| call.
+The string is already copied from user.
+
+\begin{code}
+int read(struct vinput *, char *, int);
+\end{code}
+
+This function is used for debugging and should fill the buffer parameter with the last event sent in the virtual input device format.
+The buffer will then be copied to user.
+
+vinput devices are created and destroyed using sysfs.
+And, event injection is done through a \verb|/dev| node.
+The device name will be used by the userland to export a new virtual input device.
+To create a \verb|vinputX| sysfs entry and \verb|/dev| node.
+
+\begin{codebash}
+echo "vkbd" | sudo tee /sys/class/vinput/export
+\end{codebash}
+
+To unexport the device, just echo its id in unexport:
+
+\begin{codebash}
+echo "0" | sudo tee /sys/class/vinput/unexport
+\end{codebash}
+
+\samplec{examples/vinput.h}
+\samplec{examples/vinput.c}
+
+Here the virtual keyboard is one of example to use vinput.
+It supports all \cpp|KEY_MAX| keycodes.
+The injection format is the \cpp|KEY_CODE| such as defined in \src{include/linux/input.h}.
+A positive value means \cpp|KEY_PRESS| while a negative value is a \cpp|KEY_RELEASE|.
+The keyboard supports repetition when the key stays pressed for too long.
+The following demonstrates how simulation work.
+
+Simulate a key press on "g" (\cpp|KEY_G| = 34):
+
+\begin{codebash}
+echo "+34" | sudo tee /dev/vinput0
+\end{codebash}
+
+Simulate a key release on "g" (\cpp|KEY_G| = 34:)
+
+\begin{codebash}
+echo "-34" | sudo tee /dev/vinput0
+\end{codebash}
+
+\samplec{examples/vkbd.c}
+
+% TODO: Add description of attribute
+% TODO: Add vts.c and vmouse.c example
+
 \section{Standardizing the interfaces: The Device Model}
 \label{sec:device_model}
 Up to this point we have seen all kinds of modules doing all kinds of things, but there was no consistency in their interfaces with the rest of the kernel.

+ 7 - 0
scripts/Contributors

@@ -3,9 +3,11 @@
 Arush Sharma,<46960231+arushsharma24@users.noreply.github.com>
 asas1asas200,<asas1asas200@gmail.com>
 Benno Bielmeier,<32938211+bbenno@users.noreply.github.com>
+Bob Lee,<defru04002@gmail.com>
 Brad Baker,<brad@brdbkr.com>
 ccs100203,<ccs100203@gmail.com>
 Chih-Yu Chen,<34228283+chihyu1206@users.noreply.github.com>
+Ching-Hua (Vivian) Lin,<jkrvivian@gmail.com>
 ChinYikMing,<yikming2222@gmail.com>
 Cyril Brulebois,<cyril@debamax.com>
 Daniele Paolo Scarpazza,<>
@@ -22,10 +24,15 @@ Hsin-Hsiang Peng,<hsinspeng@gmail.com>
 Ignacio Martin,<>
 JianXing Wu,<fdgkhdkgh@gmail.com>
 linD026,<shiyn.lin@gmail.com>,<0086d026@email.ntou.edu.tw>,<66012716+linD026@users.noreply.github.com>
+lyctw,<lyctw.ee@gmail.com>
+manbing,<manbing3@gmail.com>
 Marconi Jiang,<marconi1964@yahoo.com>
+mengxinayan,<31788564+mengxinayan@users.noreply.github.com>
 RinHizakura,<s921975628@gmail.com>
 Roman Lakeev,<>
 Stacy Prowell,<sprowell@gmail.com>
+Steven Lung,<1030steven@gmail.com>
+Tristan Lelong,<tristan.lelong@blunderer.org>
 Tucker Polomik,<tucker.polomik@inficon.com>
 VxTeemo,<tcccvvv123@gmail.com>
 Wei-Lun Tsai,<alan23273850@gmail.com>

+ 2 - 1
scripts/Include

@@ -4,4 +4,5 @@ Dimo Velev,<>
 Francois Audeon,<>
 Horst Schirmeier,<>
 Ignacio Martin,<>
-Roman Lakeev,<>
+Roman Lakeev,<>
+Tristan Lelong,<tristan.lelong@blunderer.org>