diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main.cpp | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..edd010d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,265 @@ +#include <mglpp/mglpp.hpp> +#include <mglpp/window/Window.hpp> +#include <mglpp/window/Event.hpp> +#include <mglpp/system/Clock.hpp> +#include <mglpp/graphics/Rectangle.hpp> +#include <mglpp/system/MemoryMappedFile.hpp> +#include <mglpp/graphics/Font.hpp> +#include <mglpp/graphics/Text.hpp> +#include <mglpp/graphics/Texture.hpp> +#include <mglpp/graphics/Sprite.hpp> + +#include <stdio.h> +#include <string.h> +#include <array> + +#include <X11/Xlib.h> + +extern "C" { +#include <mgl/mgl.h> +} + +static const mgl::Color bg_color(118, 185, 0); + +enum class State { + SLIDE_IN_WINDOW, + SLIDE_IN_CONTENT, + FADE_IN_CONTENT, + FADE_OUT_CONTENT, + SLIDE_OUT_CONTENT, + SLIDE_OUT_WINDOW, + PAUSE +}; + +struct StateWithPayload { + State state; + double time_in_state_sec; +}; + +// Linear interpolation +static double interpolate(double a, double b, double interpolation) { + return a + (b - a) * interpolation; +} + +static double min_double(double a, double b) { + return a < b ? a : b; +} + +static float max_float(float a, float b) { + return a > b ? a : b; +} + +#define _NET_WM_STATE_REMOVE 0 +#define _NET_WM_STATE_ADD 1 +#define _NET_WM_STATE_TOGGLE 2 + +static Bool set_window_wm_state(Display *display, Window window, Atom atom) { + Atom net_wm_state_atom = XInternAtom(display, "_NET_WM_STATE", False); + if(!net_wm_state_atom) { + fprintf(stderr, "Error: failed to find atom _NET_WM_STATE\n"); + return False; + } + + XClientMessageEvent xclient; + memset(&xclient, 0, sizeof(xclient)); + + xclient.type = ClientMessage; + xclient.window = window; + xclient.message_type = net_wm_state_atom; + xclient.format = 32; + xclient.data.l[0] = _NET_WM_STATE_ADD; + xclient.data.l[1] = atom; + xclient.data.l[2] = 0; + xclient.data.l[3] = 0; + xclient.data.l[4] = 0; + + XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*)&xclient); + XFlush(display); + return True; +} + +static Bool make_window_always_on_top(Display* display, Window window) { + Atom net_wm_state_above_atom = XInternAtom(display, "_NET_WM_STATE_ABOVE", False); + if(!net_wm_state_above_atom) { + fprintf(stderr, "Error: failed to find atom _NET_WM_STATE_ABOVE\n"); + return False; + } + + return set_window_wm_state(display, window, net_wm_state_above_atom); +} + +static Bool make_window_sticky(Display* display, Window window) { + Atom net_wm_state_sticky_atom = XInternAtom(display, "_NET_WM_STATE_STICKY", False); + if(!net_wm_state_sticky_atom) { + fprintf(stderr, "Error: failed to find atom _NET_WM_STATE_STICKY\n"); + return False; + } + + return set_window_wm_state(display, window, net_wm_state_sticky_atom); +} + +int main(int argc, char **argv) { + if(argc != 2) { + fprintf(stderr, "usage: gpu-screen-recorder-notification <title>\n"); + exit(1); + } + + const char *notification_title = argv[1]; + const double notification_timeout_sec = 3.0; + + mgl::Init init; + + mgl::Window::CreateParams window_create_params; + window_create_params.size = { 1280, 720 }; + window_create_params.min_size = window_create_params.size; + window_create_params.max_size = window_create_params.max_size; + window_create_params.hidden = true; + window_create_params.override_redirect = true; + window_create_params.background_color = bg_color; + window_create_params.hide_decorations = true; + window_create_params.window_type = MGL_WINDOW_TYPE_DIALOG; + + mgl::Window window; + if(!window.create("GPU Screen Recorder Notification", window_create_params)) + return 1; + + mgl_window *win = window.internal_window(); + if(win->num_monitors == 0) { + fprintf(stderr, "Error: no monitors found\n"); + exit(1); + } + + mgl::MemoryMappedFile font_file; + if(!font_file.load("/usr/share/fonts/noto/NotoSans-Bold.ttf", mgl::MemoryMappedFile::LoadOptions{true, false})) + return 1; + + mgl::Font font; + if(!font.load_from_file(font_file, win->monitors[0].size.y / 60)) + return 1; + + mgl::Texture record_texture; + if(!record_texture.load_from_file("images/record.png")) + return 1; + + const int window_height = win->monitors[0].size.y / 12; + const float content_padding_left = 10.0f;//max_float(5.0f, (float)window_size.x / 30.0f); + + mgl::Text content_title(notification_title, font); + content_title.set_color(mgl::Color(255, 255, 255, 0)); + + mgl::Sprite content_record_sprite(&record_texture); + content_record_sprite.set_color(mgl::Color(118, 185, 0, 0)); + content_record_sprite.set_height((int)(window_height * 0.4f)); + const float content_record_sprite_padding_x = (int)((window_height - content_record_sprite.get_size().y) * 0.4f); + const float padding_between_icon_and_text_x = (int)(content_record_sprite_padding_x * 0.7f); + + // TODO: Use the monitor that the cursor is on or the focused window is on + const int window_width = content_padding_left + content_record_sprite_padding_x + content_record_sprite.get_size().x + padding_between_icon_and_text_x + content_title.get_bounds().size.x + content_record_sprite_padding_x + padding_between_icon_and_text_x + content_padding_left; + const mgl::vec2i window_size{window_width, window_height}; + const mgl::vec2i window_start_position{win->monitors[0].size.x, window_size.y}; + window.set_size_limits(window_size, window_size); + window.set_size(window_size); + window.set_position(window_start_position); + window.set_visible(true); + + Display *display = (Display*)mgl_get_context()->connection; + make_window_always_on_top(display, window.get_system_handle()); + make_window_sticky(display, window.get_system_handle()); + + const int slide_window_start_x = win->monitors[0].size.x; + const int slide_window_end_x = win->monitors[0].size.x - window_size.x; + + const std::array<StateWithPayload, 9> states_to_execute_in_order = { + StateWithPayload{State::SLIDE_IN_WINDOW, 0.15}, + StateWithPayload{State::PAUSE, 0.05}, + StateWithPayload{State::SLIDE_IN_CONTENT, 0.10}, + StateWithPayload{State::FADE_IN_CONTENT, 0.50}, + StateWithPayload{State::PAUSE, notification_timeout_sec}, + StateWithPayload{State::FADE_OUT_CONTENT, 0.001}, + StateWithPayload{State::SLIDE_OUT_CONTENT, 0.15}, + StateWithPayload{State::PAUSE, 0.10}, + StateWithPayload{State::SLIDE_OUT_WINDOW, 0.15}, + }; + int current_state_index = 0; + + mgl::Clock state_timer; + + mgl::Rectangle content_bg(window_size.to_vec2f() - mgl::vec2f(content_padding_left, 0.0f)); + content_bg.set_color(mgl::Color(0, 0, 0)); + + const int slide_content_start_x = window_size.x + content_padding_left; + const int slide_content_end_x = content_padding_left; + + const mgl::vec2f content_bg_start_position{(float)slide_content_start_x, 0.0f}; + content_bg.set_position(content_bg_start_position); + + const float content_start_alpha = 0.0f; + const float content_end_alpha = 1.0f; + + 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()); + content_record_sprite.set_position((content_bg.get_position() + mgl::vec2f(content_record_sprite_padding_x, content_bg.get_size().y * 0.5f - content_record_sprite.get_size().y * 0.5f)).floor()); + const float content_space_left_pos_x = content_record_sprite.get_position().x + content_record_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; + content_title.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, content_title.get_bounds().size.y) * 0.5f).floor()); + }; + + mgl::Event event; + while(window.is_open()) { + while(window.poll_event(event)) {} + + const StateWithPayload current_state = states_to_execute_in_order[current_state_index]; + const double state_elapsed_time_sec = state_timer.get_elapsed_time_seconds(); + const double state_interpolation = min_double(1.0, state_elapsed_time_sec / current_state.time_in_state_sec); + + switch(current_state.state) { + case State::SLIDE_IN_WINDOW: { + double new_slide_x = interpolate(slide_window_start_x, slide_window_end_x, state_interpolation); + window.set_position(mgl::vec2i(new_slide_x, window_start_position.y)); + break; + } + case State::SLIDE_IN_CONTENT: { + slide_content_handler(slide_content_start_x, slide_content_end_x, state_interpolation); + break; + } + case State::FADE_IN_CONTENT: { + double new_alpha = interpolate(content_start_alpha, content_end_alpha, state_interpolation); + content_title.set_color(mgl::Color(255, 255, 255, new_alpha * 255.0f)); + content_record_sprite.set_color(mgl::Color(118, 185, 0, new_alpha * 255.0f)); + break; + } + case State::FADE_OUT_CONTENT: { + double new_alpha = interpolate(content_end_alpha, content_start_alpha, state_interpolation); + content_title.set_color(mgl::Color(255, 255, 255, new_alpha * 255.0f)); + content_record_sprite.set_color(mgl::Color(118, 185, 0, new_alpha * 255.0f)); + break; + } + case State::SLIDE_OUT_CONTENT: { + slide_content_handler(slide_content_end_x, slide_content_start_x, state_interpolation); + break; + } + case State::SLIDE_OUT_WINDOW: { + double new_slide_x = interpolate(slide_window_end_x, slide_window_start_x, state_interpolation); + window.set_position(mgl::vec2i(new_slide_x, window_start_position.y)); + break; + } + case State::PAUSE: { + break; + } + } + + window.clear(bg_color); + window.draw(content_bg); + window.draw(content_record_sprite); + window.draw(content_title); + window.display(); + + if(state_interpolation >= 1.0) { + state_timer.restart(); + ++current_state_index; + if(current_state_index >= (int)states_to_execute_in_order.size()) + window.close(); + } + } +} |