diff options
-rw-r--r-- | meson.build | 3 | ||||
-rw-r--r-- | src/background.h | 9 | ||||
-rw-r--r-- | src/client.h | 6 | ||||
-rw-r--r-- | src/color.c | 48 | ||||
-rw-r--r-- | src/color.h (renamed from src/util.h) | 10 | ||||
-rw-r--r-- | src/egl.c | 42 | ||||
-rw-r--r-- | src/egl.h | 6 | ||||
-rw-r--r-- | src/entry.c | 201 | ||||
-rw-r--r-- | src/entry.h | 25 | ||||
-rw-r--r-- | src/gl.c | 179 | ||||
-rw-r--r-- | src/gl.h | 8 | ||||
-rw-r--r-- | src/image.c (renamed from src/background.c) | 25 | ||||
-rw-r--r-- | src/image.h | 17 | ||||
-rw-r--r-- | src/log.c | 5 | ||||
-rw-r--r-- | src/main.c | 755 | ||||
-rw-r--r-- | src/surface.c | 16 | ||||
-rw-r--r-- | src/surface.h | 10 |
17 files changed, 1054 insertions, 311 deletions
diff --git a/meson.build b/meson.build index a8f7cfb..27c8144 100644 --- a/meson.build +++ b/meson.build @@ -45,10 +45,11 @@ add_project_arguments( sources = files( 'src/main.c', - 'src/background.c', + 'src/color.c', 'src/egl.c', 'src/entry.c', 'src/gl.c', + 'src/image.c', 'src/log.c', 'src/surface.c', 'src/xdg-shell-protocol.c', diff --git a/src/background.h b/src/background.h deleted file mode 100644 index 68b432a..0000000 --- a/src/background.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef PNG_H -#define PNG_H - -#include <stdint.h> -#include "client.h" - -void load_background(struct client_state *state, const char *filename); - -#endif /* PNG_H */ diff --git a/src/client.h b/src/client.h index 934d4d3..d356094 100644 --- a/src/client.h +++ b/src/client.h @@ -3,9 +3,10 @@ #include <stdbool.h> #include <stdint.h> +#include "color.h" #include "entry.h" +#include "image.h" #include "surface.h" -#include "util.h" struct client_state { /* Globals */ @@ -37,7 +38,10 @@ struct client_state { struct image background_image; struct color background_color; struct entry entry; + int32_t width; + int32_t height; uint32_t scale; + bool resize; } window; /* Keyboard state */ diff --git a/src/color.c b/src/color.c new file mode 100644 index 0000000..f6b3275 --- /dev/null +++ b/src/color.c @@ -0,0 +1,48 @@ +#include <stdlib.h> +#include <string.h> +#include "color.h" +#include "log.h" + +struct color hex_to_color(const char *hex) +{ + if (hex[0] == '#') { + hex++; + } + + uint32_t val = 0; + size_t len = strlen(hex); + + if (len == 3) { + char str[] = { + hex[0], hex[0], + hex[1], hex[1], + hex[2], hex[2], + '\0'}; + val = strtol(str, NULL, 16); + val <<= 8; + val |= 0xFFu; + } else if (len == 4) { + char str[] = { + hex[0], hex[0], + hex[1], hex[1], + hex[2], hex[2], + hex[3], hex[3], + '\0'}; + val = strtol(str, NULL, 16); + } else if (len == 6) { + val = strtol(hex, NULL, 16); + val <<= 8; + val |= 0xFFu; + } else if (len == 8) { + val = strtol(hex, NULL, 16); + } else { + log_error("Invalid hex color %s\n", hex); + } + + return (struct color) { + .r = ((val & 0xFF000000u) >> 24) / 255.0f, + .g = ((val & 0x00FF0000u) >> 16) / 255.0f, + .b = ((val & 0x0000FF00u) >> 8) / 255.0f, + .a = ((val & 0x000000FFu) >> 0) / 255.0f, + }; +} @@ -4,14 +4,6 @@ #include <stdbool.h> #include <stdint.h> -struct image { - uint8_t *buffer; - uint32_t width; - uint32_t height; - bool swizzle; - bool redraw; -}; - struct color { float r; float g; @@ -19,4 +11,6 @@ struct color { float a; }; +struct color hex_to_color(const char *hex); + #endif /* UTIL_H */ @@ -6,12 +6,13 @@ static const char *egl_error_string(); -void egl_create_window(struct egl *egl, struct wl_surface *wl_surface, uint32_t width, uint32_t height) +void egl_create_window( + struct egl *egl, + struct wl_surface *wl_surface, + uint32_t width, + uint32_t height) { - egl->window = wl_egl_window_create( - wl_surface, - width, - height); + egl->window = wl_egl_window_create(wl_surface, width, height); if (egl->window == EGL_NO_SURFACE) { egl_log_error("Couldn't create EGL window"); @@ -49,13 +50,22 @@ void egl_create_context(struct egl *egl, struct wl_display *wl_display) return; } - result = eglChooseConfig(egl->display, config_attribs, &config, 1, &num_configs); + result = eglChooseConfig( + egl->display, + config_attribs, + &config, + 1, + &num_configs); if ((result != EGL_TRUE) || (num_configs != 1)) { egl_log_error("Failed to choose EGL config"); return; } - egl->surface = eglCreateWindowSurface(egl->display, config, egl->window, NULL); + egl->surface = eglCreateWindowSurface( + egl->display, + config, + egl->window, + NULL); if (egl->surface == EGL_NO_SURFACE) { egl_log_error("Couldn't create EGL window surface"); return; @@ -66,13 +76,21 @@ void egl_create_context(struct egl *egl, struct wl_display *wl_display) EGL_NONE }; - egl->context = eglCreateContext(egl->display, config, EGL_NO_CONTEXT, context_attribs); + egl->context = eglCreateContext( + egl->display, + config, + EGL_NO_CONTEXT, + context_attribs); if (egl->context == EGL_NO_CONTEXT) { egl_log_error("Couldn't create EGL context"); return; } - result = eglMakeCurrent(egl->display, egl->surface, egl->surface, egl->context); + result = eglMakeCurrent( + egl->display, + egl->surface, + egl->surface, + egl->context); if (!result) { egl_log_error("Couldn't make EGL context current"); return; @@ -84,7 +102,11 @@ void egl_log_error(const char *msg) { } void egl_make_current(struct egl *egl) { - bool result = eglMakeCurrent(egl->display, egl->surface, egl->surface, egl->context); + bool result = eglMakeCurrent( + egl->display, + egl->surface, + egl->surface, + egl->context); if (!result) { egl_log_error("Couldn't make EGL context current"); return; @@ -11,7 +11,11 @@ struct egl { EGLSurface surface; }; -void egl_create_window(struct egl *egl, struct wl_surface *wl_surface, uint32_t width, uint32_t height); +void egl_create_window( + struct egl *egl, + struct wl_surface *wl_surface, + uint32_t width, + uint32_t height); void egl_create_context(struct egl *egl, struct wl_display *wl_display); void egl_log_error(const char *msg); void egl_make_current(struct egl *egl); diff --git a/src/entry.c b/src/entry.c index 61b0211..e6338de 100644 --- a/src/entry.c +++ b/src/entry.c @@ -9,62 +9,109 @@ #include "log.h" #include "nelem.h" -static void calculate_font_extents(struct entry *entry); +static void calculate_font_extents(struct entry *entry, uint32_t scale); -void entry_init(struct entry *entry) +void entry_init(struct entry *entry, uint32_t scale) { - calculate_font_extents(entry); + calculate_font_extents(entry, scale); + struct color color; - /* - * Cairo uses native 32 bit integers for pixels, so if this computer is - * little endian we have to tell OpenGL to swizzle the texture. + /* + * Cairo uses native 32 bit integers for pixels, so if this processor + * is little endian we have to tell OpenGL to swizzle the texture. */ if (htonl(0xFFu) != 0xFFu) { entry->image.swizzle = true; } + /* + * Create the cairo surface and context we'll be using. + */ cairo_surface_t *surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, entry->surface.width, entry->surface.height ); + cairo_surface_set_device_scale(surface, scale, scale); cairo_t *cr = cairo_create(surface); - cairo_set_source_rgb(cr, 0.031, 0.031, 0); + + /* Running size of current drawing area. */ + int32_t width = entry->surface.width / scale; + int32_t height = entry->surface.height / scale; + + /* Draw the outer outline */ + color = entry->border.outline_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); cairo_paint(cr); - cairo_translate(cr, entry->border.outline_width, entry->border.outline_width); - cairo_rectangle(cr, - 0, - 0, - entry->surface.width - 2*entry->border.outline_width, - entry->surface.height - 2*entry->border.outline_width - ); + /* Move and clip following draws to be within this outline */ + cairo_translate( + cr, + entry->border.outline_width, + entry->border.outline_width); + width -= 2 * entry->border.outline_width; + height -= 2 * entry->border.outline_width; + cairo_rectangle(cr, 0, 0, width, height); cairo_clip(cr); - cairo_set_source_rgb(cr, 0.976, 0.149, 0.447); + + /* Draw the border */ + color = entry->border.color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); cairo_paint(cr); + /* Move and clip following draws to be within the border */ cairo_translate(cr, entry->border.width, entry->border.width); - cairo_rectangle(cr, - 0, - 0, - entry->surface.width - 2*(entry->border.outline_width + entry->border.width), - entry->surface.height - 2*(entry->border.outline_width + entry->border.width) - ); + width -= 2 * entry->border.width; + height -= 2 * entry->border.width; + cairo_rectangle(cr, 0, 0, width, height); + cairo_clip(cr); + + /* Draw the inner outline */ + color = entry->border.outline_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + cairo_paint(cr); + + /* Move and clip following draws to be within this outline */ + cairo_translate( + cr, + entry->border.outline_width, + entry->border.outline_width); + width -= 2 * entry->border.outline_width; + height -= 2 * entry->border.outline_width; + cairo_rectangle(cr, 0, 0, width, height); cairo_clip(cr); - cairo_set_source_rgb(cr, 0.106, 0.114, 0.118); + + /* Draw the entry background */ + color = entry->background_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); cairo_paint(cr); - PangoLayout *layout = pango_cairo_create_layout(cr); + /* Move and clip following draws to be within the specified padding */ + cairo_translate(cr, entry->padding, entry->padding); + width -= 2 * entry->padding; + height -= 2 * entry->padding; + cairo_rectangle(cr, 0, 0, width, height); + cairo_clip(cr); + + /* + * Move the cursor back up, so that Pango draws in the correct place if + * we're doing a tight layout. + */ + cairo_translate(cr, -entry->text_bounds.x, -entry->text_bounds.y); + + /* Setup Pango. */ + PangoContext *context = pango_cairo_create_context(cr); + PangoLayout *layout = pango_layout_new(context); pango_layout_set_text(layout, "", -1); - PangoFontDescription *font_description = pango_font_description_from_string("Rubik Bold 48"); + PangoFontDescription *font_description = + pango_font_description_from_string(entry->font_name); + pango_font_description_set_size( + font_description, + entry->font_size * PANGO_SCALE); pango_layout_set_font_description(layout, font_description); pango_font_description_free(font_description); - cairo_set_source_rgb(cr, 0.973, 0.973, 0.941); - pango_cairo_update_layout(cr, layout); - pango_cairo_show_layout(cr, layout); - entry->pangocairo.surface = surface; entry->pangocairo.cr = cr; entry->pangocairo.layout = layout; @@ -77,49 +124,95 @@ void entry_update(struct entry *entry) { cairo_t *cr = entry->pangocairo.cr; PangoLayout *layout = entry->pangocairo.layout; - cairo_set_source_rgb(cr, 0.106, 0.114, 0.118); + + /* Redraw the background. */ + struct color color = entry->background_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); cairo_paint(cr); - cairo_set_source_rgb(cr, 0.973, 0.973, 0.941); + + /* Draw our text with Pango. */ + color = entry->foreground_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); //const wchar_t *src = entry->password; //wcsrtombs(entry->password_mb, &src, N_ELEM(entry->password_mb), NULL); + size_t len = 0; for (unsigned int i = 0; i < entry->password_length; i++) { - entry->password_mb[2 * i] = '\xC2'; - entry->password_mb[2 * i + 1] = '\xB7'; + len += wcrtomb(entry->password_mb + len, entry->password_character, NULL); } - entry->password_mb[2 * entry->password_length] = '\0'; + entry->password_mb[len] = '\0'; fprintf(stderr, "%s\n", entry->password_mb); pango_layout_set_text(layout, entry->password_mb, -1); pango_cairo_update_layout(cr, layout); pango_cairo_show_layout(cr, layout); + entry->image.redraw = true; } -void calculate_font_extents(struct entry *entry) +void entry_set_scale(struct entry *entry, uint32_t scale) { + cairo_surface_set_device_scale(entry->pangocairo.surface, scale, scale); +} + +void calculate_font_extents(struct entry *entry, uint32_t scale) +{ + /* + * To calculate the size of the password box, we do the following: + * 1. Load the font we're going to use. + * 2. Create a string of the desired length using the specified + * password character, e.g. "·······". + * 3. Render the string with Pango in some abstract layout. + * 4. Measure the bounding box of the layout. + * 5. Add on the size of the border / outline. + */ PangoFontMap *font_map = pango_cairo_font_map_get_default(); PangoContext *context = pango_font_map_create_context(font_map); PangoLayout *layout = pango_layout_new(context); - { - PangoFontDescription *font_description = pango_font_description_from_string("Rubik Bold 48"); - pango_layout_set_font_description(layout, font_description); - PangoFont *font = pango_font_map_load_font(font_map, context, font_description); - g_object_unref(font); - pango_font_description_free(font_description); - - font_description = pango_context_get_font_description(context); - log_info("Using family: %s\n", pango_font_description_get_family(font_description)); + + PangoFontDescription *font_description = + pango_font_description_from_string(entry->font_name); + pango_font_description_set_size( + font_description, + entry->font_size * PANGO_SCALE); + pango_layout_set_font_description(layout, font_description); + PangoFont *font = + pango_font_map_load_font(font_map, context, font_description); + pango_font_description_free(font_description); + + font_description = pango_font_describe(font); + log_info("Using font: %s\n", + pango_font_description_to_string(font_description)); + g_object_unref(font); + + char *buf = calloc(MAX_PASSWORD_LENGTH, 4); + size_t len = 0; + for (unsigned int i = 0; i < entry->num_characters; i++) { + len += wcrtomb(buf + len, entry->password_character, NULL); + } + buf[len] = '\0'; + pango_layout_set_text(layout,buf, -1); + free(buf); + + PangoRectangle rect; + if (entry->tight_layout) { + pango_layout_get_pixel_extents(layout, &rect, NULL); + } else { + pango_layout_get_pixel_extents(layout, NULL, &rect); } - pango_layout_set_text(layout, "············", -1); - - int width; - int height; - pango_layout_get_pixel_size(layout, &width, &height); - fprintf(stderr, "%d x %d\n", width, height); - fprintf(stderr, "%d, %d\n", entry->border.width, entry->border.outline_width); - width += 2 * (entry->border.width + entry->border.outline_width); - height += 2 * (entry->border.width + entry->border.outline_width); - entry->surface.width = width; - entry->surface.height = height; + int width = rect.width; + int height = rect.height; + width += 2 * ( + entry->border.width + + 2 * entry->border.outline_width + + entry->padding + ); + height += 2 * ( + entry->border.width + + 2 * entry->border.outline_width + + entry->padding + ); + entry->surface.width = width * scale; + entry->surface.height = height * scale; + entry->text_bounds = rect; g_object_unref(layout); g_object_unref(context); diff --git a/src/entry.h b/src/entry.h index 18276a1..b1b9670 100644 --- a/src/entry.h +++ b/src/entry.h @@ -2,7 +2,8 @@ #define ENTRY_H #include <pango/pangocairo.h> -#include "util.h" +#include "color.h" +#include "image.h" #include "surface.h" #define MAX_PASSWORD_LENGTH 256 @@ -16,18 +17,32 @@ struct entry { cairo_surface_t *surface; cairo_t *cr; } pangocairo; + PangoRectangle text_bounds; + + wchar_t password[MAX_PASSWORD_LENGTH]; + /* Assume maximum of 4 bytes per wchar_t (for UTF-8) */ + char password_mb[4*MAX_PASSWORD_LENGTH]; + uint32_t password_length; + + /* Options */ + uint32_t font_size; + const char *font_name; + uint32_t padding; + bool tight_layout; + wchar_t password_character; + uint32_t num_characters; + struct color foreground_color; + struct color background_color; struct { struct color color; struct color outline_color; int32_t width; int32_t outline_width; } border; - wchar_t password[MAX_PASSWORD_LENGTH]; - char password_mb[4*MAX_PASSWORD_LENGTH]; - uint32_t password_length; }; -void entry_init(struct entry *entry); +void entry_init(struct entry *entry, uint32_t scale); void entry_update(struct entry *entry); +void entry_set_scale(struct entry *entry, uint32_t scale); #endif /* ENTRY_H */ @@ -11,9 +11,26 @@ static void load_shader(GLuint shader, const char *filename); static GLuint create_shader_program(const char *vert, const char *frag); +static void GLAPIENTRY MessageCallback( + GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam); +static const char *gl_debug_source_string(GLenum type); +static const char *gl_debug_type_string(GLenum type); +static const char *gl_debug_severity_string(GLenum type); + void gl_initialise(struct gl *gl, struct image *texture) { +#ifdef DEBUG + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(MessageCallback, 0); +#endif if (texture == NULL) { return; } @@ -37,24 +54,44 @@ void gl_initialise(struct gl *gl, struct image *texture) glGenBuffers(1, &gl->vbo); glBindBuffer(GL_ARRAY_BUFFER, gl->vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + glBufferData( + GL_ARRAY_BUFFER, + sizeof(vertices), + vertices, + GL_STATIC_DRAW); - /* Create a vertex array and enable vertex attributes for the shaders. */ + /* + * Create a vertex array and enable vertex attributes for the shaders. + */ glGenVertexArrays(1, &gl->vao); glBindVertexArray(gl->vao); GLint posAttrib = glGetAttribLocation(gl->shader, "position"); - glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), 0); + glVertexAttribPointer( + posAttrib, + 2, + GL_FLOAT, + GL_FALSE, + 4*sizeof(float), + 0); glEnableVertexAttribArray(posAttrib); GLint texAttrib = glGetAttribLocation(gl->shader, "texcoord"); - glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), (void *)(2*sizeof(float))); + glVertexAttribPointer( + texAttrib, + 2, + GL_FLOAT, + GL_FALSE, + 4*sizeof(float), + (void *)(2*sizeof(float))); glEnableVertexAttribArray(texAttrib); glBindVertexArray(0); - /* Create the element buffer object that will actually be drawn via - * glDrawElements(). */ + /* + * Create the element buffer object that will actually be drawn via + * glDrawElements(). + */ GLuint elements[] = { 0, 1, 2, 2, 3, 0 @@ -62,20 +99,36 @@ void gl_initialise(struct gl *gl, struct image *texture) glGenBuffers(1, &gl->ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gl->ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW); + glBufferData( + GL_ELEMENT_ARRAY_BUFFER, + sizeof(elements), + elements, + GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); /* Create the texture we'll draw to */ glGenTextures(1, &gl->texture); glBindTexture(GL_TEXTURE_2D, gl->texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture->width, texture->height, 0, GL_RGBA, - GL_UNSIGNED_BYTE, (GLvoid *)texture->buffer); + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_RGBA, + texture->width, + texture->height, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + (GLvoid *)texture->buffer); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + /* + * On little-endian processors, textures from Cairo have to have their + * red and blue channels swapped. + */ if (texture->swizzle) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_GREEN); @@ -94,11 +147,26 @@ void gl_clear(struct gl *gl, struct color *color) glClear(GL_COLOR_BUFFER_BIT); } -void gl_draw_texture(struct gl *gl, struct image *texture, int32_t x, int32_t y, int32_t width, int32_t height) +void gl_draw_texture( + struct gl *gl, + struct image *texture, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { if (texture->redraw) { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height, GL_RGBA, - GL_UNSIGNED_BYTE, (GLvoid *)texture->buffer); + glTexSubImage2D( + GL_TEXTURE_2D, + 0, + 0, + 0, + texture->width, + texture->height, + GL_RGBA, + GL_UNSIGNED_BYTE, + (GLvoid *)texture->buffer); + glGetError(); texture->redraw = false; } @@ -111,17 +179,20 @@ void load_shader(GLuint shader, const char *filename) errno = 0; FILE *fp = fopen(filename, "rb"); if (!fp) { - log_error("Failed to load shader %s: %s.\n", filename, strerror(errno)); + log_error("Failed to load shader %s: %s.\n", + filename, strerror(errno)); exit(EXIT_FAILURE); } if (fseek(fp, 0, SEEK_END) != 0) { - log_error("Failed to load shader %s: %s.\n", filename, strerror(errno)); + log_error("Failed to load shader %s: %s.\n", + filename, strerror(errno)); fclose(fp); exit(EXIT_FAILURE); } long size = ftell(fp); if (size <= 0) { - log_error("Failed to load shader %s: %s.\n", filename, strerror(errno)); + log_error("Failed to load shader %s: %s.\n", + filename, strerror(errno)); fclose(fp); exit(EXIT_FAILURE); } @@ -129,7 +200,8 @@ void load_shader(GLuint shader, const char *filename) GLchar *source = malloc(usize + 1); rewind(fp); if (fread(source, 1, usize, fp) != usize) { - log_error("Failed to load shader %s: %s.\n", filename, strerror(errno)); + log_error("Failed to load shader %s: %s.\n", + filename, strerror(errno)); fclose(fp); exit(EXIT_FAILURE); } @@ -171,3 +243,78 @@ GLuint create_shader_program(const char *vert, const char *frag) glLinkProgram(shader); return shader; } + +void GLAPIENTRY MessageCallback( + GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam) +{ + log_debug("Message from OpenGL:\n"); + log_debug("\tSource: %s\n", gl_debug_source_string(source)); + log_debug("\tType: %s\n", gl_debug_type_string(type)); + log_debug("\tSeverity: %s\n", gl_debug_severity_string(severity)); + log_debug("\tMessage: %s\n", message); +} + +const char *gl_debug_source_string(GLenum type) +{ + switch(type) { + case GL_DEBUG_SOURCE_API: + return "API"; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: + return "Window system"; + case GL_DEBUG_SOURCE_SHADER_COMPILER: + return "Shader compiler"; + case GL_DEBUG_SOURCE_THIRD_PARTY: + return "Third party"; + case GL_DEBUG_SOURCE_APPLICATION: + return "Application"; + case GL_DEBUG_SOURCE_OTHER: + return "Other"; + } + return "unknown"; +} + +const char *gl_debug_type_string(GLenum type) +{ + switch(type) { + case GL_DEBUG_TYPE_ERROR: + return "Error"; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + return "Deprecated behavior"; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + return "Undefined behavior"; + case GL_DEBUG_TYPE_PORTABILITY: + return "Portability"; + case GL_DEBUG_TYPE_PERFORMANCE: + return "Performance"; + case GL_DEBUG_TYPE_MARKER: + return "Marker"; + case GL_DEBUG_TYPE_PUSH_GROUP: + return "Push group"; + case GL_DEBUG_TYPE_POP_GROUP: + return "Pop group"; + case GL_DEBUG_TYPE_OTHER: + return "Other"; + } + return "Unknown"; +} + +const char *gl_debug_severity_string(GLenum type) +{ + switch(type) { + case GL_DEBUG_SEVERITY_HIGH: + return "High"; + case GL_DEBUG_SEVERITY_MEDIUM: + return "Medium"; + case GL_DEBUG_SEVERITY_LOW: + return "Low"; + case GL_DEBUG_SEVERITY_NOTIFICATION: + return "Notification"; + } + return "Unknown"; +} @@ -16,6 +16,12 @@ struct gl { void gl_initialise(struct gl *gl, struct image *texture); void gl_clear(struct gl *gl, struct color *color); -void gl_draw_texture(struct gl *gl, struct image *texture, int32_t x, int32_t y, int32_t width, int32_t height); +void gl_draw_texture( + struct gl *gl, + struct image *texture, + int32_t x, + int32_t y, + int32_t width, + int32_t height); #endif /* GL_H */ diff --git a/src/background.c b/src/image.c index 68beb5e..e05353c 100644 --- a/src/background.c +++ b/src/image.c @@ -1,28 +1,31 @@ -#include <stdlib.h> +#include <errno.h> #include <stdint.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <png.h> -#include "client.h" #include "log.h" -#include "background.h" +#include "image.h" #define HEADER_BYTES 8 -void load_background(struct client_state *state, const char *filename) +void image_load(struct image *image, const char *filename) { FILE *fp = fopen(filename, "rb"); uint8_t header[HEADER_BYTES]; if (!fp) { - log_error("Couldn't open %s\n", filename); + log_error("Couldn't open '%s': %s.\n", + filename, strerror(errno)); return; } if (fread(header, 1, HEADER_BYTES, fp) != HEADER_BYTES) { - log_error("Failed to read camera data: %s\n", filename); + log_error("Failed to read '%s': %s.\n", + filename, strerror(errno)); fclose(fp); return; } if (png_sig_cmp(header, 0, HEADER_BYTES)) { - log_error("Not a PNG file: %s\n", filename); + log_error("'%s' isn't a PNG file.\n", filename); fclose(fp); return; } @@ -63,7 +66,7 @@ void load_background(struct client_state *state, const char *filename) png_set_expand(png_ptr); png_set_gray_to_rgb(png_ptr); png_set_filler(png_ptr, 0xFFu, PNG_FILLER_AFTER); - + png_read_update_info(png_ptr, info_ptr); uint32_t row_bytes = png_get_rowbytes(png_ptr, info_ptr); @@ -85,7 +88,7 @@ void load_background(struct client_state *state, const char *filename) free(row_pointers); fclose(fp); - state->window.background_image.width = width; - state->window.background_image.height = height; - state->window.background_image.buffer = buffer; + image->width = width; + image->height = height; + image->buffer = buffer; } diff --git a/src/image.h b/src/image.h new file mode 100644 index 0000000..d5bd346 --- /dev/null +++ b/src/image.h @@ -0,0 +1,17 @@ +#ifndef IMAGE_H +#define IMAGE_H + +#include <stdbool.h> +#include <stdint.h> + +struct image { + uint8_t *buffer; + uint32_t width; + uint32_t height; + bool swizzle; + bool redraw; +}; + +void image_load(struct image *image, const char *filename); + +#endif /* IMAGE_H */ @@ -1,4 +1,5 @@ #include <stdio.h> +#include <time.h> void log_error(const char *const fmt, ...) { @@ -23,9 +24,11 @@ void log_debug(const char *const fmt, ...) #ifndef DEBUG return; #endif + struct timespec t; + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t); va_list args; va_start(args, fmt); - printf("[DEBUG]: "); + fprintf(stderr, "[%ld.%03ld][DEBUG]: ", t.tv_sec, t.tv_nsec / 1000000); vprintf(fmt, args); va_end(args); } @@ -2,6 +2,7 @@ #include <wayland-egl.h> #include <epoxy/gl.h> #include <errno.h> +#include <getopt.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> @@ -10,57 +11,89 @@ #include <unistd.h> #include <wayland-client.h> #include <wctype.h> +#include <wchar.h> #include <xkbcommon/xkbcommon.h> #include <locale.h> -#include "background.h" #include "client.h" #include "egl.h" #include "entry.h" +#include "image.h" #include "gl.h" +#include "log.h" #include "nelem.h" #include "xdg-shell-client-protocol.h" #undef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) -void draw_frame(struct surface *surface) +static void resize(struct client_state *state) { - surface->redraw = true; + struct surface *surface = &state->window.surface; + struct surface *entry_surface = &state->window.entry.surface; + + /* + * Resize the main window. + * EGL wants actual pixel width / height, so we have to scale the + * values provided by Wayland. + */ + surface->width = state->window.width * state->window.scale; + surface->height = state->window.height * state->window.scale; + wl_egl_window_resize( + surface->egl.window, + surface->width, + surface->height, + 0, + 0); + + /* + * Need to redraw the background at the new size. This entails a + * wl_surface_commit, so no need to do so explicitly here. + */ + state->window.surface.redraw = true; + + /* + * Center the password entry. + * Wayland wants "surface-local" width / height, so we have to divide + * the entry's pixel size by the scale factor. + */ + int32_t x = ( + state->window.width + - entry_surface->width / state->window.scale + ) / 2; + int32_t y = ( + state->window.height + - entry_surface->height / state->window.scale + ) / 2; + wl_subsurface_set_position( state->window.entry.wl_subsurface, x, y); + wl_surface_commit(state->window.entry.surface.wl_surface); } -static void -xdg_toplevel_configure(void *data, - struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, - struct wl_array *states) +static void xdg_toplevel_configure( + void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height, + struct wl_array *states) { struct client_state *state = data; if (width == 0 || height == 0) { - /* Compositor is deferring to us */ + /* Compositor is deferring to us, so don't do anything. */ + log_debug("XDG toplevel configure with no width or height.\n"); return; } - - int32_t scaled_width = width * state->window.scale; - int32_t scaled_height = height * state->window.scale; - - if (scaled_width != state->window.surface.width || scaled_height != state->window.surface.height) { - state->window.surface.width = scaled_width; - state->window.surface.height = scaled_height; - wl_egl_window_resize(state->window.surface.egl.window, scaled_width, scaled_height, 0, 0); - - wl_subsurface_set_position(state->window.entry.wl_subsurface, - (width - state->window.entry.surface.width / state->window.scale) / 2, - (height - state->window.entry.surface.height / state->window.scale) / 2 - ); - wl_surface_commit(state->window.entry.surface.wl_surface); - draw_frame(&state->window.surface); + log_debug("XDG toplevel configure, %d x %d.\n", width, height); + if (width != state->window.width || height != state->window.height) { + state->window.width = width; + state->window.height = height; + state->window.resize = true; } } -static void -xdg_toplevel_close(void *data, struct xdg_toplevel *toplevel) +static void xdg_toplevel_close(void *data, struct xdg_toplevel *toplevel) { struct client_state *state = data; state->closed = true; + log_debug("XDG toplevel close.\n"); } static const struct xdg_toplevel_listener xdg_toplevel_listener = { @@ -68,172 +101,239 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = { .close = xdg_toplevel_close }; -static void -xdg_surface_configure(void *data, - struct xdg_surface *xdg_surface, uint32_t serial) +static void xdg_surface_configure( + void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) { - xdg_surface_ack_configure(xdg_surface, serial); + xdg_surface_ack_configure(xdg_surface, serial); + log_debug("XDG surface configured.\n"); } static const struct xdg_surface_listener xdg_surface_listener = { - .configure = xdg_surface_configure, + .configure = xdg_surface_configure, }; -static void -wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, - uint32_t format, int32_t fd, uint32_t size) +static void wl_keyboard_keymap( + void *data, + struct wl_keyboard *wl_keyboard, + uint32_t format, + int32_t fd, + uint32_t size) { - struct client_state *client_state = data; - assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); - - char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); - assert(map_shm != MAP_FAILED); - - struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_string( - client_state->xkb_context, map_shm, - XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); - munmap(map_shm, size); - close(fd); - - struct xkb_state *xkb_state = xkb_state_new(xkb_keymap); - xkb_keymap_unref(client_state->xkb_keymap); - xkb_state_unref(client_state->xkb_state); - client_state->xkb_keymap = xkb_keymap; - client_state->xkb_state = xkb_state; + struct client_state *client_state = data; + assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); + + char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + assert(map_shm != MAP_FAILED); + + struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_string( + client_state->xkb_context, + map_shm, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(map_shm, size); + close(fd); + + struct xkb_state *xkb_state = xkb_state_new(xkb_keymap); + xkb_keymap_unref(client_state->xkb_keymap); + xkb_state_unref(client_state->xkb_state); + client_state->xkb_keymap = xkb_keymap; + client_state->xkb_state = xkb_state; + log_debug("Keyboard configured.\n"); } -static void -wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, - uint32_t serial, struct wl_surface *surface, - struct wl_array *keys) +static void wl_keyboard_enter( + void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + struct wl_surface *surface, + struct wl_array *keys) { + /* Deliberately left blank */ } -static void -wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard, - uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +static void wl_keyboard_leave( + void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + struct wl_surface *surface) { - struct client_state *client_state = data; - char buf[128]; - uint32_t keycode = key + 8; - xkb_keysym_t sym = xkb_state_key_get_one_sym(client_state->xkb_state, keycode); - xkb_keysym_get_name(sym, buf, sizeof(buf)); - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - struct entry *entry = &client_state->window.entry; - int len = xkb_state_key_get_utf8(client_state->xkb_state, keycode, buf, sizeof(buf)); - wchar_t ch; - mbtowc(&ch, buf, sizeof(buf)); - if (len > 0 && iswprint(ch)) { - if (entry->password_length < N_ELEM(entry->password) - 1) { - entry->password[entry->password_length] = ch; - entry->password_length++; - } - fprintf(stderr, "%ls\n", entry->password); - entry_update(&client_state->window.entry); - draw_frame(&client_state->window.entry.surface); - } else if (entry->password_length > 0 && sym == XKB_KEY_BackSpace) { - entry->password[entry->password_length - 1] = '\0'; - entry->password_length--; - entry_update(&client_state->window.entry); - draw_frame(&client_state->window.entry.surface); - } - } + /* Deliberately left blank */ } -static void -wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, - uint32_t serial, struct wl_surface *surface) +static void wl_keyboard_key( + void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state) { + struct client_state *client_state = data; + char buf[128]; + uint32_t keycode = key + 8; + xkb_keysym_t sym = xkb_state_key_get_one_sym( + client_state->xkb_state, + keycode); + xkb_keysym_get_name(sym, buf, sizeof(buf)); + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + struct entry *entry = &client_state->window.entry; + int len = xkb_state_key_get_utf8( + client_state->xkb_state, + keycode, + buf, + sizeof(buf)); + wchar_t ch; + mbtowc(&ch, buf, sizeof(buf)); + if (len > 0 && iswprint(ch)) { + if (entry->password_length < N_ELEM(entry->password) - 1) { + entry->password[entry->password_length] = ch; + entry->password_length++; + entry->password[entry->password_length] = L'\0'; + } + fprintf(stderr, "%ls\n", entry->password); + } else if (entry->password_length > 0 && sym == XKB_KEY_BackSpace) { + entry->password[entry->password_length - 1] = '\0'; + entry->password_length--; + } else if (sym == XKB_KEY_Escape) { + entry->password[0] = '\0'; + entry->password_length = 0; + } else if (entry->password_length > 0 + && (sym == XKB_KEY_Return + || sym == XKB_KEY_KP_Enter)) { + entry->password[0] = '\0'; + entry->password_length = 0; + } + entry_update(&client_state->window.entry); + client_state->window.entry.surface.redraw = true; + } } -static void -wl_keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, - uint32_t serial, uint32_t mods_depressed, - uint32_t mods_latched, uint32_t mods_locked, - uint32_t group) +static void wl_keyboard_modifiers( + void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) { - struct client_state *client_state = data; - xkb_state_update_mask(client_state->xkb_state, - mods_depressed, mods_latched, mods_locked, 0, 0, group); + struct client_state *client_state = data; + xkb_state_update_mask( + client_state->xkb_state, + mods_depressed, + mods_latched, + mods_locked, + 0, + 0, + group); } -static void -wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, - int32_t rate, int32_t delay) +static void wl_keyboard_repeat_info( + void *data, + struct wl_keyboard *wl_keyboard, + int32_t rate, + int32_t delay) { + /* Deliberately left blank */ } static const struct wl_keyboard_listener wl_keyboard_listener = { - .keymap = wl_keyboard_keymap, - .enter = wl_keyboard_enter, - .leave = wl_keyboard_leave, - .key = wl_keyboard_key, - .modifiers = wl_keyboard_modifiers, - .repeat_info = wl_keyboard_repeat_info, + .keymap = wl_keyboard_keymap, + .enter = wl_keyboard_enter, + .leave = wl_keyboard_leave, + .key = wl_keyboard_key, + .modifiers = wl_keyboard_modifiers, + .repeat_info = wl_keyboard_repeat_info, }; -static void -wl_seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) +static void wl_seat_capabilities( + void *data, + struct wl_seat *wl_seat, + uint32_t capabilities) { - struct client_state *state = data; - - bool have_keyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; - - if (have_keyboard && state->wl_keyboard == NULL) { - state->wl_keyboard = wl_seat_get_keyboard(state->wl_seat); - wl_keyboard_add_listener(state->wl_keyboard, - &wl_keyboard_listener, state); - } else if (!have_keyboard && state->wl_keyboard != NULL) { - wl_keyboard_release(state->wl_keyboard); - state->wl_keyboard = NULL; - } + struct client_state *state = data; + + bool have_keyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; + + if (have_keyboard && state->wl_keyboard == NULL) { + state->wl_keyboard = wl_seat_get_keyboard(state->wl_seat); + wl_keyboard_add_listener( + state->wl_keyboard, + &wl_keyboard_listener, + state); + log_debug("Got keyboard from seat.\n"); + } else if (!have_keyboard && state->wl_keyboard != NULL) { + wl_keyboard_release(state->wl_keyboard); + state->wl_keyboard = NULL; + log_debug("Released keyboard.\n"); + } } -static void -wl_seat_name(void *data, struct wl_seat *wl_seat, const char *name) +static void wl_seat_name(void *data, struct wl_seat *wl_seat, const char *name) { /* Deliberately left blank */ } static const struct wl_seat_listener wl_seat_listener = { - .capabilities = wl_seat_capabilities, - .name = wl_seat_name, + .capabilities = wl_seat_capabilities, + .name = wl_seat_name, }; -static void -xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) +static void xdg_wm_base_ping( + void *data, + struct xdg_wm_base *xdg_wm_base, + uint32_t serial) { - xdg_wm_base_pong(xdg_wm_base, serial); + xdg_wm_base_pong(xdg_wm_base, serial); } static const struct xdg_wm_base_listener xdg_wm_base_listener = { - .ping = xdg_wm_base_ping, + .ping = xdg_wm_base_ping, }; -static void output_geometry(void *data, struct wl_output *wl_output, - int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, - int32_t subpixel, const char *make, const char *model, int32_t transform) +static void output_geometry( + void *data, + struct wl_output *wl_output, + int32_t x, + int32_t y, + int32_t physical_width, + int32_t physical_height, + int32_t subpixel, + const char *make, + const char *model, + int32_t transform) { /* Deliberately left blank */ } -static void output_mode(void *data, struct wl_output *wl_output, - uint32_t flags, int32_t width, int32_t height, int32_t refresh) +static void output_mode( + void *data, + struct wl_output *wl_output, + uint32_t flags, + int32_t width, + int32_t height, + int32_t refresh) { /* Deliberately left blank */ } -static void output_scale(void *data, struct wl_output *wl_output, int32_t factor) +static void output_scale( + void *data, + struct wl_output *wl_output, + int32_t factor) { struct client_state *state = data; state->window.scale = MAX(factor, (int32_t)state->window.scale); - wl_surface_set_buffer_scale(state->window.surface.wl_surface, state->window.scale); - wl_surface_set_buffer_scale(state->window.entry.surface.wl_surface, state->window.scale); + log_debug("Output scale factor is %d.\n", factor); } static void output_done(void *data, struct wl_output *wl_output) { + //struct client_state *state = data; /* TODO */ + log_debug("Output configuration done.\n"); } static const struct wl_output_listener wl_output_listener = { @@ -243,53 +343,90 @@ static const struct wl_output_listener wl_output_listener = { .scale = output_scale, }; -static void registry_global(void *data, struct wl_registry *wl_registry, - uint32_t name, const char *interface, uint32_t version) +static void registry_global( + void *data, + struct wl_registry *wl_registry, + uint32_t name, + const char *interface, + uint32_t version) { struct client_state *state = data; - //fprintf(stderr, "%s\n", interface); if (strcmp(interface, wl_compositor_interface.name) == 0) { state->wl_compositor = wl_registry_bind( - wl_registry, name, &wl_compositor_interface, 4); + wl_registry, + name, + &wl_compositor_interface, + 4); + log_debug("Bound to compositor %u.\n", name); } else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { state->wl_subcompositor = wl_registry_bind( - wl_registry, name, &wl_subcompositor_interface, 1); + wl_registry, + name, + &wl_subcompositor_interface, + 1); + log_debug("Bound to subcompositor %u.\n", name); } else if (strcmp(interface, wl_seat_interface.name) == 0) { state->wl_seat = wl_registry_bind( - wl_registry, name, &wl_seat_interface, 7); - wl_seat_add_listener(state->wl_seat, - &wl_seat_listener, state); + wl_registry, + name, + &wl_seat_interface, + 7); + wl_seat_add_listener( + state->wl_seat, + &wl_seat_listener, + state); + log_debug("Bound to seat %u.\n", name); } else if (strcmp(interface, wl_output_interface.name) == 0) { state->wl_output = wl_registry_bind( - wl_registry, name, &wl_output_interface, 3); - wl_output_add_listener(state->wl_output, - &wl_output_listener, state); + wl_registry, + name, + &wl_output_interface, + 3); + wl_output_add_listener( + state->wl_output, + &wl_output_listener, + state); + log_debug("Bound to output %u.\n", name); } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { state->xdg_wm_base = wl_registry_bind( - wl_registry, name, &xdg_wm_base_interface, 1); - xdg_wm_base_add_listener(state->xdg_wm_base, - &xdg_wm_base_listener, state); + wl_registry, + name, + &xdg_wm_base_interface, + 1); + xdg_wm_base_add_listener( + state->xdg_wm_base, + &xdg_wm_base_listener, + state); + log_debug("Bound to xdg_wm_base %u.\n", name); } } -static void registry_global_remove(void *data, struct wl_registry *wl_registry, +static void registry_global_remove( + void *data, + struct wl_registry *wl_registry, uint32_t name) { /* Deliberately left blank */ } static const struct wl_registry_listener wl_registry_listener = { - .global = registry_global, - .global_remove = registry_global_remove, + .global = registry_global, + .global_remove = registry_global_remove, }; -static void surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) +static void surface_enter( + void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) { /* TODO */ fprintf(stderr, "TODO: enter\n"); } -static void surface_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) +static void surface_leave( + void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) { /* TODO */ fprintf(stderr, "TODO: leave\n"); @@ -303,67 +440,309 @@ static const struct wl_surface_listener wl_surface_listener = { int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); - struct client_state state = { 0 }; - load_background(&state, "Night-1800.png"); - state.window.background_color = (struct color){ 0.2, 0.8, 0.8, 1.0 }; - state.window.surface.width = 640; - state.window.surface.height = 480; - state.window.entry.surface.width = 80; - state.window.entry.surface.height = 40; - state.window.entry.border.width = 12; - state.window.entry.border.outline_width = 3; + struct client_state state = { + .window = { + .background_color = {0.89, 0.8, 0.824, 1.0}, + .scale = 1, + .surface = { .width = 640, .height = 480 }, + .entry = { + .border = { + .width = 6, + .outline_width = 2, + .color = {0.976, 0.149, 0.447, 1.0}, + .outline_color = {0.031, 0.031, 0.0, 1.0}, + }, + .font_name = "Sans Bold", + .font_size = 24, + .padding = 8, + .tight_layout = true, + .password_character = L'·', + .num_characters = 12, + .background_color = {0.106, 0.114, 0.118, 1.0}, + .foreground_color = {1.0, 1.0, 1.0, 1.0} + } + } + }; + + + struct option long_options[] = { + {"background_image", required_argument, NULL, 'b'}, + {"background_color", required_argument, NULL, 'B'}, + {"border_width", required_argument, NULL, 'r'}, + {"border_color", required_argument, NULL, 'R'}, + {"outline_width", required_argument, NULL, 'o'}, + {"outline_color", required_argument, NULL, 'O'}, + {"entry_padding", required_argument, NULL, 'e'}, + {"entry_color", required_argument, NULL, 'E'}, + {"text_color", required_argument, NULL, 'T'}, + {"font_name", required_argument, NULL, 'f'}, + {"font_size", required_argument, NULL, 'F'}, + {"password_character", required_argument, NULL, 'c'}, + {"width_characters", required_argument, NULL, 'n'}, + {"wide_layout", no_argument, NULL, 'w'}, + {NULL, 0, NULL, 0} + }; + const char *short_options = "b:B:e:E:f:F:r:R:n:o:O:c:T:w"; + + int opt = getopt_long(argc, argv, short_options, long_options, NULL); + while (opt != -1) { + switch (opt) { + case 'b': + image_load( + &state.window.background_image, + optarg); + break; + case 'B': + state.window.background_color = + hex_to_color(optarg); + break; + case 'r': + state.window.entry.border.width = + strtol(optarg, NULL, 0); + break; + case 'R': + state.window.entry.border.color = + hex_to_color(optarg); + break; + case 'o': + state.window.entry.border.outline_width = + strtol(optarg, NULL, 0); + break; + case 'O': + state.window.entry.border.outline_color = + hex_to_color(optarg); + break; + case 'e': + state.window.entry.padding = + strtol(optarg, NULL, 0); + break; + case 'E': + state.window.entry.background_color = + hex_to_color(optarg); + break; + case 'T': + state.window.entry.foreground_color = + hex_to_color(optarg); + break; + case 'f': + state.window.entry.font_name = optarg; + break; + case 'F': + state.window.entry.font_size = + strtol(optarg, NULL, 0); + break; + case 'c': + mbrtowc( + &state.window.entry.password_character, + optarg, + 4, + NULL); + break; + case 'n': + state.window.entry.num_characters = + strtol(optarg, NULL, 0); + case 'w': + state.window.entry.tight_layout = false; + break; + case '?': + break; + } + opt = getopt_long(argc, argv, short_options, long_options, NULL); + } + + + /* + * Initial Wayland & XKB setup. + * The first thing to do is connect a listener to the global registry, + * so that we can bind to the various global objects and start talking + * to Wayland. + */ state.wl_display = wl_display_connect(NULL); + if (state.wl_display == NULL) { + log_error("Couldn't connect to Wayland display.\n"); + exit(EXIT_FAILURE); + } state.wl_registry = wl_display_get_registry(state.wl_display); state.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - wl_registry_add_listener(state.wl_registry, &wl_registry_listener, &state); + if (state.xkb_context == NULL) { + log_error("Couldn't create an XKB context.\n"); + exit(EXIT_FAILURE); + } + wl_registry_add_listener( + state.wl_registry, + &wl_registry_listener, + &state); + + /* + * After this first roundtrip, the only thing that should have happened + * is our registry_global() function being called and setting up the + * various global object bindings. + */ + log_debug("First roundtrip start.\n"); wl_display_roundtrip(state.wl_display); + log_debug("First roundtrip done.\n"); + + /* + * The next roundtrip causes the listeners we set up in + * registry_global() to be called. Notably, the output should be + * configured, telling us the scale factor. + */ + log_debug("Second roundtrip start.\n"); + wl_display_roundtrip(state.wl_display); + log_debug("Second roundtrip done.\n"); + + /* + * Next, we create the Wayland surfaces that we need - one for + * the whole window, and another for the password entry box. + */ + /* + * The main window surface takes on the xdg_surface and xdg_toplevel + * roles, in order to receive configure events to change its size. + */ + log_debug("Creating main window surface.\n"); + state.window.surface.wl_surface = + wl_compositor_create_surface(state.wl_compositor); + wl_surface_add_listener( + state.window.surface.wl_surface, + &wl_surface_listener, + &state); + wl_surface_set_buffer_scale( + state.window.surface.wl_surface, + state.window.scale); + + state.window.xdg_surface = xdg_wm_base_get_xdg_surface( + state.xdg_wm_base, + state.window.surface.wl_surface); + xdg_surface_add_listener( + state.window.xdg_surface, + &xdg_surface_listener, + &state); + + state.window.xdg_toplevel = + xdg_surface_get_toplevel(state.window.xdg_surface); + xdg_toplevel_add_listener( + state.window.xdg_toplevel, + &xdg_toplevel_listener, + &state); + xdg_toplevel_set_title( + state.window.xdg_toplevel, + "Greetd mini wayland greeter"); - state.window.surface.wl_surface = wl_compositor_create_surface(state.wl_compositor); - state.window.scale = 1; - wl_surface_add_listener(state.window.surface.wl_surface, &wl_surface_listener, &state); - state.window.xdg_surface = xdg_wm_base_get_xdg_surface(state.xdg_wm_base, state.window.surface.wl_surface); - xdg_surface_add_listener(state.window.xdg_surface, &xdg_surface_listener, &state); - state.window.xdg_toplevel = xdg_surface_get_toplevel(state.window.xdg_surface); - xdg_toplevel_add_listener(state.window.xdg_toplevel, - &xdg_toplevel_listener, &state); - xdg_toplevel_set_title(state.window.xdg_toplevel, "Greetd mini wayland greeter"); wl_surface_commit(state.window.surface.wl_surface); - entry_init(&state.window.entry); - xdg_toplevel_set_min_size(state.window.xdg_toplevel, - state.window.entry.surface.width, - state.window.entry.surface.height); - - state.window.entry.surface.wl_surface = wl_compositor_create_surface(state.wl_compositor); - state.window.entry.wl_subsurface = wl_subcompositor_get_subsurface(state.wl_subcompositor, state.window.entry.surface.wl_surface, state.window.surface.wl_surface); + /* + * The password entry surface takes on a subsurface role and is set to + * be desynchronised, so that we can update it independently from the + * main window. + */ + log_debug("Creating password entry surface.\n"); + state.window.entry.surface.wl_surface = + wl_compositor_create_surface(state.wl_compositor); + wl_surface_set_buffer_scale( + state.window.entry.surface.wl_surface, + state.window.scale); + + state.window.entry.wl_subsurface = wl_subcompositor_get_subsurface( + state.wl_subcompositor, + state.window.entry.surface.wl_surface, + state.window.surface.wl_surface); wl_subsurface_set_desync(state.window.entry.wl_subsurface); - wl_surface_commit(state.window.entry.surface.wl_surface); - surface_initialise(&state.window.surface, state.wl_display, &state.window.background_image); - surface_initialise(&state.window.entry.surface, state.wl_display, &state.window.entry.image); + wl_surface_commit(state.window.entry.surface.wl_surface); + /* + * Initialise the Pango & Cairo structures for rendering the password + * entry. Cairo needs to know the size of the surface it's creating, + * and there's no way to resize it aside from tearing everything down + * and starting again, so we make sure to do this after we've + * determined our output's scale factor. This stops us being able to + * change the scale factor after startup, but this is just a greeter, + * which shouldn't be moving between outputs while running. + */ + log_debug("Initialising Pango / Cairo.\n"); + entry_init(&state.window.entry, state.window.scale); + log_debug("Pango / Cairo initialised.\n"); + + /* + * Tell the compositor not to make our window smaller than the password + * entry. + */ + xdg_toplevel_set_min_size( + state.window.xdg_toplevel, + state.window.entry.surface.width / state.window.scale, + state.window.entry.surface.height / state.window.scale); + + /* + * Now that we've done all our Wayland-related setup, we do another + * roundtrip. This should cause the XDG toplevel window to be + * configured, after which we're ready to start drawing to the screen. + */ + log_debug("Third roundtrip start.\n"); wl_display_roundtrip(state.wl_display); - egl_make_current(&state.window.surface.egl); - egl_swap_buffers(&state.window.surface.egl); - egl_make_current(&state.window.entry.surface.egl); - egl_swap_buffers(&state.window.entry.surface.egl); - draw_frame(&state.window.surface); - draw_frame(&state.window.entry.surface); + log_debug("Third roundtrip done.\n"); + + /* + * Create the various EGL and GL structures for each surface, and + * perform an initial render of everything. + */ + log_debug("Initialising main window surface.\n"); + surface_initialise( + &state.window.surface, + state.wl_display, + &state.window.background_image); + surface_draw( + &state.window.surface, + &state.window.background_color, + &state.window.background_image); + + log_debug("Initialising entry window surface.\n"); + surface_initialise( + &state.window.entry.surface, + state.wl_display, + &state.window.entry.image); + surface_draw( + &state.window.entry.surface, + &state.window.background_color, + &state.window.entry.image); + + /* Call resize() just to center the password entry properly. */ + resize(&state); + + /* + * We've just rendered everything and resized, so we don't need to do + * it again right now. + */ + state.window.resize = false; + state.window.surface.redraw = false; + state.window.entry.surface.redraw = false; + while (wl_display_dispatch(state.wl_display) != -1) { if (state.closed) { break; } + if (state.window.resize) { + resize(&state); + state.window.resize = false; + } if (state.window.surface.redraw) { - surface_draw(&state.window.surface, &state.window.background_color, &state.window.background_image); + surface_draw( + &state.window.surface, + &state.window.background_color, + &state.window.background_image); state.window.surface.redraw = false; } if (state.window.entry.surface.redraw) { - surface_draw(&state.window.entry.surface, &state.window.background_color, &state.window.entry.image); + surface_draw( + &state.window.entry.surface, + &state.window.background_color, + &state.window.entry.image); state.window.entry.surface.redraw = false; } } + log_info("Window closed, performing cleanup.\n"); wl_display_disconnect(state.wl_display); + log_info("Finished, exiting.\n"); return 0; } diff --git a/src/surface.c b/src/surface.c index 6253801..76abec9 100644 --- a/src/surface.c +++ b/src/surface.c @@ -5,14 +5,24 @@ #undef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) -void surface_initialise(struct surface *surface, struct wl_display *wl_display, struct image *texture) +void surface_initialise( + struct surface *surface, + struct wl_display *wl_display, + struct image *texture) { - egl_create_window(&surface->egl, surface->wl_surface, surface->width, surface->height); + egl_create_window( + &surface->egl, + surface->wl_surface, + surface->width, + surface->height); egl_create_context(&surface->egl, wl_display); gl_initialise(&surface->gl, texture); } -void surface_draw(struct surface *surface, struct color *color, struct image *texture) +void surface_draw( + struct surface *surface, + struct color *color, + struct image *texture) { egl_make_current(&surface->egl); gl_clear(&surface->gl, color); diff --git a/src/surface.h b/src/surface.h index 2b2910a..bd79a80 100644 --- a/src/surface.h +++ b/src/surface.h @@ -15,7 +15,13 @@ struct surface { bool redraw; }; -void surface_initialise(struct surface *surface, struct wl_display *wl_display, struct image *texture); -void surface_draw(struct surface *surface, struct color *color, struct image *texture); +void surface_initialise( + struct surface *surface, + struct wl_display *wl_display, + struct image *texture); +void surface_draw( + struct surface *surface, + struct color *color, + struct image *texture); #endif /* SURFACE_H */ |