#include "mr4th_osw.wayland.h"

#include <poll.h>
#include <linux/input.h>

#include "wayland/xdg-shell-client-protocol.c"
#include "wayland/xdg-decoration-client-protocol.c"

////////////////////////////////
// Wayland Ctx

static OSW_WL_Ctx osw__wl_ctx = {0};

////////////////////////////////
// Wayland VTables

static void
osw__shm_format(void *udata, struct wl_shm *wl_shm, U32 format){}

static const struct wl_shm_listener
osw__shm_listener = {
  osw__shm_format
};

static void
osw__xdg_wm_base_ping(void *udata, struct xdg_wm_base *xdg_wm_base, U32 serial){
  xdg_wm_base_pong(xdg_wm_base, serial);
}

static const struct xdg_wm_base_listener
osw__xdg_wm_base_listener = {
  osw__xdg_wm_base_ping
};

static void
osw__pointer_enter(void *udata, struct wl_pointer *wl_pointer, U32 serial,
                   struct wl_surface *surface, wl_fixed_t fx, wl_fixed_t fy){
  OSW_WL_Seat *seat = &osw__wl_ctx.seat;
  
  OSW_Window *hover_window = 0;
  V2S32 surface_off = {0};
  for (OSW_Window *window = osw_window__first();
       window != 0; window = window->base.next){
    if (osw_wl_gtk_calc_surface_off(&window->gtk_window, surface, &surface_off)){
      hover_window = window;
      break;
    }
    else if (surface == window->main_wl_surface){
      hover_window = window;
      break;
    }
  }
  
  seat->hover_surface = surface;
  seat->hover_window = hover_window;
  seat->serial = serial;
  seat->hover_p.x = surface_off.x + wl_fixed_to_int(fx);
  seat->hover_p.y = surface_off.y + wl_fixed_to_int(fy);
}

static void
osw__pointer_leave(void *udata, struct wl_pointer *wl_pointer,
                   U32 serial, struct wl_surface *surface){
  OSW_WL_Seat *seat = &osw__wl_ctx.seat;
  seat->hover_surface = 0;
  seat->hover_window = 0;
  seat->serial = serial;
}

static void
osw__pointer_motion(void *udata, struct wl_pointer *wl_pointer,
                    U32 time, wl_fixed_t fx, wl_fixed_t fy){
  OSW_WL_Seat *seat = &osw__wl_ctx.seat;
  
  OSW_Window *hover_window = seat->hover_window;
  V2S32 surface_off = {0};
  if (hover_window != 0){
    if (osw_wl_gtk_calc_surface_off(&hover_window->gtk_window,
                                    seat->hover_surface, &surface_off)){
      // do nothing
    }
    else if (seat->hover_surface == hover_window->main_wl_surface){
      // do nothing
    }
  }
  
  seat->hover_p.x = surface_off.x + wl_fixed_to_int(fx);
  seat->hover_p.y = surface_off.y + wl_fixed_to_int(fy);
}

static void
osw__pointer_button(void *udata, struct wl_pointer *wl_pointer,
                    U32 serial, U32 time, U32 button, U32 state){
  OSW_EventKind kind  = 0;
  OSW_EventFlags flags = 0;
  U32 code = 0;
  switch (button){
    case BTN_LEFT:   { code = OSW_MouseButton_Left; }break;
    case BTN_RIGHT:  { code = OSW_MouseButton_Right; }break;
    case BTN_MIDDLE: { code = OSW_MouseButton_Middle; }break;
  }
  if (code != 0){
    kind = OSW_EventKind_MouseButton;
    switch (state){
      case 0: flags |= OSW_EventFlag_Release; break;
      case 1: flags |= OSW_EventFlag_Press; break;
    }
  }
  if (kind != 0){
    OSW_Event *event = osw_event__write_push();
    event->kind = kind;
    event->flags = flags;
    event->code = code;
    
    OSW_WL_Seat *seat = &osw__wl_ctx.seat;
    seat->serial = serial;
  }
}

static void
osw__pointer_axis(void *udata, struct wl_pointer *wl_pointer,
                  U32 time, U32 axis, wl_fixed_t fv){
}

static const struct wl_pointer_listener
osw__pointer_listener = {
  osw__pointer_enter,
  osw__pointer_leave,
  osw__pointer_motion,
  osw__pointer_button,
  osw__pointer_axis
};

static void
osw__seat_capabilities(void *udata, struct wl_seat *wl_seat, U32 capabilities){
  OSW_WL_Seat *seat = &osw__wl_ctx.seat;
  if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer == 0){
    seat->wl_pointer = wl_seat_get_pointer(seat->wl_seat);
    wl_pointer_add_listener(seat->wl_pointer, &osw__pointer_listener, 0);
  }
  else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer != 0){
    wl_pointer_release(seat->wl_pointer);
    seat->wl_pointer = 0;
  }
}

static void
osw__seat_name(void *udata, struct wl_seat *wl_seat, const char *name){
}

static const struct wl_seat_listener
osw__seat_listener = {
  osw__seat_capabilities,
  osw__seat_name
};

static void
osw__registry_global(void *udata, struct wl_registry *wl_registry,
                     U32 name, const char *interface, U32 version){
  if (strcmp(interface, "wl_compositor") == 0){
    osw__wl_ctx.wl_compositor = (struct wl_compositor*)
      wl_registry_bind(wl_registry, name, &wl_compositor_interface,
                       Min(version, 4));
  }
  else if (strcmp(interface, "wl_subcompositor") == 0){
    osw__wl_ctx.wl_subcompositor = wl_registry_bind(osw__wl_ctx.wl_registry, name,
                                                    &wl_subcompositor_interface, 1);
  }
  else if (strcmp(interface, "wl_shm") == 0){
    osw__wl_ctx.wl_shm = wl_registry_bind(osw__wl_ctx.wl_registry, name, &wl_shm_interface, 1);
    wl_shm_add_listener(osw__wl_ctx.wl_shm, &osw__shm_listener, 0);
  }
  else if (!strcmp(interface, xdg_wm_base_interface.name)){
    osw__wl_ctx.xdg_wm_base = wl_registry_bind(osw__wl_ctx.wl_registry, name,
                                               &xdg_wm_base_interface,
                                               Min(version, 2));
    xdg_wm_base_add_listener(osw__wl_ctx.xdg_wm_base, &osw__xdg_wm_base_listener, 0);
  }
  else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name)){
    osw__wl_ctx.zxdg_decoration_manager =
      wl_registry_bind(wl_registry, name,
                       &zxdg_decoration_manager_v1_interface,
                       Min(version, 2));
  }
  else if (strcmp(interface, "wl_seat") == 0){
    OSW_WL_Seat *seat = &osw__wl_ctx.seat;
    if (seat->wl_seat == 0){
      seat->wl_seat = wl_registry_bind(osw__wl_ctx.wl_registry, name,
                                       &wl_seat_interface, 3);
      wl_seat_add_listener(seat->wl_seat, &osw__seat_listener, 0);
      seat->cursor_surface = wl_compositor_create_surface(osw__wl_ctx.wl_compositor);
    }
  }
}

static void
osw__registry_global_remove(void *udata, struct wl_registry *registry, U32 name){
}

static const struct wl_registry_listener
osw__registry_listener = {
  osw__registry_global,
  osw__registry_global_remove,
};

static void
osw__xdg_surface_configure(void *udata, struct xdg_surface *xdg_surface, U32 serial){
  OSW_Window *window = (OSW_Window*)udata;
  window->config_staged.serial = serial;
}

static const struct xdg_surface_listener
osw__xdg_surface_listener = {
  osw__xdg_surface_configure
};

static void
osw__xdg_toplevel_configure(void *udata, struct xdg_toplevel *xdg_toplevel,
                            S32 width, S32 height, struct wl_array *states){
  OSW_Window *window = (OSW_Window*)udata;
  if (width != 0 && height != 0){
    window->config_staged.dim.x = width;
    window->config_staged.dim.y = height;
  }
  else{
    window->config_staged.dim = window->config.dim;
  }
  window->config_staged.flags = osw_wl_window_flags_from_states_array(states);
}

static void
osw__xdg_toplevel_close(void *udata, struct xdg_toplevel *xdg_toplevel){
  OSW_Event *event = osw_event__write_push();
  event->kind = OSW_EventKind_WindowClose;
  event->window = (OSW_Window*)udata;
}

static void
osw__xdg_toplevel_configure_bounds(void *udata, struct xdg_toplevel *xdg_toplevel,
                                   S32 w, S32 h){
}

static void
osw__xdg_toplevel_wm_capabilities(void *udata, struct xdg_toplevel *xdg_toplevel,
                                  struct wl_array *capabilities){
}

static const struct xdg_toplevel_listener
osw__xdg_toplevel_listener = {
  osw__xdg_toplevel_configure,
  osw__xdg_toplevel_close,
  osw__xdg_toplevel_configure_bounds,
  osw__xdg_toplevel_wm_capabilities,
};

static void
osw__xdg_toplevel_decoration_configure(void *udata, struct zxdg_toplevel_decoration_v1 *toplevel,
                                       U32 mode){
  OSW_Window *window = (OSW_Window*)udata;
  window->config_staged.decoration_mode = mode;
}

static const struct zxdg_toplevel_decoration_v1_listener
osw__zxdg_toplevel_decoration_listener = {
  osw__xdg_toplevel_decoration_configure
};

////////////////////////////////
// Wayland Implementation: OSW Functions

// global

MR4TH_SYMBOL void
osw_init(void){
  if (osw__wl_ctx.arena == 0){
    osw__wl_ctx.arena = arena_new(MB(1), 8, 0);
    
    // desktop settings
    osw__wl_ctx.color_scheme = osw_wl_desktop_get_color_scheme(osw__wl_ctx.arena);
    osw__wl_ctx.cursor_theme = osw_wl_desktop_get_cursor_theme(osw__wl_ctx.arena);
    
    // setup gtk
    osw_wl_gtk_init(&osw__wl_ctx.gtk_ctx);
    
    // setup wayland
    osw__wl_ctx.wl_display = wl_display_connect(0);
    osw__wl_ctx.wl_registry = wl_display_get_registry(osw__wl_ctx.wl_display);
    wl_registry_add_listener(osw__wl_ctx.wl_registry, &osw__registry_listener, 0);
    
    wl_display_flush(osw__wl_ctx.wl_display);
    wl_display_dispatch(osw__wl_ctx.wl_display);
    
    osw__wl_ctx.wl_cursor_theme =
      wl_cursor_theme_load((char*)osw__wl_ctx.cursor_theme.name.str,
                           osw__wl_ctx.cursor_theme.size,
                           osw__wl_ctx.wl_shm);
    
#define X(k,n) \
osw__wl_ctx.wl_cursors[OSW_CursorShape_##k] = \
wl_cursor_theme_get_cursor(osw__wl_ctx.wl_cursor_theme, n)
    X(Default,    "left_ptr");
    X(Pointer,    "hand2");
    X(TextInsert, "ibeam");
    X(Loading,    "wait");
    X(CrossHair,  "crosshair");
    X(Move,       "move");
    X(Resize_T,   "top_side");
    X(Resize_B,   "bottom_side");
    X(Resize_L,   "left_side");
    X(Resize_R,   "right_side");
    X(Resize_TL,  "top_left_corner");
    X(Resize_BL,  "bottom_left_corner");
    X(Resize_TR,  "top_right_corner");
    X(Resize_BR,  "bottom_right_corner");
#undef X
    
  }
}

MR4TH_SYMBOL B32
osw_has_capability(OSW_Cap cap){
  B32 result = 0;
  switch (cap){
    case OSW_Cap_CustomBorders: {
      result = 1;
    }break;
  }
  return(result);
}

MR4TH_SYMBOL void
osw_tick(Arena *arena, B32 block){
  osw_event__write_begin(arena);
  
  // clear per-frame latches
  osw__wl_ctx.did_set_cursor = 0;
  
  //// poll ////
  {
    struct pollfd fds[1] = {0};
    
    // register fds[0] ~ wayland events
    B8 wayland_started_read = 0;
    B8 wayland_did_read = 0;
    {
      fds[0].fd = -1;
      
      wl_display_dispatch_pending(osw__wl_ctx.wl_display);
      wayland_started_read = (wl_display_prepare_read(osw__wl_ctx.wl_display) != -1);
      if (wayland_started_read){
        fds[0].fd = wl_display_get_fd(osw__wl_ctx.wl_display);
        fds[0].events = POLLIN;
        wl_display_flush(osw__wl_ctx.wl_display);
      }
    }
    
    // poll and handle events
    int timeout = (block?-1:0);
    int ret = poll(fds, sizeof(fds)/sizeof(*fds), timeout);
    if (ret > 0){
      
      // handle fds[0] ~ wayland events
      if (fds[0].revents & POLLIN){
        wayland_did_read = true;
        wl_display_read_events(osw__wl_ctx.wl_display);
        wl_display_dispatch_pending(osw__wl_ctx.wl_display);
      }
    }
    
    // wayland event read cleanup
    if (wayland_started_read && !wayland_did_read){
      wl_display_cancel_read(osw__wl_ctx.wl_display);
    }
  }
  
  //// tick windows ////
  for (OSW_Window *window = osw_window__first();
       window != 0; window = window->base.next){
    
    // apply new config
    if (window->config.serial != window->config_staged.serial){
      window->config = window->config_staged;
      xdg_surface_ack_configure(window->main_xdg_surface, window->config.serial);
    }
    
    // calculate csd frame
    OSW_WL_CSDFrame csd_frame = osw_wl_gtk_calc_frame(window);
    
    // window size update
    {
      //* window sizing
      MemoryZeroStruct(&window->csd_frame);
      MemoryZeroStruct(&window->csd_dim);
      if (window->config.decoration_mode ==
          ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE){
        window->csd_frame = csd_frame;
        for (U32 i = 0; i < 2; i += 1){
          window->csd_dim.v[i] = (csd_frame.border_size.p[0].v[i] +
                                  csd_frame.border_size.p[1].v[i]);
        }
      }
      for (U32 i = 0; i < 2; i += 1){
        S32 d = window->config.dim.v[i] - window->csd_dim.v[i];
        if (!window->handled_first_size){
          d = window->dim.v[i];
          window->config.dim.v[i] = d + window->csd_dim.v[i];
        }
        d = ClampBot(d, window->mmbox.p[0].v[i]);
        d = ClampTop(d, window->mmbox.p[1].v[i]);
        d = ClampBot(d, window->csd_frame.min_size.v[i]);
        window->dim.v[i] = d;
      }
      window->handled_first_size = 1;
      
      /* window size commit */
      if (!(window->control_flags & OSW_WL_WindowControlFlag_Resize)){
        xdg_toplevel_set_min_size(window->main_xdg_toplevel,
                                  window->dim.x, window->dim.y);
        xdg_toplevel_set_max_size(window->main_xdg_toplevel,
                                  window->dim.x, window->dim.y);
      }
      else{
        for (U32 i = 0; i < 2; i += 1){
          V2S32 m;
          for (U32 k = 0; k < 2; k += 1){
            m.v[k] = window->mmbox.p[i].v[k];
            if (i == 0){
              m.v[k] = ClampBot(m.v[k], window->csd_frame.min_size.v[k]);
            }
            m.v[k] += window->csd_dim.v[k];
          }
          if (i == 0){
            xdg_toplevel_set_min_size(window->main_xdg_toplevel, m.x, m.y);
          }
          else{
            xdg_toplevel_set_max_size(window->main_xdg_toplevel, m.x, m.y);
          }
        }
      }
      xdg_surface_set_window_geometry(window->main_xdg_surface,
                                      -window->csd_frame.border_size.x0,
                                      -window->csd_frame.border_size.y0,
                                      window->dim.x + window->csd_dim.x,
                                      window->dim.y + window->csd_dim.y);
    }
    
    // frame update and render
    if (window->config.decoration_mode ==
        ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE){
      osw_wl_gtk_window_update_and_render(window);
    }
    else{
      osw_wl_gtk_window_disable(&window->gtk_window);
    }
  }
}

MR4TH_SYMBOL void
osw_set_cursor_shape(OSW_CursorShape shape){
  if (shape < OSW_CursorShape_COUNT){
    if (!osw__wl_ctx.did_set_cursor){
      OSW_WL_Seat *seat = &osw__wl_ctx.seat;
      struct wl_cursor *cursor = osw__wl_ctx.wl_cursors[shape];
      if (cursor != 0){
        struct wl_cursor_image *cursor_image = cursor->images[0];
        struct wl_buffer *cursor_buffer = wl_cursor_image_get_buffer(cursor_image);
        wl_surface_set_buffer_scale(seat->cursor_surface, 1);
        wl_surface_attach(seat->cursor_surface, cursor_buffer, 0, 0);
        wl_surface_damage_buffer(seat->cursor_surface, 0, 0,
                                 cursor_image->width, cursor_image->height);
        wl_surface_commit(seat->cursor_surface);
        wl_pointer_set_cursor(seat->wl_pointer, seat->serial, seat->cursor_surface,
                              cursor_image->hotspot_x, cursor_image->hotspot_y);
      }
      else{
        wl_pointer_set_cursor(seat->wl_pointer, seat->serial, 0, 0, 0);
      }
      osw__wl_ctx.did_set_cursor = 1;
    }
  }
}

// windows

MR4TH_SYMBOL OSW_Window*
osw_window_create(V2S32 size){
  OSW_Window *window = osw_window__alloc();
  if (window != 0){
    window->main_wl_surface =
      wl_compositor_create_surface(osw__wl_ctx.wl_compositor);
    
    window->main_xdg_surface =
      xdg_wm_base_get_xdg_surface(osw__wl_ctx.xdg_wm_base, window->main_wl_surface);
    xdg_surface_add_listener(window->main_xdg_surface,
                             &osw__xdg_surface_listener, window);
    
    window->main_xdg_toplevel =
      xdg_surface_get_toplevel(window->main_xdg_surface);
    xdg_toplevel_add_listener(window->main_xdg_toplevel,
                              &osw__xdg_toplevel_listener, window);
    
    window->config_staged.decoration_mode =
      ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
    if (osw__wl_ctx.zxdg_decoration_manager != 0){
      window->main_zxdg_toplevel_decoration =
        zxdg_decoration_manager_v1_get_toplevel_decoration(osw__wl_ctx.zxdg_decoration_manager,
                                                           window->main_xdg_toplevel);
      zxdg_toplevel_decoration_v1_add_listener(window->main_zxdg_toplevel_decoration,
                                               &osw__zxdg_toplevel_decoration_listener, window);
    }
    
    wl_surface_commit(window->main_wl_surface);
    
    window->mmbox.x0 = 1;
    window->mmbox.y0 = 1;
    window->mmbox.x1 = (1 << 30);
    window->mmbox.y1 = (1 << 30);
    window->dim.x = Clamp(window->mmbox.x0, size.x, window->mmbox.x1);
    window->dim.y = Clamp(window->mmbox.y0, size.y, window->mmbox.y1);
    window->control_flags = ~0;
    
    osw_wl_gtk_window_init(window->main_wl_surface, &window->gtk_window);
  }
  
  return(window);
}

MR4TH_SYMBOL void
osw_window_destroy(OSW_Window *window){
  // TODO(allen): 
  osw_window__release(window);
}

MR4TH_SYMBOL void
osw_window_set_title(OSW_Window *window, String8 title){
  if (title.size < ArrayCount(window->title_buffer)){
    MemoryCopy(window->title_buffer, title.str, title.size);
    window->title_buffer[title.size] = 0;
    xdg_toplevel_set_title(window->main_xdg_toplevel,
                           (char*)window->title_buffer);
  }
}

MR4TH_SYMBOL void
osw_window_set_size(OSW_Window *window, S32 x, S32 y){
  window->dim.x = Clamp(window->mmbox.x0, x, window->mmbox.x1);
  window->dim.y = Clamp(window->mmbox.y0, y, window->mmbox.y1);
}

MR4TH_SYMBOL void
osw_window_set_min_size(OSW_Window *window, V2S32 size){
  window->mmbox.x0 = ClampBot(1, size.x);
  window->mmbox.y0 = ClampBot(1, size.y);
}

MR4TH_SYMBOL void
osw_window_set_max_size(OSW_Window *window, V2S32 size){
  window->mmbox.x1 = ClampTop(size.x, (1 << 30));
  window->mmbox.y1 = ClampTop(size.y, (1 << 30));
}

MR4TH_SYMBOL void
osw_window_set_visible(OSW_Window *window, B32 visible){
}

MR4TH_SYMBOL void
osw_window_set_fullscreen(OSW_Window *window, B32 fullscreen){
  if (fullscreen){
    xdg_toplevel_set_fullscreen(window->main_xdg_toplevel, 0);
  }
  else{
    xdg_toplevel_unset_fullscreen(window->main_xdg_toplevel);
  }
}

MR4TH_SYMBOL void
osw_window_set_maximize(OSW_Window *window, B32 maximize){
  if (maximize){
    xdg_toplevel_set_maximized(window->main_xdg_toplevel);
  }
  else{
    xdg_toplevel_unset_maximized(window->main_xdg_toplevel);
  }
}

MR4TH_SYMBOL void
osw_window_set_minimize(OSW_Window *window){
  xdg_toplevel_set_minimized(window->main_xdg_toplevel);
}

MR4TH_SYMBOL V2S32
osw_window_get_size(OSW_Window *window){
  V2S32 size = window->dim;
  return(size);
}

MR4TH_SYMBOL B32
osw_window_is_visible(OSW_Window *window){
  B32 result = 0;
  return(result);
}

MR4TH_SYMBOL B32
osw_window_is_fullscreen(OSW_Window *window){
  B32 result = ((window->config.flags & OSW_WL_WindowFlag_IsFullscreen) != 0);
  return(result);
}

MR4TH_SYMBOL B32
osw_window_is_maximized(OSW_Window *window){
  B32 result = ((window->config.flags & OSW_WL_WindowFlag_IsMax) != 0);
  return(result);
}

MR4TH_SYMBOL B32
osw_window_is_minimized(OSW_Window *window){
  B32 result = 0;
  return(result);
}

// CAP: absolute positions

MR4TH_SYMBOL V2S32
osw_get_absolute_cursor_pos(void){
  osw__error_unsupported(OSW_Cap_AbsoluteCoordinates);
  V2S32 result = {0};
  return(result);
}

MR4TH_SYMBOL RectS32
osw_window_get_absolute_outer_rect(OSW_Window *window){
  osw__error_unsupported(OSW_Cap_AbsoluteCoordinates);
  RectS32 result = {0};
  return(result);
}

MR4TH_SYMBOL V2S32
osw_window_get_absolute_pos(OSW_Window *window){
  osw__error_unsupported(OSW_Cap_AbsoluteCoordinates);
  V2S32 result = {0};
  return(result);
}

// (custom borders)

MR4TH_SYMBOL void
osw_window_set_custom_border(OSW_Window *window, B32 custom_border){
}

MR4TH_SYMBOL void
osw_window_frame_custom_border_size(OSW_Window *window, RectS32 border_size){
}

////////////////////////////////
// Functions: OSW Wayland

MR4TH_SYMBOL OSW_WL_WindowFlags
osw_wl_window_flags_from_states_array(struct wl_array *states){
  OSW_WL_WindowFlags flags = 0;
  U32 *p;
  wl_array_for_each(p, states){
    switch (*p){
      case XDG_TOPLEVEL_STATE_FULLSCREEN:   flags |= OSW_WL_WindowFlag_IsFullscreen; break;
      case XDG_TOPLEVEL_STATE_MAXIMIZED:    flags |= OSW_WL_WindowFlag_IsMax; break;
      case XDG_TOPLEVEL_STATE_ACTIVATED:    flags |= OSW_WL_WindowFlag_IsActivated; break;
      case XDG_TOPLEVEL_STATE_TILED_LEFT:   flags |= OSW_WL_WindowFlag_IsTiledLeft; break;
      case XDG_TOPLEVEL_STATE_TILED_RIGHT:  flags |= OSW_WL_WindowFlag_IsTiledRight; break;
      case XDG_TOPLEVEL_STATE_TILED_TOP:    flags |= OSW_WL_WindowFlag_IsTiledTop; break;
      case XDG_TOPLEVEL_STATE_TILED_BOTTOM: flags |= OSW_WL_WindowFlag_IsTiledBottom; break;
      case XDG_TOPLEVEL_STATE_RESIZING:     flags |= OSW_WL_WindowFlag_IsResizing; break;
      case XDG_TOPLEVEL_STATE_SUSPENDED:    flags |= OSW_WL_WindowFlag_IsSuspended; break;
      case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT:   flags |= OSW_WL_WindowFlag_IsConstrainedLeft; break;
      case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT:  flags |= OSW_WL_WindowFlag_IsConstrainedRight; break;
      case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP:    flags |= OSW_WL_WindowFlag_IsConstrainedTop; break;
      case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM: flags |= OSW_WL_WindowFlag_IsConstrainedBottom; break;
      default: break;
    }
  }
  return(flags);
}

// gtk

MR4TH_SYMBOL void
osw_wl_gtk_init(OSW_WL_GTK_Ctx *gtk_ctx){
  gdk_set_allowed_backends("wayland");
  gtk_disable_setlocale();
  gtk_init_check(0, 0);
  
  g_object_set(gtk_settings_get_default(),
               "gtk-application-prefer-dark-theme",
               (osw__wl_ctx.color_scheme == OSW_WL_ColorScheme_Dark),
               NULL);
  
  {
    static const int size = 128;
    static const int boundary = 32;
    cairo_surface_t *shadow_blur =
      cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size);
    cairo_t *cr = cairo_create(shadow_blur);
    cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
    cairo_set_source_rgba(cr, 0, 0, 0, 1);
    cairo_rectangle(cr, boundary, boundary, size - 2*boundary, size - 2*boundary);
    cairo_fill(cr);
    cairo_destroy(cr);
    
    osw_wl_cairo_blur_surface(shadow_blur, 64);
    gtk_ctx->shadow_blur = shadow_blur;
  }
}

MR4TH_SYMBOL void
osw_wl_gtk_window_init(struct wl_surface *main_wl_surface,
                       OSW_WL_GTK_Window *gtk_window){
  gtk_window->shadow = osw_wl_subsurface_new(main_wl_surface);
  gtk_window->titlebar = osw_wl_subsurface_new(main_wl_surface);
  
  wl_subsurface_place_above(gtk_window->titlebar.wl_subsurface,
                            gtk_window->shadow.wl_surface);
  
  gtk_window->header = gtk_header_bar_new();
  gtk_window->window = gtk_offscreen_window_new();
  
  g_object_get(gtk_widget_get_settings(gtk_window->window),
               "gtk-double-click-time",  &gtk_window->double_click_time_ms,
               NULL);
  g_object_set(gtk_window->header,
               "title", "Example Window",
               "has-subtitle", FALSE,
               "show-close-button", TRUE,
               NULL);
  
  GtkStyleContext *context_hdr = gtk_widget_get_style_context(gtk_window->header);
  gtk_style_context_add_class(context_hdr, GTK_STYLE_CLASS_TITLEBAR);
  gtk_style_context_add_class(context_hdr, "default-decoration");
  gtk_window_set_titlebar(GTK_WINDOW(gtk_window->window), gtk_window->header);
  gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(gtk_window->header), TRUE);
  gtk_widget_show_all(gtk_window->window);
}

#define OSW_WL_GTK_SHADOW_MARGIN 24

MR4TH_SYMBOL void
osw_wl_gtk_window_update_and_render(OSW_Window *window){
  OSW_WL_GTK_Window *gtk_window = &window->gtk_window;
  
  // determine what components are visiblep
  B8 visible_titlebar = (!(window->config.flags & OSW_WL_WindowFlag_IsFullscreen));
  B8 visible_shadow = (!(window->config.flags & OSW_WL_WindowMask_IsAnchored));
  
  // setup buttons
  enum{
    HEADER_BUTTON_NULL,
    HEADER_BUTTON_MIN, HEADER_BUTTON_MAX, HEADER_BUTTON_CLOSE,
    HEADER_BUTTON_COUNT
  };
  static char* header_button_name[] = { 0, ".minimize", ".maximize", ".close" };
  B32 header_button_active[] = {
    /*NULL*/  0,
    /*MIN*/   (window->control_flags & OSW_WL_WindowControlFlag_Min),
    /*MAX*/   ((window->control_flags & OSW_WL_WindowControlFlag_Max) &&
               (window->control_flags & OSW_WL_WindowControlFlag_Resize)),
    /*CLOSE*/ (window->control_flags & OSW_WL_WindowControlFlag_Close)
  };
  
  // per-cursor locals
  enum{ LOCATION_NULL, LOCATION_SHADOW, LOCATION_TITLEBAR, };
  struct CursorVars{
    U32 loc;
    S32 shadow_loc;
    U32 header_button_hover;
  } CursorVars;
  struct CursorVars cursors[1] = {0};
  
  // determine cursor location information
  if (visible_titlebar){
    U32 si = 0;
    OSW_WL_Seat *seat = &osw__wl_ctx.seat;
    if (seat->hover_window == window){
      B32 l = (seat->hover_p.x < 0);
      B32 r = (!l && seat->hover_p.x >= window->dim.x);
      B32 t = (seat->hover_p.y < -window->csd_frame.border_size.y0);
      B32 b = (!t && seat->hover_p.y >= window->dim.y);
      cursors[si].shadow_loc = 3*(b - t) + (r - l);
      if (cursors[si].shadow_loc != 0){
        cursors[si].loc = LOCATION_SHADOW;
      }
      else if (seat->hover_p.y < 0){
        cursors[si].loc = LOCATION_TITLEBAR;
      }
    }
  }
  
  // draw shadow
  {
    OSW_WL_SubSurface *subsurface = &gtk_window->shadow;
    
    V2S32 shadow_dim = {
      window->dim.x + 2*OSW_WL_GTK_SHADOW_MARGIN,
      (window->dim.y + 2*OSW_WL_GTK_SHADOW_MARGIN +
       window->csd_frame.border_size.y0)
    };
    
    osw_wl_subsurface_buffer_clear(subsurface);
    osw_wl_subsurface_buffer_alloc(subsurface, shadow_dim);
    memset(subsurface->data, 0, subsurface->size);
    
    if (visible_shadow){
      int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, shadow_dim.x);
      cairo_surface_t *surface =
        cairo_image_surface_create_for_data(subsurface->data, CAIRO_FORMAT_ARGB32,
                                            shadow_dim.x, shadow_dim.y, stride);
      cairo_t *cr = cairo_create(surface);
      cairo_surface_set_device_scale(surface, 1, 1);
      
      osw_wl_cairo_render_shadow(cr, osw__wl_ctx.gtk_ctx.shadow_blur,
                                 -(int)OSW_WL_GTK_SHADOW_MARGIN/2,
                                 -(int)OSW_WL_GTK_SHADOW_MARGIN/2,
                                 shadow_dim.x + OSW_WL_GTK_SHADOW_MARGIN,
                                 shadow_dim.y + OSW_WL_GTK_SHADOW_MARGIN,
                                 64, 64);
      cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
      cairo_rectangle(cr,
                      OSW_WL_GTK_SHADOW_MARGIN,
                      OSW_WL_GTK_SHADOW_MARGIN + window->csd_frame.border_size.y0,
                      window->dim.x, window->dim.y);
      cairo_fill(cr);
      
      cairo_destroy(cr);
      cairo_surface_destroy(surface);
    }
    
    osw_wl_subsurface_set_position(subsurface,
                                   v2s32(-OSW_WL_GTK_SHADOW_MARGIN,
                                         -OSW_WL_GTK_SHADOW_MARGIN - window->csd_frame.border_size.y0));
  }
  osw_wl_subsurface_commit(&gtk_window->shadow);
  
  // draw titlebar
  {
    OSW_WL_SubSurface *subsurface = &gtk_window->titlebar;
    
    V2S32 title_dim = {
      window->dim.x,
      window->csd_frame.border_size.y0
    };
    
    osw_wl_subsurface_buffer_clear(subsurface);
    osw_wl_subsurface_buffer_alloc(subsurface, title_dim);
    memset(subsurface->data, 0, subsurface->size);
    
    if (visible_titlebar){
      {
        B32 is_resizable = ((window->control_flags & OSW_WL_WindowControlFlag_Resize) != 0);
        gtk_window_set_resizable(GTK_WINDOW(gtk_window->window), is_resizable);
        if (!(window->config.flags & OSW_WL_WindowFlag_IsActivated)){
          gtk_widget_set_state_flags(gtk_window->window, GTK_STATE_FLAG_BACKDROP, true);
        }
        else{
          gtk_widget_unset_state_flags(gtk_window->window, GTK_STATE_FLAG_BACKDROP);
        }
      }
      
      {
        GtkStyleContext *style = gtk_widget_get_style_context(gtk_window->window);
        if (window->config.flags & OSW_WL_WindowMask_IsAnchored){
          gtk_style_context_add_class(style, "maximized");
        }
        else{
          gtk_style_context_remove_class(style, "maximized");
        }
      }
      
      {
        GtkAllocation allocation = { 0, 0, title_dim.x, title_dim.y};
        gtk_widget_size_allocate(gtk_window->header, &allocation);
      }
      
      {
        S32 stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, title_dim.x);
        cairo_surface_t *cr_surface =
          cairo_image_surface_create_for_data(subsurface->data, CAIRO_FORMAT_ARGB32,
                                              title_dim.x, title_dim.y, stride);
        cairo_t *cr = cairo_create(cr_surface);
        cairo_surface_set_device_scale(cr_surface, 1, 1);
        
        // draw background
        {
          GtkAllocation allocation;
          gtk_widget_get_allocation(GTK_WIDGET(gtk_window->header), &allocation);
          GtkStyleContext* style = gtk_widget_get_style_context(gtk_window->header);
          gtk_render_background(style, cr,
                                allocation.x, allocation.y,
                                allocation.width, allocation.height);
        }
        
        // draw title
        {
          GtkWidget *label_title =
            osw_wl_gtk_widget_from_name(gtk_window->header, "label.title:");
          
          GtkAllocation allocation;
          gtk_widget_get_allocation(label_title, &allocation);
          
          cairo_surface_t *cr_label_surface =
            cairo_surface_create_for_rectangle(cr_surface,
                                               allocation.x, allocation.y,
                                               allocation.width, allocation.height);
          cairo_t *cr2 = cairo_create(cr_label_surface);
          gtk_widget_draw(label_title, cr2);
          cairo_destroy(cr2);
          cairo_surface_destroy(cr_label_surface);
        }
        
        // draw buttons
        for (U32 i = 1; i < HEADER_BUTTON_COUNT; i += 1){
          if (header_button_active[i]){
            char *icon_name = "";
            switch (i){
              case HEADER_BUTTON_MIN: {
                icon_name = "window-minimize-symbolic";
              }break;
              case HEADER_BUTTON_MAX: {
                icon_name = ((window->config.flags & OSW_WL_WindowFlag_IsMax) ?
                             "window-restore-symbolic" : "window-maximize-symbolic");
              }break;
              case HEADER_BUTTON_CLOSE: {
                icon_name = "window-close-symbolic";
              }break;
            }
            
            GtkWidget *widget =
              osw_wl_gtk_widget_from_name(gtk_window->header, header_button_name[i]);
            
            if (widget != 0){
              // layout
              GtkAllocation rect;
              gtk_widget_get_clip(widget, &rect);
              
              S32 x0 = rect.x;
              S32 y0 = rect.y - title_dim.y;
              S32 x1 = rect.x + rect.width;
              S32 y1 = rect.y + rect.height - title_dim.y;
              
              // check pointer hover
              B32 is_hovered = 0;
              {
                U32 si = 0;
                if (cursors[si].loc == LOCATION_TITLEBAR){
                  OSW_WL_Seat *seat = &osw__wl_ctx.seat;
                  if (x0 <= seat->hover_p.x && seat->hover_p.x < x1 &&
                      y0 <= seat->hover_p.y && seat->hover_p.y < y1){
                    cursors[si].header_button_hover = i;
                    is_hovered = 1;
                  }
                }
              }
              
              // change style based on window state and focus
              GtkStyleContext *style = gtk_widget_get_style_context(widget);
              GtkStateFlags style_state = 0;
              if (!(window->config.flags & OSW_WL_WindowFlag_IsActivated)){
                style_state |= GTK_STATE_FLAG_BACKDROP;
              }
              if (is_hovered){
                style_state |= GTK_STATE_FLAG_PRELIGHT;
                if (i == gtk_window->active_header_button){
                  style_state |= GTK_STATE_FLAG_ACTIVE;
                }
              }
              
              // background
              gtk_style_context_save(style);
              gtk_style_context_set_state(style, style_state);
              gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
              gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
              gtk_style_context_restore(style);
              
              // get scale
              double sx, sy;
              cairo_surface_get_device_scale(cr_surface, &sx, &sy);
              int scale = (sx + sy)/2.0;
              
              // get original icon dimensions
              GtkWidget *icon_widget = gtk_bin_get_child(GTK_BIN(widget));
              
              // icon info
              gint icon_width, icon_height;
              if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &icon_width, &icon_height)){
                icon_width = 16;
                icon_height = 16;
              }
              GtkIconInfo* icon_info =
                gtk_icon_theme_lookup_icon_for_scale(gtk_icon_theme_get_default(),
                                                     icon_name, icon_width, scale, 0);
              
              // icon pixel buffer
              gtk_style_context_save(style);
              gtk_style_context_set_state(style, style_state);
              GdkPixbuf *icon_pixbuf = gtk_icon_info_load_symbolic_for_context(icon_info, style, 0, 0);
              cairo_surface_t *icon_surface = gdk_cairo_surface_create_from_pixbuf(icon_pixbuf, scale, 0);
              gtk_style_context_restore(style);
              
              // dimensions and position
              gint width = 0;
              gint height = 0;
              gtk_style_context_get(style,
                                    gtk_style_context_get_state(style),
                                    "min-width", &width,
                                    "min-height", &height, NULL);
              width  = ClampBot(width, icon_width);
              height = ClampBot(height, icon_height);
              
              GtkBorder border;
              gtk_style_context_get_border(style, gtk_style_context_get_state(style), &border);
              
              GtkBorder padding;
              gtk_style_context_get_padding(style, gtk_style_context_get_state(style), &padding);
              
              gint left   = border.left + padding.left;
              gint right  = border.right + padding.right;
              gint top    = border.top + padding.top;
              gint bottom = border.bottom + padding.bottom;
              
              width  += left + right;
              height += top + bottom;
              
              gtk_render_icon_surface(gtk_widget_get_style_context(icon_widget),
                                      cr, icon_surface,
                                      rect.x + (width - icon_width)/2,
                                      rect.y + (height - icon_height)/2);
              cairo_paint(cr);
              cairo_surface_destroy(icon_surface);
              g_object_unref(icon_pixbuf);
            }
          }
        }
        
        cairo_destroy(cr);
        cairo_surface_destroy(cr_surface);
      }
    }
    
    osw_wl_subsurface_set_position(subsurface, v2s32(0, -window->csd_frame.border_size.y0));
  }
  osw_wl_subsurface_commit(&gtk_window->titlebar);
  
  // process events
  for (OSW_EachEvent(event)){
    if (event->kind == OSW_EventKind_MouseButton &&
        event->code == OSW_MouseButton_Left){
      B8 consume_event = 0;
      B8 minimize_signal = 0;
      B8 maximize_signal = 0;
      B8 close_signal = 0;
      
      if ((event->flags & OSW_EventFlag_Press) != 0){
        U32 si = 0;
        OSW_WL_Seat *seat = &osw__wl_ctx.seat;
        switch (cursors[si].loc){
          default: case LOCATION_NULL: break;
          
          case LOCATION_SHADOW: {
#define X(N) XDG_TOPLEVEL_RESIZE_EDGE_##N
            static const enum xdg_toplevel_resize_edge xedge_box[] = {
              X(TOP_LEFT),    X(TOP),    X(TOP_RIGHT),
              X(LEFT),        X(NONE),   X(RIGHT),
              X(BOTTOM_LEFT), X(BOTTOM), X(BOTTOM_RIGHT),
            };
#undef X
            xdg_toplevel_resize(window->main_xdg_toplevel, seat->wl_seat,
                                seat->serial, xedge_box[4 + cursors[si].shadow_loc]);
            consume_event = 1;
          }break;
          
          case LOCATION_TITLEBAR: {
            if (cursors[si].header_button_hover == 0){
              xdg_toplevel_move(window->main_xdg_toplevel, seat->wl_seat, seat->serial);
            }
            else{
              gtk_window->active_header_button = cursors[si].header_button_hover;
            }
            consume_event = 1;
          }break;
        }
      }
      else if ((event->flags & OSW_EventFlag_Release) != 0){
        U32 si = 0;
        if (gtk_window->active_header_button != 0 &&
            gtk_window->active_header_button == cursors[si].header_button_hover){
          switch (gtk_window->active_header_button){
            case HEADER_BUTTON_MIN:   { minimize_signal = 1; consume_event = 1; } break;
            case HEADER_BUTTON_MAX:   { maximize_signal = 1; consume_event = 1; } break;
            case HEADER_BUTTON_CLOSE: { close_signal = 1; consume_event = 1; } break;
          }
        }
        gtk_window->active_header_button = 0;
      }
      
      if (minimize_signal){
        osw_window_set_minimize(window);
      }
      if (maximize_signal){
        osw_window_set_maximize(window, 1);
      }
      if (close_signal){
        OSW_Event *event = osw_event__write_push();
        event->kind = OSW_EventKind_WindowClose;
        event->window = window;
      }
      
      if (consume_event){
        osw_event_erase(event);
      }
    }
  }
  
  // set cursor shape
  {
    U32 si = 0;
    OSW_WL_Seat *seat = &osw__wl_ctx.seat;
    if (cursors[si].shadow_loc != 0){
      if (window->control_flags & OSW_WL_WindowControlFlag_Resize){
#define X(N) OSW_CursorShape_##N
        static const OSW_CursorShape cursor_box[] = {
          X(Resize_TL), X(Resize_T), X(Resize_TR),
          X(Resize_L),  X(Pointer),  X(Resize_R),
          X(Resize_BL), X(Resize_B), X(Resize_BR),
        };
#undef X
        osw_set_cursor_shape(cursor_box[4 + cursors[si].shadow_loc]);
      }
    }
    else if (cursors[si].loc == LOCATION_TITLEBAR){
      osw_set_cursor_shape(OSW_CursorShape_Default);
    }
  }
}

MR4TH_SYMBOL void
osw_wl_gtk_window_disable(OSW_WL_GTK_Window *gtk_window){
  osw_wl_subsurface_buffer_clear(&gtk_window->shadow);
  osw_wl_subsurface_commit(&gtk_window->shadow);
  
  osw_wl_subsurface_buffer_clear(&gtk_window->titlebar);
  osw_wl_subsurface_commit(&gtk_window->titlebar);
}

MR4TH_SYMBOL OSW_WL_CSDFrame
osw_wl_gtk_calc_frame(OSW_Window *window){
  OSW_WL_CSDFrame frame = {0};
  B32 show_title = (!(window->config.flags & OSW_WL_WindowFlag_IsFullscreen));
  if (show_title){
    OSW_WL_GTK_Window *gtk_window = &window->gtk_window;
    gtk_widget_get_preferred_height(gtk_window->header, 0, &frame.border_size.y0);
    gtk_header_bar_set_title(GTK_HEADER_BAR(gtk_window->header), "");
    gtk_widget_get_preferred_width(gtk_window->header, 0, &frame.min_size.x);
    gtk_header_bar_set_title(GTK_HEADER_BAR(gtk_window->header), (char*)window->title_buffer);
  }
  return(frame);
}

MR4TH_SYMBOL B32
osw_wl_gtk_calc_surface_off(OSW_WL_GTK_Window *gtk_window, struct wl_surface *surface, V2S32 *off_out){
  B32 result = 0;
  if (surface == gtk_window->titlebar.wl_surface){
    *off_out = gtk_window->titlebar.pos;
    result = 1;
  }
  else if (surface == gtk_window->shadow.wl_surface){
    *off_out = gtk_window->shadow.pos;
    result = 1;
  }
  return(result);
}

typedef struct OSW_WL_GTK_FindWidgetVars{
  char *name;
  GtkWidget *widget;
} OSW_WL_GTK_FindWidgetVars;

MR4TH_SYMBOL void
osw_wl_gtk_widget_from_name__r(GtkWidget *root, void *data){
  OSW_WL_GTK_FindWidgetVars *vars = (OSW_WL_GTK_FindWidgetVars*)data;
  if (vars->widget == 0 && GTK_IS_WIDGET(root)){
    GtkStyleContext *stylectx = gtk_widget_get_style_context(root);
    char *style_str = gtk_style_context_to_string(stylectx, GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE);
    if (strstr(style_str, vars->name) != 0){
      vars->widget = root;
    }
    free(style_str);
  }
  if (vars->widget == 0 && GTK_IS_CONTAINER(root)){
    gtk_container_forall(GTK_CONTAINER(root), osw_wl_gtk_widget_from_name__r, data);
  }
}

MR4TH_SYMBOL GtkWidget*
osw_wl_gtk_widget_from_name(GtkWidget *root, char *name){
  OSW_WL_GTK_FindWidgetVars slot = {0};
  slot.name = name;
  osw_wl_gtk_widget_from_name__r(root, &slot);
  return(slot.widget);
}

// sub-surfaces

MR4TH_SYMBOL OSW_WL_SubSurface
osw_wl_subsurface_new(struct wl_surface *main_wl_surface){
  OSW_WL_SubSurface result = {0};
  result.wl_surface = wl_compositor_create_surface(osw__wl_ctx.wl_compositor);
  result.wl_subsurface = 
    wl_subcompositor_get_subsurface(osw__wl_ctx.wl_subcompositor,
                                    result.wl_surface, main_wl_surface);
  return(result);
}

MR4TH_SYMBOL void
osw_wl_subsurface_buffer_clear(OSW_WL_SubSurface *subsurface){
  if (subsurface->data != 0){
    munmap(subsurface->data, subsurface->size);
    subsurface->data = 0;
  }
  if (subsurface->wl_buffer != 0){
    wl_buffer_destroy(subsurface->wl_buffer);
    subsurface->wl_buffer = 0;
  }
}

MR4TH_SYMBOL void
osw_wl_subsurface_buffer_alloc(OSW_WL_SubSurface *subsurface, V2S32 dim){
  int stride = 4*dim.x;
  uint64_t size = stride*dim.y;
  
  int fd = osw_wl_create_anonymous_file(size);
  if (fd >= 0){
    void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (data != MAP_FAILED){
      struct wl_shm_pool *pool = wl_shm_create_pool(osw__wl_ctx.wl_shm, fd, size);
      struct wl_buffer *wl_buffer =
        wl_shm_pool_create_buffer(pool, 0, dim.x, dim.y,
                                  stride, WL_SHM_FORMAT_ARGB8888);
      wl_shm_pool_destroy(pool);
      
      subsurface->wl_buffer = wl_buffer;
      subsurface->data = data;
      subsurface->size = size;
      subsurface->dim = dim;
    }
    
    close(fd);
  }
}

MR4TH_SYMBOL void
osw_wl_subsurface_set_position(OSW_WL_SubSurface *subsurface, V2S32 pos){
  wl_subsurface_set_position(subsurface->wl_subsurface, pos.x, pos.y);
  subsurface->pos = pos;
}

MR4TH_SYMBOL void
osw_wl_subsurface_commit(OSW_WL_SubSurface *subsurface){
  if (subsurface->wl_buffer != 0){
    wl_surface_attach(subsurface->wl_surface, subsurface->wl_buffer, 0, 0);
    wl_surface_set_buffer_scale(subsurface->wl_surface, 1);
    wl_surface_damage_buffer(subsurface->wl_surface, 0, 0,
                             subsurface->dim.x, subsurface->dim.y);
  }
  else{
    wl_surface_attach(subsurface->wl_surface, 0, 0, 0);
  }
  wl_surface_commit(subsurface->wl_surface);
}

// anonymous files

MR4TH_SYMBOL int
osw_wl_resize_anonymous_file(int fd, off_t size){
#ifdef HAVE_POSIX_FALLOCATE
  
  sigset_t mask;
  sigset_t old_mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGALRM);
  sigprocmask(SIG_BLOCK, &mask, &old_mask);
  
  do {
    errno = posix_fallocate(fd, 0, size);
  } while (errno == EINTR);
  sigprocmask(SIG_SETMASK, &old_mask, NULL);
  
  int result = 0;
  if (errno != 0){
    result = -1;
    if (errno == EINVAL || errno == EOPNOTSUPP){
      if (ftruncate(fd, size) >= 0){
        result = 0;
      }
    }
  }
  return(result);
  
#else
  
  int result = -1;
  if (ftruncate(fd, size) >= 0){
    result = 0;
  }
  return(result);
  
#endif
}

MR4TH_SYMBOL int
osw_wl_create_anonymous_file(off_t size){
  static const char key[] = "/libdecor-shared-XXXXXX";
  int fd = -1;
  
#ifdef HAVE_MEMFD_CREATE
  fd = memfd_create("libdecor", MFD_CLOEXEC | MFD_ALLOW_SEALING);
  if (fd >= 0){
    fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
  }
#endif
  
  if (fd < 0){
    const char *path = getenv("XDG_RUNTIME_DIR");
    if (path == 0){
      errno = ENOENT;
    }
    else{
      char *name = malloc(strlen(path) + sizeof(key));
      if (name != 0){
        strcpy(name, path);
        strcat(name, key);
        
#ifdef HAVE_MKOSTEMP
        fd = mkostemp(name, O_CLOEXEC);
        if (fd >= 0){
          unlink(name);
        }
#else
        fd = mkstemp(name);
        
        if (fd >= 0){
          if (fcntl(fd, F_GETFD) == -1){
            close(fd);
            fd = -1;
          }
          else if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1){
            close(fd);
            fd = -1;
          }
        }
        
        if (fd >= 0) {
          unlink(name);
        }
#endif
        
        free(name);
      }
    }
  }
  
  if (fd >= 0 && osw_wl_resize_anonymous_file(fd, size) < 0){
    close(fd);
    fd = -1;
  }
  
  return(fd);
}

// desktop settings

MR4TH_SYMBOL OSW_WL_CursorTheme
osw_wl_desktop_get_cursor_theme_from_env(Arena *arena){
  OSW_WL_CursorTheme result = {0};
  char *env_xtheme = getenv("XCURSOR_THEME");
  char *env_xsize = getenv("XCURSOR_SIZE");
  if (env_xtheme != 0 && env_xsize != 0){
    result.name = str8_push_copy(arena, str8_cstring((U8*)env_xtheme));
    result.size = atoi(env_xsize);
  }
  else{
    result.size = 24;
  }
  return(result);
}

#ifdef HAS_DBUS
#include <dbus/dbus.h>

static DBusMessage *
osw_wl_desktop__get_setting_sync(DBusConnection *const connection,
                                 const char *key1, const char *key2){
  DBusMessage *reply = 0;
  DBusMessage *message =
    dbus_message_new_method_call("org.freedesktop.portal.Desktop",
                                 "/org/freedesktop/portal/desktop",
                                 "org.freedesktop.portal.Settings",
                                 "Read");
  
  if (message != 0){
    dbus_bool_t success = dbus_message_append_args(message,
                                                   DBUS_TYPE_STRING, &key1,
                                                   DBUS_TYPE_STRING, &key2,
                                                   DBUS_TYPE_INVALID);
    if (success){
      DBusError error;
      dbus_error_init(&error);
      reply = dbus_connection_send_with_reply_and_block(connection, message,
                                                        DBUS_TIMEOUT_USE_DEFAULT,
                                                        &error);
      if (dbus_error_is_set(&error) && reply != 0){
        dbus_message_unref(reply);
        reply = 0;
      }
      dbus_error_free(&error);
    }
    dbus_message_unref(message);
  }
  
  return(reply);
}

static int
osw_wl_desktop__parse_type(DBusMessage *const reply,
                           const int type, void *value){
  int result = 0;
  DBusMessageIter iter[3];
  dbus_message_iter_init(reply, &iter[0]);
  if (dbus_message_iter_get_arg_type(&iter[0]) == DBUS_TYPE_VARIANT){
    dbus_message_iter_recurse(&iter[0], &iter[1]);
    if (dbus_message_iter_get_arg_type(&iter[1]) == DBUS_TYPE_VARIANT){
      dbus_message_iter_recurse(&iter[1], &iter[2]);
      if (dbus_message_iter_get_arg_type(&iter[2]) == type){
        dbus_message_iter_get_basic(&iter[2], value);
        result = 1;
      }
    }
  }
  return(result);
}

static CSD_CursorTheme
osw_wl_desktop_get_cursor_theme(Arena *arena){
  static const char key[] = "org.gnome.desktop.interface";
  static const char key_theme[] = "cursor-theme";
  static const char key_size[] = "cursor-size";
  
  char *name = 0;
  int size = 0;
  int success = 0;
  
  DBusError error;
  dbus_error_init(&error);
  DBusConnection *connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
  
  if (!dbus_error_is_set(&error)){
    DBusMessage *reply = osw_wl_desktop__get_setting_sync(connection, key, key_theme);
    if (reply != 0){
      if (!osw_wl_desktop__parse_type(reply, DBUS_TYPE_STRING, &name)){
        name = 0;
      }
      dbus_message_unref(reply);
    }
  }
  
  if (name != 0){
    DBusMessage *reply = osw_wl_desktop__get_setting_sync(connection, key, key_size);
    if (reply){
      if (osw_wl_desktop__parse_type(reply, DBUS_TYPE_INT32, &size)){
        success = 1;
      }
      dbus_message_unref(reply);
    }
  }
  
  CSD_CursorTheme result = {0};
  if (success){
    result.name = str8_push_copy(arena, str8_cstring((U8*)name));
    result.size = size;
  }
  else{
    result = osw_wl_desktop_get_cursor_theme_from_env(arena);
  }
  
  return(result);
}

static CSD_ColorScheme
osw_wl_desktop_get_color_scheme(Arena *arena){
  static const char name[] = "org.freedesktop.appearance";
  static const char key_color_scheme[] = "color-scheme";
  uint32_t color = 0;
  
  DBusError error;
  dbus_error_init(&error);
  DBusConnection *connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
  
  if (!dbus_error_is_set(&error)){
    DBusMessage *reply = osw_wl_desktop__get_setting_sync(connection, name, key_color_scheme);
    if (reply){
      if (!osw_wl_desktop__parse_type(reply, DBUS_TYPE_UINT32, &color)) {
        color = 0;
      }
      dbus_message_unref(reply);
    }
  }
  return(color);
}

#else

static OSW_WL_CursorTheme
osw_wl_desktop_get_cursor_theme(Arena *arena){
  return(osw_wl_desktop_get_cursor_theme_from_env(arena));
}

static OSW_WL_ColorScheme
osw_wl_desktop_get_color_scheme(Arena *arena){
  return(OSW_WL_ColorScheme_Default);
}

#endif

// cairo shadow rendering

MR4TH_SYMBOL int
osw_wl_cairo_blur_surface(cairo_surface_t *surface, int margin){
  int32_t width, height, stride, x, y, z, w;
  uint8_t *src, *dst;
  uint32_t *s, *d, a, p;
  int i, j, k, size, half;
  uint32_t kernel[71];
  double f;
  
  size = sizeof(kernel)/sizeof(*kernel);
  width = cairo_image_surface_get_width(surface);
  height = cairo_image_surface_get_height(surface);
  stride = cairo_image_surface_get_stride(surface);
  src = cairo_image_surface_get_data(surface);
  
  dst = malloc(height * stride);
  if (dst == NULL)
    return -1;
  
  half = size / 2;
  a = 0;
  for (i = 0; i < size; i++) {
    f = (i - half);
    kernel[i] = exp(- f * f / (sizeof(kernel)/sizeof(*kernel))) * 10000;
    a += kernel[i];
  }
  
  for (i = 0; i < height; i++) {
    s = (uint32_t *) (src + i * stride);
    d = (uint32_t *) (dst + i * stride);
    for (j = 0; j < width; j++) {
      if (margin < j && j < width - margin) {
        d[j] = s[j];
        continue;
      }
      
      x = 0;
      y = 0;
      z = 0;
      w = 0;
      for (k = 0; k < size; k++) {
        if (j - half + k < 0 || j - half + k >= width)
          continue;
        p = s[j - half + k];
        
        x += (p >> 24) * kernel[k];
        y += ((p >> 16) & 0xff) * kernel[k];
        z += ((p >> 8) & 0xff) * kernel[k];
        w += (p & 0xff) * kernel[k];
      }
      d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a;
    }
  }
  
  for (i = 0; i < height; i++) {
    s = (uint32_t *) (dst + i * stride);
    d = (uint32_t *) (src + i * stride);
    for (j = 0; j < width; j++) {
      if (margin <= i && i < height - margin) {
        d[j] = s[j];
        continue;
      }
      
      x = 0;
      y = 0;
      z = 0;
      w = 0;
      for (k = 0; k < size; k++) {
        if (i - half + k < 0 || i - half + k >= height)
          continue;
        s = (uint32_t *) (dst + (i - half + k) * stride);
        p = s[j];
        
        x += (p >> 24) * kernel[k];
        y += ((p >> 16) & 0xff) * kernel[k];
        z += ((p >> 8) & 0xff) * kernel[k];
        w += (p & 0xff) * kernel[k];
      }
      d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a;
    }
  }
  
  free(dst);
  cairo_surface_mark_dirty(surface);
  
  return 0;
}

static void
osw_wl_cairo_render_shadow(cairo_t *cr, cairo_surface_t *surface,
                           int x, int y, int width, int height,
                           int margin, int top_margin){
  cairo_pattern_t *pattern;
  cairo_matrix_t matrix;
  int i, fx, fy, shadow_height, shadow_width;
  
  cairo_set_source_rgba(cr, 0, 0, 0, 0.45);
  cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
  pattern = cairo_pattern_create_for_surface (surface);
  cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
  
  for (i = 0; i < 4; i++) {
    /* when fy is set, then we are working with lower corners,
     * when fx is set, then we are working with right corners
     *
     *  00 ------- 01
     *   |         |
     *   |         |
     *  10 ------- 11
     */
    fx = i & 1;
    fy = i >> 1;
    
    cairo_matrix_init_translate(&matrix,
                                -x + fx * (128 - width),
                                -y + fy * (128 - height));
    cairo_pattern_set_matrix(pattern, &matrix);
    
    shadow_width = margin;
    shadow_height = fy ? margin : top_margin;
    
    /* if the shadows together are greater than the surface, we need
     * to fix it - set the shadow size to the half of
     * the size of surface. Also handle the case when the size is
     * not divisible by 2. In that case we need one part of the
     * shadow to be one pixel greater. !fy or !fx, respectively,
     * will do the work.
     */
    if (height < 2 * shadow_height)
      shadow_height = (height + !fy) / 2;
    
    if (width < 2 * shadow_width)
      shadow_width = (width + !fx) / 2;
    
    cairo_reset_clip(cr);
    cairo_rectangle(cr,
                    x + fx * (width - shadow_width),
                    y + fy * (height - shadow_height),
                    shadow_width, shadow_height);
    cairo_clip (cr);
    cairo_mask(cr, pattern);
  }
  
  
  shadow_width = width - 2 * margin;
  shadow_height = top_margin;
  if (height < 2 * shadow_height)
    shadow_height = height / 2;
  
  if (shadow_width > 0 && shadow_height) {
    /* Top stretch */
    cairo_matrix_init_translate(&matrix, 60, 0);
    cairo_matrix_scale(&matrix, 8.0 / width, 1);
    cairo_matrix_translate(&matrix, -x - width / 2, -y);
    cairo_pattern_set_matrix(pattern, &matrix);
    cairo_rectangle(cr, x + margin, y, shadow_width, shadow_height);
    
    cairo_reset_clip(cr);
    cairo_rectangle(cr,
                    x + margin, y,
                    shadow_width, shadow_height);
    cairo_clip (cr);
    cairo_mask(cr, pattern);
    
    /* Bottom stretch */
    cairo_matrix_translate(&matrix, 0, -height + 128);
    cairo_pattern_set_matrix(pattern, &matrix);
    
    cairo_reset_clip(cr);
    cairo_rectangle(cr, x + margin, y + height - margin,
                    shadow_width, margin);
    cairo_clip (cr);
    cairo_mask(cr, pattern);
  }
  
  shadow_width = margin;
  if (width < 2 * shadow_width)
    shadow_width = width / 2;
  
  shadow_height = height - margin - top_margin;
  
  /* if height is smaller than sum of margins,
   * then the shadow is already done by the corners */
  if (shadow_height > 0 && shadow_width) {
    /* Left stretch */
    cairo_matrix_init_translate(&matrix, 0, 60);
    cairo_matrix_scale(&matrix, 1, 8.0 / height);
    cairo_matrix_translate(&matrix, -x, -y - height / 2);
    cairo_pattern_set_matrix(pattern, &matrix);
    cairo_reset_clip(cr);
    cairo_rectangle(cr, x, y + top_margin,
                    shadow_width, shadow_height);
    cairo_clip (cr);
    cairo_mask(cr, pattern);
    
    /* Right stretch */
    cairo_matrix_translate(&matrix, -width + 128, 0);
    cairo_pattern_set_matrix(pattern, &matrix);
    cairo_rectangle(cr, x + width - shadow_width, y + top_margin,
                    shadow_width, shadow_height);
    cairo_reset_clip(cr);
    cairo_clip (cr);
    cairo_mask(cr, pattern);
  }
  
  cairo_pattern_destroy(pattern);
  cairo_reset_clip(cr);
}
