#include #include #include #include #include #include "window.h" #include "list.h" struct ws_t { Display * display; int depth; Visual * visual; pixman_format_code_t format; pixman_bool_t composited; pixman_bool_t has_alpha; int byte_order; int fd; Colormap colormap; list_t windows; }; struct window_t { link_t link; ws_t * ws; XID xid; int ref_count; pixman_image_t * backing_store; int x, y; pixman_region32_t expose; pixman_region32_t valid; list_t event_queue; uint32_t motion_notify_time; ws_event_func_t event_func; void * event_data; }; static pixman_bool_t is_composited (Display *dpy) { Atom atom = XInternAtom (dpy, "_NET_WM_CM_S0", True); return atom && XGetSelectionOwner (dpy, atom) != None; } static int popcount (uint32_t v) { int n = 0; while (v) { if (v & 1) n++; v >>= 1; } return n; } /* A mask consisting of N bits set to 1. */ #define MASK(N) ((1UL << (N))-1) static void format_to_masks (pixman_format_code_t format, uint32_t *alpha, uint32_t *red, uint32_t *green, uint32_t *blue) { int a, r, g, b; a = PIXMAN_FORMAT_A (format); r = PIXMAN_FORMAT_R (format); g = PIXMAN_FORMAT_G (format); b = PIXMAN_FORMAT_B (format); if (PIXMAN_FORMAT_TYPE (format) == PIXMAN_TYPE_ARGB) { *alpha = MASK (a) << (r + g + b); *red = MASK (r) << (g + b); *green = MASK (g) << (g); *blue = MASK (b); } else if (PIXMAN_FORMAT_TYPE (format) == PIXMAN_TYPE_ABGR) { *alpha = MASK (a) << (b + g + r); *red = MASK (r) << (g + r); *green = MASK (g) << (r); *blue = MASK (b); } else { assert (0); } } static pixman_bool_t visual_to_format (Visual *visual, int depth, pixman_format_code_t *format_o) { pixman_format_code_t format; int format_type, bpp; int a, r, g, b; /* Not bothering with anything but TrueColor */ if (visual->class != TrueColor) return FALSE; r = popcount (visual->red_mask); g = popcount (visual->green_mask); b = popcount (visual->blue_mask); a = depth - (r + g + b); if (visual->red_mask > visual->blue_mask) format_type = PIXMAN_TYPE_ARGB; else format_type = PIXMAN_TYPE_ABGR; if (depth > 16) bpp = 32; else if (depth > 8) bpp = 16; else if (depth > 4) bpp = 8; else if (depth == 4) bpp = 4; else return FALSE; format = PIXMAN_FORMAT (bpp, format_type, a, r, g, b); if (!pixman_format_supported_destination (format)) return FALSE; *format_o = format; return TRUE; } /* Window system */ ws_t * ws_open (void) { ws_t *ws; Display *display; int x; if (!(display = XOpenDisplay (NULL))) return NULL; ws = malloc (sizeof *ws); ws->display = display; ws->composited = is_composited (display); /* Use the default visual ... */ ws->depth = DefaultDepth (ws->display, 0); ws->visual = DefaultVisual (ws->display, 0); ws->has_alpha = FALSE; /* ... unless there is an ARGB visual available */ if (ws->composited) { XVisualInfo *visual_list; XVisualInfo visual_template; int n_visuals, i; n_visuals = 0; visual_template.screen = 0; visual_list = XGetVisualInfo (ws->display, VisualScreenMask, &visual_template, &n_visuals); for (i = 0; i < n_visuals; ++i) { XVisualInfo *visinfo = &(visual_list[i]); if (visinfo->class == TrueColor && visinfo->depth == 32 && (visinfo->red_mask == 0x00ff0000 && visinfo->green_mask == 0x0000ff00 && visinfo->blue_mask == 0x000000ff)) { ws->depth = 32; ws->visual = visinfo->visual; ws->has_alpha = TRUE; break; } } } ws->colormap = XCreateColormap ( ws->display, RootWindow (ws->display, 0), ws->visual, AllocNone); if (!visual_to_format (ws->visual, ws->depth, &ws->format)) return NULL; x = 1; if (*(char *)&x == 1) ws->byte_order = LSBFirst; else ws->byte_order = MSBFirst; list_init (&ws->windows); return ws; } int ws_get_fd (ws_t *ws) { return XConnectionNumber (ws->display); } static window_t * find_window (ws_t *ws, XID xid) { link_t *link; LIST_FOREACH (&ws->windows, link) { window_t *window = CONTAINER_OF (window_t, link, link); if (window->xid == xid) return window; } return NULL; } static int get_width (window_t *window) { return pixman_image_get_width (window->backing_store); } static int get_height (window_t *window) { return pixman_image_get_height (window->backing_store); } int ws_window_get_width (window_t *window) { return get_width (window); } int ws_window_get_height (window_t *window) { return get_height (window); } int ws_window_get_x (window_t *window) { return window->x; } int ws_window_get_y (window_t *window) { return window->y; } static void print_region (const char *name, pixman_region32_t *region) { int n_boxes; pixman_box32_t *boxes; printf ("-- %s\n", name); boxes = pixman_region32_rectangles (region, &n_boxes); while (n_boxes--) { printf (" %d %d %d %d\n", boxes->x1, boxes->y1, boxes->x2, boxes->y2); boxes++; } } static void copy_backing_store_to_window (window_t *window, int x, int y, int width, int height) { pixman_format_code_t format = window->ws->format; Display *dpy = window->ws->display; uint32_t a, r, g, b; XImage image; XGCValues values; GC gc; assert (width <= pixman_image_get_width (window->backing_store)); format_to_masks (format, &a, &r, &g, &b); image.width = get_width (window); image.height = get_height (window); image.xoffset = 0; image.format = ZPixmap; image.data = (char *)pixman_image_get_data (window->backing_store); image.byte_order = window->ws->byte_order; image.bitmap_unit = 32; image.bitmap_bit_order = window->ws->byte_order; image.bitmap_pad = 32; image.depth = PIXMAN_FORMAT_DEPTH (format); image.bytes_per_line = pixman_image_get_stride (window->backing_store); image.bits_per_pixel = PIXMAN_FORMAT_BPP (format); image.red_mask = r; image.green_mask = g; image.blue_mask = b; XInitImage (&image); gc = XCreateGC (dpy, window->xid, 0, &values); XPutImage (dpy, window->xid, gc, &image, x, y, x, y, width, height); XFreeGC (dpy, gc); } #define MIN(a,b) (((a) < (b))? (a) : (b)) static void update_local_geometry (window_t *window, int x, int y, int width, int height) { int old_width = get_width (window); int old_height = get_height (window); pixman_region32_intersect_rect ( &window->valid, &window->valid, 0, 0, width, height); if (width != old_width || height != old_height) { pixman_color_t ugly = { 0x0000, 0xffff, 0xffff, 0xffff }; pixman_image_t *new_backing = pixman_image_create_bits ( window->ws->format, width, height, NULL, -1); pixman_image_t *solid_fill = pixman_image_create_solid_fill (&ugly); pixman_image_composite32 ( PIXMAN_OP_SRC, solid_fill, NULL, new_backing, 0, 0, 0, 0, 0, 0, width, height); pixman_image_composite32 ( PIXMAN_OP_SRC, window->backing_store, NULL, new_backing, 0, 0, 0, 0, 0, 0, MIN (width, old_width), MIN (height, old_height)); pixman_image_unref (window->backing_store); window->backing_store = new_backing; } window->x = x; window->y = y; } static void window_queue_event (window_t *window, const ws_event_t *event) { ws_event_t *copy = malloc (sizeof *copy); *copy = *event; list_append (&window->event_queue, ©->common.link); } static void process_configure_notify (ws_t *ws, XEvent *event) { window_t *window; int new_x, new_y; int new_width, new_height; ws_window_configure_type_t config_type; if (!(window = find_window (ws, event->xexpose.window))) return; new_x = window->x; new_y = window->y; if (!event->xconfigure.send_event && !event->xconfigure.override_redirect) { Window child_window; Display *dpy = window->ws->display; XTranslateCoordinates ( dpy, window->xid, RootWindow (dpy, 0), 0, 0, &new_x, &new_y, &child_window); } else { new_x = event->xconfigure.x; new_y = event->xconfigure.y; } new_width = event->xconfigure.width; new_height = event->xconfigure.height; /* If we are configure to a new size, then notify the user */ config_type = 0; if (new_width != get_width (window) || new_height != get_height (window)) { config_type |= WS_WINDOW_SIZE_CHANGED; } if (new_x != window->x || new_y != window->y) config_type |= WS_WINDOW_POSITION_CHANGED; if (config_type) { ws_event_t wsevent; wsevent.configure.common.type = WS_CONFIGURE; wsevent.configure.configure_type = config_type; wsevent.configure.window = window; window_queue_event (window, &wsevent); } update_local_geometry (window, new_x, new_y, new_width, new_height); } static void process_expose (ws_t *ws, const XEvent *event) { window_t *window; if (!(window = find_window (ws, event->xexpose.window))) return; pixman_region32_union_rect (&window->expose, &window->expose, event->xexpose.x, event->xexpose.y, event->xexpose.width, event->xexpose.height); } pixman_bool_t ws_pending (ws_t *ws) { return XPending (ws->display); } static void emit_events (window_t *window) { while (!list_is_empty (&window->event_queue)) { link_t *link = window->event_queue.head; ws_event_t *event = CONTAINER_OF (ws_event_t, common.link, link); if (window->event_func) window->event_func (window, event, window->event_data); list_unlink (link); free (event); } } typedef struct { Window window; uint32_t last_motion_time; pixman_bool_t stop_compressing; } event_scanner_data_t; static pixman_bool_t get_motion_time (Display *dpy, XEvent *event, XPointer arg) { event_scanner_data_t *data = (event_scanner_data_t *)arg; if (data->stop_compressing) return FALSE; if (data->window != event->xany.window) { data->stop_compressing = TRUE; return FALSE; } if (event->type == MotionNotify) { data->last_motion_time = event->xmotion.time; return FALSE; } if (event->type == EnterNotify || event->type == LeaveNotify) { data->last_motion_time = event->xcrossing.time; return FALSE; } return FALSE; } static int use_this_motion_notify (Display *dpy, window_t *window, uint32_t event_time) { if (window->motion_notify_time) { if (event_time >= window->motion_notify_time) { window->motion_notify_time = 0; return TRUE; } else { /* More motion events are already queued up */ return FALSE; } } else { event_scanner_data_t data = { window->xid, 0, FALSE }; XEvent dummy; XCheckIfEvent (dpy, &dummy, get_motion_time, (XPointer)&data); if (data.last_motion_time == 0) { return TRUE; } else { window->motion_notify_time = data.last_motion_time; return FALSE; } } } static void process_motion (ws_t *ws, Window xwindow, uint32_t time, int x, int y, int root_x, int root_y) { window_t *window; if (!(window = find_window (ws, xwindow))) return; if (use_this_motion_notify (ws->display, window, time)) { ws_event_t wsevent; wsevent.motion.common.type = WS_MOTION; wsevent.motion.window = window; wsevent.motion.x = x; wsevent.motion.y = y; wsevent.motion.root_x = root_x; wsevent.motion.root_y = root_y; window_queue_event (window, &wsevent); } } static void process_button_event (ws_t *ws, ws_event_type_t type, const XEvent *event) { window_t *window; ws_event_t wsevent; if (!(window = find_window (ws, event->xbutton.window))) return; wsevent.button.common.type = type; wsevent.button.window = window; wsevent.button.x = event->xbutton.x; wsevent.button.y = event->xbutton.y; wsevent.button.root_x = event->xbutton.x_root; wsevent.button.root_y = event->xbutton.y_root; window_queue_event (window, &wsevent); } void ws_process (ws_t *ws) { pixman_region32_t expose; link_t *link; pixman_region32_init (&expose); while (ws_pending (ws)) { XEvent event; XNextEvent (ws->display, &event); switch (event.type) { case ConfigureNotify: process_configure_notify (ws, &event); break; case Expose: process_expose (ws, &event); break; case MotionNotify: process_motion (ws, event.xmotion.window, event.xmotion.time, event.xmotion.x, event.xmotion.y, event.xmotion.x_root, event.xmotion.y_root); break; case EnterNotify: case LeaveNotify: process_motion (ws, event.xcrossing.window, event.xcrossing.time, event.xcrossing.x, event.xcrossing.y, event.xcrossing.x_root, event.xcrossing.y_root); break; case ButtonPress: process_button_event (ws, WS_BUTTON_DOWN, &event); break; case ButtonRelease: process_button_event (ws, WS_BUTTON_UP, &event); break; } } LIST_FOREACH (&ws->windows, link) { window_t *window = CONTAINER_OF (window_t, link, link); pixman_box32_t *boxes; int n_boxes; pixman_region32_intersect ( &window->expose, &window->expose, &window->valid); /* FIXME: when resizing windows, the associated exposes end up * causing undefined data to be copied to the window. Something * should perhaps be done about that. For example by keeping * track of windows bits that haven't been painted, and never * painting those. */ boxes = pixman_region32_rectangles (&window->expose, &n_boxes); while (n_boxes--) { copy_backing_store_to_window (window, boxes->x1, boxes->y1, boxes->x2 - boxes->x1, boxes->y2 - boxes->y1); boxes++; } pixman_region32_fini (&window->expose); pixman_region32_init (&window->expose); emit_events (window); } } typedef struct { unsigned long flags; unsigned long functions; unsigned long decorations; long input_mode; unsigned long status; } motif_hints_t; #define MWM_HINTS_DECORATIONS (1 << 1) static void disable_decorations (window_t *window) { Display *dpy = window->ws->display; Atom atom = XInternAtom (dpy, "_MOTIF_WM_HINTS", FALSE); motif_hints_t hints; memset (&hints, 0, sizeof (hints)); hints.flags = MWM_HINTS_DECORATIONS; hints.decorations = 0; XChangeProperty (dpy, window->xid, atom, atom, 32, PropModeReplace, (unsigned char *)&hints, sizeof (motif_hints_t) / sizeof (long)); } /* Window */ window_t * ws_create_window (ws_t *ws, int x, int y, int width, int height) { window_t *window = malloc (sizeof *window); Window root = RootWindow (ws->display, 0); XSetWindowAttributes attr; unsigned long mask; attr.background_pixmap = None; attr.colormap = ws->colormap; attr.background_pixel = 0x0; attr.border_pixel = 0x0; attr.bit_gravity = NorthWestGravity; mask = CWBitGravity | CWBorderPixel | CWBackPixmap | CWColormap; if (ws->has_alpha) mask |= CWBackPixel; #if 0 printf ("%d %x %x %x %x %x\n", ws->depth, ws->visual->red_mask, ws->visual->green_mask, ws->visual->blue_mask, ws->visual->visualid, ws->colormap); #endif window->xid = XCreateWindow ( ws->display, root, x, y, width, height, 0, ws->depth, InputOutput, ws->visual, mask, &attr); window->ref_count = 1; window->backing_store = pixman_image_create_bits (ws->format, width, height, NULL, -1); window->ws = ws; window->x = x; window->y = y; list_prepend (&ws->windows, &window->link); XSelectInput (ws->display, window->xid, ExposureMask | StructureNotifyMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask | ButtonPressMask | ButtonReleaseMask); pixman_region32_init (&window->expose); pixman_region32_init (&window->valid); list_init (&window->event_queue); window->motion_notify_time = 0; disable_decorations (window); window->event_func = NULL; window->event_data = NULL; return window; } void ws_window_set_callback (window_t *window, ws_event_func_t func, void * data) { window->event_func = func; window->event_data = data; } void ws_window_ref (window_t *window) { window->ref_count++; return; } void ws_window_unref (window_t *window) { if (--window->ref_count == 0) { XDestroyWindow (window->ws->display, window->xid); free (window); } } void ws_window_show (window_t *window) { XMapWindow (window->ws->display, window->xid); } void ws_window_hide (window_t *window) { XUnmapWindow (window->ws->display, window->xid); } static void ws_window_move_resize (window_t *window, int x, int y, int width, int height) { int need = 0; if (width < 1) width = 1; if (height < 1) height = 1; if (x != window->x || y != window->y) need |= 1; if (width != get_width (window) || height != get_height (window)) need |= 2; update_local_geometry (window, x, y, width, height); switch (need) { case 0: break; case 1: XMoveWindow (window->ws->display, window->xid, x, y); break; case 2: XResizeWindow (window->ws->display, window->xid, width, height); break; case 3: XMoveResizeWindow (window->ws->display, window->xid, x, y, width, height); break; } } void ws_window_move (window_t *window, int x, int y) { ws_window_move_resize ( window, x, y, get_width (window), get_height (window)); } void ws_window_resize (window_t *window, int w, int h) { ws_window_move_resize ( window, window->x, window->y, w, h); } void ws_window_copy_area (window_t *window, int src_x, int src_y, int dst_x, int dst_y, int width, int height) { pixman_format_code_t format = pixman_image_get_format (window->backing_store); pixman_image_t *tmp = pixman_image_create_bits (format, width, height, NULL, -1); pixman_region32_t moving_valid; pixman_region32_t dest; pixman_region32_init (&moving_valid); pixman_region32_intersect_rect (&moving_valid, &window->valid, src_x, src_y, width, height); pixman_region32_translate (&moving_valid, dst_x - src_x, dst_y - src_y); pixman_region32_init_rect (&dest, dst_x, dst_y, width, height); pixman_region32_subtract (&dest, &window->valid, &dest); pixman_region32_union (&window->valid, &dest, &moving_valid); pixman_region32_intersect_rect (&moving_valid, &moving_valid, 0, 0, get_width (window), get_height (window)); pixman_region32_fini (&moving_valid); pixman_region32_fini (&dest); pixman_image_composite32 (PIXMAN_OP_SRC, window->backing_store, NULL, tmp, src_x, src_y, 0, 0, 0, 0, width, height); pixman_image_composite32 (PIXMAN_OP_SRC, tmp, NULL, window->backing_store, 0, 0, 0, 0, dst_x, dst_y, width, height); } void ws_window_copy_from_image (window_t *window, pixman_image_t *image, int image_x, int image_y, int win_x, int win_y, int width, int height) { uint32_t *data; pixman_region32_union_rect ( &window->valid, &window->valid, win_x, win_y, width, height); pixman_region32_intersect_rect ( &window->valid, &window->valid, 0, 0, get_width (window), get_height (window)); /* copy from image to backing */ pixman_image_composite32 (PIXMAN_OP_SRC, image, NULL, window->backing_store, image_x, image_y, 0, 0, win_x, win_y, width, height); data = pixman_image_get_data (window->backing_store); } void ws_window_copy_to_image (window_t *window, pixman_image_t *image, int image_x, int image_y, int win_x, int win_y, int width, int height) { /* copy from backing to image */ pixman_image_composite32 (PIXMAN_OP_SRC, window->backing_store, NULL, image, image_x, image_y, 0, 0, win_x, win_y, width, height); } void ws_window_finish (window_t **windows, int n_windows) { int i; for (i = 0; i < n_windows; ++i) { window_t *window = windows[i]; int n_boxes; pixman_box32_t *boxes; boxes = pixman_region32_rectangles (&window->valid, &n_boxes); while (n_boxes--) { copy_backing_store_to_window ( window, boxes->x1, boxes->y1, boxes->x2 - boxes->x1, boxes->y2 - boxes->y1); } XFlush (window->ws->display); } }