diff --git a/Makefile.am b/Makefile.am index 1702b2e..d5387e1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -51,7 +51,9 @@ libkmscon_core_la_SOURCES = \ src/input.c src/input.h \ src/vte.c src/vte.h \ src/terminal.c src/terminal.h \ - src/pty.c src/pty.h + src/pty.c src/pty.h \ + src/uterm.h src/uterm_internal.h \ + src/uterm_video.c if USE_XKBCOMMON libkmscon_core_la_SOURCES += \ diff --git a/src/uterm.h b/src/uterm.h new file mode 100644 index 0000000..594a7f9 --- /dev/null +++ b/src/uterm.h @@ -0,0 +1,175 @@ +/* + * uterm - Linux User-Space Terminal + * + * Copyright (c) 2011-2012 David Herrmann + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Linux User-Space Terminal + * Historically, terminals were implemented in kernel-space on linux. With the + * development of KMS and the linux input-API it is now possible to implement + * all we need in user-space. This allows to disable the in-kernel CONFIG_VT and + * similar options and reduce the kernel-overhead. + * This library provides an API to implement terminals in user-space. This is + * not limited to classic text-terminals but rather to all kind of applications + * that need graphical output (with OpenGL) or direct keyboard/mouse/etc. input + * from the kernel. + * This API is divided into categories. Each sub-API implements one aspect. + * Currently, only video output is available. Other sub-APIs will follow. + */ + +#ifndef UTERM_UTERM_H +#define UTERM_UTERM_H + +#include +#include +#include "eloop.h" + +/* + * Video Control + * Linux provides 2 famous ways to access the video hardware: fbdev and drm + * fbdev is the older one of both and is simply a mmap() of the framebuffer into + * main memory. It does not allow 3D acceleration and if you need 2D + * acceleration you should use libraries like cairo to draw into the framebuffer + * provided by this library. + * DRM is the new approach which provides 3D acceleration with mesa. It allows + * much more configuration as fbdev and is the recommended way to access video + * hardware on modern computers. + * Modern mesa provides 3D acceleration on fbdev, too. This is used in systems + * like Android. This will allow us to provide an fbdev backend here, however, + * we haven't implemented this yet, as this requires the Android software stack + * on your system which is really not what we want. + * + * Famous linux graphics systems like X.Org/X11 or Wayland use fbdev or DRM + * internally to access the video hardware. This API allows low-level access to + * fbdev and DRM without the need of X.Org/X11 or Wayland. If VT support is + * enabled in your kernel, each application can run on a different VT. For + * instance, X.Org may run on VT-7, Wayland on VT-8, your application on VT-9 + * and default consoles on VT-1 to VT-6. You can switch between them with + * ctrl-alt-F1-F12. + * If VT support is not available (very unlikely) you need other ways to switch + * between applications. + * + * The main object by this API is uterm_video. This object scans the system for + * video devices, either fbdev or drm. You cannot use both simulatneously, + * though. This wouldn't make sense as often both fbdev and drm devices refer to + * the same output. Use "UTERM_VIDEO_DRM" to scan for DRM devices. Otherwise, + * fbdev is used. DRM is the recommended way. Use fbdev only on embedded devices + * which do not come with an DRM driver. + * The uterm_video object scans for graphic-cards and connected displays. Each + * display is represented as a uterm_display object. The uterm_video object is + * hotplug-capable so it reports if a display is connected or disconnected. + * Each uterm_display object can be activated/deactivated independently of the + * other displays. To draw to a display you need to create a uterm_screen object + * and add your display to the screen. The screen object allows to spread a + * single screen onto multiple displays. Currently, the uterm_screen object + * allows only one display per screen but we may extend this in the future. + * + * If you are using fbdev, you *must* correctly destroy your uterm_video object + * and also call uterm_video_segfault() if you abnormally abort your + * application. Otherwise your video device remains in undefined state and other + * applications might not display correctly. + * If you use DRM, the same operations are recommended but not required as the + * kernel can correctly reset video devices on its own. + */ + +struct uterm_screen; +struct uterm_mode; +struct uterm_display; +struct uterm_video; + +enum uterm_display_state { + UTERM_DISPLAY_ACTIVE, + UTERM_DISPLAY_ASLEEP, + UTERM_DISPLAY_INACTIVE, + UTERM_DISPLAY_GONE, +}; + +enum uterm_display_dpms { + UTERM_DPMS_ON, + UTERM_DPMS_STANDBY, + UTERM_DPMS_SUSPEND, + UTERM_DPMS_OFF, + UTERM_DPMS_UNKNOWN, +}; + +enum uterm_video_type { + UTERM_VIDEO_DRM, + UTERM_VIDEO_FBDEV, +}; + +/* misc */ + +const char *uterm_dpms_to_name(int dpms); + +/* screen interface */ + +int uterm_screen_new_single(struct uterm_screen **out, + struct uterm_display *disp); +void uterm_screen_ref(struct uterm_screen *screen); +void uterm_screen_unref(struct uterm_screen *screen); + +int uterm_screen_use(struct uterm_screen *screen); +int uterm_screen_swap(struct uterm_screen *screen); + +/* display modes interface */ + +void uterm_mode_ref(struct uterm_mode *mode); +void uterm_mode_unref(struct uterm_mode *mode); +struct uterm_mode *uterm_mode_next(struct uterm_mode *mode); + +const char *uterm_mode_get_name(const struct uterm_mode *mode); +unsigned int uterm_mode_get_width(const struct uterm_mode *mode); +unsigned int uterm_mode_get_height(const struct uterm_mode *mode); + +/* display interface */ + +void uterm_display_ref(struct uterm_display *disp); +void uterm_display_unref(struct uterm_display *disp); +struct uterm_display *uterm_display_next(struct uterm_display *disp); + +struct uterm_mode *uterm_display_get_modes(struct uterm_display *disp); +struct uterm_mode *uterm_display_get_current(struct uterm_display *disp); +struct uterm_mode *uterm_display_get_default(struct uterm_display *disp); + +int uterm_display_get_state(struct uterm_display *disp); +int uterm_display_activate(struct uterm_display *disp, struct uterm_mode *mode); +void uterm_display_deactivate(struct uterm_display *disp); +int uterm_display_set_dpms(struct uterm_display *disp, int state); +int uterm_display_get_dpms(const struct uterm_display *disp); + +/* video interface */ + +int uterm_video_new(struct uterm_video **out, + int type, + struct ev_eloop *eloop); +void uterm_video_ref(struct uterm_video *video); +void uterm_video_unref(struct uterm_video *video); + +void uterm_video_segfault(struct uterm_video *video); +struct uterm_display *uterm_video_get_displays(struct uterm_video *video); + +void uterm_video_sleep(struct uterm_video *video); +int uterm_video_wake_up(struct uterm_video *video); +bool uterm_video_is_awake(struct uterm_video *video); + +#endif /* UTERM_UTERM_H */ diff --git a/src/uterm_internal.h b/src/uterm_internal.h new file mode 100644 index 0000000..53387d2 --- /dev/null +++ b/src/uterm_internal.h @@ -0,0 +1,278 @@ +/* + * uterm - Linux User-Space Terminal + * + * Copyright (c) 2011-2012 David Herrmann + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Internal definitions */ + +#ifndef UTERM_INTERNAL_H +#define UTERM_INTERNAL_H + +#include +#include +#include +#include +#include "eloop.h" +#include "uterm.h" + +/* backend-operations */ + +struct mode_ops { + int (*init) (struct uterm_mode *mode); + void (*destroy) (struct uterm_mode *mode); + const char *(*get_name) (const struct uterm_mode *mode); + unsigned int (*get_width) (const struct uterm_mode *mode); + unsigned int (*get_height) (const struct uterm_mode *mode); +}; + +struct display_ops { + int (*init) (struct uterm_display *display); + void (*destroy) (struct uterm_display *display); + int (*activate) (struct uterm_display *disp, struct uterm_mode *mode); + void (*deactivate) (struct uterm_display *disp); + int (*set_dpms) (struct uterm_display *disp, int state); + int (*use) (struct uterm_display *disp); + int (*swap) (struct uterm_display *disp); +}; + +struct video_ops { + int (*init) (struct uterm_video *video); + void (*destroy) (struct uterm_video *video); + void (*segfault) (struct uterm_video *video); + int (*poll) (struct uterm_video *video, int mask); + void (*sleep) (struct uterm_video *video); + int (*wake_up) (struct uterm_video *video); +}; + +#define VIDEO_CALL(func, els, ...) (func ? func(__VA_ARGS__) : els) + +/* drm */ + +#ifdef UTERM_HAVE_DRM + +#include +#include +#include +#include +#include +#include +#include + +struct drm_mode { + drmModeModeInfo info; +}; + +struct drm_rb { + struct gbm_bo *bo; + uint32_t fb; + EGLImageKHR image; + GLuint rb; +}; + +struct drm_display { + uint32_t conn_id; + int crtc_id; + drmModeCrtc *saved_crtc; + + int current_rb; + struct drm_rb rb[2]; + GLuint fb; +}; + +struct drm_video { + int id; + int fd; + struct ev_fd *efd; + struct gbm_device *gbm; + EGLDisplay *disp; + EGLContext *ctx; +}; + +static const bool drm_available = true; +extern const struct mode_ops drm_mode_ops; +extern const struct display_ops drm_display_ops; +extern const struct video_ops drm_video_ops; + +#else /* !UTERM_HAVE_DRM */ + +struct drm_mode { + int unused; +}; + +struct drm_display { + int unused; +}; + +struct drm_video { + int unused; +}; + +static const bool drm_available = false; +static const struct mode_ops drm_mode_ops; +static const struct display_ops drm_display_ops; +static const struct video_ops drm_video_ops; + +#endif /* UTERM_HAVE_DRM */ + +/* fbdev */ + +#ifdef UTERM_HAVE_FBDEV + +#include + +struct fbdev_mode { + int unused; +}; + +struct fbdev_display { + int id; + char *node; + int fd; + + size_t len; + void *map; + unsigned int stride; + + struct fb_fix_screeninfo finfo; + struct fb_var_screeninfo vinfo; + unsigned int rate; +}; + +struct fbdev_video { + int unused; +}; + +static const bool fbdev_available = true; +extern const struct mode_ops fbdev_mode_ops; +extern const struct display_ops fbdev_display_ops; +extern const struct video_ops fbdev_video_ops; + +#else /* !UTERM_HAVE_FBDEV */ + +struct fbdev_mode { + int unused; +}; + +struct fbdev_display { + int unused; +}; + +struct fbdev_video { + int unused; +}; + +static const bool fbdev_available = false; +static const struct mode_ops fbdev_mode_ops; +static const struct display_ops fbdev_display_ops; +static const struct video_ops fbdev_video_ops; + +#endif /* UTERM_HAVE_FBDEV */ + +/* uterm_screen */ + +struct uterm_screen { + unsigned long ref; + struct uterm_display *disp; +}; + +/* uterm_mode */ + +struct uterm_mode { + unsigned long ref; + struct uterm_mode *next; + + const struct mode_ops *ops; + union { + struct drm_mode drm; + struct fbdev_mode fbdev; + }; +}; + +int mode_new(struct uterm_mode **out, const struct mode_ops *ops); + +/* uterm_display */ + +#define DISPLAY_ONLINE 0x01 +#define DISPLAY_VSYNC 0x02 +#define DISPLAY_AVAILABLE 0x04 +#define DISPLAY_OPEN 0x08 + +struct uterm_display { + unsigned long ref; + unsigned int flags; + struct uterm_display *next; + struct uterm_video *video; + + struct uterm_mode *modes; + struct uterm_mode *default_mode; + struct uterm_mode *current_mode; + int dpms; + + const struct display_ops *ops; + union { + struct drm_display drm; + struct fbdev_display fbdev; + }; +}; + +int display_new(struct uterm_display **out, const struct display_ops *ops); + +static inline bool display_is_conn(const struct uterm_display *disp) +{ + return disp->video; +} + +static inline bool display_is_online(const struct uterm_display *disp) +{ + return display_is_conn(disp) && (disp->flags & DISPLAY_ONLINE); +} + +/* uterm_video */ + +#define VIDEO_AWAKE 0x01 +#define VIDEO_HOTPLUG 0x02 + +struct uterm_video { + unsigned long ref; + unsigned int flags; + struct ev_eloop *eloop; + + struct udev *udev; + struct udev_monitor *umon; + struct ev_fd *umon_fd; + + struct uterm_display *displays; + + const struct video_ops *ops; + union { + struct drm_video drm; + struct fbdev_video fbdev; + }; +}; + +static inline bool video_is_awake(const struct uterm_video *video) +{ + return video->flags & VIDEO_AWAKE; +} + +#endif /* UTERM_INTERNAL_H */ diff --git a/src/uterm_video.c b/src/uterm_video.c new file mode 100644 index 0000000..30d1126 --- /dev/null +++ b/src/uterm_video.c @@ -0,0 +1,524 @@ +/* + * uterm - Linux User-Space Terminal + * + * Copyright (c) 2011-2012 David Herrmann + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Video Control + * Core Implementation of the uterm_video, uterm_display and uterm_screen + * objects. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "eloop.h" +#include "log.h" +#include "uterm.h" +#include "uterm_internal.h" + +#define LOG_SUBSYSTEM "video" + +const char *uterm_dpms_to_name(int dpms) +{ + switch (dpms) { + case UTERM_DPMS_ON: + return "ON"; + case UTERM_DPMS_STANDBY: + return "STANDBY"; + case UTERM_DPMS_SUSPEND: + return "SUSPEND"; + case UTERM_DPMS_OFF: + return "OFF"; + default: + return "UNKNOWN"; + } +} + +/* Until we allow multiple displays in one screen, we use this constructor which + * is basically just a wrapper around "struct uterm_dispaly". + * The idea behind screens is having one single drawing-target which is spread + * across several displays which can be placed anywhere in the virtual screen. + */ +int uterm_screen_new_single(struct uterm_screen **out, + struct uterm_display *disp) +{ + struct uterm_screen *screen; + + if (!out || !disp) + return -EINVAL; + + screen = malloc(sizeof(*screen)); + if (!screen) + return -ENOMEM; + memset(screen, 0, sizeof(*screen)); + screen->ref = 1; + screen->disp = disp; + + *out = screen; + return 0; +} + +void uterm_screen_ref(struct uterm_screen *screen) +{ + if (!screen || !screen->ref) + return; + + ++screen->ref; +} + +void uterm_screen_unref(struct uterm_screen *screen) +{ + if (!screen || !screen->ref || --screen->ref) + return; + + free(screen); +} + +int uterm_screen_use(struct uterm_screen *screen) +{ + if (!screen || !display_is_online(screen->disp)) + return -EINVAL; + + return VIDEO_CALL(screen->disp->ops->use, 0, screen->disp); +} + +int uterm_screen_swap(struct uterm_screen *screen) +{ + if (!screen || !display_is_online(screen->disp)) + return -EINVAL; + + return VIDEO_CALL(screen->disp->ops->swap, 0, screen->disp); +} + +int mode_new(struct uterm_mode **out, const struct mode_ops *ops) +{ + struct uterm_mode *mode; + int ret; + + if (!out || !ops) + return -EINVAL; + + mode = malloc(sizeof(*mode)); + if (!mode) + return -ENOMEM; + memset(mode, 0, sizeof(*mode)); + mode->ref = 1; + mode->ops = ops; + + ret = VIDEO_CALL(mode->ops->init, 0, mode); + if (ret) + goto err_free; + + *out = mode; + return 0; + +err_free: + free(mode); + return ret; +} + +void uterm_mode_ref(struct uterm_mode *mode) +{ + if (!mode || !mode->ref) + return; + + ++mode->ref; +} + +void uterm_mode_unref(struct uterm_mode *mode) +{ + if (!mode || !mode->ref || --mode->ref) + return; + + VIDEO_CALL(mode->ops->destroy, 0, mode); + free(mode); +} + +struct uterm_mode *uterm_mode_next(struct uterm_mode *mode) +{ + if (!mode) + return NULL; + + return mode->next; +} + +const char *uterm_mode_get_name(const struct uterm_mode *mode) +{ + if (!mode) + return NULL; + + return VIDEO_CALL(mode->ops->get_name, NULL, mode); +} + +unsigned int uterm_mode_get_width(const struct uterm_mode *mode) +{ + if (!mode) + return 0; + + return VIDEO_CALL(mode->ops->get_width, 0, mode); +} + +unsigned int uterm_mode_get_height(const struct uterm_mode *mode) +{ + if (!mode) + return 0; + + return VIDEO_CALL(mode->ops->get_height, 0, mode); +} + +int display_new(struct uterm_display **out, const struct display_ops *ops) +{ + struct uterm_display *disp; + int ret; + + if (!out || !ops) + return -EINVAL; + + disp = malloc(sizeof(*disp)); + if (!disp) + return -ENOMEM; + memset(disp, 0, sizeof(*disp)); + disp->ref = 1; + disp->ops = ops; + + ret = VIDEO_CALL(disp->ops->init, 0, disp); + if (ret) + goto err_free; + + log_info("new display %p", disp); + *out = disp; + return 0; + +err_free: + free(disp); + return ret; +} + +void uterm_display_ref(struct uterm_display *disp) +{ + if (!disp || !disp->ref) + return; + + ++disp->ref; +} + +void uterm_display_unref(struct uterm_display *disp) +{ + struct uterm_mode *mode; + + if (!disp || !disp->ref || --disp->ref) + return; + + log_info("free display %p", disp); + + VIDEO_CALL(disp->ops->destroy, 0, disp); + + while ((mode = disp->modes)) { + disp->modes = mode->next; + mode->next = NULL; + uterm_mode_unref(mode); + } + free(disp); +} + +struct uterm_display *uterm_display_next(struct uterm_display *disp) +{ + if (!disp) + return NULL; + + return disp->next; +} + +struct uterm_mode *uterm_display_get_modes(struct uterm_display *disp) +{ + if (!disp) + return NULL; + + return disp->modes; +} + +struct uterm_mode *uterm_display_get_current(struct uterm_display *disp) +{ + if (!disp) + return NULL; + + return disp->current_mode; +} + +struct uterm_mode *uterm_display_get_default(struct uterm_display *disp) +{ + if (!disp) + return NULL; + + return disp->default_mode; +} + +int uterm_display_get_state(struct uterm_display *disp) +{ + if (!disp) + return UTERM_DISPLAY_GONE; + + if (disp->video) { + if (disp->flags & DISPLAY_ONLINE) { + if (disp->video->flags & VIDEO_AWAKE) + return UTERM_DISPLAY_ACTIVE; + else + return UTERM_DISPLAY_ASLEEP; + } else { + return UTERM_DISPLAY_INACTIVE; + } + } else { + return UTERM_DISPLAY_GONE; + } +} + +int uterm_display_activate(struct uterm_display *disp, struct uterm_mode *mode) +{ + if (!disp || !display_is_conn(disp) || display_is_online(disp)) + return -EINVAL; + + if (!mode) + mode = disp->default_mode; + + return VIDEO_CALL(disp->ops->activate, 0, disp, mode); +} + +void uterm_display_deactivate(struct uterm_display *disp) +{ + if (!disp || !display_is_online(disp)) + return; + + VIDEO_CALL(disp->ops->deactivate, 0, disp); +} + +int uterm_display_set_dpms(struct uterm_display *disp, int state) +{ + if (!disp || !display_is_conn(disp)) + return -EINVAL; + + return VIDEO_CALL(disp->ops->set_dpms, 0, disp, state); +} + +int uterm_display_get_dpms(const struct uterm_display *disp) +{ + if (!disp || !display_is_conn(disp)) + return UTERM_DPMS_OFF; + + return disp->dpms; +} + +static void video_poll(struct ev_fd *fd, int mask, void *data) +{ + struct uterm_video *video = data; + int ret; + + ret = VIDEO_CALL(video->ops->poll, 0, video, mask); + if (ret) { + ev_eloop_rm_fd(video->umon_fd); + video->umon_fd = NULL; + } +} + +static volatile int video_protect = 0; + +int uterm_video_new(struct uterm_video **out, + int type, + struct ev_eloop *eloop) +{ + struct uterm_video *video; + int ret, ufd; + const struct video_ops *ops; + + /* + * We allow only one global video object. The reason behind this is that + * the OpenGL API has thread-awareness and we want to hide this behind + * our API. Otherwise, the caller would need to manage GL-contexts + * himself and this makes the API complex. Furthermore, there is really + * no reason why someone wants two video objects in the same process. + * That would mean that you want to control two graphic-cards in one + * process and we have never heard of a situation where this was needed. + * If you think you need two video objects, feel free to contact us and + * we might add a proper GL-context management API. + * But for now we protect this by a GCC-atomic. *_unref() decrements the + * counter again. + */ + ret = __sync_fetch_and_add(&video_protect, 1); + if (ret) { + __sync_fetch_and_sub(&video_protect, 1); + log_err("cannot create multiple instances"); + return -ENOTSUP; + } + + if (!out || !eloop) + return -EINVAL; + + switch (type) { + case UTERM_VIDEO_DRM: + if (!drm_available) { + log_err("DRM backend is not available"); + return -ENOTSUP; + } + ops = &drm_video_ops; + break; + case UTERM_VIDEO_FBDEV: + if (!fbdev_available) { + log_err("FBDEV backend is not available"); + return -ENOTSUP; + } + ops = &fbdev_video_ops; + break; + default: + log_err("invalid video backend %d", type); + return -EINVAL; + } + + video = malloc(sizeof(*video)); + if (!video) + return -ENOMEM; + memset(video, 0, sizeof(*video)); + video->ref = 1; + video->ops = ops; + video->eloop = eloop; + + video->udev = udev_new(); + if (!video->udev) { + log_err("cannot create udev object"); + ret = -EFAULT; + goto err_free; + } + + video->umon = udev_monitor_new_from_netlink(video->udev, "udev"); + if (!video->umon) { + log_err("cannot create udev monitor"); + ret = -EFAULT; + goto err_udev; + } + + ufd = udev_monitor_get_fd(video->umon); + if (ufd < 0) { + log_err("cannot get udev-monitor fd"); + ret = -EFAULT; + goto err_umon; + } + + ret = ev_eloop_new_fd(video->eloop, &video->umon_fd, ufd, + EV_READABLE, video_poll, video); + if (ret) + goto err_umon; + + ret = VIDEO_CALL(video->ops->init, 0, video); + if (ret) + goto err_umon_add; + + ev_eloop_ref(video->eloop); + log_info("new device %p", video); + *out = video; + return 0; + +err_umon_add: + ev_eloop_rm_fd(video->umon_fd); +err_umon: + udev_monitor_unref(video->umon); +err_udev: + udev_unref(video->udev); +err_free: + free(video); + return ret; +} + +void uterm_video_ref(struct uterm_video *video) +{ + if (!video || !video->ref) + return; + + ++video->ref; +} + +void uterm_video_unref(struct uterm_video *video) +{ + struct uterm_display *disp; + + if (!video || !video->ref || --video->ref) + return; + + log_info("free device %p", video); + + VIDEO_CALL(video->ops->destroy, 0, video); + + while ((disp = video->displays)) { + video->displays = disp->next; + disp->next = NULL; + uterm_display_unref(disp); + } + + ev_eloop_rm_fd(video->umon_fd); + udev_monitor_unref(video->umon); + udev_unref(video->udev); + ev_eloop_unref(video->eloop); + free(video); + __sync_fetch_and_sub(&video_protect, 1); +} + +void uterm_video_segfault(struct uterm_video *video) +{ + if (!video) + return; + + VIDEO_CALL(video->ops->segfault, 0, video); +} + +struct uterm_display *uterm_video_get_displays(struct uterm_video *video) +{ + if (!video) + return NULL; + + return video->displays; +} + +void uterm_video_sleep(struct uterm_video *video) +{ + if (!video || !video_is_awake(video)) + return; + + VIDEO_CALL(video->ops->sleep, 0, video); +} + +int uterm_video_wake_up(struct uterm_video *video) +{ + if (!video) + return -EINVAL; + if (video_is_awake(video)) + return 0; + + return VIDEO_CALL(video->ops->wake_up, 0, video); +} + +bool uterm_video_is_awake(struct uterm_video *video) +{ + return video && video_is_awake(video); +}