log_warn is much shorter and we already use log_err instead of log_error so this is more consistent now. Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
1072 lines
24 KiB
C
1072 lines
24 KiB
C
/*
|
|
* kmscon - KMS/DRM output handling
|
|
*
|
|
* Copyright (c) 2011 David Herrmann <dh.herrmann@googlemail.com>
|
|
* Copyright (c) 2011 University of Tuebingen
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* KMS/DRM Output Handling
|
|
* This provides the compositor, output and mode objects and creates OpenGL
|
|
* contexts available for drawing directly to the graphics framebuffer.
|
|
*/
|
|
|
|
#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 <gbm.h>
|
|
#include <xf86drm.h>
|
|
#include <xf86drmMode.h>
|
|
|
|
#include "log.h"
|
|
#include "output.h"
|
|
|
|
struct kmscon_mode {
|
|
size_t ref;
|
|
struct kmscon_mode *next;
|
|
struct kmscon_output *output;
|
|
|
|
drmModeModeInfo info;
|
|
};
|
|
|
|
struct render_buffer {
|
|
struct gbm_bo *bo;
|
|
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;
|
|
struct kmscon_mode *def_mode;
|
|
|
|
uint32_t conn_id;
|
|
uint32_t crtc_id;
|
|
|
|
struct render_buffer rb[2];
|
|
struct kmscon_framebuffer *fb;
|
|
|
|
drmModeCrtcPtr saved_crtc;
|
|
};
|
|
|
|
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;
|
|
struct kmscon_context *ctx;
|
|
};
|
|
|
|
/*
|
|
* 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);
|
|
|
|
if (!output->def_mode)
|
|
output->def_mode = 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);
|
|
|
|
if (output->def_mode == mode)
|
|
output->def_mode = output->modes;
|
|
|
|
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;
|
|
|
|
log_debug("output: creating output object\n");
|
|
|
|
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);
|
|
log_debug("output: destroying output object\n");
|
|
}
|
|
|
|
/*
|
|
* 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 = 0;
|
|
|
|
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) {
|
|
log_warn("output: no free CRTC left to connect output\n");
|
|
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) {
|
|
log_warn("output: no suitable mode available for output\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
output->conn_id = conn->connector_id;
|
|
output->crtc_id = crtc;
|
|
output->connected = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Returns true if the output is active and the related compositor is awake.
|
|
*/
|
|
bool kmscon_output_is_awake(struct kmscon_output *output)
|
|
{
|
|
if (!output)
|
|
return NULL;
|
|
|
|
return output->active && !kmscon_compositor_is_asleep(output->comp);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* Returns a pointer to the default mode which will be used if no other mode is
|
|
* set explicitely. Returns NULL if no default mode is available.
|
|
*/
|
|
struct kmscon_mode *kmscon_output_get_default(struct kmscon_output *output)
|
|
{
|
|
if (!output)
|
|
return NULL;
|
|
|
|
return output->def_mode;
|
|
}
|
|
|
|
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) {
|
|
log_warn("output: cannot create gbm buffer object\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
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) {
|
|
log_warn("output: cannot add DRM framebuffer object\n");
|
|
ret = -EFAULT;
|
|
goto err_bo;
|
|
}
|
|
|
|
return 0;
|
|
|
|
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);
|
|
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.
|
|
* When the output is activated, its previous screen contents and mode are
|
|
* saved, to be restored when the output is deactivated.
|
|
* 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->def_mode;
|
|
|
|
log_debug("output: activating output with res %ux%u\n",
|
|
mode->info.hdisplay, mode->info.vdisplay);
|
|
|
|
comp = output->comp;
|
|
output->saved_crtc = drmModeGetCrtc(comp->drm_fd, output->crtc_id);
|
|
|
|
ret = init_rb(&output->rb[0], comp, &mode->info);
|
|
if (ret)
|
|
goto err_saved;
|
|
|
|
ret = init_rb(&output->rb[1], comp, &mode->info);
|
|
if (ret) {
|
|
destroy_rb(&output->rb[0], comp);
|
|
goto err_saved;
|
|
}
|
|
|
|
output->current = mode;
|
|
output->active = 1;
|
|
|
|
ret = kmscon_framebuffer_new(&output->fb, comp->ctx, output->rb[0].bo,
|
|
output->rb[1].bo);
|
|
if (ret)
|
|
goto err_rb;
|
|
|
|
kmscon_context_viewport(output->comp->ctx, mode->info.hdisplay,
|
|
mode->info.vdisplay);
|
|
kmscon_context_clear(output->comp->ctx);
|
|
|
|
ret = kmscon_output_swap(output);
|
|
if (ret)
|
|
goto err_fb;
|
|
|
|
return 0;
|
|
|
|
err_fb:
|
|
kmscon_framebuffer_destroy(output->fb);
|
|
err_rb:
|
|
destroy_rb(&output->rb[0], output->comp);
|
|
destroy_rb(&output->rb[1], output->comp);
|
|
output->active = 0;
|
|
output->current = NULL;
|
|
err_saved:
|
|
if (output->saved_crtc) {
|
|
drmModeFreeCrtc(output->saved_crtc);
|
|
output->saved_crtc = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Deactivate the output. This does not disconnect the output so you can
|
|
* reactivate this output again.
|
|
* When the output is deactivated, the screen contents and mode it had before
|
|
* it was activated are restored.
|
|
*/
|
|
void kmscon_output_deactivate(struct kmscon_output *output)
|
|
{
|
|
if (!output || !output->active)
|
|
return;
|
|
|
|
if (output->saved_crtc) {
|
|
drmModeSetCrtc(output->comp->drm_fd,
|
|
output->saved_crtc->crtc_id,
|
|
output->saved_crtc->buffer_id,
|
|
output->saved_crtc->x,
|
|
output->saved_crtc->y,
|
|
&output->conn_id,
|
|
1,
|
|
&output->saved_crtc->mode);
|
|
drmModeFreeCrtc(output->saved_crtc);
|
|
output->saved_crtc = NULL;
|
|
}
|
|
|
|
kmscon_framebuffer_destroy(output->fb);
|
|
destroy_rb(&output->rb[0], output->comp);
|
|
destroy_rb(&output->rb[1], output->comp);
|
|
output->current = NULL;
|
|
output->active = 0;
|
|
log_debug("output: deactivated output\n");
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
kmscon_framebuffer_use(output->fb);
|
|
|
|
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, num;
|
|
|
|
if (!output || !output->active)
|
|
return -EINVAL;
|
|
|
|
if (kmscon_compositor_is_asleep(output->comp))
|
|
return -EINVAL;
|
|
|
|
kmscon_context_flush(output->comp->ctx);
|
|
num = kmscon_framebuffer_swap(output->fb);
|
|
if (num < 0)
|
|
return num;
|
|
if (num > 1)
|
|
num = 1;
|
|
|
|
num ^= 1;
|
|
ret = drmModeSetCrtc(output->comp->drm_fd, output->crtc_id,
|
|
output->rb[num].fb, 0, 0, &output->conn_id, 1,
|
|
&output->current->info);
|
|
if (ret) {
|
|
log_warn("output: cannot set CRTC\n");
|
|
ret = -EFAULT;
|
|
}
|
|
|
|
kmscon_context_viewport(output->comp->ctx,
|
|
output->current->info.hdisplay,
|
|
output->current->info.vdisplay);
|
|
kmscon_context_clear(output->comp->ctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
log_debug("output: creating compositor\n");
|
|
|
|
comp = malloc(sizeof(*comp));
|
|
if (!comp)
|
|
return -ENOMEM;
|
|
|
|
memset(comp, 0, sizeof(*comp));
|
|
comp->ref = 1;
|
|
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) {
|
|
log_warn("output: cannot open /dev/dri/card0: %m\n");
|
|
ret = -errno;
|
|
goto err_free;
|
|
}
|
|
|
|
comp->gbm = gbm_create_device(comp->drm_fd);
|
|
if (!comp->gbm) {
|
|
log_warn("output: cannot allocate gbm device\n");
|
|
ret = -EFAULT;
|
|
goto err_drm;
|
|
}
|
|
|
|
ret = kmscon_context_new(&comp->ctx, comp->gbm);
|
|
if (ret)
|
|
goto err_gbm;
|
|
|
|
*out = comp;
|
|
return 0;
|
|
|
|
err_gbm:
|
|
gbm_device_destroy(comp->gbm);
|
|
err_drm:
|
|
close(comp->drm_fd);
|
|
err_free:
|
|
free(comp);
|
|
return ret;
|
|
}
|
|
|
|
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;
|
|
|
|
while (comp->outputs)
|
|
kmscon_output_unbind(comp->outputs);
|
|
|
|
kmscon_context_destroy(comp->ctx);
|
|
gbm_device_destroy(comp->gbm);
|
|
close(comp->drm_fd);
|
|
free(comp);
|
|
log_debug("output: destroying compositor\n");
|
|
}
|
|
|
|
/*
|
|
* This puts the compositor asleep. While the compositor is asleep, no access
|
|
* to the DRM 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)
|
|
return;
|
|
|
|
log_debug("output: putting compositor asleep\n");
|
|
comp->state = COMPOSITOR_ASLEEP;
|
|
drmDropMaster(comp->drm_fd);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
log_debug("output: waking up compositor\n");
|
|
|
|
ret = drmSetMaster(comp->drm_fd);
|
|
if (ret) {
|
|
log_warn("output: cannot acquire DRM master privs\n");
|
|
return -EACCES;
|
|
}
|
|
|
|
comp->state = COMPOSITOR_AWAKE;
|
|
ret = kmscon_compositor_refresh(comp);
|
|
if (ret >= 0)
|
|
return ret;
|
|
|
|
comp->state = COMPOSITOR_ASLEEP;
|
|
drmDropMaster(comp->drm_fd);
|
|
|
|
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 works even if the
|
|
* compositor is asleep. Moreover, most other subsystems that need an GL context
|
|
* require this function to be called before they are used.
|
|
*
|
|
* 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 (!comp)
|
|
return -EINVAL;
|
|
|
|
return kmscon_context_use(comp->ctx);
|
|
}
|
|
|
|
struct kmscon_context *kmscon_compositor_get_context(
|
|
struct kmscon_compositor *comp)
|
|
{
|
|
if (!comp)
|
|
return NULL;
|
|
|
|
return comp->ctx;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
int i;
|
|
uint32_t cid;
|
|
struct kmscon_output *output, *tmp;
|
|
|
|
if (!comp || comp->state != COMPOSITOR_AWAKE)
|
|
return -EINVAL;
|
|
|
|
res = drmModeGetResources(comp->drm_fd);
|
|
if (!res) {
|
|
log_warn("output: cannot retrieve DRM resources\n");
|
|
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;
|
|
}
|