diff options
-rw-r--r-- | README.md | 1 | ||||
m--------- | depends/mglpp | 0 | ||||
-rw-r--r-- | images/record.png | bin | 2727 -> 2630 bytes | |||
-rw-r--r-- | images/replay.png | bin | 3502 -> 3372 bytes | |||
-rw-r--r-- | images/screenshot.png | bin | 0 -> 2282 bytes | |||
-rw-r--r-- | images/stream.png | bin | 4257 -> 4137 bytes | |||
-rw-r--r-- | meson.build | 4 | ||||
-rw-r--r-- | project.conf | 2 | ||||
-rw-r--r-- | src/main.cpp | 367 |
9 files changed, 335 insertions, 39 deletions
@@ -16,6 +16,7 @@ These are the dependencies needed to build GPU Screen Recorder Notification: * x11 (libx11, libxrandr, libxrender, libxext) * libglvnd (which provides libgl, libglx and libegl) +* wayland (wayland-client, wayland-egl, wayland-scanner) # License This software is licensed under GPL3.0-only. Files under `fonts/` directory belong to the Noto Sans Google fonts project and they are licensed under `SIL Open Font License`. diff --git a/depends/mglpp b/depends/mglpp -Subproject 3c33f0f8136a8cd3844c9dc087daabf16443fa4 +Subproject a784fdb62b1ddfc8c38733c3a16cd1f39e5d415 diff --git a/images/record.png b/images/record.png Binary files differindex e6065e1..1a3d043 100644 --- a/images/record.png +++ b/images/record.png diff --git a/images/replay.png b/images/replay.png Binary files differindex 29e799f..b3f010b 100644 --- a/images/replay.png +++ b/images/replay.png diff --git a/images/screenshot.png b/images/screenshot.png Binary files differnew file mode 100644 index 0000000..72dfcb5 --- /dev/null +++ b/images/screenshot.png diff --git a/images/stream.png b/images/stream.png Binary files differindex 3410107..1be67e6 100644 --- a/images/stream.png +++ b/images/stream.png diff --git a/meson.build b/meson.build index 88da653..868d118 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gsr-notify', ['cpp'], version : '1.0.0', default_options : ['warning_level=2'], subproject_dir : 'depends') +project('gsr-notify', ['cpp'], version : '1.0.7', default_options : ['warning_level=2'], subproject_dir : 'depends') if get_option('buildtype') == 'debug' add_project_arguments('-g3', language : ['cpp']) @@ -31,4 +31,4 @@ executable( ) install_subdir('images', install_dir : gsr_notify_resources_path) -install_subdir('fonts', install_dir : gsr_notify_resources_path)
\ No newline at end of file +install_subdir('fonts', install_dir : gsr_notify_resources_path) diff --git a/project.conf b/project.conf index ecaf34e..bee6a88 100644 --- a/project.conf +++ b/project.conf @@ -1,7 +1,7 @@ [package] name = "gsr-notify" type = "executable" -version = "1.0.0" +version = "1.0.7" platforms = ["posix"] [config] diff --git a/src/main.cpp b/src/main.cpp index 210e747..a6f7dae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -110,13 +110,14 @@ static bool is_xwayland(Display *display) { } static void usage() { - fprintf(stderr, "usage: gsr-notify --text text --timeout timeout [--icon filepath] [--icon-color color] [--bg-color color]\n"); + fprintf(stderr, "usage: gsr-notify --text text --timeout timeout [--icon filepath] [--icon-color color] [--bg-color color] [--monitor monitor]\n"); fprintf(stderr, "options:\n"); fprintf(stderr, " --text The text to display in the notification. Required.\n"); fprintf(stderr, " --timeout The time to display the notification in seconds (excluding animation time), expected to be a floating point number. Required.\n"); - fprintf(stderr, " --icon A path to an image file to display on the left side of the text. This can also be either \"record\", \"replay\" or \"stream\" to use built-in images. Optional.\n"); + fprintf(stderr, " --icon A path to an image file to display on the left side of the text. This can also be either \"record\", \"replay\", \"stream\" or \"screenshot\" to use built-in images. Optional.\n"); fprintf(stderr, " --icon-color The color to display the icon as in hex format, for example FF0000. Optional, set to FFFFFF by default.\n"); fprintf(stderr, " --bg-color The notification background (and side bar) color in hex format, for example FF0000. Optional, set to 76b900 by default.\n"); + fprintf(stderr, " --monitor The monitor to display the notification on. This is the name of the monitor as displayed by xrandr. This can be \"auto\", in which case the focused monitor is used. Optional, set to \"auto\" by default.\n"); fprintf(stderr, "examples:\n"); fprintf(stderr, " gsr-notify --text 'Recording has started' --timeout 3.0\n"); fprintf(stderr, " gsr-notify --text 'Recording has started' --timeout 3.0 --icon record\n"); @@ -169,14 +170,171 @@ static const mgl_monitor* find_monitor_at_position(mgl::Window &window, mgl::vec return &win->monitors[0]; } -static mgl::vec2i create_window_get_center_position(Display *display) { - const int size = 16; +static const mgl_monitor* get_monitor_by_name(mgl::Window &window, const char *name) { + const mgl_window *win = window.internal_window(); + for(int i = 0; i < win->num_monitors; ++i) { + const mgl_monitor *mon = &win->monitors[i]; + if(strcmp(mon->name, name) == 0) + return mon; + } + return nullptr; +} + +static void print_monitor_names(mgl::Window &window) { + const mgl_window *win = window.internal_window(); + for(int i = 0; i < win->num_monitors; ++i) { + const mgl_monitor *mon = &win->monitors[i]; + fprintf(stderr, " %s\n", mon->name); + } +} + +static bool window_has_atom(Display *dpy, Window window, Atom atom) { + Atom type; + unsigned long len, bytes_left; + int format; + unsigned char *properties = NULL; + if(XGetWindowProperty(dpy, window, atom, 0, 1024, False, AnyPropertyType, &type, &format, &len, &bytes_left, &properties) < Success) + return false; + + if(properties) + XFree(properties); + + return type != None; +} + +static bool window_is_user_program(Display *dpy, Window window) { + const Atom net_wm_state_atom = XInternAtom(dpy, "_NET_WM_STATE", False); + const Atom wm_state_atom = XInternAtom(dpy, "WM_STATE", False); + return window_has_atom(dpy, window, net_wm_state_atom) || window_has_atom(dpy, window, wm_state_atom); +} + +static Window window_get_target_window_child(Display *display, Window window) { + if(window == None) + return None; + + if(window_is_user_program(display, window)) + return window; + + Window root; + Window parent; + Window *children = nullptr; + unsigned int num_children = 0; + if(!XQueryTree(display, window, &root, &parent, &children, &num_children) || !children) + return None; + + Window found_window = None; + for(int i = num_children - 1; i >= 0; --i) { + if(children[i] && window_is_user_program(display, children[i])) { + found_window = children[i]; + goto finished; + } + } + + for(int i = num_children - 1; i >= 0; --i) { + if(children[i]) { + Window win = window_get_target_window_child(display, children[i]); + if(win) { + found_window = win; + goto finished; + } + } + } + + finished: + XFree(children); + return found_window; +} + +static bool window_has_title(Display *dpy, Window window) { + bool result = false; + const Atom net_wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False); + const Atom wm_name_atom = XInternAtom(dpy, "WM_NAME", False); + const Atom utf8_string_atom = XInternAtom(dpy, "UTF8_STRING", False); + + Atom type = None; + int format = 0; + unsigned long num_items = 0; + unsigned long bytes_left = 0; + unsigned char *data = NULL; + XGetWindowProperty(dpy, window, net_wm_name_atom, 0, 1024, False, utf8_string_atom, &type, &format, &num_items, &bytes_left, &data); + + if(type == utf8_string_atom && format == 8 && data) { + result = true; + goto done; + } + + if(data) + XFree(data); + + type = None; + format = 0; + num_items = 0; + bytes_left = 0; + data = NULL; + XGetWindowProperty(dpy, window, wm_name_atom, 0, 1024, False, 0, &type, &format, &num_items, &bytes_left, &data); + + if((type == XA_STRING || type == utf8_string_atom) && data) { + result = true; + goto done; + } + + done: + if(data) + XFree(data); + return result; +} + +static mgl::vec2i get_cursor_position(Display *dpy, Window *window) { + Window root_window = None; + *window = None; + int dummy_i; + unsigned int dummy_u; + mgl::vec2i root_pos; + XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, window, &root_pos.x, &root_pos.y, &dummy_i, &dummy_i, &dummy_u); + + const Window direct_window = *window; + *window = window_get_target_window_child(dpy, *window); + // HACK: Count some other x11 windows as having an x11 window focused. Some games seem to create an Input window and that gets focused. + if(!*window) { + XWindowAttributes attr; + memset(&attr, 0, sizeof(attr)); + XGetWindowAttributes(dpy, direct_window, &attr); + if(attr.c_class == InputOnly && !window_has_title(dpy, direct_window)) + *window = direct_window; + } + return root_pos; +} + +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 window_set_decorations_visible(Display *display, Window window, bool visible) { + const Atom motif_wm_hints_atom = XInternAtom(display, "_MOTIF_WM_HINTS", False); + MotifHints motif_hints; + memset(&motif_hints, 0, sizeof(motif_hints)); + motif_hints.flags = MWM_HINTS_DECORATIONS; + motif_hints.decorations = visible ? MWM_DECOR_ALL : MWM_DECOR_NONE; + XChangeProperty(display, window, motif_wm_hints_atom, motif_wm_hints_atom, 32, PropModeReplace, (unsigned char*)&motif_hints, sizeof(motif_hints) / sizeof(long)); +} + +static bool create_window_get_center_position_kde(Display *display, mgl::vec2i &position) { + const int size = 1; XSetWindowAttributes window_attr; window_attr.event_mask = StructureNotifyMask; window_attr.background_pixel = 0; const Window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, size, size, 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixel | CWEventMask, &window_attr); if(!window) - return {0, 0}; + return false; const Atom net_wm_window_type_atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); const Atom net_wm_window_type_notification_atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False); @@ -193,12 +351,14 @@ static mgl::vec2i create_window_get_center_position(Display *display) { const unsigned long opacity = (unsigned long)(0xFFFFFFFFul * alpha); XChangeProperty(display, window, net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L); + window_set_decorations_visible(display, window, false); + XSizeHints *size_hints = XAllocSizeHints(); size_hints->width = size; - size_hints->min_width = size; - size_hints->max_width = size; size_hints->height = size; + size_hints->min_width = size; size_hints->min_height = size; + size_hints->max_width = size; size_hints->max_height = size; size_hints->flags = PSize | PMinSize | PMaxSize; XSetWMNormalHints(display, window, size_hints); @@ -207,15 +367,15 @@ static mgl::vec2i create_window_get_center_position(Display *display) { XMapWindow(display, window); XFlush(display); + bool got_data = false; const int x_fd = XConnectionNumber(display); - mgl::vec2i window_pos; XEvent xev; while(true) { struct pollfd poll_fd; poll_fd.fd = x_fd; poll_fd.events = POLLIN; poll_fd.revents = 0; - const int fds_ready = poll(&poll_fd, 1, 1000); + const int fds_ready = poll(&poll_fd, 1, 200); if(fds_ready == 0) { fprintf(stderr, "Error: timed out waiting for ConfigureNotify after XCreateWindow\n"); break; @@ -223,18 +383,108 @@ static mgl::vec2i create_window_get_center_position(Display *display) { continue; } - XNextEvent(display, &xev); - if(xev.type == ConfigureNotify) { - window_pos.x = xev.xconfigure.x + xev.xconfigure.width / 2; - window_pos.y = xev.xconfigure.y + xev.xconfigure.height / 2; + while(XPending(display)) { + XNextEvent(display, &xev); + if(xev.type == ConfigureNotify && xev.xconfigure.window == window) { + got_data = xev.xconfigure.x > 0 && xev.xconfigure.y > 0; + position.x = xev.xconfigure.x + xev.xconfigure.width / 2; + position.y = xev.xconfigure.y + xev.xconfigure.height / 2; + goto done; + } + } + } + + done: + XDestroyWindow(display, window); + XFlush(display); + + return got_data; +} + +static bool create_window_get_center_position_gnome(Display *display, mgl::vec2i &position) { + const int size = 32; + XSetWindowAttributes window_attr; + window_attr.event_mask = StructureNotifyMask | ExposureMask; + window_attr.background_pixel = 0; + const Window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, size, size, 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixel | CWEventMask, &window_attr); + if(!window) + return false; + + const Atom net_wm_window_opacity = XInternAtom(display, "_NET_WM_WINDOW_OPACITY", False); + const double alpha = 0.0; + const unsigned long opacity = (unsigned long)(0xFFFFFFFFul * alpha); + XChangeProperty(display, window, net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L); + + window_set_decorations_visible(display, window, false); + + XSizeHints *size_hints = XAllocSizeHints(); + size_hints->width = size; + size_hints->height = size; + size_hints->min_width = size; + size_hints->min_height = size; + size_hints->max_width = size; + size_hints->max_height = size; + size_hints->flags = PSize | PMinSize | PMaxSize; + XSetWMNormalHints(display, window, size_hints); + XFree(size_hints); + + XMapWindow(display, window); + XFlush(display); + + bool got_data = false; + const int x_fd = XConnectionNumber(display); + XEvent xev; + while(true) { + struct pollfd poll_fd; + poll_fd.fd = x_fd; + poll_fd.events = POLLIN; + poll_fd.revents = 0; + const int fds_ready = poll(&poll_fd, 1, 200); + if(fds_ready == 0) { + fprintf(stderr, "Error: timed out waiting for MapNotify/ConfigureNotify after XCreateWindow\n"); break; + } else if(fds_ready == -1 || !(poll_fd.revents & POLLIN)) { + continue; + } + + while(XPending(display)) { + XNextEvent(display, &xev); + if(xev.type == MapNotify && xev.xmap.window == window) { + int x = 0; + int y = 0; + Window w = None; + XTranslateCoordinates(display, window, DefaultRootWindow(display), 0, 0, &x, &y, &w); + + got_data = x > 0 && y > 0; + position.x = x + size / 2; + position.y = y + size / 2; + if(got_data) + goto done; + } else if(xev.type == ConfigureNotify && xev.xconfigure.window == window) { + got_data = xev.xconfigure.x > 0 && xev.xconfigure.y > 0; + position.x = xev.xconfigure.x + xev.xconfigure.width / 2; + position.y = xev.xconfigure.y + xev.xconfigure.height / 2; + if(got_data) + goto done; + } } } + done: XDestroyWindow(display, window); XFlush(display); - return window_pos; + return got_data; +} + +static mgl::vec2i create_window_get_center_position(Display *display) { + mgl::vec2i pos; + if(!create_window_get_center_position_kde(display, pos)) { + pos.x = 0; + pos.y = 0; + create_window_get_center_position_gnome(display, pos); + } + return pos; } int main(int argc, char **argv) { @@ -263,6 +513,7 @@ int main(int argc, char **argv) { {"--icon", nullptr}, {"--icon-color", nullptr}, {"--bg-color", nullptr}, + {"--monitor", nullptr}, }; for(int i = 1; i < argc; i += 2) { @@ -285,6 +536,7 @@ int main(int argc, char **argv) { const char *icon_filepath = args["--icon"]; const char *icon_color_str = args["--icon-color"]; const char *bg_color_str = args["--bg-color"]; + const char *monitor_str = args["--monitor"]; if(!notification_text) { fprintf(stderr, "error: missing required option '--text'\n"); @@ -302,22 +554,28 @@ int main(int argc, char **argv) { usage(); } + if(!monitor_str) + monitor_str = "auto"; + const mgl::Color icon_color = parse_hex_color(icon_color_str ? icon_color_str : "FFFFFF"); const mgl::Color bg_color = parse_hex_color(bg_color_str ? bg_color_str : "76b900"); mgl::Init init; Display *display = (Display*)mgl_get_context()->connection; const bool wayland = is_xwayland(display); + const bool use_transparency = wayland; mgl::Window::CreateParams window_create_params; - window_create_params.size = { 1280, 720 }; + window_create_params.size = { 32, 32 }; window_create_params.min_size = window_create_params.size; window_create_params.max_size = window_create_params.size; window_create_params.hidden = true; window_create_params.override_redirect = true; - window_create_params.background_color = bg_color; + window_create_params.support_alpha = true; + window_create_params.background_color = use_transparency ? mgl::Color(0, 0, 0, 0) : bg_color; window_create_params.hide_decorations = true; window_create_params.window_type = MGL_WINDOW_TYPE_NOTIFICATION; + window_create_params.graphics_api = MGL_GRAPHICS_API_GLX; mgl::Window window; if(!window.create("gsr notify", window_create_params)) @@ -329,9 +587,21 @@ int main(int argc, char **argv) { exit(1); } - // The cursor position is wrong on wayland if an x11 window is not focused. On wayland we instead create a window and get the position where the wayland compositor puts it - const mgl::vec2i monitor_position_query_value = wayland ? create_window_get_center_position(display) : mgl::vec2i(win->cursor_position.x, win->cursor_position.y); - const mgl_monitor *focused_monitor = find_monitor_at_position(window, monitor_position_query_value); + const mgl_monitor *focused_monitor = nullptr; + if(strcmp(monitor_str, "auto") == 0) { + // The cursor position is wrong on wayland if an x11 window is not focused. On wayland we instead create a window and get the position where the wayland compositor puts it + Window x11_cursor_window = None; + const mgl::vec2i cursor_position = get_cursor_position(display, &x11_cursor_window); + const mgl::vec2i monitor_position_query_value = (x11_cursor_window || !wayland) ? cursor_position : create_window_get_center_position(display); + focused_monitor = find_monitor_at_position(window, monitor_position_query_value); + } else { + focused_monitor = get_monitor_by_name(window, monitor_str); + if(!focused_monitor) { + fprintf(stderr, "Error: no monitor with the name \"%s\" was found. Expected one of:\n", monitor_str); + print_monitor_names(window); + exit(1); + } + } const std::string noto_sans_bold_filepath = resources_path + "fonts/NotoSans-Bold.ttf"; mgl::MemoryMappedFile font_file; @@ -351,8 +621,10 @@ int main(int argc, char **argv) { icon_filepath_str = resources_path + "images/replay.png"; else if(icon_filepath_str == "stream") icon_filepath_str = resources_path + "images/stream.png"; + else if(icon_filepath_str == "screenshot") + icon_filepath_str = resources_path + "images/screenshot.png"; - if(!logo_texture.load_from_file(icon_filepath_str.c_str(), {false, false, true})) { + if(!logo_texture.load_from_file(icon_filepath_str.c_str(), {false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) { fprintf(stderr, "Warning: failed to load icon\n"); } } @@ -366,8 +638,9 @@ int main(int argc, char **argv) { text.set_color(mgl::Color(255, 255, 255, 0)); mgl::Sprite logo_sprite; - float logo_sprite_padding_x = (int)(window_height * 0.5f); + float logo_sprite_padding_x = (int)(window_height * 0.4f); float padding_between_icon_and_text_x = 0.0f; + float padding_right_side = (int)(window_height * 0.4f); if(logo_texture.is_valid()) { logo_sprite.set_texture(&logo_texture); logo_sprite.set_color(mgl::Color(icon_color.r, icon_color.g, icon_color.b, 0)); @@ -377,19 +650,23 @@ int main(int argc, char **argv) { } unsigned char data = 1; // Prefer not being composed to not reduce display fps on AMD when an application is using 100% of GPU - XChangeProperty(display, window.get_system_handle(), XInternAtom(display, "_NET_WM_BYPASS_COMPOSITOR", False), XA_CARDINAL, 32, PropModeReplace, &data, 1); + XChangeProperty(display, (Window)window.get_system_handle(), XInternAtom(display, "_NET_WM_BYPASS_COMPOSITOR", False), XA_CARDINAL, 32, PropModeReplace, &data, 1); + + data = 1; + XChangeProperty(display, (Window)window.get_system_handle(), XInternAtom(display, "GAMESCOPE_EXTERNAL_OVERLAY", False), XA_CARDINAL, 32, PropModeReplace, &data, 1); // TODO: Make sure the notification always stays on top. Test with starting the notification and then opening youtube in fullscreen. - const int window_width = content_padding_left + logo_sprite_padding_x + logo_sprite.get_size().x + padding_between_icon_and_text_x + text.get_bounds().size.x + logo_sprite_padding_x + padding_between_icon_and_text_x; + const int window_width = content_padding_left + logo_sprite_padding_x + logo_sprite.get_size().x + padding_between_icon_and_text_x + text.get_bounds().size.x + padding_right_side; const mgl::vec2i window_size{window_width, window_height}; - const mgl::vec2i window_start_position{focused_monitor->pos.x + focused_monitor->size.x, focused_monitor->pos.y + window_size.y}; + const mgl::vec2i window_start_position{focused_monitor->pos.x + focused_monitor->size.x - window_size.x, focused_monitor->pos.y + window_size.y}; window.set_size_limits(window_size, window_size); window.set_size(window_size); - set_window_clip_region(display, window.get_system_handle(), {0, 0}, {0, 0}); + if(!use_transparency) + set_window_clip_region(display, (Window)window.get_system_handle(), {0, 0}, {0, 0}); window.set_position(window_start_position); - make_window_click_through(display, window.get_system_handle()); + make_window_click_through(display, (Window)window.get_system_handle()); window.set_visible(true); - make_window_sticky(display, window.get_system_handle()); + make_window_sticky(display, (Window)window.get_system_handle()); const int slide_window_start_x = focused_monitor->pos.x + focused_monitor->size.x; const int slide_window_end_x = focused_monitor->pos.x + focused_monitor->size.x - window_size.x; @@ -409,6 +686,10 @@ int main(int argc, char **argv) { mgl::Clock state_timer; + mgl::Rectangle transparency_bg(window_size.to_vec2f()); + transparency_bg.set_color(bg_color); + transparency_bg.set_position({(float)window_size.x, 0.0f}); + mgl::Rectangle content_bg(window_size.to_vec2f() - mgl::vec2f(content_padding_left, 0.0f)); content_bg.set_color(mgl::Color(0, 0, 0)); @@ -424,7 +705,7 @@ int main(int argc, char **argv) { const auto slide_content_handler = [&](double interpolate_start, double interpolate_end, double interpolation) { double new_slide_x = interpolate(interpolate_start, interpolate_end, interpolation); content_bg.set_position(mgl::vec2f(new_slide_x, content_bg_start_position.y).floor()); - logo_sprite.set_position((content_bg.get_position() + mgl::vec2f(logo_sprite_padding_x, content_bg.get_size().y * 0.5f - logo_sprite.get_size().y * 0.5f)).floor()); + logo_sprite.set_position((content_bg.get_position() + mgl::vec2f(content_padding_left + logo_sprite_padding_x, content_bg.get_size().y * 0.5f - logo_sprite.get_size().y * 0.5f)).floor()); const float content_space_left_pos_x = logo_sprite.get_position().x + logo_sprite.get_size().x + padding_between_icon_and_text_x; //const float content_space_left_x = content_bg.get_size().x - content_space_left_pos_x; text.set_position((mgl::vec2f(content_space_left_pos_x, content_bg.get_position().y) + mgl::vec2f(0.0f, content_bg.get_size().y) * 0.5f - mgl::vec2f(0.0f, text.get_bounds().size.y) * 0.5f).floor()); @@ -442,9 +723,12 @@ int main(int argc, char **argv) { case State::SLIDE_IN_WINDOW: { const double new_slide_x = interpolate(slide_window_start_x, slide_window_end_x, state_interpolation); const mgl::vec2i window_clip(std::max(0.0, slide_window_start_x - new_slide_x), window_size.y); - set_window_clip_region(display, window.get_system_handle(), {0, 0}, window_clip); - window.set_position(mgl::vec2i(new_slide_x, window_start_position.y)); - XFlush(display); + if(use_transparency) { + transparency_bg.set_size(window_clip.floor().to_vec2f()); + } else { + set_window_clip_region(display, (Window)window.get_system_handle(), {window_size.x - window_clip.x, 0}, window_clip); + XFlush(display); + } break; } case State::SLIDE_IN_CONTENT: { @@ -470,9 +754,12 @@ int main(int argc, char **argv) { case State::SLIDE_OUT_WINDOW: { const double new_slide_x = interpolate(slide_window_end_x, slide_window_start_x, state_interpolation); const mgl::vec2i window_clip(std::max(0.0, slide_window_start_x - new_slide_x), window_size.y); - set_window_clip_region(display, window.get_system_handle(), {0, 0}, window_clip); - window.set_position(mgl::vec2i(new_slide_x, window_start_position.y)); - XFlush(display); + if(use_transparency) { + transparency_bg.set_size(window_clip.floor().to_vec2f()); + } else { + set_window_clip_region(display, (Window)window.get_system_handle(), {window_size.x - window_clip.x, 0}, window_clip); + XFlush(display); + } break; } case State::PAUSE: { @@ -480,7 +767,13 @@ int main(int argc, char **argv) { } } - window.clear(bg_color); + if(use_transparency) { + window.clear(mgl::Color(0, 0, 0, 0)); + transparency_bg.set_position(mgl::vec2f(window_size.x - transparency_bg.get_size().x, 0.0f)); + window.draw(transparency_bg); + } else { + window.clear(bg_color); + } window.draw(content_bg); window.draw(logo_sprite); window.draw(text); @@ -489,8 +782,10 @@ int main(int argc, char **argv) { if(state_interpolation >= 1.0) { state_timer.restart(); ++current_state_index; - if(current_state_index >= (int)states_to_execute_in_order.size()) + if(current_state_index >= (int)states_to_execute_in_order.size()) { window.close(); + break; + } } } } |