/* * 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 */ #define _POSIX_C_SOURCE 200112L // for setenv() from stdlib.h #include "mir_toolkit/mir_client_library.h" #include "mir_toolkit/events/input/input_event.h" #include #include #include #include #include /* sleep() */ #include #include #define BYTES_PER_PIXEL(f) ((f) == mir_pixel_format_bgr_888 ? 3 : 4) #define MIN(a, b) ((a) <= (b) ? (a) : (b)) typedef struct { uint8_t r, g, b, a; } Color; static volatile bool running = true; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t change = PTHREAD_COND_INITIALIZER; static bool changed = true; static int force_radius = 0; static void shutdown(int signum) { if (running) { running = false; changed = true; pthread_cond_signal(&change); printf("Signal %d received. Good night.\n", signum); } } static void blend(uint32_t *dest, uint32_t src, int alpha_shift) { uint8_t *d = (uint8_t*)dest; uint8_t *s = (uint8_t*)&src; uint32_t src_alpha = (uint32_t)(src >> alpha_shift) & 0xff; uint32_t dest_alpha = 0xff - src_alpha; int i; for (i = 0; i < 4; i++) { d[i] = (uint8_t) ( ( ((uint32_t)d[i] * dest_alpha) + ((uint32_t)s[i] * src_alpha) ) >> 8 /* Close enough, and faster than /255 */ ); } *dest |= (0xff << alpha_shift); /* Restore alpha 1.0 in the destination */ } static void put_pixels(void *where, int count, MirPixelFormat format, const Color *color) { uint32_t pixel = 0; int alpha_shift = -1; int n; /* * We are blending in software, so can pretend that * mir_pixel_format_abgr_8888 == mir_pixel_format_xbgr_8888 * mir_pixel_format_argb_8888 == mir_pixel_format_xrgb_8888 */ switch (format) { case mir_pixel_format_abgr_8888: case mir_pixel_format_xbgr_8888: alpha_shift = 24; pixel = (uint32_t)color->a << 24 | (uint32_t)color->b << 16 | (uint32_t)color->g << 8 | (uint32_t)color->r; break; case mir_pixel_format_argb_8888: case mir_pixel_format_xrgb_8888: alpha_shift = 24; pixel = (uint32_t)color->a << 24 | (uint32_t)color->r << 16 | (uint32_t)color->g << 8 | (uint32_t)color->b; break; case mir_pixel_format_bgr_888: for (n = 0; n < count; n++) { uint8_t *p = (uint8_t*)where + n * 3; p[0] = color->b; p[1] = color->g; p[2] = color->r; } count = 0; break; default: count = 0; break; } if (alpha_shift >= 0) { for (n = 0; n < count; n++) blend((uint32_t*)where + n, pixel, alpha_shift); } else { for (n = 0; n < count; n++) ((uint32_t*)where)[n] = pixel; } } static void clear_region(const MirGraphicsRegion *region, const Color *color) { int y; char *row = region->vaddr; for (y = 0; y < region->height; y++) { put_pixels(row, region->width, region->pixel_format, color); row += region->stride; } } static void draw_box(const MirGraphicsRegion *region, int x, int y, int size, const Color *color) { if (x >= 0 && y >= 0 && x+size < region->width && y+size < region->height) { int j; char *row = region->vaddr + (y * region->stride) + (x * BYTES_PER_PIXEL(region->pixel_format)); for (j = 0; j < size; j++) { put_pixels(row, size, region->pixel_format, color); row += region->stride; } } } static void copy_region(const MirGraphicsRegion *dest, const MirGraphicsRegion *src) { int height = MIN(src->height, dest->height); int width = MIN(src->width, dest->width); int y; const char *srcrow = src->vaddr; char *destrow = dest->vaddr; int copy = width * BYTES_PER_PIXEL(dest->pixel_format); for (y = 0; y < height; y++) { memcpy(destrow, srcrow, copy); srcrow += src->stride; destrow += dest->stride; } } static void on_event(MirSurface *surface, const MirEvent *event, void *context) { (void)surface; MirGraphicsRegion *canvas = (MirGraphicsRegion*)context; static const Color color[] = { {0x80, 0xff, 0x00, 0xff}, {0x00, 0xff, 0x80, 0xff}, {0xff, 0x00, 0x80, 0xff}, {0xff, 0x80, 0x00, 0xff}, {0x00, 0x80, 0xff, 0xff}, {0x80, 0x00, 0xff, 0xff}, {0xff, 0xff, 0x00, 0xff}, {0x00, 0xff, 0xff, 0xff}, {0xff, 0x00, 0xff, 0xff}, {0xff, 0x00, 0x00, 0xff}, {0x00, 0xff, 0x00, 0xff}, {0x00, 0x00, 0xff, 0xff}, }; MirEventType event_type = mir_event_get_type(event); pthread_mutex_lock(&mutex); if (event_type == mir_event_type_input) { static size_t base_color = 0; static size_t max_fingers = 0; static float max_pressure = 1.0f; MirInputEvent const* input_event = mir_event_get_input_event(event); MirTouchEvent const* tev = NULL; MirPointerEvent const* pev = NULL; unsigned touch_count = 0; bool ended = false; MirInputEventType type = mir_input_event_get_type(input_event); switch (type) { case mir_input_event_type_touch: tev = mir_input_event_get_touch_event(input_event); touch_count = mir_touch_event_point_count(tev); ended = touch_count == 1 && (mir_touch_event_action(tev, 0) == mir_touch_action_up); break; case mir_input_event_type_pointer: pev = mir_input_event_get_pointer_event(input_event); ended = mir_pointer_event_action(pev) == mir_pointer_action_button_up; touch_count = mir_pointer_event_button_state(pev, mir_pointer_button_primary) ? 1 : 0; default: break; } if (ended) { base_color = (base_color + max_fingers) % (sizeof(color)/sizeof(color[0])); max_fingers = 0; } else if (touch_count) { size_t p; if (touch_count > max_fingers) max_fingers = touch_count; for (p = 0; p < touch_count; p++) { int x = 0; int y = 0; int radius = 1; float pressure = 1.0f; if (tev != NULL) { x = mir_touch_event_axis_value(tev, p, mir_touch_axis_x); y = mir_touch_event_axis_value(tev, p, mir_touch_axis_y); float m = mir_touch_event_axis_value(tev, p, mir_touch_axis_touch_major); float n = mir_touch_event_axis_value(tev, p, mir_touch_axis_touch_minor); radius = (m + n) / 4; /* Half the average */ // mir_touch_axis_touch_major can be 0 if (radius < 5) radius = 5; pressure = mir_touch_event_axis_value(tev, p, mir_touch_axis_pressure); } else if (pev != NULL) { x = mir_pointer_event_axis_value(pev, mir_pointer_axis_x); y = mir_pointer_event_axis_value(pev, mir_pointer_axis_y); pressure = 0.5f; radius = 5; } if (force_radius) radius = force_radius; size_t c = (base_color + p) % (sizeof(color)/sizeof(color[0])); Color tone = color[c]; if (pressure > max_pressure) max_pressure = pressure; pressure /= max_pressure; tone.a *= pressure; draw_box(canvas, x - radius, y - radius, 2*radius, &tone); } changed = true; } } else if (event_type == mir_event_type_close_surface) { static int closing = 0; ++closing; if (closing == 1) printf("Sure you don't want to save your work?\n"); else if (closing > 1) { printf("Oh I forgot you can't save your work. Quitting now...\n"); running = false; changed = true; } } else if (event_type == mir_event_type_resize) { changed = true; } pthread_cond_signal(&change); pthread_mutex_unlock(&mutex); } static const MirDisplayOutput *find_active_output( const MirDisplayConfiguration *conf) { const MirDisplayOutput *output = NULL; int d; for (d = 0; d < (int)conf->num_outputs; d++) { const MirDisplayOutput *out = conf->outputs + d; if (out->used && out->connected && out->num_modes && out->current_mode < out->num_modes) { output = out; break; } } return output; } int main(int argc, char *argv[]) { static const Color background = {180, 180, 150, 255}; MirConnection *conn; MirSurface *surf; MirGraphicsRegion canvas; unsigned int f; int swap_interval = 0; char *mir_socket = NULL; if (argc > 1) { int i; for (i = 1; i < argc; i++) { int help = 0; const char *arg = argv[i]; if (arg[0] == '-') { if (arg[1] == '-' && arg[2] == '\0') break; switch (arg[1]) { case 'm': ++i; if (i < argc) mir_socket = argv[i]; else help = 1; break; case 'r': ++i; if (i < argc) force_radius = atoi(argv[i]); else help = 1; break; case 'w': swap_interval = 1; break; case 'h': default: help = 1; break; } } else { help = 1; } if (help) { printf("Usage: %s []\n" " -h Show this help text\n" " -m Mir server socket\n" " -r Force paint brush radius\n" " -w Wait for vblank (don't drop frames)\n" " -- Ignore further arguments\n" , argv[0]); return 0; } } } // We do our own resampling now. We can keep up with raw input... // TODO: Replace setenv with a proper Mir function (LP: #1439590) setenv("MIR_CLIENT_INPUT_RATE", "0", 0); conn = mir_connect_sync(mir_socket, argv[0]); if (!mir_connection_is_valid(conn)) { fprintf(stderr, "Could not connect to a display server: %s\n", mir_connection_get_error_message(conn)); return 1; } MirDisplayConfiguration *display_config = mir_connection_create_display_config(conn); const MirDisplayOutput *dinfo = find_active_output(display_config); if (dinfo == NULL) { fprintf(stderr, "No active outputs found.\n"); mir_connection_release(conn); return 1; } unsigned int const pf_size = 32; MirPixelFormat formats[pf_size]; unsigned int valid_formats; mir_connection_get_available_surface_formats(conn, formats, pf_size, &valid_formats); MirPixelFormat pixel_format = mir_pixel_format_invalid; for (f = 0; f < valid_formats; f++) { if (BYTES_PER_PIXEL(formats[f]) == 4) { pixel_format = formats[f]; break; } } if (pixel_format == mir_pixel_format_invalid) { fprintf(stderr, "Could not find a fast 32-bit pixel format\n"); mir_connection_release(conn); return 1; } int width = dinfo->modes[dinfo->current_mode].horizontal_resolution; int height = dinfo->modes[dinfo->current_mode].vertical_resolution; mir_display_config_destroy(display_config); MirSurfaceSpec *spec = mir_connection_create_spec_for_normal_surface(conn, width, height, pixel_format); mir_surface_spec_set_name(spec, "Mir Fingerpaint"); mir_surface_spec_set_buffer_usage(spec, mir_buffer_usage_software); surf = mir_surface_create_sync(spec); mir_surface_spec_release(spec); if (surf != NULL) { mir_surface_set_swapinterval(surf, swap_interval); mir_surface_set_event_handler(surf, &on_event, &canvas); canvas.width = width; canvas.height = height; canvas.stride = canvas.width * BYTES_PER_PIXEL(pixel_format); canvas.pixel_format = pixel_format; canvas.vaddr = (char*)malloc(canvas.stride * canvas.height); if (canvas.vaddr != NULL) { signal(SIGINT, shutdown); signal(SIGTERM, shutdown); signal(SIGHUP, shutdown); clear_region(&canvas, &background); MirBufferStream *bs = mir_surface_get_buffer_stream(surf); while (running) { MirGraphicsRegion backbuffer; mir_buffer_stream_get_graphics_region(bs, &backbuffer); pthread_mutex_lock(&mutex); while (!changed) pthread_cond_wait(&change, &mutex); changed = false; copy_region(&backbuffer, &canvas); pthread_mutex_unlock(&mutex); mir_buffer_stream_swap_buffers_sync(bs); } /* Ensure canvas won't be used after it's freed */ mir_surface_set_event_handler(surf, NULL, NULL); free(canvas.vaddr); } else { fprintf(stderr, "Failed to malloc canvas\n"); } mir_surface_release_sync(surf); } else { fprintf(stderr, "mir_connection_create_surface_sync failed\n"); } mir_connection_release(conn); return 0; }