diff --git a/examples/meson.build b/examples/meson.build index 1ce9c342..e2c0189c 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -7,6 +7,7 @@ executable('rotation', 'rotation.c', dependencies: wlroots, link_with: lib_share executable('pointer', 'pointer.c', dependencies: wlroots, link_with: lib_shared) executable('touch', 'touch.c', dependencies: wlroots, link_with: lib_shared) executable('tablet', 'tablet.c', dependencies: wlroots, link_with: lib_shared) +executable('output-layout', 'output-layout.c', dependencies: wlroots, link_with: lib_shared) compositor_src = [ 'compositor/main.c', diff --git a/examples/output-layout.c b/examples/output-layout.c new file mode 100644 index 00000000..c16af634 --- /dev/null +++ b/examples/output-layout.c @@ -0,0 +1,344 @@ +#define _POSIX_C_SOURCE 199309L +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "shared.h" +#include "cat.h" + +struct sample_state { + struct wl_list config; + struct wlr_renderer *renderer; + struct wlr_texture *cat_texture; + struct wlr_output_layout *layout; + float x_offs, y_offs; + float x_vel, y_vel; + struct wlr_output *main_output; + struct wl_list outputs; +}; + +struct output_config { + char *name; + enum wl_output_transform transform; + int x, y; + struct wl_list link; +}; + +static void handle_output_frame(struct output_state *output, struct timespec *ts) { + struct compositor_state *state = output->compositor; + struct sample_state *sample = state->data; + struct wlr_output *wlr_output = output->output; + + wlr_output_make_current(wlr_output); + wlr_renderer_begin(sample->renderer, wlr_output); + + if (wlr_output_layout_intersects(sample->layout, output->output, + sample->x_offs, sample->y_offs, + sample->x_offs + 128, sample->y_offs + 128)) { + float matrix[16]; + + // transform global coordinates to local coordinates + int local_x = sample->x_offs; + int local_y = sample->y_offs; + wlr_output_layout_output_coords(sample->layout, output->output, &local_x, + &local_y); + + wlr_texture_get_matrix(sample->cat_texture, &matrix, + &wlr_output->transform_matrix, local_x, local_y); + wlr_render_with_matrix(sample->renderer, + sample->cat_texture, &matrix); + } + + wlr_renderer_end(sample->renderer); + wlr_output_swap_buffers(wlr_output); + + if (output->output == sample->main_output) { + long ms = (ts->tv_sec - output->last_frame.tv_sec) * 1000 + + (ts->tv_nsec - output->last_frame.tv_nsec) / 1000000; + // how many seconds have passed since the last frame + float seconds = ms / 1000.0f; + + if (seconds > 0.1f) { + // XXX when we switch vt, the rendering loop stops so try to detect + // that and pause when it happens. + seconds = 0.0f; + } + + // check for collisions and bounce + bool ur_collision = !wlr_output_layout_output_at(sample->layout, + sample->x_offs + 128, sample->y_offs); + bool ul_collision = !wlr_output_layout_output_at(sample->layout, + sample->x_offs, sample->y_offs); + bool ll_collision = !wlr_output_layout_output_at(sample->layout, + sample->x_offs, sample->y_offs + 128); + bool lr_collision = !wlr_output_layout_output_at(sample->layout, + sample->x_offs + 128, sample->y_offs + 128); + + if (ur_collision && ul_collision && ll_collision && lr_collision) { + // oops we went off the screen somehow + struct wlr_output_layout_output *main_l_output; + main_l_output = wlr_output_layout_get(sample->layout, sample->main_output); + sample->x_offs = main_l_output->x + 20; + sample->y_offs = main_l_output->y + 20; + } else if (ur_collision && ul_collision) { + sample->y_vel = fabs(sample->y_vel); + } else if (lr_collision && ll_collision) { + sample->y_vel = -fabs(sample->y_vel); + } else if (ll_collision && ul_collision) { + sample->x_vel = fabs(sample->x_vel); + } else if (ur_collision && lr_collision) { + sample->x_vel = -fabs(sample->x_vel); + } else { + if (ur_collision || lr_collision) { + sample->x_vel = -fabs(sample->x_vel); + } + if (ul_collision || ll_collision) { + sample->x_vel = fabs(sample->x_vel); + } + if (ul_collision || ur_collision) { + sample->y_vel = fabs(sample->y_vel); + } + if (ll_collision || lr_collision) { + sample->y_vel = -fabs(sample->y_vel); + } + } + + sample->x_offs += sample->x_vel * seconds; + sample->y_offs += sample->y_vel * seconds; + } +} + +static inline int max(int a, int b) { + return a < b ? b : a; +} + +static void configure_layout(struct sample_state *sample) { + wlr_output_layout_destroy(sample->layout); + sample->layout = wlr_output_layout_init(); + sample->main_output = NULL; + int max_x = wl_list_empty(&sample->config) ? 0 : INT_MIN; + + // first add all the configure outputs + struct output_state *output; + wl_list_for_each(output, &sample->outputs, link) { + struct output_config *conf; + wl_list_for_each(conf, &sample->config, link) { + if (strcmp(conf->name, output->output->name) == 0) { + wlr_output_layout_add(sample->layout, output->output, + conf->x, conf->y); + wlr_output_transform(output->output, conf->transform); + int width, height; + wlr_output_effective_resolution(output->output, &width, &height); + max_x = max(max_x, conf->x + width); + + if (!sample->main_output) { + sample->main_output = output->output; + sample->x_offs = conf->x + 20; + sample->y_offs = conf->y + 20; + } + break; + } + } + } + + // now add all the other configured outputs in a sensible position + wl_list_for_each(output, &sample->outputs, link) { + if (wlr_output_layout_get(sample->layout, output->output)) { + continue; + } + wlr_output_layout_add(sample->layout, output->output, max_x, 0); + int width, height; + wlr_output_effective_resolution(output->output, &width, &height); + wlr_output_effective_resolution(output->output, &width, &height); + if (!sample->main_output) { + sample->main_output = output->output; + sample->x_offs = max_x + 200; + sample->y_offs = 200; + } + max_x += width; + } +} + +static void handle_output_resolution(struct compositor_state *state, struct output_state *output) { + struct sample_state *sample = state->data; + configure_layout(sample); + + // reset the image + struct wlr_output_layout_output *l_output = wlr_output_layout_get(sample->layout, sample->main_output); + sample->x_offs = l_output->x + 20; + sample->y_offs = l_output->y + 20; +} + +static void handle_output_add(struct output_state *output) { + struct sample_state *sample = output->compositor->data; + wl_list_insert(&sample->outputs, &output->link); + configure_layout(sample); +} + +static void update_velocities(struct compositor_state *state, + float x_diff, float y_diff) { + struct sample_state *sample = state->data; + sample->x_vel += x_diff; + sample->y_vel += y_diff; +} + +static void handle_keyboard_key(struct keyboard_state *kbstate, uint32_t keycode, + xkb_keysym_t sym, enum wlr_key_state key_state) { + // NOTE: It may be better to simply refer to our key state during each frame + // and make this change in pixels/sec^2 + // Also, key repeat + int delta = 75; + if (key_state == WLR_KEY_PRESSED) { + switch (sym) { + case XKB_KEY_Left: + update_velocities(kbstate->compositor, -delta, 0); + break; + case XKB_KEY_Right: + update_velocities(kbstate->compositor, delta, 0); + break; + case XKB_KEY_Up: + update_velocities(kbstate->compositor, 0, -delta); + break; + case XKB_KEY_Down: + update_velocities(kbstate->compositor, 0, delta); + break; + } + } +} + +static void usage(const char *name, int ret) { + fprintf(stderr, + "usage: %s [-d [-r | -f]]*\n" + "\n" + " -o The name of the DRM display. e.g. DVI-I-1.\n" + " -r The rotation counter clockwise. Valid values are 90, 180, 270.\n" + " -x The X-axis coordinate position of this output in the layout.\n" + " -y The Y-axis coordinate position of this output in the layout.\n" + " -f Flip the output along the vertical axis.\n", name); + + exit(ret); +} + +static void parse_args(int argc, char *argv[], struct wl_list *config) { + struct output_config *oc = NULL; + + int c; + while ((c = getopt(argc, argv, "o:r:x:y:fh")) != -1) { + switch (c) { + case 'o': + oc = calloc(1, sizeof(*oc)); + oc->name = optarg; + oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; + wl_list_insert(config, &oc->link); + break; + case 'r': + if (!oc) { + fprintf(stderr, "You must specify an output first\n"); + usage(argv[0], 1); + } + + if (oc->transform != WL_OUTPUT_TRANSFORM_NORMAL + && oc->transform != WL_OUTPUT_TRANSFORM_FLIPPED) { + fprintf(stderr, "Rotation for %s already specified\n", oc->name); + usage(argv[0], 1); + } + + if (strcmp(optarg, "90") == 0) { + oc->transform += WL_OUTPUT_TRANSFORM_90; + } else if (strcmp(optarg, "180") == 0) { + oc->transform += WL_OUTPUT_TRANSFORM_180; + } else if (strcmp(optarg, "270") == 0) { + oc->transform += WL_OUTPUT_TRANSFORM_270; + } else { + fprintf(stderr, "Invalid rotation '%s'\n", optarg); + usage(argv[0], 1); + } + break; + case 'x': + if (!oc) { + fprintf(stderr, "You must specify an output first\n"); + usage(argv[0], 1); + } + oc->x = strtol(optarg, NULL, 0); + break; + case 'y': + if (!oc) { + fprintf(stderr, "You must specify an output first\n"); + usage(argv[0], 1); + } + oc->y = strtol(optarg, NULL, 0); + break; + case 'f': + if (!oc) { + fprintf(stderr, "You must specify an output first\n"); + usage(argv[0], 1); + } + + if (oc->transform >= WL_OUTPUT_TRANSFORM_FLIPPED) { + fprintf(stderr, "Flip for %s already specified\n", oc->name); + usage(argv[0], 1); + } + + oc->transform += WL_OUTPUT_TRANSFORM_FLIPPED; + break; + case 'h': + case '?': + usage(argv[0], c != 'h'); + } + } +} + +int main(int argc, char *argv[]) { + struct sample_state state = {0}; + + state.x_vel = 500; + state.y_vel = 500; + state.layout = wlr_output_layout_init(); + + wl_list_init(&state.config); + wl_list_init(&state.outputs); + parse_args(argc, argv, &state.config); + + struct compositor_state compositor = { 0 }; + compositor.data = &state; + compositor.output_add_cb = handle_output_add; + compositor.output_frame_cb = handle_output_frame; + compositor.output_resolution_cb = handle_output_resolution; + compositor.keyboard_key_cb = handle_keyboard_key; + compositor_init(&compositor); + + state.renderer = wlr_gles2_renderer_init(compositor.backend); + state.cat_texture = wlr_render_texture_init(state.renderer); + wlr_texture_upload_pixels(state.cat_texture, WL_SHM_FORMAT_ABGR8888, + cat_tex.width, cat_tex.width, cat_tex.height, cat_tex.pixel_data); + + compositor_run(&compositor); + + wlr_texture_destroy(state.cat_texture); + wlr_renderer_destroy(state.renderer); + + wlr_output_layout_destroy(state.layout); + + struct output_config *ptr, *tmp; + wl_list_for_each_safe(ptr, tmp, &state.config, link) { + free(ptr); + } +} diff --git a/examples/shared.c b/examples/shared.c index 58aef21a..5ebb7aa6 100644 --- a/examples/shared.c +++ b/examples/shared.c @@ -428,6 +428,15 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { compositor->last_frame = now; } +static void output_resolution_notify(struct wl_listener *listener, void *data) { + struct output_state *output = wl_container_of(listener, output, resolution); + struct compositor_state *compositor = output->compositor; + + if (compositor->output_resolution_cb) { + compositor->output_resolution_cb(compositor, output); + } +} + static void output_add_notify(struct wl_listener *listener, void *data) { struct wlr_output *output = data; struct compositor_state *state = wl_container_of(listener, state, output_add); @@ -442,8 +451,10 @@ static void output_add_notify(struct wl_listener *listener, void *data) { ostate->output = output; ostate->compositor = state; ostate->frame.notify = output_frame_notify; + ostate->resolution.notify = output_resolution_notify; wl_list_init(&ostate->frame.link); wl_signal_add(&output->events.frame, &ostate->frame); + wl_signal_add(&output->events.resolution, &ostate->resolution); wl_list_insert(&state->outputs, &ostate->link); if (state->output_add_cb) { state->output_add_cb(ostate); @@ -468,6 +479,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) { } wl_list_remove(&ostate->link); wl_list_remove(&ostate->frame.link); + wl_list_remove(&ostate->resolution.link); free(ostate); } diff --git a/examples/shared.h b/examples/shared.h index 9ffd506a..e2ebadef 100644 --- a/examples/shared.h +++ b/examples/shared.h @@ -16,6 +16,7 @@ struct output_state { struct compositor_state *compositor; struct wlr_output *output; struct wl_listener frame; + struct wl_listener resolution; struct timespec last_frame; struct wl_list link; void *data; @@ -78,6 +79,8 @@ struct compositor_state { void (*keyboard_add_cb)(struct keyboard_state *s); void (*output_frame_cb)(struct output_state *s, struct timespec *ts); void (*output_remove_cb)(struct output_state *s); + void (*output_resolution_cb)(struct compositor_state *compositor, + struct output_state *s); void (*keyboard_remove_cb)(struct keyboard_state *s); void (*keyboard_key_cb)(struct keyboard_state *s, uint32_t keycode, xkb_keysym_t sym, enum wlr_key_state key_state); diff --git a/include/wlr/types/wlr_output_layout.h b/include/wlr/types/wlr_output_layout.h new file mode 100644 index 00000000..f6a1efdd --- /dev/null +++ b/include/wlr/types/wlr_output_layout.h @@ -0,0 +1,49 @@ +#ifndef _WLR_TYPES_OUTPUT_LAYOUT_H +#define _WLR_TYPES_OUTPUT_LAYOUT_H +#include +#include +#include + +struct wlr_output_layout { + struct wl_list outputs; +}; + +struct wlr_output_layout_output { + struct wlr_output *output; + int x, y; + struct wl_list link; +}; + +struct wlr_output_layout *wlr_output_layout_init(); + +void wlr_output_layout_destroy(struct wlr_output_layout *layout); + +struct wlr_output_layout_output *wlr_output_layout_get( + struct wlr_output_layout *layout, struct wlr_output *reference); + +struct wlr_output *wlr_output_layout_output_at(struct wlr_output_layout *layout, + double x, double y); + +void wlr_output_layout_add(struct wlr_output_layout *layout, + struct wlr_output *output, int x, int y); + +void wlr_output_layout_move(struct wlr_output_layout *layout, + struct wlr_output *output, int x, int y); + +void wlr_output_layout_remove(struct wlr_output_layout *layout, + struct wlr_output *output); + +/** + * Given x and y as pointers to global coordinates, adjusts them to local output + * coordinates relative to the given reference output. + */ +void wlr_output_layout_output_coords(struct wlr_output_layout *layout, + struct wlr_output *reference, int *x, int *y); + +bool wlr_output_layout_contains_point(struct wlr_output_layout *layout, + struct wlr_output *reference, int x, int y); + +bool wlr_output_layout_intersects(struct wlr_output_layout *layout, + struct wlr_output *reference, int x1, int y1, int x2, int y2); + +#endif diff --git a/types/meson.build b/types/meson.build index 61dd913c..bb313565 100644 --- a/types/meson.build +++ b/types/meson.build @@ -2,6 +2,7 @@ lib_wlr_types = static_library('wlr_types', files( 'wlr_input_device.c', 'wlr_keyboard.c', 'wlr_output.c', + 'wlr_output_layout.c', 'wlr_pointer.c', 'wlr_region.c', 'wlr_seat.c', diff --git a/types/wlr_output_layout.c b/types/wlr_output_layout.c new file mode 100644 index 00000000..2e2032b3 --- /dev/null +++ b/types/wlr_output_layout.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include + +struct wlr_output_layout *wlr_output_layout_init() { + struct wlr_output_layout *layout = calloc(1, sizeof(struct wlr_output_layout)); + wl_list_init(&layout->outputs); + return layout; +} + +void wlr_output_layout_destroy(struct wlr_output_layout *layout) { + if (!layout) { + return; + } + + struct wlr_output_layout_output *_output, *temp = NULL; + wl_list_for_each_safe(_output, temp, &layout->outputs, link) { + wl_list_remove(&_output->link); + free(_output); + } + + free(layout); +} + +void wlr_output_layout_add(struct wlr_output_layout *layout, + struct wlr_output *output, int x, int y) { + struct wlr_output_layout_output *layout_output = calloc(1, sizeof(struct wlr_output_layout_output)); + layout_output->output = output; + layout_output->x = x; + layout_output->y = y; + wl_list_insert(&layout->outputs, &layout_output->link); +} + +struct wlr_output_layout_output *wlr_output_layout_get( + struct wlr_output_layout *layout, struct wlr_output *reference) { + struct wlr_output_layout_output *_output; + wl_list_for_each(_output, &layout->outputs, link) { + if (_output->output == reference) { + return _output; + } + } + return NULL; +} + +static bool output_contains_point( struct wlr_output_layout_output *l_output, + int x, int y, int width, int height) { + return x >= l_output->x && x <= l_output->x + width && + y >= l_output->y && y <= l_output->y + height; +} + +bool wlr_output_layout_contains_point(struct wlr_output_layout *layout, + struct wlr_output *reference, int x, int y) { + struct wlr_output_layout_output *layout_output = wlr_output_layout_get(layout, reference); + int width, height; + wlr_output_effective_resolution(layout_output->output, &width, &height); + return output_contains_point(layout_output, x, y, width, height); +} + +bool wlr_output_layout_intersects(struct wlr_output_layout *layout, + struct wlr_output *reference, int x1, int y1, int x2, int y2) { + struct wlr_output_layout_output *l_output = wlr_output_layout_get(layout, reference); + if (!l_output) { + return false; + } + int width, height; + wlr_output_effective_resolution(l_output->output, &width, &height); + + // the output must contain one of the points + return output_contains_point(l_output, x1, y1, width, height) || + output_contains_point(l_output, x2, y2, width, height) || + output_contains_point(l_output, x2, y1, width, height) || + output_contains_point(l_output, y2, x1, width, height); +} + +struct wlr_output *wlr_output_layout_output_at(struct wlr_output_layout *layout, + double x, double y) { + struct wlr_output_layout_output *_output; + wl_list_for_each(_output, &layout->outputs, link) { + if (_output->output) { + int width, height; + wlr_output_effective_resolution(_output->output, &width, &height); + bool has_x = x >= _output->x && x <= _output->x + width; + bool has_y = y >= _output->y && y <= _output->y + height; + if (has_x && has_y) { + return _output->output; + } + } + } + return NULL; +} + +void wlr_output_layout_move(struct wlr_output_layout *layout, + struct wlr_output *output, int x, int y) { + struct wlr_output_layout_output *layout_output = + wlr_output_layout_get(layout, output); + if (layout_output) { + layout_output->x = x; + layout_output->y = y; + } +} + +void wlr_output_layout_remove(struct wlr_output_layout *layout, + struct wlr_output *output) { + struct wlr_output_layout_output *layout_output = + wlr_output_layout_get(layout, output); + if (layout_output) { + wl_list_remove(&layout_output->link); + free(layout_output); + } +} + +void wlr_output_layout_output_coords(struct wlr_output_layout *layout, + struct wlr_output *reference, int *x, int *y) { + assert(layout && reference); + int src_x = *x; + int src_y = *y; + + struct wlr_output_layout_output *_output; + wl_list_for_each(_output, &layout->outputs, link) { + if (_output->output == reference) { + *x = src_x - _output->x; + *y = src_y - _output->y; + return; + } + } +}