/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Author: Daniel van Vugt
*/
#include "eglapp.h"
#include "mir_toolkit/mir_client_library.h"
#include
#include
#include
#include
#include
#include
#include
#include
float mir_eglapp_background_opacity = 1.0f;
static const char* appname = "egldemo";
static MirConnection *connection;
static MirWindow* window;
static EGLDisplay egldisplay;
static EGLSurface eglsurface;
static volatile sig_atomic_t running = 0;
static double refresh_rate = 0.0;
#define CHECK(_cond, _err) \
if (!(_cond)) \
{ \
printf("%s\n", (_err)); \
return 0; \
}
void mir_eglapp_cleanup(void)
{
eglMakeCurrent(egldisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglTerminate(egldisplay);
mir_window_release_sync(window);
window = NULL;
mir_connection_release(connection);
connection = NULL;
}
void mir_eglapp_quit(void)
{
running = 0;
}
static void shutdown(int signum)
{
if (running)
{
mir_eglapp_quit();
printf("Signal %d received. Good night.\n", signum);
}
}
mir_eglapp_bool mir_eglapp_running(void)
{
return running;
}
void mir_eglapp_swap_buffers(void)
{
EGLint width, height;
if (!running)
return;
eglSwapBuffers(egldisplay, eglsurface);
/*
* Querying the surface (actually the current buffer) dimensions here is
* the only truly safe way to be sure that the dimensions we think we
* have are those of the buffer being rendered to. But this should be
* improved in future; https://bugs.launchpad.net/mir/+bug/1194384
*/
if (eglQuerySurface(egldisplay, eglsurface, EGL_WIDTH, &width) &&
eglQuerySurface(egldisplay, eglsurface, EGL_HEIGHT, &height))
{
glViewport(0, 0, width, height);
}
}
static void mir_eglapp_handle_input_event(MirInputEvent const* event)
{
if (mir_input_event_get_type(event) != mir_input_event_type_key)
return;
MirKeyboardEvent const* kev = mir_input_event_get_keyboard_event(event);
if (mir_keyboard_event_action(kev) != mir_keyboard_action_up)
return;
if (mir_keyboard_event_key_code(kev) != XKB_KEY_q)
return;
running = 0;
}
static void mir_eglapp_handle_window_event(MirWindowEvent const* sev)
{
MirWindowAttrib attrib = mir_window_event_get_attribute(sev);
int value = mir_window_event_get_attribute_value(sev);
switch (attrib)
{
case mir_window_attrib_visibility:
printf("Window %s\n", value == mir_window_visibility_exposed ?
"exposed" : "occluded");
break;
case mir_window_attrib_dpi:
// value is still zero - never implemented. Deprecate? (LP: #1559831)
break;
default:
break;
}
}
static void handle_window_output_event(MirWindowOutputEvent const* out)
{
static char const* const form_factor_name[6] =
{"unknown", "phone", "tablet", "monitor", "TV", "projector"};
unsigned ff = mir_window_output_event_get_form_factor(out);
char const* form_factor = (ff < 6) ? form_factor_name[ff] : "out-of-range";
refresh_rate = mir_window_output_event_get_refresh_rate(out);
printf("Window is on output %u: %d DPI, scale %.1fx, %s form factor, %.2fHz\n",
mir_window_output_event_get_output_id(out),
mir_window_output_event_get_dpi(out),
mir_window_output_event_get_scale(out),
form_factor,
refresh_rate);
}
double mir_eglapp_display_hz(void)
{
return refresh_rate;
}
void mir_eglapp_handle_event(MirWindow* window, MirEvent const* ev, void* unused)
{
(void) window;
(void) unused;
switch (mir_event_get_type(ev))
{
case mir_event_type_input:
mir_eglapp_handle_input_event(mir_event_get_input_event(ev));
break;
case mir_event_type_window:
mir_eglapp_handle_window_event(mir_event_get_window_event(ev));
break;
case mir_event_type_window_output:
handle_window_output_event(mir_event_get_window_output_event(ev));
break;
case mir_event_type_resize:
/*
* FIXME: https://bugs.launchpad.net/mir/+bug/1194384
* It is unsafe to set the width and height here because we're in a
* different thread to that doing the rendering. So we either need
* support for event queuing (directing them to another thread) or
* full single-threaded callbacks. (LP: #1194384).
*/
{
MirResizeEvent const* resize = mir_event_get_resize_event(ev);
printf("Resized to %dx%d\n",
mir_resize_event_get_width(resize),
mir_resize_event_get_height(resize));
}
break;
case mir_event_type_close_window:
printf("Received close event from server.\n");
running = 0;
break;
default:
break;
}
}
static MirOutput const* find_active_output(
MirDisplayConfig const* conf)
{
size_t num_outputs = mir_display_config_get_num_outputs(conf);
for (size_t i = 0; i < num_outputs; i++)
{
MirOutput const* output = mir_display_config_get_output(conf, i);
MirOutputConnectionState state = mir_output_get_connection_state(output);
if (state == mir_output_connection_state_connected && mir_output_is_enabled(output))
{
return output;
}
}
return NULL;
}
static void show_help(struct mir_eglapp_arg const* const* arg_lists)
{
int const indent = 2, desc_offset = 2;
struct mir_eglapp_arg const* const* list;
int max_len = 0;
for (list = arg_lists; *list != NULL; ++list)
{
struct mir_eglapp_arg const* arg;
for (arg = *list; arg->syntax != NULL; ++arg)
{
int len = indent + strlen(arg->syntax);
if (len > max_len)
max_len = len;
}
}
for (list = arg_lists; *list != NULL; ++list)
{
struct mir_eglapp_arg const* arg;
for (arg = *list; arg->syntax != NULL; ++arg)
{
int len = 0, remainder = 0;
printf("%*c%s%n", indent, ' ', arg->syntax, &len);
remainder = desc_offset + max_len - len;
printf("%*c%s", remainder, ' ', arg->description);
switch (arg->format[0])
{
case '=':
{
char const* str = *(char const**)arg->variable;
if (str)
printf(" [%s]", str);
}
break;
case '%':
switch (arg->format[1])
{
case 'u': printf(" [%u]", *(unsigned*)arg->variable); break;
case 'f': printf(" [%.1f]", *(float*)arg->variable); break;
default: break;
}
default:
break;
}
printf("\n");
}
}
}
static mir_eglapp_bool parse_args(int argc, char *argv[],
struct mir_eglapp_arg const* const* arg_lists)
{
for (int i = 1; i < argc; ++i)
{
char const* operator = argv[i];
struct mir_eglapp_arg const* const* list;
for (list = arg_lists; *list != NULL; ++list)
{
struct mir_eglapp_arg const* arg;
for (arg = *list; arg->syntax != NULL; ++arg)
{
char const* space = strchr(arg->syntax, ' ');
int operator_len = space != NULL ? space - arg->syntax
: (int)strlen(arg->syntax);
if (!strncmp(operator, arg->syntax, operator_len))
{
char const* operand = operator + operator_len;
if (!operand[0] && strchr("=%", arg->format[0]))
{
if (i+1 < argc)
operand = argv[++i];
else
{
fprintf(stderr, "Missing argument for %s\n",
operator);
return 0;
}
}
switch (arg->format[0])
{
case '$': return 1; /* -- is special */
case '!': *(mir_eglapp_bool*)arg->variable = 1; break;
case '=': *(char const**)arg->variable = operand; break;
case '%':
if (!sscanf(operand, arg->format, arg->variable))
{
fprintf(stderr,
"Invalid option: %s (expected %s)\n",
operand, arg->syntax);
return 0;
}
break;
default: abort(); break;
}
goto next;
}
}
}
fprintf(stderr, "Unknown option: %s\n", operator);
return 0;
next:
(void)0; /* Stop compiler warnings about label at the end */
}
return 1;
}
mir_eglapp_bool mir_eglapp_init(int argc, char* argv[],
unsigned int* width, unsigned int* height,
struct mir_eglapp_arg const* custom_args)
{
EGLint ctxattribs[] =
{
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLConfig eglconfig;
EGLint neglconfigs;
EGLContext eglctx;
EGLBoolean ok;
EGLint swapinterval = 1;
unsigned int output_id = mir_display_output_id_invalid;
char const* mir_socket = NULL;
char const* dims = NULL;
char const* cursor_name = mir_default_cursor_name;
unsigned int rgb_bits = 8;
mir_eglapp_bool help = 0, no_vsync = 0, quiet = 0;
mir_eglapp_bool fullscreen = !*width || !*height;
struct mir_eglapp_arg const default_args[] =
{
{"-a ", "=", &appname, "Set application name"},
{"-b <0.0-0.1>", "%f", &mir_eglapp_background_opacity, "Background opacity"},
{"-c ", "=", &cursor_name, "Request cursor image by name"},
{"-e ", "%u", &rgb_bits, "EGL colour channel size"},
{"-f", "!", &fullscreen, "Force full screen"},
{"-h", "!", &help, "Show this help text"},
{"-m ", "=", &mir_socket, "Mir server socket"},
{"-n", "!", &no_vsync, "Don't sync to vblank"},
{"-o ", "%u", &output_id, "Force placement on output monitor ID"},
{"-q", "!", &quiet, "Quiet mode (no messages output)"},
{"-s x", "=", &dims, "Force window size"},
{"--", "$", NULL, "Ignore all arguments that follow"},
{NULL, NULL, NULL, NULL}
};
struct mir_eglapp_arg const* const arg_lists[] =
{
default_args,
custom_args,
NULL
};
if (!parse_args(argc, argv, arg_lists))
return 0;
if (help)
{
printf("Usage: %s []\n", argv[0]);
show_help(arg_lists);
return 0;
}
if (no_vsync)
swapinterval = 0;
if (dims)
{
if (2 != sscanf(dims, "%ux%u", width, height))
{
fprintf(stderr, "Invalid dimensions: %s\n", dims);
return 0;
}
fullscreen = 0;
}
if (quiet)
{
FILE *unused = freopen("/dev/null", "a", stdout);
(void)unused;
}
connection = mir_connect_sync(mir_socket, appname);
CHECK(mir_connection_is_valid(connection), "Can't get connection");
egldisplay = eglGetDisplay(
mir_connection_get_egl_native_display(connection));
CHECK(egldisplay != EGL_NO_DISPLAY, "Can't eglGetDisplay");
ok = eglInitialize(egldisplay, NULL, NULL);
CHECK(ok, "Can't eglInitialize");
EGLint alpha_bits = mir_eglapp_background_opacity == 1.0f ? 0 : rgb_bits;
const EGLint attribs[] =
{
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, rgb_bits,
EGL_GREEN_SIZE, rgb_bits,
EGL_BLUE_SIZE, rgb_bits,
EGL_ALPHA_SIZE, alpha_bits,
EGL_NONE
};
ok = eglChooseConfig(egldisplay, attribs, &eglconfig, 1, &neglconfigs);
CHECK(ok, "Could not eglChooseConfig");
CHECK(neglconfigs > 0, "No EGL config available");
MirPixelFormat pixel_format =
mir_connection_get_egl_pixel_format(connection, egldisplay, eglconfig);
printf("Mir chose pixel format %d.\n", pixel_format);
if (alpha_bits == 0)
{
/*
* If we are opaque then it's OK to switch pixel format slightly,
* to enable bypass/overlays to work. Otherwise the presence of an
* alpha channel would prevent them from being used.
* It would be really nice if Mesa just gave us the right answer in
* the first place though. (LP: #1480755)
*/
if (pixel_format == mir_pixel_format_abgr_8888)
pixel_format = mir_pixel_format_xbgr_8888;
else if (pixel_format == mir_pixel_format_argb_8888)
pixel_format = mir_pixel_format_xrgb_8888;
}
printf("Using pixel format %d.\n", pixel_format);
/* eglapps are interested in the screen size, so
use mir_connection_create_display_config */
MirDisplayConfig* display_config =
mir_connection_create_display_configuration(connection);
MirOutput const* output = find_active_output(display_config);
if (output == NULL)
{
printf("No active outputs found.\n");
return 0;
}
MirOutputMode const* mode = mir_output_get_current_mode(output);
int pos_x = mir_output_get_position_x(output);
int pos_y = mir_output_get_position_y(output);
int mode_width = mir_output_mode_get_width(mode);
int mode_height = mir_output_mode_get_height(mode);
printf("Current active output is %dx%d %+d%+d\n",
mode_width, mode_height,
pos_x, pos_y);
if (fullscreen) /* TODO: Use surface states for this */
{
*width = mode_width;
*height = mode_height;
}
mir_display_config_release(display_config);
MirWindowSpec *spec =
mir_create_normal_window_spec(connection, *width, *height);
CHECK(spec != NULL, "Can't create a window spec");
mir_window_spec_set_pixel_format(spec, pixel_format);
char const* name = argv[0];
for (char const* p = name; *p; p++)
{
if (*p == '/')
name = p + 1;
}
mir_window_spec_set_name(spec, name);
if (output_id != mir_display_output_id_invalid)
mir_window_spec_set_fullscreen_on_output(spec, output_id);
window = mir_create_window_sync(spec);
mir_window_spec_release(spec);
CHECK(mir_window_is_valid(window), "Can't create a window");
mir_window_set_event_handler(window, mir_eglapp_handle_event, NULL);
spec = mir_create_window_spec(connection);
mir_window_spec_set_cursor_name(spec, cursor_name);
mir_window_apply_spec(window, spec);
mir_window_spec_release(spec);
eglsurface = eglCreateWindowSurface(egldisplay, eglconfig,
(EGLNativeWindowType)mir_buffer_stream_get_egl_native_window(mir_window_get_buffer_stream(window)), NULL);
CHECK(eglsurface != EGL_NO_SURFACE, "eglCreateWindowSurface failed");
eglctx = eglCreateContext(egldisplay, eglconfig, EGL_NO_CONTEXT,
ctxattribs);
CHECK(eglctx != EGL_NO_CONTEXT, "eglCreateContext failed");
ok = eglMakeCurrent(egldisplay, eglsurface, eglsurface, eglctx);
CHECK(ok, "Can't eglMakeCurrent");
signal(SIGINT, shutdown);
signal(SIGTERM, shutdown);
signal(SIGHUP, shutdown);
eglSwapInterval(egldisplay, swapinterval);
running = 1;
return 1;
}
struct MirConnection* mir_eglapp_native_connection()
{
return connection;
}
MirWindow* mir_eglapp_native_window()
{
return window;
}