• Linux下输入子系统上报触摸屏坐标


    Linux下输入子系统上报触摸屏坐标

    1.输入子系统简介

      在 Linux 中,输入子系统是由输入子系统设备驱动层输入子系统核心层(Input Core)输入子系统事件处理层(Event Handler)组成。
    在这里插入图片描述

    • 设备驱动层

        设备驱动层实现对硬件设备的各个寄存的访问,将底层硬件对用户层的响应数据转换为标准输入事件,再通过核心层提交给事件处理层。
    • 核心层

        核心层是设备驱动层和事件处理层的连接桥梁,为设备驱动层和事件处理层提供编程接口。
    • 事件处理层

        事件处理层则为用户空间提供统一访问接口,处理驱动层提交的数据,所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,只需要关心对各硬件寄存器的操作和提交的输入事件。

    2.输入子系统好处

    1. 统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论 PS/2、 USB、还是蓝牙,都被同样处理。输入子系统常见事件类型为:按键事件(如键盘)、相对坐标事件(如鼠标)、绝对坐标事件(如触摸屏)。
    2. 提供了用于分发输入报告给用户应用程序的简单的事件( event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能够很方便的调用输入 API 以发送鼠标移动、键盘按键,或触摸事件给用户空间。
    3. 抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为 serio)的集合,支持对串口和键盘控制器等硬件输入的访问。

    3.输入子系统相关接口函数

    • struct input_dev 结构体
        结构体 input_dev 表示底层硬件设备,是所有输入设备的抽象。驱动层需要实现对input_dev 结构体的填充。
    struct input_dev {
    	const char *name; //设备名字--比如:键盘的名字
    	const char *phys; //设备在系统中的路径。比如:input/key0
    	const char *uniq; //唯一ID号
    	struct input_id id; //用于匹配事件处理层 handler
    
    	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; 
    
    	unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //记录支持的事件
    	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)];
    	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;
    
    	unsigned int repeat_key;
    	struct timer_list timer;
    
    	int rep[REP_CNT];
    
    	struct input_mt_slot *mt;
    	int mtsize;
    	int slot;
    	int trkid;
    
    	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;
    
    	bool sync;//最后一次同步后没有新的事件置 1
    
    	struct device dev;
    
    	struct list_head	h_list;
    	struct list_head	node;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • struct input_event 结构体
        该结构体一般在应用层调用,用户接收事件层上报的数据内容。
    struct input_event {
    	struct timeval time; //时间戳
    	__u16 type;//事件类型EV_KEY、EV_REL、EV_ABS
    	__u16 code;//事件数据值,若按键事件,则保证按键键值;若坐标信息,则表明为x,y
    	__s32 value;//标志值,若按键,则表示按下还是松开;若坐标,则表示位具体的坐标值
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 动态分配和释放inptu_dev结构体函数

    //动态分配input_dev结构体
    struct input_dev *input_allocate_device(void)
    //释放input_dev结构体
    void input_free_device(struct input_dev *dev)

    • 注册和注销输入子系统

    //注册输入子系统
    int input_register_device(struct input_dev *dev)
    //注销输入子系统
    void input_free_device(struct input_dev *dev)
    形参: input_dev --输入设备结构体
    返回值: 注册成功返回0,失败返回其它值

    • 设置上报的数据内容input_set_capability

        input_set_capability函数用于填充input_dev结构体,设置要报的数据类型和数据信息。

    void input_set_capability(struct input_dev *dev, unsigned int type,unsigned int code)
    形参: dev --input_dev结构体
       type --事件类型EV_KEY、EV_REL、EV_ABS
       code --要上报的具体值
    例:input_set_capability(dev,EV_KEY,KEY_A);//上报按键事件,上报的键值为’A’

    • 设置上报的数据内容__set_bit

      通过设置位的函数实现inptu_dev结构体填充,input_set_capability函数的内部就是通过调用__set_bit函数来实现的。

    inline void __set_bit(int nr, volatile unsigned long *addr)
    形参: nr–要上报的具体值
       addr --设置的地址
    上报按键事件例:
      __set_bit(EV_KEY,dev->evbit);//设置事件属性为按键事件
      __set_bit(KEY_A,dev->keybit);//设置上报的键值

    设置重复上报例:__set_bit(EV_REP,dev->evbit);

    • 设置上报的值的范围input_set_abs_params

        input_set_abs_params函数用于设置上报的数值的取值范围。

    void input_set_abs_params(struct input_dev *dev, unsigned int axis, int min, int max, int fuzz, int flat)
    形参: dev --input_dev结构体
       axis --上报的数值
       min --最小值
       max --最大值
        fuzz --数据偏差值
        flat --平滑位置
    设置触摸屏x坐标范围:
     input_set_abs_params(touch_dev,ABS_X,0,800,0,0);//设置x坐标范围
    设置触摸屏x坐标范围:
     input_set_abs_params(touch_dev,ABS_PRESSURE,0,1,0,0);//设置压力值范围

    • 上报数据到事件处理层

    //上报按键事件键值,如键盘
    inline void input_report_key(struct input_dev *dev, unsigned int code, int value);
    //上报相对事件坐标值,如鼠标
    inline void input_report_rel(struct input_dev *dev, unsigned int code, int value);
    //上报绝对事件坐标值,如触摸屏
    inline void input_report_abs(struct input_dev *dev, unsigned int code, int value);
    形参: dev --input_dev结构体
        code --事件数据值,若按键事件,则保证按键键值;若坐标信息,则表明为x,y
        value --标志值,若按键,则表示按下还是松开;若坐标,则表示位具体的坐标值

      这几个函数完成数据上报内部靠input_event函数实现。

    • 事件同步input_mt_sync

    void input_mt_sync(struct input_dev *dev)
    形参: dev --input_dev结构体

      在完成数据上报后一定要调用事件同步函数。

    4.输入子系统上报触摸屏坐标示例

    硬件平台:tiny4412
    开发平台:ubuntu18.04
    交叉编译器:arm-linux-gcc
    内核:linux3.5
    触摸屏驱动IC:ft5X06

    ft5x06驱动示例参考:Linux下IIC子系统和触摸屏驱动

    • 输入子系统注册上报数据示例
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include 
    #include 
    #include 
    static struct work_struct touch_work;
    static struct i2c_client *touch_client;
    static struct input_dev *touch_dev=NULL;
    /*工作处理函数*/
    static void touch_work_func(struct work_struct *work)
    {
    	u8 touch_buff[7];
    	int x,y;
    	int num;
    	i2c_smbus_read_i2c_block_data(touch_client,0, 7,touch_buff);
    	num=touch_buff[2]&0xf;//触控点个数
    	x=((touch_buff[3]&0xf)<<8)|touch_buff[4];
    	y=((touch_buff[5]&0xf)<<8)|touch_buff[6];
    	//printk("(x,y)=%d,%d\tnum=%d\n",x,y,num);
    	if(num)
    	{
    		 input_report_abs(touch_dev,ABS_X,x);//上报x坐标
    		 input_report_abs(touch_dev,ABS_Y,y);//上报x坐标
    		 input_report_abs(touch_dev,ABS_PRESSURE,1);//压力值,1表示按下
    		 input_report_key(touch_dev,BTN_TOUCH,1);//按下
    	}
    	else
    	{
    		input_report_abs(touch_dev,ABS_PRESSURE,0);//压力值,0表示松开
    		input_report_key(touch_dev,BTN_TOUCH,0);//释放
    	}
    	input_sync(touch_dev);//同步
    }
    /*中断处理函数*/
    static irqreturn_t touch_irq_work(int irq, void *dev)
    {
    	schedule_work(&touch_work);//调度工作
    	return IRQ_HANDLED;
    }
    
    static int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id)//资源匹配函数
    {
    	int ret; 
    	printk("资源匹配成功\n");
    	printk("name=%s\taddr=%#x\tirq=%d\n",client->name,client->addr,client->irq);
    	touch_client=client;
    
    	/*动态分配input_dev结构体*/
    	touch_dev=input_allocate_device();
    	if(!touch_dev)return -1;//动态分配失败
    
    	/*设置要上报的数据内容*/
    	input_set_capability(touch_dev,EV_ABS,ABS_X);//上报x坐标
    	input_set_capability(touch_dev,EV_ABS,ABS_Y);//上报x坐标
    	input_set_capability(touch_dev,EV_ABS,ABS_PRESSURE);//压力值
    	input_set_capability(touch_dev,EV_KEY,BTN_TOUCH);//触摸屏点击事件
    	/*设置xy取值范围*/
    	input_set_abs_params(touch_dev,ABS_X,0,800,0,0);//设置x坐标范围
    	input_set_abs_params(touch_dev,ABS_Y,0,480,0,0);//设置y坐标范围
    	input_set_abs_params(touch_dev,ABS_PRESSURE,0,1,0,0);//设置压力值
    
    	/*注册输入子系统*/
    	ret=input_register_device(touch_dev);
    	if(ret)return ret;//注册输入子系统设备失败
    	/*1.初始化工作*/
    	INIT_WORK(&touch_work, touch_work_func);
    	/*注册中断*/
    	ret=request_irq(client->irq,touch_irq_work,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,"ft5x06",NULL);
    	if(ret)
    	{
    		printk("中断注册失败\n");
    		return -1;
    	}
    
    	return 0;
    }
    static int ft5x06_remove(struct i2c_client *client)//资源释放函数
    {
    	printk("IIC驱动程资源释放成功\n");
    	free_irq(client->irq,NULL);//注销中断
    	/*注销输入子系统设备*/
    	input_unregister_device(touch_dev);
    	/*释放input_dev结构体*/
    	input_free_device(touch_dev);
    	return 0;
    }
    //资源匹配结构体
    static struct i2c_device_id id_table[]=
    {
    		{"touch_ft5x06",0},
    			{},
    };
    static struct i2c_driver ft5x06_drv=
    {
    	.probe=ft5x06_probe,
    	.remove=ft5x06_remove,
    	.driver=
    	{
    		.name="touch_drv",
    	},
    	.id_table=id_table,//资源匹配结构体
    };
    
    static int __init wbyq_ft5x06_drv_init(void)
    {
    	i2c_add_driver(&ft5x06_drv); 
    	return 0;
    	
    }
    /*驱动释放*/
    static void __exit wbyq_ft5x06_drv_cleanup(void)
    {
    	i2c_del_driver(&ft5x06_drv);
        printk("IIC驱动层注销成功\n");
    }
    module_init(wbyq_ft5x06_drv_init);//驱动入口函数
    module_exit(wbyq_ft5x06_drv_cleanup);//驱动出口函数
    
    MODULE_LICENSE("GPL");//驱动注册协议
    MODULE_AUTHOR("it_ashui");
    MODULE_DESCRIPTION("Exynos4 ft5x06_drv Driver");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 应用层读取触摸屏坐标示例
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    typedef unsigned char u8;
    typedef unsigned short u16;
    typedef unsigned int u32;
    static unsigned char *lcd_p=NULL;//屏幕缓存地址
    static unsigned char *gbk_addr=NULL;//屏幕缓存地址
    static struct fb_fix_screeninfo fb_fix;//固定参数结构体
    static struct fb_var_screeninfo fb_var;//可变参数结构体
    extern const unsigned char ascii_32_16[][32*16/8];//逐列式,高位在前
    
    /*LCD画点函数*/
    static inline void LCD_DrawPoint(int x,int y,int c)
    {
    	//获取要绘制的点的地址
    	unsigned int *p= (unsigned int *)(lcd_p+y*fb_fix.line_length+x*fb_var.bits_per_pixel/8);
    	*p=c;//写入颜色值
    }
    
    /*
    显示汉字
    x,y  --要显示的位置
    size  --字体大小
    font --要显示的汉字
    c -- 颜色值
    */
    static void LCD_DisplayFont(int x,int y,int size,char *font,int c)
    {
    	u8 *p=NULL;
    	u8 H,L;
    	u32 addr=0;//汉字偏移地址
    	u16 font_size=size*size/8;//汉字点阵大小(宽度保证为8的倍数)
    	H=*font;//汉字高字节
    	L=*(font+1);//汉字的低字节
    	if(L<0x7F)L-=0x40;
    	else L-=0x41;
    	H-=0x81;
    	addr=(190*H+L)*font_size;//汉字所在点阵中的偏移地址
    	p=malloc(font_size);
    	if(p==NULL)
    	{
    		printf("申请空间失败\r\n");
    		return ;
    	}
    	memcpy(p,gbk_addr+addr,font_size);//读取点阵码数据	
    	int i,j;
    	int x0=x;
    	unsigned char tmep;
    	for(i=0;i<size*size/8;i++)
    	{
    		tmep=p[i];//取出每个字节数据
    		for(j=0;j<8;j++)
    		{
    			if(tmep&0x80)
    			{
    				 LCD_DrawPoint(x0,y,c);
    			}
    			x0++;
    			c+=60;//修改颜色值
    			tmep<<=1;//继续下一个像素点
    		}
    		//换行
    		if(x0-x>=size)
    		{
    			x0=x;
    			y++;
    		}
    	}
    }
    /*
    显示字符
    x,y  --要显示的位置
    h,w -- 字符高和宽
    cha --要显示的字符
    c -- 颜色值
    取模走向:逐列式,高位在前
    */
    static void LCD_DisplayCha(int x,int y,int h,int w,char cha,int c)
    {
    	int i,j;
    	int y0=y;
    	u8 temp;
    	for(i=0;i<w*h/8;i++)
    	{
    		temp=ascii_32_16[cha-' '][i];
    		for(j=0;j<8;j++)
    		{
    			if(temp&0x80)LCD_DrawPoint(x,y0,c);
    			y0++;
    			c+=100;//修改颜色值
    			temp<<=1;
    		}
    		if(y0-y==h)//换到下一列
    		{
    			y0=y;
    			x++;
    		}
    	}
    }
    /*
    显示字符串
    x,y  --要显示的位置
    size -- 字符高度
    str --要显示的字符串
    c -- 颜色值
    */
    static void LCD_DisplayStr(int x,int y,int size,char *str,int c)
    {
    	int x0=x;
    	while(*str)
    	{
    		if(*str>=0x80)//汉字
    		{
    			LCD_DisplayFont(x0,y,size,str,c);
    			str+=2;
    			x0+=size;
    		}
    		else if(*str>=' ' && *str<='~')//字符显示
    		{
    			LCD_DisplayCha(x0,y,size,size/2,*str,c);
    			str++;
    			x0+=size/2;
    		}
    		else str++;
    	}
    	
    }
    int main()
    {
    	/*1.打开设备*/
    	int fd=open("/dev/fb0", 2);
    	if(fd<0)
    	{
    		printf("打开设备失败\n");
    	}
    	/*2.获取固定参数*/
    	memset(&fb_fix,0, sizeof(fb_fix));
     	ioctl(fd,FBIOGET_FSCREENINFO,&fb_fix);
    	printf("屏幕缓存大小:%d\n",fb_fix.smem_len);
    	printf("一行的字节数:%d\n",fb_fix.line_length);
    	/*3.获取屏幕可变参数*/
    	memset(&fb_var,0, sizeof(fb_var));
    	ioctl(fd,FBIOGET_VSCREENINFO,&fb_var);
    	printf("屏幕尺寸:%d*%d\n",fb_var.xres,fb_var.yres);
    	printf("颜色位数:%d\n",fb_var.bits_per_pixel);
    	/*4.将屏幕缓冲区映射到进程空间*/
    	lcd_p=mmap(NULL,fb_fix.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    	close(fd);
    	if(lcd_p==(void *)-1)
    	{
    		printf("内存映射失败\n");
    		return 0;
    	}
    	/*打开字库文件*/
    	int fontfd=open("GBK_32.DZK",2);
    	if(fontfd<0)
    	{
    		printf("字库文件打开失败\n");
    		return 0;
    	}
    	struct stat statbuf;
    	fstat(fontfd,&statbuf);
    	if(statbuf.st_size<=0)goto AA;
    	gbk_addr=mmap(NULL,statbuf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fontfd,0);
    	close(fontfd);
    	if(gbk_addr==(void *)-1)goto AA;
    	memset(lcd_p,0xff,fb_fix.smem_len);//将屏幕清空为白色
    	LCD_DisplayStr(300,50,32,"触摸屏驱动测试",0x45ff);
    	
    	/*打开触摸屏设备*/
    	fd=open("/dev/input/event1",2);
    	if(fd<0)
    	{
    		printf("触摸屏驱动打开失败\n");
    		return 0;
    	}
    	struct pollfd fds=
    	{
    		.fd=fd,
    		.events=POLLIN,
    	};
    	/*
    		struct input_event {
    		struct timeval time;
    		__u16 type;
    		__u16 code;
    		__s32 value;
    		};
    	*/
    	struct input_event touchxy;
    	while(1)
    	{
    		poll(&fds,1,-1);
    		read(fd,&touchxy,sizeof(touchxy));
    		switch(touchxy.type)
    		{
    			case EV_KEY://按键值
    				printf("key=%d\tstat=%d\n",touchxy.code,touchxy.value);
    				break;
    			case EV_ABS://绝对坐标
    				if(touchxy.code == ABS_X)//x坐标
    				{
    					printf("x=%d\n",touchxy.value);
    				}
    				else if(touchxy.code == ABS_Y)//Y坐标
    				{
    					printf("y=%d\n",touchxy.value);
    				}
    				else if(touchxy.code == ABS_PRESSURE)//压力值
    				{
    					printf("press=%d\n",touchxy.value);
    				}
    				break;
    		}
    	}
    	munmap(gbk_addr,statbuf.st_size);
    AA:	
    	//取消映射
    	munmap(lcd_p,fb_fix.smem_len);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
  • 相关阅读:
    vulnhub之GeminiInc v2
    DCDC直流低压升高压隔离电源模块(带短路保护)
    Win11任务栏太宽了怎么变窄?Win11任务栏宽度调整方法
    设计模式之工厂模式
    代码随想录二刷 Day42
    【经验分享】一个实用的C语言宏定义技巧写法
    WebSocket理解和使用
    Harbor断电重启postgres报错 could not locate a valid checkpoint record
    81 # 多语言
    Shopee虾皮API接口:解锁商品买家评论数据的宝藏
  • 原文地址:https://blog.csdn.net/weixin_44453694/article/details/126906896