Initial commit
This commit is contained in:
commit
2c136debce
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
build/
|
9
LICENSE
Normal file
9
LICENSE
Normal 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
99
Makefile
Normal 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
58
README.md
Normal 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
31
inc/character_utils.h
Normal 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
6
inc/shader_utils.h
Normal 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
2
inc/shadersrc.h
Normal 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
40
inc/shortcuts.h
Normal 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
31
inc/timer.h
Normal 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
12
shaders/shader.frag
Normal 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
11
shaders/shader.vert
Normal 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
137
src/character_utils.cpp
Normal 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
177
src/main.cpp
Normal 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
91
src/shader_utils.cpp
Normal 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
26
src/shadersrc.cpp
Normal 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
354
src/shortcuts.cpp
Normal 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
51
src/timer.cpp
Normal 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
20
timer.desktop
Executable 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=
|
Loading…
Reference in New Issue
Block a user