static void resize(struct vo *vo) { struct vdpctx *vc = vo->priv; struct vdp_functions *vdp = vc->vdp; VdpStatus vdp_st; struct mp_rect src_rect; struct mp_rect dst_rect; vo_get_src_dst_rects(vo, &src_rect, &dst_rect, &vc->osd_rect); vc->out_rect_vid.x0 = dst_rect.x0; vc->out_rect_vid.x1 = dst_rect.x1; vc->out_rect_vid.y0 = dst_rect.y0; vc->out_rect_vid.y1 = dst_rect.y1; vc->src_rect_vid.x0 = src_rect.x0; vc->src_rect_vid.x1 = src_rect.x1; vc->src_rect_vid.y0 = vc->flip ? src_rect.y1 : src_rect.y0; vc->src_rect_vid.y1 = vc->flip ? src_rect.y0 : src_rect.y1; int flip_offset_ms = vo->opts->fullscreen ? vc->flip_offset_fs : vc->flip_offset_window; vo->flip_queue_offset = flip_offset_ms / 1000.; if (vc->output_surface_width < vo->dwidth || vc->output_surface_height < vo->dheight) { if (vc->output_surface_width < vo->dwidth) { vc->output_surface_width += vc->output_surface_width >> 1; vc->output_surface_width = FFMAX(vc->output_surface_width, vo->dwidth); } if (vc->output_surface_height < vo->dheight) { vc->output_surface_height += vc->output_surface_height >> 1; vc->output_surface_height = FFMAX(vc->output_surface_height, vo->dheight); }
// Always called under mpgl_lock static void resize(struct gl_priv *p) { struct vo *vo = p->vo; mp_msg(MSGT_VO, MSGL_V, "[gl] Resize: %dx%d\n", vo->dwidth, vo->dheight); struct mp_rect wnd = {0, 0, vo->dwidth, vo->dheight}; struct mp_rect src, dst; struct mp_osd_res osd; vo_get_src_dst_rects(vo, &src, &dst, &osd); gl_video_resize(p->renderer, &wnd, &src, &dst, &osd); vo->want_redraw = true; }
static void resize(struct gl_priv *p) { struct vo *vo = p->vo; MP_VERBOSE(vo, "Resize: %dx%d\n", vo->dwidth, vo->dheight); struct mp_rect src, dst; struct mp_osd_res osd; vo_get_src_dst_rects(vo, &src, &dst, &osd); int height = p->glctx->flip_v ? vo->dheight : -vo->dheight; gl_video_resize(p->renderer, vo->dwidth, height, &src, &dst, &osd); vo->want_redraw = true; }
static void resize(struct vo *vo) { struct xvctx *ctx = vo->priv; // Can't be used, because the function calculates screen-space coordinates, // while we need video-space. struct mp_osd_res unused; vo_get_src_dst_rects(vo, &ctx->src_rect, &ctx->dst_rect, &unused); vo_x11_clear_background(vo, &ctx->dst_rect); xv_draw_colorkey(vo, &ctx->dst_rect); read_xv_csp(vo); vo->want_redraw = true; }
static void resize(struct vo *vo) { struct xvctx *ctx = vo->priv; // Can't be used, because the function calculates screen-space coordinates, // while we need video-space. struct mp_osd_res unused; vo_get_src_dst_rects(vo, &ctx->src_rect, &ctx->dst_rect, &unused); struct mp_rect *dst = &ctx->dst_rect; int dw = dst->x1 - dst->x0, dh = dst->y1 - dst->y0; vo_x11_clearwindow_part(vo, vo->x11->window, dw, dh); vo_xv_draw_colorkey(vo, dst->x0, dst->y0, dw, dh); read_xv_csp(vo); }
static bool resize(struct vo *vo) { struct priv *p = vo->priv; struct vo_x11_state *x11 = vo->x11; for (int i = 0; i < 2; i++) freeMyXImage(p, i); vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd); p->src_w = p->src.x1 - p->src.x0; p->src_h = p->src.y1 - p->src.y0; p->dst_w = p->dst.x1 - p->dst.x0; p->dst_h = p->dst.y1 - p->dst.y0; // p->osd contains the parameters assuming OSD rendering in window // coordinates, but OSD can only be rendered in the intersection // between window and video rectangle (i.e. not into panscan borders). p->osd.w = p->dst_w; p->osd.h = p->dst_h; p->osd.mt = MPMIN(0, p->osd.mt); p->osd.mb = MPMIN(0, p->osd.mb); p->osd.mr = MPMIN(0, p->osd.mr); p->osd.ml = MPMIN(0, p->osd.ml); mp_input_set_mouse_transform(vo->input_ctx, &p->dst, NULL); p->image_width = (p->dst_w + 7) & (~7); p->image_height = p->dst_h; for (int i = 0; i < 2; i++) { if (!getMyXImage(p, i)) return -1; } const struct fmt_entry *fmte = mp_to_x_fmt; while (fmte->mpfmt) { if (fmte->depth == p->myximage[0]->bits_per_pixel && fmte->byte_order == p->myximage[0]->byte_order && fmte->red_mask == p->myximage[0]->red_mask && fmte->green_mask == p->myximage[0]->green_mask && fmte->blue_mask == p->myximage[0]->blue_mask) break; fmte++; } if (!fmte->mpfmt) { MP_ERR(vo, "X server image format not supported, use another VO.\n"); return -1; } mp_sws_set_from_cmdline(p->sws, vo->opts->sws_opts); p->sws->dst = (struct mp_image_params) { .imgfmt = fmte->mpfmt, .w = p->dst_w, .h = p->dst_h, .p_w = 1, .p_h = 1, }; mp_image_params_guess_csp(&p->sws->dst); if (mp_sws_reinit(p->sws) < 0) return false; XFillRectangle(x11->display, x11->window, p->gc, 0, 0, vo->dwidth, vo->dheight); vo->want_redraw = true; return true; } static void Display_Image(struct priv *p, XImage *myximage) { struct vo *vo = p->vo; XImage *x_image = p->myximage[p->current_buf]; #if HAVE_SHM && HAVE_XEXT if (p->Shmem_Flag) { XShmPutImage(vo->x11->display, vo->x11->window, p->gc, x_image, 0, 0, p->dst.x0, p->dst.y0, p->dst_w, p->dst_h, True); vo->x11->ShmCompletionWaitCount++; } else #endif { XPutImage(vo->x11->display, vo->x11->window, p->gc, x_image, 0, 0, p->dst.x0, p->dst.y0, p->dst_w, p->dst_h); } } static struct mp_image get_x_buffer(struct priv *p, int buf_index) { struct mp_image img = {0}; mp_image_set_params(&img, &p->sws->dst); img.planes[0] = p->myximage[buf_index]->data; img.stride[0] = p->image_width * ((p->myximage[buf_index]->bits_per_pixel + 7) / 8); return img; } static void wait_for_completion(struct vo *vo, int max_outstanding) { #if HAVE_SHM && HAVE_XEXT struct priv *ctx = vo->priv; struct vo_x11_state *x11 = vo->x11; if (ctx->Shmem_Flag) { while (x11->ShmCompletionWaitCount > max_outstanding) { if (!ctx->Shm_Warned_Slow) { MP_WARN(vo, "can't keep up! Waiting" " for XShm completion events...\n"); ctx->Shm_Warned_Slow = 1; } mp_sleep_us(1000); vo_x11_check_events(vo); } } #endif } static void flip_page(struct vo *vo) { struct priv *p = vo->priv; Display_Image(p, p->myximage[p->current_buf]); p->current_buf = (p->current_buf + 1) % 2; } // Note: REDRAW_FRAME can call this with NULL. static void draw_image(struct vo *vo, mp_image_t *mpi) { struct priv *p = vo->priv; wait_for_completion(vo, 1); struct mp_image img = get_x_buffer(p, p->current_buf); if (mpi) { struct mp_image src = *mpi; struct mp_rect src_rc = p->src; src_rc.x0 = MP_ALIGN_DOWN(src_rc.x0, src.fmt.align_x); src_rc.y0 = MP_ALIGN_DOWN(src_rc.y0, src.fmt.align_y); mp_image_crop_rc(&src, src_rc); mp_sws_scale(p->sws, &img, &src); } else { mp_image_clear(&img, 0, 0, img.w, img.h); } osd_draw_on_image(vo->osd, p->osd, mpi ? mpi->pts : 0, 0, &img); if (mpi != p->original_image) { talloc_free(p->original_image); p->original_image = mpi; } } static int query_format(struct vo *vo, int format) { if (sws_isSupportedInput(imgfmt2pixfmt(format))) return 1; return 0; } static void uninit(struct vo *vo) { struct priv *p = vo->priv; if (p->myximage[0]) freeMyXImage(p, 0); if (p->myximage[1]) freeMyXImage(p, 1); if (p->gc) XFreeGC(vo->x11->display, p->gc); talloc_free(p->original_image); vo_x11_uninit(vo); } static int preinit(struct vo *vo) { struct priv *p = vo->priv; p->vo = vo; p->sws = mp_sws_alloc(vo); if (!vo_x11_init(vo)) goto error; struct vo_x11_state *x11 = vo->x11; XWindowAttributes attribs; XGetWindowAttributes(x11->display, x11->rootwin, &attribs); p->depth = attribs.depth; if (!XMatchVisualInfo(x11->display, x11->screen, p->depth, TrueColor, &p->vinfo)) goto error; MP_VERBOSE(vo, "selected visual: %d\n", (int)p->vinfo.visualid); if (!vo_x11_create_vo_window(vo, &p->vinfo, "x11")) goto error; p->gc = XCreateGC(x11->display, x11->window, 0, NULL); MP_WARN(vo, "Warning: this legacy VO has bad performance. Consider fixing " "your graphics drivers, or not forcing the x11 VO.\n"); return 0; error: uninit(vo); return -1; } static int control(struct vo *vo, uint32_t request, void *data) { struct priv *p = vo->priv; switch (request) { case VOCTRL_SET_PANSCAN: if (vo->config_ok) resize(vo); return VO_TRUE; case VOCTRL_REDRAW_FRAME: draw_image(vo, p->original_image); return true; } int events = 0; int r = vo_x11_control(vo, &events, request, data); if (vo->config_ok && (events & (VO_EVENT_EXPOSE | VO_EVENT_RESIZE))) resize(vo); vo_event(vo, events); return r; } const struct vo_driver video_out_x11 = { .description = "X11 (slow, old crap)", .name = "x11", .priv_size = sizeof(struct priv), .preinit = preinit, .query_format = query_format, .reconfig = reconfig, .control = control, .draw_image = draw_image, .flip_page = flip_page, .wakeup = vo_x11_wakeup, .wait_events = vo_x11_wait_events, .uninit = uninit, };
static void modeset_destroy_fb(int fd, struct modeset_buf *buf) { if (buf->map) { munmap(buf->map, buf->size); } if (buf->fb) { drmModeRmFB(fd, buf->fb); } if (buf->handle) { struct drm_mode_destroy_dumb dreq = { .handle = buf->handle, }; drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); } } static int modeset_create_fb(struct vo *vo, int fd, struct modeset_buf *buf) { int ret = 0; buf->handle = 0; // create dumb buffer struct drm_mode_create_dumb creq = { .width = buf->width, .height = buf->height, .bpp = 32, }; ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); if (ret < 0) { MP_ERR(vo, "Cannot create dumb buffer: %s\n", mp_strerror(errno)); ret = -errno; goto end; } buf->stride = creq.pitch; buf->size = creq.size; buf->handle = creq.handle; // create framebuffer object for the dumb-buffer ret = drmModeAddFB(fd, buf->width, buf->height, 24, 32, buf->stride, buf->handle, &buf->fb); if (ret) { MP_ERR(vo, "Cannot create framebuffer: %s\n", mp_strerror(errno)); ret = -errno; goto end; } // prepare buffer for memory mapping struct drm_mode_map_dumb mreq = { .handle = buf->handle, }; ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); if (ret) { MP_ERR(vo, "Cannot map dumb buffer: %s\n", mp_strerror(errno)); ret = -errno; goto end; } // perform actual memory mapping buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mreq.offset); if (buf->map == MAP_FAILED) { MP_ERR(vo, "Cannot map dumb buffer: %s\n", mp_strerror(errno)); ret = -errno; goto end; } memset(buf->map, 0, buf->size); end: if (ret == 0) { return 0; } modeset_destroy_fb(fd, buf); return ret; } static int modeset_find_crtc(struct vo *vo, int fd, drmModeRes *res, drmModeConnector *conn, struct modeset_dev *dev) { for (unsigned int i = 0; i < conn->count_encoders; ++i) { drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[i]); if (!enc) { MP_WARN(vo, "Cannot retrieve encoder %u:%u: %s\n", i, conn->encoders[i], mp_strerror(errno)); continue; } // iterate all global CRTCs for (unsigned int j = 0; j < res->count_crtcs; ++j) { // check whether this CRTC works with the encoder if (!(enc->possible_crtcs & (1 << j))) continue; dev->enc = enc; dev->crtc = enc->crtc_id; return 0; } drmModeFreeEncoder(enc); } MP_ERR(vo, "Connector %u has no suitable CRTC\n", conn->connector_id); return -ENOENT; } static bool is_connector_valid(struct vo *vo, int conn_id, drmModeConnector *conn, bool silent) { if (!conn) { if (!silent) { MP_ERR(vo, "Cannot get connector %d: %s\n", conn_id, mp_strerror(errno)); } return false; } if (conn->connection != DRM_MODE_CONNECTED) { if (!silent) { MP_ERR(vo, "Connector %d is disconnected\n", conn_id); } return false; } if (conn->count_modes == 0) { if (!silent) { MP_ERR(vo, "Connector %d has no valid modes\n", conn_id); } return false; } return true; } static int modeset_prepare_dev(struct vo *vo, int fd, int conn_id, struct modeset_dev **out) { struct modeset_dev *dev = NULL; drmModeConnector *conn = NULL; int ret = 0; *out = NULL; drmModeRes *res = drmModeGetResources(fd); if (!res) { MP_ERR(vo, "Cannot retrieve DRM resources: %s\n", mp_strerror(errno)); ret = -errno; goto end; } if (conn_id == -1) { // get the first connected connector for (int i = 0; i < res->count_connectors; i++) { conn = drmModeGetConnector(fd, res->connectors[i]); if (is_connector_valid(vo, i, conn, true)) { conn_id = i; break; } if (conn) { drmModeFreeConnector(conn); conn = NULL; } } if (conn_id == -1) { MP_ERR(vo, "No connected connectors found\n"); ret = -ENODEV; goto end; } } if (conn_id < 0 || conn_id >= res->count_connectors) { MP_ERR(vo, "Bad connector ID. Max valid connector ID = %u\n", res->count_connectors); ret = -ENODEV; goto end; } conn = drmModeGetConnector(fd, res->connectors[conn_id]); if (!is_connector_valid(vo, conn_id, conn, false)) { ret = -ENODEV; goto end; } dev = talloc_zero(vo->priv, struct modeset_dev); dev->conn = conn->connector_id; dev->front_buf = 0; dev->mode = conn->modes[0]; dev->bufs[0].width = conn->modes[0].hdisplay; dev->bufs[0].height = conn->modes[0].vdisplay; dev->bufs[1].width = conn->modes[0].hdisplay; dev->bufs[1].height = conn->modes[0].vdisplay; MP_INFO(vo, "Connector using mode %ux%u\n", dev->bufs[0].width, dev->bufs[0].height); ret = modeset_find_crtc(vo, fd, res, conn, dev); if (ret) { MP_ERR(vo, "Connector %d has no valid CRTC\n", conn_id); goto end; } for (unsigned int i = 0; i < BUF_COUNT; i++) { ret = modeset_create_fb(vo, fd, &dev->bufs[i]); if (ret) { MP_ERR(vo, "Cannot create framebuffer for connector %d\n", conn_id); for (unsigned int j = 0; j < i; j++) { modeset_destroy_fb(fd, &dev->bufs[j]); } goto end; } } end: if (conn) { drmModeFreeConnector(conn); conn = NULL; } if (res) { drmModeFreeResources(res); res = NULL; } if (ret == 0) { *out = dev; } else { talloc_free(dev); } return ret; } static void modeset_page_flipped(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { struct priv *p = data; p->pflip_happening = false; } static int setup_vo_crtc(struct vo *vo) { struct priv *p = vo->priv; if (p->active) return 0; p->old_crtc = drmModeGetCrtc(p->fd, p->dev->crtc); int ret = drmModeSetCrtc(p->fd, p->dev->crtc, p->dev->bufs[p->dev->front_buf + BUF_COUNT - 1].fb, 0, 0, &p->dev->conn, 1, &p->dev->mode); p->active = true; return ret; } static void release_vo_crtc(struct vo *vo) { struct priv *p = vo->priv; if (!p->active) return; p->active = false; // wait for current page flip while (p->pflip_happening) { int ret = drmHandleEvent(p->fd, &p->ev); if (ret) { MP_ERR(vo, "drmHandleEvent failed: %i\n", ret); break; } } if (p->old_crtc) { drmModeSetCrtc(p->fd, p->old_crtc->crtc_id, p->old_crtc->buffer_id, p->old_crtc->x, p->old_crtc->y, &p->dev->conn, 1, &p->dev->mode); drmModeFreeCrtc(p->old_crtc); p->old_crtc = NULL; } } static void release_vt(void *data) { struct vo *vo = data; release_vo_crtc(vo); if (USE_MASTER) { //this function enables support for switching to x, weston etc. //however, for whatever reason, it can be called only by root users. //until things change, this is commented. struct priv *p = vo->priv; if (drmDropMaster(p->fd)) { MP_WARN(vo, "Failed to drop DRM master: %s\n", mp_strerror(errno)); } } } static void acquire_vt(void *data) { struct vo *vo = data; if (USE_MASTER) { struct priv *p = vo->priv; if (drmSetMaster(p->fd)) { MP_WARN(vo, "Failed to acquire DRM master: %s\n", mp_strerror(errno)); } } setup_vo_crtc(vo); } static int wait_events(struct vo *vo, int64_t until_time_us) { struct priv *p = vo->priv; int64_t wait_us = until_time_us - mp_time_us(); int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000); vt_switcher_poll(&p->vt_switcher, timeout_ms); return 0; } static void wakeup(struct vo *vo) { struct priv *p = vo->priv; vt_switcher_interrupt_poll(&p->vt_switcher); } static int reconfig(struct vo *vo, struct mp_image_params *params, int flags) { struct priv *p = vo->priv; vo->dwidth = p->device_w; vo->dheight = p->device_h; vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd); int32_t w = p->dst.x1 - p->dst.x0; int32_t h = p->dst.y1 - p->dst.y0; // p->osd contains the parameters assuming OSD rendering in window // coordinates, but OSD can only be rendered in the intersection // between window and video rectangle (i.e. not into panscan borders). p->osd.w = w; p->osd.h = h; p->osd.mt = MPMIN(0, p->osd.mt); p->osd.mb = MPMIN(0, p->osd.mb); p->osd.mr = MPMIN(0, p->osd.mr); p->osd.ml = MPMIN(0, p->osd.ml); p->x = (p->device_w - w) >> 1; p->y = (p->device_h - h) >> 1; mp_sws_set_from_cmdline(p->sws, vo->opts->sws_opts); p->sws->src = *params; p->sws->dst = (struct mp_image_params) { .imgfmt = IMGFMT_BGR0, .w = w, .h = h, .d_w = w, .d_h = h, }; talloc_free(p->cur_frame); p->cur_frame = mp_image_alloc(IMGFMT_BGR0, p->device_w, p->device_h); mp_image_params_guess_csp(&p->sws->dst); mp_image_set_params(p->cur_frame, &p->sws->dst); struct modeset_buf *buf = p->dev->bufs; memset(buf[0].map, 0, buf[0].size); memset(buf[1].map, 0, buf[1].size); if (mp_sws_reinit(p->sws) < 0) return -1; vo->want_redraw = true; return 0; } static void draw_image(struct vo *vo, mp_image_t *mpi) { struct priv *p = vo->priv; if (p->active) { struct mp_image src = *mpi; struct mp_rect src_rc = p->src; src_rc.x0 = MP_ALIGN_DOWN(src_rc.x0, mpi->fmt.align_x); src_rc.y0 = MP_ALIGN_DOWN(src_rc.y0, mpi->fmt.align_y); mp_image_crop_rc(&src, src_rc); mp_sws_scale(p->sws, p->cur_frame, &src); osd_draw_on_image(vo->osd, p->osd, src.pts, 0, p->cur_frame); struct modeset_buf *front_buf = &p->dev->bufs[p->dev->front_buf]; int32_t shift = (p->device_w * p->y + p->x) * 4; memcpy_pic(front_buf->map + shift, p->cur_frame->planes[0], (p->dst.x1 - p->dst.x0) * 4, p->dst.y1 - p->dst.y0, p->device_w * 4, p->cur_frame->stride[0]); } if (mpi != p->last_input) { talloc_free(p->last_input); p->last_input = mpi; } } static void flip_page(struct vo *vo) { struct priv *p = vo->priv; if (!p->active || p->pflip_happening) return; int ret = drmModePageFlip(p->fd, p->dev->crtc, p->dev->bufs[p->dev->front_buf].fb, DRM_MODE_PAGE_FLIP_EVENT, p); if (ret) { MP_WARN(vo, "Cannot flip page for connector\n"); } else { p->dev->front_buf++; p->dev->front_buf %= BUF_COUNT; p->pflip_happening = true; } // poll page flip finish event const int timeout_ms = 3000; struct pollfd fds[1] = { { .events = POLLIN, .fd = p->fd }, }; poll(fds, 1, timeout_ms); if (fds[0].revents & POLLIN) { ret = drmHandleEvent(p->fd, &p->ev); if (ret != 0) { MP_ERR(vo, "drmHandleEvent failed: %i\n", ret); return; } } } static void uninit(struct vo *vo) { struct priv *p = vo->priv; if (p->dev) { release_vo_crtc(vo); modeset_destroy_fb(p->fd, &p->dev->bufs[1]); modeset_destroy_fb(p->fd, &p->dev->bufs[0]); drmModeFreeEncoder(p->dev->enc); } vt_switcher_destroy(&p->vt_switcher); talloc_free(p->last_input); talloc_free(p->cur_frame); talloc_free(p->dev); close(p->fd); } static int preinit(struct vo *vo) { struct priv *p = vo->priv; p->sws = mp_sws_alloc(vo); p->fd = -1; p->ev.version = DRM_EVENT_CONTEXT_VERSION; p->ev.page_flip_handler = modeset_page_flipped; if (vt_switcher_init(&p->vt_switcher, vo->log)) goto err; vt_switcher_acquire(&p->vt_switcher, acquire_vt, vo); vt_switcher_release(&p->vt_switcher, release_vt, vo); if (modeset_open(vo, &p->fd, p->device_path)) goto err; if (modeset_prepare_dev(vo, p->fd, p->connector_id, &p->dev)) goto err; assert(p->dev); p->device_w = p->dev->bufs[0].width; p->device_h = p->dev->bufs[0].height; if (setup_vo_crtc(vo)) { MP_ERR(vo, "Cannot set CRTC for connector %u: %s\n", p->connector_id, mp_strerror(errno)); goto err; } return 0; err: uninit(vo); return -1; } static int query_format(struct vo *vo, int format) { return sws_isSupportedInput(imgfmt2pixfmt(format)); } static int control(struct vo *vo, uint32_t request, void *data) { struct priv *p = vo->priv; switch (request) { case VOCTRL_SCREENSHOT_WIN: *(struct mp_image**)data = mp_image_new_copy(p->cur_frame); return VO_TRUE; case VOCTRL_REDRAW_FRAME: draw_image(vo, p->last_input); return VO_TRUE; case VOCTRL_GET_PANSCAN: return VO_TRUE; case VOCTRL_SET_PANSCAN: if (vo->config_ok) reconfig(vo, vo->params, 0); return VO_TRUE; } return VO_NOTIMPL; } #define OPT_BASE_STRUCT struct priv const struct vo_driver video_out_drm = { .name = "drm", .description = "Direct Rendering Manager", .preinit = preinit, .query_format = query_format, .reconfig = reconfig, .control = control, .draw_image = draw_image, .flip_page = flip_page, .uninit = uninit, .wait_events = wait_events, .wakeup = wakeup, .priv_size = sizeof(struct priv), .options = (const struct m_option[]) { OPT_STRING("devpath", device_path, 0), OPT_INT("connector", connector_id, 0), {0}, }, .priv_defaults = &(const struct priv) {