v4l2-ioctl.c
来自「trident tm5600的linux驱动」· C语言 代码 · 共 1,872 行 · 第 1/4 页
C
1,872 行
/* * Video capture interface for Linux version 2 * * A generic framework to process V4L2 ioctl commands. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Authors: Alan Cox, <alan@redhat.com> (version 1) * Mauro Carvalho Chehab <mchehab@infradead.org> (version 2) */#include <linux/module.h>#include <linux/types.h>#include <linux/kernel.h>#define __OLD_VIDIOC_ /* To allow fixing old calls */#include <linux/videodev2.h>#ifdef CONFIG_VIDEO_V4L1#include <linux/videodev.h>#endif#include <media/v4l2-common.h>#include <media/v4l2-ioctl.h>#include <linux/video_decoder.h>#include "compat.h"#define dbgarg(cmd, fmt, arg...) \ do { \ if (vfd->debug & V4L2_DEBUG_IOCTL_ARG) { \ printk(KERN_DEBUG "%s: ", vfd->name); \ v4l_printk_ioctl(cmd); \ printk(" " fmt, ## arg); \ } \ } while (0)#define dbgarg2(fmt, arg...) \ do { \ if (vfd->debug & V4L2_DEBUG_IOCTL_ARG) \ printk(KERN_DEBUG "%s: " fmt, vfd->name, ## arg);\ } while (0)struct std_descr { v4l2_std_id std; const char *descr;};static const struct std_descr standards[] = { { V4L2_STD_NTSC, "NTSC" }, { V4L2_STD_NTSC_M, "NTSC-M" }, { V4L2_STD_NTSC_M_JP, "NTSC-M-JP" }, { V4L2_STD_NTSC_M_KR, "NTSC-M-KR" }, { V4L2_STD_NTSC_443, "NTSC-443" }, { V4L2_STD_PAL, "PAL" }, { V4L2_STD_PAL_BG, "PAL-BG" }, { V4L2_STD_PAL_B, "PAL-B" }, { V4L2_STD_PAL_B1, "PAL-B1" }, { V4L2_STD_PAL_G, "PAL-G" }, { V4L2_STD_PAL_H, "PAL-H" }, { V4L2_STD_PAL_I, "PAL-I" }, { V4L2_STD_PAL_DK, "PAL-DK" }, { V4L2_STD_PAL_D, "PAL-D" }, { V4L2_STD_PAL_D1, "PAL-D1" }, { V4L2_STD_PAL_K, "PAL-K" }, { V4L2_STD_PAL_M, "PAL-M" }, { V4L2_STD_PAL_N, "PAL-N" }, { V4L2_STD_PAL_Nc, "PAL-Nc" }, { V4L2_STD_PAL_60, "PAL-60" }, { V4L2_STD_SECAM, "SECAM" }, { V4L2_STD_SECAM_B, "SECAM-B" }, { V4L2_STD_SECAM_G, "SECAM-G" }, { V4L2_STD_SECAM_H, "SECAM-H" }, { V4L2_STD_SECAM_DK, "SECAM-DK" }, { V4L2_STD_SECAM_D, "SECAM-D" }, { V4L2_STD_SECAM_K, "SECAM-K" }, { V4L2_STD_SECAM_K1, "SECAM-K1" }, { V4L2_STD_SECAM_L, "SECAM-L" }, { V4L2_STD_SECAM_LC, "SECAM-Lc" }, { 0, "Unknown" }};/* video4linux standard ID conversion to standard name */const char *v4l2_norm_to_name(v4l2_std_id id){ u32 myid = id; int i; /* HACK: ppc32 architecture doesn't have __ucmpdi2 function to handle 64 bit comparations. So, on that architecture, with some gcc variants, compilation fails. Currently, the max value is 30bit wide. */ BUG_ON(myid != id); for (i = 0; standards[i].std; i++) if (myid == standards[i].std) break; return standards[i].descr;}EXPORT_SYMBOL(v4l2_norm_to_name);/* Fill in the fields of a v4l2_standard structure according to the 'id' and 'transmission' parameters. Returns negative on error. */int v4l2_video_std_construct(struct v4l2_standard *vs, int id, const char *name){ u32 index = vs->index; memset(vs, 0, sizeof(struct v4l2_standard)); vs->index = index; vs->id = id; if (id & V4L2_STD_525_60) { vs->frameperiod.numerator = 1001; vs->frameperiod.denominator = 30000; vs->framelines = 525; } else { vs->frameperiod.numerator = 1; vs->frameperiod.denominator = 25; vs->framelines = 625; } strlcpy(vs->name, name, sizeof(vs->name)); return 0;}EXPORT_SYMBOL(v4l2_video_std_construct);/* ----------------------------------------------------------------- *//* some arrays for pretty-printing debug messages of enum types */const char *v4l2_field_names[] = { [V4L2_FIELD_ANY] = "any", [V4L2_FIELD_NONE] = "none", [V4L2_FIELD_TOP] = "top", [V4L2_FIELD_BOTTOM] = "bottom", [V4L2_FIELD_INTERLACED] = "interlaced", [V4L2_FIELD_SEQ_TB] = "seq-tb", [V4L2_FIELD_SEQ_BT] = "seq-bt", [V4L2_FIELD_ALTERNATE] = "alternate", [V4L2_FIELD_INTERLACED_TB] = "interlaced-tb", [V4L2_FIELD_INTERLACED_BT] = "interlaced-bt",};EXPORT_SYMBOL(v4l2_field_names);const char *v4l2_type_names[] = { [V4L2_BUF_TYPE_VIDEO_CAPTURE] = "vid-cap", [V4L2_BUF_TYPE_VIDEO_OVERLAY] = "vid-overlay", [V4L2_BUF_TYPE_VIDEO_OUTPUT] = "vid-out", [V4L2_BUF_TYPE_VBI_CAPTURE] = "vbi-cap", [V4L2_BUF_TYPE_VBI_OUTPUT] = "vbi-out", [V4L2_BUF_TYPE_SLICED_VBI_CAPTURE] = "sliced-vbi-cap", [V4L2_BUF_TYPE_SLICED_VBI_OUTPUT] = "sliced-vbi-out", [V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY] = "vid-out-overlay",};EXPORT_SYMBOL(v4l2_type_names);static const char *v4l2_memory_names[] = { [V4L2_MEMORY_MMAP] = "mmap", [V4L2_MEMORY_USERPTR] = "userptr", [V4L2_MEMORY_OVERLAY] = "overlay",};#define prt_names(a, arr) ((((a) >= 0) && ((a) < ARRAY_SIZE(arr))) ? \ arr[a] : "unknown")/* ------------------------------------------------------------------ *//* debug help functions */#ifdef CONFIG_VIDEO_V4L1_COMPATstatic const char *v4l1_ioctls[] = { [_IOC_NR(VIDIOCGCAP)] = "VIDIOCGCAP", [_IOC_NR(VIDIOCGCHAN)] = "VIDIOCGCHAN", [_IOC_NR(VIDIOCSCHAN)] = "VIDIOCSCHAN", [_IOC_NR(VIDIOCGTUNER)] = "VIDIOCGTUNER", [_IOC_NR(VIDIOCSTUNER)] = "VIDIOCSTUNER", [_IOC_NR(VIDIOCGPICT)] = "VIDIOCGPICT", [_IOC_NR(VIDIOCSPICT)] = "VIDIOCSPICT", [_IOC_NR(VIDIOCCAPTURE)] = "VIDIOCCAPTURE", [_IOC_NR(VIDIOCGWIN)] = "VIDIOCGWIN", [_IOC_NR(VIDIOCSWIN)] = "VIDIOCSWIN", [_IOC_NR(VIDIOCGFBUF)] = "VIDIOCGFBUF", [_IOC_NR(VIDIOCSFBUF)] = "VIDIOCSFBUF", [_IOC_NR(VIDIOCKEY)] = "VIDIOCKEY", [_IOC_NR(VIDIOCGFREQ)] = "VIDIOCGFREQ", [_IOC_NR(VIDIOCSFREQ)] = "VIDIOCSFREQ", [_IOC_NR(VIDIOCGAUDIO)] = "VIDIOCGAUDIO", [_IOC_NR(VIDIOCSAUDIO)] = "VIDIOCSAUDIO", [_IOC_NR(VIDIOCSYNC)] = "VIDIOCSYNC", [_IOC_NR(VIDIOCMCAPTURE)] = "VIDIOCMCAPTURE", [_IOC_NR(VIDIOCGMBUF)] = "VIDIOCGMBUF", [_IOC_NR(VIDIOCGUNIT)] = "VIDIOCGUNIT", [_IOC_NR(VIDIOCGCAPTURE)] = "VIDIOCGCAPTURE", [_IOC_NR(VIDIOCSCAPTURE)] = "VIDIOCSCAPTURE", [_IOC_NR(VIDIOCSPLAYMODE)] = "VIDIOCSPLAYMODE", [_IOC_NR(VIDIOCSWRITEMODE)] = "VIDIOCSWRITEMODE", [_IOC_NR(VIDIOCGPLAYINFO)] = "VIDIOCGPLAYINFO", [_IOC_NR(VIDIOCSMICROCODE)] = "VIDIOCSMICROCODE", [_IOC_NR(VIDIOCGVBIFMT)] = "VIDIOCGVBIFMT", [_IOC_NR(VIDIOCSVBIFMT)] = "VIDIOCSVBIFMT"};#define V4L1_IOCTLS ARRAY_SIZE(v4l1_ioctls)#endifstatic const char *v4l2_ioctls[] = { [_IOC_NR(VIDIOC_QUERYCAP)] = "VIDIOC_QUERYCAP", [_IOC_NR(VIDIOC_RESERVED)] = "VIDIOC_RESERVED", [_IOC_NR(VIDIOC_ENUM_FMT)] = "VIDIOC_ENUM_FMT", [_IOC_NR(VIDIOC_G_FMT)] = "VIDIOC_G_FMT", [_IOC_NR(VIDIOC_S_FMT)] = "VIDIOC_S_FMT", [_IOC_NR(VIDIOC_REQBUFS)] = "VIDIOC_REQBUFS", [_IOC_NR(VIDIOC_QUERYBUF)] = "VIDIOC_QUERYBUF", [_IOC_NR(VIDIOC_G_FBUF)] = "VIDIOC_G_FBUF", [_IOC_NR(VIDIOC_S_FBUF)] = "VIDIOC_S_FBUF", [_IOC_NR(VIDIOC_OVERLAY)] = "VIDIOC_OVERLAY", [_IOC_NR(VIDIOC_QBUF)] = "VIDIOC_QBUF", [_IOC_NR(VIDIOC_DQBUF)] = "VIDIOC_DQBUF", [_IOC_NR(VIDIOC_STREAMON)] = "VIDIOC_STREAMON", [_IOC_NR(VIDIOC_STREAMOFF)] = "VIDIOC_STREAMOFF", [_IOC_NR(VIDIOC_G_PARM)] = "VIDIOC_G_PARM", [_IOC_NR(VIDIOC_S_PARM)] = "VIDIOC_S_PARM", [_IOC_NR(VIDIOC_G_STD)] = "VIDIOC_G_STD", [_IOC_NR(VIDIOC_S_STD)] = "VIDIOC_S_STD", [_IOC_NR(VIDIOC_ENUMSTD)] = "VIDIOC_ENUMSTD", [_IOC_NR(VIDIOC_ENUMINPUT)] = "VIDIOC_ENUMINPUT", [_IOC_NR(VIDIOC_G_CTRL)] = "VIDIOC_G_CTRL", [_IOC_NR(VIDIOC_S_CTRL)] = "VIDIOC_S_CTRL", [_IOC_NR(VIDIOC_G_TUNER)] = "VIDIOC_G_TUNER", [_IOC_NR(VIDIOC_S_TUNER)] = "VIDIOC_S_TUNER", [_IOC_NR(VIDIOC_G_AUDIO)] = "VIDIOC_G_AUDIO", [_IOC_NR(VIDIOC_S_AUDIO)] = "VIDIOC_S_AUDIO", [_IOC_NR(VIDIOC_QUERYCTRL)] = "VIDIOC_QUERYCTRL", [_IOC_NR(VIDIOC_QUERYMENU)] = "VIDIOC_QUERYMENU", [_IOC_NR(VIDIOC_G_INPUT)] = "VIDIOC_G_INPUT", [_IOC_NR(VIDIOC_S_INPUT)] = "VIDIOC_S_INPUT", [_IOC_NR(VIDIOC_G_OUTPUT)] = "VIDIOC_G_OUTPUT", [_IOC_NR(VIDIOC_S_OUTPUT)] = "VIDIOC_S_OUTPUT", [_IOC_NR(VIDIOC_ENUMOUTPUT)] = "VIDIOC_ENUMOUTPUT", [_IOC_NR(VIDIOC_G_AUDOUT)] = "VIDIOC_G_AUDOUT", [_IOC_NR(VIDIOC_S_AUDOUT)] = "VIDIOC_S_AUDOUT", [_IOC_NR(VIDIOC_G_MODULATOR)] = "VIDIOC_G_MODULATOR", [_IOC_NR(VIDIOC_S_MODULATOR)] = "VIDIOC_S_MODULATOR", [_IOC_NR(VIDIOC_G_FREQUENCY)] = "VIDIOC_G_FREQUENCY", [_IOC_NR(VIDIOC_S_FREQUENCY)] = "VIDIOC_S_FREQUENCY", [_IOC_NR(VIDIOC_CROPCAP)] = "VIDIOC_CROPCAP", [_IOC_NR(VIDIOC_G_CROP)] = "VIDIOC_G_CROP", [_IOC_NR(VIDIOC_S_CROP)] = "VIDIOC_S_CROP", [_IOC_NR(VIDIOC_G_JPEGCOMP)] = "VIDIOC_G_JPEGCOMP", [_IOC_NR(VIDIOC_S_JPEGCOMP)] = "VIDIOC_S_JPEGCOMP", [_IOC_NR(VIDIOC_QUERYSTD)] = "VIDIOC_QUERYSTD", [_IOC_NR(VIDIOC_TRY_FMT)] = "VIDIOC_TRY_FMT", [_IOC_NR(VIDIOC_ENUMAUDIO)] = "VIDIOC_ENUMAUDIO", [_IOC_NR(VIDIOC_ENUMAUDOUT)] = "VIDIOC_ENUMAUDOUT", [_IOC_NR(VIDIOC_G_PRIORITY)] = "VIDIOC_G_PRIORITY", [_IOC_NR(VIDIOC_S_PRIORITY)] = "VIDIOC_S_PRIORITY", [_IOC_NR(VIDIOC_G_SLICED_VBI_CAP)] = "VIDIOC_G_SLICED_VBI_CAP", [_IOC_NR(VIDIOC_LOG_STATUS)] = "VIDIOC_LOG_STATUS", [_IOC_NR(VIDIOC_G_EXT_CTRLS)] = "VIDIOC_G_EXT_CTRLS", [_IOC_NR(VIDIOC_S_EXT_CTRLS)] = "VIDIOC_S_EXT_CTRLS", [_IOC_NR(VIDIOC_TRY_EXT_CTRLS)] = "VIDIOC_TRY_EXT_CTRLS",#if 1 /*KEEP*/ [_IOC_NR(VIDIOC_ENUM_FRAMESIZES)] = "VIDIOC_ENUM_FRAMESIZES", [_IOC_NR(VIDIOC_ENUM_FRAMEINTERVALS)] = "VIDIOC_ENUM_FRAMEINTERVALS", [_IOC_NR(VIDIOC_G_ENC_INDEX)] = "VIDIOC_G_ENC_INDEX", [_IOC_NR(VIDIOC_ENCODER_CMD)] = "VIDIOC_ENCODER_CMD", [_IOC_NR(VIDIOC_TRY_ENCODER_CMD)] = "VIDIOC_TRY_ENCODER_CMD", [_IOC_NR(VIDIOC_DBG_S_REGISTER)] = "VIDIOC_DBG_S_REGISTER", [_IOC_NR(VIDIOC_DBG_G_REGISTER)] = "VIDIOC_DBG_G_REGISTER", [_IOC_NR(VIDIOC_G_CHIP_IDENT)] = "VIDIOC_G_CHIP_IDENT", [_IOC_NR(VIDIOC_S_HW_FREQ_SEEK)] = "VIDIOC_S_HW_FREQ_SEEK",#endif};#define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls)static const char *v4l2_int_ioctls[] = {#ifdef CONFIG_VIDEO_V4L1_COMPAT [_IOC_NR(DECODER_GET_CAPABILITIES)] = "DECODER_GET_CAPABILITIES", [_IOC_NR(DECODER_GET_STATUS)] = "DECODER_GET_STATUS", [_IOC_NR(DECODER_SET_NORM)] = "DECODER_SET_NORM", [_IOC_NR(DECODER_SET_INPUT)] = "DECODER_SET_INPUT", [_IOC_NR(DECODER_SET_OUTPUT)] = "DECODER_SET_OUTPUT", [_IOC_NR(DECODER_ENABLE_OUTPUT)] = "DECODER_ENABLE_OUTPUT", [_IOC_NR(DECODER_SET_PICTURE)] = "DECODER_SET_PICTURE", [_IOC_NR(DECODER_SET_GPIO)] = "DECODER_SET_GPIO", [_IOC_NR(DECODER_INIT)] = "DECODER_INIT", [_IOC_NR(DECODER_SET_VBI_BYPASS)] = "DECODER_SET_VBI_BYPASS", [_IOC_NR(DECODER_DUMP)] = "DECODER_DUMP",#endif [_IOC_NR(AUDC_SET_RADIO)] = "AUDC_SET_RADIO", [_IOC_NR(TUNER_SET_TYPE_ADDR)] = "TUNER_SET_TYPE_ADDR", [_IOC_NR(TUNER_SET_STANDBY)] = "TUNER_SET_STANDBY", [_IOC_NR(TUNER_SET_CONFIG)] = "TUNER_SET_CONFIG", [_IOC_NR(VIDIOC_INT_S_TUNER_MODE)] = "VIDIOC_INT_S_TUNER_MODE", [_IOC_NR(VIDIOC_INT_RESET)] = "VIDIOC_INT_RESET", [_IOC_NR(VIDIOC_INT_AUDIO_CLOCK_FREQ)] = "VIDIOC_INT_AUDIO_CLOCK_FREQ", [_IOC_NR(VIDIOC_INT_DECODE_VBI_LINE)] = "VIDIOC_INT_DECODE_VBI_LINE", [_IOC_NR(VIDIOC_INT_S_VBI_DATA)] = "VIDIOC_INT_S_VBI_DATA", [_IOC_NR(VIDIOC_INT_G_VBI_DATA)] = "VIDIOC_INT_G_VBI_DATA", [_IOC_NR(VIDIOC_INT_I2S_CLOCK_FREQ)] = "VIDIOC_INT_I2S_CLOCK_FREQ", [_IOC_NR(VIDIOC_INT_S_STANDBY)] = "VIDIOC_INT_S_STANDBY", [_IOC_NR(VIDIOC_INT_S_AUDIO_ROUTING)] = "VIDIOC_INT_S_AUDIO_ROUTING", [_IOC_NR(VIDIOC_INT_G_AUDIO_ROUTING)] = "VIDIOC_INT_G_AUDIO_ROUTING", [_IOC_NR(VIDIOC_INT_S_VIDEO_ROUTING)] = "VIDIOC_INT_S_VIDEO_ROUTING", [_IOC_NR(VIDIOC_INT_G_VIDEO_ROUTING)] = "VIDIOC_INT_G_VIDEO_ROUTING", [_IOC_NR(VIDIOC_INT_S_CRYSTAL_FREQ)] = "VIDIOC_INT_S_CRYSTAL_FREQ", [_IOC_NR(VIDIOC_INT_INIT)] = "VIDIOC_INT_INIT", [_IOC_NR(VIDIOC_INT_G_STD_OUTPUT)] = "VIDIOC_INT_G_STD_OUTPUT", [_IOC_NR(VIDIOC_INT_S_STD_OUTPUT)] = "VIDIOC_INT_S_STD_OUTPUT",};#define V4L2_INT_IOCTLS ARRAY_SIZE(v4l2_int_ioctls)/* Common ioctl debug function. This function can be used by external ioctl messages as well as internal V4L ioctl */void v4l_printk_ioctl(unsigned int cmd){ char *dir, *type; switch (_IOC_TYPE(cmd)) { case 'd': if (_IOC_NR(cmd) >= V4L2_INT_IOCTLS) { type = "v4l2_int"; break; } printk("%s", v4l2_int_ioctls[_IOC_NR(cmd)]); return;#ifdef CONFIG_VIDEO_V4L1_COMPAT case 'v': if (_IOC_NR(cmd) >= V4L1_IOCTLS) { type = "v4l1"; break; } printk("%s", v4l1_ioctls[_IOC_NR(cmd)]); return;#endif case 'V': if (_IOC_NR(cmd) >= V4L2_IOCTLS) { type = "v4l2"; break; } printk("%s", v4l2_ioctls[_IOC_NR(cmd)]); return; default: type = "unknown"; } switch (_IOC_DIR(cmd)) { case _IOC_NONE: dir = "--"; break; case _IOC_READ: dir = "r-"; break; case _IOC_WRITE: dir = "-w"; break; case _IOC_READ | _IOC_WRITE: dir = "rw"; break; default: dir = "*ERR*"; break; } printk("%s ioctl '%c', dir=%s, #%d (0x%08x)", type, _IOC_TYPE(cmd), dir, _IOC_NR(cmd), cmd);}EXPORT_SYMBOL(v4l_printk_ioctl);/* * helper function -- handles userspace copying for ioctl arguments */#ifdef __OLD_VIDIOC_static unsigned intvideo_fix_command(unsigned int cmd){ switch (cmd) { case VIDIOC_OVERLAY_OLD: cmd = VIDIOC_OVERLAY; break; case VIDIOC_S_PARM_OLD: cmd = VIDIOC_S_PARM; break; case VIDIOC_S_CTRL_OLD: cmd = VIDIOC_S_CTRL; break; case VIDIOC_G_AUDIO_OLD: cmd = VIDIOC_G_AUDIO; break; case VIDIOC_G_AUDOUT_OLD: cmd = VIDIOC_G_AUDOUT; break; case VIDIOC_CROPCAP_OLD: cmd = VIDIOC_CROPCAP; break; } return cmd;}#endif/* * Obsolete usercopy function - Should be removed soon */intvideo_usercopy(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg, int (*func)(struct inode *inode, struct file *file, unsigned int cmd, void *arg)){ char sbuf[128]; void *mbuf = NULL; void *parg = NULL; int err = -EINVAL; int is_ext_ctrl; size_t ctrls_size = 0; void __user *user_ptr = NULL;#ifdef __OLD_VIDIOC_ cmd = video_fix_command(cmd);#endif is_ext_ctrl = (cmd == VIDIOC_S_EXT_CTRLS || cmd == VIDIOC_G_EXT_CTRLS || cmd == VIDIOC_TRY_EXT_CTRLS); /* Copy arguments into temp kernel buffer */ switch (_IOC_DIR(cmd)) { case _IOC_NONE: parg = NULL; break; case _IOC_READ: case _IOC_WRITE: case (_IOC_WRITE | _IOC_READ): if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { parg = sbuf; } else { /* too big to allocate from stack */ mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL); if (NULL == mbuf) return -ENOMEM; parg = mbuf; } err = -EFAULT; if (_IOC_DIR(cmd) & _IOC_WRITE) if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) goto out; break; } if (is_ext_ctrl) { struct v4l2_ext_controls *p = parg; /* In case of an error, tell the caller that it wasn't a specific control that caused it. */ p->error_idx = p->count; user_ptr = (void __user *)p->controls; if (p->count) { ctrls_size = sizeof(struct v4l2_ext_control) * p->count; /* Note: v4l2_ext_controls fits in sbuf[] so mbuf is still NULL. */ mbuf = kmalloc(ctrls_size, GFP_KERNEL); err = -ENOMEM; if (NULL == mbuf) goto out_ext_ctrl; err = -EFAULT; if (copy_from_user(mbuf, user_ptr, ctrls_size)) goto out_ext_ctrl; p->controls = mbuf; } } /* call driver */ err = func(inode, file, cmd, parg); if (err == -ENOIOCTLCMD) err = -EINVAL; if (is_ext_ctrl) { struct v4l2_ext_controls *p = parg; p->controls = (void *)user_ptr;
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?