linux内核input子系统概述

目录

  • 一、input子系统
  • 二、关键数据结构和api
    • 2.1 数据结构
      • 2.1.1 input_dev
      • 2.1.2 input_handler
      • 2.1.3 input_event
      • 2.1.4 input_handle
    • 2.2 api接口
      • 2.2.1 input_device 相关接口
        • input_device 注册流程
        • 事件上报
      • 2.2.2 input handle 相关接口
        • 注册 handle
        • 指定 handle
      • 2.2.3 input handler 相关接口
        • 注册handler
  • 三、input handler
    • 3.1 evdev handler
      • 3.1.1 handler 注册
      • 3.1.2 evdev_connect
      • 3.1.3 evdev_events
      • 3.1.4 file_operations
    • 3.2 mousedev handler

一、input子系统

input子系统处理Linux下的输入事件。

驱动层:输入设备的驱动程序,负责检测和接收输入设备的输入事件,将输入事件上报给核心层;
核心层:提供设备驱动、事件 handler 注册和操作的接口;接收驱动层的输入事件并上报给事件处理层;
事件处理层:通过提供 sysfs 接口等方式和用户空间交互,例如用户空间打开特定设备,当有输入数据时就会上传给用户空间。

input子系统框架结构图(总结来自这里):
input子系统框架结构图

input driver 接收到硬件的输入事件 ==> 发送到input core,input core 根据事件类型 ==> 将事件交给对应的input handler处理 ==> input handler 上报用户空间,用户空间收收到事件后进行对应的处理。

二、关键数据结构和api

2.1 数据结构

2.1.1 input_dev

input_dev 描述输入设备,结构体中的多个 bitmap 描述了输入设备的类型和支持的输入事件。这些事件类型相关的宏定义在 input-event-codes.h 头文件中。

struct input_dev {
	const char *name;
	const char *phys;
	const char *uniq;
	struct input_id id;

	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];    // 设备支持的事件类型的bitmap
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];  // 设备支持的按键类型
	unsigned long relbit[BITS_TO_LONGS(REL_CNT)];  // 设备支持的相对坐标事件
	unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];  // 设备支持的绝对坐标事件
	unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];  // 设备支持的杂项事件
	unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];  // led
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];  // 声音
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];    // 压力反馈事件
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];    // 开关

	unsigned int hint_events_per_packet;

	unsigned int keycodemax;
	unsigned int keycodesize;
	void *keycode;

	int (*setkeycode)(struct input_dev *dev,
			  const struct input_keymap_entry *ke,
			  unsigned int *old_keycode);
	int (*getkeycode)(struct input_dev *dev,
			  struct input_keymap_entry *ke);

	struct ff_device *ff;

	struct input_dev_poller *poller;

	unsigned int repeat_key;
	struct timer_list timer;

	int rep[REP_CNT];

	struct input_mt *mt;

	struct input_absinfo *absinfo;

	unsigned long key[BITS_TO_LONGS(KEY_CNT)];
	unsigned long led[BITS_TO_LONGS(LED_CNT)];
	unsigned long snd[BITS_TO_LONGS(SND_CNT)];
	unsigned long sw[BITS_TO_LONGS(SW_CNT)];

	int (*open)(struct input_dev *dev);
	void (*close)(struct input_dev *dev);
	int (*flush)(struct input_dev *dev, struct file *file);
	int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

	struct input_handle __rcu *grab;

	spinlock_t event_lock;
	struct mutex mutex;

	unsigned int users;
	bool going_away;

	struct device dev;

	struct list_head	h_list;
	struct list_head	node;

	unsigned int num_vals;
	unsigned int max_vals;
	struct input_value *vals;

	bool devres_managed;

	ktime_t timestamp[INPUT_CLK_MAX];
};

2.1.2 input_handler

input_handler 提供了对一类设备输入事件处理的接口。

struct input_handler {

	void *private;

	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	void (*events)(struct input_handle *handle,
		       const struct input_value *vals, unsigned int count);
	bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	bool (*match)(struct input_handler *handler, struct input_dev *dev);
	int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
	void (*disconnect)(struct input_handle *handle);
	void (*start)(struct input_handle *handle);

	bool legacy_minors;
	int minor;
	const char *name;

	const struct input_device_id *id_table;

	struct list_head	h_list;
	struct list_head	node;
};

以 evdev handler 为例,

connect 接口,通过 input_register_handle 接口,实现将 input_dev 和 input_handler 绑定,并创建对应输入设备的字符设备;
event/events 接口,将设备的输入拷贝到 buffer 中,当用户空间调用字符设备的 read 接口时,就可以从 buffer 中读取输入信息;

2.1.3 input_event

handler 上报事件到用户层的时候,以 input_event 格式进行上报。

struct input_event {
	struct timeval time;  // 事件发生事件
	__u16 type;           // 事件类型,例如 EV_KEY 按键类型
	__u16 code;           // 事件编码,例如 KEY_0 按键
	__s32 value;          // 事件值
};

2.1.4 input_handle

input_handle 实现将 input_device 和 input_handler 绑定的功能,上面已经介绍到,evdev handler 的 connect 接口中,会调用 input_register_handle 接口,实现将 input_dev 和 input_handler 绑定。

struct input_handle {

	void *private;

	int open;  // 当前handle是否open
	const char *name;

	struct input_dev *dev;
	struct input_handler *handler;

	struct list_head	d_node;
	struct list_head	h_node;
};

open 记录了当前 handle 是否被 open,以 evdev 为例,当用户空间 open 字符设备的时候,会调用到input_open_device 接口,接口内部实现 input_handle->open++。

2.2 api接口

2.2.1 input_device 相关接口

input核心层提供了如下一系列input device相关的接口,事件input device的注册、事件的上报等功能:

// 申请
struct input_dev *input_allocate_device(void);
struct input_dev *devm_input_allocate_device(struct device *dev);

// 设置支持的事件类型
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)// 注册、注销
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);

// 释放
void input_free_device(struct input_dev *dev);

// 事件上报
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value);
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value);
static inline void input_sync(struct input_dev *dev);  // 同步通知事件发送完成
static inline void input_mt_sync(struct input_dev *dev);
input_device 注册流程

注册接口中主要做了以下动作:

  1. 检查 bitmap 等参数设置是否正确;
  2. 将 device 添加到 input_device_list 链表中;
  3. 对于 input_handler_list 中的每一个 handler,调用 input_attach_handler 接口尝试将 device 和 handler 绑定,在接口内部会检查 device 和 handler 是否 match,match 的话则调用 handler 的 connect 接口完成绑定动作。
int input_register_device(struct input_dev *dev)
{

    // 检查bitmap等参数、配置input_dev部分参数
	/* Every input device generates EV_SYN/SYN_REPORT events. */
	__set_bit(EV_SYN, dev->evbit);

	/* KEY_RESERVED is not supposed to be transmitted to userspace. */
	__clear_bit(KEY_RESERVED, dev->keybit);

	/* Make sure that bitmasks not mentioned in dev->evbit are clean. */
	input_cleanse_bitmasks(dev);


	dev->max_vals = dev->hint_events_per_packet + 2;
	dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);

    // device_add
	error = device_add(&dev->dev);

    // device 和 handler绑定
	error = mutex_lock_interruptible(&input_mutex);
	list_add_tail(&dev->node, &input_dev_list);

	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);

	input_wakeup_procfs_readers();

	mutex_unlock(&input_mutex);

	if (dev->devres_managed) {
		dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",
			__func__, dev_name(&dev->dev));
		devres_add(dev->dev.parent, devres);
	}
	return 0;
}
EXPORT_SYMBOL(input_register_device);
事件上报

input 子系统中封装了针对不同类型事件的上报接口,例如 input_report_key\input_report_abs 等,这些接口实际都是调用 input_event 接口完成事件上报,只不过接口参数中的 type 类型不同,以 input_report_key 为例:

input_report_key(struct input_dev *dev, unsigned int code, int value)
	-> input_event(dev, EV_KEY, code, !!value);
        -> input_handle_event(dev, type, code, value);
            -> input_get_disposition(dev, type, code, &value); // 获取事件类型
            -> input_pass_values(dev, dev->vals, dev->num_vals);  
                -> input_to_handler(handle, vals, count);
                    -> handler->events(handle, vals, count); // 通知handler处理事件

在 input_handle_event 接口中,会将事件缓存在 dev->vals 中,并记录事件数目到 dev-num_vals,当检测到 dev->num_vals >= dev->max_vals - 2 或者 input_sync 事件时,将所有缓存事件通知 handler 处理。

static void input_handle_event(struct input_dev *dev,
			       unsigned int type, unsigned int code, int value)
{
    // 获取事件类型
	int disposition = input_get_disposition(dev, type, code, &value);

	if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
		add_input_randomness(type, code, value);

    // 如果 INPUT_PASS_TO_DEVICE并且device实现了event,则通知device
	if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
		dev->event(dev, type, code, value);

	if (!dev->vals)
		return;

    // 记录要通知给handler的事件
	if (disposition & INPUT_PASS_TO_HANDLERS) {
		struct input_value *v;

		if (disposition & INPUT_SLOT) {
			v = &dev->vals[dev->num_vals++];
			v->type = EV_ABS;
			v->code = ABS_MT_SLOT;
			v->value = dev->mt->slot;
		}

		v = &dev->vals[dev->num_vals++];
		v->type = type;
		v->code = code;
		v->value = value;
	}

    // sync事件或者要超出缓存,则将缓存的vals flush到handler
	if (disposition & INPUT_FLUSH) {
		if (dev->num_vals >= 2)
			input_pass_values(dev, dev->vals, dev->num_vals);
		dev->num_vals = 0;
		dev->timestamp[INPUT_CLK_MONO] = ktime_set(0, 0);
	} else if (dev->num_vals >= dev->max_vals - 2) {
		dev->vals[dev->num_vals++] = input_value_sync;
		input_pass_values(dev, dev->vals, dev->num_vals);
		dev->num_vals = 0;
	}

}

2.2.2 input handle 相关接口

int input_register_handle(struct input_handle *handle);
void input_unregister_handle(struct input_handle *handle);
注册 handle

input_register_handle 接口实现注册一个input_handle,将 device 和 handler 绑定,例如在 evdev handler 的 connect 接口中,就调用了 input_register_handle 接口。
接口流程:

int input_register_handle(struct input_handle *handle)
{

    // 将 handle->d_node 加入到 dev->h_list,实现遍历dev->h_list就能找到所有关联的input_handle,进而找到input_handler
	if (handler->filter)
		list_add_rcu(&handle->d_node, &dev->h_list);
	else
		list_add_tail_rcu(&handle->d_node, &dev->h_list);

    // 将 handle->h_node 加入到 handler->h_list,实现遍历handler->h_list就能找到所有关联的input_handler,进而找到input_device
	list_add_tail_rcu(&handle->h_node, &handler->h_list);

	if (handler->start)
		handler->start(handle);

	return 0;
}

在 input_register_handle 接口中,会将 handle->d_node 加入到 dev->h_list,实现遍历dev->h_list就能找到所有关联的input_handle,进而找到input_handler。
实际上在 input_pass_values 中,如果未指定 input_device 的 input_handle, 就是通过遍历列表的方式,将事件通过所有关联的 input_handle 发送到 input_handler 中。
也就是说默认input_event的事件上报是一个广播行为:

	handle = rcu_dereference(dev->grab);
	if (handle) {
		count = input_to_handler(handle, vals, count);  // 指定handle
	} else {
		list_for_each_entry_rcu(handle, &dev->h_list, d_node)   // 广播
			if (handle->open) {
				count = input_to_handler(handle, vals, count);
				if (!count)
					break;
			}
	}
指定 handle

在 input_grab_device 接口中,实现了 dev->grab 与 handle 的绑定:

int input_grab_device(struct input_handle *handle)
{
	if (dev->grab) {
		retval = -EBUSY;
		goto out;
	}
	rcu_assign_pointer(dev->grab, handle);
}

以 evdev handler 为例,ioctl 中实现了 EVIOCGRAB,用于 input_device 指定 input_handle:

// ioctl接口中,调用evdev_grab或evdev_ungrab事件绑定和解绑:
	case EVIOCGRAB:
		if (p)
			return evdev_grab(evdev, client);
		else
			return evdev_ungrab(evdev, client);

// evcev_grab中调用 input_grab_device 实现 dev->grab 与 handle 的绑定
static int evdev_grab(struct evdev *evdev, struct evdev_client *client)
{
	int error;

	if (evdev->grab)
		return -EBUSY;

	error = input_grab_device(&evdev->handle);
	if (error)
		return error;

	rcu_assign_pointer(evdev->grab, client);

	return 0;
}

2.2.3 input handler 相关接口

// 注册、注销
int input_register_handler(struct input_handler *handler);
void input_unregister_handler(struct input_handler *handler);
注册handler

input_register_handler 接口中,将 handler 添加到 input_handler_list 中,遍历 input_dev_list,执行input_attach_handler(dev, handler):

int input_register_handler(struct input_handler *handler)
{
	INIT_LIST_HEAD(&handler->h_list);

	list_add_tail(&handler->node, &input_handler_list);

	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler);

	return 0;
}

三、input handler

3.1 evdev handler

3.1.1 handler 注册

在evdev_init中调用 input_register_handler 实现 handler 的注册。

static struct input_handler evdev_handler = {
	.event		= evdev_event,
	.events		= evdev_events,
	.connect	= evdev_connect,
	.disconnect	= evdev_disconnect,
	.legacy_minors	= true,
	.minor		= EVDEV_MINOR_BASE,
	.name		= "evdev",
	.id_table	= evdev_ids,
};

static int __init evdev_init(void)
{
	return input_register_handler(&evdev_handler);
}

3.1.2 evdev_connect

evdev_connect 接口在 input_attach_handler 中被调用,接口实现以下功能:

  • 以 evdev_fops 为 file_operations 创建 cdev,设备名称为 event%d;
  • 调用 input_register_handle 实现 input_device 与 input_handler 的绑定;
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{

	minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);

	INIT_LIST_HEAD(&evdev->client_list);

	dev_no = minor;
	dev_set_name(&evdev->dev, "event%d", dev_no);

	evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
	evdev->dev.class = &input_class;
	evdev->dev.parent = &dev->dev;
	evdev->dev.release = evdev_free;
	device_initialize(&evdev->dev);

	error = input_register_handle(&evdev->handle);

	cdev_init(&evdev->cdev, &evdev_fops);

	error = cdev_device_add(&evdev->cdev, &evdev->dev);

}

3.1.3 evdev_events

evdev_events 接口负责处理 input_device 上报的事件,并上报给用户层:

handler->events(handle, vals, count); // evdev_events,读时间
    --> evdev_pass_values(client, vals, count, ev_time);  // 组input_event
        --> __pass_event(client, &event);  // 将event存在evdev_client的buffer中
            --> kill_fasync(&client->fasync, SIGIO, POLL_IN); // 异步信号通知用户层
        --> wake_up_interruptible(&evdev->wait);  // 唤醒等待队列

3.1.4 file_operations

evdev handler 的 file_operations 提供了 fasync\poll\read 等接口,供用户层读取 input event。

static const struct file_operations evdev_fops = {
	.owner		= THIS_MODULE,
	.read		= evdev_read,
	.write		= evdev_write,
	.poll		= evdev_poll,
	.open		= evdev_open,
	.release	= evdev_release,
	.unlocked_ioctl	= evdev_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= evdev_ioctl_compat,
#endif
	.fasync		= evdev_fasync,
	.flush		= evdev_flush,
	.llseek		= no_llseek,
};

evdev_fasync接口实现异步通知处理函数,当有input_event事件时,在evdev_events接口中,最终会调用 kill_fasync实现发送异步通知信号,用户层接收到状态变化后,可知晓有input_event事件需要处理。
evdev_read接口为用户空间提供了读input_event事件的接口,实际是将evdev_events接口中缓存在buffer中的数据copy到用户空间。当缓存中没有数据是,调用wait_event_interruptible 等待 evdev_events 唤醒。

3.2 mousedev handler

TODO

参考链接:https://www.cnblogs.com/arnoldlu/p/17952329

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/474941.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[隐私计算实训营学习笔记] 第1讲 数据要素流通

信任四基石 数据的分级分类 技术信任:全链路审计、闭环完成的数据可信流通体系 技术信任:开启数据密态时代 数据可流通的基础设施:密态天空计算

第3章 数据治理

思维导图 数据治理的定义:是在管理数据资产过程中行使权力和管控,包括计划、监控、和实施。 职能:指导所有其他数据管理领域的活动。目的:确保根据数据管理制度和最佳实践正确地管理数据。整体驱动力:确保组织可以从其…

sd卡数据不小心删除了如何恢复,sd卡中的数据不小心被删除了如何进行恢复

在现代科技快速发展的时代,SD卡已经成为我们存储和传输数据的重要工具之一。当您不小心删除了SD卡中的数据时,这种意外情况可能引起您的困惑和焦虑。那些重要的文件、无价的回忆似乎在转瞬间消失得无影无踪。面对这种突发的数据丢失问题,我深感理解。sd卡数据不小心删除了如…

Mac上轻松几步搞定Docker与Redis安装:从下载安装到容器运行实测全程指南

1、去官网下载docker 安装:把图标拉到应用程序即可把图标拉到应用程序即可 https://docs.docker.com/desktop/install/mac-install/ 2、docker拉取redis镜像 拉取命令,后面填上版本号3.2.1,可以看到已经成功了。 docker pull redis:3.2.1…

Guitar Pro8吉他学习 、打谱 、 创作神器,让你的吉他之路更上一层楼!

Guitar Pro8吉他学习 、打谱 、 创作神器,让你的吉他之路更上一层楼!轻松学习吉他,实现音乐梦想,Guitar Pro8助你一臂之力! Guitar Pro 2024 win-安装包下载如下: https://wm.makeding.com/iclk/?zoneid5…

u盘数据删除或者移除了怎么办?冷静,恢复指南来帮你

在数字化时代,U盘已成为我们存储和传输数据的重要设备。然而,有时由于疏忽或误操作,我们可能会不小心删除或移除了U盘上的重要数据。面对这种情况,许多人可能会感到焦虑和困惑,不知道如何是好。本文将为您提供一些建议…

【Unity】宏定义Scripting Define Symbols

1.宏的用处 我们在使用Unity开发的时候,经常需要根据不同环境执行不同的代码 比如安卓手机和苹果手机获取路径代码 这个时候,宏就派上用场了。 代码示例: //获取路径public string GtePath(){//不同平台,取不同的存储路径string…

java 项目新建遇到的问题

IntelliJ IDEA创建Spring工程 报错1:Selected version of Java 17 is not supported by the project SDK ‘1.8’. Either choose a lower version of Java, or set a higher version of the SDK. 解决方法: 报错2:Cannot download ‘htt…

电脑照片分辨率怎么调?这款dpi修改工具好用

许多考试平台在上传证件照片的时候,大多都会对图片分辨率有具体要求,但是如果遇上手上的图片分辨率达不到要求,那么怎么改图片分辨率呢?可以利用专业的dpi修改工具来处理,比如今天分享的就是一个在线修改图片分辨率的方…

右键菜单事件

<div id"editor-container"></div> <div class"custom-context-menu" id"customContextMenu"> <ul> <li value"copy">创建副本</li> <li value"delete" class"ed-bottom-line&…

每日一题——LeetCode1716.计算力扣银行的钱

方法一 循环模拟 每七天为一个节点&#xff0c;从周一到周日每天比前一天1&#xff0c;到了下一个周一&#xff0c;比上一个周一1&#xff0c;再继续从周一到周日每天1 var totalMoney function(n) {let Monday 1,now1,sum1for(let i2;i<n;i){now1sumnowif(i%70){Monday1…

vue学习笔记27-组件生命周期⭐

每个vue组件实例在创建时都需要经历一系列初始化步骤&#xff0c;比如设置好数据侦听&#xff0c;编译模板&#xff0c;挂载实列到DOM&#xff0c;以及在数据改变时更新DOM。在此过程中&#xff0c;它也会运行称为生命周期钩子的函数&#xff0c;让开发者有机会在特定阶段运行自…

产品经理面试如何自我介绍?

金三银四求职季&#xff0c;你是不是也有面试的冲动&#xff01;但面试并不是头脑一热就能取得好结果&#xff0c;在此之前&#xff0c;必须得有周全的准备&#xff0c;才能应对好面试官的“连环问”&#xff01; 所以&#xff0c;今天这篇产品经理面试干货文章&#xff0c;别…

内网横向1

IPC$详解 IPC( Internet Process Connection) 共享 “ 命名管道 ” 的资源 , 是为了实现进程间通信而开放的命名管道。 IPC 可以通过验证用户名和密码获得相应的权限&#xff0c;通常在远程管理计算机和查看计算机的共享资源时使用 通过 ipc$ &#xff0c;可以与目标机器建立连…

开启Github 2FA验证

1、什么玩意&#xff1f; 一觉睡醒看到邮箱来了一封邮件&#xff0c;让我做什么&#xff1f;(迷迷糊糊看不清&#xff0c;再睡一会~) 2、好吧&#xff0c;准备开搞 看了一下网上的解决方案&#xff0c;那些什么密码 app 我又没有也不想注册。 还有 SMS 短信的&#xff0c;但…

ssm项目(tomcat项目),定时任务(每天运行一次)相同时间多次重复运行job 的bug

目录标题 一、原因 一、原因 debug本地调试没有出现定时任务多次运行的bug&#xff0c;上传到服务器就出现多次运行的bug。&#xff08;war的方式部署到tomcat&#xff09; 一开始我以为是代码原因&#xff0c;或者是linux和win环境不同运行定时任务的方式不一样。 但是自己…

成都欣丰洪泰文化传媒有限公司怎么样?靠谱吗?

在数字经济的浪潮中&#xff0c;成都欣丰洪泰文化传媒有限公司凭借其深厚的行业经验和敏锐的市场洞察力&#xff0c;专注于电商服务领域&#xff0c;不断推陈出新&#xff0c;成为行业的佼佼者。今天&#xff0c;就让我们一同走进欣丰洪泰的世界&#xff0c;探索其如何引领电商…

自动报警灭火系统有哪三种类型?最新选择是这种!

用一句话概括自动报警灭火系统&#xff0c;就是能够在无需人工干预的基础上&#xff0c;自行灭火且发出告警的系统。听上去是不是很神奇&#xff1f;事实上&#xff0c;在科技的帮助下&#xff0c;只需要在监控场所预先安装好火灾探测器和报警控制器就可以实现这一切。接下来&a…

【在table里显示下拉框形式】

图&#xff1a; 一&#xff1a;主要是看审核方式&#xff0c;prop"status"和保存按钮的scope.row <el-table-column prop"status" label"审核方式" align"center"><template slot-scope"scope"><el-select v…