Add output subsystem
authorDavid Herrmann <dh.herrmann@googlemail.com>
Sat, 19 Nov 2011 23:09:39 +0000 (00:09 +0100)
committerDavid Herrmann <dh.herrmann@googlemail.com>
Sat, 19 Nov 2011 23:09:39 +0000 (00:09 +0100)
The output subsystem manages the connected monitors, provides framebuffers and
OpenGL contexts and handles all DRM/DRI/KMS related functionality.

Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
src/output.c [new file with mode: 0644]
src/output.h [new file with mode: 0644]

diff --git a/src/output.c b/src/output.c
new file mode 100644 (file)
index 0000000..4c051e4
--- /dev/null
@@ -0,0 +1,1072 @@
+/*
+ * kmscon - KMS/DRI output handling
+ * Written 2011 by David Herrmann <dh.herrmann@googlemail.com>
+ */
+
+/*
+ * KMS/DRI Output Handling
+ * This provides the compositor, output and mode objects and creates OpenGL
+ * contexts available for drawing directly to the graphics framebuffer.
+ */
+
+/*
+ * TODO: Avoid using this hack and instead retrieve EGL and GL extension
+ * pointers dynamically on initialization.
+ */
+#define EGL_EGLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <gbm.h>
+#include <GL/gl.h>
+#include <GL/glext.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include "output.h"
+
+struct kmscon_mode {
+       size_t ref;
+       struct kmscon_mode *next;
+       struct kmscon_output *output;
+
+       drmModeModeInfo info;
+};
+
+struct render_buffer {
+       GLuint rb;
+       struct gbm_bo *bo;
+       EGLImageKHR image;
+       uint32_t fb;
+};
+
+struct kmscon_output {
+       size_t ref;
+       struct kmscon_output *next;
+       struct kmscon_compositor *comp;
+
+       /* temporary flag used in compositor_refresh */
+       unsigned int available : 1;
+       /* flag which indicates whether the output is connected */
+       unsigned int connected : 1;
+       /* flag which indicates whether the output is active */
+       unsigned int active : 1;
+
+       size_t count_modes;
+       struct kmscon_mode *modes;
+       struct kmscon_mode *current;
+
+       uint32_t conn_id;
+       uint32_t crtc_id;
+
+       unsigned int cur_rb;
+       struct render_buffer rb[2];
+       GLuint fb;
+};
+
+enum compositor_state {
+       COMPOSITOR_ASLEEP,
+       COMPOSITOR_AWAKE,
+};
+
+struct kmscon_compositor {
+       size_t ref;
+       int state;
+
+       size_t count_outputs;
+       struct kmscon_output *outputs;
+
+       int drm_fd;
+       struct gbm_device *gbm;
+       EGLDisplay display;
+       EGLContext context;
+};
+
+/*
+ * Creates a new output mode. This mode is not bound to any output and all
+ * values are initialized to zero.
+ * Returns 0 on success and copies a pointer to the object into \out.
+ * Otherwise returns negative error code.
+ */
+int kmscon_mode_new(struct kmscon_mode **out)
+{
+       struct kmscon_mode *mode;
+
+       if (!out)
+               return -EINVAL;
+
+       mode = malloc(sizeof(*mode));
+       if (!mode)
+               return -ENOMEM;
+
+       memset(mode, 0, sizeof(*mode));
+       mode->ref = 1;
+
+       *out = mode;
+       return 0;
+}
+
+void kmscon_mode_ref(struct kmscon_mode *mode)
+{
+       if (!mode)
+               return;
+
+       ++mode->ref;
+}
+
+void kmscon_mode_unref(struct kmscon_mode *mode)
+{
+       if (!mode || !mode->ref)
+               return;
+
+       if (--mode->ref)
+               return;
+
+       /*
+        * The mode is always unbound at this time, because mode_bind takes a
+        * reference and mode_unbind releases it.
+        */
+
+       free(mode);
+}
+
+/*
+ * Binds the mode to an output. Even though this is called "mode"_bind, its the
+ * output object that owns the mode, not vice versa!
+ * The output object must go sure that it unbinds all modes before destroying
+ * itself.
+ * Binding a mode does not mean using it. This only links it into the list of
+ * available modes. The output must set the values of the mode directly. By
+ * default they are set to 0/NULL.
+ * Returns 0 on success or negative error code on failure.
+ */
+static int kmscon_mode_bind(struct kmscon_mode *mode,
+                                               struct kmscon_output *output)
+{
+       if (!mode || !output)
+               return -EINVAL;
+
+       if (mode->output || mode->next)
+               return -EALREADY;
+
+       mode->next = output->modes;
+       output->modes = mode;
+       ++output->count_modes;
+
+       mode->output = output;
+       kmscon_mode_ref(mode);
+
+       return 0;
+}
+
+/*
+ * This unbinds the mode from its output. If the mode is currently active, then
+ * this function will return -EBUSY. Otherwise it returns 0.
+ */
+static int kmscon_mode_unbind(struct kmscon_mode *mode)
+{
+       struct kmscon_mode *iter;
+       struct kmscon_output *output;
+
+       if (!mode || !mode->output)
+               return 0;
+
+       output = mode->output;
+
+       if (output->current == mode)
+               return -EBUSY;
+
+       if (output->modes == mode) {
+               output->modes = output->modes->next;
+       } else if (output->modes) {
+               for (iter = output->modes; iter->next; iter = iter->next) {
+                       if (iter->next == mode) {
+                               iter->next = mode->next;
+                               break;
+                       }
+               }
+       }
+
+       mode->next = NULL;
+       mode->output = NULL;
+       --output->count_modes;
+       kmscon_mode_unref(mode);
+
+       return 0;
+}
+
+struct kmscon_mode *kmscon_mode_next(struct kmscon_mode *mode)
+{
+       if (!mode)
+               return NULL;
+
+       return mode->next;
+}
+
+const char *kmscon_mode_get_name(const struct kmscon_mode *mode)
+{
+       if (!mode)
+               return NULL;
+
+       return mode->info.name;
+}
+
+uint32_t kmscon_mode_get_width(const struct kmscon_mode *mode)
+{
+       if (!mode)
+               return 0;
+
+       return mode->info.hdisplay;
+}
+
+uint32_t kmscon_mode_get_height(const struct kmscon_mode *mode)
+{
+       if (!mode)
+               return 0;
+
+       return mode->info.vdisplay;
+}
+
+/*
+ * Creates a new output object. The returned raw output object is useless
+ * unless you bind it to a compositor, connect it to the DRM and activate it.
+ * Returns 0 on success, otherwise a negative error code.
+ */
+int kmscon_output_new(struct kmscon_output **out)
+{
+       struct kmscon_output *output;
+
+       if (!out)
+               return -EINVAL;
+
+       output = malloc(sizeof(*output));
+       if (!output)
+               return -ENOMEM;
+
+       memset(output, 0, sizeof(*output));
+       output->ref = 1;
+
+       *out = output;
+       return 0;
+}
+
+void kmscon_output_ref(struct kmscon_output *output)
+{
+       if (!output)
+               return;
+
+       ++output->ref;
+}
+
+/*
+ * Drops a reference. All connected modes should already be removed when the
+ * output is unbound so no cleanup needs to be done here.
+ */
+void kmscon_output_unref(struct kmscon_output *output)
+{
+       if (!output || !output->ref)
+               return;
+
+       if (--output->ref)
+               return;
+
+       /*
+        * Output is already deactivated because output_bind takes
+        * a reference and output_unbind drops it.
+        * output->current is also NULL then.
+        */
+
+       free(output);
+}
+
+/*
+ * This binds the output to the given compositor. If the output is already
+ * bound, this will fail with -EALREADY.
+ * This only links the output into the list of available outputs, it does not
+ * activate the output or connect an crtc, nor does it create a framebuffer.
+ */
+static int kmscon_output_bind(struct kmscon_output *output,
+                                               struct kmscon_compositor *comp)
+{
+       if (!output || !comp)
+               return -EINVAL;
+
+       if (output->comp || output->next)
+               return -EALREADY;
+
+       output->next = comp->outputs;
+       comp->outputs = output;
+       ++comp->count_outputs;
+
+       output->comp = comp;
+       kmscon_output_ref(output);
+
+       return 0;
+}
+
+/*
+ * This unbinds the output from its compositor. If the output is currently
+ * active, then it is deactivated first. The DRM connection is also removed so
+ * the object is quite useless now unless you reconnect it.
+ */
+static void kmscon_output_unbind(struct kmscon_output *output)
+{
+       struct kmscon_output *iter;
+       struct kmscon_compositor *comp;
+
+       if (!output || !output->comp)
+               return;
+
+       /* deactivate and disconnect the output */
+       kmscon_output_deactivate(output);
+       output->connected = 0;
+       while (output->modes)
+               kmscon_mode_unbind(output->modes);
+
+       comp = output->comp;
+
+       if (comp->outputs == output) {
+               comp->outputs = comp->outputs->next;
+       } else if (comp->outputs) {
+               for (iter = comp->outputs; iter->next; iter = iter->next) {
+                       if (iter->next == output) {
+                               iter->next = output->next;
+                               break;
+                       }
+               }
+       }
+
+       output->next = NULL;
+       output->comp = NULL;
+       --comp->count_outputs;
+       kmscon_output_unref(output);
+}
+
+/*
+ * Finds an available unused crtc for the given encoder. Returns -1 if none is
+ * found. Otherwise returns the non-negative crtc id.
+ */
+static int32_t find_crtc(struct kmscon_compositor *comp, drmModeRes *res,
+                                                       drmModeEncoder *enc)
+{
+       int i;
+       struct kmscon_output *iter;
+       uint32_t crtc;
+
+       for (i = 0; i < res->count_crtcs; ++i) {
+               if (enc->possible_crtcs & (1 << i)) {
+                       crtc = res->crtcs[i];
+
+                       /* check that the crtc is unused */
+                       for (iter = comp->outputs; iter; iter = iter->next) {
+                               if (iter->connected && iter->crtc_id == crtc)
+                                       break;
+                       }
+
+                       if (!iter)
+                               break;
+               }
+       }
+
+       if (i == res->count_crtcs)
+               return -1;
+
+       return crtc;
+}
+
+/*
+ * This connects the given output with the drm connector/crtc/encoder. This can
+ * only be called once on a bound output. It will fail if it is called again
+ * unless you unbind and rebind the object.
+ * If the given drm connector is invalid or cannot be initialized, then this
+ * function returns an appropriate negative error code. Returns 0 on success.
+ *
+ * This does not create any framebuffer or renderbuffers. It only reads the
+ * available data so the application can retrieve information about the output.
+ * The application can now activate and deactivate the output as often as it
+ * wants.
+ *
+ * This does not work if the bound compositor is asleep!
+ */
+static int kmscon_output_connect(struct kmscon_output *output, drmModeRes *res,
+                                                       drmModeConnector *conn)
+{
+       struct kmscon_compositor *comp;
+       struct kmscon_mode *mode;
+       drmModeEncoder *enc;
+       int ret, i;
+       int32_t crtc = -1;
+
+       if (!output || !output->comp || !conn->count_modes)
+               return -EINVAL;
+
+       if (kmscon_compositor_is_asleep(output->comp))
+               return -EINVAL;
+
+       if (output->connected)
+               return -EALREADY;
+
+       comp = output->comp;
+
+       /* find unused crtc */
+       for (i = 0; i < conn->count_encoders; ++i) {
+               enc = drmModeGetEncoder(comp->drm_fd, conn->encoders[i]);
+               if (!enc)
+                       continue;
+
+               crtc = find_crtc(comp, res, enc);
+               drmModeFreeEncoder(enc);
+               if (crtc >= 0)
+                       break;
+       }
+
+       if (crtc < 0)
+               return -EINVAL;
+
+       /* copy all modes into the output modes-list */
+       for (i = 0; i < conn->count_modes; ++i) {
+               ret = kmscon_mode_new(&mode);
+               if (ret)
+                       continue;
+
+               ret = kmscon_mode_bind(mode, output);
+               if (ret) {
+                       kmscon_mode_unref(mode);
+                       continue;
+               }
+
+               mode->info = conn->modes[i];
+               kmscon_mode_unref(mode);
+       }
+
+       if (!output->count_modes)
+               return -EINVAL;
+
+       output->conn_id = conn->connector_id;
+       output->crtc_id = crtc;
+       output->connected = 1;
+
+       return 0;
+}
+
+/*
+ * Returns the next output in the list. If there is no next output or the
+ * output is not bound to any compositor, then it returns NULL.
+ * This does not take a reference of the next output nor drop a reference of
+ * the current output.
+ */
+struct kmscon_output *kmscon_output_next(struct kmscon_output *output)
+{
+       if (!output)
+               return NULL;
+
+       return output->next;
+}
+
+/*
+ * Returns the first entry in the list of available modes at this output. This
+ * does not take a reference of the returned mode so you shouldn't call unref
+ * on it unless you called *_ref earlier.
+ * Returns NULL if the list is empty.
+ */
+struct kmscon_mode *kmscon_output_get_modes(struct kmscon_output *output)
+{
+       if (!output)
+               return NULL;
+
+       return output->modes;
+}
+
+/*
+ * Returns a pointer to the currently used mode. Returns NULL if no mode is
+ * currently active.
+ */
+struct kmscon_mode *kmscon_output_get_current(struct kmscon_output *output)
+{
+       if (!output)
+               return NULL;
+
+       return output->current;
+}
+
+static int init_rb(struct render_buffer *rb, struct kmscon_compositor *comp,
+                                                       drmModeModeInfo *mode)
+{
+       int ret;
+       unsigned int stride, handle;
+
+       rb->bo = gbm_bo_create(comp->gbm, mode->hdisplay, mode->vdisplay,
+                               GBM_BO_FORMAT_XRGB8888,
+                               GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
+       if (!rb->bo)
+               return -EFAULT;
+
+       rb->image = eglCreateImageKHR(comp->display, NULL,
+                                       EGL_NATIVE_PIXMAP_KHR, rb->bo, NULL);
+       if (!rb->image) {
+               ret = -EFAULT;
+               goto err_bo;
+       }
+
+       glGenRenderbuffers(1, &rb->rb);
+       glBindRenderbuffer(GL_RENDERBUFFER, rb->rb);
+       glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, rb->image);
+
+       stride = gbm_bo_get_pitch(rb->bo);
+       handle = gbm_bo_get_handle(rb->bo).u32;
+
+       /*
+        * TODO: Is there a way to choose 24/32 dynamically without hard-coding
+        * these values here?
+        */
+       ret = drmModeAddFB(comp->drm_fd, mode->hdisplay, mode->vdisplay,
+                                       24, 32, stride, handle, &rb->fb);
+       if (ret) {
+               ret = -EFAULT;
+               goto err_rb;
+       }
+
+       return 0;
+
+err_rb:
+       glBindRenderbuffer(GL_RENDERBUFFER, 0);
+       glDeleteRenderbuffers(1, &rb->rb);
+       eglDestroyImageKHR(comp->display, rb->image);
+err_bo:
+       gbm_bo_destroy(rb->bo);
+       return ret;
+}
+
+static void destroy_rb(struct render_buffer *rb,
+                                       struct kmscon_compositor *comp)
+{
+       drmModeRmFB(comp->drm_fd, rb->fb);
+       glBindRenderbuffer(GL_RENDERBUFFER, 0);
+       glDeleteRenderbuffers(1, &rb->rb);
+       eglDestroyImageKHR(comp->display, rb->image);
+       gbm_bo_destroy(rb->bo);
+}
+
+/*
+ * This activates the output in the given mode. This returns -EALREADY if the
+ * output is already activated. To switch modes, deactivate and then reactivate
+ * the output.
+ * Returns 0 on success.
+ * This does not work if the compositor is asleep.
+ */
+int kmscon_output_activate(struct kmscon_output *output,
+                                               struct kmscon_mode *mode)
+{
+       struct kmscon_compositor *comp;
+       int ret;
+
+       if (!output || !output->comp || !output->connected || !output->modes)
+               return -EINVAL;
+
+       if (kmscon_compositor_is_asleep(output->comp))
+               return -EINVAL;
+
+       if (output->active)
+               return -EALREADY;
+
+       if (!mode)
+               mode = output->modes;
+
+       comp = output->comp;
+
+       ret = init_rb(&output->rb[0], comp, &mode->info);
+       if (ret)
+               return ret;
+
+       ret = init_rb(&output->rb[1], comp, &mode->info);
+       if (ret) {
+               destroy_rb(&output->rb[0], comp);
+               return ret;
+       }
+
+       output->current = mode;
+       output->active = 1;
+       output->cur_rb = 0;
+       glGenFramebuffers(1, &output->fb);
+       glBindFramebuffer(GL_FRAMEBUFFER, output->fb);
+       glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                       GL_RENDERBUFFER, output->rb[0].rb);
+
+       if (glCheckFramebufferStatus(GL_FRAMEBUFFER) !=
+                                               GL_FRAMEBUFFER_COMPLETE) {
+               ret = -EFAULT;
+               goto err_fb;
+       }
+
+       glViewport(0, 0, mode->info.hdisplay, mode->info.vdisplay);
+       glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+       glClear(GL_COLOR_BUFFER_BIT);
+
+       ret = kmscon_output_swap(output);
+       if (ret)
+               goto err_fb;
+
+       return 0;
+
+err_fb:
+       glBindFramebuffer(GL_FRAMEBUFFER, 0);
+       glDeleteFramebuffers(1, &output->fb);
+       destroy_rb(&output->rb[0], output->comp);
+       destroy_rb(&output->rb[1], output->comp);
+       output->active = 0;
+       output->current = NULL;
+       return ret;
+}
+
+/*
+ * Deactivate the output. This does not disconnect the output so you can
+ * reactivate this output again.
+ */
+void kmscon_output_deactivate(struct kmscon_output *output)
+{
+       if (!output || !output->active)
+               return;
+
+       /*
+        * TODO: Do we need to reset the CRTC/connector here? We delete an
+        * active framebuffer here so the crtc will become blank, but instead
+        * the previous framebuffer should be restored.
+        */
+
+       glBindFramebuffer(GL_FRAMEBUFFER, 0);
+       glDeleteFramebuffers(1, &output->fb);
+       destroy_rb(&output->rb[0], output->comp);
+       destroy_rb(&output->rb[1], output->comp);
+       output->current = NULL;
+       output->active = 0;
+}
+
+/*
+ * Returns true if the output is currently active. Otherwise returns false.
+ */
+bool kmscon_output_is_active(struct kmscon_output *output)
+{
+       if (!output)
+               return false;
+
+       return output->active;
+}
+
+/*
+ * Binds the framebuffer of this output and sets a valid viewport so you can
+ * start drawing to this output.
+ * This does not work if the compositor is asleep. Returns 0 on success.
+ */
+int kmscon_output_use(struct kmscon_output *output)
+{
+       if (!output || !output->active)
+               return -EINVAL;
+
+       if (kmscon_compositor_is_asleep(output->comp))
+               return -EINVAL;
+
+       glBindFramebuffer(GL_FRAMEBUFFER, output->fb);
+       glViewport(0, 0, output->current->info.hdisplay,
+                                       output->current->info.vdisplay);
+
+       return 0;
+}
+
+/*
+ * This swaps the two renderbuffers and displays the new front buffer on the
+ * screen. This does not work if the compositor is asleep.
+ * This automatically binds the framebuffer of the output so you do not need to
+ * call kmscon_output_use after calling this even if another framebuffer was
+ * bound before.
+ * Returns 0 on success.
+ */
+int kmscon_output_swap(struct kmscon_output *output)
+{
+       int ret;
+
+       if (!output || !output->active)
+               return -EINVAL;
+
+       if (kmscon_compositor_is_asleep(output->comp))
+               return -EINVAL;
+
+       glBindFramebuffer(GL_FRAMEBUFFER, output->fb);
+       glFinish();
+
+       ret = drmModeSetCrtc(output->comp->drm_fd, output->crtc_id,
+               output->rb[output->cur_rb].fb, 0, 0, &output->conn_id, 1,
+                                               &output->current->info);
+       if (ret)
+               ret = -EFAULT;
+
+       output->cur_rb ^= 1;
+       glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                       GL_RENDERBUFFER, output->rb[output->cur_rb].rb);
+
+       if (glCheckFramebufferStatus(GL_FRAMEBUFFER) !=
+                                               GL_FRAMEBUFFER_COMPLETE)
+               ret = -EFAULT;
+
+       return ret;
+}
+
+/*
+ * Initializes the compositor object. This opens the DRI device, initializes
+ * EGL and creates a GL context. It does not activate the GL context. You need
+ * to call kmscon_compositor_use() to activate the context.
+ * Returns 0 on success.
+ */
+static int compositor_init(struct kmscon_compositor *comp)
+{
+       EGLint major, minor;
+       int ret;
+       const char *ext;
+
+       comp->state = COMPOSITOR_ASLEEP;
+
+       /* TODO: Retrieve this path dynamically */
+       comp->drm_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
+       if (comp->drm_fd < 0)
+               return -errno;
+
+       /* TODO: When do we need this? */
+       /*ret = drmSetMaster(comp->drm_fd);
+       if (ret) {
+               ret = -EACCES;
+               goto err_drm;
+       }*/
+
+       comp->gbm = gbm_create_device(comp->drm_fd);
+       if (!comp->gbm) {
+               ret = -EFAULT;
+               goto err_drm;
+       }
+
+       comp->display = eglGetDisplay((EGLNativeDisplayType)comp->gbm);
+       if (!comp->display) {
+               ret = -EFAULT;
+               goto err_gbm;
+       }
+
+       ret = eglInitialize(comp->display, &major, &minor);
+       if (!ret) {
+               ret = -EFAULT;
+               goto err_gbm;
+       }
+
+       ext = eglQueryString(comp->display, EGL_EXTENSIONS);
+       if (!ext || !strstr(ext, "EGL_KHR_surfaceless_opengl")) {
+               ret = -ENOTSUP;
+               goto err_display;
+       }
+
+       if (!eglBindAPI(EGL_OPENGL_API)) {
+               ret = -EFAULT;
+               goto err_display;
+       }
+
+       comp->context = eglCreateContext(comp->display, NULL,
+                                                       EGL_NO_CONTEXT, NULL);
+       if (!comp->context) {
+               ret = -EFAULT;
+               goto err_display;
+       }
+
+       return 0;
+
+err_display:
+       eglTerminate(comp->display);
+err_gbm:
+       gbm_device_destroy(comp->gbm);
+err_drm:
+       close(comp->drm_fd);
+       return ret;
+}
+
+/*
+ * Counterpart of compositor_init(). Must not be called if compositor_init()
+ * failed.
+ */
+static void compositor_deinit(struct kmscon_compositor *comp)
+{
+       while (comp->outputs)
+               kmscon_output_unbind(comp->outputs);
+
+       eglDestroyContext(comp->display, comp->context);
+       eglTerminate(comp->display);
+       gbm_device_destroy(comp->gbm);
+       close(comp->drm_fd);
+}
+
+/*
+ * Create a new compositor object. A GL context is created but the
+ * compositor is asleep by default so no outputs are connected.
+ */
+int kmscon_compositor_new(struct kmscon_compositor **out)
+{
+       struct kmscon_compositor *comp;
+       int ret;
+
+       if (!out)
+               return -EINVAL;
+
+       comp = malloc(sizeof(*comp));
+       if (!comp)
+               return -ENOMEM;
+
+       memset(comp, 0, sizeof(*comp));
+       comp->ref = 1;
+
+       ret = compositor_init(comp);
+       if (ret) {
+               free(comp);
+               return ret;
+       }
+
+       *out = comp;
+       return 0;
+}
+
+void kmscon_compositor_ref(struct kmscon_compositor *comp)
+{
+       if (!comp)
+               return;
+
+       ++comp->ref;
+}
+
+/*
+ * Drops a compositor reference. This automatically disconnects all outputs if
+ * the last reference is dropped.
+ */
+void kmscon_compositor_unref(struct kmscon_compositor *comp)
+{
+       if (!comp || !comp->ref)
+               return;
+
+       if (--comp->ref)
+               return;
+
+       compositor_deinit(comp);
+       free(comp);
+}
+
+/*
+ * This puts the compositor asleep. While the compositor is asleep, no access
+ * to the DRI are made so other applications may use the DRM.
+ * You shouldn't access the compositor and its outputs while it is asleep as
+ * almost all functions will return -EINVAL while asleep.
+ */
+void kmscon_compositor_sleep(struct kmscon_compositor *comp)
+{
+       if (comp)
+               comp->state = COMPOSITOR_ASLEEP;
+}
+
+/*
+ * This wakes up the compositor. It automatically calls
+ * kmscon_compositor_refresh(). If this function fails, the compositor is kept
+ * asleep.
+ * Returns the number of detected outputs on success or a negative error code
+ * on failure.
+ */
+int kmscon_compositor_wake_up(struct kmscon_compositor *comp)
+{
+       int ret;
+
+       if (!comp)
+               return -EINVAL;
+       
+       if (comp->state == COMPOSITOR_AWAKE)
+               return comp->count_outputs;
+
+       comp->state = COMPOSITOR_AWAKE;
+       ret = kmscon_compositor_refresh(comp);
+       if (ret >= 0)
+               return ret;
+
+       comp->state = COMPOSITOR_ASLEEP;
+       return ret;
+}
+
+/*
+ * Returns true if the compositor is asleep. Returns false if the compositor is
+ * awake.
+ */
+bool kmscon_compositor_is_asleep(struct kmscon_compositor *comp)
+{
+       if (!comp)
+               return false;
+
+       return comp->state == COMPOSITOR_ASLEEP;
+}
+
+/*
+ * This activates the EGL/GL context of this compositor. This fails if the
+ * compositor is asleep.
+ * You must call this before trying to enable outputs. A new compositor is not
+ * enabled by default.
+ * Returns 0 on success.
+ *
+ * If you have multiple compositors or GL contexts, you must take into account
+ * that only one context can be active at a time. It is not recommended to have
+ * different contexts in different threads.
+ */
+int kmscon_compositor_use(struct kmscon_compositor *comp)
+{
+       if (kmscon_compositor_is_asleep(comp))
+               return -EINVAL;
+
+       if (!eglMakeCurrent(comp->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+                                                       comp->context))
+               return -EFAULT;
+
+       return 0;
+}
+
+/*
+ * Returns the DRM file descriptor or -1 on failure.
+ * The DRM descriptor can be used to react on DRI events. This descriptor must
+ * not be closed or modified in any way by the application. The only thing you
+ * are allowed to do is watching for events via epoll, select or similar.
+ */
+int kmscon_compositor_get_fd(struct kmscon_compositor *comp)
+{
+       if (!comp)
+               return -1;
+
+       return comp->drm_fd;
+}
+
+/*
+ * Returns a pointer to the first output that is bound to the compositor. You
+ * can use kmscon_output_next() to iterate through the single linked list of
+ * outputs.
+ * Returns NULL if the list is empty or on failure.
+ * You do *NOT* own a reference to the returned output. If you want to keep the
+ * pointer you *MUST* call kmscon_output_ref() on it. Otherwise, you must not
+ * call kmscon_output_unref() on the returned object.
+ * This is because you are considered to own the compositor and guarantee that
+ * the compositor is not destroyed while you iterate the list. The compositor
+ * itself owns a reference of all its outputs so there is no need to increase
+ * this every time you iterate the list.
+ *
+ * This works even if the compositor is asleep.
+ */
+struct kmscon_output *kmscon_compositor_get_outputs(
+                                       struct kmscon_compositor *comp)
+{
+       if (!comp)
+               return NULL;
+
+       return comp->outputs;
+}
+
+static int add_output(struct kmscon_compositor *comp, drmModeRes *res,
+                                                       drmModeConnector *conn)
+{
+       struct kmscon_output *output;
+       int ret;
+
+       ret = kmscon_output_new(&output);
+       if (ret)
+               return ret;
+
+       ret = kmscon_output_bind(output, comp);
+       if (ret)
+               goto err_unref;
+
+       ret = kmscon_output_connect(output, res, conn);
+       if (ret)
+               goto err_unbind;
+
+       output->available = 1;
+       kmscon_output_unref(output);
+       return 0;
+
+err_unbind:
+       kmscon_output_unbind(output);
+err_unref:
+       kmscon_output_unref(output);
+       return ret;
+}
+
+/*
+ * Refreshs the list of available outputs. This returns -EINVAL if the
+ * compositor is asleep.
+ * All currently connected outputs that are still available are left untouched.
+ * If an output is no longer available, it is disconnected and unbound from the
+ * compositor. You should no longer use it and drop all your references.
+ *
+ * New monitors are automatically added into the list of outputs and all
+ * available modes are added. The outputs are left deactivated, though. You
+ * should reiterate the output list and activate new outputs if you want
+ * hotplug support.
+ *
+ * Returns the number of available outputs on success and negative error code
+ * on failure.
+ */
+int kmscon_compositor_refresh(struct kmscon_compositor *comp)
+{
+       drmModeConnector *conn;
+       drmModeRes *res;
+       size_t i;
+       int cid;
+       struct kmscon_output *output, *tmp;
+
+       if (!comp || comp->state != COMPOSITOR_AWAKE)
+               return -EINVAL;
+
+       res = drmModeGetResources(comp->drm_fd);
+       if (!res)
+               return -EACCES;
+
+       for (output = comp->outputs; output; output = output->next)
+               output->available = 0;
+
+       for (i = 0; i < res->count_connectors; ++i) {
+               cid = res->connectors[i];
+               conn = drmModeGetConnector(comp->drm_fd, cid);
+               if (!conn)
+                       continue;
+
+               if (conn->connection == DRM_MODE_CONNECTED) {
+                       for (output = comp->outputs; output;
+                                               output = output->next) {
+                               if (output->conn_id == cid) {
+                                       output->available = 1;
+                                       break;
+                               }
+                       }
+
+                       if (!output)
+                               add_output(comp, res, conn);
+               }
+
+               drmModeFreeConnector(conn);
+       }
+
+       drmModeFreeResources(res);
+
+       for (output = comp->outputs; output; ) {
+               tmp = output;
+               output = output->next;
+
+               if (tmp->available)
+                       continue;
+
+               kmscon_output_unbind(tmp);
+       }
+
+       return comp->count_outputs;
+}
diff --git a/src/output.h b/src/output.h
new file mode 100644 (file)
index 0000000..a272677
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * kmscon - KMS/DRI output handling
+ * Written 2011 by David Herrmann <dh.herrmann@googlemail.com>
+ */
+
+/*
+ * KMS/DRI Output Handling
+ * This module provides a compositor object which manages the different outputs.
+ * Each output object belongs to a connected monitor.
+ * After creating a compositor object it will create a list of all available
+ * outputs. All outputs are disconnected by default. If you connect an
+ * output, a framebuffer with two renderbuffers is registered and you can start
+ * drawing to it using double-buffering.
+ * You can connect as many outputs as you want.
+ *
+ * To allow other applications to access the DRI you can put a compositor asleep
+ * and wake it up. When the compositor is asleep, the OpenGL context and
+ * framebuffers are still available, however, you cannot add or remove outputs
+ * unless the compositor is awake. You also cannot modify output modes or other
+ * output settings. It is recommended to avoid accessing the output objects at
+ * all as most of the functions simply return -EINVAL while being asleep.
+ *
+ * When waking up the compositor, it rereads all connected outputs. If a
+ * previously connected output has gone, it disconnects the output, removes
+ * the associated framebuffer and context and unbinds the output object from the
+ * compositor. If you own a reference to the output object, you should unref it
+ * now.
+ * You should also reread the output list for newly connected outputs.
+ * You can also force the compositor to reread all outputs if you noticed any
+ * monitor hotplugging (for instance via udev).
+ *
+ * An output may be used in different modes. Each output chooses one mode by
+ * default, however, you can always switch to another mode if you want another
+ * pixel-resolution, color-mode, etc. When switching modes, the current
+ * framebuffer is destroyed and a new one is created.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+struct kmscon_mode;
+struct kmscon_output;
+struct kmscon_compositor;
+
+/* output modes */
+
+int kmscon_mode_new(struct kmscon_mode **out);
+void kmscon_mode_ref(struct kmscon_mode *mode);
+void kmscon_mode_unref(struct kmscon_mode *mode);
+
+struct kmscon_mode *kmscon_mode_next(struct kmscon_mode *mode);
+const char *kmscon_mode_get_name(const struct kmscon_mode *mode);
+uint32_t kmscon_mode_get_width(const struct kmscon_mode *mode);
+uint32_t kmscon_mode_get_height(const struct kmscon_mode *mode);
+
+/* compositor outputs */
+
+int kmscon_output_new(struct kmscon_output **out);
+void kmscon_output_ref(struct kmscon_output *output);
+void kmscon_output_unref(struct kmscon_output *output);
+
+struct kmscon_output *kmscon_output_next(struct kmscon_output *output);
+struct kmscon_mode *kmscon_output_get_modes(struct kmscon_output *output);
+struct kmscon_mode *kmscon_output_get_current(struct kmscon_output *output);
+
+int kmscon_output_activate(struct kmscon_output *output,
+                                               struct kmscon_mode *mode);
+void kmscon_output_deactivate(struct kmscon_output *output);
+bool kmscon_output_is_active(struct kmscon_output *output);
+
+int kmscon_output_use(struct kmscon_output *output);
+int kmscon_output_swap(struct kmscon_output *output);
+
+/* compositors */
+
+int kmscon_compositor_new(struct kmscon_compositor **out);
+void kmscon_compositor_ref(struct kmscon_compositor *comp);
+void kmscon_compositor_unref(struct kmscon_compositor *comp);
+
+void kmscon_compositor_sleep(struct kmscon_compositor *comp);
+int kmscon_compositor_wake_up(struct kmscon_compositor *comp);
+bool kmscon_compositor_is_asleep(struct kmscon_compositor *comp);
+int kmscon_compositor_use(struct kmscon_compositor *comp);
+
+int kmscon_compositor_get_fd(struct kmscon_compositor *comp);
+struct kmscon_output *kmscon_compositor_get_outputs(
+                                       struct kmscon_compositor *comp);
+int kmscon_compositor_refresh(struct kmscon_compositor *comp);