16. 输入设备应用编程

16. 输入设备应用编程

  • 1. 输入类设备编程介绍
    • 1.1 什么是输入设备
    • 1.2 input 子系统
    • 1.3 读取数据的流程
    • 1.4 应用程序如何解析数据
  • 2. 读取 struct input_event 数据
  • 3. 按键应用编程
  • 4. 触摸屏应用编程
    • 4.1 解析触摸屏设备上报的数据
      • 4.1.1 单点触摸设备——事件上报顺序
      • 4.1.2 多点触摸设备——事件上报顺序
        • 4.1.2.1 TypeB 协议
        • 4.1.2.2 触摸屏上报数据分析
    • 4.2 获取触摸屏的信息
    • 4.3 单点触摸应用程序——获取一个触摸点的坐标
    • 4.4 多点触摸应用程序

1. 输入类设备编程介绍

1.1 什么是输入设备

常见的输入设备有鼠标、键盘、触摸屏、遥控器等,用户通过输入设备与系统进行交互。

1.2 input 子系统

Linux 为了管理这些输入设备,实现了一套能够兼容所有输入设备的框架,这个框架就是 input 子系统,可以屏蔽硬件的差异,向应用层提供一套统一的接口。基于子系统注册成功的输入设备,都会在 /dev/input 目录下生成对应的设备节点,名称通常为 eventX,通过读取这些设备节点可以获取输入设备上报的数据。

1.3 读取数据的流程

假设读取的设备对应的节点为 event0:

  1. 打开 /dev/input/event0 设备文件
  2. 发起读操作,如果没有数据可读则会进入休眠(阻塞 IO 情况下)
  3. 当有数据可读时,应用程序会被唤醒,读操作获取到数据返回
  4. 应用程序对读取到的数据进行解析

1.4 应用程序如何解析数据

每一次 read 操作获取的都是一个 struct input_event 结构体类型数据

struct input_event
{
	struct timeval time;
	__u16 type;
	__u16 code;
	__s32 value;
};

主要关注以下三个:

  • type: 用于描述发生了哪一中类型的事件,事件分类具体可以查看 <linux/input.h>头文件
  • code: 表示该类事件中的哪一个具体事件
  • value: 内核每次上报事件都会向应用层发送一个数据 value,对 value 值的解释随 code 变化而变化。

数据同步:
事件中有一个同步事件 EV_SYN,用于实现同步操作,告知接收者本轮上报的数据已经完整。应用程序读取输入设备上报的数据时,一次 read 只能读取一个 struct input_event 类型数据,但是如果读取触摸屏数据时,有横纵坐标,需要多次 read。内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整,可以进行同步了。同步事件也包含多种不同的事件:
在这里插入图片描述
所有的输入设备都需要上报同步事件,上报的同步事件通常是 SYN_REPORT,而 value 值通常为 0.

2. 读取 struct input_event 数据

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main(int argc,char *argv[])	// 执行文件时需要传入参数,参数就是输入设备文件
{
	if(argc!=2)
	{
		fprintf(stderr,"usage:%s input-dev\n",argv[0]);
		return -1;
	}
	struct input_event in_ev={0};
	int fd=open(argv[1],O_RDONLY);
	for(;;)
	{
		// 循环读取数据
		if(sizeof(struct input_event)!=read(fd,&in_ev,sizeof(struct input_event)))
		{
			perror("read");
			return -1;
		}
		printf("type:%d code:%d value:%d\n",in_ev.type,in_ev.code,in_ev.value);
	}
}

3. 按键应用编程

获取按键状态,以字母 A 为例,上报 KEY_A 事件时,value=1;如果是松开,value=0;如果是长按,value=2

int main(int argc,char *argv[])
{
	if(argc!=2)
	{
		fprintf(stderr,"usage:%s input-dev\n",argv[0]);
		return -1;
	}
	struct input_event in_ev={0};
	int fd=open(argv[1],O_RDONLY);
	for(;;)
	{
		if(sizeof(struct input_event)!=read(fd, &in_ev, sizeof(struct input_event))) 
		{
			perror("read error");
			exit(-1);
		}
		if(EV_KEY == in_ev.type)
		{
			switch (in_ev.value) 
			{
				case 0:
					printf("code<%d>: 松开\n", in_ev.code);
					break;
				case 1:
					printf("code<%d>: 按下\n", in_ev.code);
					break;
				case 2:
					printf("code<%d>: 长按\n", in_ev.code);
					break;
			}
		}	
	}
}

4. 触摸屏应用编程

4.1 解析触摸屏设备上报的数据

触摸屏设备是一个绝对位移设备,可以上报绝对位移事件,绝对位移事件如下:
在这里插入图片描述
单点触摸和多点触摸:
单点触摸设备只支持单点触摸, 一轮完整的数据只包含一个触摸点信息;单点触摸设备以 ABS_XXX 事件承载、上报触摸点的信息,譬如 ABS_X(value 值对应的是 X 轴坐标值)、ABS_Y(value 值对应的是 Y 轴坐标值)等绝对位移事件,而有些设备可能还支持 Z 轴坐标(通过 ABS_Z 事件上报、 value 值对应的便是 Z 轴坐标值)、按压力大小(通过 ABS_PRESSURE 事件上报、 value 值对应的便是按压力大小) 以及接触面积等属性。 大部分的单点触摸设备都会上报 ABS_X 和 ABS_Y 事件,而其它绝对位移事件则根据具体的设备以及驱动的实现而定!
多点触摸设备可支持多点触摸,一轮完整的数据可能包含多个触摸点信息。 多点触摸设备则是以 ABS_MT_XXX(MT 是 Multi-touch, 意思为:多点触摸)事件承载、上报触摸点的信息,如 ABS_MT_POSITION_X(X 轴坐标)、ABS_MT_POSITION_Y(Y 轴坐标)等绝对位移事件。
触摸屏设备除了上报绝对位移事件之外,还可以上报按键类事件和同步类事件。同步事件很好理解,因为几乎每一个输入设备都会上报同步事件、告知应用层本轮数据是否完整;当手指点击触摸屏或手指从触摸屏离开时,此时就会上报按键类事件,用于描述按下触摸屏和松开触摸屏;具体的按键事件为 BTN_TOUCH(code=0x14a,也就是 330),当然,手指在触摸屏上滑动不会上报 BTN_TOUCH 事件。
Tips:BTN_TOUCH 事件不支持长按状态,故其 value 不会等于 2。 对于多点触摸设备来说,只有第一个点按下时上报 BTN_TOUCH 事件 value=1,当最后一个点离开触摸屏时上报 BTN_TOUCH 事件 value=0。

4.1.1 单点触摸设备——事件上报顺序

流程大致如下:

// 点击触摸屏时
BTN_TOUCH  // value=1
ABS_X
ABS_Y
SYN_REPORT

// 滑动
ABS_X
ABS_Y
SYN_REPORT

// 松开
BTN_TOUCH	// value=0
SYN_REPORT

4.1.2 多点触摸设备——事件上报顺序

在 Linux 内核中,多点触摸设备使用多点触摸协议(MT)上报各个触摸点的数据,MT 协议分为 TypeA 和 TypeB,前者几乎不用,介绍后一个。

4.1.2.1 TypeB 协议

该协议适用于能够追踪并区分触摸点的设备,是通过 ABS_MT_SLOT 事件上报各个触摸点信息的更新。这类设备通常在硬件上能够区分不同的触摸点,为每一个识别到的触摸点与一个 slot 进行关联,这个 slot 就是一个编号。底层驱动向应用层上报 ABS_MT_SLOT 事件,此事件会告诉接收者当前正在更新的是哪个触摸点的数据,ABS_MT_SLOT 事件中对应的 value 数据存放的便是一个 slot,以告知应用层当前正在更新 slot 关联的触摸点对应的信息。
每个识别出来的触摸点分配一个 slot,与该 slot 关联起来,利用这个 slot 来传递对应触点的变化。除了 ABS_MT_SLOT 事件之外,TypeB 协议还会使用到 ABS_MT_TRACTKING_ID 事件,用于触摸点的创建、替换和销毁工作,携带的 value 表示一个 ID,如果是非负数,表示一个有效的触摸点,如果是 -1,表示该触摸点已经不存在,如果是一个以前不存在的 ID 表示这是一个新的触摸点。
该协议可以减少发送到用户空间的数据,只有发生了变更的数据才会上报。

4.1.2.2 触摸屏上报数据分析

使用命令cat /proc/bus/input/devices确定触摸屏对应的设备节点。点击后先不要松开
在这里插入图片描述
首先第一行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_TRACKING_ID(code=57)事件,并且 value 值等于 78,也就是 ID,这个 ID 是一个非负数,所以表示这是一个新的触摸点被创建,也就意味着触摸屏上产生了一个新的触摸点(手指按下) 。
第二行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_POSITION_X(code=53)事件,其 value对应的便是触摸点的 X 坐标;
第三行上报了 ABS_MT_POSITION_Y(code=54)事件,其 value 值对应的便是触摸点 Y 坐标,所以由此可知该触摸点的坐标为(372, 381)。
第四行上报了按键类事件 EV_KEY(type=1)中的 BTN_TOUCH(code=330),value 值等于 1,表示这是触摸屏上最先产生的触摸点(slot=0、也就是触摸点 0)
第五行和第六行分别上报了绝对位移事件 EV_ABS(type=3)中的 ABS_X(code=0)和 ABS_Y(code=1),其 value 分别对应的是触摸点的 X 坐标和 Y 坐标。多点触摸设备也会通过 ABS_X、 ABS_Y 事件上报触摸点的 X、 Y 坐标,但通常只有触摸点 0 支持,所以可以把多点触摸设备当成单点触摸设备来使用。
最后一行上报了同步类事件 EV_SYN(type=0)中的 SYN_REPORT(code=0)事件,表示此次触摸点的信息全部上报完毕。
在第一个触摸点的基础上,增加第二个触摸点,打印信息如下所示:
在这里插入图片描述
1~7 行不再解释,第八行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_SLOT 事件(code=47),表示目前要更新 slot=1 所关联的触摸点(也就是触摸点 1) 对应的信息。
第九行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_TRACKING_ID 事件(code=57),ID=79,这是之前没有出现过的 ID,表示这是一个新的触摸点。
第十、十一行分别上报了 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 事件。
最后一行上报同步事件(type=0、 code=0) ,告知应用层数据完整。
当手指松开时,触摸点就会被销毁,上报 ABS_MT_TRACKING_ID 事件,并将 value 设置为 -1(ID)

4.2 获取触摸屏的信息

这里需要用到 ioctl() 函数

#include <sys/ioctl.h>
int ioctl(int fd,unsigned long request,...);

第二个参数在input.h头文件中 EVIOC 相关的宏中有定义,第三个参数则是根据第二个参数来确定。譬如获取设备名称:

char name[100];
ioctl(fd,EVIOCGNAME(sizeof(name)),name);

使用最多的是EVIOCGABS(abs),可以获取到触摸屏 slot 的取值范围,参数表示一个 ABS_XXX 绝对位移事件,譬如获取 slot 信息,第三个参数是 struct input_absinfo*

在这里插入图片描述
获取触摸屏支持的最大触摸点数

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main(int argc,char *argv[])
{
	if (2 != argc) 
	{
		fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
		return -1;
	}
	struct input_absinfo info;
	int fd=open(argv[1],O_RDONLY);
	ioctl(fd,EVIOCGABS(ABS_MT_SLOT),&info);
	int max_slots=info.maximum+1-info.minimum;
	printf("最大触摸点数: %d\n",max_slots);
	close(fd);
	return 0;
}

4.3 单点触摸应用程序——获取一个触摸点的坐标

int main(int argc,char *argv[])
{
	if (2 != argc) 
	{
		fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
		return -1;
	}
	struct input_event in_ev;
	int x=0,y=0;
	int down=-1; 	// 记录 BTN_TOUCH 事件的 value,1 表示按下,0 表示松开,-1 表示移动
	int valid=0;	// 记录数据是否有效,更新表示有效,1 表示有效,0 表示无效
	struct input_absinfo info;
	int fd=open(argv[1],O_RDONLY);
	for(;;)
	{
		if(sizeof(struct input_event) != read(fd,&in_ev,sizeof(struct input_event)))
		{
			perror("read");
			return -1;
		}
		switch(in_ev.type)
		{
			case EV_KEY:	// 按键事件
				if(BTN_TOUCH == in_ev.code)
				{
					down = in_ev.value;
					valid=1;
				}
				break;
			case EV_ABS: //绝对位移事件
				switch (in_ev.code)
				{
					case ABS_X: //X 坐标
						x = in_ev.value;
						valid = 1;
						break;
					case ABS_Y: //Y 坐标
						y = in_ev.value;
						valid = 1;
						break;
				}
				break;
			case EV_SYN: //同步事件
				if (SYN_REPORT == in_ev.code) 
				{
					if (valid) //判断是否有效
					{
						switch (down) //判断状态
						{
							case 1:
								printf("按下(%d, %d)\n", x, y);
								break;
							case 0:
								printf("松开\n");
								break;
							case -1:
								printf("移动(%d, %d)\n", x, y);
								break;
						}
						valid = 0; //重置 valid
						down = -1; //重置 down
					}
				}
				break;
		}
	}
}

4.4 多点触摸应用程序

// 描述 MT 多点触摸每一个触摸点的信息
struct ts_mt
{
	int x;
	int y;
	int id;		// 对应 ABS_MT_TRACKING_ID
	int valid;	// 数据有效标志位
};
// 触摸点的坐标
struct tp_xy
{
	int x;
	int y;
};
int ts_read(const int fd,const int max_slots,struct ts_mt *mt)
{
	struct input_event in_ev;
	static int slot = 0;
	static struct tp_xy xy[12]={0};
	int i;

	// 对缓冲区进行初始化
	memset(mt,0x0,max_slots *sizeof(struct ts_mt));
	for(i=0;i<max_slots;i++)
	{
		mt[i].id=-2;	// 初始化为 -2,-1 表示触摸点删除,id>=0 表示创建
	}
	for(;;)
	{
		read(fd,&in_ev,sizeof(struct input_event));
		
		switch(in_ev.type)
		{
			// 按键事件对单点触摸比较有用
			case EV_ABS: //绝对位移事件
				switch (in_ev.code)
				{
					case ABS_MT_SLOT: 
						slot = in_ev.value;
						break;
					case ABS_MT_POSITION_X: 
						xy[slot].x = in_ev.value;
						mt[slot].valid = 1;
						break;
					case ABS_MT_POSITION_Y: 
						xy[slot].y = in_ev.value;
						mt[slot].valid = 1;
						break;
					case ABS_MT_TRACKING_ID: 
						mt[slot].id = in_ev.value;
						mt[slot].valid = 1;
						break;
				}
				break;
			case EV_SYN: //同步事件
				if (SYN_REPORT == in_ev.code) 
				{
					for(i=0;i<max_slots;i++)
					{
						mt[i].x=xy[i].x;
						mt[i].y=xy[i].y;
					}
				}
				return 0;
		}
	}
}
int main(int argc, char *argv[])
{
	if (2 != argc) 
	{
		fprintf(stderr,"usage: %s <input_dev>\n", argv[0]);
		exit(EXIT_FAILURE);
	}
	struct input_absinfo slot;
	struct ts_mt *mt =NULL;
	int max_slots;
	int fd;
	int i;
	fd=open(argv[1],O_RDONLY);
	ioctl(fd,EVIOCGABS(ABS_MT_SLOT),&slot)
	max_slots=slot.maximum+1-slot.minimum;
	printf("max_slots:%d\n",max_slot);
	mt=calloc(max_slots,sizeof(struct ts_mt));
	// 读数据
	for(;;)
	{
		if(ts_read(fd,max_slots,mt)<0)
		{
			break;
		}
		for(i=0;i<max_slots;i++)
		{
			if(mt[i].valid)	// 判断每一个触摸点信息是否发生更新
			{
				if(mt[i].id>=0)
					printf("slot<%d>, 按下(%d, %d)\n", i, mt[i].x, mt[i].y);
				else if (-1 == mt[i].id)
					printf("slot<%d>, 松开\n", i);
				else
					printf("slot<%d>, 移动(%d, %d)\n", i, mt[i].x, mt[i].y);
			}
		}
	}
	close(fd);
	free(mt);
	return 0;
}

在单点触摸中,是通过BTN_TOUCH事件判断手指动作的,而多点触摸中,需要通过 id 来判断多个手指的动作。

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

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

相关文章

消息中间件RabbitMQ介绍

一、基础知识 1. 什么是RabbitMQ RabbitMQ是2007年发布&#xff0c;是一个在AMQP(高级消息队列协议)基础上完成的&#xff0c;简称MQ全称为Message Queue, 消息队列&#xff08;MQ&#xff09;是一种应用程序对应用程序的通信方法&#xff0c;由Erlang&#xff08;专门针对于大…

Unix环境高级编程-学习-04-匿名管道PIPE

目录 一、环境 二、介绍 三、C标准函数介绍 1、pipe 2、popen 3、pclose 4、注意 四、宏 五、常见的管道用法 1、一对一&#xff08;父进程读子进程写一条管道&#xff09; 2、一对一&#xff08;父进程写子进程读一条管道&#xff09; 3、一对多&#xff08;父进程…

代码随想录算法训练营DAY6 | 哈希表(1)

DAY5休息一天&#xff0c;今天重启~ 哈希表理论基础&#xff1a;代码随想录 Java hash实现 &#xff1a;java 哈希表-CSDN博客 一、LeetCode 242 有效的字母异位词 题目链接&#xff1a;242.有效的字母异位词 思路&#xff1a;设置字典 class Solution {public boolean isAnag…

微搭低代码从入门到精通02数据源的介绍

目录 1 数据源的功能组成2 在低码编辑器中调用数据源的能力3 视频讲解 一款低代码产品好不好用&#xff0c;数据建模的能力是一个重要的衡量指标。因为灵活的定义表之间的关系&#xff0c;自由的选择字段的类型&#xff0c;尤其在我们依据模型自动生成页面的时候是比较重要的。…

Windows Server 2025 LTSC 预览版来了

Windows Server 2025 LTSC 预览版来了 1. 安装 Windows Server 2025 LTSC 预览版2. 安装 VMware Tools3. Windows Server 2025 LTSC 预览版4. Windows Server 2025 LTSC 预览版下载地址 1. 安装 Windows Server 2025 LTSC 预览版 使用 VMware Workstation 安装&#xff0c; 安…

JVM实战(30)——模拟堆内存溢出

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

JVM内存调优常用参数

视频讲解地址 文章目录 一、开始二、常用命令1、原生命令2、arthas命令 三、Parallel四、G1 相关参数五、通用参数六、JVM调优参数 一、开始 查看当前JDK版本所支持的垃圾回收器有哪些、以及默认使用的回收器 java -XX:PrintFlagsFinal -version | grep -E \<Use.*GC\>J…

类和对象(2)之类的6个默认成员函数(2)

上次我们梳理了初始化和清理的知识点&#xff0c;今天我们要梳理的是拷贝赋值的知识点。 拷贝构造函数 看到拷贝构造函数这个名字就能看的出来它是一个构造函数&#xff0c;所以它的语法和构造函数很相似。 既然他是一个构造函数&#xff0c;那么他就具有构造函数的语法&…

前端颜料盘??

前端颜料盘&#xff1f;&#xff1f; 一、原生颜料盘 <input type"color" placeholder"选择颜色">二、第三方开源库 Pickr&#xff1a; GitHub: https://github.com/Simonwep/pickr官方网站: https://simonwep.github.io/pickr/Pickr 是一个轻量级…

Windows Server 2003 Web服务器搭建

系列文章目录 目录 系列文章目录 前言 一、Web服务器是什么&#xff1f; 二、配置服务器 1.实验环境搭建 2.服务器搭建 1)控制面板中找到增加或删除程序打开 2)点击增加程序 3)安装Web服务器 4)查看安装是否成功 5)打开Internet信息服务(IIS)管理器,进行配置 6)找…

Java 字符串 07 练习-手机号屏蔽、身份证号信息查看,游戏骂人敏感词替换

注意点&#xff1a;只有返回值才是被截取的小串&#xff0c;所以需要有一个变量去承接它&#xff1b; 自己写的代码&#xff1a; import java.util.Scanner; public class practice {public static void main(String[] args) {Scanner input new Scanner(System.in);String …

离散数学4

谓词公式与量词的辖域 谓词逻辑中量词的符号化 例3&#xff0c;没有人不犯错误&#xff0c;可以理解为&#xff0c;有人不犯错误&#xff0c;用存在量词&#xff0c;因为用的是存在量词&#xff0c;就用合取 可以分步理解&#xff0c; 所有大学生都喜欢歌星&#xff1a; 所有…

HTML实战

HTML实战 标题 标题排版 img标签路径书写的两种方式&#xff1a; 绝对路径 绝对磁盘路径&#xff1a;C:\User…绝对网络路径&#xff1a;https://i2.sinaimg.cn/dy/deco/2012/0613/yocc20120613img01/news_logo.png 相对路径 ./ : 当前目录 ../ &#xff1a; 上一级目录宽…

ARL灯塔vps云服务器安装

前提是vps服务器已经安装好docker 1、下载压缩包到本地 2、解除不能扫描edu等域名的限制 解压docker_arl.zip&#xff0c;打开docker_arl/config-docker.yaml文件 删除掉edu.cn等限制域名(图中已删除) 3、上传至vps云服务器 将docker_arl文件上传到云服务器opt目录下 这里我…

仅使用 Python 创建的 Web 应用程序(前端版本)第08章_商品详细

在本章中,我们将实现一个产品详细信息页面。 完成后的图像如下。 Model、MockDB、Service都是在产品列表页实现的,所以创建步骤如下。 No分类内容1Page定义PageId并创建继承自BasePage的页面类2Application将页面 ID 和页面类对添加到 MultiPageApp 的页面中Page:定义PageI…

12种相关系数汇总,别再以为只有皮尔逊了!

所谓相关关系是指2个或2个以上变量取值之间在某种意义下所存在的规律&#xff0c;其目的在于探索数据集所存在隐藏的关系网&#xff0c;在19世纪80年代&#xff0c;Galton通过研究人类身高遗传问题首次提出了相关的概念&#xff0c;文中指出相关关系可以定义为&#xff1a;一个…

PGsql 解析json及json数组

创建测试数据 drop table if exists json_test; create table json_test as select 111 as id, {"nodes":{"1692328028076":{"nodeId":"1692328028076","nodeName":"测试表1","nodeType":"DATACO…

报错“MySql配置文件已损坏,请联系技术支持”的解决方法

目录 第一步 打开控制面板&#xff0c;选择管理工具&#xff0c;再选择事件查看器 第二步 在【应用程序】里找到这条报错&#xff0c;记下来文件内容。我自己的来源是“MsiInstaller” 第三步 winR组合键&#xff0c;输入regedit打开注册表 第四步 根据前面报错的文件名定位…

【网页设计期末】咖啡商城网页代码

本文资源&#xff1a;咖啡商城网页代码htmlCSSJavaScript_前端-CSDN问答 1.题目要求 用HTMLCSSJavaScript创建网站项目 说明:制作首页、登录页、列表页。要求: 1、首页页面结构:logo&#xff0c;导航&#xff0c;轮播&#xff0c;商品列表(九宫格结构&#xff0c; 每一格均含图…

如何过有「松弛感」的生活?

最近网上有一个词很流行&#xff0c;叫做「松弛感」。最早的出处似乎是这么一条微博&#xff1a;博主见到一家人出门旅游&#xff0c;行李全部被退回&#xff0c;空手抵达目的地&#xff0c;竟然没人紧张和生气&#xff0c;而是重新安排好行李后继续开心聊天&#xff0c;全程非…
最新文章