diff options
41 files changed, 5831 insertions, 1990 deletions
@@ -3,3 +3,7 @@ sibs-build/ compile_commands.json tests/sibs-build/ tests/compile_commands.json +build/ + +src/window/xdg-shell-client-protocol.h +src/window/xdg-shell-protocol.c @@ -1,10 +1,15 @@ # Minimal Graphics Library Written in C and uses OpenGL 2.1 to support as many platforms as possible.\ -Right now mgl only supports x11. +Mgl supports both x11 and wayland and allows you to choose either glx or egl at runtime. # Dependencies ## Build -`x11, libxrender, libxrandr` +`libx11, libxrender, libxrandr`\ +`wayland-client, wayland-egl, wayland-scanner` ## Runtime -`libglvnd (libGL.so)` +`libglvnd (libGL.so, libGLX.so, libEGL.so)` # Notes -Every window _get_ function is cached from the last event poll, no calls to x11 is made. +Every window _get_ function is cached from the last event poll, no calls to x11/wayland is made.\ +Only one window can be created and used at once.\ +mgl needs to be initialized first and then a window has to be created before other functions are called. +# TODO +Wayland support is not finished yet. Use MGL_WINDOW_SYSTEM_X11 for now in mgl_init.
\ No newline at end of file @@ -8,4 +8,5 @@ Only render one glyph for missing symbols.\ High precision mouse wheel event by using xi2, which also allows us to get which scroll wheel was used and scrolling in y direction.\ Use XPresent (PresentNotifyMSC, glSwapBuffers, wait for PresentCompleteNotify before doing presentNotifyMsc and glSwapBuffers again), see https://invent.kde.org/plasma/kwin/-/issues/27. This doesn't work on nvidia because nvidia never sends the PresentCompleteNotify event. Support drag and drop.\ -Use _NET_WM_SYNC_REQUEST. +Use _NET_WM_SYNC_REQUEST.\ +Finish wayland support.
\ No newline at end of file diff --git a/external/stb_image.h b/external/stb_image.h index 1f44c87..9eedabe 100644 --- a/external/stb_image.h +++ b/external/stb_image.h @@ -1,4 +1,4 @@ -/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb +/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb no warranty implied; use at your own risk Do this: @@ -48,6 +48,8 @@ LICENSE RECENT REVISION HISTORY: + 2.30 (2024-05-31) avoid erroneous gcc warning + 2.29 (2023-05-xx) optimizations 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes 2.26 (2020-07-13) many minor fixes @@ -1072,8 +1074,8 @@ static int stbi__addints_valid(int a, int b) return a <= INT_MAX - b; } -// returns 1 if the product of two signed shorts is valid, 0 on overflow. -static int stbi__mul2shorts_valid(short a, short b) +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) { if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid @@ -3384,13 +3386,13 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) return 1; } -static int stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) { // some JPEGs have junk at end, skip over it but if we find what looks // like a valid marker, resume there while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - while (x == 255) { // might be a marker + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker if (stbi__at_eof(j->s)) return STBI__MARKER_none; x = stbi__get8(j->s); if (x != 0x00 && x != 0xff) { @@ -4176,6 +4178,7 @@ typedef struct { stbi_uc *zbuffer, *zbuffer_end; int num_bits; + int hit_zeof_once; stbi__uint32 code_buffer; char *zout; @@ -4242,9 +4245,20 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) int b,s; if (a->num_bits < 16) { if (stbi__zeof(a)) { - return -1; /* report error for unexpected end of data. */ + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); } - stbi__fill_bits(a); } b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; if (b) { @@ -4309,6 +4323,13 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) int len,dist; if (z == 256) { a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } return 1; } if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data @@ -4320,7 +4341,7 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) dist = stbi__zdist_base[z]; if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (zout + len > a->zout_end) { + if (len > a->zout_end - zout) { if (!stbi__zexpand(a, zout, len)) return 0; zout = a->zout; } @@ -4464,6 +4485,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) if (!stbi__parse_zlib_header(a)) return 0; a->num_bits = 0; a->code_buffer = 0; + a->hit_zeof_once = 0; do { final = stbi__zreceive(a,1); type = stbi__zreceive(a,2); @@ -4619,9 +4641,8 @@ enum { STBI__F_up=2, STBI__F_avg=3, STBI__F_paeth=4, - // synthetic filters used for first scanline to avoid needing a dummy row of 0s - STBI__F_avg_first, - STBI__F_paeth_first + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first }; static stbi_uc first_row_filter[5] = @@ -4630,29 +4651,56 @@ static stbi_uc first_row_filter[5] = STBI__F_sub, STBI__F_none, STBI__F_avg_first, - STBI__F_paeth_first + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub }; static int stbi__paeth(int a, int b, int c) { - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; } static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + // create the png data from post-deflated data static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) { - int bytes = (depth == 16? 2 : 1); + int bytes = (depth == 16 ? 2 : 1); stbi__context *s = a->s; stbi__uint32 i,j,stride = x*out_n*bytes; stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; int k; int img_n = s->img_n; // copy it into a local for later @@ -4664,8 +4712,11 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into if (!a->out) return stbi__err("outofmem", "Out of memory"); + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); img_len = (img_width_bytes + 1) * y; // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, @@ -4673,189 +4724,137 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r // so just check for raw_len < img_len always. if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior; + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; int filter = *raw++; - if (filter > 4) - return stbi__err("invalid filter","Corrupt PNG"); - - if (depth < 8) { - if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); - cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place - filter_bytes = 1; - width = img_width_bytes; + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; } - prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above // if first row, use special filter that doesn't sample previous row if (j == 0) filter = first_row_filter[filter]; - // handle first byte explicitly - for (k=0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = raw[k]; break; - case STBI__F_sub : cur[k] = raw[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } - } - - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; // first pixel - raw += img_n; - cur += out_n; - prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; // first pixel top byte - cur[filter_bytes+1] = 255; // first pixel bottom byte - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; - } else { - raw += 1; - cur += 1; - prior += 1; + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; } - // this is a little gross, so that we don't switch per-pixel or per-component - if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*filter_bytes; - #define STBI__CASE(f) \ - case f: \ - for (k=0; k < nk; ++k) - switch (filter) { - // "none" filter turns into a memcpy here; make that explicit. - case STBI__F_none: memcpy(cur, raw, nk); break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; - } - #undef STBI__CASE - raw += nk; - } else { - STBI_ASSERT(img_n+1 == out_n); - #define STBI__CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ - for (k=0; k < filter_bytes; ++k) - switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; - } - #undef STBI__CASE - - // the loop above sets the high byte of the pixels' alpha, but for - // 16 bit png files we also need the low byte set. we'll do that here. - if (depth == 16) { - cur = a->out + stride*j; // start at the beginning of the row again - for (i=0; i < x; ++i,cur+=output_bytes) { - cur[filter_bytes+1] = 255; - } - } - } - } + raw += nk; - // we make a separate pass to expand bits to pixels; for performance, - // this could run two scanlines behind the above code, so it won't - // intefere with filtering but will still be in the cache. - if (depth < 8) { - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; - // note that the final byte might overshoot and write more data than desired. - // we can allocate enough data that this never writes out of memory, but it - // could also overwrite the next scanline. can it overwrite non-empty data - // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. - // so we need to explicitly clamp the final ones - + // expand bits to bytes first if (depth == 4) { - for (k=x*img_n; k >= 2; k-=2, ++in) { - *cur++ = scale * ((*in >> 4) ); - *cur++ = scale * ((*in ) & 0x0f); + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; } - if (k > 0) *cur++ = scale * ((*in >> 4) ); } else if (depth == 2) { - for (k=x*img_n; k >= 4; k-=4, ++in) { - *cur++ = scale * ((*in >> 6) ); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in ) & 0x03); + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; } - if (k > 0) *cur++ = scale * ((*in >> 6) ); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k=x*img_n; k >= 8; k-=8, ++in) { - *cur++ = scale * ((*in >> 7) ); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in ) & 0x01); + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; } - if (k > 0) *cur++ = scale * ((*in >> 7) ); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); } - if (img_n != out_n) { - int q; - // insert alpha = 255 - cur = a->out + stride*j; + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); if (img_n == 1) { - for (q=x-1; q >= 0; --q) { - cur[q*2+1] = 255; - cur[q*2+0] = cur[q]; + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; } } else { STBI_ASSERT(img_n == 3); - for (q=x-1; q >= 0; --q) { - cur[q*4+3] = 255; - cur[q*4+2] = cur[q*3+2]; - cur[q*4+1] = cur[q*3+1]; - cur[q*4+0] = cur[q*3+0]; + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; } } } } - } else if (depth == 16) { - // force the image data from big-endian to platform-native. - // this is done in a separate pass due to the decoding relying - // on the data being untouched, but could probably be done - // per-line during decode if care is taken. - stbi_uc *cur = a->out; - stbi__uint16 *cur16 = (stbi__uint16*)cur; - - for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { - *cur16 = (cur[0] << 8) | cur[1]; - } } + STBI_FREE(filter_buf); + if (!all_ok) return 0; + return 1; } @@ -5161,9 +5160,11 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning + tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is } else { - for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + for (k = 0; k < s->img_n && k < 3; ++k) + tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger } } break; @@ -7984,4 +7985,4 @@ AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ -*/
\ No newline at end of file +*/ diff --git a/include/mgl/gl.h b/include/mgl/gl.h index c1059b6..4658198 100644 --- a/include/mgl/gl.h +++ b/include/mgl/gl.h @@ -10,10 +10,26 @@ typedef struct _XDisplay Display; typedef struct __GLXcontextRec *GLXContext; typedef unsigned long GLXDrawable; typedef struct __GLXFBConfigRec *GLXFBConfig; +typedef void(*__GLXextFuncPtr)(void); + +typedef void (*FUNC_glXSwapIntervalEXT)(Display * dpy, GLXDrawable drawable, int interval); +typedef int (*FUNC_glXSwapIntervalMESA)(unsigned int interval); +typedef int (*FUNC_glXSwapIntervalSGI)(int interval); + +typedef void* EGLDisplay; +typedef void* EGLNativeDisplayType; +typedef uintptr_t EGLNativeWindowType; +typedef void* EGLConfig; +typedef void* EGLSurface; +typedef void* EGLContext; +typedef void (*__eglMustCastToProperFunctionPointerType)(void); typedef struct { - void *handle; + void *gl_library; + void *glx_library; + void *egl_library; + __GLXextFuncPtr (*glXGetProcAddress)(const unsigned char *procName); GLXContext (*glXCreateNewContext)(Display *dpy, GLXFBConfig config, int renderType, GLXContext shareList, int direct); int (*glXMakeContextCurrent)(Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx); void (*glXDestroyContext)(Display *dpy, GLXContext ctx); @@ -21,19 +37,35 @@ typedef struct { GLXFBConfig* (*glXChooseFBConfig)(Display *dpy, int screen, const int *attribList, int *nitems); _XVisualInfo* (*glXGetVisualFromFBConfig)(Display *dpy, GLXFBConfig config); + __eglMustCastToProperFunctionPointerType (*eglGetProcAddress)(const char *procname); + EGLDisplay (*eglGetDisplay)(EGLNativeDisplayType display_id); + unsigned int (*eglInitialize)(EGLDisplay dpy, int32_t *major, int32_t *minor); + unsigned int (*eglTerminate)(EGLDisplay dpy); + unsigned int (*eglGetConfigs)(EGLDisplay dpy, EGLConfig *configs, int32_t config_size, int32_t *num_config); + EGLSurface (*eglCreateWindowSurface)(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const int32_t *attrib_list); + EGLContext (*eglCreateContext)(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const int32_t *attrib_list); + unsigned int (*eglMakeCurrent)(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); + unsigned int (*eglDestroyContext)(EGLDisplay dpy, EGLContext ctx); + unsigned int (*eglDestroySurface)(EGLDisplay dpy, EGLSurface surface); + unsigned int (*eglSwapInterval)(EGLDisplay dpy, int32_t interval); + unsigned int (*eglSwapBuffers)(EGLDisplay dpy, EGLSurface surface); + unsigned int (*eglBindAPI)(unsigned int api); + unsigned int (*eglGetConfigAttrib)(EGLDisplay dpy, EGLConfig config, int32_t attribute, int32_t *value); + void (*glViewport)(int x, int y, int width, int height); void (*glScissor)(int x, int y, int width, int height); void (*glClearColor)(float red, float green, float blue, float alpha); void (*glClear)(unsigned int mask); void (*glEnable)(unsigned int cap); void (*glBlendFunc)(unsigned int sfactor, unsigned int dfactor); + void (*glBlendFuncSeparate)(unsigned int sfactorRGB, unsigned int dfactorRGB, unsigned int sfactorAlpha, unsigned int dfactorAlpha); void (*glGenTextures)(int n, unsigned int *textures); void (*glDeleteTextures)(int n, const unsigned int *textures); + void (*glGetTexLevelParameteriv)(unsigned int target, int level, unsigned int pname, int *params); void (*glTexImage2D)(unsigned int target, int level, int internalFormat, int width, int height, int border, unsigned int format, unsigned int type, const void *pixels); void (*glTexSubImage2D)(unsigned int target, int level, int xoffset, int yoffset, int width, int height, unsigned int format, unsigned int type, const void *pixels); void (*glBindTexture)(unsigned int target, unsigned int texture); void (*glTexParameteri)(unsigned int target, unsigned int pname, int param); - void (*glHint)(unsigned int target, unsigned int mode); void (*glBegin)(unsigned int mode); void (*glEnd)(void); void (*glVertex3f)(float x, float y, float z); @@ -73,6 +105,8 @@ typedef struct { int (*glGetUniformLocation)(unsigned int program, const char *name); void (*glUniform1f)(int location, float v0); void (*glUniform2f)(int location, float v0, float v1); + void (*glUniform3f)(int location, float v0, float v1, float v2); + void (*glUniform4f)(int location, float v0, float v1, float v2, float v3); unsigned int (*glGetError)(void); const unsigned char* (*glGetString)(unsigned int name); void (*glGetIntegerv)(unsigned int pname, int *params); @@ -81,9 +115,10 @@ typedef struct { void (*glFinish)(void); /* Optional*/ - void (*glXSwapIntervalEXT)(Display * dpy, GLXDrawable drawable, int interval); - int (*glXSwapIntervalMESA)(unsigned int interval); - int (*glXSwapIntervalSGI)(int interval); + FUNC_glXSwapIntervalEXT glXSwapIntervalEXT; + FUNC_glXSwapIntervalMESA glXSwapIntervalMESA; + FUNC_glXSwapIntervalSGI glXSwapIntervalSGI; + void (*glGenerateMipmap)(unsigned int target); } mgl_gl; int mgl_gl_load(mgl_gl *self); diff --git a/include/mgl/gl_macro.h b/include/mgl/gl_macro.h index 3141f78..6057bbb 100644 --- a/include/mgl/gl_macro.h +++ b/include/mgl/gl_macro.h @@ -18,6 +18,7 @@ #define GL_COLOR_BUFFER_BIT 0x00004000 #define GL_BLEND 0x0BE2 +#define GL_ONE 1 #define GL_SRC_ALPHA 0x0302 #define GL_ONE_MINUS_SRC_ALPHA 0x0303 #define GL_TEXTURE_2D 0x0DE1 @@ -27,6 +28,8 @@ #define GL_TEXTURE_MIN_FILTER 0x2801 #define GL_UNSIGNED_BYTE 0x1401 #define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 #define GL_ALPHA 0x1906 #define GL_LUMINANCE 0x1909 @@ -45,7 +48,9 @@ #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 #define GL_CLAMP_TO_EDGE 0x812F +#define GL_NEAREST 0x2600 #define GL_LINEAR 0x2601 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 #define GL_POINTS 0x0000 #define GL_LINES 0x0001 @@ -82,6 +87,7 @@ #define GL_LINK_STATUS 0x8B82 #define GL_COMPILE_STATUS 0x8B81 +#define GL_PACK_ALIGNMENT 0x0D05 #define GL_UNPACK_ALIGNMENT 0x0CF5 #define GL_FALSE 0 @@ -89,4 +95,22 @@ #define GL_SCISSOR_TEST 0x0C11 +#define EGL_SUCCESS 0x3000 +#define EGL_BUFFER_SIZE 0x3020 +#define EGL_RED_SIZE 0x3024 +#define EGL_GREEN_SIZE 0x3023 +#define EGL_BLUE_SIZE 0x3022 +#define EGL_ALPHA_SIZE 0x3021 +#define EGL_RENDERABLE_TYPE 0x3040 +#define EGL_OPENGL_API 0x30A2 +#define EGL_OPENGL_BIT 0x0008 +#define EGL_NONE 0x3038 +#define EGL_CONTEXT_CLIENT_VERSION 0x3098 +#define EGL_NATIVE_VISUAL_ID 0x302E +#define EGL_COLOR_BUFFER_TYPE 0x303F +#define EGL_RGB_BUFFER 0x308E +#define EGL_SURFACE_TYPE 0x3033 +#define EGL_OPENGL_ES2_BIT 0x0004 +#define EGL_WINDOW_BIT 0x0004 + #endif /* MGL_GL_MACRO_H */ diff --git a/include/mgl/graphics/backend/egl.h b/include/mgl/graphics/backend/egl.h new file mode 100644 index 0000000..5dede61 --- /dev/null +++ b/include/mgl/graphics/backend/egl.h @@ -0,0 +1,8 @@ +#ifndef MGL_GRAPHICS_EGL +#define MGL_GRAPHICS_EGL + +#include "graphics.h" + +bool mgl_graphics_egl_init(mgl_graphics *self); + +#endif /* MGL_GRAPHICS_EGL */ diff --git a/include/mgl/graphics/backend/glx.h b/include/mgl/graphics/backend/glx.h new file mode 100644 index 0000000..e2a5758 --- /dev/null +++ b/include/mgl/graphics/backend/glx.h @@ -0,0 +1,8 @@ +#ifndef MGL_GRAPHICS_GLX +#define MGL_GRAPHICS_GLX + +#include "graphics.h" + +bool mgl_graphics_glx_init(mgl_graphics *self); + +#endif /* MGL_GRAPHICS_GLX */ diff --git a/include/mgl/graphics/backend/graphics.h b/include/mgl/graphics/backend/graphics.h new file mode 100644 index 0000000..1553364 --- /dev/null +++ b/include/mgl/graphics/backend/graphics.h @@ -0,0 +1,48 @@ +#ifndef MGL_GRAPHICS_H +#define MGL_GRAPHICS_H + +#include <stdbool.h> + +/* Each window should have its own mgl_graphics */ + +typedef struct mgl_graphics mgl_graphics; +typedef void* mgl_window_handle; + +typedef enum { + MGL_GRAPHICS_API_EGL, + MGL_GRAPHICS_API_GLX /* Only available when using X11 (or XWayland) */ +} mgl_graphics_api; + +struct mgl_graphics { + void (*deinit)(mgl_graphics *self); + bool (*make_context_current)(mgl_graphics *self, mgl_window_handle window); + void (*swap_buffers)(mgl_graphics *self, mgl_window_handle window); + bool (*set_swap_interval)(mgl_graphics *self, mgl_window_handle window, bool enabled); + void* (*get_xvisual_info)(mgl_graphics *self); + + /* Optional */ + void* (*get_display)(mgl_graphics *self); + void* (*get_context)(mgl_graphics *self); + + void *impl; + + mgl_graphics_api graphics_api; + bool alpha; +}; + +typedef struct { + mgl_graphics_api graphics_api; /* The graphics api to use. MGL_GRAPHICS_API_EGL by default */ + bool alpha; /* Support window alpha transparency. False by default */ +} mgl_graphics_create_params; + +bool mgl_graphics_init(mgl_graphics *self, const mgl_graphics_create_params *params); +void mgl_graphics_deinit(mgl_graphics *self); + +bool mgl_graphics_make_context_current(mgl_graphics *self, mgl_window_handle window); +void mgl_graphics_swap_buffers(mgl_graphics *self, mgl_window_handle window); +bool mgl_graphics_set_swap_interval(mgl_graphics *self, mgl_window_handle window, bool enabled); +void* mgl_graphics_get_xvisual_info(mgl_graphics *self); +void* mgl_graphics_get_display(mgl_graphics *self); +void* mgl_graphics_get_context(mgl_graphics *self); + +#endif /* MGL_GRAPHICS_H */ diff --git a/include/mgl/graphics/shader.h b/include/mgl/graphics/shader.h index daf8ac3..19d0605 100644 --- a/include/mgl/graphics/shader.h +++ b/include/mgl/graphics/shader.h @@ -2,6 +2,7 @@ #define MGL_SHADER_H #include "../system/vec.h" +#include "../graphics/color.h" typedef struct mgl_texture mgl_texture; @@ -24,6 +25,9 @@ int mgl_shader_program_finalize(mgl_shader_program *self); int mgl_shader_program_set_uniform_float(mgl_shader_program *self, const char *uniform_name, float value); int mgl_shader_program_set_uniform_vec2f(mgl_shader_program *self, const char *uniform_name, mgl_vec2f value); +int mgl_shader_program_set_uniform_vec3f(mgl_shader_program *self, const char *uniform_name, mgl_vec3f value); +int mgl_shader_program_set_uniform_vec4f(mgl_shader_program *self, const char *uniform_name, mgl_vec4f value); +int mgl_shader_program_set_uniform_color(mgl_shader_program *self, const char *uniform_name, mgl_color color); /* If |shader_program| is NULL then no shader is used */ void mgl_shader_program_use(mgl_shader_program *shader_program); diff --git a/include/mgl/graphics/sprite.h b/include/mgl/graphics/sprite.h index 64d9c03..c37ee39 100644 --- a/include/mgl/graphics/sprite.h +++ b/include/mgl/graphics/sprite.h @@ -25,6 +25,14 @@ void mgl_sprite_set_position(mgl_sprite *self, mgl_vec2f position); void mgl_sprite_set_color(mgl_sprite *self, mgl_color color); void mgl_sprite_set_rotation(mgl_sprite *self, float degrees); void mgl_sprite_set_origin(mgl_sprite *self, mgl_vec2f origin); +/* This only has an effect if the sprite has a texture set */ +void mgl_sprite_set_size(mgl_sprite *self, mgl_vec2f size); +/* This only has an effect if the sprite has a texture set. Scales height in proportion */ +void mgl_sprite_set_width(mgl_sprite *self, float width); +/* This only has an effect if the sprite has a texture set. Scales width in proportion */ +void mgl_sprite_set_height(mgl_sprite *self, float height); +/* Texture size multiplied by the sprite scaling */ +mgl_vec2f mgl_sprite_get_size(const mgl_sprite *self); void mgl_sprite_draw(mgl_context *context, mgl_sprite *sprite); #endif /* MGL_SPRITE_H */ diff --git a/include/mgl/graphics/text.h b/include/mgl/graphics/text.h index 89ae541..86d297f 100644 --- a/include/mgl/graphics/text.h +++ b/include/mgl/graphics/text.h @@ -23,6 +23,8 @@ typedef struct { mgl_vec2f position; mgl_vec2f bounds; bool bounds_dirty; + float max_width; + unsigned int max_rows; } mgl_text; /* @@ -41,8 +43,15 @@ void mgl_text_set_string(mgl_text *self, const char *str, size_t str_size); void mgl_text_set_font(mgl_text *self, mgl_font *font); void mgl_text_set_position(mgl_text *self, mgl_vec2f position); void mgl_text_set_color(mgl_text *self, mgl_color color); +/* If |max_width| is 0 then the text has no max width */ +void mgl_text_set_max_width(mgl_text *self, float max_width); +/* If |max_rows| is 0 then the text can display an unlimited amount of rows */ +void mgl_text_set_max_rows(mgl_text *self, unsigned int max_rows); mgl_vec2f mgl_text_get_bounds(mgl_text *self); -/* Returns the position of the character on the screen (relative to the current mgl_view) */ +/* + Returns the position of the character relative to the window top left (relative to the current mgl_view). + If the index is out of range, then the position of the end of the string is returned. +*/ mgl_vec2f mgl_text_find_character_pos(mgl_text *self, size_t index); void mgl_text_draw(mgl_context *context, mgl_text *text); diff --git a/include/mgl/graphics/texture.h b/include/mgl/graphics/texture.h index 387a977..b658daf 100644 --- a/include/mgl/graphics/texture.h +++ b/include/mgl/graphics/texture.h @@ -14,6 +14,12 @@ typedef enum { MGL_TEXTURE_FORMAT_RGBA } mgl_texture_format; +typedef enum { + MGL_TEXTURE_SCALE_NEAREST, + MGL_TEXTURE_SCALE_LINEAR, + MGL_TEXTURE_SCALE_LINEAR_MIPMAP /* generate mipmaps (available since opengl 3.0) */ +} mgl_texture_scale_type; + struct mgl_texture { unsigned int id; int width; @@ -22,23 +28,35 @@ struct mgl_texture { int max_width; int max_height; bool pixel_coordinates; + mgl_texture_scale_type scale_type; + bool owned; }; typedef struct { bool compressed; /* false by default */ bool pixel_coordinates; /* false by default, texture coordinates are normalized */ + mgl_texture_scale_type scale_type; /* MGL_TEXTURE_SCALE_LINEAR by default */ } mgl_texture_load_options; +typedef struct { + bool pixel_coordinates; /* false by default, texture coordinates are normalized */ + mgl_texture_scale_type scale_type; /* MGL_TEXTURE_SCALE_LINEAR by default */ +} mgl_texture_reference_options; + int mgl_texture_init(mgl_texture *self); +/* |reference_options| can be null, in which case the default options are used */ +int mgl_texture_init_reference_existing_gl_texture(mgl_texture *self, unsigned int texture_id, mgl_texture_format format, const mgl_texture_reference_options *reference_options); + /* |load_options| can be null, in which case the default options are used */ -int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, mgl_texture_load_options *load_options); +int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, const mgl_texture_load_options *load_options); /* |load_options| can be null, in which case the default options are used */ -int mgl_texture_load_from_image(mgl_texture *self, const mgl_image *image, mgl_texture_load_options *load_options); +int mgl_texture_load_from_image(mgl_texture *self, const mgl_image *image, const mgl_texture_load_options *load_options); /* |load_options| can be null, in which case the default options are used */ -int mgl_texture_load_from_memory(mgl_texture *self, const unsigned char *data, int width, int height, mgl_image_format format, mgl_texture_load_options *load_options); +int mgl_texture_load_from_memory(mgl_texture *self, const unsigned char *data, int width, int height, mgl_image_format format, const mgl_texture_load_options *load_options); + int mgl_texture_update(mgl_texture *self, const unsigned char *data, int offset_x, int offset_y, int width, int height, mgl_image_format format); /* |load_options| can be null, in which case the default options are used */ -int mgl_texture_resize(mgl_texture *self, int new_width, int new_height, mgl_texture_load_options *load_options); +int mgl_texture_resize(mgl_texture *self, int new_width, int new_height, const mgl_texture_load_options *load_options); /* If |texture| is NULL then no texture is used */ void mgl_texture_use(const mgl_texture *texture); const mgl_texture* mgl_texture_current_texture(void); diff --git a/include/mgl/mgl.h b/include/mgl/mgl.h index 4e1a181..fd3367e 100644 --- a/include/mgl/mgl.h +++ b/include/mgl/mgl.h @@ -2,14 +2,26 @@ #define MGL_MGL_H #include "gl.h" +#include <stdbool.h> /* Display* on x11 */ typedef void* mgl_connection; typedef struct mgl_context mgl_context; +typedef struct mgl_window mgl_window; + +typedef enum { + MGL_WINDOW_SYSTEM_NATIVE, /* Use X11 on X11 and Wayland on Wayland */ + MGL_WINDOW_SYSTEM_X11, /* Use X11 on X11 and XWayland on Wayland */ + MGL_WINDOW_SYSTEM_WAYLAND, /* Use Wayland. If user runs on X11 then it fails to connect */ +} mgl_window_system; struct mgl_context { + bool display_server_is_wayland; + mgl_window_system window_system; /* Window system requested with mgl_init */ mgl_connection connection; mgl_gl gl; + mgl_window *current_window; + unsigned long wm_delete_window_atom; unsigned long net_wm_ping_atom; unsigned long net_wm_pid_atom; @@ -26,7 +38,7 @@ struct mgl_context { Returns non-0 value on failure. Note: not thread safe. */ -int mgl_init(void); +int mgl_init(mgl_window_system window_system); /* Safe to call multiple times, but will only be deinitialized the last time called. @@ -35,5 +47,13 @@ int mgl_init(void); void mgl_deinit(void); mgl_context* mgl_get_context(void); +/* Returns true is mgl_init has been setup successfully and the connection to the display server hasn't been severed */ +bool mgl_is_connected_to_display_server(void); +/* + This can be used when no window has been created to update the connection status to the display server. + If the connection to the display server has been severed and mgl_ping_display_server has been called then + |mgl_is_connected_to_display_server| will return false. +*/ +void mgl_ping_display_server(void); #endif /* MGL_MGL_H */ diff --git a/include/mgl/system/vec.h b/include/mgl/system/vec.h index 8b87376..444efcf 100644 --- a/include/mgl/system/vec.h +++ b/include/mgl/system/vec.h @@ -6,7 +6,23 @@ typedef struct { } mgl_vec2f; typedef struct { + float x, y, z; +} mgl_vec3f; + +typedef struct { + float x, y, z, w; +} mgl_vec4f; + +typedef struct { int x, y; } mgl_vec2i; +typedef struct { + int x, y, z; +} mgl_vec3i; + +typedef struct { + int x, y, z, w; +} mgl_vec4i; + #endif /* MGL_VEC_H */ diff --git a/include/mgl/window/event.h b/include/mgl/window/event.h index 4813154..0f0d94e 100644 --- a/include/mgl/window/event.h +++ b/include/mgl/window/event.h @@ -60,6 +60,16 @@ typedef struct { } mgl_monitor_disconnected_event; typedef enum { + MGL_MAPPING_CHANGED_MODIFIER, + MGL_MAPPING_CHANGED_KEYBOARD, + MGL_MAPPING_CHANGED_POINTER +} mgl_mapping_changed_type; + +typedef struct { + int type; /* mgl_mapping_changed_type */ +} mgl_mapping_changed_event; + +typedef enum { MGL_EVENT_UNKNOWN, MGL_EVENT_CLOSED, /* Window closed */ MGL_EVENT_RESIZED, /* Window resized */ @@ -75,6 +85,7 @@ typedef enum { MGL_EVENT_MONITOR_CONNECTED, MGL_EVENT_MONITOR_DISCONNECTED, MGL_EVENT_MONITOR_PROPERTY_CHANGED, + MGL_EVENT_MAPPING_CHANGED } mgl_event_type; struct mgl_event { @@ -89,6 +100,7 @@ struct mgl_event { mgl_monitor_connected_event monitor_connected; mgl_monitor_disconnected_event monitor_disconnected; mgl_monitor_property_changed_event monitor_property_changed; + mgl_mapping_changed_event mapping_changed; }; }; diff --git a/include/mgl/window/key.h b/include/mgl/window/key.h index 0cd3c03..befd65a 100644 --- a/include/mgl/window/key.h +++ b/include/mgl/window/key.h @@ -1,6 +1,10 @@ #ifndef _MGL_KEY_H_ #define _MGL_KEY_H_ +#include <stdbool.h> +#include <stdint.h> + +/* New keys should always be added at the end! otherwise it will break some software that depends on this order */ typedef enum { MGL_KEY_UNKNOWN, MGL_KEY_A, @@ -104,9 +108,38 @@ typedef enum { MGL_KEY_F14, MGL_KEY_F15, MGL_KEY_PAUSE, + MGL_KEY_PRINTSCREEN, + MGL_KEY_NUMPAD_ENTER, + MGL_KEY_AUDIO_LOWER_VOLUME, + MGL_KEY_AUDIO_RAISE_VOLUME, + MGL_KEY_AUDIO_PLAY, + MGL_KEY_AUDIO_STOP, + MGL_KEY_AUDIO_PAUSE, + MGL_KEY_AUDIO_MUTE, + MGL_KEY_AUDIO_PREV, + MGL_KEY_AUDIO_NEXT, + MGL_KEY_AUDIO_REWIND, + MGL_KEY_AUDIO_FORWARD, + MGL_KEY_DEAD_ACUTE, + MGL_KEY_APOSTROPHE, + MGL_KEY_F16, + MGL_KEY_F17, + MGL_KEY_F18, + MGL_KEY_F19, + MGL_KEY_F20, + MGL_KEY_F21, + MGL_KEY_F22, + MGL_KEY_F23, + MGL_KEY_F24, /* This should always be the last key */ __MGL_NUM_KEYS__ } mgl_key; +/* Return NULL if unknown key */ +const char* mgl_key_to_string(mgl_key key); +bool mgl_key_is_modifier(mgl_key key); +/* Returns XK_VoidSymbol on no match */ +uint64_t mgl_key_to_x11_keysym(mgl_key key); + #endif /* _MGL_KEY_H_ */ diff --git a/include/mgl/window/wayland.h b/include/mgl/window/wayland.h new file mode 100644 index 0000000..081f87c --- /dev/null +++ b/include/mgl/window/wayland.h @@ -0,0 +1,8 @@ +#ifndef MGL_WINDOW_WAYLAND_H +#define MGL_WINDOW_WAYLAND_H + +#include "window.h" + +bool mgl_window_wayland_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window); + +#endif /* MGL_WINDOW_WAYLAND_H */ diff --git a/include/mgl/window/window.h b/include/mgl/window/window.h index 90bbd3d..3a190f5 100644 --- a/include/mgl/window/window.h +++ b/include/mgl/window/window.h @@ -2,6 +2,7 @@ #define MGL_WINDOW_H #include "../graphics/color.h" +#include "../graphics/backend/graphics.h" #include "../system/vec.h" #include "../system/clock.h" #include "key.h" @@ -9,12 +10,16 @@ #include <stdbool.h> #include <stddef.h> +#define MGL_MAX_MONITORS 12 + /* Vsync is automatically set for created windows, if supported by the system */ +typedef union _XEvent XEvent; typedef struct mgl_event mgl_event; -/* x11 window handle. TODO: Add others when wayland, etc is added */ -typedef unsigned long mgl_window_handle; +/* x11/wayland window handle */ +typedef void* mgl_window_handle; +typedef void* mgl_connection; typedef struct mgl_window mgl_window; typedef struct { @@ -36,35 +41,77 @@ typedef struct { int refresh_rate; } mgl_monitor; +typedef enum { + MGL_WINDOW_TYPE_NORMAL, + MGL_WINDOW_TYPE_DIALOG, /* Also sets the window as always on top */ + MGL_WINDOW_TYPE_NOTIFICATION /* Also sets the window as always on top */ +} mgl_window_type; + +typedef enum { + MGL_CLIPBOARD_TYPE_STRING = 1 << 0, + MGL_CLIPBOARD_TYPE_IMAGE_PNG = 1 << 1, + MGL_CLIPBOARD_TYPE_IMAGE_JPG = 1 << 2, + MGL_CLIPBOARD_TYPE_IMAGE_GIF = 1 << 3, +} mgl_clipboard_type; + +#define MGL_CLIPBOARD_TYPE_ALL 0xFFFFFFFF +#define MGL_CLIPBOARD_TYPE_IMAGE (MGL_CLIPBOARD_TYPE_IMAGE_PNG | MGL_CLIPBOARD_TYPE_IMAGE_JPG | MGL_CLIPBOARD_TYPE_IMAGE_GIF) + +/* + Return true to continue. |mgl_window_get_clipboard| returns false if this returns false. + Note: |size| is the size of the current data, not the total data (if the callback only contains a part of the data). +*/ +typedef bool (*mgl_clipboard_callback)(const unsigned char *data, size_t size, mgl_clipboard_type clipboard_type, void *userdata); +typedef void (*mgl_active_monitor_callback)(const mgl_monitor *monitor, void *userdata); + struct mgl_window { - mgl_window_handle window; - void *context; + mgl_window_handle (*get_system_handle)(const mgl_window *self); + void (*deinit)(mgl_window *self); + void (*close)(mgl_window *self); + bool (*poll_event)(mgl_window *self, mgl_event *event); + bool (*inject_x11_event)(mgl_window *self, XEvent *xev, mgl_event *event); /* Optional */ + void (*swap_buffers)(mgl_window *self); + void (*set_visible)(mgl_window *self, bool visible); + bool (*is_key_pressed)(const mgl_window *self, mgl_key key); + bool (*is_mouse_button_pressed)(const mgl_window *self, mgl_mouse_button button); + void (*set_title)(mgl_window *self, const char *title); + void (*set_cursor_visible)(mgl_window *self, bool visible); + void (*set_vsync_enabled)(mgl_window *self, bool enabled); + bool (*is_vsync_enabled)(const mgl_window *self); + void (*set_fullscreen)(mgl_window *self, bool fullscreen); + bool (*is_fullscreen)(const mgl_window *self); + void (*set_position)(mgl_window *self, mgl_vec2i position); + void (*set_size)(mgl_window *self, mgl_vec2i size); + void (*set_size_limits)(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum); + void (*set_clipboard)(mgl_window *self, const char *str, size_t size); + bool (*get_clipboard)(mgl_window *self, mgl_clipboard_callback callback, void *userdata, uint32_t clipboard_types); + void (*set_key_repeat_enabled)(mgl_window *self, bool enabled); + void (*flush)(mgl_window *self); + void* (*get_egl_display)(mgl_window *self); + void* (*get_egl_context)(mgl_window *self); + void (*for_each_active_monitor_output)(mgl_window *self, mgl_active_monitor_callback callback, void *userdata); + + void *impl; + + bool vsync_enabled; /* true by default */ + bool low_latency; /* false by default */ + bool open; + bool focused; + bool key_repeat_enabled; /* true by default */ + double frame_time_limit; + double frame_time_limit_monitor; + mgl_clock frame_timer; mgl_vec2i pos; mgl_vec2i size; /* relative to the top left of the window. only updates when the cursor is inside the window */ mgl_vec2i cursor_position; mgl_view view; mgl_scissor scissor; - bool open; - bool focused; - bool key_repeat_enabled; - bool vsync_enabled; /* true by default */ - double frame_time_limit; - double frame_time_limit_monitor; - mgl_clock frame_timer; - char *clipboard_string; - size_t clipboard_size; - bool low_latency; /* false by default */ - - mgl_monitor *monitors; /* TODO: Move these to mgl file */ + /* This only contains connected and active monitors */ + mgl_monitor monitors[MGL_MAX_MONITORS]; int num_monitors; }; -typedef enum { - MGL_WINDOW_TYPE_NORMAL, - MGL_WINDOW_TYPE_DIALOG -} mgl_window_type; - /* TODO: Some of these parameters only apply to new window */ typedef struct { mgl_vec2i position; @@ -75,28 +122,14 @@ typedef struct { bool hidden; /* false by default */ bool override_redirect; /* false by default */ bool support_alpha; /* support alpha for the window, false by default */ + bool hide_decorations; /* this is a hint, it may be ignored by the window manager, false by default */ mgl_color background_color; /* default: black */ - const char *class_name; + const char *class_name; /* Class name on X11, App ID on Wayland */ mgl_window_type window_type; /* default: normal */ - mgl_window_handle transient_for_window; + mgl_window_handle transient_for_window; /* 0 = none */ + mgl_graphics_api graphics_api; /* Can only be MGL_GRAPHICS_API_GLX in an X11 window. default: MGL_GRAPHICS_API_EGL */ } mgl_window_create_params; -typedef enum { - MGL_CLIPBOARD_TYPE_STRING = 1 << 0, - MGL_CLIPBOARD_TYPE_IMAGE_PNG = 1 << 1, - MGL_CLIPBOARD_TYPE_IMAGE_JPG = 1 << 2, - MGL_CLIPBOARD_TYPE_IMAGE_GIF = 1 << 3, -} mgl_clipboard_type; - -#define MGL_CLIPBOARD_TYPE_ALL 0xFFFFFFFF -#define MGL_CLIPBOARD_TYPE_IMAGE (MGL_CLIPBOARD_TYPE_IMAGE_PNG | MGL_CLIPBOARD_TYPE_IMAGE_JPG | MGL_CLIPBOARD_TYPE_IMAGE_GIF) - -/* - Return true to continue. |mgl_window_get_clipboard| returns false if this returns false. - Note: |size| is the size of the current data, not the total data (if the callback only contains a part of the data). -*/ -typedef bool (*mgl_clipboard_callback)(const unsigned char *data, size_t size, mgl_clipboard_type clipboard_type, void *userdata); - /* |params| can be NULL. Note: vsync is enabled by default */ int mgl_window_create(mgl_window *self, const char *title, const mgl_window_create_params *params); int mgl_window_init_from_existing_window(mgl_window *self, mgl_window_handle existing_window); @@ -104,6 +137,7 @@ void mgl_window_deinit(mgl_window *self); void mgl_window_clear(mgl_window *self, mgl_color color); bool mgl_window_poll_event(mgl_window *self, mgl_event *event); +bool mgl_window_inject_x11_event(mgl_window *self, XEvent *xev, mgl_event *event); void mgl_window_display(mgl_window *self); /* @@ -123,7 +157,7 @@ void mgl_window_get_view(mgl_window *self, mgl_view *view); |mgl_window_set_scissor| with that saved scissor. The scissor is set to the window size when the window is resized (window resize event). */ -void mgl_window_set_scissor(mgl_window *self, mgl_scissor *new_scissor); +void mgl_window_set_scissor(mgl_window *self, const mgl_scissor *new_scissor); void mgl_window_get_scissor(mgl_window *self, mgl_scissor *scissor); void mgl_window_set_visible(mgl_window *self, bool visible); @@ -132,6 +166,8 @@ bool mgl_window_has_focus(const mgl_window *self); bool mgl_window_is_key_pressed(const mgl_window *self, mgl_key key); bool mgl_window_is_mouse_button_pressed(const mgl_window *self, mgl_mouse_button button); +/* Returns 0 if none is available */ +mgl_window_handle mgl_window_get_system_handle(const mgl_window *self); void mgl_window_close(mgl_window *self); void mgl_window_set_title(mgl_window *self, const char *title); void mgl_window_set_cursor_visible(mgl_window *self, bool visible); @@ -145,9 +181,17 @@ bool mgl_window_is_fullscreen(const mgl_window *self); void mgl_window_set_low_latency(mgl_window *self, bool low_latency); bool mgl_window_is_low_latency_enabled(const mgl_window *self); +/* This also flushes the X11 data so the position is changed as soon as possible */ void mgl_window_set_position(mgl_window *self, mgl_vec2i position); +/* + Note that window size in mgl_window doesn't update immediately as the change size request can be ignored by the window manager. A MGL_EVENT_RESIZED event will be received if the resize occurred. + This also flushes the X11 data so the size is changed as soon as possible. +*/ void mgl_window_set_size(mgl_window *self, mgl_vec2i size); -/* if |minimum| is (0, 0) then there is no minimum limit, if |maximum| is (0, 0) then there is no maximum limit */ +/* + If |minimum| is (0, 0) then there is no minimum limit, if |maximum| is (0, 0) then there is no maximum limit. + This also flushes the X11 data so the size is changed as soon as possible. +*/ void mgl_window_set_size_limits(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum); void mgl_window_set_clipboard(mgl_window *self, const char *str, size_t size); @@ -166,4 +210,9 @@ void mgl_window_set_key_repeat_enabled(mgl_window *self, bool enabled); void mgl_window_flush(mgl_window *self); +void* mgl_window_get_egl_display(mgl_window *self); +void* mgl_window_get_egl_context(mgl_window *self); + +void mgl_window_for_each_active_monitor_output(mgl_window *self, mgl_active_monitor_callback callback, void *userdata); + #endif /* MGL_WINDOW_H */ diff --git a/include/mgl/window/x11.h b/include/mgl/window/x11.h new file mode 100644 index 0000000..6c4aa93 --- /dev/null +++ b/include/mgl/window/x11.h @@ -0,0 +1,8 @@ +#ifndef MGL_WINDOW_X11_H +#define MGL_WINDOW_X11_H + +#include "window.h" + +bool mgl_window_x11_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window); + +#endif /* MGL_WINDOW_X11_H */ diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..58a280a --- /dev/null +++ b/meson.build @@ -0,0 +1,88 @@ +project('mgl', ['c'], version : '1.0.0', default_options : ['warning_level=2']) + +if get_option('buildtype') == 'debug' + add_project_arguments('-g3', language : ['c']) +elif get_option('buildtype') == 'release' + add_project_arguments('-DNDEBUG', language : ['c']) +endif + +src = [ + 'src/graphics/texture.c', + 'src/graphics/sprite.c', + 'src/graphics/image.c', + 'src/graphics/font_char_map.c', + 'src/graphics/font.c', + 'src/graphics/vertex.c', + 'src/graphics/primitive_type.c', + 'src/graphics/vertex_buffer.c', + 'src/graphics/text.c', + 'src/graphics/shader.c', + 'src/graphics/rectangle.c', + 'src/graphics/backend/graphics.c', + 'src/graphics/backend/glx.c', + 'src/graphics/backend/egl.c', + 'src/system/fileutils.c', + 'src/system/utf8.c', + 'src/system/clock.c', + 'src/mgl.c', + 'src/window/x11.c', + 'src/window/window.c', + 'src/window/key.c', + 'src/gl.c', +] + +src += [ + 'src/window/wayland.c', +] + +subdir('protocol') +src += protocol_src + +cc = meson.get_compiler('c') +dep = [ + dependency('x11'), + dependency('xrender'), + dependency('xrandr'), + dependency('threads'), + cc.find_library('m'), +] + +dep += [ + dependency('wayland-client'), + dependency('wayland-egl'), +] + +public_headers = include_directories('include') + +project_target = static_library( + meson.project_name(), + src, + install : false, + include_directories : public_headers, + dependencies : dep, +) + +project_dep = declare_dependency(include_directories : public_headers, link_with : project_target) +set_variable(meson.project_name() + '_dep', project_dep) + +#install_headers(project_headers, subdir : meson.project_name(), preserve_path : true) + +# pkg_mod = import('pkgconfig') +# pkg_mod.generate( +# name : meson.project_name(), +# filebase : meson.project_name(), +# description : 'Minimal Graphics Library', +# subdirs : meson.project_name(), +# libraries : project_target, +# ) + +if not meson.is_subproject() + subdir('tests') + test('tests', + executable('run_tests', + files(['tests/main.c']), + dependencies : [project_dep, test_dep], + install : false + ) + ) +endif
\ No newline at end of file diff --git a/project.conf b/project.conf index d218df6..e4ea76a 100644 --- a/project.conf +++ b/project.conf @@ -5,9 +5,12 @@ version = "0.1.0" platforms = ["posix"] [config] +ignore_dirs = ["build"] expose_include_dirs = ["include"] [dependencies] x11 = ">=0.5" xrender = ">=0.5" -xrandr = ">=0.5"
\ No newline at end of file +xrandr = ">=0.5" +wayland-client = ">=1" +wayland-egl = ">=15"
\ No newline at end of file diff --git a/protocol/meson.build b/protocol/meson.build new file mode 100644 index 0000000..adaaefc --- /dev/null +++ b/protocol/meson.build @@ -0,0 +1,25 @@ +wayland_scanner = dependency('wayland-scanner', native: true) +wayland_scanner_path = wayland_scanner.get_variable(pkgconfig: 'wayland_scanner') +wayland_scanner_prog = find_program(wayland_scanner_path, native: true) + +wayland_scanner_code = generator( + wayland_scanner_prog, + output: '@BASENAME@-protocol.c', + arguments: ['private-code', '@INPUT@', '@OUTPUT@'], +) + +wayland_scanner_client = generator( + wayland_scanner_prog, + output: '@BASENAME@-client-protocol.h', + arguments: ['client-header', '@INPUT@', '@OUTPUT@'], +) + +protocols = [ + 'xdg-shell.xml', +] + +protocol_src = [] +foreach xml : protocols + protocol_src += wayland_scanner_code.process(xml) + protocol_src += wayland_scanner_client.process(xml) +endforeach diff --git a/protocol/xdg-shell.xml b/protocol/xdg-shell.xml new file mode 100644 index 0000000..c4d4685 --- /dev/null +++ b/protocol/xdg-shell.xml @@ -0,0 +1,1415 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="xdg_shell"> + + <copyright> + Copyright © 2008-2013 Kristian Høgsberg + Copyright © 2013 Rafael Antognolli + Copyright © 2013 Jasper St. Pierre + Copyright © 2010-2013 Intel Corporation + Copyright © 2015-2017 Samsung Electronics Co., Ltd + Copyright © 2015-2017 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + </copyright> + + <interface name="xdg_wm_base" version="7"> + <description summary="create desktop-style surfaces"> + The xdg_wm_base interface is exposed as a global object enabling clients + to turn their wl_surfaces into windows in a desktop environment. It + defines the basic functionality needed for clients and the compositor to + create windows that can be dragged, resized, maximized, etc, as well as + creating transient windows such as popup menus. + </description> + + <enum name="error"> + <entry name="role" value="0" summary="given wl_surface has another role"/> + <entry name="defunct_surfaces" value="1" + summary="xdg_wm_base was destroyed before children"/> + <entry name="not_the_topmost_popup" value="2" + summary="the client tried to map or destroy a non-topmost popup"/> + <entry name="invalid_popup_parent" value="3" + summary="the client specified an invalid popup parent surface"/> + <entry name="invalid_surface_state" value="4" + summary="the client provided an invalid surface state"/> + <entry name="invalid_positioner" value="5" + summary="the client provided an invalid positioner"/> + <entry name="unresponsive" value="6" + summary="the client didn’t respond to a ping event in time"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy xdg_wm_base"> + Destroy this xdg_wm_base object. + + Destroying a bound xdg_wm_base object while there are surfaces + still alive created by this xdg_wm_base object instance is illegal + and will result in a defunct_surfaces error. + </description> + </request> + + <request name="create_positioner"> + <description summary="create a positioner object"> + Create a positioner object. A positioner object is used to position + surfaces relative to some parent surface. See the interface description + and xdg_surface.get_popup for details. + </description> + <arg name="id" type="new_id" interface="xdg_positioner"/> + </request> + + <request name="get_xdg_surface"> + <description summary="create a shell surface from a surface"> + This creates an xdg_surface for the given surface. While xdg_surface + itself is not a role, the corresponding surface may only be assigned + a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is + illegal to create an xdg_surface for a wl_surface which already has an + assigned role and this will result in a role error. + + This creates an xdg_surface for the given surface. An xdg_surface is + used as basis to define a role to a given surface, such as xdg_toplevel + or xdg_popup. It also manages functionality shared between xdg_surface + based surface roles. + + See the documentation of xdg_surface for more details about what an + xdg_surface is and how it is used. + </description> + <arg name="id" type="new_id" interface="xdg_surface"/> + <arg name="surface" type="object" interface="wl_surface"/> + </request> + + <request name="pong"> + <description summary="respond to a ping event"> + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_wm_base.ping + and xdg_wm_base.error.unresponsive. + </description> + <arg name="serial" type="uint" summary="serial of the ping event"/> + </request> + + <event name="ping"> + <description summary="check if the client is alive"> + The ping event asks the client if it's still alive. Pass the + serial specified in the event back to the compositor by sending + a "pong" request back with the specified serial. See xdg_wm_base.pong. + + Compositors can use this to determine if the client is still + alive. It's unspecified what will happen if the client doesn't + respond to the ping request, or in what timeframe. Clients should + try to respond in a reasonable amount of time. The “unresponsive” + error is provided for compositors that wish to disconnect unresponsive + clients. + + A compositor is free to ping in any way it wants, but a client must + always respond to any xdg_wm_base object it created. + </description> + <arg name="serial" type="uint" summary="pass this to the pong request"/> + </event> + </interface> + + <interface name="xdg_positioner" version="7"> + <description summary="child surface positioner"> + The xdg_positioner provides a collection of rules for the placement of a + child surface relative to a parent surface. Rules can be defined to ensure + the child surface remains within the visible area's borders, and to + specify how the child surface changes its position, such as sliding along + an axis, or flipping around a rectangle. These positioner-created rules are + constrained by the requirement that a child surface must intersect with or + be at least partially adjacent to its parent surface. + + See the various requests for details about possible rules. + + At the time of the request, the compositor makes a copy of the rules + specified by the xdg_positioner. Thus, after the request is complete the + xdg_positioner object can be destroyed or reused; further changes to the + object will have no effect on previous usages. + + For an xdg_positioner object to be considered complete, it must have a + non-zero size set by set_size, and a non-zero anchor rectangle set by + set_anchor_rect. Passing an incomplete xdg_positioner object when + positioning a surface raises an invalid_positioner error. + </description> + + <enum name="error"> + <entry name="invalid_input" value="0" summary="invalid input provided"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy the xdg_positioner object"> + Notify the compositor that the xdg_positioner will no longer be used. + </description> + </request> + + <request name="set_size"> + <description summary="set the size of the to-be positioned rectangle"> + Set the size of the surface that is to be positioned with the positioner + object. The size is in surface-local coordinates and corresponds to the + window geometry. See xdg_surface.set_window_geometry. + + If a zero or negative size is set the invalid_input error is raised. + </description> + <arg name="width" type="int" summary="width of positioned rectangle"/> + <arg name="height" type="int" summary="height of positioned rectangle"/> + </request> + + <request name="set_anchor_rect"> + <description summary="set the anchor rectangle within the parent surface"> + Specify the anchor rectangle within the parent surface that the child + surface will be placed relative to. The rectangle is relative to the + window geometry as defined by xdg_surface.set_window_geometry of the + parent surface. + + When the xdg_positioner object is used to position a child surface, the + anchor rectangle may not extend outside the window geometry of the + positioned child's parent surface. + + If a negative size is set the invalid_input error is raised. + </description> + <arg name="x" type="int" summary="x position of anchor rectangle"/> + <arg name="y" type="int" summary="y position of anchor rectangle"/> + <arg name="width" type="int" summary="width of anchor rectangle"/> + <arg name="height" type="int" summary="height of anchor rectangle"/> + </request> + + <enum name="anchor"> + <entry name="none" value="0"/> + <entry name="top" value="1"/> + <entry name="bottom" value="2"/> + <entry name="left" value="3"/> + <entry name="right" value="4"/> + <entry name="top_left" value="5"/> + <entry name="bottom_left" value="6"/> + <entry name="top_right" value="7"/> + <entry name="bottom_right" value="8"/> + </enum> + + <request name="set_anchor"> + <description summary="set anchor rectangle anchor"> + Defines the anchor point for the anchor rectangle. The specified anchor + is used derive an anchor point that the child surface will be + positioned relative to. If a corner anchor is set (e.g. 'top_left' or + 'bottom_right'), the anchor point will be at the specified corner; + otherwise, the derived anchor point will be centered on the specified + edge, or in the center of the anchor rectangle if no edge is specified. + </description> + <arg name="anchor" type="uint" enum="anchor" + summary="anchor"/> + </request> + + <enum name="gravity"> + <entry name="none" value="0"/> + <entry name="top" value="1"/> + <entry name="bottom" value="2"/> + <entry name="left" value="3"/> + <entry name="right" value="4"/> + <entry name="top_left" value="5"/> + <entry name="bottom_left" value="6"/> + <entry name="top_right" value="7"/> + <entry name="bottom_right" value="8"/> + </enum> + + <request name="set_gravity"> + <description summary="set child surface gravity"> + Defines in what direction a surface should be positioned, relative to + the anchor point of the parent surface. If a corner gravity is + specified (e.g. 'bottom_right' or 'top_left'), then the child surface + will be placed towards the specified gravity; otherwise, the child + surface will be centered over the anchor point on any axis that had no + gravity specified. If the gravity is not in the ‘gravity’ enum, an + invalid_input error is raised. + </description> + <arg name="gravity" type="uint" enum="gravity" + summary="gravity direction"/> + </request> + + <enum name="constraint_adjustment" bitfield="true"> + <description summary="constraint adjustments"> + The constraint adjustment value define ways the compositor will adjust + the position of the surface, if the unadjusted position would result + in the surface being partly constrained. + + Whether a surface is considered 'constrained' is left to the compositor + to determine. For example, the surface may be partly outside the + compositor's defined 'work area', thus necessitating the child surface's + position be adjusted until it is entirely inside the work area. + + The adjustments can be combined, according to a defined precedence: 1) + Flip, 2) Slide, 3) Resize. + </description> + <entry name="none" value="0"> + <description summary="don't move the child surface when constrained"> + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of an output. + </description> + </entry> + <entry name="slide_x" value="1"> + <description summary="move along the x axis until unconstrained"> + Slide the surface along the x axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the x axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + x axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + </description> + </entry> + <entry name="slide_y" value="2"> + <description summary="move along the y axis until unconstrained"> + Slide the surface along the y axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the y axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + y axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + </description> + </entry> + <entry name="flip_x" value="4"> + <description summary="invert the anchor and gravity on the x axis"> + Invert the anchor and gravity on the x axis if the surface is + constrained on the x axis. For example, if the left edge of the + surface is constrained, the gravity is 'left' and the anchor is + 'left', change the gravity to 'right' and the anchor to 'right'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_x adjustment will be the one before the + adjustment. + </description> + </entry> + <entry name="flip_y" value="8"> + <description summary="invert the anchor and gravity on the y axis"> + Invert the anchor and gravity on the y axis if the surface is + constrained on the y axis. For example, if the bottom edge of the + surface is constrained, the gravity is 'bottom' and the anchor is + 'bottom', change the gravity to 'top' and the anchor to 'top'. + + The adjusted position is calculated given the original anchor + rectangle and offset, but with the new flipped anchor and gravity + values. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_y adjustment will be the one before the + adjustment. + </description> + </entry> + <entry name="resize_x" value="16"> + <description summary="horizontally resize the surface"> + Resize the surface horizontally so that it is completely + unconstrained. + </description> + </entry> + <entry name="resize_y" value="32"> + <description summary="vertically resize the surface"> + Resize the surface vertically so that it is completely unconstrained. + </description> + </entry> + </enum> + + <request name="set_constraint_adjustment"> + <description summary="set the adjustment to be done when constrained"> + Specify how the window should be positioned if the originally intended + position caused the surface to be constrained, meaning at least + partially outside positioning boundaries set by the compositor. The + adjustment is set by constructing a bitmask describing the adjustment to + be made when the surface is constrained on that axis. + + If no bit for one axis is set, the compositor will assume that the child + surface should not change its position on that axis when constrained. + + If more than one bit for one axis is set, the order of how adjustments + are applied is specified in the corresponding adjustment descriptions. + + The default adjustment is none. + </description> + <arg name="constraint_adjustment" type="uint" enum="constraint_adjustment" + summary="bit mask of constraint adjustments"/> + </request> + + <request name="set_offset"> + <description summary="set surface position offset"> + Specify the surface position offset relative to the position of the + anchor on the anchor rectangle and the anchor on the surface. For + example if the anchor of the anchor rectangle is at (x, y), the surface + has the gravity bottom|right, and the offset is (ox, oy), the calculated + surface position will be (x + ox, y + oy). The offset position of the + surface is the one used for constraint testing. See + set_constraint_adjustment. + + An example use case is placing a popup menu on top of a user interface + element, while aligning the user interface element of the parent surface + with some user interface element placed somewhere in the popup surface. + </description> + <arg name="x" type="int" summary="surface position x offset"/> + <arg name="y" type="int" summary="surface position y offset"/> + </request> + + <!-- Version 3 additions --> + + <request name="set_reactive" since="3"> + <description summary="continuously reconstrain the surface"> + When set reactive, the surface is reconstrained if the conditions used + for constraining changed, e.g. the parent window moved. + + If the conditions changed and the popup was reconstrained, an + xdg_popup.configure event is sent with updated geometry, followed by an + xdg_surface.configure event. + </description> + </request> + + <request name="set_parent_size" since="3"> + <description summary=""> + Set the parent window geometry the compositor should use when + positioning the popup. The compositor may use this information to + determine the future state the popup should be constrained using. If + this doesn't match the dimension of the parent the popup is eventually + positioned against, the behavior is undefined. + + The arguments are given in the surface-local coordinate space. + </description> + <arg name="parent_width" type="int" + summary="future window geometry width of parent"/> + <arg name="parent_height" type="int" + summary="future window geometry height of parent"/> + </request> + + <request name="set_parent_configure" since="3"> + <description summary="set parent configure this is a response to"> + Set the serial of an xdg_surface.configure event this positioner will be + used in response to. The compositor may use this information together + with set_parent_size to determine what future state the popup should be + constrained using. + </description> + <arg name="serial" type="uint" + summary="serial of parent configure event"/> + </request> + </interface> + + <interface name="xdg_surface" version="7"> + <description summary="desktop user interface surface base interface"> + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides a base set of functionality required to construct user + interface elements requiring management by the compositor, such as + toplevel windows, menus, etc. The types of functionality are split into + xdg_surface roles. + + Creating an xdg_surface does not set the role for a wl_surface. In order + to map an xdg_surface, the client must create a role-specific object + using, e.g., get_toplevel, get_popup. The wl_surface for any given + xdg_surface can have at most one role, and may not be assigned any role + not based on xdg_surface. + + A role must be assigned before any other requests are made to the + xdg_surface object. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. + + Creating an xdg_surface from a wl_surface which has a buffer attached or + committed is a client error, and any attempts by a client to attach or + manipulate a buffer prior to the first xdg_surface.configure call must + also be treated as errors. + + After creating a role-specific object and setting it up (e.g. by sending + the title, app ID, size constraints, parent, etc), the client must + perform an initial commit without any buffer attached. The compositor + will reply with initial wl_surface state such as + wl_surface.preferred_buffer_scale followed by an xdg_surface.configure + event. The client must acknowledge it and is then allowed to attach a + buffer to map the surface. + + Mapping an xdg_surface-based role surface is defined as making it + possible for the surface to be shown by the compositor. Note that + a mapped surface is not guaranteed to be visible once it is mapped. + + For an xdg_surface to be mapped by the compositor, the following + conditions must be met: + (1) the client has assigned an xdg_surface-based role to the surface + (2) the client has set and committed the xdg_surface state and the + role-dependent state to the surface + (3) the client has committed a buffer to the surface + + A newly-unmapped surface is considered to have met condition (1) out + of the 3 required conditions for mapping a surface if its role surface + has not been destroyed, i.e. the client must perform the initial commit + again before attaching a buffer. + </description> + + <enum name="error"> + <entry name="not_constructed" value="1" + summary="Surface was not fully constructed"/> + <entry name="already_constructed" value="2" + summary="Surface was already constructed"/> + <entry name="unconfigured_buffer" value="3" + summary="Attaching a buffer to an unconfigured surface"/> + <entry name="invalid_serial" value="4" + summary="Invalid serial number when acking a configure event"/> + <entry name="invalid_size" value="5" + summary="Width or height was zero or negative"/> + <entry name="defunct_role_object" value="6" + summary="Surface was destroyed before its role object"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy the xdg_surface"> + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed, otherwise + a defunct_role_object error is raised. + </description> + </request> + + <request name="get_toplevel"> + <description summary="assign the xdg_toplevel surface role"> + This creates an xdg_toplevel object for the given xdg_surface and gives + the associated wl_surface the xdg_toplevel role. + + See the documentation of xdg_toplevel for more details about what an + xdg_toplevel is and how it is used. + </description> + <arg name="id" type="new_id" interface="xdg_toplevel"/> + </request> + + <request name="get_popup"> + <description summary="assign the xdg_popup surface role"> + This creates an xdg_popup object for the given xdg_surface and gives + the associated wl_surface the xdg_popup role. + + If null is passed as a parent, a parent surface must be specified using + some other protocol, before committing the initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + </description> + <arg name="id" type="new_id" interface="xdg_popup"/> + <arg name="parent" type="object" interface="xdg_surface" allow-null="true"/> + <arg name="positioner" type="object" interface="xdg_positioner"/> + </request> + + <request name="set_window_geometry"> + <description summary="set the new window geometry"> + The window geometry of a surface is its "visible bounds" from the + user's perspective. Client-side decorations often have invisible + portions like drop-shadows which should be ignored for the + purposes of aligning, placing and constraining windows. + + The window geometry is double-buffered state, see wl_surface.commit. + + When maintaining a position, the compositor should treat the (x, y) + coordinate of the window geometry as the top left corner of the window. + A client changing the (x, y) window geometry coordinate should in + general not alter the position of the window. + + Once the window geometry of the surface is set, it is not possible to + unset it, and it will remain the same until set_window_geometry is + called again, even if a new subsurface or buffer is attached. + + If never set, the value is the full bounds of the surface, + including any subsurfaces. This updates dynamically on every + commit. This unset is meant for extremely simple clients. + + The arguments are given in the surface-local coordinate space of + the wl_surface associated with this xdg_surface, and may extend outside + of the wl_surface itself to mark parts of the subsurface tree as part of + the window geometry. + + When applied, the effective window geometry will be the set window + geometry clamped to the bounding rectangle of the combined + geometry of the surface of the xdg_surface and the associated + subsurfaces. + + The effective geometry will not be recalculated unless a new call to + set_window_geometry is done and the new pending surface state is + subsequently applied. + + The width and height of the effective window geometry must be + greater than zero. Setting an invalid size will raise an + invalid_size error. + </description> + <arg name="x" type="int"/> + <arg name="y" type="int"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <request name="ack_configure"> + <description summary="ack a configure event"> + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + For instance, for toplevel surfaces the compositor might use this + information to move a surface to the top left only when the client has + drawn itself for the maximized or fullscreen state. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + Acking a configure event that was never sent raises an invalid_serial + error. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + Sending an ack_configure request consumes the serial number sent with + the request, as well as serial numbers sent by all configure events + sent on this xdg_surface prior to the configure event referenced by + the committed serial. + + It is an error to issue multiple ack_configure requests referencing a + serial from the same configure event, or to issue an ack_configure + request referencing a serial from a configure event issued before the + event identified by the last ack_configure request for the same + xdg_surface. Doing so will raise an invalid_serial error. + </description> + <arg name="serial" type="uint" summary="the serial from the configure event"/> + </request> + + <event name="configure"> + <description summary="suggest a surface change"> + The configure event marks the end of a configure sequence. A configure + sequence is a set of one or more events configuring the state of the + xdg_surface, including the final xdg_surface.configure event. + + Where applicable, xdg_surface surface roles will during a configure + sequence extend this event as a latched state sent as events before the + xdg_surface.configure event. Such events should be considered to make up + a set of atomically applied configuration states, where the + xdg_surface.configure commits the accumulated state. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + If the client receives multiple configure events before it can respond + to one, it is free to discard all but the last event it received. + </description> + <arg name="serial" type="uint" summary="serial of the configure event"/> + </event> + + </interface> + + <interface name="xdg_toplevel" version="7"> + <description summary="toplevel surface"> + This interface defines an xdg_surface role which allows a surface to, + among other things, set window-like properties such as maximize, + fullscreen, and minimize, set application-specific metadata like title and + id, and well as trigger user interactive operations such as interactive + resize and move. + + A xdg_toplevel by default is responsible for providing the full intended + visual representation of the toplevel, which depending on the window + state, may mean things like a title bar, window controls and drop shadow. + + Unmapping an xdg_toplevel means that the surface cannot be shown + by the compositor until it is explicitly mapped again. + All active operations (e.g., move, resize) are canceled and all + attributes (e.g. title, state, stacking, ...) are discarded for + an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + the state it had right after xdg_surface.get_toplevel. The client + can re-map the toplevel by performing a commit without any buffer + attached, waiting for a configure event and handling it as usual (see + xdg_surface description). + + Attaching a null buffer to a toplevel unmaps the surface. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the xdg_toplevel"> + This request destroys the role surface and unmaps the surface; + see "Unmapping" behavior in interface section for details. + </description> + </request> + + <enum name="error"> + <entry name="invalid_resize_edge" value="0" summary="provided value is + not a valid variant of the resize_edge enum"/> + <entry name="invalid_parent" value="1" + summary="invalid parent toplevel"/> + <entry name="invalid_size" value="2" + summary="client provided an invalid min or max size"/> + </enum> + + <request name="set_parent"> + <description summary="set the parent of this surface"> + Set the "parent" of this surface. This surface should be stacked + above the parent surface and all other ancestor surfaces. + + Parent surfaces should be set on dialogs, toolboxes, or other + "auxiliary" surfaces, so that the parent is raised when the dialog + is raised. + + Setting a null parent for a child surface unsets its parent. Setting + a null parent for a surface which currently has no parent is a no-op. + + Only mapped surfaces can have child surfaces. Setting a parent which + is not mapped is equivalent to setting a null parent. If a surface + becomes unmapped, its children's parent is set to the parent of + the now-unmapped surface. If the now-unmapped surface has no parent, + its children's parent is unset. If the now-unmapped surface becomes + mapped again, its parent-child relationship is not restored. + + The parent toplevel must not be one of the child toplevel's + descendants, and the parent must be different from the child toplevel, + otherwise the invalid_parent protocol error is raised. + </description> + <arg name="parent" type="object" interface="xdg_toplevel" allow-null="true"/> + </request> + + <request name="set_title"> + <description summary="set surface title"> + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + </description> + <arg name="title" type="string"/> + </request> + + <request name="set_app_id"> + <description summary="set application ID"> + Set an application identifier for the surface. + + The app ID identifies the general class of applications to which + the surface belongs. The compositor can use this to group multiple + surfaces together, or to determine how to launch a new application. + + For D-Bus activatable applications, the app ID is used as the D-Bus + service name. + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + + Like other properties, a set_app_id request can be sent after the + xdg_toplevel has been mapped to update the property. + + See the desktop-entry specification [0] for more details on + application identifiers and how they relate to well-known D-Bus + names and .desktop files. + + [0] https://standards.freedesktop.org/desktop-entry-spec/ + </description> + <arg name="app_id" type="string"/> + </request> + + <request name="show_window_menu"> + <description summary="show the window menu"> + Clients implementing client-side decorations might want to show + a context menu when right-clicking on the decorations, giving the + user a menu that they can use to maximize or minimize the window. + + This request asks the compositor to pop up such a window menu at + the given position, relative to the local surface coordinates of + the parent surface. There are no guarantees as to what menu items + the window menu contains, or even if a window menu will be drawn + at all. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + </description> + <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/> + <arg name="serial" type="uint" summary="the serial of the user event"/> + <arg name="x" type="int" summary="the x position to pop up the window menu at"/> + <arg name="y" type="int" summary="the y position to pop up the window menu at"/> + </request> + + <request name="move"> + <description summary="start an interactive move"> + Start an interactive, user-driven move of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive move (touch, + pointer, etc). + + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized), or if the passed serial + is no longer valid. + + If triggered, the surface will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the move. It is up to the + compositor to visually indicate that the move is taking place, such as + updating a pointer cursor, during the move. There is no guarantee + that the device focus will return when the move is completed. + </description> + <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/> + <arg name="serial" type="uint" summary="the serial of the user event"/> + </request> + + <enum name="resize_edge"> + <description summary="edge values for resizing"> + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + </description> + <entry name="none" value="0"/> + <entry name="top" value="1"/> + <entry name="bottom" value="2"/> + <entry name="left" value="4"/> + <entry name="top_left" value="5"/> + <entry name="bottom_left" value="6"/> + <entry name="right" value="8"/> + <entry name="top_right" value="9"/> + <entry name="bottom_right" value="10"/> + </enum> + + <request name="resize"> + <description summary="start an interactive resize"> + Start a user-driven, interactive resize of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive resize (touch, + pointer, etc). + + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + If triggered, the client will receive configure events with the + "resize" state enum value and the expected sizes. See the "resize" + enum value for more details about what is required. The client + must also acknowledge configure events using "ack_configure". After + the resize is completed, the client will receive another "configure" + event without the resize state. + + If triggered, the surface also will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the resize. It is up to the + compositor to visually indicate that the resize is taking place, + such as updating a pointer cursor, during the resize. There is no + guarantee that the device focus will return when the resize is + completed. + + The edges parameter specifies how the surface should be resized, and + is one of the values of the resize_edge enum. Values not matching + a variant of the enum will cause the invalid_resize_edge protocol error. + The compositor may use this information to update the surface position + for example when dragging the top left corner. The compositor may also + use this information to adapt its behavior, e.g. choose an appropriate + cursor image. + </description> + <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/> + <arg name="serial" type="uint" summary="the serial of the user event"/> + <arg name="edges" type="uint" enum="resize_edge" summary="which edge or corner is being dragged"/> + </request> + + <enum name="state"> + <description summary="types of state on the surface"> + The different state values used on the surface. This is designed for + state values like maximized, fullscreen. It is paired with the + configure event to ensure that both the client and the compositor + setting the state can be synchronized. + + States set in this way are double-buffered, see wl_surface.commit. + </description> + <entry name="maximized" value="1" summary="the surface is maximized"> + <description summary="the surface is maximized"> + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client, or the xdg_wm_base.invalid_surface_state + error is raised. + + The client should draw without shadow or other + decoration outside of the window geometry. + </description> + </entry> + <entry name="fullscreen" value="2" summary="the surface is fullscreen"> + <description summary="the surface is fullscreen"> + The surface is fullscreen. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. For + a surface to cover the whole fullscreened area, the geometry + dimensions must be obeyed by the client. For more details, see + xdg_toplevel.set_fullscreen. + </description> + </entry> + <entry name="resizing" value="3" summary="the surface is being resized"> + <description summary="the surface is being resized"> + The surface is being resized. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. + Clients that have aspect ratio or cell sizing configuration can use + a smaller size, however. + </description> + </entry> + <entry name="activated" value="4" summary="the surface is now activated"> + <description summary="the surface is now activated"> + Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. + </description> + </entry> + <entry name="tiled_left" value="5" since="2"> + <description summary="the surface’s left edge is tiled"> + The window is currently in a tiled layout and the left edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the left edge. + </description> + </entry> + <entry name="tiled_right" value="6" since="2"> + <description summary="the surface’s right edge is tiled"> + The window is currently in a tiled layout and the right edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the right edge. + </description> + </entry> + <entry name="tiled_top" value="7" since="2"> + <description summary="the surface’s top edge is tiled"> + The window is currently in a tiled layout and the top edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the top edge. + </description> + </entry> + <entry name="tiled_bottom" value="8" since="2"> + <description summary="the surface’s bottom edge is tiled"> + The window is currently in a tiled layout and the bottom edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the bottom edge. + </description> + </entry> + <entry name="suspended" value="9" since="6"> + <description summary="surface repaint is suspended"> + The surface is currently not ordinarily being repainted; for + example because its content is occluded by another window, or its + outputs are switched off due to screen locking. + </description> + </entry> + <entry name="constrained_left" value="10" since="7"> + <description summary="the surface’s left edge is constrained"> + The left edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + </description> + </entry> + <entry name="constrained_right" value="11" since="7"> + <description summary="the surface’s right edge is constrained"> + The right edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + </description> + </entry> + <entry name="constrained_top" value="12" since="7"> + <description summary="the surface’s top edge is constrained"> + The top edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + </description> + </entry> + <entry name="constrained_bottom" value="13" since="7"> + <description summary="the surface’s bottom edge is tiled"> + The bottom edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + </description> + </entry> + </enum> + + <request name="set_max_size"> + <description summary="set the maximum size"> + Set a maximum size for the window. + + The client can specify a maximum size so that the compositor does + not try to configure the window beyond this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered, see wl_surface.commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the maximum + size. The compositor may decide to ignore the values set by the + client and request a larger size. + + If never set, or a value of zero in the request, means that the + client has no expected maximum size in the given dimension. + As a result, a client wishing to reset the maximum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a maximum size to be smaller than the minimum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width or height will result in a + invalid_size error. + </description> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <request name="set_min_size"> + <description summary="set the minimum size"> + Set a minimum size for the window. + + The client can specify a minimum size so that the compositor does + not try to configure the window below this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered, see wl_surface.commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the minimum + size. The compositor may decide to ignore the values set by the + client and request a smaller size. + + If never set, or a value of zero in the request, means that the + client has no expected minimum size in the given dimension. + As a result, a client wishing to reset the minimum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a minimum size to be larger than the maximum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + invalid_size error. + </description> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <request name="set_maximized"> + <description summary="maximize the window"> + Maximize the surface. + + After requesting that the surface should be maximized, the compositor + will respond by emitting a configure event. Whether this configure + actually sets the window maximized is subject to compositor policies. + The client must then update its content, drawing in the configured + state. The client must also acknowledge the configure when committing + the new content (see ack_configure). + + It is up to the compositor to decide how and where to maximize the + surface, for example which output and what region of the screen should + be used. + + If the surface was already maximized, the compositor will still emit + a configure event with the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + </description> + </request> + + <request name="unset_maximized"> + <description summary="unmaximize the window"> + Unmaximize the surface. + + After requesting that the surface should be unmaximized, the compositor + will respond by emitting a configure event. Whether this actually + un-maximizes the window is subject to compositor policies. + If available and applicable, the compositor will include the window + geometry dimensions the window had prior to being maximized in the + configure event. The client must then update its content, drawing it in + the configured state. The client must also acknowledge the configure + when committing the new content (see ack_configure). + + It is up to the compositor to position the surface after it was + unmaximized; usually the position the surface had before maximizing, if + applicable. + + If the surface was already not maximized, the compositor will still + emit a configure event without the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + </description> + </request> + + <request name="set_fullscreen"> + <description summary="set the window as fullscreen on an output"> + Make the surface fullscreen. + + After requesting that the surface should be fullscreened, the + compositor will respond by emitting a configure event. Whether the + client is actually put into a fullscreen state is subject to compositor + policies. The client must also acknowledge the configure when + committing the new content (see ack_configure). + + The output passed by the request indicates the client's preference as + to which display it should be set fullscreen on. If this value is NULL, + it's up to the compositor to choose which display will be used to map + this surface. + + If the surface doesn't cover the whole output, the compositor will + position the surface in the center of the output and compensate with + with border fill covering the rest of the output. The content of the + border fill is undefined, but should be assumed to be in some way that + attempts to blend into the surrounding area (e.g. solid black). + + If the fullscreened surface is not opaque, the compositor must make + sure that other screen content not part of the same surface tree (made + up of subsurfaces, popups or similarly coupled surfaces) are not + visible below the fullscreened surface. + </description> + <arg name="output" type="object" interface="wl_output" allow-null="true"/> + </request> + + <request name="unset_fullscreen"> + <description summary="unset the window as fullscreen"> + Make the surface no longer fullscreen. + + After requesting that the surface should be unfullscreened, the + compositor will respond by emitting a configure event. + Whether this actually removes the fullscreen state of the client is + subject to compositor policies. + + Making a surface unfullscreen sets states for the surface based on the following: + * the state(s) it may have had before becoming fullscreen + * any state(s) decided by the compositor + * any state(s) requested by the client while the surface was fullscreen + + The compositor may include the previous window geometry dimensions in + the configure event, if applicable. + + The client must also acknowledge the configure when committing the new + content (see ack_configure). + </description> + </request> + + <request name="set_minimized"> + <description summary="set the window as minimized"> + Request that the compositor minimize your surface. There is no + way to know if the surface is currently minimized, nor is there + any way to unset minimization on this surface. + + If you are looking to throttle redrawing when minimized, please + instead use the wl_surface.frame event for this, as this will + also work with live previews on windows in Alt-Tab, Expose or + similar compositor features. + </description> + </request> + + <event name="configure"> + <description summary="suggest a surface change"> + This configure event asks the client to resize its toplevel surface or + to change its state. The configured state should not be applied + immediately. See xdg_surface.configure for details. + + The width and height arguments specify a hint to the window + about how its surface should be resized in window geometry + coordinates. See set_window_geometry. + + If the width or height arguments are zero, it means the client + should decide its own window dimension. This may happen when the + compositor needs to configure the state of the surface but doesn't + have any information about any previous or expected dimension. + + The states listed in the event specify how the width/height + arguments should be interpreted, and possibly how it should be + drawn. + + Clients must send an ack_configure in response to this event. See + xdg_surface.configure and xdg_surface.ack_configure for details. + </description> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + <arg name="states" type="array"/> + </event> + + <event name="close"> + <description summary="surface wants to be closed"> + The close event is sent by the compositor when the user + wants the surface to be closed. This should be equivalent to + the user clicking the close button in client-side decorations, + if your application has any. + + This is only a request that the user intends to close the + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data, etc. + </description> + </event> + + <!-- Version 4 additions --> + + <event name="configure_bounds" since="4"> + <description summary="recommended window geometry bounds"> + The configure_bounds event may be sent prior to a xdg_toplevel.configure + event to communicate the bounds a window geometry size is recommended + to constrain to. + + The passed width and height are in surface coordinate space. If width + and height are 0, it means bounds is unknown and equivalent to as if no + configure_bounds event was ever sent for this surface. + + The bounds can for example correspond to the size of a monitor excluding + any panels or other shell components, so that a surface isn't created in + a way that it cannot fit. + + The bounds may change at any point, and in such a case, a new + xdg_toplevel.configure_bounds will be sent, followed by + xdg_toplevel.configure and xdg_surface.configure. + </description> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </event> + + <!-- Version 5 additions --> + + <enum name="wm_capabilities" since="5"> + <entry name="window_menu" value="1" summary="show_window_menu is available"/> + <entry name="maximize" value="2" summary="set_maximized and unset_maximized are available"/> + <entry name="fullscreen" value="3" summary="set_fullscreen and unset_fullscreen are available"/> + <entry name="minimize" value="4" summary="set_minimized is available"/> + </enum> + + <event name="wm_capabilities" since="5"> + <description summary="compositor capabilities"> + This event advertises the capabilities supported by the compositor. If + a capability isn't supported, clients should hide or disable the UI + elements that expose this functionality. For instance, if the + compositor doesn't advertise support for minimized toplevels, a button + triggering the set_minimized request should not be displayed. + + The compositor will ignore requests it doesn't support. For instance, + a compositor which doesn't advertise support for minimized will ignore + set_minimized requests. + + Compositors must send this event once before the first + xdg_surface.configure event. When the capabilities change, compositors + must send this event again and then send an xdg_surface.configure + event. + + The configured state should not be applied immediately. See + xdg_surface.configure for details. + + The capabilities are sent as an array of 32-bit unsigned integers in + native endianness. + </description> + <arg name="capabilities" type="array" summary="array of 32-bit capabilities"/> + </event> + </interface> + + <interface name="xdg_popup" version="7"> + <description summary="short-lived, popup surfaces for menus"> + A popup surface is a short-lived, temporary surface. It can be used to + implement for example menus, popovers, tooltips and other similar user + interface concepts. + + A popup can be made to take an explicit grab. See xdg_popup.grab for + details. + + When the popup is dismissed, a popup_done event will be sent out, and at + the same time the surface will be unmapped. See the xdg_popup.popup_done + event for details. + + Explicitly destroying the xdg_popup object will also dismiss the popup and + unmap the surface. Clients that want to dismiss the popup when another + surface of their own is clicked should dismiss the popup using the destroy + request. + + A newly created xdg_popup will be stacked on top of all previously created + xdg_popup surfaces associated with the same xdg_toplevel. + + The parent of an xdg_popup must be mapped (see the xdg_surface + description) before the xdg_popup itself. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_popup state to take effect. + </description> + + <enum name="error"> + <entry name="invalid_grab" value="0" + summary="tried to grab after being mapped"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="remove xdg_popup interface"> + This destroys the popup. Explicitly destroying the xdg_popup + object will also dismiss the popup, and unmap the surface. + + If this xdg_popup is not the "topmost" popup, the + xdg_wm_base.not_the_topmost_popup protocol error will be sent. + </description> + </request> + + <request name="grab"> + <description summary="make the popup take an explicit grab"> + This request makes the created popup take an explicit grab. An explicit + grab will be dismissed when the user dismisses the popup, or when the + client destroys the xdg_popup. This can be done by the user clicking + outside the surface, using the keyboard, or even locking the screen + through closing the lid or a timeout. + + If the compositor denies the grab, the popup will be immediately + dismissed. + + This request must be used in response to some sort of user action like a + button press, key press, or touch down event. The serial number of the + event should be passed as 'serial'. + + The parent of a grabbing popup must either be an xdg_toplevel surface or + another xdg_popup with an explicit grab. If the parent is another + xdg_popup it means that the popups are nested, with this popup now being + the topmost popup. + + Nested popups must be destroyed in the reverse order they were created + in, e.g. the only popup you are allowed to destroy at all times is the + topmost one. + + When compositors choose to dismiss a popup, they may dismiss every + nested grabbing popup as well. When a compositor dismisses popups, it + will follow the same dismissing order as required from the client. + + If the topmost grabbing popup is destroyed, the grab will be returned to + the parent of the popup, if that parent previously had an explicit grab. + + If the parent is a grabbing popup which has already been dismissed, this + popup will be immediately dismissed. If the parent is a popup that did + not take an explicit grab, an error will be raised. + + During a popup grab, the client owning the grab will receive pointer + and touch events for all their surfaces as normal (similar to an + "owner-events" grab in X11 parlance), while the top most grabbing popup + will always have keyboard focus. + </description> + <arg name="seat" type="object" interface="wl_seat" + summary="the wl_seat of the user event"/> + <arg name="serial" type="uint" summary="the serial of the user event"/> + </request> + + <event name="configure"> + <description summary="configure the popup surface"> + This event asks the popup surface to configure itself given the + configuration. The configured state should not be applied immediately. + See xdg_surface.configure for details. + + The x and y arguments represent the position the popup was placed at + given the xdg_positioner rule, relative to the upper left corner of the + window geometry of the parent surface. + + For version 2 or older, the configure event for an xdg_popup is only + ever sent once for the initial configuration. Starting with version 3, + it may be sent again if the popup is setup with an xdg_positioner with + set_reactive requested, or in response to xdg_popup.reposition requests. + </description> + <arg name="x" type="int" + summary="x position relative to parent surface window geometry"/> + <arg name="y" type="int" + summary="y position relative to parent surface window geometry"/> + <arg name="width" type="int" summary="window geometry width"/> + <arg name="height" type="int" summary="window geometry height"/> + </event> + + <event name="popup_done"> + <description summary="popup interaction is done"> + The popup_done event is sent out when a popup is dismissed by the + compositor. The client should destroy the xdg_popup object at this + point. + </description> + </event> + + <!-- Version 3 additions --> + + <request name="reposition" since="3"> + <description summary="recalculate the popup's location"> + Reposition an already-mapped popup. The popup will be placed given the + details in the passed xdg_positioner object, and a + xdg_popup.repositioned followed by xdg_popup.configure and + xdg_surface.configure will be emitted in response. Any parameters set + by the previous positioner will be discarded. + + The passed token will be sent in the corresponding + xdg_popup.repositioned event. The new popup position will not take + effect until the corresponding configure event is acknowledged by the + client. See xdg_popup.repositioned for details. The token itself is + opaque, and has no other special meaning. + + If multiple reposition requests are sent, the compositor may skip all + but the last one. + + If the popup is repositioned in response to a configure event for its + parent, the client should send an xdg_positioner.set_parent_configure + and possibly an xdg_positioner.set_parent_size request to allow the + compositor to properly constrain the popup. + + If the popup is repositioned together with a parent that is being + resized, but not in response to a configure event, the client should + send an xdg_positioner.set_parent_size request. + </description> + <arg name="positioner" type="object" interface="xdg_positioner"/> + <arg name="token" type="uint" summary="reposition request token"/> + </request> + + <event name="repositioned" since="3"> + <description summary="signal the completion of a repositioned request"> + The repositioned event is sent as part of a popup configuration + sequence, together with xdg_popup.configure and lastly + xdg_surface.configure to notify the completion of a reposition request. + + The repositioned event is to notify about the completion of a + xdg_popup.reposition request. The token argument is the token passed + in the xdg_popup.reposition request. + + Immediately after this event is emitted, xdg_popup.configure and + xdg_surface.configure will be sent with the updated size and position, + as well as a new configure serial. + + The client should optionally update the content of the popup, but must + acknowledge the new popup configuration for the new position to take + effect. See xdg_surface.ack_configure for details. + </description> + <arg name="token" type="uint" summary="reposition request token"/> + </event> + + </interface> +</protocol> @@ -1,6 +1,7 @@ #include "../include/mgl/gl.h" #include <dlfcn.h> #include <stdio.h> +#include <string.h> typedef struct { void **func; @@ -18,110 +19,200 @@ static void* dlsym_print_fail(void *handle, const char *name, int required) { return sym; } -int mgl_gl_load(mgl_gl *self) { - const char *glx_path = "libGL.so.1"; - self->handle = dlopen(glx_path, RTLD_LAZY); - if(!self->handle) { - fprintf(stderr, "mgl error:dlopen(\"%s\", RTLD_LAZY) failed\n", glx_path); - return -1; - } - - dlsym_assign required_dlsym[] = { - { &self->glXCreateNewContext, "glXCreateNewContext" }, - { &self->glXMakeContextCurrent, "glXMakeContextCurrent" }, - { &self->glXDestroyContext, "glXDestroyContext" }, - { &self->glXSwapBuffers, "glXSwapBuffers" }, - { &self->glXChooseFBConfig, "glXChooseFBConfig" }, - { &self->glXGetVisualFromFBConfig, "glXGetVisualFromFBConfig" }, - - { &self->glViewport, "glViewport" }, - { &self->glScissor, "glScissor" }, - { &self->glClearColor, "glClearColor" }, - { &self->glClear, "glClear" }, - { &self->glEnable, "glEnable" }, - { &self->glBlendFunc, "glBlendFunc" }, - { &self->glGenTextures, "glGenTextures" }, - { &self->glDeleteTextures, "glDeleteTextures" }, - { &self->glTexImage2D, "glTexImage2D" }, - { &self->glTexSubImage2D, "glTexSubImage2D" }, - { &self->glBindTexture, "glBindTexture" }, - { &self->glTexParameteri, "glTexParameteri" }, - { &self->glHint, "glHint" }, - { &self->glBegin, "glBegin" }, - { &self->glEnd, "glEnd" }, - { &self->glVertex3f, "glVertex3f" }, - { &self->glColor4ub, "glColor4ub" }, - { &self->glTexCoord2f, "glTexCoord2f" }, - { &self->glOrtho, "glOrtho" }, - { &self->glMatrixMode, "glMatrixMode" }, - { &self->glPushMatrix, "glPushMatrix" }, - { &self->glPopMatrix, "glPopMatrix" }, - { &self->glLoadIdentity, "glLoadIdentity" }, - { &self->glLoadMatrixf, "glLoadMatrixf" }, - { &self->glTranslatef, "glTranslatef" }, - { &self->glRotatef, "glRotatef" }, - { &self->glGenBuffers, "glGenBuffers" }, - { &self->glBindBuffer, "glBindBuffer" }, - { &self->glDeleteBuffers, "glDeleteBuffers" }, - { &self->glBufferData, "glBufferData" }, - { &self->glBufferSubData, "glBufferSubData" }, - { &self->glDrawArrays, "glDrawArrays" }, - { &self->glEnableClientState, "glEnableClientState" }, - { &self->glVertexPointer, "glVertexPointer" }, - { &self->glColorPointer, "glColorPointer" }, - { &self->glTexCoordPointer, "glTexCoordPointer" }, - { &self->glCompileShader, "glCompileShader" }, - { &self->glCreateProgram, "glCreateProgram" }, - { &self->glCreateShader, "glCreateShader" }, - { &self->glDeleteProgram, "glDeleteProgram" }, - { &self->glDeleteShader, "glDeleteShader" }, - { &self->glGetShaderiv, "glGetShaderiv" }, - { &self->glGetShaderInfoLog, "glGetShaderInfoLog" }, - { &self->glGetProgramiv, "glGetProgramiv" }, - { &self->glGetProgramInfoLog, "glGetProgramInfoLog" }, - { &self->glLinkProgram, "glLinkProgram" }, - { &self->glShaderSource, "glShaderSource" }, - { &self->glUseProgram, "glUseProgram" }, - { &self->glAttachShader, "glAttachShader" }, - { &self->glGetUniformLocation, "glGetUniformLocation" }, - { &self->glUniform1f, "glUniform1f" }, - { &self->glUniform2f, "glUniform2f" }, - { &self->glGetError, "glGetError" }, - { &self->glGetString, "glGetString" }, - { &self->glGetIntegerv, "glGetIntegerv" }, - { &self->glPixelStorei, "glPixelStorei" }, - { &self->glFlush, "glFlush" }, - { &self->glFinish, "glFinish" }, +static int mgl_gl_load_gl(mgl_gl *self) { + const dlsym_assign required_dlsym[] = { + { (void**)&self->glViewport, "glViewport" }, + { (void**)&self->glScissor, "glScissor" }, + { (void**)&self->glClearColor, "glClearColor" }, + { (void**)&self->glClear, "glClear" }, + { (void**)&self->glEnable, "glEnable" }, + { (void**)&self->glBlendFunc, "glBlendFunc" }, + { (void**)&self->glBlendFuncSeparate, "glBlendFuncSeparate" }, + { (void**)&self->glGenTextures, "glGenTextures" }, + { (void**)&self->glDeleteTextures, "glDeleteTextures" }, + { (void**)&self->glGetTexLevelParameteriv, "glGetTexLevelParameteriv" }, + { (void**)&self->glTexImage2D, "glTexImage2D" }, + { (void**)&self->glTexSubImage2D, "glTexSubImage2D" }, + { (void**)&self->glBindTexture, "glBindTexture" }, + { (void**)&self->glTexParameteri, "glTexParameteri" }, + { (void**)&self->glBegin, "glBegin" }, + { (void**)&self->glEnd, "glEnd" }, + { (void**)&self->glVertex3f, "glVertex3f" }, + { (void**)&self->glColor4ub, "glColor4ub" }, + { (void**)&self->glTexCoord2f, "glTexCoord2f" }, + { (void**)&self->glOrtho, "glOrtho" }, + { (void**)&self->glMatrixMode, "glMatrixMode" }, + { (void**)&self->glPushMatrix, "glPushMatrix" }, + { (void**)&self->glPopMatrix, "glPopMatrix" }, + { (void**)&self->glLoadIdentity, "glLoadIdentity" }, + { (void**)&self->glLoadMatrixf, "glLoadMatrixf" }, + { (void**)&self->glTranslatef, "glTranslatef" }, + { (void**)&self->glRotatef, "glRotatef" }, + { (void**)&self->glGenBuffers, "glGenBuffers" }, + { (void**)&self->glBindBuffer, "glBindBuffer" }, + { (void**)&self->glDeleteBuffers, "glDeleteBuffers" }, + { (void**)&self->glBufferData, "glBufferData" }, + { (void**)&self->glBufferSubData, "glBufferSubData" }, + { (void**)&self->glDrawArrays, "glDrawArrays" }, + { (void**)&self->glEnableClientState, "glEnableClientState" }, + { (void**)&self->glVertexPointer, "glVertexPointer" }, + { (void**)&self->glColorPointer, "glColorPointer" }, + { (void**)&self->glTexCoordPointer, "glTexCoordPointer" }, + { (void**)&self->glCompileShader, "glCompileShader" }, + { (void**)&self->glCreateProgram, "glCreateProgram" }, + { (void**)&self->glCreateShader, "glCreateShader" }, + { (void**)&self->glDeleteProgram, "glDeleteProgram" }, + { (void**)&self->glDeleteShader, "glDeleteShader" }, + { (void**)&self->glGetShaderiv, "glGetShaderiv" }, + { (void**)&self->glGetShaderInfoLog, "glGetShaderInfoLog" }, + { (void**)&self->glGetProgramiv, "glGetProgramiv" }, + { (void**)&self->glGetProgramInfoLog, "glGetProgramInfoLog" }, + { (void**)&self->glLinkProgram, "glLinkProgram" }, + { (void**)&self->glShaderSource, "glShaderSource" }, + { (void**)&self->glUseProgram, "glUseProgram" }, + { (void**)&self->glAttachShader, "glAttachShader" }, + { (void**)&self->glGetUniformLocation, "glGetUniformLocation" }, + { (void**)&self->glUniform1f, "glUniform1f" }, + { (void**)&self->glUniform2f, "glUniform2f" }, + { (void**)&self->glUniform3f, "glUniform3f" }, + { (void**)&self->glUniform4f, "glUniform4f" }, + { (void**)&self->glGetError, "glGetError" }, + { (void**)&self->glGetString, "glGetString" }, + { (void**)&self->glGetIntegerv, "glGetIntegerv" }, + { (void**)&self->glPixelStorei, "glPixelStorei" }, + { (void**)&self->glFlush, "glFlush" }, + { (void**)&self->glFinish, "glFinish" }, { NULL, NULL } }; for(int i = 0; required_dlsym[i].func; ++i) { - *required_dlsym[i].func = dlsym_print_fail(self->handle, required_dlsym[i].name, 1); - if(!*required_dlsym[i].func) { - mgl_gl_unload(self); + *required_dlsym[i].func = dlsym_print_fail(self->gl_library, required_dlsym[i].name, 1); + if(!*required_dlsym[i].func) return -1; - } } const dlsym_assign optional_dlsym[] = { - { &self->glXSwapIntervalEXT, "glXSwapIntervalEXT" }, - { &self->glXSwapIntervalMESA, "glXGetSwapIntervalMESA" }, - { &self->glXSwapIntervalSGI, "glXSwapIntervalSGI" }, + { (void**)&self->glGenerateMipmap, "glGenerateMipmap" }, { NULL, NULL } }; for(int i = 0; optional_dlsym[i].func; ++i) { - *optional_dlsym[i].func = dlsym_print_fail(self->handle, optional_dlsym[i].name, 0); + *optional_dlsym[i].func = dlsym_print_fail(self->gl_library, optional_dlsym[i].name, 0); + } + + return 0; +} + +static int mgl_gl_load_glx(mgl_gl *self) { + const dlsym_assign required_dlsym[] = { + { (void**)&self->glXGetProcAddress, "glXGetProcAddress" }, + { (void**)&self->glXCreateNewContext, "glXCreateNewContext" }, + { (void**)&self->glXMakeContextCurrent, "glXMakeContextCurrent" }, + { (void**)&self->glXDestroyContext, "glXDestroyContext" }, + { (void**)&self->glXSwapBuffers, "glXSwapBuffers" }, + { (void**)&self->glXChooseFBConfig, "glXChooseFBConfig" }, + { (void**)&self->glXGetVisualFromFBConfig, "glXGetVisualFromFBConfig" }, + + { NULL, NULL } + }; + + /* In some distros (alpine for example libGLX doesn't exist, but libGL can be used instead) */ + void *library = self->glx_library ? self->glx_library : self->gl_library; + + for(int i = 0; required_dlsym[i].func; ++i) { + *required_dlsym[i].func = dlsym_print_fail(library, required_dlsym[i].name, 1); + if(!*required_dlsym[i].func) + return -1; + } + + self->glXSwapIntervalEXT = (FUNC_glXSwapIntervalEXT)self->glXGetProcAddress((const unsigned char*)"glXSwapIntervalEXT"); + self->glXSwapIntervalMESA = (FUNC_glXSwapIntervalMESA)self->glXGetProcAddress((const unsigned char*)"glXSwapIntervalMESA"); + self->glXSwapIntervalSGI = (FUNC_glXSwapIntervalSGI)self->glXGetProcAddress((const unsigned char*)"glXSwapIntervalSGI"); + + return 0; +} + +static int mgl_gl_load_egl(mgl_gl *self) { + const dlsym_assign required_dlsym[] = { + { (void**)&self->eglGetProcAddress, "eglGetProcAddress" }, + { (void**)&self->eglGetDisplay, "eglGetDisplay" }, + { (void**)&self->eglInitialize, "eglInitialize" }, + { (void**)&self->eglTerminate, "eglTerminate" }, + { (void**)&self->eglGetConfigs, "eglGetConfigs" }, + { (void**)&self->eglCreateWindowSurface, "eglCreateWindowSurface" }, + { (void**)&self->eglCreateContext, "eglCreateContext" }, + { (void**)&self->eglMakeCurrent, "eglMakeCurrent" }, + { (void**)&self->eglDestroyContext, "eglDestroyContext" }, + { (void**)&self->eglDestroySurface, "eglDestroySurface" }, + { (void**)&self->eglSwapInterval, "eglSwapInterval" }, + { (void**)&self->eglSwapBuffers, "eglSwapBuffers" }, + { (void**)&self->eglBindAPI, "eglBindAPI" }, + { (void**)&self->eglGetConfigAttrib, "eglGetConfigAttrib" }, + + { NULL, NULL } + }; + + for(int i = 0; required_dlsym[i].func; ++i) { + *required_dlsym[i].func = dlsym_print_fail(self->egl_library, required_dlsym[i].name, 1); + if(!*required_dlsym[i].func) + return -1; + } + + return 0; +} + +int mgl_gl_load(mgl_gl *self) { + memset(self, 0, sizeof(*self)); + + self->gl_library = dlopen("libGL.so.1", RTLD_LAZY); + if(!self->gl_library) { + fprintf(stderr, "mgl error:dlopen(\"%s\", RTLD_LAZY) failed\n", "libGL.so.1"); + mgl_gl_unload(self); + return -1; + } + + self->glx_library = dlopen("libGLX.so.0", RTLD_LAZY); + + self->egl_library = dlopen("libEGL.so.1", RTLD_LAZY); + if(!self->egl_library) { + fprintf(stderr, "mgl error:dlopen(\"%s\", RTLD_LAZY) failed\n", "libEGL.so.1"); + mgl_gl_unload(self); + return -1; + } + + if(mgl_gl_load_gl(self) != 0) { + mgl_gl_unload(self); + return -1; + } + + if(mgl_gl_load_glx(self) != 0) { + mgl_gl_unload(self); + return -1; + } + + if(mgl_gl_load_egl(self) != 0) { + mgl_gl_unload(self); + return -1; } return 0; } void mgl_gl_unload(mgl_gl *self) { - if(self->handle) { - dlclose(self->handle); - self->handle = NULL; + if(self->egl_library) { + dlclose(self->egl_library); + self->egl_library = NULL; + } + + if(self->glx_library) { + dlclose(self->glx_library); + self->glx_library = NULL; + } + + if(self->gl_library) { + dlclose(self->gl_library); + self->gl_library = NULL; } } diff --git a/src/graphics/backend/egl.c b/src/graphics/backend/egl.c new file mode 100644 index 0000000..cabbf04 --- /dev/null +++ b/src/graphics/backend/egl.c @@ -0,0 +1,248 @@ +#include "../../../include/mgl/graphics/backend/egl.h" +#include "../../../include/mgl/mgl.h" + +#include <stdlib.h> +#include <stdio.h> + +#include <X11/Xutil.h> +#include <X11/extensions/Xrender.h> + +static void mgl_graphics_egl_deinit(mgl_graphics *self); + +typedef struct { + EGLDisplay display; + EGLSurface surface; + EGLContext context; + EGLConfig *configs; + EGLConfig ecfg; + XVisualInfo *visual_info; +} mgl_graphics_egl; + +static int32_t mgl_graphics_egl_get_config_attrib(mgl_graphics_egl *self, EGLConfig ecfg, int32_t attribute_name) { + mgl_context *context = mgl_get_context(); + int32_t value = 0; + context->gl.eglGetConfigAttrib(self->display, ecfg, attribute_name, &value); + return value; +} + +static bool xvisual_match_alpha(Display *dpy, XVisualInfo *visual_info, bool alpha) { + XRenderPictFormat *pict_format = XRenderFindVisualFormat(dpy, visual_info->visual); + if(!pict_format) + return false; + return (alpha && pict_format->direct.alphaMask > 0) || (!alpha && pict_format->direct.alphaMask == 0); +} + +static bool mgl_graphics_egl_choose_config(mgl_graphics_egl *self, mgl_context *context, bool alpha) { + self->configs = NULL; + self->ecfg = NULL; + self->visual_info = NULL; + + int32_t num_configs = 0; + context->gl.eglGetConfigs(self->display, NULL, 0, &num_configs); + if(num_configs == 0) { + fprintf(stderr, "mgl error: mgl_graphics_egl_choose_config: no configs found\n"); + return false; + } + + self->configs = (EGLConfig*)calloc(num_configs, sizeof(EGLConfig)); + if(!self->configs) { + fprintf(stderr, "mgl error: mgl_graphics_egl_choose_config: failed to allocate %d configs\n", (int)num_configs); + return false; + } + + context->gl.eglGetConfigs(self->display, self->configs, num_configs, &num_configs); + for(int i = 0; i < num_configs; i++) { + self->ecfg = self->configs[i]; + + //if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_RENDERABLE_TYPE) != EGL_OPENGL_ES2_BIT) + // continue; + + if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_COLOR_BUFFER_TYPE) != EGL_RGB_BUFFER) + continue; + + if(!(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_SURFACE_TYPE) & EGL_WINDOW_BIT)) + continue; + + if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_RED_SIZE) != 8) + continue; + + if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_GREEN_SIZE) != 8) + continue; + + if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_BLUE_SIZE) != 8) + continue; + + if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_ALPHA_SIZE) != (alpha ? 8 : 0)) + continue; + + if(context->window_system == MGL_WINDOW_SYSTEM_WAYLAND) + break; + + XVisualInfo vi = {0}; + vi.visualid = mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_NATIVE_VISUAL_ID); + if(!vi.visualid) + continue; + + int vis_count = 0; + self->visual_info = XGetVisualInfo(context->connection, VisualIDMask, &vi, &vis_count); + if(!self->visual_info) + continue; + + if(xvisual_match_alpha(context->connection, self->visual_info, alpha)) { + break; + } else { + XFree(self->visual_info); + self->visual_info = NULL; + self->ecfg = NULL; + } + } + + if(!self->ecfg) { + fprintf(stderr, "mgl error: mgl_graphics_egl_choose_config: no appropriate glx config found\n"); + return false; + } + + if(context->window_system == MGL_WINDOW_SYSTEM_X11 && !self->visual_info) { + fprintf(stderr, "mgl error: mgl_graphics_egl_choose_config: no appropriate visual found\n"); + return false; + } + + return true; +} + +static bool mgl_graphics_egl_make_context_current(mgl_graphics *self, mgl_window_handle window) { + (void)window; + mgl_graphics_egl *impl = self->impl; + mgl_context *context = mgl_get_context(); + + if(!impl->surface) { + impl->surface = context->gl.eglCreateWindowSurface(impl->display, impl->ecfg, (EGLNativeWindowType)window, NULL); + if(!impl->surface) { + fprintf(stderr, "mgl error: mgl_graphics_egl_make_context_current: failed to create window surface\n"); + return false; + } + } + + return context->gl.eglMakeCurrent(impl->display, impl->surface, impl->surface, impl->context) != 0; +} + +static void mgl_graphics_egl_swap_buffers(mgl_graphics *self, mgl_window_handle window) { + (void)window; + mgl_graphics_egl *impl = self->impl; + mgl_context *context = mgl_get_context(); + context->gl.eglSwapBuffers(impl->display, impl->surface); +} + +static bool mgl_graphics_egl_set_swap_interval(mgl_graphics *self, mgl_window_handle window, bool enabled) { + (void)window; + mgl_graphics_egl *impl = self->impl; + mgl_context *context = mgl_get_context(); + return context->gl.eglSwapInterval(impl->display, (int32_t)enabled) == 1; +} + +static void* mgl_graphics_egl_get_xvisual_info(mgl_graphics *self) { + mgl_graphics_egl *impl = self->impl; + return impl->visual_info; +} + +static void* mgl_graphics_egl_get_display(mgl_graphics *self) { + mgl_graphics_egl *impl = self->impl; + return impl->display; +} + +static void* mgl_graphics_egl_get_context(mgl_graphics *self) { + mgl_graphics_egl *impl = self->impl; + return impl->context; +} + +bool mgl_graphics_egl_init(mgl_graphics *self) { + mgl_graphics_egl *impl = calloc(1, sizeof(mgl_graphics_egl)); + if(!impl) + return false; + + self->deinit = mgl_graphics_egl_deinit; + self->make_context_current = mgl_graphics_egl_make_context_current; + self->swap_buffers = mgl_graphics_egl_swap_buffers; + self->set_swap_interval = mgl_graphics_egl_set_swap_interval; + self->get_xvisual_info = mgl_graphics_egl_get_xvisual_info; + self->get_display = mgl_graphics_egl_get_display; + self->get_context = mgl_graphics_egl_get_context; + self->impl = impl; + + const int32_t ctxattr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE, EGL_NONE + }; + + mgl_context *context = mgl_get_context(); + context->gl.eglBindAPI(EGL_OPENGL_API); + + impl->display = context->gl.eglGetDisplay((EGLNativeDisplayType)context->connection); + if(!impl->display) { + fprintf(stderr, "mgl error: mgl_graphics_egl_init: eglGetDisplay failed\n"); + mgl_graphics_egl_deinit(self); + return false; + } + + if(!context->gl.eglInitialize(impl->display, NULL, NULL)) { + fprintf(stderr, "mgl error: mgl_graphics_egl_init: eglInitialize failed\n"); + mgl_graphics_egl_deinit(self); + return false; + } + + if(!mgl_graphics_egl_choose_config(impl, context, self->alpha)) { + mgl_graphics_egl_deinit(self); + return false; + } + + impl->context = context->gl.eglCreateContext(impl->display, impl->ecfg, NULL, ctxattr); + if(!impl->context) { + fprintf(stderr, "mgl error: mgl_graphics_egl_init: failed to create egl context\n"); + mgl_graphics_egl_deinit(self); + return false; + } + + return true; +} + +void mgl_graphics_egl_deinit(mgl_graphics *self) { + mgl_graphics_egl *impl = self->impl; + if(!impl) + return; + + mgl_context *context = mgl_get_context(); + + if(impl->visual_info) { + XFree(impl->visual_info); + impl->visual_info = NULL; + } + + if(impl->configs) { + free(impl->configs); + impl->configs = NULL; + } + + if(impl->context) { + context->gl.eglMakeCurrent(impl->display, NULL, NULL, NULL); + context->gl.eglDestroyContext(impl->display, impl->context); + impl->context = NULL; + } + + if(impl->surface) { + context->gl.eglDestroySurface(impl->display, impl->surface); + impl->surface = NULL; + } + + if(impl->display) { + context->gl.eglTerminate(impl->display); + impl->display = NULL; + } + + if(impl->visual_info) { + XFree(impl->visual_info); + impl->visual_info = NULL; + } + + free(self->impl); + self->impl = NULL; +} diff --git a/src/graphics/backend/glx.c b/src/graphics/backend/glx.c new file mode 100644 index 0000000..70836e3 --- /dev/null +++ b/src/graphics/backend/glx.c @@ -0,0 +1,173 @@ +#include "../../../include/mgl/graphics/backend/glx.h" +#include "../../../include/mgl/mgl.h" + +#include <stdlib.h> +#include <stdio.h> + +#include <X11/Xutil.h> +#include <X11/extensions/Xrender.h> + +static void mgl_graphics_glx_deinit(mgl_graphics *self); + +typedef struct { + GLXContext glx_context; + GLXFBConfig *fbconfigs; + GLXFBConfig fbconfig; + XVisualInfo *visual_info; +} mgl_graphics_glx; + +static bool xvisual_match_alpha(Display *dpy, XVisualInfo *visual_info, bool alpha) { + XRenderPictFormat *pict_format = XRenderFindVisualFormat(dpy, visual_info->visual); + if(!pict_format) + return false; + return (alpha && pict_format->direct.alphaMask > 0) || (!alpha && pict_format->direct.alphaMask == 0); +} + +static bool mgl_graphics_glx_choose_config(mgl_graphics_glx *self, mgl_context *context, bool alpha) { + const int attr[] = { + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_DOUBLEBUFFER, True, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + GLX_ALPHA_SIZE, alpha ? 8 : 0, + // TODO: + //GLX_DEPTH_SIZE, 0, + None + }; + + self->fbconfigs = NULL; + self->visual_info = NULL; + self->fbconfig = NULL; + + int numfbconfigs = 0; + self->fbconfigs = context->gl.glXChooseFBConfig(context->connection, DefaultScreen(context->connection), attr, &numfbconfigs); + for(int i = 0; i < numfbconfigs; i++) { + self->visual_info = (XVisualInfo*)context->gl.glXGetVisualFromFBConfig(context->connection, self->fbconfigs[i]); + if(!self->visual_info) + continue; + + if(xvisual_match_alpha(context->connection, self->visual_info, alpha)) { + self->fbconfig = self->fbconfigs[i]; + break; + } else { + XFree(self->visual_info); + self->visual_info = NULL; + self->fbconfig = NULL; + } + } + + if(!self->fbconfig) { + fprintf(stderr, "mgl error: mgl_graphics_glx_choose_config: no appropriate glx config found\n"); + return false; + } + + if(!self->visual_info) { + fprintf(stderr, "mgl error: mgl_graphics_glx_choose_config: no appropriate visual found\n"); + return false; + } + + return true; +} + +static bool mgl_graphics_glx_make_context_current(mgl_graphics *self, mgl_window_handle window) { + mgl_graphics_glx *impl = self->impl; + mgl_context *context = mgl_get_context(); + return context->gl.glXMakeContextCurrent(context->connection, (GLXDrawable)window, (GLXDrawable)window, impl->glx_context) != 0; +} + +static void mgl_graphics_glx_swap_buffers(mgl_graphics *self, mgl_window_handle window) { + (void)self; + mgl_context *context = mgl_get_context(); + context->gl.glXSwapBuffers(context->connection, (GLXDrawable)window); +} + +/* TODO: Use gl OML present for other platforms than nvidia? nvidia doesn't support present yet */ +/* TODO: check for glx swap control extension string (GLX_EXT_swap_control, etc) */ +static bool mgl_graphics_glx_set_swap_interval(mgl_graphics *self, mgl_window_handle window, bool enabled) { + (void)self; + mgl_context *context = mgl_get_context(); + + int result = 0; + if(context->gl.glXSwapIntervalEXT) { + context->gl.glXSwapIntervalEXT(context->connection, (GLXDrawable)window, enabled); + } else if(context->gl.glXSwapIntervalMESA) { + result = context->gl.glXSwapIntervalMESA(enabled); + } else if(context->gl.glXSwapIntervalSGI) { + result = context->gl.glXSwapIntervalSGI(enabled); + } else { + static int warned = 0; + if (!warned) { + warned = 1; + fprintf(stderr, "mgl warning: setting vertical sync not supported\n"); + } + } + + if(result != 0) + fprintf(stderr, "mgl warning: setting vertical sync failed\n"); + + return result == 0; +} + +static void* mgl_graphics_glx_get_xvisual_info(mgl_graphics *self) { + mgl_graphics_glx *impl = self->impl; + return impl->visual_info; +} + +bool mgl_graphics_glx_init(mgl_graphics *self) { + mgl_graphics_glx *impl = calloc(1, sizeof(mgl_graphics_glx)); + if(!impl) + return false; + + self->deinit = mgl_graphics_glx_deinit; + self->make_context_current = mgl_graphics_glx_make_context_current; + self->swap_buffers = mgl_graphics_glx_swap_buffers; + self->set_swap_interval = mgl_graphics_glx_set_swap_interval; + self->get_xvisual_info = mgl_graphics_glx_get_xvisual_info; + self->impl = impl; + + mgl_context *context = mgl_get_context(); + if(!mgl_graphics_glx_choose_config(impl, context, self->alpha)) { + mgl_graphics_glx_deinit(self); + return false; + } + + impl->glx_context = context->gl.glXCreateNewContext(context->connection, impl->fbconfig, GLX_RGBA_TYPE, 0, True); + if(!impl->glx_context) { + fprintf(stderr, "mgl error: mgl_graphics_glx_init: glXCreateContext failed\n"); + mgl_graphics_glx_deinit(self); + return false; + } + + return true; +} + +void mgl_graphics_glx_deinit(mgl_graphics *self) { + mgl_graphics_glx *impl = self->impl; + if(!impl) + return; + + mgl_context *context = mgl_get_context(); + + if(impl->glx_context) { + context->gl.glXMakeContextCurrent(context->connection, None, None, NULL); + context->gl.glXDestroyContext(context->connection, impl->glx_context); + impl->glx_context = NULL; + } + + if(impl->visual_info) { + XFree(impl->visual_info); + impl->visual_info = NULL; + } + + if(impl->fbconfigs) { + XFree(impl->fbconfigs); + impl->fbconfigs = NULL; + } + + impl->fbconfig = NULL; + + free(self->impl); + self->impl = NULL; +} diff --git a/src/graphics/backend/graphics.c b/src/graphics/backend/graphics.c new file mode 100644 index 0000000..201b9e3 --- /dev/null +++ b/src/graphics/backend/graphics.c @@ -0,0 +1,68 @@ +#include "../../../include/mgl/graphics/backend/graphics.h" +#include "../../../include/mgl/graphics/backend/glx.h" +#include "../../../include/mgl/graphics/backend/egl.h" +#include "../../../include/mgl/mgl.h" + +#include <string.h> + +bool mgl_graphics_init(mgl_graphics *self, const mgl_graphics_create_params *params) { + memset(self, 0, sizeof(*self)); + self->graphics_api = params ? params->graphics_api : MGL_GRAPHICS_API_EGL; + self->alpha = params && params->alpha; + + switch(self->graphics_api) { + case MGL_GRAPHICS_API_GLX: + return mgl_graphics_glx_init(self); + case MGL_GRAPHICS_API_EGL: + return mgl_graphics_egl_init(self); + } + return false; +} + +void mgl_graphics_deinit(mgl_graphics *self) { + if(self->deinit) + self->deinit(self); +} + +bool mgl_graphics_make_context_current(mgl_graphics *self, mgl_window_handle window) { + const bool result = self->make_context_current(self, window); + if(result) { + mgl_context *context = mgl_get_context(); + context->gl.glEnable(GL_TEXTURE_2D); + context->gl.glEnable(GL_BLEND); + context->gl.glEnable(GL_SCISSOR_TEST); + context->gl.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + context->gl.glEnableClientState(GL_VERTEX_ARRAY); + context->gl.glEnableClientState(GL_TEXTURE_COORD_ARRAY); + context->gl.glEnableClientState(GL_COLOR_ARRAY); + context->gl.glPixelStorei(GL_PACK_ALIGNMENT, 1); + context->gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + } + return result; +} + +void mgl_graphics_swap_buffers(mgl_graphics *self, mgl_window_handle window) { + self->swap_buffers(self, window); +} + +bool mgl_graphics_set_swap_interval(mgl_graphics *self, mgl_window_handle window, bool enabled) { + return self->set_swap_interval(self, window, enabled); +} + +void* mgl_graphics_get_xvisual_info(mgl_graphics *self) { + return self->get_xvisual_info(self); +} + +void* mgl_graphics_get_display(mgl_graphics *self) { + if(self->get_display) + return self->get_display(self); + else + return NULL; +} + +void* mgl_graphics_get_context(mgl_graphics *self) { + if(self->get_context) + return self->get_context(self); + else + return NULL; +} diff --git a/src/graphics/font.c b/src/graphics/font.c index 12235f6..671d8a1 100644 --- a/src/graphics/font.c +++ b/src/graphics/font.c @@ -210,7 +210,7 @@ int mgl_font_get_glyph(mgl_font *self, uint32_t codepoint, mgl_font_glyph *glyph /* TODO: Use stbtt_MakeGlyphBitmapSubpixelPrefilter instead for better text quality */ const size_t pixels_width = (width + GLYPH_PADDING * 2); const size_t pixels_height = (height + GLYPH_PADDING * 2); - const size_t pixels_size = pixels_width * pixels_height; + const size_t pixels_size = pixels_width * pixels_height * 2; // *2 required for opengl glTexSubImage2D unsigned char *pixels = calloc(pixels_size, 1); if(pixels) { const int top_padding = GLYPH_PADDING; diff --git a/src/graphics/shader.c b/src/graphics/shader.c index 83c8407..f362490 100644 --- a/src/graphics/shader.c +++ b/src/graphics/shader.c @@ -203,6 +203,39 @@ int mgl_shader_program_set_uniform_vec2f(mgl_shader_program *self, const char *u return 0; } +int mgl_shader_program_set_uniform_vec3f(mgl_shader_program *self, const char *uniform_name, mgl_vec3f value) { + mgl_context *context = mgl_get_context(); + int uniform_location = context->gl.glGetUniformLocation(self->id, uniform_name); + if(uniform_location == -1) { + fprintf(stderr, "mgl error: no uniform by the name %s was found in the shader\n", uniform_name); + return -1; + } + + context->gl.glUseProgram(self->id); + context->gl.glUniform3f(uniform_location, value.x, value.y, value.z); + context->gl.glUseProgram(0); + return 0; +} + +int mgl_shader_program_set_uniform_vec4f(mgl_shader_program *self, const char *uniform_name, mgl_vec4f value) { + mgl_context *context = mgl_get_context(); + int uniform_location = context->gl.glGetUniformLocation(self->id, uniform_name); + if(uniform_location == -1) { + fprintf(stderr, "mgl error: no uniform by the name %s was found in the shader\n", uniform_name); + return -1; + } + + context->gl.glUseProgram(self->id); + context->gl.glUniform4f(uniform_location, value.x, value.y, value.z, value.w); + context->gl.glUseProgram(0); + return 0; +} + +int mgl_shader_program_set_uniform_color(mgl_shader_program *self, const char *uniform_name, mgl_color color) { + return mgl_shader_program_set_uniform_vec4f(self, uniform_name, + (mgl_vec4f){ color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f }); +} + /* TODO: Optimize glUseProgram */ void mgl_shader_program_use(mgl_shader_program *shader_program) { mgl_context *context = mgl_get_context(); diff --git a/src/graphics/sprite.c b/src/graphics/sprite.c index df3e902..7ef81af 100644 --- a/src/graphics/sprite.c +++ b/src/graphics/sprite.c @@ -31,6 +31,38 @@ void mgl_sprite_set_origin(mgl_sprite *self, mgl_vec2f origin) { self->origin = origin; } +void mgl_sprite_set_size(mgl_sprite *self, mgl_vec2f size) { + if(!self->texture) + return; + + self->scale.x = size.x / (float)self->texture->width; + self->scale.y = size.y / (float)self->texture->height; +} + +void mgl_sprite_set_width(mgl_sprite *self, float width) { + if(!self->texture) + return; + + self->scale.x = width / (float)self->texture->width; + self->scale.y = self->scale.x; +} + +void mgl_sprite_set_height(mgl_sprite *self, float height) { + if(!self->texture) + return; + + self->scale.y = height / (float)self->texture->height; + self->scale.x = self->scale.y; +} + +mgl_vec2f mgl_sprite_get_size(const mgl_sprite *self) { + if(self->texture) { + return (mgl_vec2f){ (float)self->texture->width * self->scale.x, (float)self->texture->height * self->scale.y }; + } else { + return (mgl_vec2f){ 0.0f, 0.0f }; + } +} + /* TODO: Cache texture bind to not bind texture if its already bound and do not bind texture 0 */ void mgl_sprite_draw(mgl_context *context, mgl_sprite *sprite) { if(!sprite->texture) diff --git a/src/graphics/text.c b/src/graphics/text.c index 0942b62..3385667 100644 --- a/src/graphics/text.c +++ b/src/graphics/text.c @@ -8,15 +8,38 @@ #define TAB_WIDTH 4 +typedef struct { + size_t codepoint_index; + uint32_t codepoint; + float kerning; + float glyph_width; + const mgl_font_glyph *glyph; + mgl_vec2f glyph_offset; +} codepoint_loop_callback_data; + +static float max_float(float a, float b) { + return a >= b ? a : b; +} + +static float float_abs(float value) { + return value >= 0.0f ? value : -value; +} + /* Return false to stop the loop. |glyph| is not valid if codepoint is '\n' */ -typedef bool (*codepoint_loop_callback)(size_t codepoint_index, uint32_t codepoint, const mgl_font_glyph *glyph, void *userdata); +typedef bool (*codepoint_loop_callback)(codepoint_loop_callback_data callback_data, void *userdata); static void mgl_text_for_each_codepoint(const mgl_text *self, codepoint_loop_callback callback, void *userdata) { mgl_font_glyph glyph; size_t codepoint_index = 0; + float kerning = 0.0f; + float glyph_width = 0.0f; + uint32_t prev_codepoint = 0; + mgl_vec2f glyph_offset = {0, 0}; + unsigned int rows = 1; + for(size_t i = 0; i < self->text_size;) { unsigned char *cp = (unsigned char*)&self->text[i]; - uint32_t codepoint; - size_t clen; + uint32_t codepoint = 0; + size_t clen = 0; if(!mgl_utf8_decode(cp, self->text_size - i, &codepoint, &clen)) { codepoint = *cp; clen = 1; @@ -24,52 +47,60 @@ static void mgl_text_for_each_codepoint(const mgl_text *self, codepoint_loop_cal if(codepoint == '\t') { if(mgl_font_get_glyph(self->font, ' ', &glyph) == 0) { - if(!callback(codepoint_index, codepoint, &glyph, userdata)) - return; + kerning = mgl_font_get_kerning(self->font, prev_codepoint, codepoint); + glyph_width = glyph.advance * TAB_WIDTH; + } else { + goto next; } } else if(codepoint == '\n') { - if(!callback(codepoint_index, codepoint, &glyph, userdata)) - return; + kerning = 0.0f; + glyph_width = 0.0f; + glyph.size = (mgl_vec2i){ (int)glyph_width, (int)self->font->character_size }; } else { if(mgl_font_get_glyph(self->font, codepoint, &glyph) == 0) { - if(!callback(codepoint_index, codepoint, &glyph, userdata)) - return; + kerning = mgl_font_get_kerning(self->font, prev_codepoint, codepoint); + glyph_width = glyph.advance; + } else { + goto next; } } + if(((glyph_offset.x + kerning + glyph_width > self->max_width) && self->max_width > 0.001f) || codepoint == '\n') { + glyph_offset.x = 0.0f; + glyph_offset.y += self->font->character_size; + prev_codepoint = 0; + + if(rows == self->max_rows) + return; + ++rows; + } else { + glyph_offset.x += kerning; + } + + if(!callback((codepoint_loop_callback_data){ codepoint_index, codepoint, kerning, glyph_width, &glyph, glyph_offset }, userdata)) + return; + + glyph_offset.x += glyph_width; + //if(codepoint == '\n') + // glyph_offset.y += self->font->character_size; + + prev_codepoint = codepoint; + + next: i += clen; ++codepoint_index; } } -static float max_float(float a, float b) { - return a >= b ? a : b; -} - typedef struct { mgl_vec2f bounds; - float line_width; - const mgl_font *font; - uint32_t prev_codepoint; + mgl_font *font; } CalculateBoundsUserdata; -static bool calculate_bounds_callback(size_t codepoint_index, uint32_t codepoint, const mgl_font_glyph *glyph, void *userdata) { - (void)codepoint_index; - +static bool calculate_bounds_callback(codepoint_loop_callback_data callback_data, void *userdata) { CalculateBoundsUserdata *calculate_bounds_userdata = userdata; - if(codepoint == '\t') { - calculate_bounds_userdata->line_width += (glyph->advance * TAB_WIDTH) + mgl_font_get_kerning(calculate_bounds_userdata->font, calculate_bounds_userdata->prev_codepoint, codepoint); - calculate_bounds_userdata->bounds.x = max_float(calculate_bounds_userdata->bounds.x, calculate_bounds_userdata->line_width); - } else if(codepoint == '\n') { - calculate_bounds_userdata->line_width = 0.0f; - calculate_bounds_userdata->bounds.y += calculate_bounds_userdata->font->character_size; - calculate_bounds_userdata->prev_codepoint = 0; - } else { - calculate_bounds_userdata->line_width += glyph->advance + mgl_font_get_kerning(calculate_bounds_userdata->font, calculate_bounds_userdata->prev_codepoint, codepoint); - calculate_bounds_userdata->bounds.x = max_float(calculate_bounds_userdata->bounds.x, calculate_bounds_userdata->line_width); - } - - calculate_bounds_userdata->prev_codepoint = codepoint; + calculate_bounds_userdata->bounds.x = max_float(calculate_bounds_userdata->bounds.x, callback_data.glyph_offset.x + callback_data.glyph_width); + calculate_bounds_userdata->bounds.y = max_float(calculate_bounds_userdata->bounds.y, callback_data.glyph_offset.y + calculate_bounds_userdata->font->character_size); return true; } @@ -77,9 +108,7 @@ static mgl_vec2f mgl_text_calculate_bounds(mgl_text *self) { CalculateBoundsUserdata calculate_bounds_userdata; calculate_bounds_userdata.bounds.x = 0.0f; calculate_bounds_userdata.bounds.y = self->font->character_size; - calculate_bounds_userdata.line_width = 0.0f; calculate_bounds_userdata.font = self->font; - calculate_bounds_userdata.prev_codepoint = 0; mgl_text_for_each_codepoint(self, calculate_bounds_callback, &calculate_bounds_userdata); return calculate_bounds_userdata.bounds; } @@ -91,6 +120,8 @@ void mgl_text_init(mgl_text *self, mgl_font *font, const char *str, size_t str_s self->text = NULL; self->text_size = 0; self->bounds_dirty = true; + self->max_width = 0.0f; + self->max_rows = 0; mgl_text_set_string(self, str, str_size); } @@ -105,14 +136,12 @@ void mgl_text_deinit(mgl_text *self) { void mgl_text_set_string(mgl_text *self, const char *str, size_t str_size) { self->text = str; self->text_size = str_size; - if(self->text && self->text_size > 0 && self->font) - self->bounds_dirty = true; + self->bounds_dirty = true; } void mgl_text_set_font(mgl_text *self, mgl_font *font) { self->font = font; - if(self->text && self->text_size > 0 && self->font) - self->bounds_dirty = true; + self->bounds_dirty = true; } void mgl_text_set_position(mgl_text *self, mgl_vec2f position) { @@ -123,11 +152,26 @@ void mgl_text_set_color(mgl_text *self, mgl_color color) { self->color = color; } +void mgl_text_set_max_width(mgl_text *self, float max_width) { + if(float_abs(max_width - self->max_width) < 0.01) + return; + self->bounds_dirty = true; + self->max_width = max_width; +} + +void mgl_text_set_max_rows(mgl_text *self, unsigned int max_rows) { + if(max_rows != self->max_rows) + self->bounds_dirty = true; + self->max_rows = max_rows; +} + mgl_vec2f mgl_text_get_bounds(mgl_text *self) { if(self->bounds_dirty) { self->bounds_dirty = false; if(self->text && self->text_size > 0 && self->font) self->bounds = mgl_text_calculate_bounds(self); + else if(self->font) + self->bounds = (mgl_vec2f){ 0.0f, (float)self->font->character_size }; else self->bounds = (mgl_vec2f){ 0.0f, 0.0f }; } @@ -135,39 +179,30 @@ mgl_vec2f mgl_text_get_bounds(mgl_text *self) { } typedef struct { - mgl_vec2f position; + mgl_vec2f glyph_offset; const mgl_text *text; size_t index; - uint32_t prev_codepoint; } FindCharacterPosUserdata; -static bool find_character_pos_callback(size_t codepoint_index, uint32_t codepoint, const mgl_font_glyph *glyph, void *userdata) { +static bool find_character_pos_callback(codepoint_loop_callback_data callback_data, void *userdata) { FindCharacterPosUserdata *find_character_pos_userdata = userdata; - if(codepoint_index >= find_character_pos_userdata->index) + find_character_pos_userdata->glyph_offset = callback_data.glyph_offset; + if(callback_data.codepoint_index >= find_character_pos_userdata->index) return false; - if(codepoint == '\t') { - find_character_pos_userdata->position.x += (glyph->advance * TAB_WIDTH) + mgl_font_get_kerning(find_character_pos_userdata->text->font, find_character_pos_userdata->prev_codepoint, codepoint); - } else if(codepoint == '\n') { - find_character_pos_userdata->position.x = find_character_pos_userdata->text->position.x; - find_character_pos_userdata->position.y += find_character_pos_userdata->text->font->character_size; - find_character_pos_userdata->prev_codepoint = 0; - } else { - find_character_pos_userdata->position.x += glyph->advance + mgl_font_get_kerning(find_character_pos_userdata->text->font, find_character_pos_userdata->prev_codepoint, codepoint); - } - - find_character_pos_userdata->prev_codepoint = codepoint; + find_character_pos_userdata->glyph_offset.x += callback_data.glyph_width; + if(callback_data.codepoint == '\n') + find_character_pos_userdata->glyph_offset.y += find_character_pos_userdata->text->font->character_size; return true; } mgl_vec2f mgl_text_find_character_pos(mgl_text *self, size_t index) { FindCharacterPosUserdata find_character_pos_userdata; - find_character_pos_userdata.position = self->position; + find_character_pos_userdata.glyph_offset = (mgl_vec2f){0.0f, 0.0f}; find_character_pos_userdata.text = self; find_character_pos_userdata.index = index; - find_character_pos_userdata.prev_codepoint = 0; mgl_text_for_each_codepoint(self, find_character_pos_callback, &find_character_pos_userdata); - return (mgl_vec2f){ find_character_pos_userdata.position.x, find_character_pos_userdata.position.y }; + return (mgl_vec2f){ self->position.x + find_character_pos_userdata.glyph_offset.x, self->position.y + find_character_pos_userdata.glyph_offset.y }; } static void mgl_text_draw_glyph(mgl_context *context, const mgl_font_glyph *glyph, mgl_vec2i position) { @@ -188,24 +223,14 @@ typedef struct { mgl_vec2f position; const mgl_text *text; mgl_context *context; - uint32_t prev_codepoint; } TextDrawUserdata; -static bool text_draw_callback(size_t codepoint_index, uint32_t codepoint, const mgl_font_glyph *glyph, void *userdata) { - (void)codepoint_index; +static bool text_draw_callback(codepoint_loop_callback_data callback_data, void *userdata) { TextDrawUserdata *text_draw_userdata = userdata; - if(codepoint == '\t') { - text_draw_userdata->position.x += (glyph->advance * TAB_WIDTH) + mgl_font_get_kerning(text_draw_userdata->text->font, text_draw_userdata->prev_codepoint, codepoint); - } else if(codepoint == '\n') { - text_draw_userdata->position.x = text_draw_userdata->text->position.x; - text_draw_userdata->position.y += text_draw_userdata->text->font->character_size; - text_draw_userdata->prev_codepoint = 0; - } else { - text_draw_userdata->position.x += mgl_font_get_kerning(text_draw_userdata->text->font, text_draw_userdata->prev_codepoint, codepoint); - mgl_text_draw_glyph(text_draw_userdata->context, glyph, (mgl_vec2i){ text_draw_userdata->position.x, text_draw_userdata->position.y }); - text_draw_userdata->position.x += glyph->advance; + if(callback_data.codepoint != '\t' && callback_data.codepoint != '\n') { + mgl_text_draw_glyph(text_draw_userdata->context, callback_data.glyph, + (mgl_vec2i){ text_draw_userdata->position.x + callback_data.glyph_offset.x, text_draw_userdata->position.y + callback_data.glyph_offset.y }); } - text_draw_userdata->prev_codepoint = codepoint; return true; } @@ -223,7 +248,6 @@ void mgl_text_draw(mgl_context *context, mgl_text *text) { text_draw_userdata.position = (mgl_vec2f){ text->position.x, text->position.y }; text_draw_userdata.text = text; text_draw_userdata.context = context; - text_draw_userdata.prev_codepoint = 0; context->gl.glColor4ub(text->color.r, text->color.g, text->color.b, text->color.a); mgl_texture_use(&text->font->texture); diff --git a/src/graphics/texture.c b/src/graphics/texture.c index 128d910..d33bf58 100644 --- a/src/graphics/texture.c +++ b/src/graphics/texture.c @@ -51,6 +51,17 @@ static mgl_texture_format mgl_image_format_to_mgl_texture_format(mgl_image_forma return 0; } +static void gl_get_texture_size(unsigned int texture_id, int *width, int *height) { + *width = 0; + *height = 0; + + mgl_context *context = mgl_get_context(); + context->gl.glBindTexture(GL_TEXTURE_2D, texture_id); + context->gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, width); + context->gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, height); + context->gl.glBindTexture(GL_TEXTURE_2D, 0); +} + int mgl_texture_init(mgl_texture *self) { self->id = 0; self->width = 0; @@ -59,6 +70,8 @@ int mgl_texture_init(mgl_texture *self) { self->max_width = 0; self->max_height = 0; self->pixel_coordinates = false; + self->scale_type = MGL_TEXTURE_SCALE_LINEAR; + self->owned = true; mgl_context *context = mgl_get_context(); context->gl.glGenTextures(1, &self->id); @@ -75,7 +88,29 @@ int mgl_texture_init(mgl_texture *self) { return 0; } -int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, mgl_texture_load_options *load_options) { +int mgl_texture_init_reference_existing_gl_texture(mgl_texture *self, unsigned int texture_id, mgl_texture_format format, const mgl_texture_reference_options *reference_options) { + self->id = texture_id; + self->width = 0; + self->height = 0; + self->format = format; + self->max_width = 0; + self->max_height = 0; + self->pixel_coordinates = reference_options && reference_options->pixel_coordinates; + self->scale_type = reference_options ? reference_options->scale_type : MGL_TEXTURE_SCALE_LINEAR; + self->owned = false; + + gl_get_texture_size(self->id, &self->width, &self->height); + + int max_texture_size = 0; + mgl_context *context = mgl_get_context(); + context->gl.glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); + self->max_width = max_texture_size; + self->max_height = max_texture_size; + + return 0; +} + +int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, const mgl_texture_load_options *load_options) { mgl_image image; if(mgl_image_load_from_file(&image, filepath) != 0) return -1; @@ -85,33 +120,56 @@ int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, mgl_text return result; } -int mgl_texture_load_from_image(mgl_texture *self, const mgl_image *image, mgl_texture_load_options *load_options) { +int mgl_texture_load_from_image(mgl_texture *self, const mgl_image *image, const mgl_texture_load_options *load_options) { return mgl_texture_load_from_memory(self, image->data, image->width, image->height, image->format, load_options); } -int mgl_texture_load_from_memory(mgl_texture *self, const unsigned char *data, int width, int height, mgl_image_format format, mgl_texture_load_options *load_options) { +int mgl_texture_load_from_memory(mgl_texture *self, const unsigned char *data, int width, int height, mgl_image_format format, const mgl_texture_load_options *load_options) { if(width < 0 || height < 0) return -1; if(width > self->max_width || height > self->max_height) return -1; + mgl_context *context = mgl_get_context(); + self->width = width; self->height = height; self->format = mgl_image_format_to_mgl_texture_format(format); - self->pixel_coordinates = load_options ? load_options->pixel_coordinates : false; + self->pixel_coordinates = load_options && load_options->pixel_coordinates; + self->scale_type = load_options ? load_options->scale_type : MGL_TEXTURE_SCALE_LINEAR; /* TODO: Check if glGenerateMipmap is actually available */ int opengl_texture_format = mgl_texture_format_to_opengl_format(self->format); if(load_options && load_options->compressed) opengl_texture_format = mgl_texture_format_to_compressed_opengl_format(self->format); - mgl_context *context = mgl_get_context(); context->gl.glBindTexture(GL_TEXTURE_2D, self->id); context->gl.glTexImage2D(GL_TEXTURE_2D, 0, opengl_texture_format, self->width, self->height, 0, mgl_texture_format_to_source_opengl_format(self->format), GL_UNSIGNED_BYTE, data); context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + switch(self->scale_type) { + default: + case MGL_TEXTURE_SCALE_LINEAR: { + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + break; + } + case MGL_TEXTURE_SCALE_NEAREST: { + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + break; + } + case MGL_TEXTURE_SCALE_LINEAR_MIPMAP: { + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + if(context->gl.glGenerateMipmap) { + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + context->gl.glGenerateMipmap(GL_TEXTURE_2D); + } else { + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + break; + } + } context->gl.glBindTexture(GL_TEXTURE_2D, 0); return 0; @@ -125,11 +183,13 @@ int mgl_texture_update(mgl_texture *self, const unsigned char *data, int offset_ context->gl.glBindTexture(GL_TEXTURE_2D, self->id); const mgl_texture_format texture_format = mgl_image_format_to_mgl_texture_format(format); context->gl.glTexSubImage2D(GL_TEXTURE_2D, 0, offset_x, offset_y, width, height, mgl_texture_format_to_source_opengl_format(texture_format), GL_UNSIGNED_BYTE, data); + if(self->scale_type == MGL_TEXTURE_SCALE_LINEAR_MIPMAP && context->gl.glGenerateMipmap) + context->gl.glGenerateMipmap(GL_TEXTURE_2D); context->gl.glBindTexture(GL_TEXTURE_2D, 0); return 0; } -int mgl_texture_resize(mgl_texture *self, int new_width, int new_height, mgl_texture_load_options *load_options) { +int mgl_texture_resize(mgl_texture *self, int new_width, int new_height, const mgl_texture_load_options *load_options) { if(new_width == self->width && new_height == self->height) return 0; @@ -147,6 +207,8 @@ int mgl_texture_resize(mgl_texture *self, int new_width, int new_height, mgl_tex mgl_context *context = mgl_get_context(); context->gl.glBindTexture(GL_TEXTURE_2D, self->id); context->gl.glTexImage2D(GL_TEXTURE_2D, 0, opengl_texture_format, self->width, self->height, 0, mgl_texture_format_to_source_opengl_format(self->format), GL_UNSIGNED_BYTE, NULL); + if(self->scale_type == MGL_TEXTURE_SCALE_LINEAR_MIPMAP && context->gl.glGenerateMipmap) + context->gl.glGenerateMipmap(GL_TEXTURE_2D); context->gl.glBindTexture(GL_TEXTURE_2D, 0); return 0; } @@ -185,6 +247,9 @@ const mgl_texture* mgl_texture_current_texture(void) { void mgl_texture_unload(mgl_texture *self) { mgl_context *context = mgl_get_context(); + if(!self->owned) + return; + if(self->id) { context->gl.glDeleteTextures(1, &self->id); self->id = 0; @@ -1,26 +1,32 @@ #include "../include/mgl/mgl.h" -#include <X11/Xutil.h> -#include <X11/XKBlib.h> -#include <X11/extensions/Xrender.h> -#include <X11/extensions/Xrandr.h> #include <stdbool.h> #include <string.h> #include <stdio.h> #include <stdlib.h> +#include <assert.h> + +#include <X11/Xutil.h> +#include <X11/XKBlib.h> +#include <X11/extensions/Xrender.h> +#include <X11/extensions/Xrandr.h> +#include <wayland-client.h> static mgl_context context; static int init_count = 0; static XErrorHandler prev_xerror = NULL; static XIOErrorHandler prev_xioerror = NULL; +static bool connected_to_display_server = false; -static int ignore_xerror(Display *display, XErrorEvent *ee) { +static int mgl_x_error_handler(Display *display, XErrorEvent *ee) { (void)display; (void)ee; return 0; } -static int ignore_xioerror(Display *display) { +static int mgl_x_io_error_handler(Display *display) { (void)display; + /* TODO: Do something equivalent for wayland */ + connected_to_display_server = false; return 0; } @@ -52,42 +58,112 @@ static bool xrandr_is_supported(Display *display, int *event_base, int *error_ba return major_version > 1 || (major_version == 1 && minor_version >= 2); } -int mgl_init(void) { - ++init_count; - if(init_count == 1) { - setenv("__GL_MaxFramesAllowed", "1", true); - memset(&context, 0, sizeof(context)); +static bool is_xwayland(Display *dpy) { + int opcode, event, error; + return XQueryExtension(dpy, "XWAYLAND", &opcode, &event, &error); +} +static int mgl_init_x11(void) { + if(!context.connection) { context.connection = XOpenDisplay(NULL); if(!context.connection) { - fprintf(stderr, "mgl error: XOpenDisplay failed\n"); + fprintf(stderr, "mgl error: mgl_init_x11: failed to connect to the X11 server\n"); mgl_deinit(); return -1; } + } + connected_to_display_server = true; + /* If we dont call we will never get a MappingNotify until a key has been pressed */ + XKeysymToKeycode(context.connection, XK_F1); - prev_xerror = XSetErrorHandler(ignore_xerror); - prev_xioerror = XSetIOErrorHandler(ignore_xioerror); + prev_xerror = XSetErrorHandler(mgl_x_error_handler); + prev_xioerror = XSetIOErrorHandler(mgl_x_io_error_handler); - if(!xrender_is_supported(context.connection, &context.render_event_base, &context.render_error_base)) { - fprintf(stderr, "mgl error: x11 render extension is not supported by your X server\n"); - mgl_deinit(); - return -1; - } + context.display_server_is_wayland = is_xwayland(context.connection); - if(!xrandr_is_supported(context.connection, &context.randr_event_base, &context.randr_error_base)) { - fprintf(stderr, "mgl error: x11 randr extension is not supported by your X server\n"); - mgl_deinit(); - return -1; + if(!xrender_is_supported(context.connection, &context.render_event_base, &context.render_error_base)) { + fprintf(stderr, "mgl error: mgl_init_x11: x11 render extension is not supported by your X server\n"); + mgl_deinit(); + return -1; + } + + if(!xrandr_is_supported(context.connection, &context.randr_event_base, &context.randr_error_base)) { + fprintf(stderr, "mgl error: mgl_init_x11: x11 randr extension is not supported by your X server\n"); + mgl_deinit(); + return -1; + } + + XRRSelectInput(context.connection, DefaultRootWindow(context.connection), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | RROutputChangeNotifyMask); + + XInitThreads(); + XkbSetDetectableAutoRepeat(context.connection, True, NULL); + + context.wm_delete_window_atom = XInternAtom(context.connection, "WM_DELETE_WINDOW", False); + context.net_wm_ping_atom = XInternAtom(context.connection, "_NET_WM_PING", False); + context.net_wm_pid_atom = XInternAtom(context.connection, "_NET_WM_PID", False); + return 0; +} + +static int mgl_init_wayland(void) { + context.connection = wl_display_connect(NULL); + if(!context.connection) { + fprintf(stderr, "mgl error: mgl_init_wayland: failed to connect to the Wayland server\n"); + mgl_deinit(); + return -1; + } + connected_to_display_server = true; + return 0; +} + +static int mgl_init_native(void) { + context.connection = XOpenDisplay(NULL); + if(context.connection) { + context.display_server_is_wayland = is_xwayland(context.connection); + if(context.display_server_is_wayland) { + XCloseDisplay(context.connection); + context.connection = NULL; } + } else { + context.display_server_is_wayland = true; + } - XRRSelectInput(context.connection, DefaultRootWindow(context.connection), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | RROutputChangeNotifyMask); + if(context.display_server_is_wayland) { + context.window_system = MGL_WINDOW_SYSTEM_WAYLAND; + if(mgl_init_wayland() != 0) + return -1; + } else { + context.window_system = MGL_WINDOW_SYSTEM_X11; + if(mgl_init_x11() != 0) + return -1; + } - XInitThreads(); - XkbSetDetectableAutoRepeat(context.connection, True, NULL); + return 0; +} - context.wm_delete_window_atom = XInternAtom(context.connection, "WM_DELETE_WINDOW", False); - context.net_wm_ping_atom = XInternAtom(context.connection, "_NET_WM_PING", False); - context.net_wm_pid_atom = XInternAtom(context.connection, "_NET_WM_PID", False); +int mgl_init(mgl_window_system window_system) { + ++init_count; + if(init_count == 1) { + setenv("__GL_MaxFramesAllowed", "1", true); + memset(&context, 0, sizeof(context)); + context.window_system = window_system; + + switch(window_system) { + case MGL_WINDOW_SYSTEM_NATIVE: { + if(mgl_init_native() != 0) + return -1; + break; + } + case MGL_WINDOW_SYSTEM_X11: { + if(mgl_init_x11() != 0) + return -1; + break; + } + case MGL_WINDOW_SYSTEM_WAYLAND: { + if(mgl_init_wayland() != 0) + return -1; + break; + } + } if(mgl_gl_load(&context.gl) != 0) { mgl_deinit(); @@ -97,28 +173,50 @@ int mgl_init(void) { return 0; } -void mgl_deinit(void) { - if(init_count == 1) { - if(prev_xioerror) { - XSetIOErrorHandler(prev_xioerror); - prev_xioerror = NULL; - } +static void mgl_deinit_x11(void) { + if(context.connection) { + XCloseDisplay(context.connection); + context.connection = NULL; + connected_to_display_server = false; + } - if(prev_xerror) { - XSetErrorHandler(prev_xerror); - prev_xerror = NULL; - } + if(prev_xioerror) { + XSetIOErrorHandler(prev_xioerror); + prev_xioerror = NULL; + } - if(context.connection) { - XCloseDisplay(context.connection); - context.connection = NULL; + if(prev_xerror) { + XSetErrorHandler(prev_xerror); + prev_xerror = NULL; + } +} - /* - GLX needs to be unloaded after closing the display on nvidia because - nvidia registers cleanup callbacks on exit, that uses the x11 display. - */ - mgl_gl_unload(&context.gl); +static void mgl_deinit_wayland(void) { + if(context.connection) { + wl_display_disconnect(context.connection); + context.connection = NULL; + connected_to_display_server = false; + } +} + +void mgl_deinit(void) { + if(init_count == 1) { + switch(context.window_system) { + case MGL_WINDOW_SYSTEM_NATIVE: + assert(false); + break; + case MGL_WINDOW_SYSTEM_X11: { + mgl_deinit_x11(); + break; + } + case MGL_WINDOW_SYSTEM_WAYLAND: { + mgl_deinit_wayland(); + break; + } } + + mgl_gl_unload(&context.gl); + context.current_window = NULL; } if(init_count > 0) @@ -134,3 +232,18 @@ mgl_context* mgl_get_context(void) { #endif return &context; } + +bool mgl_is_connected_to_display_server(void) { + return connected_to_display_server; +} + +void mgl_ping_display_server(void) { + if(!context.connection) + return; + + // TODO: Do something equivalent for wayland, maybe wl_display_roundtrip and if it returns -1 then the connection to the server died + if(context.window_system == MGL_WINDOW_SYSTEM_X11) { + XNoOp(context.connection); + XFlush(context.connection); + } +} diff --git a/src/system/clock.c b/src/system/clock.c index 52869b4..37cedf5 100644 --- a/src/system/clock.c +++ b/src/system/clock.c @@ -3,7 +3,7 @@ /* TODO: Implement for macOS */ -static double clock_get_monotonic_seconds() { +static double clock_get_monotonic_seconds(void) { struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 0; diff --git a/src/window/key.c b/src/window/key.c new file mode 100644 index 0000000..ffef2b4 --- /dev/null +++ b/src/window/key.c @@ -0,0 +1,216 @@ +#include "../../include/mgl/window/key.h" +#include <X11/keysym.h> +#include <X11/XF86keysym.h> + +const char* mgl_key_to_string(mgl_key key) { + switch(key) { + case MGL_KEY_UNKNOWN: return ""; + case MGL_KEY_A: return "A"; + case MGL_KEY_B: return "B"; + case MGL_KEY_C: return "C"; + case MGL_KEY_D: return "D"; + case MGL_KEY_E: return "E"; + case MGL_KEY_F: return "F"; + case MGL_KEY_G: return "G"; + case MGL_KEY_H: return "H"; + case MGL_KEY_I: return "I"; + case MGL_KEY_J: return "J"; + case MGL_KEY_K: return "K"; + case MGL_KEY_L: return "L"; + case MGL_KEY_M: return "M"; + case MGL_KEY_N: return "N"; + case MGL_KEY_O: return "O"; + case MGL_KEY_P: return "P"; + case MGL_KEY_Q: return "Q"; + case MGL_KEY_R: return "R"; + case MGL_KEY_S: return "S"; + case MGL_KEY_T: return "T"; + case MGL_KEY_U: return "U"; + case MGL_KEY_V: return "V"; + case MGL_KEY_W: return "W"; + case MGL_KEY_X: return "X"; + case MGL_KEY_Y: return "Y"; + case MGL_KEY_Z: return "Z"; + case MGL_KEY_NUM0: return "0"; + case MGL_KEY_NUM1: return "1"; + case MGL_KEY_NUM2: return "2"; + case MGL_KEY_NUM3: return "3"; + case MGL_KEY_NUM4: return "4"; + case MGL_KEY_NUM5: return "5"; + case MGL_KEY_NUM6: return "6"; + case MGL_KEY_NUM7: return "7"; + case MGL_KEY_NUM8: return "8"; + case MGL_KEY_NUM9: return "9"; + case MGL_KEY_ESCAPE: return "Escape"; + case MGL_KEY_LCONTROL: return "Left Ctrl"; + case MGL_KEY_LSHIFT: return "Left Shift"; + case MGL_KEY_LALT: return "Left Alt"; + case MGL_KEY_LSYSTEM: return "Left System"; + case MGL_KEY_RCONTROL: return "Right Ctrl"; + case MGL_KEY_RSHIFT: return "Right Shift"; + case MGL_KEY_RALT: return "Right Alt"; + case MGL_KEY_RSYSTEM: return "Right System"; + case MGL_KEY_MENU: return "Menu"; + case MGL_KEY_LBRACKET: return "["; + case MGL_KEY_RBRACKET: return "]"; + case MGL_KEY_SEMICOLON: return ";"; + case MGL_KEY_COMMA: return ","; + case MGL_KEY_PERIOD: return "."; + case MGL_KEY_QUOTE: return "'"; + case MGL_KEY_SLASH: return "/"; + case MGL_KEY_BACKSLASH: return "\\"; + case MGL_KEY_TILDE: return "~"; + case MGL_KEY_EQUAL: return "="; + case MGL_KEY_HYPHEN: return "-"; + case MGL_KEY_SPACE: return "Space"; + case MGL_KEY_ENTER: return "Enter"; + case MGL_KEY_BACKSPACE: return "Backspace"; + case MGL_KEY_TAB: return "Tab"; + case MGL_KEY_PAGEUP: return "PageUp"; + case MGL_KEY_PAGEDOWN: return "PageDown"; + case MGL_KEY_END: return "End"; + case MGL_KEY_HOME: return "Home"; + case MGL_KEY_INSERT: return "Insert"; + case MGL_KEY_DELETE: return "Delete"; + case MGL_KEY_ADD: return "Add"; + case MGL_KEY_SUBTRACT: return "Subtract"; + case MGL_KEY_MULTIPLY: return "Multiply"; + case MGL_KEY_DIVIDE: return "Divide"; + case MGL_KEY_LEFT: return "Left"; + case MGL_KEY_RIGHT: return "Right"; + case MGL_KEY_UP: return "Up"; + case MGL_KEY_DOWN: return "Down"; + case MGL_KEY_NUMPAD0: return "Numpad0"; + case MGL_KEY_NUMPAD1: return "Numpad1"; + case MGL_KEY_NUMPAD2: return "Numpad2"; + case MGL_KEY_NUMPAD3: return "Numpad3"; + case MGL_KEY_NUMPAD4: return "Numpad4"; + case MGL_KEY_NUMPAD5: return "Numpad5"; + case MGL_KEY_NUMPAD6: return "Numpad6"; + case MGL_KEY_NUMPAD7: return "Numpad7"; + case MGL_KEY_NUMPAD8: return "Numpad8"; + case MGL_KEY_NUMPAD9: return "Numpad9"; + case MGL_KEY_F1: return "F1"; + case MGL_KEY_F2: return "F2"; + case MGL_KEY_F3: return "F3"; + case MGL_KEY_F4: return "F4"; + case MGL_KEY_F5: return "F5"; + case MGL_KEY_F6: return "F6"; + case MGL_KEY_F7: return "F7"; + case MGL_KEY_F8: return "F8"; + case MGL_KEY_F9: return "F9"; + case MGL_KEY_F10: return "F10"; + case MGL_KEY_F11: return "F11"; + case MGL_KEY_F12: return "F12"; + case MGL_KEY_F13: return "F13"; + case MGL_KEY_F14: return "F14"; + case MGL_KEY_F15: return "F15"; + case MGL_KEY_PAUSE: return "Pause"; + case MGL_KEY_PRINTSCREEN: return "PrintScreen"; + case MGL_KEY_NUMPAD_ENTER: return "Numpad enter"; + case MGL_KEY_AUDIO_LOWER_VOLUME: return "Audio Lower"; + case MGL_KEY_AUDIO_RAISE_VOLUME: return "Audio Raise"; + case MGL_KEY_AUDIO_PLAY: return "Audio Play"; + case MGL_KEY_AUDIO_STOP: return "Audio Stop"; + case MGL_KEY_AUDIO_PAUSE: return "Audio Pause"; + case MGL_KEY_AUDIO_MUTE: return "Audio Mute"; + case MGL_KEY_AUDIO_PREV: return "Audio Prev"; + case MGL_KEY_AUDIO_NEXT: return "Audio Next"; + case MGL_KEY_AUDIO_REWIND: return "Audio Rewind"; + case MGL_KEY_AUDIO_FORWARD: return "Audio Forward"; + case MGL_KEY_DEAD_ACUTE: return "´"; + case MGL_KEY_APOSTROPHE: return "'"; + case MGL_KEY_F16: return "F16"; + case MGL_KEY_F17: return "F17"; + case MGL_KEY_F18: return "F18"; + case MGL_KEY_F19: return "F19"; + case MGL_KEY_F20: return "F20"; + case MGL_KEY_F21: return "F21"; + case MGL_KEY_F22: return "F22"; + case MGL_KEY_F23: return "F23"; + case MGL_KEY_F24: return "F24"; + case __MGL_NUM_KEYS__: return ""; + } + return ""; +} + +bool mgl_key_is_modifier(mgl_key key) { + return key >= MGL_KEY_LCONTROL && key <= MGL_KEY_RSYSTEM; +} + +uint64_t mgl_key_to_x11_keysym(mgl_key key) { + if(key >= MGL_KEY_A && key <= MGL_KEY_Z) + return XK_A + (key - MGL_KEY_A); + if(key >= MGL_KEY_NUM0 && key <= MGL_KEY_NUM9) + return XK_0 + (key - MGL_KEY_NUM0); + if(key >= MGL_KEY_NUMPAD0 && key <= MGL_KEY_NUMPAD9) + return XK_KP_0 + (key - MGL_KEY_NUMPAD0); + + /* TODO: Fill in the rest */ + switch(key) { + case MGL_KEY_SPACE: return XK_space; + case MGL_KEY_BACKSPACE: return XK_BackSpace; + case MGL_KEY_TAB: return XK_Tab; + case MGL_KEY_ENTER: return XK_Return; + case MGL_KEY_ESCAPE: return XK_Escape; + case MGL_KEY_LCONTROL: return XK_Control_L; + case MGL_KEY_LSHIFT: return XK_Shift_L; + case MGL_KEY_LALT: return XK_Alt_L; + case MGL_KEY_LSYSTEM: return XK_Super_L; + case MGL_KEY_RCONTROL: return XK_Control_R; + case MGL_KEY_RSHIFT: return XK_Shift_R; + case MGL_KEY_RALT: return XK_Alt_R; + case MGL_KEY_RSYSTEM: return XK_Super_R; + case MGL_KEY_DELETE: return XK_Delete; + case MGL_KEY_HOME: return XK_Home; + case MGL_KEY_LEFT: return XK_Left; + case MGL_KEY_UP: return XK_Up; + case MGL_KEY_RIGHT: return XK_Right; + case MGL_KEY_DOWN: return XK_Down; + case MGL_KEY_PAGEUP: return XK_Page_Up; + case MGL_KEY_PAGEDOWN: return XK_Page_Down; + case MGL_KEY_END: return XK_End; + case MGL_KEY_F1: return XK_F1; + case MGL_KEY_F2: return XK_F2; + case MGL_KEY_F3: return XK_F3; + case MGL_KEY_F4: return XK_F4; + case MGL_KEY_F5: return XK_F5; + case MGL_KEY_F6: return XK_F6; + case MGL_KEY_F7: return XK_F7; + case MGL_KEY_F8: return XK_F8; + case MGL_KEY_F9: return XK_F9; + case MGL_KEY_F10: return XK_F10; + case MGL_KEY_F11: return XK_F11; + case MGL_KEY_F12: return XK_F12; + case MGL_KEY_F13: return XK_F13; + case MGL_KEY_F14: return XK_F14; + case MGL_KEY_F15: return XK_F15; + case MGL_KEY_INSERT: return XK_Insert; + case MGL_KEY_PAUSE: return XK_Pause; + case MGL_KEY_PRINTSCREEN: return XK_Print; + case MGL_KEY_NUMPAD_ENTER: return XK_KP_Enter; + case MGL_KEY_AUDIO_LOWER_VOLUME: return XF86XK_AudioLowerVolume; + case MGL_KEY_AUDIO_RAISE_VOLUME: return XF86XK_AudioRaiseVolume; + case MGL_KEY_AUDIO_PLAY: return XF86XK_AudioPlay; + case MGL_KEY_AUDIO_STOP: return XF86XK_AudioStop; + case MGL_KEY_AUDIO_PAUSE: return XF86XK_AudioPause; + case MGL_KEY_AUDIO_MUTE: return XF86XK_AudioMute; + case MGL_KEY_AUDIO_PREV: return XF86XK_AudioPrev; + case MGL_KEY_AUDIO_NEXT: return XF86XK_AudioNext; + case MGL_KEY_AUDIO_REWIND: return XF86XK_AudioRewind; + case MGL_KEY_AUDIO_FORWARD: return XF86XK_AudioForward; + case MGL_KEY_DEAD_ACUTE: return XK_dead_acute; + case MGL_KEY_APOSTROPHE: return XK_apostrophe; + case MGL_KEY_F16: return XK_F16; + case MGL_KEY_F17: return XK_F17; + case MGL_KEY_F18: return XK_F18; + case MGL_KEY_F19: return XK_F19; + case MGL_KEY_F20: return XK_F20; + case MGL_KEY_F21: return XK_F21; + case MGL_KEY_F22: return XK_F22; + case MGL_KEY_F23: return XK_F23; + case MGL_KEY_F24: return XK_F24; + default: return XK_VoidSymbol; + } + return XK_VoidSymbol; +} diff --git a/src/window/wayland.c b/src/window/wayland.c new file mode 100644 index 0000000..f889126 --- /dev/null +++ b/src/window/wayland.c @@ -0,0 +1,781 @@ +#include "../../include/mgl/window/wayland.h" +#include "../../include/mgl/mgl.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include <wayland-client.h> +#include <wayland-egl.h> +#include "xdg-shell-client-protocol.h" + +static void mgl_window_wayland_deinit(mgl_window *self); + +typedef struct { + struct wl_egl_window *window; + struct wl_registry *registry; + struct wl_surface *surface; + struct wl_compositor *compositor; + struct wl_shell *shell; + struct wl_shell_surface *shell_surface; + struct wl_seat *seat; + struct wl_pointer *pointer; + struct wl_keyboard *keyboard; + + struct xdg_wm_base *xdg_wm_base; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + + bool mouse_moved; + mgl_vec2i mouse_position; + + mgl_graphics graphics; +} mgl_window_wayland; + +static void mgl_window_wayland_on_resize(mgl_window *self, int width, int height) { + mgl_window_wayland *impl = self->impl; + self->size.x = width; + self->size.y = height; + wl_egl_window_resize(impl->window, self->size.x, self->size.y, 0, 0); + + mgl_view view; + view.position = (mgl_vec2i){ 0, 0 }; + view.size = self->size; + mgl_window_set_view(self, &view); + mgl_window_set_scissor(self, &(mgl_scissor){ .position = { 0, 0 }, .size = self->size }); +} + +static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { + (void)version; + mgl_window *self = data; + mgl_window_wayland *impl = self->impl; + if(strcmp(interface, wl_compositor_interface.name) == 0) { + if(impl->compositor) + return; + + impl->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1); + } else if(strcmp(interface, wl_output_interface.name) == 0) { + if(version < 4) { + fprintf(stderr, "mgl warning: wl output interface version is < 4, expected >= 4\n"); + return; + } + + if(self->num_monitors == MGL_MAX_MONITORS) { + fprintf(stderr, "mgl warning: reached maximum outputs (%d), ignoring output %u\n", MGL_MAX_MONITORS, name); + return; + } + + // gsr_wayland_output *gsr_output = &window_wayland->outputs[window_wayland->num_outputs]; + // ++self->num_monitors; + // *gsr_output = (gsr_wayland_output) { + // .wl_name = name, + // .output = wl_registry_bind(registry, name, &wl_output_interface, 4), + // .pos = { .x = 0, .y = 0 }, + // .size = { .x = 0, .y = 0 }, + // .transform = 0, + // .name = NULL, + // }; + // wl_output_add_listener(gsr_output->output, &output_listener, gsr_output); + } else if(strcmp(interface, wl_shell_interface.name) == 0) { + if(impl->shell) + return; + + impl->shell = wl_registry_bind(registry, name, &wl_shell_interface, 1); + } else if(strcmp(interface, xdg_wm_base_interface.name) == 0) { + if(impl->xdg_wm_base) + return; + + impl->xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); + } else if(strcmp(interface, wl_seat_interface.name) == 0) { + if(version < 5) { + fprintf(stderr, "mgl warning: wl seat interface version is < 5, expected >= 5\n"); + return; + } + + if(impl->seat) + return; + + impl->seat = wl_registry_bind(registry, name, &wl_seat_interface, 5); + } +} + +static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) { + (void)data; + (void)registry; + (void)name; + // TODO: Remove output +} + +static struct wl_registry_listener registry_listener = { + .global = registry_add_object, + .global_remove = registry_remove_object, +}; + +static void shell_surface_ping(void *data, struct wl_shell_surface *shell_surface, uint32_t serial) { + wl_shell_surface_pong(shell_surface, serial); +} + +static void shell_surface_configure(void *data, struct wl_shell_surface *shell_surface, uint32_t edges, int32_t width, int32_t height) { + mgl_window *self = data; + mgl_window_wayland *impl = self->impl; + wl_egl_window_resize(impl->window, width, height, 0, 0); +} + +void shell_surface_popup_done(void *data, struct wl_shell_surface *shell_surface) { + +} + +static struct wl_shell_surface_listener shell_surface_listener = { + .ping = shell_surface_ping, + .configure = shell_surface_configure, + .popup_done = shell_surface_popup_done, +}; + +static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { + xdg_wm_base_pong(shell, serial); +} + +static const struct xdg_wm_base_listener wm_base_listener = { + .ping = xdg_wm_base_ping, +}; + +static void xdg_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial) { + xdg_surface_ack_configure(surface, serial); + // TODO: + //window->wait_for_configure = false; +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure, +}; + +static void xdg_toplevel_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { + mgl_window *self = data; + mgl_window_wayland *impl = self->impl; + // TODO: + mgl_window_wayland_on_resize(self, width, height); + // struct window *window = data; + // uint32_t *p; + + // window->fullscreen = 0; + // window->maximized = 0; + // wl_array_for_each(p, states) { + // uint32_t state = *p; + // switch (state) { + // case XDG_TOPLEVEL_STATE_FULLSCREEN: + // window->fullscreen = 1; + // break; + // case XDG_TOPLEVEL_STATE_MAXIMIZED: + // window->maximized = 1; + // break; + // } + // } + + // if (width > 0 && height > 0) { + // if (!window->fullscreen && !window->maximized) { + // window->window_size.width = width; + // window->window_size.height = height; + // } + // window->geometry.width = width; + // window->geometry.height = height; + // } else if (!window->fullscreen && !window->maximized) { + // window->geometry = window->window_size; + // } + + // if (window->native) + // wl_egl_window_resize(window->native, + // window->geometry.width, + // window->geometry.height, 0, 0); +} + +static void xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) { + // TODO: + //running = 0; +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure, + .close = xdg_toplevel_close, +}; + +static void +wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + // // Set our pointer + // wl_pointer_set_cursor(wl_pointer, serial, cursor_surface, + // cursor_image->hotspot_x, cursor_image->hotspot_y); +} + +static void +wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { + mgl_window *self = data; + mgl_window_wayland *impl = self->impl; + impl->mouse_moved = true; + impl->mouse_position = (mgl_vec2i){ .x = wl_fixed_to_int(surface_x), .y = wl_fixed_to_int(surface_y) }; +} + +static void +wl_pointer_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, + uint32_t time, uint32_t button, uint32_t state) +{ + +} + +static void +wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, + uint32_t axis, wl_fixed_t value) +{ + +} + +static void +wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer, + uint32_t axis_source) +{ + +} + +static void +wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis) +{ + +} + +static void +wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t discrete) +{ + +} + +static void +wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) +{ + mgl_window *self = data; + mgl_window_wayland *impl = self->impl; + if(impl->mouse_moved) { + impl->mouse_moved = false; + self->cursor_position = impl->mouse_position; + } +} + +static const struct wl_pointer_listener wl_pointer_listener = { + .enter = wl_pointer_enter, + .leave = wl_pointer_leave, + .motion = wl_pointer_motion, + .button = wl_pointer_button, + .axis = wl_pointer_axis, + .frame = wl_pointer_frame, + .axis_source = wl_pointer_axis_source, + .axis_stop = wl_pointer_axis_stop, + .axis_discrete = wl_pointer_axis_discrete, +}; + +static void +wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) +{ + // struct client_state *client_state = data; + // assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); + + // char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + // assert(map_shm != MAP_FAILED); + + // struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_string( + // client_state->xkb_context, map_shm, + // XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + // munmap(map_shm, size); + // close(fd); + + // struct xkb_state *xkb_state = xkb_state_new(xkb_keymap); + // xkb_keymap_unref(client_state->xkb_keymap); + // xkb_state_unref(client_state->xkb_state); + // client_state->xkb_keymap = xkb_keymap; + // client_state->xkb_state = xkb_state; +} + +static void +wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ + // struct client_state *client_state = data; + // fprintf(stderr, "keyboard enter; keys pressed are:\n"); + // uint32_t *key; + // wl_array_for_each(key, keys) { + // char buf[128]; + // xkb_keysym_t sym = xkb_state_key_get_one_sym( + // client_state->xkb_state, *key + 8); + // xkb_keysym_get_name(sym, buf, sizeof(buf)); + // fprintf(stderr, "sym: %-12s (%d), ", buf, sym); + // xkb_state_key_get_utf8(client_state->xkb_state, + // *key + 8, buf, sizeof(buf)); + // fprintf(stderr, "utf8: '%s'\n", buf); + // } +} + +static void +wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface) +{ + //fprintf(stderr, "keyboard leave\n"); +} + +static void +wl_keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ + // struct client_state *client_state = data; + // xkb_state_update_mask(client_state->xkb_state, + // mods_depressed, mods_latched, mods_locked, 0, 0, group); +} + +static void +wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, + int32_t rate, int32_t delay) +{ + /* Left as an exercise for the reader */ +} + +static void +wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +{ + // struct client_state *client_state = data; + // char buf[128]; + // uint32_t keycode = key + 8; + // xkb_keysym_t sym = xkb_state_key_get_one_sym( + // client_state->xkb_state, keycode); + // xkb_keysym_get_name(sym, buf, sizeof(buf)); + // const char *action = + // state == WL_KEYBOARD_KEY_STATE_PRESSED ? "press" : "release"; + // fprintf(stderr, "key %s: sym: %-12s (%d), ", action, buf, sym); + // xkb_state_key_get_utf8(client_state->xkb_state, keycode, + // buf, sizeof(buf)); + // fprintf(stderr, "utf8: '%s'\n", buf); +} + +static const struct wl_keyboard_listener wl_keyboard_listener = { + .keymap = wl_keyboard_keymap, + .enter = wl_keyboard_enter, + .leave = wl_keyboard_leave, + .key = wl_keyboard_key, + .modifiers = wl_keyboard_modifiers, + .repeat_info = wl_keyboard_repeat_info, +}; + +static void wl_seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) { + struct mgl_window *self = data; + mgl_window_wayland *impl = self->impl; + const bool have_pointer = capabilities & WL_SEAT_CAPABILITY_POINTER; + const bool have_keyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; + + if(have_pointer && impl->pointer == NULL) { + impl->pointer = wl_seat_get_pointer(impl->seat); + wl_pointer_add_listener(impl->pointer, &wl_pointer_listener, self); + } else if(!have_pointer && impl->pointer != NULL) { + wl_pointer_release(impl->pointer); + impl->pointer = NULL; + } + + if(have_keyboard && impl->keyboard == NULL) { + impl->keyboard = wl_seat_get_keyboard(impl->seat); + wl_keyboard_add_listener(impl->keyboard, &wl_keyboard_listener, self); + } else if(!have_keyboard && impl->keyboard != NULL) { + wl_keyboard_release(impl->keyboard); + impl->keyboard = NULL; + } +} + +static void wl_seat_name(void *data, struct wl_seat *wl_seat, const char *name) { + fprintf(stderr, "seat name: %s\n", name); +} + +static const struct wl_seat_listener wl_seat_listener = { + .capabilities = wl_seat_capabilities, + .name = wl_seat_name, +}; + +// TODO: params and existing_window +bool mgl_wayland_setup_window(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window) { + mgl_window_wayland *impl = self->impl; + mgl_context *context = mgl_get_context(); + + mgl_vec2i window_size = params ? params->size : (mgl_vec2i){ 0, 0 }; + if(window_size.x <= 0 || window_size.y <= 0) { + window_size.x = 640; + window_size.y = 480; + } + self->size = window_size; + + impl->registry = wl_display_get_registry(context->connection); /* TODO: Error checking */ + wl_registry_add_listener(impl->registry, ®istry_listener, self); /* TODO: Error checking */ + + /* Fetch globals */ + wl_display_roundtrip(context->connection); + + /* Fetch wl_output */ + //wl_display_roundtrip(context->connection); + + if(!impl->compositor) { + fprintf(stderr, "mgl error: mgl_wayland_setup_window: failed to find compositor\n"); + return false; + } + + if(!impl->xdg_wm_base && !impl->shell) { + fprintf(stderr, "mgl error: mgl_wayland_setup_window: failed to find shell\n"); + return false; + } + + if(!impl->seat) { + fprintf(stderr, "mgl error: mgl_wayland_setup_window: failed to find seat\n"); + return false; + } + + impl->surface = wl_compositor_create_surface(impl->compositor); + if(!impl->surface) { + fprintf(stderr, "mgl error: mgl_wayland_setup_window: failed to create surface\n"); + return false; + } + + wl_seat_add_listener(impl->seat, &wl_seat_listener, self); + + if(impl->xdg_wm_base) { + // TODO: Error check, cleanup + xdg_wm_base_add_listener(impl->xdg_wm_base, &wm_base_listener, self); + + impl->xdg_surface = xdg_wm_base_get_xdg_surface(impl->xdg_wm_base, impl->surface); + xdg_surface_add_listener(impl->xdg_surface, &xdg_surface_listener, self); + + impl->xdg_toplevel = xdg_surface_get_toplevel(impl->xdg_surface); + xdg_toplevel_add_listener(impl->xdg_toplevel, &xdg_toplevel_listener, self); + + xdg_toplevel_set_title(impl->xdg_toplevel, title); + if(params && params->class_name) + xdg_toplevel_set_app_id(impl->xdg_toplevel, params->class_name); + } else { + // TODO: Error check, cleanup + impl->shell_surface = wl_shell_get_shell_surface(impl->shell, impl->surface); + wl_shell_surface_add_listener(impl->shell_surface, &shell_surface_listener, self); + wl_shell_surface_set_toplevel(impl->shell_surface); + wl_shell_surface_set_title(impl->shell_surface, title); + if(params && params->class_name) + wl_shell_surface_set_class(impl->shell_surface, params->class_name); + } + + wl_surface_commit(impl->surface); + + // TODO: Error check + struct wl_region *region = wl_compositor_create_region(impl->compositor); + wl_region_add(region, 0, 0, self->size.x, self->size.y); + wl_surface_set_opaque_region(impl->surface, region); + wl_region_destroy(region); + + impl->window = wl_egl_window_create(impl->surface, self->size.x, self->size.y); + if(!impl->window) { + fprintf(stderr, "mgl error: mgl_wayland_setup_window: failed to create the Wayland window\n"); + return false; + } + + //mgl_window_x11_on_move(self, window_pos.x, window_pos.y); + mgl_window_wayland_on_resize(self, self->size.x, self->size.y); + + self->open = true; + self->focused = false; + return true; +} + +static mgl_window_handle mgl_window_wayland_get_system_handle(const mgl_window *self) { + const mgl_window_wayland *impl = self->impl; + return (mgl_window_handle)impl->window; +} + +static void mgl_window_wayland_close(mgl_window *self) { + mgl_window_wayland *impl = self->impl; + + if(impl->window) { + wl_egl_window_destroy(impl->window); + impl->window = NULL; + } + + if(impl->surface) { + wl_surface_destroy(impl->surface); + impl->surface = NULL; + } + + self->open = false; +} + +static bool mgl_window_wayland_poll_event(mgl_window *self, mgl_event *event) { + mgl_context *context = mgl_get_context(); + struct wl_display *display = context->connection; + const bool events_available = wl_display_dispatch_pending(display) > 0; + //wl_display_flush(display); + /* TODO: Return event here, pop from circular buffer */ + return events_available; +} + +static void mgl_window_wayland_swap_buffers(mgl_window *self) { + mgl_window_wayland *impl = self->impl; + mgl_graphics_swap_buffers(&impl->graphics, (mgl_window_handle)impl->window); +} + +static void mgl_window_wayland_set_visible(mgl_window *self, bool visible) { + fprintf(stderr, "TODO: Implement\n"); +} + +static bool mgl_window_wayland_is_key_pressed(const mgl_window *self, mgl_key key) { + (void)self; + if(key < 0 || key >= __MGL_NUM_KEYS__) + return false; + + fprintf(stderr, "TODO: Implement\n"); + return false; +} + +static bool mgl_window_wayland_is_mouse_button_pressed(const mgl_window *self, mgl_mouse_button button) { + (void)self; + if(button < 0 || button >= __MGL_NUM_MOUSE_BUTTONS__) + return false; + + fprintf(stderr, "TODO: Implement\n"); + return false; +} + +static void mgl_window_wayland_set_title(mgl_window *self, const char *title) { + mgl_window_wayland *impl = self->impl; + // TODO: Check if wl_display_flush is needed after these calls + if(impl->xdg_toplevel) + xdg_toplevel_set_title(impl->xdg_toplevel, title); + else + wl_shell_surface_set_title(impl->shell_surface, title); +} + +static void mgl_window_wayland_set_cursor_visible(mgl_window *self, bool visible) { + fprintf(stderr, "TODO: Implement\n"); +} + +static void mgl_window_wayland_set_vsync_enabled(mgl_window *self, bool enabled) { + mgl_window_wayland *impl = self->impl; + self->vsync_enabled = enabled; + if(!mgl_graphics_set_swap_interval(&impl->graphics, (mgl_window_handle)impl->window, self->vsync_enabled)) + fprintf(stderr, "mgl warning: mgl_window_wayland_set_vsync_enabled: failed to enable vsync\n"); +} + +static bool mgl_window_wayland_is_vsync_enabled(const mgl_window *self) { + return self->vsync_enabled; +} + +static void mgl_window_wayland_set_fullscreen(mgl_window *self, bool fullscreen) { + mgl_window_wayland *impl = self->impl; + // TODO: The last argument is the monitor we want to fullscreen on. Use this! + if(impl->xdg_toplevel) { + if(fullscreen) + xdg_toplevel_set_fullscreen(impl->xdg_toplevel, NULL); + else + xdg_toplevel_unset_fullscreen(impl->xdg_toplevel); + } else { + if(fullscreen) + wl_shell_surface_set_fullscreen(impl->shell_surface, WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, 0, NULL); + else + fprintf(stderr, "TODO: Implement\n"); + } +} + +static bool mgl_window_wayland_is_fullscreen(const mgl_window *self) { + fprintf(stderr, "TODO: Implement\n"); + return false; +} + +static void mgl_window_wayland_set_position(mgl_window *self, mgl_vec2i position) { + fprintf(stderr, "TODO: Implement\n"); +} + +static void mgl_window_wayland_set_size(mgl_window *self, mgl_vec2i size) { + fprintf(stderr, "TODO: Implement\n"); +} + +static void mgl_window_wayland_set_size_limits(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum) { + mgl_window_wayland *impl = self->impl; + if(impl->xdg_toplevel) { + xdg_toplevel_set_min_size(impl->xdg_toplevel, minimum.x, minimum.y); + xdg_toplevel_set_max_size(impl->xdg_toplevel, maximum.x, maximum.y); + } else { + fprintf(stderr, "TODO: Implement\n"); + } +} + +static void mgl_window_wayland_set_clipboard(mgl_window *self, const char *str, size_t size) { + fprintf(stderr, "TODO: Implement\n"); +} + +static bool mgl_window_wayland_get_clipboard(mgl_window *self, mgl_clipboard_callback callback, void *userdata, uint32_t clipboard_types) { + fprintf(stderr, "TODO: Implement\n"); + return false; +} + +static void mgl_window_wayland_set_key_repeat_enabled(mgl_window *self, bool enabled) { + self->key_repeat_enabled = enabled; + // TODO: Implement +} + +static void mgl_window_wayland_flush(mgl_window *self) { + (void)self; + mgl_context *context = mgl_get_context(); + struct wl_display *display = context->connection; + wl_display_flush(display); +} + +static void* mgl_window_wayland_get_egl_display(mgl_window *self) { + mgl_window_wayland *impl = self->impl; + if(impl->graphics.graphics_api == MGL_GRAPHICS_API_EGL) + return mgl_graphics_get_display(&impl->graphics); + else + return NULL; +} + +static void* mgl_window_wayland_get_egl_context(mgl_window *self) { + mgl_window_wayland *impl = self->impl; + if(impl->graphics.graphics_api == MGL_GRAPHICS_API_EGL) + return mgl_graphics_get_context(&impl->graphics); + else + return NULL; +} + +static void mgl_window_wayland_for_each_active_monitor_output(mgl_window *self, mgl_active_monitor_callback callback, void *userdata) { + fprintf(stderr, "TODO: Implement\n"); +} + +bool mgl_window_wayland_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window) { + mgl_window_wayland *impl = calloc(1, sizeof(mgl_window_wayland)); + if(!impl) + return false; + + self->get_system_handle = mgl_window_wayland_get_system_handle; + self->deinit = mgl_window_wayland_deinit; + self->close = mgl_window_wayland_close; + self->poll_event = mgl_window_wayland_poll_event; + self->swap_buffers = mgl_window_wayland_swap_buffers; + self->set_visible = mgl_window_wayland_set_visible; + self->is_key_pressed = mgl_window_wayland_is_key_pressed; + self->is_mouse_button_pressed = mgl_window_wayland_is_mouse_button_pressed; + self->set_title = mgl_window_wayland_set_title; + self->set_cursor_visible = mgl_window_wayland_set_cursor_visible; + self->set_vsync_enabled = mgl_window_wayland_set_vsync_enabled; + self->is_vsync_enabled = mgl_window_wayland_is_vsync_enabled; + self->set_fullscreen = mgl_window_wayland_set_fullscreen; + self->is_fullscreen = mgl_window_wayland_is_fullscreen; + self->set_position = mgl_window_wayland_set_position; + self->set_size = mgl_window_wayland_set_size; + self->set_size_limits = mgl_window_wayland_set_size_limits; + self->set_clipboard = mgl_window_wayland_set_clipboard; + self->get_clipboard = mgl_window_wayland_get_clipboard; + self->set_key_repeat_enabled = mgl_window_wayland_set_key_repeat_enabled; + self->flush = mgl_window_wayland_flush; + self->get_egl_display = mgl_window_wayland_get_egl_display; + self->get_egl_context = mgl_window_wayland_get_egl_context; + self->for_each_active_monitor_output = mgl_window_wayland_for_each_active_monitor_output; + self->impl = impl; + + if(!mgl_wayland_setup_window(self, title, params, existing_window)) { + mgl_window_wayland_deinit(self); + return false; + } + + assert(!params || params->graphics_api == MGL_GRAPHICS_API_EGL); + const bool alpha = params && params->support_alpha; + if(!mgl_graphics_init(&impl->graphics, &(mgl_graphics_create_params){ .graphics_api = MGL_GRAPHICS_API_EGL, .alpha = alpha })) { + mgl_window_wayland_deinit(self); + return false; + } + + if(!mgl_graphics_make_context_current(&impl->graphics, impl->window)) { + fprintf(stderr, "mgl error: mgl_window_wayland_init: failed to make window context current\n"); + mgl_window_wayland_deinit(self); + return false; + } + + self->vsync_enabled = true; + mgl_graphics_set_swap_interval(&impl->graphics, (mgl_window_handle)impl->window, self->vsync_enabled); + + mgl_context *context = mgl_get_context(); + context->current_window = self; + return true; +} + +void mgl_window_wayland_deinit(mgl_window *self) { + mgl_window_wayland *impl = self->impl; + if(!impl) + return; + + mgl_graphics_deinit(&impl->graphics); + mgl_window_wayland_close(self); + + if(impl->xdg_toplevel) { + xdg_toplevel_destroy(impl->xdg_toplevel); + impl->xdg_toplevel = NULL; + } + + if(impl->xdg_surface) { + xdg_surface_destroy(impl->xdg_surface); + impl->xdg_surface = NULL; + } + + if(impl->shell_surface) { + wl_shell_surface_destroy(impl->shell_surface); + impl->shell_surface = NULL; + } + + if(impl->pointer) { + wl_pointer_release(impl->pointer); + impl->pointer = NULL; + } + + if(impl->keyboard) { + wl_keyboard_release(impl->keyboard); + impl->keyboard = NULL; + } + + if(impl->seat) { + wl_seat_destroy(impl->seat); + impl->seat = NULL; + } + + if(impl->shell) { + wl_shell_destroy(impl->shell); + impl->shell = NULL; + } + + if(impl->xdg_wm_base) { + xdg_wm_base_destroy(impl->xdg_wm_base); + impl->xdg_wm_base = NULL; + } + + if(impl->compositor) { + wl_compositor_destroy(impl->compositor); + impl->compositor = NULL; + } + + if(impl->registry) { + wl_registry_destroy(impl->registry); + impl->registry = NULL; + } + + mgl_context *context = mgl_get_context(); + if(context->current_window == self) + context->current_window = NULL; + + free(self->impl); + self->impl = NULL; +} diff --git a/src/window/window.c b/src/window/window.c index 17066a0..a11bc15 100644 --- a/src/window/window.c +++ b/src/window/window.c @@ -1,1246 +1,66 @@ #include "../../include/mgl/window/window.h" -#include "../../include/mgl/window/event.h" +#include "../../include/mgl/window/x11.h" +#include "../../include/mgl/window/wayland.h" #include "../../include/mgl/mgl.h" -#include "../../include/mgl/system/utf8.h" -#include <X11/Xutil.h> -#include <X11/cursorfont.h> -#include <X11/Xatom.h> -#include <X11/extensions/Xrender.h> -#include <X11/extensions/Xrandr.h> + #include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <stdio.h> -#include <limits.h> -#include <assert.h> #include <unistd.h> +#include <assert.h> +#include <string.h> -/* TODO: Handle XIM better. Set XIM position to text position on screen (for text input) and reset input when selecting a new text input, etc */ -/* TODO: Separate events from windows. Especially when it comes to monitor events */ - -/* Should be in range [2,] */ -#define MAX_STACKED_EVENTS 32 - -typedef struct { - mgl_event stack[MAX_STACKED_EVENTS]; - int start; - int end; - int size; -} x11_events_circular_buffer; - -static void x11_events_circular_buffer_init(x11_events_circular_buffer *self) { - self->start = 0; - self->end = 0; - self->size = 0; -} - -static bool x11_events_circular_buffer_append(x11_events_circular_buffer *self, const mgl_event *event) { - if(self->size == MAX_STACKED_EVENTS) - return false; - - self->stack[self->end] = *event; - self->end = (self->end + 1) % MAX_STACKED_EVENTS; - ++self->size; - return true; -} - -static bool x11_events_circular_buffer_pop(x11_events_circular_buffer *self, mgl_event *event) { - if(self->size == 0) - return false; - - *event = self->stack[self->start]; - self->start = (self->start + 1) % MAX_STACKED_EVENTS; - --self->size; - return true; -} - -#define MAX_MONITORS 12 - -typedef struct { - GLXContext glx_context; - XIM xim; - XIC xic; - Atom clipboard_atom; - Atom targets_atom; - Atom text_atom; - Atom utf8_string_atom; - Atom image_png_atom; - Atom image_jpg_atom; - Atom image_jpeg_atom; - Atom image_gif_atom; - Atom incr_atom; - Atom net_wm_state_atom; - Atom net_wm_state_fullscreen_atom; - Atom net_wm_state_above_atom; - Atom net_wm_name_atom; - Atom net_wm_window_type_atom; - Atom net_wm_window_type_normal_atom; - Atom net_wm_window_type_dialog_atom; - Cursor default_cursor; - Cursor invisible_cursor; - unsigned int prev_keycode_pressed; - bool key_was_released; - Colormap color_map; - GLXFBConfig *fbconfigs; - GLXFBConfig fbconfig; - XVisualInfo *visual_info; - - /* This only contains connected and active monitors */ - mgl_monitor monitors[MAX_MONITORS]; - int num_monitors; - - /* - Used to stack text event on top of key press/release events and other text events. - For example pressing a key should give the user both key press and text events - and for IM with multiple characters entered (such as chinese), we want to trigger - an event for all of them. - */ - x11_events_circular_buffer events; -} x11_context; - -static void x11_context_deinit(x11_context *self); - -static bool glx_context_choose(mgl_context *context, GLXFBConfig **fbconfigs, GLXFBConfig *fbconfig, XVisualInfo **visual_info, bool alpha) { - const int attr[] = { - GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, - GLX_DOUBLEBUFFER, True, - GLX_RED_SIZE, 8, - GLX_GREEN_SIZE, 8, - GLX_BLUE_SIZE, 8, - GLX_ALPHA_SIZE, alpha ? 8 : 0, - GLX_DEPTH_SIZE, 0, - None - }; - - *fbconfigs = NULL; - *visual_info = NULL; - *fbconfig = NULL; - - int numfbconfigs = 0; - *fbconfigs = context->gl.glXChooseFBConfig(context->connection, DefaultScreen(context->connection), attr, &numfbconfigs); - for(int i = 0; i < numfbconfigs; i++) { - *visual_info = (XVisualInfo*)context->gl.glXGetVisualFromFBConfig(context->connection, (*fbconfigs)[i]); - if(!*visual_info) - continue; - - XRenderPictFormat *pict_format = XRenderFindVisualFormat(context->connection, (*visual_info)->visual); - if(!pict_format) { - XFree(*visual_info); - *visual_info = NULL; - continue; - } - - *fbconfig = (*fbconfigs)[i]; - if((alpha && pict_format->direct.alphaMask > 0) || (!alpha && pict_format->direct.alphaMask == 0)) - break; - - XFree(*visual_info); - *visual_info = NULL; - *fbconfig = NULL; - } - - if(!*visual_info) { - fprintf(stderr, "mgl error: no appropriate visual found\n"); - return false; - } - - return true; -} - -static int x11_context_init(x11_context *self, bool alpha) { - mgl_context *context = mgl_get_context(); - - /* TODO: Use CLIPBOARD_MANAGER and SAVE_TARGETS to save clipboard in clipboard manager on exit */ - - self->glx_context = NULL; - self->xim = NULL; - self->xic = NULL; - self->clipboard_atom = XInternAtom(context->connection, "CLIPBOARD", False); - self->targets_atom = XInternAtom(context->connection, "TARGETS", False); - self->text_atom = XInternAtom(context->connection, "TEXT", False); - self->utf8_string_atom = XInternAtom(context->connection, "UTF8_STRING", False); - self->image_png_atom = XInternAtom(context->connection, "image/png", False); - self->image_jpg_atom = XInternAtom(context->connection, "image/jpg", False); - self->image_jpeg_atom = XInternAtom(context->connection, "image/jpeg", False); - self->image_gif_atom = XInternAtom(context->connection, "image/gif", False); - self->incr_atom = XInternAtom(context->connection, "INCR", False); - self->net_wm_state_atom = XInternAtom(context->connection, "_NET_WM_STATE", False); - self->net_wm_state_fullscreen_atom = XInternAtom(context->connection, "_NET_WM_STATE_FULLSCREEN", False); - self->net_wm_state_above_atom = XInternAtom(context->connection, "_NET_WM_STATE_ABOVE", False); - self->net_wm_name_atom = XInternAtom(context->connection, "_NET_WM_NAME", False); - self->net_wm_window_type_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE", False); - self->net_wm_window_type_normal_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_NORMAL", False); - self->net_wm_window_type_dialog_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_DIALOG", False); - self->default_cursor = None; - self->invisible_cursor = None; - - self->prev_keycode_pressed = 0; - self->key_was_released = false; - self->color_map = None; - - self->fbconfigs = NULL; - self->fbconfig = NULL; - self->visual_info = NULL; - - memset(self->monitors, 0, sizeof(self->monitors)); - self->num_monitors = 0; - - if(!glx_context_choose(context, &self->fbconfigs, &self->fbconfig, &self->visual_info, alpha)) { - x11_context_deinit(self); - return -1; - } - - self->default_cursor = XCreateFontCursor(context->connection, XC_arrow); - if(!self->default_cursor) { - x11_context_deinit(self); - return -1; - } - - const char data[1] = {0}; - Pixmap blank_bitmap = XCreateBitmapFromData(context->connection, DefaultRootWindow(context->connection), data, 1, 1); - if(!blank_bitmap) { - x11_context_deinit(self); - return -1; - } - - XColor dummy; - self->invisible_cursor = XCreatePixmapCursor(context->connection, blank_bitmap, blank_bitmap, &dummy, &dummy, 0, 0); - XFreePixmap(context->connection, blank_bitmap); - if(!self->invisible_cursor) { - x11_context_deinit(self); - return -1; - } - - x11_events_circular_buffer_init(&self->events); - - return 0; -} - -void x11_context_deinit(x11_context *self) { - mgl_context *context = mgl_get_context(); - - for(int i = 0; i < self->num_monitors; ++i) { - mgl_monitor *monitor = &self->monitors[i]; - if(monitor->name) { - free((char*)monitor->name); - monitor->name = NULL; - } - } - self->num_monitors = 0; - - if(self->color_map) { - XFreeColormap(context->connection, self->color_map); - self->color_map = None; - } - - if(self->invisible_cursor) { - XFreeCursor(context->connection, self->invisible_cursor); - self->invisible_cursor = None; - } - - if(self->default_cursor) { - XFreeCursor(context->connection, self->default_cursor); - self->default_cursor = None; - } - - if(self->xic) { - XDestroyIC(self->xic); - self->xic = NULL; - } - - if(self->xim) { - XCloseIM(self->xim); - self->xim = NULL; - } - - if(self->glx_context) { - context->gl.glXMakeContextCurrent(context->connection, None, None, NULL); - context->gl.glXDestroyContext(context->connection, self->glx_context); - self->glx_context = NULL; - } - - if(self->visual_info) { - XFree(self->visual_info); - self->visual_info = NULL; - } - - if(self->fbconfigs) { - XFree(self->fbconfigs); - self->fbconfigs = NULL; - } - - self->fbconfig = NULL; -} - -static bool x11_context_append_event(x11_context *self, const mgl_event *event) { - return x11_events_circular_buffer_append(&self->events, event); -} - -static bool x11_context_pop_event(x11_context *self, mgl_event *event) { - return x11_events_circular_buffer_pop(&self->events, event); -} - -static mgl_monitor* x11_context_add_monitor(x11_context *self, RROutput output_id, RRCrtc crtc_id, const char *name, mgl_vec2i pos, mgl_vec2i size, int refresh_rate) { - if(self->num_monitors == MAX_MONITORS) - return NULL; - - mgl_monitor *monitor = &self->monitors[self->num_monitors]; - monitor->id = output_id; - monitor->crtc_id = crtc_id; - monitor->name = strdup(name); - if(!monitor->name) - return NULL; - monitor->pos = pos; - monitor->size = size; - monitor->refresh_rate = refresh_rate; - self->num_monitors++; - - return monitor; -} - -static mgl_monitor* x11_context_get_monitor_by_id(x11_context *self, RROutput output_id) { - for(int i = 0; i < self->num_monitors; ++i) { - mgl_monitor *monitor = &self->monitors[i]; - if(monitor->id == (int)output_id) - return monitor; - } - return NULL; -} - -static mgl_monitor* x11_context_get_monitor_by_crtc_id(x11_context *self, RRCrtc crtc_id) { - for(int i = 0; i < self->num_monitors; ++i) { - mgl_monitor *monitor = &self->monitors[i]; - if(monitor->crtc_id == (int)crtc_id) - return monitor; - } - return NULL; -} - -static bool x11_context_remove_monitor(x11_context *self, RROutput output_id, mgl_event *event) { - int index_to_remove = -1; - for(int i = 0; i < self->num_monitors; ++i) { - mgl_monitor *monitor = &self->monitors[i]; - if(monitor->id == (int)output_id) { - index_to_remove = i; - break; - } - } - - if(index_to_remove == -1) - return false; - - mgl_monitor *monitor = &self->monitors[index_to_remove]; - free((char*)monitor->name); - monitor->name = NULL; - - for(int i = index_to_remove + 1; i < self->num_monitors; ++i) { - self->monitors[i - 1] = self->monitors[i]; - } - self->num_monitors--; - - event->monitor_disconnected.id = output_id; - return true; -} - -static int round_int(double value) { - return value + 0.5; -} - -static int monitor_info_get_framerate(const XRRModeInfo *mode_info) { - double v_total = mode_info->vTotal; - if(mode_info->modeFlags & RR_DoubleScan) { - v_total *= 2; - } - - if(mode_info->modeFlags & RR_Interlace) { - v_total /= 2; - } - - if(mode_info->hTotal > 0 && v_total > 0.0001) { - return round_int((double)mode_info->dotClock / ((double)mode_info->hTotal * v_total)); - } else { - return 0; - } -} - -static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id) { - for(int i = 0; i < sr->nmode; ++i) { - if(sr->modes[i].id == id) - return &sr->modes[i]; - } - return NULL; -} - -typedef void (*active_monitor_callback)(const mgl_monitor *monitor, void *userdata); -static void for_each_active_monitor_output(Display *display, active_monitor_callback callback, void *userdata) { - XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display)); - if(!screen_res) - return; - - char display_name[256]; - for(int i = 0; i < screen_res->noutput; ++i) { - XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]); - if(out_info && out_info->crtc && out_info->connection == RR_Connected) { - XRRCrtcInfo *crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc); - if(crt_info && crt_info->mode) { - const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode); - if(mode_info && out_info->nameLen < (int)sizeof(display_name)) { - memcpy(display_name, out_info->name, out_info->nameLen); - display_name[out_info->nameLen] = '\0'; - - mgl_monitor monitor = { - .id = screen_res->outputs[i], - .crtc_id = out_info->crtc, - .name = display_name, - .pos = { .x = crt_info->x, .y = crt_info->y }, - .size = { .x = (int)crt_info->width, .y = (int)crt_info->height }, - .refresh_rate = monitor_info_get_framerate(mode_info), - }; - callback(&monitor, userdata); - } - } - if(crt_info) - XRRFreeCrtcInfo(crt_info); - } - if(out_info) - XRRFreeOutputInfo(out_info); - } - - XRRFreeScreenResources(screen_res); -} - -static void monitor_callback_add_to_x11_context(const mgl_monitor *monitor, void *userdata) { - x11_context *x11_context = userdata; - x11_context_add_monitor(x11_context, monitor->id, monitor->crtc_id, monitor->name, monitor->pos, monitor->size, monitor->refresh_rate); -} - -/* TODO: Use gl OML present for other platforms than nvidia? nvidia doesn't support present yet */ - -/* TODO: check for glx swap control extension string (GLX_EXT_swap_control, etc) */ -static void set_vertical_sync_enabled(Window window, int enabled) { - int result = 0; - mgl_context *context = mgl_get_context(); - - if(context->gl.glXSwapIntervalEXT) { - context->gl.glXSwapIntervalEXT(context->connection, window, enabled ? 1 : 0); - } else if(context->gl.glXSwapIntervalMESA) { - result = context->gl.glXSwapIntervalMESA(enabled ? 1 : 0); - } else if(context->gl.glXSwapIntervalSGI) { - result = context->gl.glXSwapIntervalSGI(enabled ? 1 : 0); - } else { - static int warned = 0; - if (!warned) { - warned = 1; - fprintf(stderr, "mgl warning: setting vertical sync not supported\n"); - } - } - - if(result != 0) - fprintf(stderr, "mgl warning: setting vertical sync failed\n"); -} - -static void mgl_window_set_frame_time_limit_monitor(mgl_window *self) { - int monitor_refresh_rate = 0; - mgl_vec2i window_center = (mgl_vec2i) { self->pos.x + self->size.x / 2, self->pos.y + self->size.y / 2 }; - for(int i = 0; i < self->num_monitors; ++i) { - mgl_monitor *monitor = &self->monitors[i]; - if(window_center.x >= monitor->pos.x && window_center.x <= monitor->pos.x + monitor->size.x - && window_center.y >= monitor->pos.y && window_center.y <= monitor->pos.y + monitor->size.y) - { - monitor_refresh_rate = monitor->refresh_rate; - break; - } - } - - if(monitor_refresh_rate == 0 && self->num_monitors > 0) - monitor_refresh_rate = self->monitors[0].refresh_rate; - - if(monitor_refresh_rate == 0) - monitor_refresh_rate = 60; - - self->frame_time_limit_monitor = 1.0 / (double)monitor_refresh_rate; -} - -static void mgl_window_on_move(mgl_window *self, int x, int y) { - self->pos.x = x; - self->pos.y = y; - mgl_window_set_frame_time_limit_monitor(self); -} - -static void mgl_window_on_resize(mgl_window *self, int width, int height) { - self->size.x = width; - self->size.y = height; - - mgl_view view; - view.position = (mgl_vec2i){ 0, 0 }; - view.size = self->size; - mgl_window_set_view(self, &view); - mgl_window_set_scissor(self, &(mgl_scissor){ .position = { 0, 0 }, .size = self->size }); -} - -static unsigned long mgl_color_to_x11_pixel(mgl_color color) { - if(color.a == 0) - return 0; - return ((uint32_t)color.a << 24) | ((uint32_t)color.r << 16) | ((uint32_t)color.g << 8) | (uint32_t)color.b; -} - -static void mgl_set_window_type(mgl_window *self, mgl_window_type window_type) { - mgl_context *context = mgl_get_context(); - x11_context *x11_context = self->context; - switch(window_type) { - case MGL_WINDOW_TYPE_NORMAL: { - XChangeProperty(context->connection, self->window, x11_context->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&x11_context->net_wm_window_type_normal_atom, 1L); - break; - } - case MGL_WINDOW_TYPE_DIALOG: { - XChangeProperty(context->connection, self->window, x11_context->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&x11_context->net_wm_window_type_dialog_atom, 1L); - XChangeProperty(context->connection, self->window, x11_context->net_wm_state_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&x11_context->net_wm_state_above_atom, 1L); - break; - } - } -} - -static int mgl_window_init(mgl_window *self, const char *title, const mgl_window_create_params *params, Window existing_window) { - self->window = 0; - self->context = NULL; - self->open = false; - self->focused = false; +int mgl_window_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window) { + memset(self, 0, sizeof(*self)); + self->vsync_enabled = true; self->key_repeat_enabled = true; - self->frame_time_limit = 0.0; - self->frame_time_limit_monitor = 0.0; mgl_clock_init(&self->frame_timer); - self->clipboard_string = NULL; - self->clipboard_size = 0; - self->monitors = NULL; - self->num_monitors = 0; - self->pos = (mgl_vec2i){ 0, 0 }; - self->low_latency = false; - - mgl_vec2i window_size = params ? params->size : (mgl_vec2i){ 0, 0 }; - if(window_size.x <= 0 || window_size.y <= 0) { - window_size.x = 640; - window_size.y = 480; - } - self->size = window_size; - - mgl_vec2i window_pos = params ? params->position : (mgl_vec2i){ 0, 0 }; - - self->context = malloc(sizeof(x11_context)); - if(!self->context) { - fprintf(stderr, "mgl error: failed to allocate x11 context\n"); - return -1; - } - - x11_context *x11_context = self->context; - if(x11_context_init(x11_context, params ? params->support_alpha : false) != 0) { - fprintf(stderr, "mgl error: x11_context_init failed\n"); - mgl_window_deinit(self); - return -1; - } - - self->monitors = x11_context->monitors; - self->num_monitors = 0; mgl_context *context = mgl_get_context(); - - Window parent_window = params ? params->parent_window : None; - if(parent_window == 0) - parent_window = DefaultRootWindow(context->connection); - - x11_context->glx_context = context->gl.glXCreateNewContext(context->connection, x11_context->fbconfig, GLX_RGBA_TYPE, 0, True); - if(!x11_context->glx_context) { - fprintf(stderr, "mgl error: glXCreateContext failed\n"); - mgl_window_deinit(self); - return -1; - } - - x11_context->color_map = XCreateColormap(context->connection, DefaultRootWindow(context->connection), x11_context->visual_info->visual, AllocNone); - if(!x11_context->color_map) { - fprintf(stderr, "mgl error: XCreateColormap failed\n"); - mgl_window_deinit(self); - return -1; - } - - XSetWindowAttributes window_attr; - window_attr.override_redirect = params ? params->override_redirect : false; - window_attr.colormap = x11_context->color_map; - window_attr.background_pixel = mgl_color_to_x11_pixel(params ? params->background_color : (mgl_color){ .r = 0, .g = 0, .b = 0, .a = 0 }); - window_attr.border_pixel = 0; - window_attr.bit_gravity = NorthWestGravity; - window_attr.event_mask = - KeyPressMask | KeyReleaseMask | - ButtonPressMask | ButtonReleaseMask | - PointerMotionMask | Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | ButtonMotionMask | - StructureNotifyMask | EnterWindowMask | LeaveWindowMask | VisibilityChangeMask | PropertyChangeMask | FocusChangeMask; - - const bool hide_window = params ? params->hidden : false; - - if(existing_window) { - if(!XChangeWindowAttributes(context->connection, existing_window, CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr)) { - fprintf(stderr, "mgl error: XChangeWindowAttributes failed\n"); - mgl_window_deinit(self); - return -1; - } - - if(params && params->size.x > 0 && params->size.y > 0) { - XResizeWindow(context->connection, existing_window, params->size.x, params->size.y); - } - - self->window = existing_window; - if(hide_window) - XUnmapWindow(context->connection, existing_window); - } else { - self->window = XCreateWindow(context->connection, parent_window, window_pos.x, window_pos.y, - window_size.x, window_size.y, 0, - x11_context->visual_info->depth, InputOutput, x11_context->visual_info->visual, - CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr); - if(!self->window) { - fprintf(stderr, "mgl error: XCreateWindow failed\n"); - mgl_window_deinit(self); - return -1; - } - - mgl_window_set_title(self, title); - if(!hide_window) - XMapWindow(context->connection, self->window); - } - - if(params) - mgl_window_set_size_limits(self, params->min_size, params->max_size); - - /* TODO: Call XGetWMProtocols and add wm_delete_window_atom on top, to not overwrite existing wm protocol atoms */ - Atom wm_protocol_atoms[2] = { - context->wm_delete_window_atom, - context->net_wm_ping_atom - }; - XSetWMProtocols(context->connection, self->window, wm_protocol_atoms, 2); - - if(context->net_wm_pid_atom) { - const long pid = getpid(); - XChangeProperty(context->connection, self->window, context->net_wm_pid_atom, XA_CARDINAL, - 32, PropModeReplace, (const unsigned char*)&pid, 1); - } - - char host_name[HOST_NAME_MAX]; - if(gethostname(host_name, sizeof(host_name)) == 0) { - XTextProperty txt_prop; - txt_prop.value = host_name; - txt_prop.encoding = XA_STRING; - txt_prop.format = 8; - txt_prop.nitems = strlen(host_name); - XSetWMClientMachine(context->connection, self->window, &txt_prop); - } - - if(params && params->class_name) { - XClassHint class_hint = { params->class_name, params->class_name }; - XSetClassHint(context->connection, self->window, &class_hint); - } - - if(params && params->transient_for_window) { - XSetTransientForHint(context->connection, self->window, params->transient_for_window); - } - - mgl_window_type window_type = params ? params->window_type : MGL_WINDOW_TYPE_NORMAL; - mgl_set_window_type(self, window_type); - - XFlush(context->connection); - - /* TODO: Check for failure? */ - if(!context->gl.glXMakeContextCurrent(context->connection, self->window, self->window, x11_context->glx_context)) { - fprintf(stderr, "mgl error: failed to make opengl context current!\n"); - mgl_window_deinit(self); - return -1; - } - - self->vsync_enabled = true; - set_vertical_sync_enabled(self->window, self->vsync_enabled ? 1 : 0); - - context->gl.glEnable(GL_TEXTURE_2D); - context->gl.glEnable(GL_BLEND); - context->gl.glEnable(GL_SCISSOR_TEST); - context->gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - context->gl.glEnableClientState(GL_VERTEX_ARRAY); - context->gl.glEnableClientState(GL_TEXTURE_COORD_ARRAY); - context->gl.glEnableClientState(GL_COLOR_ARRAY); - context->gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - Window dummy_w; - int dummy_i; - unsigned int dummy_u; - XQueryPointer(context->connection, self->window, &dummy_w, &dummy_w, &dummy_i, &dummy_i, &self->cursor_position.x, &self->cursor_position.y, &dummy_u); - - x11_context->xim = XOpenIM(context->connection, NULL, NULL, NULL); - if(!x11_context->xim) { - fprintf(stderr, "mgl error: XOpenIM failed\n"); - mgl_window_deinit(self); - return -1; - } - - x11_context->xic = XCreateIC(x11_context->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, self->window, NULL); - if(!x11_context->xic) { - fprintf(stderr, "mgl error: XCreateIC failed\n"); - mgl_window_deinit(self); - return -1; + switch(context->window_system) { + case MGL_WINDOW_SYSTEM_NATIVE: + assert(false); + break; + case MGL_WINDOW_SYSTEM_X11: + return mgl_window_x11_init(self, title, params, existing_window) ? 0 : -1; + case MGL_WINDOW_SYSTEM_WAYLAND: + return mgl_window_wayland_init(self, title, params, existing_window) ? 0 : -1; } - - for_each_active_monitor_output(context->connection, monitor_callback_add_to_x11_context, x11_context); - self->num_monitors = x11_context->num_monitors; - - mgl_window_on_resize(self, self->size.x, self->size.y); - mgl_window_on_move(self, window_pos.x, window_pos.y); - - self->open = true; - self->focused = false; /* TODO: Check if we need to call XGetInputFocus for this, or just wait for focus event */ - return 0; + return -1; } int mgl_window_create(mgl_window *self, const char *title, const mgl_window_create_params *params) { - return mgl_window_init(self, title, params, None); + return mgl_window_init(self, title, params, 0); } -/* TODO: Test this */ int mgl_window_init_from_existing_window(mgl_window *self, mgl_window_handle existing_window) { return mgl_window_init(self, "", NULL, existing_window); } void mgl_window_deinit(mgl_window *self) { - x11_context *x11_context = self->context; - - mgl_window_close(self); - - if(x11_context) { - x11_context_deinit(x11_context); - free(x11_context); - self->context = NULL; - } - - if(self->clipboard_string) { - free(self->clipboard_string); - self->clipboard_string = NULL; - } - self->clipboard_size = 0; - - self->open = false; -} - -/* Returns MGL_KEY_UNKNOWN on no match */ -static mgl_key x11_keysym_to_mgl_key(KeySym key_sym) { - if(key_sym >= XK_A && key_sym <= XK_Z) - return MGL_KEY_A + (key_sym - XK_A); - /* TODO: Check if this ever happens */ - if(key_sym >= XK_a && key_sym <= XK_z) - return MGL_KEY_A + (key_sym - XK_a); - if(key_sym >= XK_0 && key_sym <= XK_9) - return MGL_KEY_NUM0 + (key_sym - XK_0); - if(key_sym >= XK_KP_0 && key_sym <= XK_KP_9) - return MGL_KEY_NUMPAD0 + (key_sym - XK_KP_0); - - /* TODO: Fill in the rest */ - switch(key_sym) { - case XK_space: return MGL_KEY_SPACE; - case XK_BackSpace: return MGL_KEY_BACKSPACE; - case XK_Tab: return MGL_KEY_TAB; - case XK_Return: return MGL_KEY_ENTER; - case XK_Escape: return MGL_KEY_ESCAPE; - case XK_Control_L: return MGL_KEY_LCONTROL; - case XK_Shift_L: return MGL_KEY_LSHIFT; - case XK_Alt_L: return MGL_KEY_LALT; - case XK_Super_L: return MGL_KEY_LSYSTEM; - case XK_Control_R: return MGL_KEY_RCONTROL; - case XK_Shift_R: return MGL_KEY_RSHIFT; - case XK_Alt_R: return MGL_KEY_RALT; - case XK_Super_R: return MGL_KEY_RSYSTEM; - case XK_Delete: return MGL_KEY_DELETE; - case XK_Home: return MGL_KEY_HOME; - case XK_Left: return MGL_KEY_LEFT; - case XK_Up: return MGL_KEY_UP; - case XK_Right: return MGL_KEY_RIGHT; - case XK_Down: return MGL_KEY_DOWN; - case XK_Page_Up: return MGL_KEY_PAGEUP; - case XK_Page_Down: return MGL_KEY_PAGEDOWN; - case XK_End: return MGL_KEY_END; - case XK_F1: return MGL_KEY_F1; - case XK_F2: return MGL_KEY_F2; - case XK_F3: return MGL_KEY_F3; - case XK_F4: return MGL_KEY_F4; - case XK_F5: return MGL_KEY_F5; - case XK_F6: return MGL_KEY_F6; - case XK_F7: return MGL_KEY_F7; - case XK_F8: return MGL_KEY_F8; - case XK_F9: return MGL_KEY_F9; - case XK_F10: return MGL_KEY_F10; - case XK_F11: return MGL_KEY_F11; - case XK_F12: return MGL_KEY_F12; - case XK_F13: return MGL_KEY_F13; - case XK_F14: return MGL_KEY_F14; - case XK_F15: return MGL_KEY_F15; - } - return MGL_KEY_UNKNOWN; -} - -/* Returns XK_VoidSymbol on no match */ -static KeySym mgl_key_to_x11_keysym(mgl_key key) { - if(key >= MGL_KEY_A && key <= MGL_KEY_Z) - return XK_A + (key - MGL_KEY_A); - if(key >= MGL_KEY_NUM0 && key <= MGL_KEY_NUM9) - return XK_0 + (key - MGL_KEY_NUM0); - if(key >= MGL_KEY_NUMPAD0 && key <= MGL_KEY_NUMPAD9) - return XK_KP_0 + (key - MGL_KEY_NUMPAD0); - - /* TODO: Fill in the rest */ - switch(key) { - case MGL_KEY_SPACE: return XK_space; - case MGL_KEY_BACKSPACE: return XK_BackSpace; - case MGL_KEY_TAB: return XK_Tab; - case MGL_KEY_ENTER: return XK_Return; - case MGL_KEY_ESCAPE: return XK_Escape; - case MGL_KEY_LCONTROL: return XK_Control_L; - case MGL_KEY_LSHIFT: return XK_Shift_L; - case MGL_KEY_LALT: return XK_Alt_L; - case MGL_KEY_LSYSTEM: return XK_Super_L; - case MGL_KEY_RCONTROL: return XK_Control_R; - case MGL_KEY_RSHIFT: return XK_Shift_R; - case MGL_KEY_RALT: return XK_Alt_R; - case MGL_KEY_RSYSTEM: return XK_Super_R; - case MGL_KEY_DELETE: return XK_Delete; - case MGL_KEY_HOME: return XK_Home; - case MGL_KEY_LEFT: return XK_Left; - case MGL_KEY_UP: return XK_Up; - case MGL_KEY_RIGHT: return XK_Right; - case MGL_KEY_DOWN: return XK_Down; - case MGL_KEY_PAGEUP: return XK_Page_Up; - case MGL_KEY_PAGEDOWN: return XK_Page_Down; - case MGL_KEY_END: return XK_End; - case MGL_KEY_F1: return XK_F1; - case MGL_KEY_F2: return XK_F2; - case MGL_KEY_F3: return XK_F3; - case MGL_KEY_F4: return XK_F4; - case MGL_KEY_F5: return XK_F5; - case MGL_KEY_F6: return XK_F6; - case MGL_KEY_F7: return XK_F7; - case MGL_KEY_F8: return XK_F8; - case MGL_KEY_F9: return XK_F9; - case MGL_KEY_F10: return XK_F10; - case MGL_KEY_F11: return XK_F11; - case MGL_KEY_F12: return XK_F12; - case MGL_KEY_F13: return XK_F13; - case MGL_KEY_F14: return XK_F14; - case MGL_KEY_F15: return XK_F15; - default: return XK_VoidSymbol; - } - return XK_VoidSymbol; -} - -static mgl_mouse_button x11_button_to_mgl_button(unsigned int button) { - switch(button) { - case Button1: return MGL_BUTTON_LEFT; - case Button2: return MGL_BUTTON_MIDDLE; - case Button3: return MGL_BUTTON_RIGHT; - case 8: return MGL_BUTTON_XBUTTON1; - case 9: return MGL_BUTTON_XBUTTON2; - } - return MGL_BUTTON_UNKNOWN; -} - -static void mgl_window_handle_key_event(mgl_window *self, XKeyEvent *xkey, mgl_event *event, mgl_context *context) { - event->key.code = x11_keysym_to_mgl_key(XKeycodeToKeysym(context->connection, xkey->keycode, 0)); - event->key.alt = ((xkey->state & Mod1Mask) != 0) || ((xkey->state & Mod5Mask) != 0); - event->key.control = ((xkey->state & ControlMask) != 0); - event->key.shift = ((xkey->state & ShiftMask) != 0); - event->key.system = ((xkey->state & Mod4Mask) != 0); -} - -static void mgl_window_handle_text_event(mgl_window *self, XEvent *xev) { - /* Ignore silent keys */ - if(XFilterEvent(xev, None)) - return; - - x11_context *x11_context = self->context; - char buf[128]; - - Status status; - KeySym ksym; - const size_t input_str_len = Xutf8LookupString(x11_context->xic, &xev->xkey, buf, sizeof(buf), &ksym, &status); - /* TODO: Handle XBufferOverflow */ - if(status == XBufferOverflow || input_str_len == 0) - return; - - /* TODO: Set XIC location on screen with XSetICValues */ - - for(size_t i = 0; i < input_str_len;) { - const unsigned char *cp = (const unsigned char*)&buf[i]; - uint32_t codepoint; - size_t clen; - if(!mgl_utf8_decode(cp, input_str_len - i, &codepoint, &clen)) { - codepoint = *cp; - clen = 1; - } - - mgl_event text_event; - text_event.type = MGL_EVENT_TEXT_ENTERED; - text_event.text.codepoint = codepoint; - text_event.text.size = clen; - memcpy(text_event.text.str, &buf[i], clen); - text_event.text.str[clen] = '\0'; - /* Text may contain multiple codepoints so they need to separated into multiple events */ - if(!x11_context_append_event(x11_context, &text_event)) - break; - - i += clen; - } -} - -static bool mgl_on_monitor_added(Display *display, x11_context *x11_context, XRROutputChangeNotifyEvent *rr_output_change_event, XRRScreenResources *screen_res, RROutput output_id, XRROutputInfo *out_info, mgl_event *event) { - char display_name[256]; - mgl_monitor *monitor = NULL; - XRRCrtcInfo *crt_info = NULL; - const XRRModeInfo *mode_info = NULL; - - if(!rr_output_change_event->mode) - return false; - - if(x11_context_get_monitor_by_id(x11_context, output_id)) - return false; - - if(out_info->nameLen >= (int)sizeof(display_name)) - return false; - - crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc); - if(!crt_info) - goto done; - - mode_info = get_mode_info(screen_res, rr_output_change_event->mode); - if(!mode_info) - goto done; - - memcpy(display_name, out_info->name, out_info->nameLen); - display_name[out_info->nameLen] = '\0'; - - monitor = x11_context_add_monitor(x11_context, output_id, out_info->crtc, display_name, - (mgl_vec2i){ .x = crt_info->x, .y = crt_info->y }, - (mgl_vec2i){ .x = (int)crt_info->width, .y = (int)crt_info->height }, - monitor_info_get_framerate(mode_info)); - - if(!monitor) - goto done; - - event->monitor_connected.id = monitor->id; - event->monitor_connected.name = monitor->name; - event->monitor_connected.x = monitor->pos.x; - event->monitor_connected.y = monitor->pos.y; - event->monitor_connected.width = monitor->size.x; - event->monitor_connected.height = monitor->size.y; - event->monitor_connected.refresh_rate = monitor->refresh_rate; - - done: - if(crt_info) - XRRFreeCrtcInfo(crt_info); - return monitor != NULL; -} - -static bool mgl_on_monitor_state_changed(Display *display, x11_context *x11_context, XRROutputChangeNotifyEvent *rr_output_change_event, mgl_event *event) { - bool state_changed = false; - XRROutputInfo *out_info = NULL; - - if(!rr_output_change_event->output) - return false; - - XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display)); - if(!screen_res) - return false; - - out_info = XRRGetOutputInfo(display, screen_res, rr_output_change_event->output); - if(out_info && out_info->crtc && out_info->connection == RR_Connected) { - state_changed = mgl_on_monitor_added(display, x11_context, rr_output_change_event, screen_res, rr_output_change_event->output, out_info, event); - } else { - state_changed = x11_context_remove_monitor(x11_context, rr_output_change_event->output, event); - } - - if(out_info) - XRRFreeOutputInfo(out_info); - - XRRFreeScreenResources(screen_res); - return state_changed; -} - -static bool mgl_on_monitor_property_changed(Display *display, x11_context *x11_context, XRRCrtcChangeNotifyEvent *rr_crtc_change_event, mgl_event *event) { - if(!rr_crtc_change_event->crtc) - return false; - - mgl_monitor *monitor = x11_context_get_monitor_by_crtc_id(x11_context, rr_crtc_change_event->crtc); - if(!monitor) - return false; - - XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display)); - if(!screen_res) - return false; - - monitor->pos = (mgl_vec2i){ .x = rr_crtc_change_event->x, .y = rr_crtc_change_event->y }; - monitor->size = (mgl_vec2i){ .x = rr_crtc_change_event->width, .y = rr_crtc_change_event->height }; - const XRRModeInfo *mode_info = get_mode_info(screen_res, rr_crtc_change_event->mode); - if(mode_info) - monitor->refresh_rate = monitor_info_get_framerate(mode_info); - - XRRFreeScreenResources(screen_res); - - event->monitor_property_changed.id = monitor->id; - event->monitor_property_changed.name = monitor->name; - event->monitor_property_changed.x = monitor->pos.x; - event->monitor_property_changed.y = monitor->pos.y; - event->monitor_property_changed.width = monitor->size.x; - event->monitor_property_changed.height = monitor->size.y; - event->monitor_property_changed.refresh_rate = monitor->refresh_rate; - return true; -} - -/* Returns true if an event was generated */ -static bool mgl_on_rr_notify(mgl_context *context, x11_context *x11_context, XEvent *xev, int subtype, mgl_event *event) { - switch(subtype) { - case RRNotify_CrtcChange: { - XRRCrtcChangeNotifyEvent *rr_crtc_change_event = (XRRCrtcChangeNotifyEvent*)xev; - return mgl_on_monitor_property_changed(context->connection, x11_context, rr_crtc_change_event, event); - } - case RRNotify_OutputChange: { - XRROutputChangeNotifyEvent *rr_output_change_event = (XRROutputChangeNotifyEvent*)xev; - return mgl_on_monitor_state_changed(context->connection, x11_context, rr_output_change_event, event); - } - } - return false; -} - -static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event *event, mgl_context *context) { - x11_context *x11_context = self->context; - switch(xev->type - context->randr_event_base) { - case RRScreenChangeNotify: { - XRRUpdateConfiguration(xev); - event->type = MGL_EVENT_UNKNOWN; - return; - } - case RRNotify: { - XRRNotifyEvent *rr_event = (XRRNotifyEvent*)xev; - if(mgl_on_rr_notify(context, x11_context, xev, rr_event->subtype, event)) { - self->num_monitors = x11_context->num_monitors; - return; - } - - event->type = MGL_EVENT_UNKNOWN; - return; - } - } - - switch(xev->type) { - case KeyPress: { - if(!self->key_repeat_enabled && xev->xkey.keycode == x11_context->prev_keycode_pressed && !x11_context->key_was_released) { - event->type = MGL_EVENT_UNKNOWN; - return; - } - - x11_context->prev_keycode_pressed = xev->xkey.keycode; - x11_context->key_was_released = false; - - event->type = MGL_EVENT_KEY_PRESSED; - mgl_window_handle_key_event(self, &xev->xkey, event, context); - mgl_window_handle_text_event(self, xev); - return; - } - case KeyRelease: { - if(xev->xkey.keycode == x11_context->prev_keycode_pressed) - x11_context->key_was_released = true; - - event->type = MGL_EVENT_KEY_RELEASED; - mgl_window_handle_key_event(self, &xev->xkey, event, context); - return; - } - case ButtonPress: { - if(xev->xbutton.button == Button4) { - /* Mouse scroll up */ - event->type = MGL_EVENT_MOUSE_WHEEL_SCROLLED; - event->mouse_wheel_scroll.delta = 1; - event->mouse_wheel_scroll.x = xev->xbutton.x; - event->mouse_wheel_scroll.y = xev->xbutton.y; - } else if(xev->xbutton.button == Button5) { - /* Mouse scroll down */ - event->type = MGL_EVENT_MOUSE_WHEEL_SCROLLED; - event->mouse_wheel_scroll.delta = -1; - event->mouse_wheel_scroll.x = xev->xbutton.x; - event->mouse_wheel_scroll.y = xev->xbutton.y; - } else { - event->type = MGL_EVENT_MOUSE_BUTTON_PRESSED; - event->mouse_button.button = x11_button_to_mgl_button(xev->xbutton.button); - event->mouse_button.x = xev->xbutton.x; - event->mouse_button.y = xev->xbutton.y; - } - return; - } - case ButtonRelease: { - event->type = MGL_EVENT_MOUSE_BUTTON_RELEASED; - event->mouse_button.button = x11_button_to_mgl_button(xev->xbutton.button); - event->mouse_button.x = xev->xbutton.x; - event->mouse_button.y = xev->xbutton.y; - return; - } - case FocusIn: { - XSetICFocus(x11_context->xic); - - XWMHints* hints = XGetWMHints(context->connection, self->window); - if(hints) { - hints->flags &= ~XUrgencyHint; - XSetWMHints(context->connection, self->window, hints); - XFree(hints); - } - - event->type = MGL_EVENT_GAINED_FOCUS; - self->focused = true; - return; - } - case FocusOut: { - XUnsetICFocus(x11_context->xic); - event->type = MGL_EVENT_LOST_FOCUS; - self->focused = false; - return; - } - case ConfigureNotify: { - while(XCheckTypedWindowEvent(context->connection, self->window, ConfigureNotify, xev)) {} - if(xev->xconfigure.x != self->pos.x || xev->xconfigure.y != self->pos.y) { - mgl_window_on_move(self, xev->xconfigure.x, xev->xconfigure.y); - } - - if(xev->xconfigure.width != self->size.x || xev->xconfigure.height != self->size.y) { - mgl_window_on_resize(self, xev->xconfigure.width, xev->xconfigure.height); - - event->type = MGL_EVENT_RESIZED; - event->size.width = self->size.x; - event->size.height = self->size.y; - return; - } - - event->type = MGL_EVENT_UNKNOWN; - return; - } - case MotionNotify: { - while(XCheckTypedWindowEvent(context->connection, self->window, MotionNotify, xev)) {} - self->cursor_position.x = xev->xmotion.x; - self->cursor_position.y = xev->xmotion.y; - - event->type = MGL_EVENT_MOUSE_MOVED; - event->mouse_move.x = self->cursor_position.x; - event->mouse_move.y = self->cursor_position.y; - return; - } - case SelectionClear: { - event->type = MGL_EVENT_UNKNOWN; - return; - } - case SelectionRequest: { - XSelectionEvent selection_event; - selection_event.type = SelectionNotify; - selection_event.requestor = xev->xselectionrequest.requestor; - selection_event.selection = xev->xselectionrequest.selection; - selection_event.property = xev->xselectionrequest.property; - selection_event.time = xev->xselectionrequest.time; - selection_event.target = xev->xselectionrequest.target; - - if(selection_event.selection == x11_context->clipboard_atom) { - /* TODO: Support ascii text separately by unsetting the 8th bit in the clipboard string? */ - if(selection_event.target == x11_context->targets_atom) { - /* A client requested for our valid conversion targets */ - int num_targets = 3; - Atom targets[4]; - targets[0] = x11_context->targets_atom; - targets[1] = x11_context->text_atom; - targets[2] = XA_STRING; - if(x11_context->utf8_string_atom) { - targets[3] = x11_context->utf8_string_atom; - num_targets = 4; - } - - XChangeProperty(context->connection, selection_event.requestor, selection_event.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, num_targets); - selection_event.target = x11_context->targets_atom; - XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event); - XFlush(context->connection); - event->type = MGL_EVENT_UNKNOWN; - return; - } else if(selection_event.target == XA_STRING || (!x11_context->utf8_string_atom && selection_event.target == x11_context->text_atom)) { - /* A client requested ascii clipboard */ - XChangeProperty(context->connection, selection_event.requestor, selection_event.property, XA_STRING, 8, PropModeReplace, self->clipboard_string, self->clipboard_size); - selection_event.target = XA_STRING; - XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event); - XFlush(context->connection); - event->type = MGL_EVENT_UNKNOWN; - return; - } else if(x11_context->utf8_string_atom && (selection_event.target == x11_context->utf8_string_atom || selection_event.target == x11_context->text_atom)) { - /* A client requested utf8 clipboard */ - XChangeProperty(context->connection, selection_event.requestor, selection_event.property, x11_context->utf8_string_atom, 8, PropModeReplace, self->clipboard_string, self->clipboard_size); - selection_event.target = x11_context->utf8_string_atom; - XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event); - XFlush(context->connection); - event->type = MGL_EVENT_UNKNOWN; - return; - } - } - - selection_event.property = None; - XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event); - XFlush(context->connection); - event->type = MGL_EVENT_UNKNOWN; - return; - } - case ClientMessage: { - if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->wm_delete_window_atom) { - event->type = MGL_EVENT_CLOSED; - self->open = false; - return; - } else if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->net_wm_ping_atom) { - xev->xclient.window = DefaultRootWindow(context->connection); - XSendEvent(context->connection, DefaultRootWindow(context->connection), False, SubstructureNotifyMask | SubstructureRedirectMask, xev); - XFlush(context->connection); - } - event->type = MGL_EVENT_UNKNOWN; - return; - } - case MappingNotify: { - XRefreshKeyboardMapping(&xev->xmapping); - event->type = MGL_EVENT_UNKNOWN; - break; - } - default: { - event->type = MGL_EVENT_UNKNOWN; - return; - } - } + if(self->deinit) + self->deinit(self); } void mgl_window_clear(mgl_window *self, mgl_color color) { + (void)self; mgl_context *context = mgl_get_context(); context->gl.glClearColor((float)color.r / 255.0f, (float)color.g / 255.0f, (float)color.b / 255.0f, (float)color.a / 255.0f); context->gl.glClear(GL_COLOR_BUFFER_BIT); } bool mgl_window_poll_event(mgl_window *self, mgl_event *event) { - mgl_context *context = mgl_get_context(); - Display *display = context->connection; - x11_context *x11_context = self->context; - - if(x11_context_pop_event(x11_context, event)) - return true; + return self->poll_event(self, event); +} - if(XPending(display)) { - XEvent xev; /* TODO: Move to window struct */ - XNextEvent(display, &xev); - if(xev.xany.window == self->window || xev.type == ClientMessage || xev.type - context->randr_event_base >= 0) - mgl_window_on_receive_event(self, &xev, event, context); - else - event->type = MGL_EVENT_UNKNOWN; - return true; - } else { +bool mgl_window_inject_x11_event(mgl_window *self, XEvent *xev, mgl_event *event) { + if(self->inject_x11_event) + return self->inject_x11_event(self, xev, event); + else return false; - } } void mgl_window_display(mgl_window *self) { mgl_context *context = mgl_get_context(); - context->gl.glXSwapBuffers(context->connection, self->window); + self->swap_buffers(self); if(self->low_latency) { context->gl.glFlush(); context->gl.glFinish(); @@ -1274,7 +94,7 @@ void mgl_window_get_view(mgl_window *self, mgl_view *view) { *view = self->view; } -void mgl_window_set_scissor(mgl_window *self, mgl_scissor *new_scissor) { +void mgl_window_set_scissor(mgl_window *self, const mgl_scissor *new_scissor) { mgl_context *context = mgl_get_context(); self->scissor = *new_scissor; context->gl.glScissor(self->scissor.position.x, self->size.y - self->scissor.position.y - self->scissor.size.y, self->scissor.size.x, self->scissor.size.y); @@ -1285,11 +105,7 @@ void mgl_window_get_scissor(mgl_window *self, mgl_scissor *scissor) { } void mgl_window_set_visible(mgl_window *self, bool visible) { - mgl_context *context = mgl_get_context(); - if(visible) - XMapWindow(context->connection, self->window); - else - XUnmapWindow(context->connection, self->window); + self->set_visible(self, visible); } bool mgl_window_is_open(const mgl_window *self) { @@ -1305,19 +121,7 @@ bool mgl_window_is_key_pressed(const mgl_window *self, mgl_key key) { if(key < 0 || key >= __MGL_NUM_KEYS__) return false; - mgl_context *context = mgl_get_context(); - const KeySym keysym = mgl_key_to_x11_keysym(key); - if(keysym == XK_VoidSymbol) - return false; - - KeyCode keycode = XKeysymToKeycode(context->connection, keysym); - if(keycode == 0) - return false; - - char keys[32]; - XQueryKeymap(context->connection, keys); - - return (keys[keycode / 8] & (1 << (keycode % 8))) != 0; + return self->is_key_pressed(self, key); } /* TODO: Track keys with events instead, but somehow handle window focus lost */ @@ -1325,48 +129,23 @@ bool mgl_window_is_mouse_button_pressed(const mgl_window *self, mgl_mouse_button if(button < 0 || button >= __MGL_NUM_MOUSE_BUTTONS__) return false; - mgl_context *context = mgl_get_context(); - Window root, child; - int root_x, root_y, win_x, win_y; - - unsigned int buttons_mask = 0; - XQueryPointer(context->connection, DefaultRootWindow(context->connection), &root, &child, &root_x, &root_y, &win_x, &win_y, &buttons_mask); - - switch(button) { - case MGL_BUTTON_LEFT: return buttons_mask & Button1Mask; - case MGL_BUTTON_MIDDLE: return buttons_mask & Button2Mask; - case MGL_BUTTON_RIGHT: return buttons_mask & Button3Mask; - case MGL_BUTTON_XBUTTON1: return false; /* Not supported by x11 */ - case MGL_BUTTON_XBUTTON2: return false; /* Not supported by x11 */ - default: return false; - } + return self->is_mouse_button_pressed(self, button); +} - return false; +mgl_window_handle mgl_window_get_system_handle(const mgl_window *self) { + return self->get_system_handle(self); } void mgl_window_close(mgl_window *self) { - mgl_context *context = mgl_get_context(); - if(self->window) { - XDestroyWindow(context->connection, self->window); - XSync(context->connection, False); - self->window = None; - } - self->open = false; + self->close(self); } void mgl_window_set_title(mgl_window *self, const char *title) { - mgl_context *context = mgl_get_context(); - x11_context *x11_context = self->context; - - XStoreName(context->connection, self->window, title); - XChangeProperty(context->connection, self->window, x11_context->net_wm_name_atom, x11_context->utf8_string_atom, 8, PropModeReplace, (unsigned char*)title, strlen(title)); - XFlush(context->connection); + self->set_title(self, title); } void mgl_window_set_cursor_visible(mgl_window *self, bool visible) { - mgl_context *context = mgl_get_context(); - x11_context *x11_context = self->context; - XDefineCursor(context->connection, self->window, visible ? x11_context->default_cursor : x11_context->invisible_cursor); + self->set_cursor_visible(self, visible); } void mgl_window_set_framerate_limit(mgl_window *self, int fps) { @@ -1377,52 +156,19 @@ void mgl_window_set_framerate_limit(mgl_window *self, int fps) { } void mgl_window_set_vsync_enabled(mgl_window *self, bool enabled) { - self->vsync_enabled = enabled; - set_vertical_sync_enabled(self->window, self->vsync_enabled ? 1 : 0); + self->set_vsync_enabled(self, enabled); } bool mgl_window_is_vsync_enabled(const mgl_window *self) { - return self->vsync_enabled; + return self->is_vsync_enabled(self); } void mgl_window_set_fullscreen(mgl_window *self, bool fullscreen) { - mgl_context *context = mgl_get_context(); - x11_context *x11_context = self->context; - - XEvent xev; - xev.type = ClientMessage; - xev.xclient.window = self->window; - xev.xclient.message_type = x11_context->net_wm_state_atom; - xev.xclient.format = 32; - xev.xclient.data.l[0] = fullscreen ? 1 : 0; - xev.xclient.data.l[1] = x11_context->net_wm_state_fullscreen_atom; - xev.xclient.data.l[2] = 0; - xev.xclient.data.l[3] = 1; - xev.xclient.data.l[4] = 0; - - if(!XSendEvent(context->connection, DefaultRootWindow(context->connection), 0, SubstructureRedirectMask | SubstructureNotifyMask, &xev)) { - fprintf(stderr, "mgl warning: failed to change window fullscreen state\n"); - return; - } - - XFlush(context->connection); + self->set_fullscreen(self, fullscreen); } bool mgl_window_is_fullscreen(const mgl_window *self) { - mgl_context *context = mgl_get_context(); - x11_context *x11_context = self->context; - - bool is_fullscreen = false; - Atom type = None; - int format = 0; - unsigned long items = 0; - unsigned long remaining_bytes = 0; - unsigned char *data = NULL; - if(XGetWindowProperty(context->connection, self->window, x11_context->net_wm_state_atom, 0, 1024, False, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success && data) { - is_fullscreen = format == 32 && *(unsigned long*)data == x11_context->net_wm_state_fullscreen_atom; - XFree(data); - } - return is_fullscreen; + return self->is_fullscreen(self); } void mgl_window_set_low_latency(mgl_window *self, bool low_latency) { @@ -1434,256 +180,23 @@ bool mgl_window_is_low_latency_enabled(const mgl_window *self) { } void mgl_window_set_position(mgl_window *self, mgl_vec2i position) { - XMoveWindow(mgl_get_context()->connection, self->window, position.x, position.y); + self->set_position(self, position); } void mgl_window_set_size(mgl_window *self, mgl_vec2i size) { - XResizeWindow(mgl_get_context()->connection, self->window, size.x, size.y); + self->set_size(self, size); } void mgl_window_set_size_limits(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum) { - XSizeHints *size_hints = XAllocSizeHints(); - if(size_hints) { - size_hints->width = self->size.x; - size_hints->min_width = minimum.x; - size_hints->max_width = maximum.x; - - size_hints->height = self->size.y; - size_hints->min_height = minimum.y; - size_hints->max_height = maximum.y; - - size_hints->flags = PSize; - if(size_hints->min_width || size_hints->min_height) - size_hints->flags |= PMinSize; - if(size_hints->max_width || size_hints->max_height) - size_hints->flags |= PMaxSize; - - mgl_context *context = mgl_get_context(); - XSetWMNormalHints(context->connection, self->window, size_hints); - XFree(size_hints); - XSync(context->connection, False); - } else { - fprintf(stderr, "mgl warning: failed to set window size hints\n"); - } + self->set_size_limits(self, minimum, maximum); } void mgl_window_set_clipboard(mgl_window *self, const char *str, size_t size) { - mgl_context *context = mgl_get_context(); - x11_context *x11_context = self->context; - - if(self->clipboard_string) { - free(self->clipboard_string); - self->clipboard_string = NULL; - self->clipboard_size = 0; - } - - XSetSelectionOwner(context->connection, x11_context->clipboard_atom, self->window, CurrentTime); - XFlush(context->connection); - - /* Check if setting the selection owner was successful */ - if(XGetSelectionOwner(context->connection, x11_context->clipboard_atom) != self->window) { - fprintf(stderr, "mgl error: mgl_window_set_clipboard failed\n"); - return; - } - - self->clipboard_string = malloc(size + 1); - if(!self->clipboard_string) { - fprintf(stderr, "mgl error: failed to allocate string for clipboard\n"); - return; - } - memcpy(self->clipboard_string, str, size); - self->clipboard_string[size] = '\0'; - self->clipboard_size = size; -} - -static Atom find_matching_atom(const Atom *supported_atoms, size_t num_supported_atoms, const Atom *atoms, size_t num_atoms) { - for(size_t j = 0; j < num_supported_atoms; ++j) { - for(size_t i = 0; i < num_atoms; ++i) { - if(atoms[i] == supported_atoms[j]) - return atoms[i]; - } - } - return None; -} - -static mgl_clipboard_type atom_type_to_supported_clipboard_type(x11_context *x11_context, Atom type, int format) { - if((type == x11_context->utf8_string_atom || type == XA_STRING || type == x11_context->text_atom) && format == 8) { - return MGL_CLIPBOARD_TYPE_STRING; - } else if(type == x11_context->image_png_atom && format == 8){ - return MGL_CLIPBOARD_TYPE_IMAGE_PNG; - } else if((type == x11_context->image_jpg_atom || type == x11_context->image_jpeg_atom) && format == 8){ - return MGL_CLIPBOARD_TYPE_IMAGE_JPG; - } else if(type == x11_context->image_gif_atom && format == 8){ - return MGL_CLIPBOARD_TYPE_IMAGE_GIF; - } else { - return -1; - } + self->set_clipboard(self, str, size); } bool mgl_window_get_clipboard(mgl_window *self, mgl_clipboard_callback callback, void *userdata, uint32_t clipboard_types) { - assert(callback); - - mgl_context *context = mgl_get_context(); - x11_context *x11_context = self->context; - - Window selection_owner = XGetSelectionOwner(context->connection, x11_context->clipboard_atom); - - if(!selection_owner) - return false; - - /* Return data immediately if we are the owner of the clipboard, because we can't process other events in the below event loop */ - if(selection_owner == self->window) { - if(!self->clipboard_string) - return false; - - return callback((const unsigned char*)self->clipboard_string, self->clipboard_size, MGL_CLIPBOARD_TYPE_STRING, userdata); - } - - XEvent xev; - while(XCheckTypedWindowEvent(context->connection, self->window, SelectionNotify, &xev)) {} - - /* Sorted by preference */ - /* TODO: Support more types (BITMAP?, PIXMAP?) */ - Atom supported_clipboard_types[7]; - - int supported_clipboard_type_index = 0; - if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_PNG) { - supported_clipboard_types[supported_clipboard_type_index++] = x11_context->image_png_atom; - } - - if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_JPG) { - supported_clipboard_types[supported_clipboard_type_index++] = x11_context->image_jpeg_atom; - } - - if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_GIF) { - supported_clipboard_types[supported_clipboard_type_index++] = x11_context->image_gif_atom; - } - - if(clipboard_types & MGL_CLIPBOARD_TYPE_STRING) { - supported_clipboard_types[supported_clipboard_type_index++] = x11_context->utf8_string_atom; - supported_clipboard_types[supported_clipboard_type_index++] = XA_STRING; - supported_clipboard_types[supported_clipboard_type_index++] = x11_context->text_atom; - } - - const unsigned long num_supported_clipboard_types = supported_clipboard_type_index; - - Atom requested_clipboard_type = None; - const Atom XA_TARGETS = XInternAtom(context->connection, "TARGETS", False); - XConvertSelection(context->connection, x11_context->clipboard_atom, XA_TARGETS, x11_context->clipboard_atom, self->window, CurrentTime); - - mgl_clock timeout_timer; - mgl_clock_init(&timeout_timer); - bool success = false; - - const double timeout_seconds = 5.0; - while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) { - /* TODO: Wait for SelectionNotify event instead */ - while(XCheckTypedWindowEvent(context->connection, self->window, SelectionNotify, &xev)) { - if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds) - break; - - if(!xev.xselection.property) - continue; - - if(!xev.xselection.target || xev.xselection.selection != x11_context->clipboard_atom) - continue; - - Atom type = None; - int format; - unsigned long items; - unsigned long remaining_bytes = 0; - unsigned char *data = NULL; - - if(xev.xselection.target == XA_TARGETS && requested_clipboard_type == None) { - /* TODO: Wait for PropertyNotify event instead */ - if(XGetWindowProperty(context->connection, xev.xselection.requestor, xev.xselection.property, 0, 1024, False, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success && data) { - if(type != x11_context->incr_atom && type == XA_ATOM && format == 32) { - requested_clipboard_type = find_matching_atom(supported_clipboard_types, num_supported_clipboard_types, (Atom*)data, items); - if(requested_clipboard_type == None) { - /* Pasting clipboard data type we dont support */ - XFree(data); - goto done; - } else { - XConvertSelection(context->connection, x11_context->clipboard_atom, requested_clipboard_type, x11_context->clipboard_atom, self->window, CurrentTime); - } - } - - XFree(data); - } - } else if(xev.xselection.target == requested_clipboard_type) { - bool got_data = false; - long chunk_size = 65536; - - //XDeleteProperty(context->connection, self->window, x11_context->incr_atom); - //XFlush(context->connection); - - while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) { - unsigned long offset = 0; - - /* offset is specified in XGetWindowProperty as a multiple of 32-bit (4 bytes) */ - /* TODO: Wait for PropertyNotify event instead */ - while(XGetWindowProperty(context->connection, xev.xselection.requestor, xev.xselection.property, offset/4, chunk_size, True, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success) { - if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds) - break; - - if(type == x11_context->incr_atom) { - if(data) - chunk_size = *(long*)data; - XDeleteProperty(context->connection, self->window, x11_context->incr_atom); - XFlush(context->connection); - XFree(data); - data = NULL; - break; - } else if(type == requested_clipboard_type) { - got_data = true; - } - - if(!got_data && items == 0) { - XDeleteProperty(context->connection, self->window, type); - XFlush(context->connection); - if(data) { - XFree(data); - data = NULL; - } - break; - } - - const size_t num_items_bytes = items * (format/8); /* format is the bit size of the data */ - if(got_data) { - const mgl_clipboard_type clipboard_type = atom_type_to_supported_clipboard_type(x11_context, type, format); - if(data && num_items_bytes > 0 && clipboard_type != -1) { - if(!callback(data, num_items_bytes, clipboard_type, userdata)) { - XFree(data); - goto done; - } - } - } - offset += num_items_bytes; - - if(data) { - XFree(data); - data = NULL; - } - - if(got_data && num_items_bytes == 0/* && format == 8*/) { - success = true; - goto done; - } - - if(remaining_bytes == 0) - break; - } - } - - goto done; - } - } - } - - done: - if(requested_clipboard_type) - XDeleteProperty(context->connection, self->window, requested_clipboard_type); - return success; + return self->get_clipboard(self, callback, userdata, clipboard_types); } typedef struct { @@ -1735,10 +248,21 @@ bool mgl_window_get_clipboard_string(mgl_window *self, char **str, size_t *size) } void mgl_window_set_key_repeat_enabled(mgl_window *self, bool enabled) { - self->key_repeat_enabled = enabled; + self->set_key_repeat_enabled(self, enabled); } void mgl_window_flush(mgl_window *self) { - mgl_context *context = mgl_get_context(); - XFlush(context->connection); + self->flush(self); +} + +void* mgl_window_get_egl_display(mgl_window *self) { + return self->get_egl_display(self); +} + +void* mgl_window_get_egl_context(mgl_window *self) { + return self->get_egl_context(self); +} + +void mgl_window_for_each_active_monitor_output(mgl_window *self, mgl_active_monitor_callback callback, void *userdata) { + self->for_each_active_monitor_output(self, callback, userdata); } diff --git a/src/window/x11.c b/src/window/x11.c new file mode 100644 index 0000000..c865a98 --- /dev/null +++ b/src/window/x11.c @@ -0,0 +1,1600 @@ +#include "../../include/mgl/window/x11.h" +#include "../../include/mgl/window/event.h" +#include "../../include/mgl/mgl.h" +#include "../../include/mgl/system/utf8.h" +#include "../../include/mgl/system/clock.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <limits.h> +#include <assert.h> +#include <unistd.h> + +#include <X11/Xutil.h> +#include <X11/cursorfont.h> +#include <X11/Xatom.h> +#include <X11/extensions/Xrender.h> +#include <X11/extensions/Xrandr.h> +#include <X11/XF86keysym.h> + +static void mgl_window_x11_deinit(mgl_window *self); + +/* TODO: Handle XIM better. Set XIM position to text position on screen (for text input) and reset input when selecting a new text input, etc */ +/* TODO: Separate events from windows. Especially when it comes to monitor events */ + +/* Should be in range [2,] */ +#define MAX_STACKED_EVENTS 32 + +typedef struct { + mgl_event stack[MAX_STACKED_EVENTS]; + int start; + int end; + int size; +} x11_events_circular_buffer; + +typedef struct { + Window window; + char *clipboard_string; + size_t clipboard_size; + + mgl_graphics graphics; + Colormap color_map; + XIM xim; + XIC xic; + Atom clipboard_atom; + Atom targets_atom; + Atom text_atom; + Atom utf8_string_atom; + Atom image_png_atom; + Atom image_jpg_atom; + Atom image_jpeg_atom; + Atom image_gif_atom; + Atom incr_atom; + Atom net_wm_state_atom; + Atom net_wm_state_fullscreen_atom; + Atom net_wm_state_above_atom; + Atom net_wm_name_atom; + Atom net_wm_window_type_atom; + Atom net_wm_window_type_normal_atom; + Atom net_wm_window_type_dialog_atom; + Atom net_wm_window_type_notification_atom; + Atom net_wm_window_type_utility; + Atom motif_wm_hints_atom; + Cursor default_cursor; + Cursor invisible_cursor; + unsigned int prev_keycode_pressed; + bool key_was_released; + bool support_alpha; + + XEvent xev; + + /* + Used to stack text event on top of key press/release events and other text events. + For example pressing a key should give the user both key press and text events + and for IM with multiple characters entered (such as chinese), we want to trigger + an event for all of them. + */ + x11_events_circular_buffer events; +} mgl_window_x11; + +static void x11_events_circular_buffer_init(x11_events_circular_buffer *self) { + self->start = 0; + self->end = 0; + self->size = 0; +} + +static bool x11_events_circular_buffer_append(x11_events_circular_buffer *self, const mgl_event *event) { + if(self->size == MAX_STACKED_EVENTS) + return false; + + self->stack[self->end] = *event; + self->end = (self->end + 1) % MAX_STACKED_EVENTS; + ++self->size; + return true; +} + +static bool x11_events_circular_buffer_pop(x11_events_circular_buffer *self, mgl_event *event) { + if(self->size == 0) + return false; + + *event = self->stack[self->start]; + self->start = (self->start + 1) % MAX_STACKED_EVENTS; + --self->size; + return true; +} + +static void mgl_window_x11_clear_monitors(mgl_window *self) { + for(int i = 0; i < self->num_monitors; ++i) { + mgl_monitor *monitor = &self->monitors[i]; + if(monitor->name) { + free((char*)monitor->name); + monitor->name = NULL; + } + } + self->num_monitors = 0; +} + +static bool mgl_window_x11_append_event(mgl_window_x11 *self, const mgl_event *event) { + return x11_events_circular_buffer_append(&self->events, event); +} + +static bool mgl_window_x11_pop_event(mgl_window_x11 *self, mgl_event *event) { + return x11_events_circular_buffer_pop(&self->events, event); +} + +static mgl_monitor* mgl_window_x11_add_monitor(mgl_window *self, RROutput output_id, RRCrtc crtc_id, const char *name, mgl_vec2i pos, mgl_vec2i size, int refresh_rate) { + if(self->num_monitors == MGL_MAX_MONITORS) + return NULL; + + mgl_monitor *monitor = &self->monitors[self->num_monitors]; + monitor->id = output_id; + monitor->crtc_id = crtc_id; + monitor->name = strdup(name); + if(!monitor->name) + return NULL; + monitor->pos = pos; + monitor->size = size; + monitor->refresh_rate = refresh_rate; + self->num_monitors++; + + return monitor; +} + +static mgl_monitor* mgl_window_x11_get_monitor_by_id(mgl_window *self, RROutput output_id) { + for(int i = 0; i < self->num_monitors; ++i) { + mgl_monitor *monitor = &self->monitors[i]; + if(monitor->id == (int)output_id) + return monitor; + } + return NULL; +} + +static mgl_monitor* mgl_window_x11_get_monitor_by_crtc_id(mgl_window *self, RRCrtc crtc_id) { + for(int i = 0; i < self->num_monitors; ++i) { + mgl_monitor *monitor = &self->monitors[i]; + if(monitor->crtc_id == (int)crtc_id) + return monitor; + } + return NULL; +} + +static bool mgl_window_x11_remove_monitor(mgl_window *self, RROutput output_id, mgl_event *event) { + int index_to_remove = -1; + for(int i = 0; i < self->num_monitors; ++i) { + mgl_monitor *monitor = &self->monitors[i]; + if(monitor->id == (int)output_id) { + index_to_remove = i; + break; + } + } + + if(index_to_remove == -1) + return false; + + mgl_monitor *monitor = &self->monitors[index_to_remove]; + free((char*)monitor->name); + monitor->name = NULL; + + for(int i = index_to_remove + 1; i < self->num_monitors; ++i) { + self->monitors[i - 1] = self->monitors[i]; + } + self->num_monitors--; + + event->monitor_disconnected.id = output_id; + event->type = MGL_EVENT_MONITOR_DISCONNECTED; + return true; +} + +static int round_int(double value) { + return value + 0.5; +} + +static int monitor_info_get_framerate(const XRRModeInfo *mode_info) { + double v_total = mode_info->vTotal; + if(mode_info->modeFlags & RR_DoubleScan) { + v_total *= 2; + } + + if(mode_info->modeFlags & RR_Interlace) { + v_total /= 2; + } + + if(mode_info->hTotal > 0 && v_total > 0.0001) { + return round_int((double)mode_info->dotClock / ((double)mode_info->hTotal * v_total)); + } else { + return 0; + } +} + +static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id) { + for(int i = 0; i < sr->nmode; ++i) { + if(sr->modes[i].id == id) + return &sr->modes[i]; + } + return NULL; +} + +static void mgl_window_x11_for_each_active_monitor_output(mgl_window *self, mgl_active_monitor_callback callback, void *userdata) { + (void)self; + mgl_context *context = mgl_get_context(); + XRRScreenResources *screen_res = XRRGetScreenResources(context->connection, DefaultRootWindow(context->connection)); + if(!screen_res) + return; + + char display_name[256]; + for(int i = 0; i < screen_res->noutput; ++i) { + XRROutputInfo *out_info = XRRGetOutputInfo(context->connection, screen_res, screen_res->outputs[i]); + if(out_info && out_info->crtc && out_info->connection == RR_Connected) { + XRRCrtcInfo *crt_info = XRRGetCrtcInfo(context->connection, screen_res, out_info->crtc); + if(crt_info && crt_info->mode) { + const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode); + if(mode_info) { + snprintf(display_name, sizeof(display_name), "%s", out_info->name); + mgl_monitor monitor = { + .id = screen_res->outputs[i], + .crtc_id = out_info->crtc, + .name = display_name, + .pos = { .x = crt_info->x, .y = crt_info->y }, + .size = { .x = (int)crt_info->width, .y = (int)crt_info->height }, + .refresh_rate = monitor_info_get_framerate(mode_info), + }; + callback(&monitor, userdata); + } + } + if(crt_info) + XRRFreeCrtcInfo(crt_info); + } + if(out_info) + XRRFreeOutputInfo(out_info); + } + + XRRFreeScreenResources(screen_res); +} + +static void monitor_callback_add_to_mgl_window_x11(const mgl_monitor *monitor, void *userdata) { + mgl_window *mgl_window = userdata; + mgl_window_x11_add_monitor(mgl_window, monitor->id, monitor->crtc_id, monitor->name, monitor->pos, monitor->size, monitor->refresh_rate); +} + +static void mgl_window_x11_set_frame_time_limit_monitor(mgl_window *self) { + int monitor_refresh_rate = 0; + mgl_vec2i window_center = (mgl_vec2i) { self->pos.x + self->size.x / 2, self->pos.y + self->size.y / 2 }; + for(int i = 0; i < self->num_monitors; ++i) { + mgl_monitor *monitor = &self->monitors[i]; + if(window_center.x >= monitor->pos.x && window_center.x <= monitor->pos.x + monitor->size.x + && window_center.y >= monitor->pos.y && window_center.y <= monitor->pos.y + monitor->size.y) + { + monitor_refresh_rate = monitor->refresh_rate; + break; + } + } + + if(monitor_refresh_rate == 0 && self->num_monitors > 0) + monitor_refresh_rate = self->monitors[0].refresh_rate; + + if(monitor_refresh_rate == 0) + monitor_refresh_rate = 60; + + self->frame_time_limit_monitor = 1.0 / (double)monitor_refresh_rate; +} + +static void mgl_window_x11_on_move(mgl_window *self, int x, int y) { + self->pos.x = x; + self->pos.y = y; + mgl_window_x11_set_frame_time_limit_monitor(self); +} + +static void mgl_window_x11_on_resize(mgl_window *self, int width, int height) { + self->size.x = width; + self->size.y = height; + + mgl_view view; + view.position = (mgl_vec2i){ 0, 0 }; + view.size = self->size; + mgl_window_set_view(self, &view); + mgl_window_set_scissor(self, &(mgl_scissor){ .position = { 0, 0 }, .size = self->size }); +} + +static unsigned long mgl_color_to_x11_pixel(mgl_color color) { + if(color.a == 0) + return 0; + return ((uint32_t)color.a << 24) | (((uint32_t)color.r * color.a / 0xFF) << 16) | (((uint32_t)color.g * color.a / 0xFF) << 8) | ((uint32_t)color.b * color.a / 0xFF); +} + +static void mgl_set_window_type(mgl_window *self, mgl_window_type window_type) { + mgl_window_x11 *impl = self->impl; + mgl_context *context = mgl_get_context(); + switch(window_type) { + case MGL_WINDOW_TYPE_NORMAL: { + XChangeProperty(context->connection, impl->window, impl->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&impl->net_wm_window_type_normal_atom, 1L); + break; + } + case MGL_WINDOW_TYPE_DIALOG: { + XChangeProperty(context->connection, impl->window, impl->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&impl->net_wm_window_type_dialog_atom, 1L); + XChangeProperty(context->connection, impl->window, impl->net_wm_state_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&impl->net_wm_state_above_atom, 1L); + break; + } + case MGL_WINDOW_TYPE_NOTIFICATION: { + const Atom data[2] = { + impl->net_wm_window_type_notification_atom, + impl->net_wm_window_type_utility + }; + XChangeProperty(context->connection, impl->window, impl->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (const unsigned char*)data, 2L); + XChangeProperty(context->connection, impl->window, impl->net_wm_state_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&impl->net_wm_state_above_atom, 1L); + break; + } + } +} + +typedef struct { + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long input_mode; + unsigned long status; +} MotifHints; + +#define MWM_HINTS_DECORATIONS 2 + +#define MWM_DECOR_NONE 0 +#define MWM_DECOR_ALL 1 + +static void mgl_window_x11_set_decorations_visible(mgl_window *self, bool visible) { + mgl_window_x11 *impl = self->impl; + mgl_context *context = mgl_get_context(); + + MotifHints motif_hints = {0}; + motif_hints.flags = MWM_HINTS_DECORATIONS; + motif_hints.decorations = visible ? MWM_DECOR_ALL : MWM_DECOR_NONE; + + XChangeProperty(context->connection, impl->window, impl->motif_wm_hints_atom, impl->motif_wm_hints_atom, 32, PropModeReplace, (unsigned char*)&motif_hints, sizeof(motif_hints) / sizeof(long)); +} + +static void mgl_window_x11_set_title(mgl_window *self, const char *title) { + mgl_window_x11 *impl = self->impl; + mgl_context *context = mgl_get_context(); + + XStoreName(context->connection, impl->window, title); + XChangeProperty(context->connection, impl->window, impl->net_wm_name_atom, impl->utf8_string_atom, 8, PropModeReplace, (unsigned char*)title, strlen(title)); + XFlush(context->connection); +} + +static void mgl_window_x11_set_size_limits(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum) { + mgl_window_x11 *impl = self->impl; + XSizeHints *size_hints = XAllocSizeHints(); + if(size_hints) { + size_hints->width = self->size.x; + size_hints->min_width = minimum.x; + size_hints->max_width = maximum.x; + + size_hints->height = self->size.y; + size_hints->min_height = minimum.y; + size_hints->max_height = maximum.y; + + size_hints->flags = PSize; + if(size_hints->min_width || size_hints->min_height) + size_hints->flags |= PMinSize; + if(size_hints->max_width || size_hints->max_height) + size_hints->flags |= PMaxSize; + + mgl_context *context = mgl_get_context(); + XSetWMNormalHints(context->connection, impl->window, size_hints); + XFree(size_hints); + XFlush(context->connection); + } else { + fprintf(stderr, "mgl warning: failed to set window size hints\n"); + } +} + +static bool mgl_x11_setup_window(mgl_window *self, const char *title, const mgl_window_create_params *params, Window existing_window) { + mgl_window_x11 *impl = self->impl; + impl->window = 0; + impl->clipboard_string = NULL; + impl->clipboard_size = 0; + + mgl_vec2i window_size = params ? params->size : (mgl_vec2i){ 0, 0 }; + if(window_size.x <= 0 || window_size.y <= 0) { + window_size.x = 640; + window_size.y = 480; + } + self->size = window_size; + + mgl_vec2i window_pos = params ? params->position : (mgl_vec2i){ 0, 0 }; + mgl_context *context = mgl_get_context(); + + Window parent_window = params ? (Window)params->parent_window : None; + if(parent_window == 0) + parent_window = DefaultRootWindow(context->connection); + + XVisualInfo *visual_info = mgl_graphics_get_xvisual_info(&impl->graphics); + impl->color_map = XCreateColormap(context->connection, DefaultRootWindow(context->connection), visual_info->visual, AllocNone); + if(!impl->color_map) { + fprintf(stderr, "mgl error: mgl_x11_setup_window: XCreateColormap failed\n"); + return false; + } + + XSetWindowAttributes window_attr; + window_attr.override_redirect = params ? params->override_redirect : false; + window_attr.colormap = impl->color_map; + window_attr.background_pixel = mgl_color_to_x11_pixel(params ? params->background_color : (mgl_color){ .r = 0, .g = 0, .b = 0, .a = 255 }); + window_attr.border_pixel = 0; + window_attr.bit_gravity = NorthWestGravity; + window_attr.event_mask = + KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | + PointerMotionMask | Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | ButtonMotionMask | + StructureNotifyMask | EnterWindowMask | LeaveWindowMask | VisibilityChangeMask | PropertyChangeMask | FocusChangeMask; + + const bool hide_window = params ? params->hidden : false; + + if(existing_window) { + if(!XChangeWindowAttributes(context->connection, existing_window, CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr)) { + fprintf(stderr, "mgl error: mgl_x11_setup_window: XChangeWindowAttributes failed\n"); + return false; + } + + if(params && params->size.x > 0 && params->size.y > 0) { + XResizeWindow(context->connection, existing_window, params->size.x, params->size.y); + } + + impl->window = existing_window; + + if(params && params->hide_decorations) { + mgl_window_x11_set_decorations_visible(self, false); + } + + if(hide_window) + XUnmapWindow(context->connection, existing_window); + } else { + impl->window = XCreateWindow(context->connection, parent_window, window_pos.x, window_pos.y, + window_size.x, window_size.y, 0, + visual_info->depth, InputOutput, visual_info->visual, + CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr); + if(!impl->window) { + fprintf(stderr, "mgl error: mgl_x11_setup_window: XCreateWindow failed\n"); + return false; + } + + if(params && params->hide_decorations) { + mgl_window_x11_set_decorations_visible(self, false); + } + + mgl_window_x11_set_title(self, title); + if(!hide_window) + XMapWindow(context->connection, impl->window); + } + + if(params) + mgl_window_x11_set_size_limits(self, params->min_size, params->max_size); + + /* TODO: Call XGetWMProtocols and add wm_delete_window_atom on top, to not overwrite existing wm protocol atoms */ + Atom wm_protocol_atoms[2] = { + context->wm_delete_window_atom, + context->net_wm_ping_atom + }; + XSetWMProtocols(context->connection, impl->window, wm_protocol_atoms, 2); + + if(context->net_wm_pid_atom) { + const long pid = getpid(); + XChangeProperty(context->connection, impl->window, context->net_wm_pid_atom, XA_CARDINAL, + 32, PropModeReplace, (const unsigned char*)&pid, 1); + } + + char host_name[HOST_NAME_MAX]; + if(gethostname(host_name, sizeof(host_name)) == 0) { + XTextProperty txt_prop; + txt_prop.value = (unsigned char*)host_name; + txt_prop.encoding = XA_STRING; + txt_prop.format = 8; + txt_prop.nitems = strlen(host_name); + XSetWMClientMachine(context->connection, impl->window, &txt_prop); + } + + if(params && params->class_name) { + XClassHint class_hint = { (char*)params->class_name, (char*)params->class_name }; + XSetClassHint(context->connection, impl->window, &class_hint); + } + + if(params && params->transient_for_window) { + XSetTransientForHint(context->connection, impl->window, (Window)params->transient_for_window); + } + + /* TODO: Move this to above XMapWindow? */ + mgl_window_type window_type = params ? params->window_type : MGL_WINDOW_TYPE_NORMAL; + mgl_set_window_type(self, window_type); + + XFlush(context->connection); + + if(!mgl_graphics_make_context_current(&impl->graphics, (mgl_window_handle)impl->window)) { + fprintf(stderr, "mgl error: mgl_x11_setup_window: failed to make window context current\n"); + return false; + } + + self->vsync_enabled = true; + mgl_graphics_set_swap_interval(&impl->graphics, (mgl_window_handle)impl->window, self->vsync_enabled); + + Window dummy_w; + int dummy_i; + unsigned int dummy_u; + XQueryPointer(context->connection, impl->window, &dummy_w, &dummy_w, &dummy_i, &dummy_i, &self->cursor_position.x, &self->cursor_position.y, &dummy_u); + + impl->xim = XOpenIM(context->connection, NULL, NULL, NULL); + if(!impl->xim) { + fprintf(stderr, "mgl error: mgl_x11_setup_window: XOpenIM failed\n"); + return false; + } + + impl->xic = XCreateIC(impl->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, impl->window, NULL); + if(!impl->xic) { + fprintf(stderr, "mgl error: mgl_x11_setup_window: XCreateIC failed\n"); + return false; + } + + // TODO: This should be done once and monitor events should be done once, no matter how many windows you have + mgl_window_x11_clear_monitors(self); + mgl_window_x11_for_each_active_monitor_output(self, monitor_callback_add_to_mgl_window_x11, self); + + mgl_window_x11_on_resize(self, self->size.x, self->size.y); + mgl_window_x11_on_move(self, window_pos.x, window_pos.y); + + self->open = true; + self->focused = false; /* TODO: Check if we need to call XGetInputFocus for this, or just wait for focus event */ + return true; +} + +/* Returns MGL_KEY_UNKNOWN on no match */ +static mgl_key x11_keysym_to_mgl_key(KeySym key_sym) { + if(key_sym >= XK_A && key_sym <= XK_Z) + return MGL_KEY_A + (key_sym - XK_A); + /* TODO: Check if this ever happens */ + if(key_sym >= XK_a && key_sym <= XK_z) + return MGL_KEY_A + (key_sym - XK_a); + if(key_sym >= XK_0 && key_sym <= XK_9) + return MGL_KEY_NUM0 + (key_sym - XK_0); + if(key_sym >= XK_KP_0 && key_sym <= XK_KP_9) + return MGL_KEY_NUMPAD0 + (key_sym - XK_KP_0); + + /* TODO: Fill in the rest */ + switch(key_sym) { + case XK_space: return MGL_KEY_SPACE; + case XK_BackSpace: return MGL_KEY_BACKSPACE; + case XK_Tab: return MGL_KEY_TAB; + case XK_Return: return MGL_KEY_ENTER; + case XK_Escape: return MGL_KEY_ESCAPE; + case XK_Control_L: return MGL_KEY_LCONTROL; + case XK_Shift_L: return MGL_KEY_LSHIFT; + case XK_Alt_L: return MGL_KEY_LALT; + case XK_Super_L: return MGL_KEY_LSYSTEM; + case XK_Control_R: return MGL_KEY_RCONTROL; + case XK_Shift_R: return MGL_KEY_RSHIFT; + case XK_Alt_R: return MGL_KEY_RALT; + case XK_Super_R: return MGL_KEY_RSYSTEM; + case XK_Delete: return MGL_KEY_DELETE; + case XK_Home: return MGL_KEY_HOME; + case XK_Left: return MGL_KEY_LEFT; + case XK_Up: return MGL_KEY_UP; + case XK_Right: return MGL_KEY_RIGHT; + case XK_Down: return MGL_KEY_DOWN; + case XK_Page_Up: return MGL_KEY_PAGEUP; + case XK_Page_Down: return MGL_KEY_PAGEDOWN; + case XK_End: return MGL_KEY_END; + case XK_F1: return MGL_KEY_F1; + case XK_F2: return MGL_KEY_F2; + case XK_F3: return MGL_KEY_F3; + case XK_F4: return MGL_KEY_F4; + case XK_F5: return MGL_KEY_F5; + case XK_F6: return MGL_KEY_F6; + case XK_F7: return MGL_KEY_F7; + case XK_F8: return MGL_KEY_F8; + case XK_F9: return MGL_KEY_F9; + case XK_F10: return MGL_KEY_F10; + case XK_F11: return MGL_KEY_F11; + case XK_F12: return MGL_KEY_F12; + case XK_F13: return MGL_KEY_F13; + case XK_F14: return MGL_KEY_F14; + case XK_F15: return MGL_KEY_F15; + case XK_Insert: return MGL_KEY_INSERT; + case XK_Pause: return MGL_KEY_PAUSE; + case XK_Print: return MGL_KEY_PRINTSCREEN; + case XK_KP_Insert: return MGL_KEY_NUMPAD0; + case XK_KP_End: return MGL_KEY_NUMPAD1; + case XK_KP_Down: return MGL_KEY_NUMPAD2; + case XK_KP_Page_Down: return MGL_KEY_NUMPAD3; + case XK_KP_Left: return MGL_KEY_NUMPAD4; + case XK_KP_Begin: return MGL_KEY_NUMPAD5; + case XK_KP_Right: return MGL_KEY_NUMPAD6; + case XK_KP_Home: return MGL_KEY_NUMPAD7; + case XK_KP_Up: return MGL_KEY_NUMPAD8; + case XK_KP_Page_Up: return MGL_KEY_NUMPAD9; + case XK_KP_Enter: return MGL_KEY_NUMPAD_ENTER; + case XF86XK_AudioLowerVolume: return MGL_KEY_AUDIO_LOWER_VOLUME; + case XF86XK_AudioRaiseVolume: return MGL_KEY_AUDIO_RAISE_VOLUME; + case XF86XK_AudioPlay: return MGL_KEY_AUDIO_PLAY; + case XF86XK_AudioStop: return MGL_KEY_AUDIO_STOP; + case XF86XK_AudioPause: return MGL_KEY_AUDIO_PAUSE; + case XF86XK_AudioMute: return MGL_KEY_AUDIO_MUTE; + case XF86XK_AudioPrev: return MGL_KEY_AUDIO_PREV; + case XF86XK_AudioNext: return MGL_KEY_AUDIO_NEXT; + case XF86XK_AudioRewind: return MGL_KEY_AUDIO_REWIND; + case XF86XK_AudioForward: return MGL_KEY_AUDIO_FORWARD; + case XK_dead_acute: return MGL_KEY_DEAD_ACUTE; + case XK_apostrophe: return MGL_KEY_APOSTROPHE; + case XK_F16: return MGL_KEY_F16; + case XK_F17: return MGL_KEY_F17; + case XK_F18: return MGL_KEY_F18; + case XK_F19: return MGL_KEY_F19; + case XK_F20: return MGL_KEY_F20; + case XK_F21: return MGL_KEY_F21; + case XK_F22: return MGL_KEY_F22; + case XK_F23: return MGL_KEY_F23; + case XK_F24: return MGL_KEY_F24; + } + return MGL_KEY_UNKNOWN; +} + +static mgl_mouse_button x11_button_to_mgl_button(unsigned int button) { + switch(button) { + case Button1: return MGL_BUTTON_LEFT; + case Button2: return MGL_BUTTON_MIDDLE; + case Button3: return MGL_BUTTON_RIGHT; + case 8: return MGL_BUTTON_XBUTTON1; + case 9: return MGL_BUTTON_XBUTTON2; + } + return MGL_BUTTON_UNKNOWN; +} + +static void mgl_window_handle_key_event(XKeyEvent *xkey, mgl_event *event, mgl_context *context) { + event->key.code = x11_keysym_to_mgl_key(XKeycodeToKeysym(context->connection, xkey->keycode, 0)); + event->key.alt = ((xkey->state & Mod1Mask) != 0) || ((xkey->state & Mod5Mask) != 0); + event->key.control = ((xkey->state & ControlMask) != 0); + event->key.shift = ((xkey->state & ShiftMask) != 0); + event->key.system = ((xkey->state & Mod4Mask) != 0); +} + +static void mgl_window_handle_text_event(mgl_window_x11 *self, XEvent *xev) { + /* Ignore silent keys */ + if(XFilterEvent(xev, None)) + return; + + char buf[128]; + + Status status; + KeySym ksym; + const size_t input_str_len = Xutf8LookupString(self->xic, &xev->xkey, buf, sizeof(buf), &ksym, &status); + /* TODO: Handle XBufferOverflow */ + if(status == XBufferOverflow || input_str_len == 0) + return; + + /* TODO: Set XIC location on screen with XSetICValues */ + + for(size_t i = 0; i < input_str_len;) { + const unsigned char *cp = (const unsigned char*)&buf[i]; + uint32_t codepoint; + size_t clen; + if(!mgl_utf8_decode(cp, input_str_len - i, &codepoint, &clen)) { + codepoint = *cp; + clen = 1; + } + + mgl_event text_event; + text_event.type = MGL_EVENT_TEXT_ENTERED; + text_event.text.codepoint = codepoint; + text_event.text.size = clen; + memcpy(text_event.text.str, &buf[i], clen); + text_event.text.str[clen] = '\0'; + /* Text may contain multiple codepoints so they need to separated into multiple events */ + if(!mgl_window_x11_append_event(self, &text_event)) + break; + + i += clen; + } +} + +static bool mgl_on_monitor_added(Display *display, mgl_window *mgl_window, XRROutputChangeNotifyEvent *rr_output_change_event, XRRScreenResources *screen_res, RROutput output_id, XRROutputInfo *out_info, mgl_event *event) { + char display_name[256]; + mgl_monitor *monitor = NULL; + XRRCrtcInfo *crt_info = NULL; + const XRRModeInfo *mode_info = NULL; + + if(!rr_output_change_event->mode) + return false; + + if(mgl_window_x11_get_monitor_by_id(mgl_window, output_id)) + return false; + + if(out_info->nameLen >= (int)sizeof(display_name)) + return false; + + crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc); + if(!crt_info) + goto done; + + mode_info = get_mode_info(screen_res, rr_output_change_event->mode); + if(!mode_info) + goto done; + + memcpy(display_name, out_info->name, out_info->nameLen); + display_name[out_info->nameLen] = '\0'; + + monitor = mgl_window_x11_add_monitor(mgl_window, output_id, out_info->crtc, display_name, + (mgl_vec2i){ .x = crt_info->x, .y = crt_info->y }, + (mgl_vec2i){ .x = (int)crt_info->width, .y = (int)crt_info->height }, + monitor_info_get_framerate(mode_info)); + + if(!monitor) + goto done; + + event->monitor_connected.id = monitor->id; + event->monitor_connected.name = monitor->name; + event->monitor_connected.x = monitor->pos.x; + event->monitor_connected.y = monitor->pos.y; + event->monitor_connected.width = monitor->size.x; + event->monitor_connected.height = monitor->size.y; + event->monitor_connected.refresh_rate = monitor->refresh_rate; + event->type = MGL_EVENT_MONITOR_CONNECTED; + + done: + if(crt_info) + XRRFreeCrtcInfo(crt_info); + return monitor != NULL; +} + +static bool mgl_on_monitor_state_changed(Display *display, mgl_window *mgl_window, XRROutputChangeNotifyEvent *rr_output_change_event, mgl_event *event) { + bool state_changed = false; + XRROutputInfo *out_info = NULL; + + if(!rr_output_change_event->output) + return false; + + XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display)); + if(!screen_res) + return false; + + out_info = XRRGetOutputInfo(display, screen_res, rr_output_change_event->output); + if(out_info && out_info->crtc && out_info->connection == RR_Connected) { + state_changed = mgl_on_monitor_added(display, mgl_window, rr_output_change_event, screen_res, rr_output_change_event->output, out_info, event); + } else { + state_changed = mgl_window_x11_remove_monitor(mgl_window, rr_output_change_event->output, event); + } + + if(out_info) + XRRFreeOutputInfo(out_info); + + XRRFreeScreenResources(screen_res); + return state_changed; +} + +static bool mgl_on_monitor_property_changed(Display *display, mgl_window *mgl_window, XRRCrtcChangeNotifyEvent *rr_crtc_change_event, mgl_event *event) { + if(!rr_crtc_change_event->crtc) + return false; + + mgl_monitor *monitor = mgl_window_x11_get_monitor_by_crtc_id(mgl_window, rr_crtc_change_event->crtc); + if(!monitor) + return false; + + XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display)); + if(!screen_res) + return false; + + monitor->pos = (mgl_vec2i){ .x = rr_crtc_change_event->x, .y = rr_crtc_change_event->y }; + monitor->size = (mgl_vec2i){ .x = rr_crtc_change_event->width, .y = rr_crtc_change_event->height }; + const XRRModeInfo *mode_info = get_mode_info(screen_res, rr_crtc_change_event->mode); + if(mode_info) + monitor->refresh_rate = monitor_info_get_framerate(mode_info); + + XRRFreeScreenResources(screen_res); + + event->monitor_property_changed.id = monitor->id; + event->monitor_property_changed.name = monitor->name; + event->monitor_property_changed.x = monitor->pos.x; + event->monitor_property_changed.y = monitor->pos.y; + event->monitor_property_changed.width = monitor->size.x; + event->monitor_property_changed.height = monitor->size.y; + event->monitor_property_changed.refresh_rate = monitor->refresh_rate; + event->type = MGL_EVENT_MONITOR_PROPERTY_CHANGED; + return true; +} + +/* Returns true if an event was generated */ +static bool mgl_on_rr_notify(mgl_context *context, mgl_window *mgl_window, XEvent *xev, int subtype, mgl_event *event) { + switch(subtype) { + case RRNotify_CrtcChange: { + XRRCrtcChangeNotifyEvent *rr_crtc_change_event = (XRRCrtcChangeNotifyEvent*)xev; + return mgl_on_monitor_property_changed(context->connection, mgl_window, rr_crtc_change_event, event); + } + case RRNotify_OutputChange: { + XRROutputChangeNotifyEvent *rr_output_change_event = (XRROutputChangeNotifyEvent*)xev; + return mgl_on_monitor_state_changed(context->connection, mgl_window, rr_output_change_event, event); + } + } + return false; +} + +static void mgl_window_x11_on_receive_event(mgl_window *self, XEvent *xev, mgl_event *event, mgl_context *context, bool injected) { + mgl_window_x11 *impl = self->impl; + switch(xev->type - context->randr_event_base) { + case RRScreenChangeNotify: { + XRRUpdateConfiguration(xev); + event->type = MGL_EVENT_UNKNOWN; + return; + } + case RRNotify: { + XRRNotifyEvent *rr_event = (XRRNotifyEvent*)xev; + if(mgl_on_rr_notify(context, self, xev, rr_event->subtype, event)) { + // TODO: + //impl->num_monitors = mgl_window_x11->num_monitors; + return; + } + + event->type = MGL_EVENT_UNKNOWN; + return; + } + } + + switch(xev->type) { + case KeyPress: { + if(!self->key_repeat_enabled && xev->xkey.keycode == impl->prev_keycode_pressed && !impl->key_was_released) { + event->type = MGL_EVENT_UNKNOWN; + return; + } + + impl->prev_keycode_pressed = xev->xkey.keycode; + impl->key_was_released = false; + + event->type = MGL_EVENT_KEY_PRESSED; + mgl_window_handle_key_event(&xev->xkey, event, context); + mgl_window_handle_text_event(impl, xev); + return; + } + case KeyRelease: { + if(xev->xkey.keycode == impl->prev_keycode_pressed) + impl->key_was_released = true; + + event->type = MGL_EVENT_KEY_RELEASED; + mgl_window_handle_key_event(&xev->xkey, event, context); + return; + } + case ButtonPress: { + if(xev->xbutton.button == Button4) { + /* Mouse scroll up */ + event->type = MGL_EVENT_MOUSE_WHEEL_SCROLLED; + event->mouse_wheel_scroll.delta = 1; + event->mouse_wheel_scroll.x = xev->xbutton.x; + event->mouse_wheel_scroll.y = xev->xbutton.y; + } else if(xev->xbutton.button == Button5) { + /* Mouse scroll down */ + event->type = MGL_EVENT_MOUSE_WHEEL_SCROLLED; + event->mouse_wheel_scroll.delta = -1; + event->mouse_wheel_scroll.x = xev->xbutton.x; + event->mouse_wheel_scroll.y = xev->xbutton.y; + } else { + event->type = MGL_EVENT_MOUSE_BUTTON_PRESSED; + event->mouse_button.button = x11_button_to_mgl_button(xev->xbutton.button); + event->mouse_button.x = xev->xbutton.x; + event->mouse_button.y = xev->xbutton.y; + } + return; + } + case ButtonRelease: { + event->type = MGL_EVENT_MOUSE_BUTTON_RELEASED; + event->mouse_button.button = x11_button_to_mgl_button(xev->xbutton.button); + event->mouse_button.x = xev->xbutton.x; + event->mouse_button.y = xev->xbutton.y; + return; + } + case FocusIn: { + XSetICFocus(impl->xic); + + XWMHints* hints = XGetWMHints(context->connection, impl->window); + if(hints) { + hints->flags &= ~XUrgencyHint; + XSetWMHints(context->connection, impl->window, hints); + XFree(hints); + } + + event->type = MGL_EVENT_GAINED_FOCUS; + self->focused = true; + return; + } + case FocusOut: { + XUnsetICFocus(impl->xic); + event->type = MGL_EVENT_LOST_FOCUS; + self->focused = false; + return; + } + case ConfigureNotify: { + if(!injected) + while(XCheckTypedWindowEvent(context->connection, impl->window, ConfigureNotify, xev)) {} + + if(xev->xconfigure.x != self->pos.x || xev->xconfigure.y != self->pos.y) { + mgl_window_x11_on_move(self, xev->xconfigure.x, xev->xconfigure.y); + } + + if(xev->xconfigure.width != self->size.x || xev->xconfigure.height != self->size.y) { + mgl_window_x11_on_resize(self, xev->xconfigure.width, xev->xconfigure.height); + + event->type = MGL_EVENT_RESIZED; + event->size.width = self->size.x; + event->size.height = self->size.y; + return; + } + + event->type = MGL_EVENT_UNKNOWN; + return; + } + case MotionNotify: { + if(!injected) + while(XCheckTypedWindowEvent(context->connection, impl->window, MotionNotify, xev)) {} + + self->cursor_position.x = xev->xmotion.x; + self->cursor_position.y = xev->xmotion.y; + + event->type = MGL_EVENT_MOUSE_MOVED; + event->mouse_move.x = self->cursor_position.x; + event->mouse_move.y = self->cursor_position.y; + return; + } + case SelectionClear: { + event->type = MGL_EVENT_UNKNOWN; + return; + } + case SelectionRequest: { + XSelectionEvent selection_event; + selection_event.type = SelectionNotify; + selection_event.requestor = xev->xselectionrequest.requestor; + selection_event.selection = xev->xselectionrequest.selection; + selection_event.property = xev->xselectionrequest.property; + selection_event.time = xev->xselectionrequest.time; + selection_event.target = xev->xselectionrequest.target; + + if(selection_event.selection == impl->clipboard_atom) { + /* TODO: Support ascii text separately by unsetting the 8th bit in the clipboard string? */ + if(selection_event.target == impl->targets_atom) { + /* A client requested for our valid conversion targets */ + int num_targets = 3; + Atom targets[4]; + targets[0] = impl->targets_atom; + targets[1] = impl->text_atom; + targets[2] = XA_STRING; + if(impl->utf8_string_atom) { + targets[3] = impl->utf8_string_atom; + num_targets = 4; + } + + XChangeProperty(context->connection, selection_event.requestor, selection_event.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, num_targets); + selection_event.target = impl->targets_atom; + XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event); + XFlush(context->connection); + event->type = MGL_EVENT_UNKNOWN; + return; + } else if(selection_event.target == XA_STRING || (!impl->utf8_string_atom && selection_event.target == impl->text_atom)) { + /* A client requested ascii clipboard */ + XChangeProperty(context->connection, selection_event.requestor, selection_event.property, XA_STRING, 8, PropModeReplace, (const unsigned char*)impl->clipboard_string, impl->clipboard_size); + selection_event.target = XA_STRING; + XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event); + XFlush(context->connection); + event->type = MGL_EVENT_UNKNOWN; + return; + } else if(impl->utf8_string_atom && (selection_event.target == impl->utf8_string_atom || selection_event.target == impl->text_atom)) { + /* A client requested utf8 clipboard */ + XChangeProperty(context->connection, selection_event.requestor, selection_event.property, impl->utf8_string_atom, 8, PropModeReplace, (const unsigned char*)impl->clipboard_string, impl->clipboard_size); + selection_event.target = impl->utf8_string_atom; + XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event); + XFlush(context->connection); + event->type = MGL_EVENT_UNKNOWN; + return; + } + } + + selection_event.property = None; + XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event); + XFlush(context->connection); + event->type = MGL_EVENT_UNKNOWN; + return; + } + case ClientMessage: { + if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->wm_delete_window_atom) { + event->type = MGL_EVENT_CLOSED; + self->open = false; + return; + } else if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->net_wm_ping_atom) { + xev->xclient.window = DefaultRootWindow(context->connection); + XSendEvent(context->connection, DefaultRootWindow(context->connection), False, SubstructureNotifyMask | SubstructureRedirectMask, xev); + XFlush(context->connection); + } + event->type = MGL_EVENT_UNKNOWN; + return; + } + case MappingNotify: { + /* TODO: Only handle this globally once for mgl, not for each window */ + XRefreshKeyboardMapping(&xev->xmapping); + event->type = MGL_EVENT_MAPPING_CHANGED; + event->mapping_changed.type = MappingModifier; + switch(xev->xmapping.request) { + case MappingModifier: + event->mapping_changed.type = MGL_MAPPING_CHANGED_MODIFIER; + break; + case MappingKeyboard: + event->mapping_changed.type = MGL_MAPPING_CHANGED_KEYBOARD; + break; + case MappingPointer: + event->mapping_changed.type = MGL_MAPPING_CHANGED_POINTER; + break; + } + return; + } + default: { + event->type = MGL_EVENT_UNKNOWN; + return; + } + } +} + +static bool mgl_window_x11_poll_event(mgl_window *self, mgl_event *event) { + mgl_window_x11 *impl = self->impl; + mgl_context *context = mgl_get_context(); + Display *display = context->connection; + + if(mgl_window_x11_pop_event(impl, event)) + return true; + + if(XPending(display)) { + XNextEvent(display, &impl->xev); + if(impl->xev.xany.window == impl->window || impl->xev.type == ClientMessage || impl->xev.type == MappingNotify || impl->xev.type - context->randr_event_base >= 0) + mgl_window_x11_on_receive_event(self, &impl->xev, event, context, false); + else + event->type = MGL_EVENT_UNKNOWN; + return true; + } else { + return false; + } +} + +static bool mgl_window_x11_inject_x11_event(mgl_window *self, XEvent *xev, mgl_event *event) { + mgl_context *context = mgl_get_context(); + event->type = MGL_EVENT_UNKNOWN; + mgl_window_x11_on_receive_event(self, xev, event, context, true); + return event->type != MGL_EVENT_UNKNOWN; +} + +static void mgl_window_x11_swap_buffers(mgl_window *self) { + mgl_window_x11 *impl = self->impl; + mgl_graphics_swap_buffers(&impl->graphics, (mgl_window_handle)impl->window); +} + +static void mgl_window_x11_set_visible(mgl_window *self, bool visible) { + mgl_window_x11 *impl = self->impl; + mgl_context *context = mgl_get_context(); + if(visible) + XMapWindow(context->connection, impl->window); + else + XUnmapWindow(context->connection, impl->window); + XFlush(context->connection); +} + +/* TODO: Track keys with events instead, but somehow handle window focus lost */ +static bool mgl_window_x11_is_key_pressed(const mgl_window *self, mgl_key key) { + (void)self; + if(key < 0 || key >= __MGL_NUM_KEYS__) + return false; + + mgl_context *context = mgl_get_context(); + const KeySym keysym = mgl_key_to_x11_keysym(key); + if(keysym == XK_VoidSymbol) + return false; + + KeyCode keycode = XKeysymToKeycode(context->connection, keysym); + if(keycode == 0) + return false; + + char keys[32]; + XQueryKeymap(context->connection, keys); + + return (keys[keycode / 8] & (1 << (keycode % 8))) != 0; +} + +/* TODO: Track keys with events instead, but somehow handle window focus lost */ +static bool mgl_window_x11_is_mouse_button_pressed(const mgl_window *self, mgl_mouse_button button) { + (void)self; + if(button < 0 || button >= __MGL_NUM_MOUSE_BUTTONS__) + return false; + + mgl_context *context = mgl_get_context(); + Window root, child; + int root_x, root_y, win_x, win_y; + + unsigned int buttons_mask = 0; + XQueryPointer(context->connection, DefaultRootWindow(context->connection), &root, &child, &root_x, &root_y, &win_x, &win_y, &buttons_mask); + + switch(button) { + case MGL_BUTTON_LEFT: return buttons_mask & Button1Mask; + case MGL_BUTTON_MIDDLE: return buttons_mask & Button2Mask; + case MGL_BUTTON_RIGHT: return buttons_mask & Button3Mask; + case MGL_BUTTON_XBUTTON1: return false; /* Not supported by x11 */ + case MGL_BUTTON_XBUTTON2: return false; /* Not supported by x11 */ + default: return false; + } + + return false; +} + +static mgl_window_handle mgl_window_x11_get_system_handle(const mgl_window *self) { + const mgl_window_x11 *impl = self->impl; + return (mgl_window_handle)impl->window; +} + +static void mgl_window_x11_close(mgl_window *self) { + mgl_window_x11 *impl = self->impl; + mgl_context *context = mgl_get_context(); + if(impl->window) { + XDestroyWindow(context->connection, impl->window); + XSync(context->connection, False); + impl->window = None; + } + self->open = false; +} + +static void mgl_window_x11_set_cursor_visible(mgl_window *self, bool visible) { + mgl_window_x11 *impl = self->impl; + mgl_context *context = mgl_get_context(); + XDefineCursor(context->connection, impl->window, visible ? impl->default_cursor : impl->invisible_cursor); +} + +static void mgl_window_x11_set_vsync_enabled(mgl_window *self, bool enabled) { + mgl_window_x11 *impl = self->impl; + self->vsync_enabled = enabled; + mgl_graphics_set_swap_interval(&impl->graphics, (mgl_window_handle)impl->window, self->vsync_enabled); +} + +static bool mgl_window_x11_is_vsync_enabled(const mgl_window *self) { + return self->vsync_enabled; +} + +static void mgl_window_x11_set_fullscreen(mgl_window *self, bool fullscreen) { + mgl_window_x11 *impl = self->impl; + mgl_context *context = mgl_get_context(); + + XEvent xev; + xev.type = ClientMessage; + xev.xclient.window = impl->window; + xev.xclient.message_type = impl->net_wm_state_atom; + xev.xclient.format = 32; + xev.xclient.data.l[0] = fullscreen ? 1 : 0; + xev.xclient.data.l[1] = impl->net_wm_state_fullscreen_atom; + xev.xclient.data.l[2] = 0; + xev.xclient.data.l[3] = 1; + xev.xclient.data.l[4] = 0; + + if(!XSendEvent(context->connection, DefaultRootWindow(context->connection), 0, SubstructureRedirectMask | SubstructureNotifyMask, &xev)) { + fprintf(stderr, "mgl warning: failed to change window fullscreen state\n"); + return; + } + + XFlush(context->connection); +} + +static bool mgl_window_x11_is_fullscreen(const mgl_window *self) { + mgl_window_x11 *impl = self->impl; + mgl_context *context = mgl_get_context(); + + bool is_fullscreen = false; + Atom type = None; + int format = 0; + unsigned long items = 0; + unsigned long remaining_bytes = 0; + unsigned char *data = NULL; + if(XGetWindowProperty(context->connection, impl->window, impl->net_wm_state_atom, 0, 1024, False, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success && data) { + is_fullscreen = format == 32 && *(unsigned long*)data == impl->net_wm_state_fullscreen_atom; + XFree(data); + } + return is_fullscreen; +} + +static void mgl_window_x11_set_position(mgl_window *self, mgl_vec2i position) { + mgl_window_x11 *impl = self->impl; + XMoveWindow(mgl_get_context()->connection, impl->window, position.x, position.y); + XFlush(mgl_get_context()->connection); +} + +static void mgl_window_x11_set_size(mgl_window *self, mgl_vec2i size) { + mgl_window_x11 *impl = self->impl; + if(size.x < 0) + size.x = 0; + if(size.y < 0) + size.y = 0; + XResizeWindow(mgl_get_context()->connection, impl->window, size.x, size.y); + XFlush(mgl_get_context()->connection); +} + +static void mgl_window_x11_set_clipboard(mgl_window *self, const char *str, size_t size) { + mgl_window_x11 *impl = self->impl; + mgl_context *context = mgl_get_context(); + + if(impl->clipboard_string) { + free(impl->clipboard_string); + impl->clipboard_string = NULL; + impl->clipboard_size = 0; + } + + XSetSelectionOwner(context->connection, impl->clipboard_atom, impl->window, CurrentTime); + XFlush(context->connection); + + /* Check if setting the selection owner was successful */ + if(XGetSelectionOwner(context->connection, impl->clipboard_atom) != impl->window) { + fprintf(stderr, "mgl error: mgl_window_x11_set_clipboard failed\n"); + return; + } + + impl->clipboard_string = malloc(size + 1); + if(!impl->clipboard_string) { + fprintf(stderr, "mgl error: failed to allocate string for clipboard\n"); + return; + } + memcpy(impl->clipboard_string, str, size); + impl->clipboard_string[size] = '\0'; + impl->clipboard_size = size; +} + +static Atom find_matching_atom(const Atom *supported_atoms, size_t num_supported_atoms, const Atom *atoms, size_t num_atoms) { + for(size_t j = 0; j < num_supported_atoms; ++j) { + for(size_t i = 0; i < num_atoms; ++i) { + if(atoms[i] == supported_atoms[j]) + return atoms[i]; + } + } + return None; +} + +static mgl_clipboard_type atom_type_to_supported_clipboard_type(mgl_window_x11 *mgl_window_x11, Atom type, int format) { + if((type == mgl_window_x11->utf8_string_atom || type == XA_STRING || type == mgl_window_x11->text_atom) && format == 8) { + return MGL_CLIPBOARD_TYPE_STRING; + } else if(type == mgl_window_x11->image_png_atom && format == 8){ + return MGL_CLIPBOARD_TYPE_IMAGE_PNG; + } else if((type == mgl_window_x11->image_jpg_atom || type == mgl_window_x11->image_jpeg_atom) && format == 8){ + return MGL_CLIPBOARD_TYPE_IMAGE_JPG; + } else if(type == mgl_window_x11->image_gif_atom && format == 8){ + return MGL_CLIPBOARD_TYPE_IMAGE_GIF; + } else { + return -1; + } +} + +static bool mgl_window_x11_get_clipboard(mgl_window *self, mgl_clipboard_callback callback, void *userdata, uint32_t clipboard_types) { + mgl_window_x11 *impl = self->impl; + assert(callback); + + mgl_context *context = mgl_get_context(); + Window selection_owner = XGetSelectionOwner(context->connection, impl->clipboard_atom); + + if(!selection_owner) + return false; + + /* Return data immediately if we are the owner of the clipboard, because we can't process other events in the below event loop */ + if(selection_owner == impl->window) { + if(!impl->clipboard_string) + return false; + + return callback((const unsigned char*)impl->clipboard_string, impl->clipboard_size, MGL_CLIPBOARD_TYPE_STRING, userdata); + } + + XEvent xev; + while(XCheckTypedWindowEvent(context->connection, impl->window, SelectionNotify, &xev)) {} + + /* Sorted by preference */ + /* TODO: Support more types (BITMAP?, PIXMAP?) */ + Atom supported_clipboard_types[7]; + + int supported_clipboard_type_index = 0; + if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_PNG) { + supported_clipboard_types[supported_clipboard_type_index++] = impl->image_png_atom; + } + + if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_JPG) { + supported_clipboard_types[supported_clipboard_type_index++] = impl->image_jpeg_atom; + } + + if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_GIF) { + supported_clipboard_types[supported_clipboard_type_index++] = impl->image_gif_atom; + } + + if(clipboard_types & MGL_CLIPBOARD_TYPE_STRING) { + supported_clipboard_types[supported_clipboard_type_index++] = impl->utf8_string_atom; + supported_clipboard_types[supported_clipboard_type_index++] = XA_STRING; + supported_clipboard_types[supported_clipboard_type_index++] = impl->text_atom; + } + + const unsigned long num_supported_clipboard_types = supported_clipboard_type_index; + + Atom requested_clipboard_type = None; + const Atom XA_TARGETS = XInternAtom(context->connection, "TARGETS", False); + XConvertSelection(context->connection, impl->clipboard_atom, XA_TARGETS, impl->clipboard_atom, impl->window, CurrentTime); + + mgl_clock timeout_timer; + mgl_clock_init(&timeout_timer); + bool success = false; + + const double timeout_seconds = 5.0; + while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) { + /* TODO: Wait for SelectionNotify event instead */ + while(XCheckTypedWindowEvent(context->connection, impl->window, SelectionNotify, &xev)) { + if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds) + break; + + if(!xev.xselection.property) + continue; + + if(!xev.xselection.target || xev.xselection.selection != impl->clipboard_atom) + continue; + + Atom type = None; + int format; + unsigned long items; + unsigned long remaining_bytes = 0; + unsigned char *data = NULL; + + if(xev.xselection.target == XA_TARGETS && requested_clipboard_type == None) { + /* TODO: Wait for PropertyNotify event instead */ + if(XGetWindowProperty(context->connection, xev.xselection.requestor, xev.xselection.property, 0, 1024, False, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success && data) { + if(type != impl->incr_atom && type == XA_ATOM && format == 32) { + requested_clipboard_type = find_matching_atom(supported_clipboard_types, num_supported_clipboard_types, (Atom*)data, items); + if(requested_clipboard_type == None) { + /* Pasting clipboard data type we dont support */ + XFree(data); + goto done; + } else { + XConvertSelection(context->connection, impl->clipboard_atom, requested_clipboard_type, impl->clipboard_atom, impl->window, CurrentTime); + } + } + + XFree(data); + } + } else if(xev.xselection.target == requested_clipboard_type) { + bool got_data = false; + long chunk_size = 65536; + + //XDeleteProperty(context->connection, self->window, mgl_window_x11->incr_atom); + //XFlush(context->connection); + + while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) { + unsigned long offset = 0; + + /* offset is specified in XGetWindowProperty as a multiple of 32-bit (4 bytes) */ + /* TODO: Wait for PropertyNotify event instead */ + while(XGetWindowProperty(context->connection, xev.xselection.requestor, xev.xselection.property, offset/4, chunk_size, True, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success) { + if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds) + break; + + if(type == impl->incr_atom) { + if(data) + chunk_size = *(long*)data; + XDeleteProperty(context->connection, impl->window, impl->incr_atom); + XFlush(context->connection); + XFree(data); + data = NULL; + break; + } else if(type == requested_clipboard_type) { + got_data = true; + } + + if(!got_data && items == 0) { + XDeleteProperty(context->connection, impl->window, type); + XFlush(context->connection); + if(data) { + XFree(data); + data = NULL; + } + break; + } + + const size_t num_items_bytes = items * (format/8); /* format is the bit size of the data */ + if(got_data) { + const mgl_clipboard_type clipboard_type = atom_type_to_supported_clipboard_type(impl, type, format); + if(data && num_items_bytes > 0 && (int)clipboard_type != -1) { + if(!callback(data, num_items_bytes, clipboard_type, userdata)) { + XFree(data); + goto done; + } + } + } + offset += num_items_bytes; + + if(data) { + XFree(data); + data = NULL; + } + + if(got_data && num_items_bytes == 0/* && format == 8*/) { + success = true; + goto done; + } + + if(remaining_bytes == 0) + break; + } + } + + goto done; + } + } + } + + done: + if(requested_clipboard_type) + XDeleteProperty(context->connection, impl->window, requested_clipboard_type); + return success; +} + +static void mgl_window_x11_set_key_repeat_enabled(mgl_window *self, bool enabled) { + self->key_repeat_enabled = enabled; +} + +static void mgl_window_x11_flush(mgl_window *self) { + (void)self; + mgl_context *context = mgl_get_context(); + XFlush(context->connection); +} + +static void* mgl_window_x11_get_egl_display(mgl_window *self) { + mgl_window_x11 *impl = self->impl; + if(impl->graphics.graphics_api == MGL_GRAPHICS_API_EGL) + return mgl_graphics_get_display(&impl->graphics); + else + return NULL; +} + +static void* mgl_window_x11_get_egl_context(mgl_window *self) { + mgl_window_x11 *impl = self->impl; + if(impl->graphics.graphics_api == MGL_GRAPHICS_API_EGL) + return mgl_graphics_get_context(&impl->graphics); + else + return NULL; +} + +bool mgl_window_x11_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window) { + mgl_window_x11 *impl = calloc(1, sizeof(mgl_window_x11)); + if(!impl) + return false; + + self->get_system_handle = mgl_window_x11_get_system_handle; + self->deinit = mgl_window_x11_deinit; + self->close = mgl_window_x11_close; + self->inject_x11_event = mgl_window_x11_inject_x11_event; + self->poll_event = mgl_window_x11_poll_event; + self->swap_buffers = mgl_window_x11_swap_buffers; + self->set_visible = mgl_window_x11_set_visible; + self->is_key_pressed = mgl_window_x11_is_key_pressed; + self->is_mouse_button_pressed = mgl_window_x11_is_mouse_button_pressed; + self->set_title = mgl_window_x11_set_title; + self->set_cursor_visible = mgl_window_x11_set_cursor_visible; + self->set_vsync_enabled = mgl_window_x11_set_vsync_enabled; + self->is_vsync_enabled = mgl_window_x11_is_vsync_enabled; + self->set_fullscreen = mgl_window_x11_set_fullscreen; + self->is_fullscreen = mgl_window_x11_is_fullscreen; + self->set_position = mgl_window_x11_set_position; + self->set_size = mgl_window_x11_set_size; + self->set_size_limits = mgl_window_x11_set_size_limits; + self->set_clipboard = mgl_window_x11_set_clipboard; + self->get_clipboard = mgl_window_x11_get_clipboard; + self->set_key_repeat_enabled = mgl_window_x11_set_key_repeat_enabled; + self->flush = mgl_window_x11_flush; + self->get_egl_display = mgl_window_x11_get_egl_display; + self->get_egl_context = mgl_window_x11_get_egl_context; + self->for_each_active_monitor_output = mgl_window_x11_for_each_active_monitor_output; + self->impl = impl; + + /* TODO: Use CLIPBOARD_MANAGER and SAVE_TARGETS to save clipboard in clipboard manager on exit */ + + mgl_context *context = mgl_get_context(); + /* TODO: Create all of these with one XInternAtoms call instead */ + impl->clipboard_atom = XInternAtom(context->connection, "CLIPBOARD", False); + impl->targets_atom = XInternAtom(context->connection, "TARGETS", False); + impl->text_atom = XInternAtom(context->connection, "TEXT", False); + impl->utf8_string_atom = XInternAtom(context->connection, "UTF8_STRING", False); + impl->image_png_atom = XInternAtom(context->connection, "image/png", False); + impl->image_jpg_atom = XInternAtom(context->connection, "image/jpg", False); + impl->image_jpeg_atom = XInternAtom(context->connection, "image/jpeg", False); + impl->image_gif_atom = XInternAtom(context->connection, "image/gif", False); + impl->incr_atom = XInternAtom(context->connection, "INCR", False); + impl->net_wm_state_atom = XInternAtom(context->connection, "_NET_WM_STATE", False); + impl->net_wm_state_fullscreen_atom = XInternAtom(context->connection, "_NET_WM_STATE_FULLSCREEN", False); + impl->net_wm_state_above_atom = XInternAtom(context->connection, "_NET_WM_STATE_ABOVE", False); + impl->net_wm_name_atom = XInternAtom(context->connection, "_NET_WM_NAME", False); + impl->net_wm_window_type_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE", False); + impl->net_wm_window_type_normal_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_NORMAL", False); + impl->net_wm_window_type_dialog_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_DIALOG", False); + impl->net_wm_window_type_notification_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False); + impl->net_wm_window_type_utility = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_UTILITY", False); + impl->motif_wm_hints_atom = XInternAtom(context->connection, "_MOTIF_WM_HINTS", False); + + impl->support_alpha = params && params->support_alpha; + + const mgl_graphics_api graphics_api = params ? params->graphics_api : MGL_GRAPHICS_API_EGL; + const bool alpha = params && params->support_alpha; + if(!mgl_graphics_init(&impl->graphics, &(mgl_graphics_create_params){ .graphics_api = graphics_api, .alpha = alpha })) { + mgl_window_x11_deinit(self); + return false; + } + + impl->default_cursor = XCreateFontCursor(context->connection, XC_arrow); + if(!impl->default_cursor) { + mgl_window_x11_deinit(self); + return false; + } + + const char data[1] = {0}; + Pixmap blank_bitmap = XCreateBitmapFromData(context->connection, DefaultRootWindow(context->connection), data, 1, 1); + if(!blank_bitmap) { + mgl_window_x11_deinit(self); + return false; + } + + XColor dummy; + impl->invisible_cursor = XCreatePixmapCursor(context->connection, blank_bitmap, blank_bitmap, &dummy, &dummy, 0, 0); + XFreePixmap(context->connection, blank_bitmap); + if(!impl->invisible_cursor) { + mgl_window_x11_deinit(self); + return false; + } + + x11_events_circular_buffer_init(&impl->events); + + if(!mgl_x11_setup_window(self, title, params, (Window)existing_window)) { + mgl_window_x11_deinit(self); + return false; + } + + context->current_window = self; + return true; +} + +void mgl_window_x11_deinit(mgl_window *self) { + mgl_window_x11 *impl = self->impl; + mgl_context *context = mgl_get_context(); + if(!impl) + return; + + mgl_window_x11_clear_monitors(self); + + if(impl->color_map) { + XFreeColormap(context->connection, impl->color_map); + impl->color_map = None; + } + + if(impl->invisible_cursor) { + XFreeCursor(context->connection, impl->invisible_cursor); + impl->invisible_cursor = None; + } + + if(impl->default_cursor) { + XFreeCursor(context->connection, impl->default_cursor); + impl->default_cursor = None; + } + + if(impl->xic) { + XDestroyIC(impl->xic); + impl->xic = NULL; + } + + if(impl->xim) { + XCloseIM(impl->xim); + impl->xim = NULL; + } + + mgl_graphics_deinit(&impl->graphics); + mgl_window_x11_close(self); + + if(impl->clipboard_string) { + free(impl->clipboard_string); + impl->clipboard_string = NULL; + } + impl->clipboard_size = 0; + + self->open = false; + + if(context->current_window == self) + context->current_window = NULL; + + free(self->impl); + self->impl = NULL; +} diff --git a/tests/main.c b/tests/main.c index 720cf06..e3d199c 100644 --- a/tests/main.c +++ b/tests/main.c @@ -40,7 +40,7 @@ static void draw(mgl_window *window, void *userdata) { mgl_sprite sprite; mgl_sprite_init(&sprite, u->texture); - mgl_sprite_set_position(&sprite, (mgl_vec2f){ 100.0f - 10.0f + u->texture->width * 0.5f * 2.0f, u->texture->height * 0.5f * 2.0f }); + mgl_sprite_set_position(&sprite, (mgl_vec2f){ 500.0f - 10.0f + u->texture->width * 0.5f * 2.0f, 500.0f + u->texture->height * 0.5f * 2.0f }); mgl_sprite_set_color(&sprite, (mgl_color){255, 255, 255, 128}); mgl_sprite_set_rotation(&sprite, rot); mgl_sprite_set_origin(&sprite, (mgl_vec2f){ u->texture->width * 0.5f, u->texture->height * 0.5f }); @@ -61,6 +61,16 @@ static void draw(mgl_window *window, void *userdata) { mgl_text text; mgl_text_init(&text, u->font, str, strlen(str)); + mgl_text_set_max_width(&text, 200.0f); + //mgl_text_set_max_rows(&text, 5); + { + mgl_rectangle text_bg = { + .position = text.position, + .size = mgl_text_get_bounds(&text), + .color = { 255, 0, 0, 255 }, + }; + mgl_rectangle_draw(context, &text_bg); + } mgl_text_draw(context, &text); mgl_text_deinit(&text); @@ -220,11 +230,11 @@ static bool clipboard_callback(const unsigned char *data, size_t size, mgl_clipb return true; } -int main(int argc, char **argv) { +int main(void) { test_hash_map(); test_utf8(); - if(mgl_init() != 0) + if(mgl_init(MGL_WINDOW_SYSTEM_X11) != 0) return 1; mgl_texture texture; @@ -252,7 +262,7 @@ int main(int argc, char **argv) { if(mgl_texture_init(&texture) != 0) return 1; - if(mgl_texture_load_from_file(&texture, "tests/X11.jpg", &(mgl_texture_load_options){ false, false }) != 0) + if(mgl_texture_load_from_file(&texture, "tests/X11.jpg", &(mgl_texture_load_options){ .compressed = false, .pixel_coordinates = false, .scale_type = MGL_TEXTURE_SCALE_LINEAR }) != 0) return 1; if(mgl_mapped_file_load("/usr/share/fonts/TTF/Hack-Regular.ttf", &font_file, &(mgl_memory_mapped_file_load_options){ .readable = true, .writable = false }) != 0) @@ -311,6 +321,18 @@ int main(int argc, char **argv) { fprintf(stderr, "key release event, code: %u\n", event.key.code); break; } + case MGL_EVENT_MONITOR_PROPERTY_CHANGED: { + fprintf(stderr, "monitor property changed\n"); + break; + } + case MGL_EVENT_MONITOR_CONNECTED: { + fprintf(stderr, "monitor connected\n"); + break; + } + case MGL_EVENT_MONITOR_DISCONNECTED: { + fprintf(stderr, "monitor disconnected\n"); + break; + } } } diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..244810e --- /dev/null +++ b/tests/meson.build @@ -0,0 +1 @@ +test_dep = declare_dependency()
\ No newline at end of file |