mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-01-31 01:13:04 +09:00
Initial commit, showing ui above target window
This commit is contained in:
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "depends/mglpp"]
|
||||
path = depends/mglpp
|
||||
url = https://repo.dec05eba.com/mglpp
|
||||
1
depends/mglpp
Submodule
1
depends/mglpp
Submodule
Submodule depends/mglpp added at 2c879c34c9
BIN
fonts/Orbitron-Bold.ttf
Normal file
BIN
fonts/Orbitron-Bold.ttf
Normal file
Binary file not shown.
BIN
fonts/Orbitron-Regular.ttf
Normal file
BIN
fonts/Orbitron-Regular.ttf
Normal file
Binary file not shown.
BIN
images/record.png
Normal file
BIN
images/record.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
BIN
images/replay.png
Normal file
BIN
images/replay.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
BIN
images/stream.png
Normal file
BIN
images/stream.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
16
include/gui/Button.hpp
Normal file
16
include/gui/Button.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "Widget.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace gsr {
|
||||
class Button : public Widget {
|
||||
public:
|
||||
Button(mgl::vec2f size);
|
||||
void on_event(mgl::Event &event, mgl::Window &window) override;
|
||||
void draw(mgl::Window &window) override;
|
||||
private:
|
||||
mgl::vec2f size;
|
||||
bool mouse_inside = false;
|
||||
};
|
||||
}
|
||||
21
include/gui/Widget.hpp
Normal file
21
include/gui/Widget.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <mglpp/system/vec.hpp>
|
||||
|
||||
namespace mgl {
|
||||
class Event;
|
||||
class Window;
|
||||
}
|
||||
|
||||
namespace gsr {
|
||||
class Widget {
|
||||
public:
|
||||
virtual ~Widget() = default;
|
||||
|
||||
virtual void on_event(mgl::Event &event, mgl::Window &window) = 0;
|
||||
virtual void draw(mgl::Window &window) = 0;
|
||||
virtual void set_position(mgl::vec2f position);
|
||||
protected:
|
||||
mgl::vec2f position;
|
||||
};
|
||||
}
|
||||
71
main.cpp
71
main.cpp
@@ -1,71 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/cursorfont.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int x11_error_handler(Display *dpy, XErrorEvent *ev) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
fprintf(stderr, "usage: window-overlay <window>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static bool string_to_i64(const char *str, int64_t *result) {
|
||||
errno = 0;
|
||||
char *endptr = nullptr;
|
||||
int64_t res = strtoll(str, &endptr, 0);
|
||||
if(endptr == str || errno != 0)
|
||||
return false;
|
||||
|
||||
*result = res;
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if(argc != 2)
|
||||
usage();
|
||||
|
||||
int64_t target_window;
|
||||
if(!string_to_i64(argv[1], &target_window)) {
|
||||
fprintf(stderr, "Error: invalid number '%s' was specific for window argument\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
XSetErrorHandler(x11_error_handler);
|
||||
|
||||
Display *display = XOpenDisplay(NULL);
|
||||
if(!display) {
|
||||
fprintf(stderr, "Error: XOpenDisplay failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
//const Window root_window = DefaultRootWindow(display);
|
||||
const int screen = DefaultScreen(display);
|
||||
|
||||
XSetWindowAttributes attr;
|
||||
attr.background_pixel = XWhitePixel(display, screen);
|
||||
attr.override_redirect = True;
|
||||
|
||||
Window overlay_window = XCreateWindow(display, target_window, 0, 0, 256, 256, 0, DefaultDepth(display, screen), InputOutput, XDefaultVisual(display, screen), CWBackPixel|CWOverrideRedirect, &attr);
|
||||
if(!overlay_window) {
|
||||
fprintf(stderr, "Error: failed to create overlay window\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Cursor default_cursor = XCreateFontCursor(display, XC_arrow);
|
||||
|
||||
XMapWindow(display, overlay_window);
|
||||
XGrabPointer(display, overlay_window, True, ButtonPressMask|ButtonReleaseMask, GrabModeSync, GrabModeSync, None, default_cursor, CurrentTime);
|
||||
sleep(3);
|
||||
|
||||
/*XEvent xev;
|
||||
for(;;) {
|
||||
XNextEvent(display, &xev);
|
||||
}*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
[package]
|
||||
name = "window-overlay"
|
||||
name = "gpu-screen-recorder-overlay"
|
||||
type = "executable"
|
||||
version = "0.1.0"
|
||||
platforms = ["posix"]
|
||||
|
||||
[dependencies]
|
||||
x11 = ">=1"
|
||||
x11 = "1"
|
||||
xext = "1"
|
||||
71
src/gui/Button.cpp
Normal file
71
src/gui/Button.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "../../include/gui/Button.hpp"
|
||||
#include <mglpp/graphics/Rectangle.hpp>
|
||||
#include <mglpp/window/Window.hpp>
|
||||
#include <mglpp/window/Event.hpp>
|
||||
#include <mglpp/system/FloatRect.hpp>
|
||||
|
||||
namespace gsr {
|
||||
Button::Button(mgl::vec2f size) : size(size) {
|
||||
|
||||
}
|
||||
|
||||
void Button::on_event(mgl::Event &event, mgl::Window&) {
|
||||
if(event.type == mgl::Event::MouseMoved) {
|
||||
const bool inside = mgl::FloatRect(position, size).contains({ (float)event.mouse_move.x, (float)event.mouse_move.y });
|
||||
if(mouse_inside && !inside) {
|
||||
mouse_inside = false;
|
||||
} else if(!mouse_inside && inside) {
|
||||
mouse_inside = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Button::draw(mgl::Window &window) {
|
||||
if(mouse_inside) {
|
||||
// Background
|
||||
/*
|
||||
{
|
||||
mgl::Rectangle rect(size);
|
||||
rect.set_position(position);
|
||||
rect.set_color(mgl::Color(20, 20, 20, 255));
|
||||
window.draw(rect);
|
||||
}
|
||||
*/
|
||||
|
||||
const int border_size = 5;
|
||||
const mgl::Color border_color(118, 185, 0);
|
||||
|
||||
// Green line at top
|
||||
{
|
||||
mgl::Rectangle rect({ size.x, border_size });
|
||||
rect.set_position(position);
|
||||
rect.set_color(border_color);
|
||||
window.draw(rect);
|
||||
}
|
||||
|
||||
// Green line at bottom
|
||||
{
|
||||
mgl::Rectangle rect({ size.x, border_size });
|
||||
rect.set_position(position + mgl::vec2f(0.0f, size.y - border_size));
|
||||
rect.set_color(border_color);
|
||||
window.draw(rect);
|
||||
}
|
||||
|
||||
// Green line at left
|
||||
{
|
||||
mgl::Rectangle rect({ border_size, size.y });
|
||||
rect.set_position(position);
|
||||
rect.set_color(border_color);
|
||||
window.draw(rect);
|
||||
}
|
||||
|
||||
// Green line at right
|
||||
{
|
||||
mgl::Rectangle rect({ border_size, size.y });
|
||||
rect.set_position(position + mgl::vec2f(size.x - border_size, 0.0f));
|
||||
rect.set_color(border_color);
|
||||
window.draw(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/gui/Widget.cpp
Normal file
7
src/gui/Widget.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "../../include/gui/Widget.hpp"
|
||||
|
||||
namespace gsr {
|
||||
void Widget::set_position(mgl::vec2f position) {
|
||||
this->position = position;
|
||||
}
|
||||
}
|
||||
276
src/main.cpp
Normal file
276
src/main.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
#include "../include/gui/Button.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/cursorfont.h>
|
||||
#include <X11/extensions/shape.h>
|
||||
|
||||
#include <mglpp/mglpp.hpp>
|
||||
#include <mglpp/graphics/Font.hpp>
|
||||
#include <mglpp/graphics/Text.hpp>
|
||||
#include <mglpp/graphics/Texture.hpp>
|
||||
#include <mglpp/graphics/Sprite.hpp>
|
||||
#include <mglpp/window/Window.hpp>
|
||||
#include <mglpp/window/Event.hpp>
|
||||
#include <mglpp/system/MemoryMappedFile.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
#include <mgl/mgl.h>
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
fprintf(stderr, "usage: window-overlay <window>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void startup_error(const char *msg) {
|
||||
fprintf(stderr, "Error: %s\n", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static bool string_to_i64(const char *str, int64_t *result) {
|
||||
errno = 0;
|
||||
char *endptr = nullptr;
|
||||
int64_t res = strtoll(str, &endptr, 0);
|
||||
if(endptr == str || errno != 0)
|
||||
return false;
|
||||
|
||||
*result = res;
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if(argc != 2)
|
||||
usage();
|
||||
|
||||
int64_t target_window;
|
||||
if(!string_to_i64(argv[1], &target_window)) {
|
||||
fprintf(stderr, "Error: invalid number '%s' was specific for window argument\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
mgl::Init init;
|
||||
Display *display = (Display*)mgl_get_context()->connection;
|
||||
|
||||
XWindowAttributes target_win_attr;
|
||||
memset(&target_win_attr, 0, sizeof(target_win_attr));
|
||||
if(!XGetWindowAttributes(display, target_window, &target_win_attr)) {
|
||||
fprintf(stderr, "Error: window argument %s is not a valid window\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
XSelectInput(display, target_window, StructureNotifyMask);
|
||||
|
||||
mgl::vec2i target_window_size = { target_win_attr.width, target_win_attr.height };
|
||||
|
||||
mgl::Window::CreateParams window_create_params;
|
||||
window_create_params.size = target_window_size;
|
||||
window_create_params.parent_window = target_window;
|
||||
window_create_params.hidden = true;
|
||||
|
||||
mgl::Window window;
|
||||
if(!window.create("mglpp", window_create_params))
|
||||
startup_error("failed to create window");
|
||||
|
||||
mgl::MemoryMappedFile title_font_file;
|
||||
if(!title_font_file.load("fonts/Orbitron-Bold.ttf", mgl::MemoryMappedFile::LoadOptions{true, false}))
|
||||
startup_error("failed to load file: fonts/Orbitron-Bold.ttf");
|
||||
|
||||
mgl::MemoryMappedFile font_file;
|
||||
if(!font_file.load("fonts/Orbitron-Regular.ttf", mgl::MemoryMappedFile::LoadOptions{true, false}))
|
||||
startup_error("failed to load file: fonts/Orbitron-Regular.ttf");
|
||||
|
||||
mgl::Font title_font;
|
||||
if(!title_font.load_from_file(title_font_file, 32))
|
||||
startup_error("failed to load font: fonts/Orbitron-Bold.ttf");
|
||||
|
||||
mgl::Font font;
|
||||
if(!font.load_from_file(font_file, 20))
|
||||
startup_error("failed to load font: fonts/Orbitron-Regular.ttf");
|
||||
|
||||
mgl::Texture replay_button_texture;
|
||||
if(!replay_button_texture.load_from_file("images/replay.png"))
|
||||
startup_error("failed to load texture: images/replay.png");
|
||||
|
||||
mgl::Texture record_button_texture;
|
||||
if(!record_button_texture.load_from_file("images/record.png"))
|
||||
startup_error("failed to load texture: images/record.png");
|
||||
|
||||
mgl::Texture stream_button_texture;
|
||||
if(!stream_button_texture.load_from_file("images/stream.png"))
|
||||
startup_error("failed to load texture: images/stream.png");
|
||||
|
||||
struct MainButton {
|
||||
mgl::Text title;
|
||||
mgl::Text description;
|
||||
mgl::Sprite icon;
|
||||
gsr::Button button;
|
||||
};
|
||||
|
||||
const char *titles[] = {
|
||||
"Instant Replay",
|
||||
"Record",
|
||||
"Livestream"
|
||||
};
|
||||
|
||||
const char *descriptions[] = {
|
||||
"Off",
|
||||
"Not recording",
|
||||
"Not streaming"
|
||||
};
|
||||
|
||||
mgl::Texture *textures[] = {
|
||||
&replay_button_texture,
|
||||
&record_button_texture,
|
||||
&stream_button_texture
|
||||
};
|
||||
|
||||
std::vector<MainButton> main_buttons;
|
||||
std::vector<XRectangle> shapes;
|
||||
|
||||
for(int i = 0; i < 3; ++i) {
|
||||
mgl::Text title(titles[i], {0.0f, 0.0f}, title_font);
|
||||
title.set_color(mgl::Color(255, 255, 255));
|
||||
|
||||
mgl::Text description(descriptions[i], {0.0f, 0.0f}, font);
|
||||
description.set_color(mgl::Color(255, 255, 255, 150));
|
||||
|
||||
mgl::Sprite sprite(textures[i]);
|
||||
gsr::Button button({425, 300});
|
||||
|
||||
MainButton main_button = {
|
||||
std::move(title),
|
||||
std::move(description),
|
||||
std::move(sprite),
|
||||
std::move(button)
|
||||
};
|
||||
|
||||
main_buttons.push_back(std::move(main_button));
|
||||
shapes.push_back({});
|
||||
}
|
||||
|
||||
// Settings button
|
||||
shapes.push_back({});
|
||||
|
||||
const int per_button_width = 425;
|
||||
const mgl::vec2i overlay_desired_size = { per_button_width * (int)main_buttons.size(), 300 };
|
||||
|
||||
XGCValues xgcv;
|
||||
xgcv.foreground = WhitePixel(display, DefaultScreen(display));
|
||||
xgcv.line_width = 1;
|
||||
xgcv.line_style = LineSolid;
|
||||
|
||||
Pixmap shape_pixmap = None;
|
||||
GC shape_gc = None;
|
||||
|
||||
auto update_overlay_shape = [&]() {
|
||||
const int main_button_margin = 20;
|
||||
const mgl::vec2i main_buttons_start_pos = target_window_size/2 - overlay_desired_size/2;
|
||||
mgl::vec2i main_button_pos = main_buttons_start_pos;
|
||||
|
||||
for(size_t i = 0; i < main_buttons.size(); ++i) {
|
||||
main_buttons[i].title.set_position(
|
||||
mgl::vec2f(
|
||||
main_button_pos.x + per_button_width * 0.5f - main_buttons[i].title.get_bounds().size.x * 0.5f,
|
||||
main_button_pos.y + main_button_margin).floor());
|
||||
|
||||
main_buttons[i].description.set_position(
|
||||
mgl::vec2f(
|
||||
main_button_pos.x + per_button_width * 0.5f - main_buttons[i].description.get_bounds().size.x * 0.5f,
|
||||
main_button_pos.y + overlay_desired_size.y - main_buttons[i].description.get_bounds().size.y - main_button_margin).floor());
|
||||
|
||||
main_buttons[i].icon.set_position(
|
||||
mgl::vec2f(
|
||||
main_button_pos.x + per_button_width * 0.5f - main_buttons[i].icon.get_texture()->get_size().x * 0.5f,
|
||||
main_button_pos.y + overlay_desired_size.y * 0.5f - main_buttons[i].icon.get_texture()->get_size().y * 0.5f).floor());
|
||||
|
||||
main_buttons[i].button.set_position(main_button_pos.to_vec2f());
|
||||
shapes[i] = {
|
||||
(short)main_button_pos.x, (short)main_button_pos.y, (unsigned short)per_button_width, (unsigned short)overlay_desired_size.y
|
||||
};
|
||||
main_button_pos.x += per_button_width;
|
||||
}
|
||||
|
||||
const mgl::vec2i settings_button_size(128, 128);
|
||||
shapes[main_buttons.size()] = {
|
||||
(short)(main_buttons_start_pos.x + overlay_desired_size.x), (short)(main_buttons_start_pos.y - settings_button_size.y), (unsigned short)settings_button_size.x, (unsigned short)settings_button_size.y
|
||||
};
|
||||
|
||||
if(shape_pixmap) {
|
||||
XFreePixmap(display, shape_pixmap);
|
||||
shape_pixmap = None;
|
||||
}
|
||||
|
||||
if(shape_gc) {
|
||||
XFreeGC(display, shape_gc);
|
||||
shape_gc = None;
|
||||
}
|
||||
|
||||
shape_pixmap = XCreatePixmap(display, window.get_system_handle(), target_window_size.x, target_window_size.y, 1);
|
||||
if(!shape_pixmap)
|
||||
fprintf(stderr, "Error: failed to create shape pixmap\n");
|
||||
|
||||
shape_gc = XCreateGC(display, shape_pixmap, 0, &xgcv);
|
||||
|
||||
XSetForeground(display, shape_gc, 0);
|
||||
XFillRectangle(display, shape_pixmap, shape_gc, 0, 0, target_window_size.x, target_window_size.y);
|
||||
|
||||
XSetForeground(display, shape_gc, 1);
|
||||
XDrawRectangles(display, shape_pixmap, shape_gc, shapes.data(), shapes.size());
|
||||
XFillRectangles(display, shape_pixmap, shape_gc, shapes.data(), shapes.size());
|
||||
|
||||
XShapeCombineMask(display, window.get_system_handle(), ShapeBounding, 0, 0, shape_pixmap, ShapeSet);
|
||||
};
|
||||
|
||||
update_overlay_shape();
|
||||
window.set_visible(true);
|
||||
|
||||
Cursor default_cursor = XCreateFontCursor(display, XC_arrow);
|
||||
|
||||
// TODO: Retry if these fail
|
||||
XGrabPointer(display, window.get_system_handle(), True, ButtonPressMask|ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, default_cursor, CurrentTime);
|
||||
XGrabKeyboard(display, window.get_system_handle(), True, GrabModeAsync, GrabModeAsync, CurrentTime);
|
||||
|
||||
XEvent xev;
|
||||
mgl::Event event;
|
||||
|
||||
while(window.is_open()) {
|
||||
if(XCheckTypedWindowEvent(display, target_window, ConfigureNotify, &xev) && (xev.xconfigure.width != target_window_size.x || xev.xconfigure.height != target_window_size.y)) {
|
||||
while(XCheckTypedWindowEvent(display, target_window, ConfigureNotify, &xev)) {}
|
||||
target_window_size.x = xev.xconfigure.width;
|
||||
target_window_size.y = xev.xconfigure.height;
|
||||
window.set_size(target_window_size);
|
||||
update_overlay_shape();
|
||||
}
|
||||
|
||||
if(window.poll_event(event)) {
|
||||
for(auto &main_button : main_buttons) {
|
||||
main_button.button.on_event(event, window);
|
||||
}
|
||||
|
||||
if(event.type == mgl::Event::KeyPressed) {
|
||||
if(event.key.code == mgl::Keyboard::Escape) {
|
||||
window.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.clear(mgl::Color(37, 43, 47, 255));
|
||||
for(auto &main_button : main_buttons) {
|
||||
main_button.button.draw(window);
|
||||
window.draw(main_button.icon);
|
||||
window.draw(main_button.title);
|
||||
window.draw(main_button.description);
|
||||
}
|
||||
window.display();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user