Initial commit

This commit is contained in:
Cameron Reed 2024-06-12 17:48:27 -06:00
commit 2c136debce
18 changed files with 1156 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2024 CameronReed
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 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.

99
Makefile Normal file
View File

@ -0,0 +1,99 @@
# Inputs
SRC_DIRS := src
INC_DIRS := inc
LIBS := glew glfw3 freetype2 sdbus-c++
# Outputs
OUT_NAME := timer_overlay
BUILD_DIR := build
OFILE_DIR := $(BUILD_DIR)/objects
PREFIX ?= /usr/bin
# Project sources
LIB_INCLUDES := $(foreach lib, $(LIBS), $(shell pkg-config --cflags-only-I $(lib)))
INCLUDES := $(addprefix -I, $(INC_DIRS)) $(LIB_INCLUDES)
C_SOURCES := $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.c))
CXX_SOURCES := $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.cpp))
# Intermediary Outputs
OFILES := $(addprefix $(OFILE_DIR)/, $(notdir $(C_SOURCES:.c=.o) $(CXX_SOURCES:.cpp=.o)))
# Compiler flags
OPT := -O2
CPPFLAGS := $(INCLUDES) -MMD
CFLAGS := $(OPT) -Wall -Wextra -Wpedantic
CXXFLAGS := -std=c++17 $(OPT) -Wall -Wextra -Wpedantic
# Linker flags
LDFLAGS := $(foreach lib, $(LIBS), $(shell pkg-config --libs-only-L $(lib)))
LDLIBS := $(foreach lib, $(LIBS), $(shell pkg-config --libs-only-l $(lib)))
# Dependency files generated by the compiler
DEPENDS := $(OFILES:.o=.d)
# All output directories
ALL_DIRS := $(BUILD_DIR) $(OFILE_DIR)
# Use CC as linker if there are no CXX source files
ifeq ($(strip $(CXX_SOURCES)),)
LD := $(CC)
else
LD := $(CXX)
endif
.PHONY: all run clean install
all: $(BUILD_DIR)/$(OUT_NAME)
run: $(BUILD_DIR)/$(OUT_NAME)
$(BUILD_DIR)/$(OUT_NAME)
clean:
$(RM) -r $(ALL_DIRS)
install: $(BUILD_DIR)/$(OUT_NAME)
install -D $(BUILD_DIR)/$(OUT_NAME) $(PREFIX)
# Include generated header file dependencies
-include $(DEPENDS)
$(BUILD_DIR)/$(OUT_NAME): $(OFILES) | $(COMPILED_SHADERS) $(TEXTURES) $(MODELS) $(BUILD_DIR)
$(LD) $^ $(LDFLAGS) $(LDLIBS) -o $@
$(OFILE_DIR)/%.o: %.c | $(OFILE_DIR)
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
$(OFILE_DIR)/%.o: %.cpp | $(OFILE_DIR)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
# Directories
$(ALL_DIRS):
mkdir -p $@
vpath %.c $(SRC_DIRS)
vpath %.cpp $(SRC_DIRS)

58
README.md Normal file
View File

@ -0,0 +1,58 @@
# Timer overlay
This is a project to display a timer overlayed on top of the rest of your desktop that is controlled with keyboard shortcuts so you can quickly
start a timer any time you need with only a couple key presses
This relies on the [GlobalShortcuts XDG desktop portal](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html),
which is currently only implemented on KDE Plasma. I am considering reworking it to not rely on the portal, but we'll see if I get around to that
# Dependencies
This project relies on [glew](https://github.com/nigels-com/glew) [glfw3](https://www.glfw.org/) [freetype2](https://freetype.org/) and [sdbus-cpp](https://github.com/Kistler-Group/sdbus-cpp).
You will need to install these libraries to be able to build/run this project
For example, on Arch Linux:
```
sudo pacman -S glew glfw freetype2 sdbus-cpp
```
You will also need to have make and a c++ compiler to build the project
For example, on Arch Linux: (you probably already have these if you are using Arch as they are dependencies of base-devel)
```
sudo pacman -S make gcc
```
# Building
Once the dependencies have been installed, you can compile the project simply with:
```
make
```
# Configuring
In the future, there will be a configuration file to configure the position, size, and font. However, when running under Wayland,
the requested position of the window will not be respected, so to set the position, as well as removing window decorations, and
preventing the window from stealing focus when it is opened, you will need to set window rules. You can see the rules I use below:
![window rules](https://cam123.dev/files/hidden/images/window_rules.png)
# Installing
Installation is likewise very simple, simply run:
```
sudo make install
```
There is also a desktop file provided that you may want to install to /usr/share/applications/, ~/.local/share/applications/, or ~/.config/autostart,
but that is left up to you
# Licensing
The MIT license attached to this project applies to most of the repo, with the exception of one file, ```src/character_utils.cpp```.
This file's core logic was copied from [https://learnopengl.com/In-Practice/Text-Rendering](https://learnopengl.com/In-Practice/Text-Rendering),
and is therefore licensed and copyrighted by its creator, [Joey de Vries](https://twitter.com/JoeyDeVriez) under the CC BY 4.0 license
which you can read about [here](https://creativecommons.org/licenses/by/4.0/) or [here](https://creativecommons.org/licenses/by/4.0/legalcode)

31
inc/character_utils.h Normal file
View File

@ -0,0 +1,31 @@
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <unordered_map>
#include <ft2build.h>
#include FT_FREETYPE_H
struct Character {
unsigned int TextureID; // ID handle of the glyph texture
glm::ivec2 Size; // Size of glyph
glm::ivec2 Bearing; // Offset from baseline to left/top of glyph
FT_Pos Advance; // Offset to advance to next glyph
};
class Font
{
public:
Font() = delete;
Font(const char* font);
void RenderText(GLuint VAO, GLuint VBO, GLuint shaderProgram, const char* text, float x, float y, float scale);
private:
void SetupCharMap(FT_Face& face);
public:
bool LoadError;
private:
std::unordered_map<GLchar, Character> m_Characters;
};

6
inc/shader_utils.h Normal file
View File

@ -0,0 +1,6 @@
#include <GL/glew.h>
GLuint loadShader(const char* const file_path, GLenum shader_type);
GLuint loadShaderDirect(const char* const shader_src, GLenum shader_type);
GLuint linkShaderProgram(GLuint vert_shader, GLuint frag_shader);

2
inc/shadersrc.h Normal file
View File

@ -0,0 +1,2 @@
extern const char* const shader_text_frag;
extern const char* const shader_text_vert;

40
inc/shortcuts.h Normal file
View File

@ -0,0 +1,40 @@
#include <sdbus-c++/sdbus-c++.h>
#include <memory>
#include <string>
#include <vector>
#include <map>
typedef sdbus::Struct<std::string, std::map<std::string, sdbus::Variant>> dbus_shortcut_t;
typedef void(*shortcut_callback_t)(void*);
// typedef std::function<void()> shortcut_callback_t;
struct ShortcutCallback {
void* userData;
shortcut_callback_t callback;
};
class GlobalShortcuts {
public:
GlobalShortcuts() = delete;
GlobalShortcuts(const char* const tokenPrefix);
void addShortcut(const std::string& id, const std::string& description, const std::string& trigger, shortcut_callback_t callback, void* userData);
int createSession();
int bindKeys();
bool alreadyBound();
std::vector<dbus_shortcut_t> listBinds();
void listen();
private:
std::string AddNumToToken();
private:
sdbus::ObjectPath m_SessionPath;
std::string m_TokenPrefix;
std::string m_ConnName;
std::string m_Sender;
std::unique_ptr<sdbus::IProxy> m_xdgProxy;
std::vector<dbus_shortcut_t> m_Shortcuts;
std::map<std::string, ShortcutCallback> m_Callbacks;
};

31
inc/timer.h Normal file
View File

@ -0,0 +1,31 @@
#include <cstdint>
#include <chrono>
struct TimeDuration
{
int64_t hours;
int64_t minutes;
int64_t seconds;
int64_t milliseconds;
int64_t minutes_absolute;
int64_t seconds_absolute;
int64_t milliseconds_absolute;
bool negative;
};
class Timer
{
public:
Timer();
TimeDuration GetTimeLeft();
void AddMinutes(int64_t minutes);
void Clear();
private:
std::chrono::system_clock::time_point end_time;
};

12
shaders/shader.frag Normal file
View File

@ -0,0 +1,12 @@
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
void main()
{
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
color = vec4(textColor, 1.0) * sampled;
}

11
shaders/shader.vert Normal file
View File

@ -0,0 +1,11 @@
#version 330 core
layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
out vec2 TexCoords;
uniform mat4 projection;
void main()
{
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
TexCoords = vertex.zw;
}

137
src/character_utils.cpp Normal file
View File

@ -0,0 +1,137 @@
// This was copied from https://learnopengl.com/In-Practice/Text-Rendering
// and then modifying it slightly the main modification being wrapping it
// in a class. But the core logic was written by Joey de Vries. This file
// is therefore licensed under the terms of the CC BY 4.0. You can read
// about it at https://creativecommons.org/licenses/by/4.0/ or find the
// full license at https://creativecommons.org/licenses/by/4.0/legalcode
// This is not provided under any warranty
//
// (c) Joey de Vries
// (You can find him on twitter at https://twitter.com/JoeyDeVriez
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <unordered_map>
#include <iostream>
#include "character_utils.h"
Font::Font(const char* font)
: LoadError(false), m_Characters()
{
FT_Library ft;
if (FT_Init_FreeType(&ft)) {
std::cerr << "Error: Failed to initialize FreeType2" << std::endl;
LoadError = true;
return;
}
std::cout << "Loading font " << font << std::endl;
FT_Face face;
if (FT_New_Face(ft, font, 0, &face)) {
std::cerr << "Error: Failed to load font" << std::endl;
LoadError = true;
return;
}
SetupCharMap(face);
FT_Done_Face(face);
FT_Done_FreeType(ft);
}
void Font::SetupCharMap(FT_Face &face)
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // disable byte-alignment restriction
FT_Set_Pixel_Sizes(face, 0, 48);
for (unsigned char c = 0; c < 128; c++) {
// load character glyph
if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
std::cerr << "Error: Failed to load " << std::hex << (uint8_t)c << " character" << std::endl;
continue;
}
// generate texture
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RED,
face->glyph->bitmap.width,
face->glyph->bitmap.rows,
0,
GL_RED,
GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer);
// set texture options
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// now store character for later use
Character character = {
texture,
glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
face->glyph->advance.x
};
m_Characters.insert(std::pair<char, Character>(c, character));
}
glBindTexture(GL_TEXTURE_2D, 0);
}
void Font::RenderText(GLuint VAO, GLuint VBO, GLuint shaderProgram, const char* text, float x, float y, float scale)
{
// activate corresponding render state
glUseProgram(shaderProgram);
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(VAO);
// iterate through all characters
for (size_t i = 0; text[i] != '\0'; i++)
{
char c = text[i];
Character ch = m_Characters[c];
float xpos = x + ch.Bearing.x * scale;
float ypos = y - (ch.Size.y - ch.Bearing.y) * scale;
float w = ch.Size.x * scale;
float h = ch.Size.y * scale;
// update VBO for each character
float vertices[6][4] = {
{ xpos, ypos + h, 0.0f, 0.0f },
{ xpos, ypos, 0.0f, 1.0f },
{ xpos + w, ypos, 1.0f, 1.0f },
{ xpos, ypos + h, 0.0f, 0.0f },
{ xpos + w, ypos, 1.0f, 1.0f },
{ xpos + w, ypos + h, 1.0f, 0.0f }
};
// render glyph texture over quad
glBindTexture(GL_TEXTURE_2D, ch.TextureID);
// update content of VBO memory
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// render quad
glDrawArrays(GL_TRIANGLES, 0, 6);
// now advance cursors for next glyph (note that advance is number of 1/64 pixels)
x += (ch.Advance >> 6) * scale; // bitshift by 6 to get value in pixels (2^6 = 64)
}
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}

177
src/main.cpp Normal file
View File

@ -0,0 +1,177 @@
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <climits>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <glm/common.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <wayland-client-protocol.h>
#include <unistd.h>
#include <iostream>
#include "shader_utils.h"
#include "shadersrc.h"
#include "character_utils.h"
#include "shortcuts.h"
#include "timer.h"
void error_callback(int error, const char* description)
{
std::cerr << "Error " << error << ": " << description << std::endl;
}
int main()
{
std::cout << glfwGetVersionString() << std::endl;
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WAYLAND);
glfwSetErrorCallback(error_callback);
if (glfwInit() != GLFW_TRUE) {
std::cerr << "Error initializing glfw" << std::endl;
return -1;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE);
glfwWindowHint(GLFW_FOCUS_ON_SHOW, GLFW_FALSE);
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE);
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE);
glfwWindowHint(GLFW_POSITION_X, 200);
glfwWindowHint(GLFW_POSITION_Y, 10);
glfwWindowHintString(GLFW_WAYLAND_APP_ID, "timer-overlay");
glfwWindowHintString(GLFW_X11_CLASS_NAME, "timer-overlay");
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, "timer-overlay");
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(250, 50, "Timer Overlay", nullptr, nullptr);
if (window == nullptr) {
std::cout << "Failed to create glfw window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glewExperimental = GL_TRUE;
int err = glewInit();
if (err != GLEW_OK) {
std::cerr << "Failed to initialize GLEW: " << glewGetErrorString(err) << std::endl;
return -1;
}
glEnable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
GLuint vertShader = loadShaderDirect(shader_text_vert, GL_VERTEX_SHADER);
GLuint fragShader = loadShaderDirect(shader_text_frag, GL_FRAGMENT_SHADER);
GLuint shaderProgram = linkShaderProgram(vertShader, fragShader);
glDeleteShader(vertShader);
glDeleteShader(fragShader);
glm::mat4 projection = glm::ortho(0.0f, 250.0f, 0.0f, 50.0f);
glUseProgram(shaderProgram);
glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
Font NotoSans("/usr/share/fonts/noto/NotoSans-Regular.ttf");
if (NotoSans.LoadError) {
return -1;
}
GLuint VAO, VBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
Timer timer;
GlobalShortcuts shortcuts("timer_overlay");
if (shortcuts.createSession() != 0) {
std::cout << "Failed to create shortcuts session" << std::endl;
glfwTerminate();
}
shortcuts.addShortcut("time1", "Adds 1 minute to the timer", "ALT+F5", [](void* timer_ptr)
{
((Timer*)timer_ptr)->AddMinutes(1);
}, &timer);
shortcuts.addShortcut("time5", "Adds 5 minutes to the timer", "ALT+F6", [](void* timer_ptr)
{
((Timer*)timer_ptr)->AddMinutes(5);
}, &timer);
shortcuts.addShortcut("time15", "Adds 15 minutes to the timer", "ALT+F7", [](void* timer_ptr)
{
((Timer*)timer_ptr)->AddMinutes(15);
}, &timer);
shortcuts.addShortcut("time60", "Adds 1 hour to the timer", "ALT+F8", [](void* timer_ptr)
{
((Timer*)timer_ptr)->AddMinutes(60);
}, &timer);
shortcuts.addShortcut("timeclear", "Clears the timer", "ALT+F9", [](void* timer_ptr)
{
((Timer*)timer_ptr)->Clear();
}, &timer);
if (!shortcuts.alreadyBound()) {
std::cout << "Requsting to bind keys" << std::endl;
if (shortcuts.bindKeys() != 0) {
std::cerr << "Failed to bind keys" << std::endl;
return -1;
}
}
shortcuts.listen();
int64_t last_frame_seconds = LONG_MAX;
while (!glfwWindowShouldClose(window)) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, GLFW_TRUE);
}
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
TimeDuration time_diff = timer.GetTimeLeft();
if (time_diff.seconds == last_frame_seconds && !time_diff.negative) {
continue;
}
char buf[50]; // max of hhh:mm:ss\0
if (time_diff.negative) {
memcpy(buf, "0:00", 5);
} else if (time_diff.hours > 999) {
memcpy(buf, "999:59:59", 10);
} else if (time_diff.hours < 1) {
sprintf(buf, "%ld:%02ld", time_diff.minutes, time_diff.seconds);
} else {
sprintf(buf, "%ld:%02ld:%02ld", time_diff.hours, time_diff.minutes, time_diff.seconds);
}
if (!time_diff.negative || (time_diff.seconds_absolute < 3 && time_diff.milliseconds > 500)) {
NotoSans.RenderText(VAO, VBO, shaderProgram, buf, 0.0f, 10.0f, 1.0f);
} else {
usleep(500);
}
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
}

91
src/shader_utils.cpp Normal file
View File

@ -0,0 +1,91 @@
#include <GL/glew.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include "shader_utils.h"
GLuint loadShader(const char* const file_path, GLenum shader_type)
{
GLuint shaderID = glCreateShader(shader_type);
std::string shaderSrc;
std::ifstream shaderStream(file_path, std::ios::in);
if (shaderStream.is_open()) {
std::stringstream sstr;
sstr << shaderStream.rdbuf();
shaderSrc = sstr.str();
shaderStream.close();
} else {
std::cerr << "Failed to open shader file " << file_path << std::endl;
return 0;
}
std::cout << "Compiling shader " << file_path << std::endl;
const char* const p_shaderSrc = shaderSrc.c_str();
glShaderSource(shaderID, 1, &p_shaderSrc, NULL);
glCompileShader(shaderID);
GLint result = GL_FALSE;
int infoLogLen;
glGetShaderiv(shaderID, GL_COMPILE_STATUS, &result);
glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &infoLogLen);
if (infoLogLen > 0) {
char* shaderErrMsg = new char[infoLogLen + 1];
glGetShaderInfoLog(shaderID, infoLogLen, NULL, shaderErrMsg);
std::cerr << shaderErrMsg << std::endl;
}
return shaderID;
}
GLuint loadShaderDirect(const char* const shader_src, GLenum shader_type)
{
GLuint shaderID = glCreateShader(shader_type);
glShaderSource(shaderID, 1, &shader_src, NULL);
glCompileShader(shaderID);
GLint result = GL_FALSE;
int infoLogLen;
glGetShaderiv(shaderID, GL_COMPILE_STATUS, &result);
glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &infoLogLen);
if (infoLogLen > 0) {
char* shaderErrMsg = new char[infoLogLen + 1];
glGetShaderInfoLog(shaderID, infoLogLen, NULL, shaderErrMsg);
std::cerr << shaderErrMsg << std::endl;
}
return shaderID;
}
GLuint linkShaderProgram(GLuint vert_shader, GLuint frag_shader)
{
std::cout << "Linking shader program" << std::endl;
GLuint programID = glCreateProgram();
glAttachShader(programID, vert_shader);
glAttachShader(programID, frag_shader);
glLinkProgram(programID);
GLint result = GL_FALSE;
int infoLogLen;
glGetProgramiv(programID, GL_LINK_STATUS, &result);
glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLogLen);
if (infoLogLen > 0) {
char* programErrMsg = new char[infoLogLen + 1];
glGetProgramInfoLog(programID, infoLogLen, NULL, programErrMsg);
std::cerr << programErrMsg << std::endl;
}
glDetachShader(programID, vert_shader);
glDetachShader(programID, frag_shader);
return programID;
}

26
src/shadersrc.cpp Normal file
View File

@ -0,0 +1,26 @@
extern const char* const shader_text_frag = R"(
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
void main() {
color = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
}
)";
extern const char* const shader_text_vert = R"(
#version 330 core
layout (location = 0) in vec4 vertex;
out vec2 TexCoords;
uniform mat4 projection;
void main() {
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
TexCoords = vertex.zw;
}
)";

354
src/shortcuts.cpp Normal file
View File

@ -0,0 +1,354 @@
#include <cstdlib>
#include <sdbus-c++/IProxy.h>
#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
#include <cstdint>
#include <memory>
#include <string>
#include <unistd.h>
#include <utility>
#include <random>
#include <vector>
#include <map>
#include "shortcuts.h"
const char* xdgName = "org.freedesktop.portal.Desktop";
const char* xdgPath = "/org/freedesktop/portal/desktop";
const char* shortcutsInterface = "org.freedesktop.portal.GlobalShortcuts";
const char* requstInterface = "org.freedesktop.portal.Request";
GlobalShortcuts::GlobalShortcuts(const char* const tokenPrefix)
: m_TokenPrefix(tokenPrefix), m_Shortcuts() { };
std::string GlobalShortcuts::AddNumToToken()
{
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<std::mt19937::result_type> dist(1000,9999);
return m_TokenPrefix + std::to_string(dist(rng));
}
void GlobalShortcuts::addShortcut(const std::string& id, const std::string& description, const std::string& trigger, shortcut_callback_t callback, void* userData)
{
std::map<std::string, sdbus::Variant> smap;
smap["description"] = sdbus::Variant(description);
smap["preferred_trigger"] = sdbus::Variant(trigger);
m_Shortcuts.emplace_back(id, smap);
m_Callbacks[id] = ShortcutCallback{ userData, callback };
}
int GlobalShortcuts::createSession()
{
bool requestInProgress = false;
int result = 1;
auto conn = sdbus::createSessionBusConnection();
m_ConnName = conn->getUniqueName();
m_Sender = m_ConnName.substr(1);
m_xdgProxy = sdbus::createProxy(std::move(conn), xdgName, xdgPath);
std::map<std::string, sdbus::Variant> create_session_args;
std::string token = AddNumToToken();
create_session_args["handle_token"] = sdbus::Variant(token);
create_session_args["session_handle_token"] = sdbus::Variant(m_TokenPrefix);
for (size_t i = 1; i < m_Sender.length(); i++) {
if (m_Sender[i] == '.') {
m_Sender[i] = '_';
}
}
std::string expectedRequestPath = "/org/freedesktop/portal/desktop/request/" + m_Sender + "/" + token;
auto requestProxy = sdbus::createProxy(m_xdgProxy->getConnection(), xdgName, expectedRequestPath);
requestProxy->uponSignal("Response").onInterface(requstInterface)
.call([this, &requestInProgress, &result](uint32_t result_code, std::map<std::string, sdbus::Variant> res_map)
{
if (result_code == 0) {
this->m_SessionPath = res_map.at("session_handle").get<std::string>();
} else {
std::cerr << "Failed to create GlobalShortcuts session" << std::endl;
}
result = result_code;
requestInProgress = false;
});
requestProxy->finishRegistration();
sdbus::ObjectPath requestPath;
requestInProgress = true;
m_xdgProxy->callMethod("CreateSession").onInterface(shortcutsInterface)
.withArguments(create_session_args).storeResultsTo(requestPath);
std::unique_ptr<sdbus::IProxy> requestProxy2;
if (requestPath != expectedRequestPath) {
requestProxy->unregister();
requestProxy2 = sdbus::createProxy(m_xdgProxy->getConnection(), xdgName, requestPath);
requestProxy2->uponSignal("Response").onInterface(requstInterface)
.call([this, &requestInProgress, &result](uint32_t result_code, std::map<std::string, sdbus::Variant> res_map)
{
if (result_code == 0) {
this->m_SessionPath = res_map.at("session_handle").get<sdbus::ObjectPath>();
} else {
std::cerr << "Failed to create GlobalShortcuts session" << std::endl;
}
result = result_code;
requestInProgress = false;
});
requestProxy2->finishRegistration();
}
while (requestInProgress) {
usleep(500);
}
return result;
}
bool GlobalShortcuts::alreadyBound()
{
std::vector<dbus_shortcut_t> binds = listBinds();
if (binds.size() != m_Shortcuts.size()) {
return false;
}
for (dbus_shortcut_t shortcut: m_Shortcuts) {
const std::string shortcut_id = shortcut.get<0>();
bool match = false;
for (dbus_shortcut_t bind: binds) {
const std::string bind_id = bind.get<0>();
if (shortcut_id == bind_id) {
match = true;
break;
}
}
if (!match) {
return false;
}
}
return true;
}
std::vector<dbus_shortcut_t> GlobalShortcuts::listBinds()
{
std::vector<dbus_shortcut_t> binds;
bool requestInProgress = false;
std::map<std::string, sdbus::Variant> list_shortcuts_args;
const std::string token = AddNumToToken();
list_shortcuts_args["handle_token"] = sdbus::Variant(token);
sdbus::ObjectPath expectedRequestPath = "/org/freedesktop/portal/desktop/request/" + m_Sender + "/" + token;
auto requestProxy = sdbus::createProxy(m_xdgProxy->getConnection(), xdgName, expectedRequestPath);
requestProxy->uponSignal("Response").onInterface(requstInterface)
.call([&requestInProgress, &binds](uint32_t result_code, std::map<std::string, sdbus::Variant> res_map)
{
if (result_code == 0) {
binds = res_map.at("shortcuts").get<std::vector<dbus_shortcut_t>>();
// for (dbus_shortcut_t bind: binds) {
// std::cout << "id: " << bind.get<0>();
// std::cout << ", desc: " << bind.get<1>().at("description").get<std::string>();
// std::cout << ", trigger: " << bind.get<1>().at("trigger_description").get<std::string>();
// std::cout << std::endl << std::endl;
// }
} else {
std::cerr << "Failed to list shortcuts" << std::endl;
}
requestInProgress = false;
});
requestProxy->finishRegistration();
sdbus::ObjectPath requestPath;
requestInProgress = true;
m_xdgProxy->callMethod("ListShortcuts").onInterface(shortcutsInterface)
.withArguments(m_SessionPath, list_shortcuts_args).storeResultsTo(requestPath);
std::unique_ptr<sdbus::IProxy> requestProxy2;
if (requestPath != expectedRequestPath) {
requestProxy->unregister();
requestProxy2 = sdbus::createProxy(m_xdgProxy->getConnection(), xdgName, requestPath);
requestProxy2->uponSignal("Response").onInterface(requstInterface)
.call([&requestInProgress, &binds](uint32_t result_code, std::map<std::string, sdbus::Variant> res_map)
{
if (result_code == 0) {
binds = res_map.at("shortcuts").get<std::vector<dbus_shortcut_t>>();
} else {
std::cerr << "Failed to list shortcuts" << std::endl;
}
requestInProgress = false;
});
requestProxy2->finishRegistration();
}
while (requestInProgress) {
usleep(500);
}
return binds;
}
int GlobalShortcuts::bindKeys()
{
bool requestInProgress = false;
int result = 1;
// std::cout << "session: " << this->sessionPath.c_str() << std::endl;
std::map<std::string, sdbus::Variant> bind_shortcuts_args;
const std::string token = AddNumToToken();
bind_shortcuts_args["handle_token"] = sdbus::Variant(token);
sdbus::ObjectPath expectedRequestPath = "/org/freedesktop/portal/desktop/request/" + m_Sender + "/" + token;
auto requestProxy = sdbus::createProxy(m_xdgProxy->getConnection(), xdgName, expectedRequestPath);
requestProxy->uponSignal("Response").onInterface(requstInterface)
.call([&requestInProgress, &result](uint32_t result_code, std::map<std::string, sdbus::Variant> res_map){
(void) res_map;
if (result_code == 0) {
// auto binds = res_map.at("shortcuts").get<std::vector<dbus_shortcut_t>>();
// for (auto bind: binds) {
// std::cout << "id: " << bind.get<0>();
// std::cout << ", desc: " << bind.get<1>().at("description").get<std::string>();
// std::cout << ", trigger: " << bind.get<1>().at("trigger_description").get<std::string>();
// std::cout << std::endl;
// }
} else {
std::cerr << "Failed to bind shortcuts" << std::endl;
}
result = result_code;
requestInProgress = false;
});
requestProxy->finishRegistration();
sdbus::ObjectPath requestPath;
requestInProgress = true;
m_xdgProxy->callMethod("BindShortcuts").onInterface(shortcutsInterface)
.withArguments(m_SessionPath, m_Shortcuts, "", bind_shortcuts_args).storeResultsTo(requestPath);
std::unique_ptr<sdbus::IProxy> requestProxy2;
if (requestPath != expectedRequestPath) {
requestProxy->unregister();
requestProxy2 = sdbus::createProxy(m_xdgProxy->getConnection(), xdgName, requestPath);
requestProxy2->uponSignal("Response").onInterface(requstInterface)
.call([&requestInProgress, &result](uint32_t result_code, std::map<std::string, sdbus::Variant> res_map){
(void) res_map;
if (result_code == 0) {
std::cerr << "Failed to bind shortcuts" << std::endl;
}
result = result_code;
requestInProgress = false;
});
requestProxy2->finishRegistration();
}
while (requestInProgress) {
usleep(500);
}
return result;
}
void GlobalShortcuts::listen()
{
m_xdgProxy->uponSignal("Activated").onInterface(shortcutsInterface)
.call([this](sdbus::ObjectPath session_handle, const std::string& shortcut_id, uint64_t timestamp, std::map<std::string, sdbus::Variant> options)
{
(void) options;
(void) session_handle;
(void) timestamp;
// std::cout << "Shortcut activated!" << std::endl;
// std::cout << "session: " << session_handle << ", id: " << shortcut_id << ", time: " << timestamp << std::endl << std::endl;
ShortcutCallback cb = this->m_Callbacks[shortcut_id];
cb.callback(cb.userData);
});
m_xdgProxy->finishRegistration();
}
// Very basic example
//int main()
//{
// GlobalShortcuts shortcuts;
//
// if (shortcuts.createSession() != 0) {
// std::cout << "Failed to create shortcuts session" << std::endl;
// return -1;
// }
//
// shortcuts.addShortcut("test1", "Prints things", "CTRL+SHIFT+a");
// shortcuts.addShortcut("test2", "Prints things, but like, different", "CTRL+SHIFT+b");
//
// if (shortcuts.listBinds().size() == 0) {
// std::cout << "Requsting to bind keys" << std::endl;
// shortcuts.bindKeys();
// }
//
// shortcuts.listen();
//
// while (true) {};
//}
//
// Code for working with libdbus directly. There isn't a lot of information around on how to use it
// And it is pretty difficult to use
//void connect()
//{
// DBusConnection* dbus_conn = nullptr;
// DBusError dbus_error;
//
// dbus_error_init(&dbus_error);
//
// dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &dbus_error);
// std::cout << "Connected to DBUS as \"" << dbus_bus_get_unique_name(dbus_conn) << "\"." << std::endl;
//
// // Request connection to GlobalShortcuts portal
// DBusMessage* conn_request_msg = dbus_message_new_method_call("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.GlobalShortcuts", "CreateSession");
// DBusMessage* reply = dbus_connection_send_with_reply_and_block(dbus_conn, conn_request_msg, DBUS_TIMEOUT_USE_DEFAULT, &dbus_error);
//
// const char* conn_request_path = nullptr;
// dbus_message_get_args(reply, &dbus_error, DBUS_TYPE_OBJECT_PATH, conn_request_path, DBUS_TYPE_INVALID);
//
// dbus_message_unref(reply);
// dbus_message_unref(conn_request_msg);
//
// std::string rule = "type='signal',path='";
// rule = rule + conn_request_path + '\'';
// dbus_bus_add_match(dbus_conn, rule.c_str(), &dbus_error);
// while (true) {
// dbus_connection_read_write(dbus_conn, DBUS_TIMEOUT_USE_DEFAULT);
// DBusMessage* msg_in = dbus_connection_pop_message(dbus_conn);
// if (msg_in == nullptr) {
// continue;
// }
//
// if (dbus_message_is_signal(msg_in, "org.freedesktop.portal.Request", "Response")) {
// uint32_t result;
//
// dbus_message_get_args(msg_in, &dbus_error, DBUS_TYPE_UINT32, &result, DBUS_TYPE_ARRAY, , DBUS_TYPE_INVALID);
//
// std::cout << "Global shortcut session creation result: " << result << std::endl;
// }
//
// dbus_message_unref(msg_in);
// }
//
// dbus_connection_unref(dbus_conn);
//}

51
src/timer.cpp Normal file
View File

@ -0,0 +1,51 @@
#include <chrono>
#include <cstdint>
#include "timer.h"
Timer::Timer()
{
Clear();
}
TimeDuration Timer::GetTimeLeft()
{
auto time_left = end_time - std::chrono::system_clock::now();
bool negative = false;
if (time_left.count() < 0) {
time_left *= -1;
negative = true;
}
return TimeDuration{
std::chrono::duration_cast<std::chrono::hours>(time_left).count(),
std::chrono::duration_cast<std::chrono::minutes>(time_left).count() % 60,
std::chrono::duration_cast<std::chrono::seconds>(time_left).count() % 60,
std::chrono::duration_cast<std::chrono::milliseconds>(time_left).count() % 1000,
std::chrono::duration_cast<std::chrono::minutes>(time_left).count(),
std::chrono::duration_cast<std::chrono::seconds>(time_left).count(),
std::chrono::duration_cast<std::chrono::milliseconds>(time_left).count(),
negative,
};
}
void Timer::AddMinutes(int64_t minutes)
{
const auto time_left = end_time - std::chrono::system_clock::now();
if (time_left.count() <= 0) {
end_time = std::chrono::system_clock::now() + std::chrono::minutes(minutes);
} else {
end_time += std::chrono::minutes(minutes);
}
}
void Timer::Clear()
{
end_time = std::chrono::system_clock::now() - std::chrono::seconds(60);
}

20
timer.desktop Executable file
View File

@ -0,0 +1,20 @@
[Desktop Entry]
Categories=Utility;
Comment[en_US]=Timer osd controlled with global shortcuts
Comment=Timer osd controlled with global shortcuts
Exec=timer_overlay
GenericName[en_US]=Overlay
GenericName=Overlay
MimeType=
Name[en_US]=Timer Overlay
Name=Timer Overlay
NoDisplay=false
Path=/usr/bin
SingleMainWindow=true
StartupNotify=false
StartupWMClass=timer-overlay
Terminal=false
TerminalOptions=
Type=Application
X-KDE-SubstituteUID=false
X-KDE-Username=