/* * Copyright (c) 2019 Jiri Svoboda * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup display * @{ */ /** * @file Display server seat */ #include #include #include #include #include #include "client.h" #include "display.h" #include "seat.h" #include "window.h" /** Create seat. * * @param display Parent display * @param rseat Place to store pointer to new seat. * @return EOK on success, ENOMEM if out of memory */ errno_t ds_seat_create(ds_display_t *display, ds_seat_t **rseat) { ds_seat_t *seat; seat = calloc(1, sizeof(ds_seat_t)); if (seat == NULL) return ENOMEM; ds_display_add_seat(display, seat); seat->pntpos.x = 0; seat->pntpos.y = 0; *rseat = seat; return EOK; } /** Destroy seat. * * @param seat Seat */ void ds_seat_destroy(ds_seat_t *seat) { ds_display_remove_seat(seat); free(seat); } /** Set seat focus to a window. * * @param seat Seat * @param wnd Window to focus */ void ds_seat_set_focus(ds_seat_t *seat, ds_window_t *wnd) { if (seat->focus != NULL) ds_window_post_unfocus_event(seat->focus); seat->focus = wnd; if (wnd != NULL) ds_window_post_focus_event(wnd); } /** Evacuate focus from window. * * If seat's focus is @a wnd, it will be set to a different window. * * @param seat Seat * @param wnd Window to evacuate focus from */ void ds_seat_evac_focus(ds_seat_t *seat, ds_window_t *wnd) { ds_window_t *nwnd; if (seat->focus == wnd) { nwnd = ds_display_next_window(wnd); if (nwnd == NULL) nwnd = ds_display_first_window(wnd->display); if (nwnd == wnd) nwnd = NULL; ds_seat_set_focus(seat, nwnd); } } /** Post keyboard event to the seat's focused window. * * @param seat Seat * @param event Event * * @return EOK on success or an error code */ errno_t ds_seat_post_kbd_event(ds_seat_t *seat, kbd_event_t *event) { ds_window_t *dwindow; bool alt_or_shift; alt_or_shift = event->mods & (KM_SHIFT | KM_ALT); if (event->type == KEY_PRESS && alt_or_shift && event->key == KC_TAB) { /* On Alt-Tab or Shift-Tab, switch focus to next window */ ds_seat_evac_focus(seat, seat->focus); return EOK; } dwindow = seat->focus; if (dwindow == NULL) return EOK; return ds_window_post_kbd_event(dwindow, event); } /** Draw cross at seat pointer position. * * @param seat Seat * @param len Cross length * @param w Cross extra width * @param color Color * * @return EOK on success or an error code */ static errno_t ds_seat_draw_cross(ds_seat_t *seat, gfx_coord_t len, gfx_coord_t w, gfx_color_t *color) { gfx_context_t *gc; gfx_rect_t rect, r0; errno_t rc; gc = ds_display_get_gc(seat->display); if (gc == NULL) return EOK; rc = gfx_set_color(gc, color); if (rc != EOK) goto error; r0.p0.x = -len; r0.p0.y = -w; r0.p1.x = +len + 1; r0.p1.y = +w + 1; gfx_rect_translate(&seat->pntpos, &r0, &rect); rc = gfx_fill_rect(gc, &rect); if (rc != EOK) goto error; r0.p0.x = -w; r0.p0.y = -len; r0.p1.x = +w + 1; r0.p1.y = +len + 1; gfx_rect_translate(&seat->pntpos, &r0, &rect); rc = gfx_fill_rect(gc, &rect); if (rc != EOK) goto error; return EOK; error: return rc; } /** Draw seat pointer * * @param seat Seat * * @return EOK on success or an error code */ static errno_t ds_seat_draw_pointer(ds_seat_t *seat) { errno_t rc; gfx_color_t *black = NULL; gfx_color_t *white; rc = gfx_color_new_rgb_i16(0, 0, 0, &black); if (rc != EOK) goto error; rc = gfx_color_new_rgb_i16(0xffff, 0xffff, 0xffff, &white); if (rc != EOK) goto error; rc = ds_seat_draw_cross(seat, 8, 1, black); if (rc != EOK) goto error; rc = ds_seat_draw_cross(seat, 8, 0, white); if (rc != EOK) goto error; gfx_color_delete(black); gfx_color_delete(white); return EOK; error: if (black != NULL) gfx_color_delete(black); if (white != NULL) gfx_color_delete(white); return rc; } /** Clear seat pointer * * @param seat Seat * * @return EOK on success or an error code */ static errno_t ds_seat_clear_pointer(ds_seat_t *seat) { gfx_rect_t rect; rect.p0.x = seat->pntpos.x - 8; rect.p0.y = seat->pntpos.y - 8; rect.p1.x = seat->pntpos.x + 8 + 1; rect.p1.y = seat->pntpos.y + 8 + 1; return ds_display_paint(seat->display, &rect); } /** Post pointing device event to the seat * * Update pointer position and generate position event. * * @param seat Seat * @param event Event * * @return EOK on success or an error code */ #include errno_t ds_seat_post_ptd_event(ds_seat_t *seat, ptd_event_t *event) { ds_display_t *disp = seat->display; gfx_coord2_t npos; ds_window_t *wnd; pos_event_t pevent; errno_t rc; wnd = ds_display_window_by_pos(seat->display, &seat->pntpos); /* Focus window on button press */ if (event->type == PTD_PRESS && event->btn_num == 1) { if (wnd != NULL) { ds_seat_set_focus(seat, wnd); } } if (event->type == PTD_PRESS || event->type == PTD_RELEASE) { pevent.pos_id = 0; pevent.type = (event->type == PTD_PRESS) ? POS_PRESS : POS_RELEASE; pevent.btn_num = event->btn_num; pevent.hpos = seat->pntpos.x; pevent.vpos = seat->pntpos.y; rc = ds_seat_post_pos_event(seat, &pevent); if (rc != EOK) return rc; } if (event->type == PTD_MOVE) { gfx_coord2_add(&seat->pntpos, &event->dmove, &npos); gfx_coord2_clip(&npos, &disp->rect, &npos); (void) ds_seat_clear_pointer(seat); seat->pntpos = npos; pevent.pos_id = 0; pevent.type = POS_UPDATE; pevent.btn_num = 0; pevent.hpos = seat->pntpos.x; pevent.vpos = seat->pntpos.y; rc = ds_seat_post_pos_event(seat, &pevent); if (rc != EOK) return rc; (void) ds_seat_draw_pointer(seat); } if (event->type == PTD_ABS_MOVE) { /* * Project input device area onto display area. Technically * we probably want to project onto the area of a particular * display device. The tricky part is figuring out which * display device the input device is associated with. */ gfx_coord2_project(&event->apos, &event->abounds, &disp->rect, &npos); gfx_coord2_clip(&npos, &disp->rect, &npos); (void) ds_seat_clear_pointer(seat); seat->pntpos = npos; pevent.pos_id = 0; pevent.type = POS_UPDATE; pevent.btn_num = 0; pevent.hpos = seat->pntpos.x; pevent.vpos = seat->pntpos.y; rc = ds_seat_post_pos_event(seat, &pevent); if (rc != EOK) return rc; (void) ds_seat_draw_pointer(seat); } return EOK; } /** Post position event to seat. * * Deliver event to relevant windows. * * @param seat Seat * @param event Position event */ errno_t ds_seat_post_pos_event(ds_seat_t *seat, pos_event_t *event) { ds_window_t *wnd; errno_t rc; wnd = ds_display_window_by_pos(seat->display, &seat->pntpos); if (wnd != NULL) { rc = ds_window_post_pos_event(wnd, event); if (rc != EOK) return rc; } if (seat->focus != wnd) { rc = ds_window_post_pos_event(seat->focus, event); if (rc != EOK) return rc; } return EOK; } /** @} */