mirror of
https://github.com/hyprwm/hyprcursor.git
synced 2025-05-13 05:40:38 +01:00
Initial commit: very basics
This commit is contained in:
parent
47d8cfc1c8
commit
5227dcc787
15 changed files with 1072 additions and 0 deletions
65
.clang-format
Normal file
65
.clang-format
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
|
||||||
|
AccessModifierOffset: -2
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignConsecutiveMacros: true
|
||||||
|
AlignConsecutiveAssignments: true
|
||||||
|
AlignEscapedNewlines: Right
|
||||||
|
AlignOperands: false
|
||||||
|
AlignTrailingComments: true
|
||||||
|
AllowAllArgumentsOnNextLine: true
|
||||||
|
AllowAllConstructorInitializersOnNextLine: true
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: true
|
||||||
|
AllowShortBlocksOnASingleLine: true
|
||||||
|
AllowShortCaseLabelsOnASingleLine: true
|
||||||
|
AllowShortFunctionsOnASingleLine: Empty
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
AllowShortLambdasOnASingleLine: All
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: None
|
||||||
|
AlwaysBreakAfterReturnType: None
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
AlwaysBreakTemplateDeclarations: Yes
|
||||||
|
BreakBeforeBraces: Attach
|
||||||
|
BreakBeforeTernaryOperators: false
|
||||||
|
BreakConstructorInitializers: AfterColon
|
||||||
|
ColumnLimit: 180
|
||||||
|
CompactNamespaces: false
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
FixNamespaceComments: false
|
||||||
|
IncludeBlocks: Preserve
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentWidth: 4
|
||||||
|
PointerAlignment: Left
|
||||||
|
ReflowComments: false
|
||||||
|
SortIncludes: false
|
||||||
|
SortUsingDeclarations: false
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceAfterLogicalNot: false
|
||||||
|
SpaceAfterTemplateKeyword: true
|
||||||
|
SpaceBeforeCtorInitializerColon: true
|
||||||
|
SpaceBeforeInheritanceColon: true
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceBeforeRangeBasedForLoopColon: true
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesBeforeTrailingComments: 1
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInContainerLiterals: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
Standard: Auto
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Never
|
||||||
|
|
||||||
|
AllowShortEnumsOnASingleLine: false
|
||||||
|
|
||||||
|
BraceWrapping:
|
||||||
|
AfterEnum: false
|
||||||
|
|
||||||
|
AlignConsecutiveDeclarations: AcrossEmptyLines
|
||||||
|
|
||||||
|
NamespaceIndentation: All
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.vscode/
|
||||||
|
build/
|
71
CMakeLists.txt
Normal file
71
CMakeLists.txt
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
cmake_minimum_required(VERSION 3.19)
|
||||||
|
|
||||||
|
set(HYPRCURSOR_VERSION "0.1.0")
|
||||||
|
|
||||||
|
project(hyprcursor
|
||||||
|
VERSION ${HYPRCURSOR_VERSION}
|
||||||
|
DESCRIPTION "A library and toolkit for the Hyprland cursor format"
|
||||||
|
)
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
set(PREFIX ${CMAKE_INSTALL_PREFIX})
|
||||||
|
set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR})
|
||||||
|
set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})
|
||||||
|
|
||||||
|
configure_file(hyprcursor.pc.in hyprcursor.pc @ONLY)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprlang>=0.4.0 libzip cairo)
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
||||||
|
message(STATUS "Configuring hyprcursor in Debug")
|
||||||
|
add_compile_definitions(HYPRLAND_DEBUG)
|
||||||
|
else()
|
||||||
|
add_compile_options(-O3)
|
||||||
|
message(STATUS "Configuring hyprcursor in Release")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "libhyprcursor/*.cpp" "include/hyprcursor.hpp" "include/hyprcursor.h")
|
||||||
|
|
||||||
|
add_library(hyprcursor SHARED ${SRCFILES})
|
||||||
|
target_include_directories( hyprcursor
|
||||||
|
PUBLIC "./include"
|
||||||
|
PRIVATE "./libhyprcursor"
|
||||||
|
)
|
||||||
|
set_target_properties(hyprcursor PROPERTIES
|
||||||
|
VERSION ${hyprcursor_VERSION}
|
||||||
|
SOVERSION 0
|
||||||
|
PUBLIC_HEADER include/hyprcursor.hpp
|
||||||
|
PUBLIC_HEADER include/hyprcursor.h
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(hyprcursor PkgConfig::deps)
|
||||||
|
|
||||||
|
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
# for std::expected.
|
||||||
|
# probably evil. Arch's clang is very outdated tho...
|
||||||
|
target_compile_options(hyprcursor PUBLIC -std=gnu++2b -D__cpp_concepts=202002L -Wno-macro-redefined)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# hyprcursor-util
|
||||||
|
add_subdirectory(hyprcursor-util)
|
||||||
|
|
||||||
|
install(TARGETS hyprcursor)
|
||||||
|
|
||||||
|
# tests
|
||||||
|
add_custom_target(tests)
|
||||||
|
|
||||||
|
add_executable(hyprcursor_test "tests/test.cpp")
|
||||||
|
target_link_libraries(hyprcursor_test PRIVATE hyprcursor)
|
||||||
|
add_test(NAME "Test libhyprcursor" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test)
|
||||||
|
add_dependencies(tests hyprcursor_test)
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
install(TARGETS hyprcursor
|
||||||
|
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
install(FILES ${CMAKE_BINARY_DIR}/hyprcursor.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
23
hyprcursor-util/CMakeLists.txt
Normal file
23
hyprcursor-util/CMakeLists.txt
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
cmake_minimum_required(VERSION 3.19)
|
||||||
|
|
||||||
|
project(
|
||||||
|
hyprcursor-util
|
||||||
|
DESCRIPTION "A utility for creating and converting hyprcursor themes"
|
||||||
|
)
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprlang>=0.4.0 libzip)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
add_executable(hyprcursor-util ${SRCFILES})
|
||||||
|
|
||||||
|
target_link_libraries(hyprcursor-util PkgConfig::deps)
|
||||||
|
target_include_directories(hyprcursor-util
|
||||||
|
PRIVATE
|
||||||
|
.
|
||||||
|
)
|
||||||
|
|
||||||
|
install(TARGETS hyprcursor-util)
|
1
hyprcursor-util/internalSharedTypes.hpp
Symbolic link
1
hyprcursor-util/internalSharedTypes.hpp
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../libhyprcursor/internalSharedTypes.hpp
|
246
hyprcursor-util/src/main.cpp
Normal file
246
hyprcursor-util/src/main.cpp
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <zip.h>
|
||||||
|
#include <optional>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <hyprlang.hpp>
|
||||||
|
#include "internalSharedTypes.hpp"
|
||||||
|
|
||||||
|
static std::string removeBeginEndSpacesTabs(std::string str) {
|
||||||
|
if (str.empty())
|
||||||
|
return str;
|
||||||
|
|
||||||
|
int countBefore = 0;
|
||||||
|
while (str[countBefore] == ' ' || str[countBefore] == '\t') {
|
||||||
|
countBefore++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int countAfter = 0;
|
||||||
|
while ((int)str.length() - countAfter - 1 >= 0 && (str[str.length() - countAfter - 1] == ' ' || str[str.length() - 1 - countAfter] == '\t')) {
|
||||||
|
countAfter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.substr(countBefore, str.length() - countBefore - countAfter);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<SCursorTheme> currentTheme;
|
||||||
|
|
||||||
|
static Hyprlang::CParseResult parseDefineSize(const char* C, const char* V) {
|
||||||
|
Hyprlang::CParseResult result;
|
||||||
|
const std::string VALUE = V;
|
||||||
|
|
||||||
|
if (!VALUE.contains(",")) {
|
||||||
|
result.setError("Invalid define_size");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto LHS = removeBeginEndSpacesTabs(VALUE.substr(0, VALUE.find_first_of(",")));
|
||||||
|
const auto RHS = removeBeginEndSpacesTabs(VALUE.substr(VALUE.find_first_of(",") + 1));
|
||||||
|
|
||||||
|
SCursorImage image;
|
||||||
|
image.filename = RHS;
|
||||||
|
|
||||||
|
try {
|
||||||
|
image.size = std::stoull(LHS);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
result.setError(e.what());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTheme->shapes.back().images.push_back(image);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Hyprlang::CParseResult parseOverride(const char* C, const char* V) {
|
||||||
|
Hyprlang::CParseResult result;
|
||||||
|
const std::string VALUE = V;
|
||||||
|
|
||||||
|
currentTheme->shapes.back().overrides.push_back(V);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> createCursorThemeFromPath(const std::string& path, const std::string& out_ = {}) {
|
||||||
|
if (!std::filesystem::exists(path))
|
||||||
|
return "input path does not exist";
|
||||||
|
|
||||||
|
std::string out = out_.empty() ? path.substr(0, path.find_last_of('/') + 1) + "theme/" : out_;
|
||||||
|
|
||||||
|
const auto MANIFESTPATH = path + "/manifest.hl";
|
||||||
|
if (!std::filesystem::exists(MANIFESTPATH))
|
||||||
|
return "manifest.hl is missing";
|
||||||
|
|
||||||
|
std::unique_ptr<Hyprlang::CConfig> manifest;
|
||||||
|
try {
|
||||||
|
manifest = std::make_unique<Hyprlang::CConfig>(MANIFESTPATH.c_str(), Hyprlang::SConfigOptions{});
|
||||||
|
manifest->addConfigValue("cursors_directory", Hyprlang::STRING{""});
|
||||||
|
manifest->commence();
|
||||||
|
manifest->parse();
|
||||||
|
} catch (const char* err) { return "failed parsing manifest: " + std::string{err}; }
|
||||||
|
|
||||||
|
const std::string CURSORSSUBDIR = std::any_cast<Hyprlang::STRING>(manifest->getConfigValue("cursors_directory"));
|
||||||
|
const std::string CURSORDIR = path + "/" + CURSORSSUBDIR;
|
||||||
|
|
||||||
|
if (CURSORSSUBDIR.empty() || !std::filesystem::exists(CURSORDIR))
|
||||||
|
return "manifest: cursors_directory missing or empty";
|
||||||
|
|
||||||
|
// iterate over the directory and record all cursors
|
||||||
|
|
||||||
|
currentTheme = std::make_unique<SCursorTheme>();
|
||||||
|
for (auto& dir : std::filesystem::directory_iterator(CURSORDIR)) {
|
||||||
|
const auto METAPATH = dir.path().string() + "/meta.hl";
|
||||||
|
|
||||||
|
auto& SHAPE = currentTheme->shapes.emplace_back();
|
||||||
|
|
||||||
|
//
|
||||||
|
std::unique_ptr<Hyprlang::CConfig> meta;
|
||||||
|
|
||||||
|
try {
|
||||||
|
meta = std::make_unique<Hyprlang::CConfig>(METAPATH.c_str(), Hyprlang::SConfigOptions{});
|
||||||
|
meta->addConfigValue("hotspot_x", Hyprlang::FLOAT{0.F});
|
||||||
|
meta->addConfigValue("hotspot_y", Hyprlang::FLOAT{0.F});
|
||||||
|
meta->addConfigValue("resize_algorithm", Hyprlang::STRING{"nearest"});
|
||||||
|
meta->registerHandler(::parseDefineSize, "define_size", {.allowFlags = false});
|
||||||
|
meta->registerHandler(::parseOverride, "define_override", {.allowFlags = false});
|
||||||
|
meta->commence();
|
||||||
|
meta->parse();
|
||||||
|
} catch (const char* err) { return "failed parsing meta (" + METAPATH + "): " + std::string{err}; }
|
||||||
|
|
||||||
|
// check if we have at least one image.
|
||||||
|
for (auto& i : SHAPE.images) {
|
||||||
|
if (!std::filesystem::exists(dir.path().string() + "/" + i.filename))
|
||||||
|
return "meta invalid: image " + i.filename + " does not exist";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SHAPE.images.empty())
|
||||||
|
return "meta invalid: no images for shape " + dir.path().stem().string();
|
||||||
|
|
||||||
|
SHAPE.directory = dir.path().stem().string();
|
||||||
|
SHAPE.hotspotX = std::any_cast<float>(meta->getConfigValue("hotspot_x"));
|
||||||
|
SHAPE.hotspotY = std::any_cast<float>(meta->getConfigValue("hotspot_y"));
|
||||||
|
SHAPE.resizeAlgo = std::string{std::any_cast<Hyprlang::STRING>(meta->getConfigValue("resize_algorithm"))} == "nearest" ? RESIZE_NEAREST : RESIZE_BILINEAR;
|
||||||
|
|
||||||
|
std::cout << "Shape " << SHAPE.directory << ": \n\toverrides: " << SHAPE.overrides.size() << "\n\tsizes: " << SHAPE.images.size() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// create output fs structure
|
||||||
|
if (!std::filesystem::exists(out))
|
||||||
|
std::filesystem::create_directory(out);
|
||||||
|
else {
|
||||||
|
// clear the entire thing, avoid melting themes together
|
||||||
|
std::filesystem::remove_all(out);
|
||||||
|
std::filesystem::create_directory(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// manifest is copied
|
||||||
|
std::filesystem::copy(MANIFESTPATH, out + "/manifest.hl");
|
||||||
|
|
||||||
|
// create subdir for cursors
|
||||||
|
std::filesystem::create_directory(out + "/" + CURSORSSUBDIR);
|
||||||
|
|
||||||
|
// create zips (.hlc) for each
|
||||||
|
for (auto& shape : currentTheme->shapes) {
|
||||||
|
const auto CURRENTCURSORSDIR = path + "/" + CURSORSSUBDIR + "/" + shape.directory;
|
||||||
|
const auto OUTPUTFILE = out + "/" + CURSORSSUBDIR + "/" + shape.directory + ".hlc";
|
||||||
|
int errp = 0;
|
||||||
|
zip_t* zip = zip_open(OUTPUTFILE.c_str(), ZIP_CREATE | ZIP_EXCL, &errp);
|
||||||
|
|
||||||
|
if (!zip) {
|
||||||
|
zip_error_t ziperror;
|
||||||
|
zip_error_init_with_code(&ziperror, errp);
|
||||||
|
return "Failed to open " + OUTPUTFILE + " for writing: " + zip_error_strerror(&ziperror);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add meta.hl
|
||||||
|
zip_source_t* meta = zip_source_file(zip, (CURRENTCURSORSDIR + "/meta.hl").c_str(), 0, 0);
|
||||||
|
if (!meta)
|
||||||
|
return "(1) failed to add meta " + (CURRENTCURSORSDIR + "/meta.hl") + " to hlc";
|
||||||
|
if (zip_file_add(zip, "meta.hl", meta, ZIP_FL_ENC_UTF_8) < 0)
|
||||||
|
return "(2) failed to add meta " + (CURRENTCURSORSDIR + "/meta.hl") + " to hlc";
|
||||||
|
|
||||||
|
meta = nullptr;
|
||||||
|
|
||||||
|
// add each cursor png
|
||||||
|
for (auto& i : shape.images) {
|
||||||
|
zip_source_t* image = zip_source_file(zip, (CURRENTCURSORSDIR + "/" + i.filename).c_str(), 0, 0);
|
||||||
|
if (!image)
|
||||||
|
return "(1) failed to add image " + (CURRENTCURSORSDIR + "/" + i.filename) + " to hlc";
|
||||||
|
if (zip_file_add(zip, (i.filename).c_str(), image, ZIP_FL_ENC_UTF_8) < 0)
|
||||||
|
return "(2) failed to add image " + i.filename + " to hlc";
|
||||||
|
|
||||||
|
std::cout << "Added image " << i.filename << " to shape " << shape.directory << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// close zip and write
|
||||||
|
if (zip_close(zip) < 0) {
|
||||||
|
zip_error_t ziperror;
|
||||||
|
zip_error_init_with_code(&ziperror, errp);
|
||||||
|
return "Failed to write " + OUTPUTFILE + ": " + zip_error_strerror(&ziperror);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Written " << OUTPUTFILE << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// done!
|
||||||
|
std::cout << "Done, written " << currentTheme->shapes.size() << " shapes.\n";
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv, char** envp) {
|
||||||
|
|
||||||
|
if (argc < 2) {
|
||||||
|
std::cerr << "Not enough args.\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
eOperation op = OPERATION_CREATE;
|
||||||
|
std::string path = "", out = "";
|
||||||
|
|
||||||
|
for (size_t i = 1; i < argc; ++i) {
|
||||||
|
std::string arg = argv[i];
|
||||||
|
|
||||||
|
if (i == 1) {
|
||||||
|
// mode
|
||||||
|
if (arg == "--create" || arg == "-c") {
|
||||||
|
op = OPERATION_CREATE;
|
||||||
|
|
||||||
|
if (argc < 3) {
|
||||||
|
std::cerr << "Missing path for create.\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = argv[++i];
|
||||||
|
} else {
|
||||||
|
std::cerr << "Invalid mode.\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg == "-o" || arg == "--output") {
|
||||||
|
out = argv[++i];
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
std::cerr << "Unknown arg: " << arg << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (op) {
|
||||||
|
case OPERATION_CREATE: {
|
||||||
|
const auto RET = createCursorThemeFromPath(path, out);
|
||||||
|
if (RET.has_value()) {
|
||||||
|
std::cerr << "Failed: " << RET.value() << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: std::cerr << "Invalid mode.\n"; return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
10
hyprcursor.pc.in
Normal file
10
hyprcursor.pc.in
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
prefix=@PREFIX@
|
||||||
|
includedir=@INCLUDE@
|
||||||
|
libdir=@LIBDIR@
|
||||||
|
|
||||||
|
Name: hyprcursor
|
||||||
|
URL: https://github.com/hyprwm/hyprcursor
|
||||||
|
Description: A library and toolkit for the Hyprland cursor format
|
||||||
|
Version: @HYPRCURSOR_VERSION@
|
||||||
|
Cflags: -I${includedir}
|
||||||
|
Libs: -L${libdir} -lhyprcursor
|
44
include/hyprcursor.h
Normal file
44
include/hyprcursor.h
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
#ifndef HYPRCURSOR_H
|
||||||
|
#define HYPRCURSOR_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
#define CAPI extern "C"
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define CAPI
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct hyprcursor_manager_t;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Basic Hyprcursor manager.
|
||||||
|
|
||||||
|
Has to be created for either a specified theme, or
|
||||||
|
nullptr if you want to use a default from the env.
|
||||||
|
|
||||||
|
If no env is set, picks the first found.
|
||||||
|
|
||||||
|
If none found, hyprcursor_manager_valid will be false.
|
||||||
|
|
||||||
|
If loading fails, hyprcursor_manager_valid will be false.
|
||||||
|
|
||||||
|
The caller gets the ownership, call hyprcursor_manager_free to free this object.
|
||||||
|
*/
|
||||||
|
CAPI hyprcursor_manager_t* hyprcursor_manager_create(const char* theme_name);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Free a hyprcursor_manager_t*
|
||||||
|
*/
|
||||||
|
CAPI void hyprcursor_manager_free(hyprcursor_manager_t* manager);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns true if the theme was successfully loaded,
|
||||||
|
i.e. everything is A-OK and nothing should fail.
|
||||||
|
*/
|
||||||
|
CAPI bool hyprcursor_manager_valid(hyprcursor_manager_t* manager);
|
||||||
|
|
||||||
|
#endif
|
62
include/hyprcursor.hpp
Normal file
62
include/hyprcursor.hpp
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cairo/cairo.h>
|
||||||
|
|
||||||
|
class CHyprcursorImplementation;
|
||||||
|
|
||||||
|
namespace Hyprcursor {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Simple struct for some info about shape requests
|
||||||
|
*/
|
||||||
|
struct SCursorSurfaceInfo {
|
||||||
|
/*
|
||||||
|
Shape size
|
||||||
|
*/
|
||||||
|
unsigned int size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Basic Hyprcursor manager.
|
||||||
|
|
||||||
|
Has to be created for either a specified theme, or
|
||||||
|
nullptr if you want to use a default from the env.
|
||||||
|
|
||||||
|
If no env is set, picks the first found.
|
||||||
|
|
||||||
|
If none found, bool valid() will be false.
|
||||||
|
|
||||||
|
If loading fails, bool valid() will be false.
|
||||||
|
*/
|
||||||
|
class CHyprcursorManager {
|
||||||
|
public:
|
||||||
|
CHyprcursorManager(const char* themeName);
|
||||||
|
~CHyprcursorManager();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns true if the theme was successfully loaded,
|
||||||
|
i.e. everything is A-OK and nothing should fail.
|
||||||
|
*/
|
||||||
|
bool valid();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns a cairo_surface_t for a given cursor
|
||||||
|
shape and size.
|
||||||
|
|
||||||
|
Once done, call cursorSurfaceDone()
|
||||||
|
*/
|
||||||
|
cairo_surface_t* getSurfaceFor(const char* shape, const SCursorSurfaceInfo& info);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Marks a surface as done, meaning ready to be freed.
|
||||||
|
|
||||||
|
Always call after using a surface.
|
||||||
|
*/
|
||||||
|
void cursorSurfaceDone(cairo_surface_t* surface);
|
||||||
|
|
||||||
|
private:
|
||||||
|
CHyprcursorImplementation* impl = nullptr;
|
||||||
|
bool finalizedAndValid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
53
libhyprcursor/Log.hpp
Normal file
53
libhyprcursor/Log.hpp
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
enum eLogLevel {
|
||||||
|
TRACE = 0,
|
||||||
|
INFO,
|
||||||
|
LOG,
|
||||||
|
WARN,
|
||||||
|
ERR,
|
||||||
|
CRIT,
|
||||||
|
NONE
|
||||||
|
};
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace Debug {
|
||||||
|
inline bool quiet = false;
|
||||||
|
inline bool verbose = false;
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void log(eLogLevel level, const std::string& fmt, Args&&... args) {
|
||||||
|
|
||||||
|
#ifndef HYPRLAND_DEBUG
|
||||||
|
// don't log in release
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!verbose && level == TRACE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (quiet)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (level != NONE) {
|
||||||
|
std::cout << '[';
|
||||||
|
|
||||||
|
switch (level) {
|
||||||
|
case TRACE: std::cout << "TRACE"; break;
|
||||||
|
case INFO: std::cout << "INFO"; break;
|
||||||
|
case LOG: std::cout << "LOG"; break;
|
||||||
|
case WARN: std::cout << "WARN"; break;
|
||||||
|
case ERR: std::cout << "ERR"; break;
|
||||||
|
case CRIT: std::cout << "CRITICAL"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "] ";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::vformat(fmt, std::make_format_args(args...)) << "\n";
|
||||||
|
}
|
||||||
|
};
|
392
libhyprcursor/hyprcursor.cpp
Normal file
392
libhyprcursor/hyprcursor.cpp
Normal file
|
@ -0,0 +1,392 @@
|
||||||
|
#include "hyprcursor.hpp"
|
||||||
|
#include "internalSharedTypes.hpp"
|
||||||
|
#include "internalDefines.hpp"
|
||||||
|
#include <array>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <hyprlang.hpp>
|
||||||
|
#include <zip.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "Log.hpp"
|
||||||
|
|
||||||
|
using namespace Hyprcursor;
|
||||||
|
|
||||||
|
// directories for lookup
|
||||||
|
constexpr const std::array<const char*, 1> systemThemeDirs = {"/usr/share/icons"};
|
||||||
|
constexpr const std::array<const char*, 2> userThemeDirs = {"/.local/share/icons", "/.icons"};
|
||||||
|
|
||||||
|
//
|
||||||
|
static std::string themeNameFromEnv() {
|
||||||
|
const auto ENV = getenv("HYPRCURSOR_THEME");
|
||||||
|
if (!ENV)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
return std::string{ENV};
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string getFirstTheme() {
|
||||||
|
// try user directories first
|
||||||
|
|
||||||
|
const auto HOMEENV = getenv("HOME");
|
||||||
|
if (!HOMEENV)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
const std::string HOME{HOMEENV};
|
||||||
|
|
||||||
|
for (auto& dir : userThemeDirs) {
|
||||||
|
const auto FULLPATH = HOME + dir;
|
||||||
|
if (!std::filesystem::exists(FULLPATH))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// loop over dirs and see if any has a manifest.hl
|
||||||
|
for (auto& themeDir : std::filesystem::directory_iterator(FULLPATH)) {
|
||||||
|
if (!themeDir.is_directory())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl";
|
||||||
|
|
||||||
|
if (std::filesystem::exists(MANIFESTPATH))
|
||||||
|
return themeDir.path().stem().string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& dir : systemThemeDirs) {
|
||||||
|
const auto FULLPATH = dir;
|
||||||
|
if (!std::filesystem::exists(FULLPATH))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// loop over dirs and see if any has a manifest.hl
|
||||||
|
for (auto& themeDir : std::filesystem::directory_iterator(FULLPATH)) {
|
||||||
|
if (!themeDir.is_directory())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl";
|
||||||
|
|
||||||
|
if (std::filesystem::exists(MANIFESTPATH))
|
||||||
|
return themeDir.path().stem().string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string getFullPathForThemeName(const std::string& name) {
|
||||||
|
const auto HOMEENV = getenv("HOME");
|
||||||
|
if (!HOMEENV)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
const std::string HOME{HOMEENV};
|
||||||
|
|
||||||
|
for (auto& dir : userThemeDirs) {
|
||||||
|
const auto FULLPATH = HOME + dir;
|
||||||
|
if (!std::filesystem::exists(FULLPATH))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// loop over dirs and see if any has a manifest.hl
|
||||||
|
for (auto& themeDir : std::filesystem::directory_iterator(FULLPATH)) {
|
||||||
|
if (!themeDir.is_directory())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl";
|
||||||
|
|
||||||
|
if (std::filesystem::exists(MANIFESTPATH))
|
||||||
|
return std::filesystem::canonical(themeDir.path()).string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& dir : systemThemeDirs) {
|
||||||
|
const auto FULLPATH = dir;
|
||||||
|
if (!std::filesystem::exists(FULLPATH))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// loop over dirs and see if any has a manifest.hl
|
||||||
|
for (auto& themeDir : std::filesystem::directory_iterator(FULLPATH)) {
|
||||||
|
if (!themeDir.is_directory())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl";
|
||||||
|
|
||||||
|
if (std::filesystem::exists(MANIFESTPATH))
|
||||||
|
return std::filesystem::canonical(themeDir.path()).string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
CHyprcursorManager::CHyprcursorManager(const char* themeName_) {
|
||||||
|
std::string themeName = themeName_ ? themeName_ : "";
|
||||||
|
|
||||||
|
if (themeName.empty()) {
|
||||||
|
// try reading from env
|
||||||
|
themeName = themeNameFromEnv();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themeName.empty()) {
|
||||||
|
// try finding first, in the hierarchy
|
||||||
|
themeName = getFirstTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themeName.empty()) {
|
||||||
|
// holy shit we're done
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize theme
|
||||||
|
impl = new CHyprcursorImplementation;
|
||||||
|
impl->themeName = themeName;
|
||||||
|
impl->themeFullDir = getFullPathForThemeName(themeName);
|
||||||
|
|
||||||
|
if (impl->themeFullDir.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Debug::log(LOG, "Found theme {} at {}\n", impl->themeName, impl->themeFullDir);
|
||||||
|
|
||||||
|
const auto LOADSTATUS = impl->loadTheme();
|
||||||
|
|
||||||
|
if (LOADSTATUS.has_value()) {
|
||||||
|
Debug::log(ERR, "Theme failed to load with {}\n", LOADSTATUS.value());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
finalizedAndValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHyprcursorManager::~CHyprcursorManager() {
|
||||||
|
if (impl)
|
||||||
|
delete impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CHyprcursorManager::valid() {
|
||||||
|
return finalizedAndValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
cairo_surface_t* CHyprcursorManager::getSurfaceFor(const char* shape_, const SCursorSurfaceInfo& info) {
|
||||||
|
std::string REQUESTEDSHAPE = shape_;
|
||||||
|
|
||||||
|
for (auto& shape : impl->theme.shapes) {
|
||||||
|
if (REQUESTEDSHAPE != shape.directory && std::find(shape.overrides.begin(), shape.overrides.end(), REQUESTEDSHAPE) == shape.overrides.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// matched :)
|
||||||
|
for (auto& image : impl->loadedShapes[&shape].images) {
|
||||||
|
if (image->side != info.size)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// found pixel-perfect size
|
||||||
|
return image->cairoSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resampling
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHyprcursorManager::cursorSurfaceDone(cairo_surface_t* surface) {
|
||||||
|
;
|
||||||
|
// TODO: when resampling.
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
static std::string removeBeginEndSpacesTabs(std::string str) {
|
||||||
|
if (str.empty())
|
||||||
|
return str;
|
||||||
|
|
||||||
|
int countBefore = 0;
|
||||||
|
while (str[countBefore] == ' ' || str[countBefore] == '\t') {
|
||||||
|
countBefore++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int countAfter = 0;
|
||||||
|
while ((int)str.length() - countAfter - 1 >= 0 && (str[str.length() - countAfter - 1] == ' ' || str[str.length() - 1 - countAfter] == '\t')) {
|
||||||
|
countAfter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.substr(countBefore, str.length() - countBefore - countAfter);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCursorTheme* currentTheme;
|
||||||
|
|
||||||
|
static Hyprlang::CParseResult parseDefineSize(const char* C, const char* V) {
|
||||||
|
Hyprlang::CParseResult result;
|
||||||
|
const std::string VALUE = V;
|
||||||
|
|
||||||
|
if (!VALUE.contains(",")) {
|
||||||
|
result.setError("Invalid define_size");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto LHS = removeBeginEndSpacesTabs(VALUE.substr(0, VALUE.find_first_of(",")));
|
||||||
|
const auto RHS = removeBeginEndSpacesTabs(VALUE.substr(VALUE.find_first_of(",") + 1));
|
||||||
|
|
||||||
|
SCursorImage image;
|
||||||
|
image.filename = RHS;
|
||||||
|
|
||||||
|
try {
|
||||||
|
image.size = std::stoull(LHS);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
result.setError(e.what());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTheme->shapes.back().images.push_back(image);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Hyprlang::CParseResult parseOverride(const char* C, const char* V) {
|
||||||
|
Hyprlang::CParseResult result;
|
||||||
|
const std::string VALUE = V;
|
||||||
|
|
||||||
|
currentTheme->shapes.back().overrides.push_back(V);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
PNG reading
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
static cairo_status_t readPNG(void* data, unsigned char* output, unsigned int len) {
|
||||||
|
const auto DATA = (SLoadedCursorImage*)data;
|
||||||
|
|
||||||
|
if (!DATA->data)
|
||||||
|
return CAIRO_STATUS_READ_ERROR;
|
||||||
|
|
||||||
|
size_t toRead = len > DATA->dataLen - DATA->readNeedle ? DATA->dataLen - DATA->readNeedle : len;
|
||||||
|
|
||||||
|
std::memcpy(output, DATA->data + DATA->readNeedle, toRead);
|
||||||
|
DATA->readNeedle += toRead;
|
||||||
|
|
||||||
|
if (DATA->readNeedle >= DATA->dataLen) {
|
||||||
|
delete[] (char*)DATA->data;
|
||||||
|
DATA->data = nullptr;
|
||||||
|
Debug::log(LOG, "cairo: png read, freeing mem");
|
||||||
|
}
|
||||||
|
|
||||||
|
return CAIRO_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
General
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::optional<std::string> CHyprcursorImplementation::loadTheme() {
|
||||||
|
|
||||||
|
currentTheme = &theme;
|
||||||
|
|
||||||
|
// load manifest
|
||||||
|
std::unique_ptr<Hyprlang::CConfig> manifest;
|
||||||
|
try {
|
||||||
|
// TODO: unify this between util and lib
|
||||||
|
manifest = std::make_unique<Hyprlang::CConfig>((themeFullDir + "/manifest.hl").c_str(), Hyprlang::SConfigOptions{});
|
||||||
|
manifest->addConfigValue("cursors_directory", Hyprlang::STRING{""});
|
||||||
|
manifest->commence();
|
||||||
|
manifest->parse();
|
||||||
|
} catch (const char* err) {
|
||||||
|
Debug::log(ERR, "Failed parsing manifest due to {}", err);
|
||||||
|
return std::string{"failed: "} + err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string CURSORSSUBDIR = std::any_cast<Hyprlang::STRING>(manifest->getConfigValue("cursors_directory"));
|
||||||
|
const std::string CURSORDIR = themeFullDir + "/" + CURSORSSUBDIR;
|
||||||
|
|
||||||
|
if (CURSORSSUBDIR.empty() || !std::filesystem::exists(CURSORDIR))
|
||||||
|
return "loadTheme: cursors_directory missing or empty";
|
||||||
|
|
||||||
|
for (auto& cursor : std::filesystem::directory_iterator(CURSORDIR)) {
|
||||||
|
if (!cursor.is_regular_file())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto& SHAPE = theme.shapes.emplace_back();
|
||||||
|
auto& LOADEDSHAPE = loadedShapes[&SHAPE];
|
||||||
|
|
||||||
|
// extract zip to raw data.
|
||||||
|
int errp = 0;
|
||||||
|
zip_t* zip = zip_open(cursor.path().string().c_str(), ZIP_RDONLY, &errp);
|
||||||
|
|
||||||
|
zip_file_t* meta_file = zip_fopen(zip, "meta.hl", ZIP_FL_UNCHANGED);
|
||||||
|
if (!meta_file)
|
||||||
|
return "cursor" + cursor.path().string() + "failed to load meta";
|
||||||
|
|
||||||
|
char* buffer = new char[1024 * 1024]; /* 1MB should be more than enough */
|
||||||
|
|
||||||
|
int readBytes = zip_fread(meta_file, buffer, 1024 * 1024 - 1);
|
||||||
|
|
||||||
|
zip_fclose(meta_file);
|
||||||
|
|
||||||
|
if (readBytes < 0) {
|
||||||
|
delete[] buffer;
|
||||||
|
return "cursor" + cursor.path().string() + "failed to read meta";
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[readBytes] = '\0';
|
||||||
|
|
||||||
|
std::unique_ptr<Hyprlang::CConfig> meta;
|
||||||
|
|
||||||
|
try {
|
||||||
|
meta = std::make_unique<Hyprlang::CConfig>(buffer, Hyprlang::SConfigOptions{.pathIsStream = true});
|
||||||
|
meta->addConfigValue("hotspot_x", Hyprlang::FLOAT{0.F});
|
||||||
|
meta->addConfigValue("hotspot_y", Hyprlang::FLOAT{0.F});
|
||||||
|
meta->addConfigValue("resize_algorithm", Hyprlang::STRING{"nearest"});
|
||||||
|
meta->registerHandler(::parseDefineSize, "define_size", {.allowFlags = false});
|
||||||
|
meta->registerHandler(::parseOverride, "define_override", {.allowFlags = false});
|
||||||
|
meta->commence();
|
||||||
|
meta->parse();
|
||||||
|
} catch (const char* err) { return "failed parsing meta: " + std::string{err}; }
|
||||||
|
|
||||||
|
delete[] buffer;
|
||||||
|
|
||||||
|
for (auto& i : SHAPE.images) {
|
||||||
|
// load image
|
||||||
|
Debug::log(LOG, "Loading {} for shape {}", i.filename, cursor.path().stem().string());
|
||||||
|
auto* IMAGE = LOADEDSHAPE.images.emplace_back(std::make_unique<SLoadedCursorImage>()).get();
|
||||||
|
IMAGE->side = i.size;
|
||||||
|
|
||||||
|
// read from zip
|
||||||
|
zip_file_t* image_file = zip_fopen(zip, i.filename.c_str(), ZIP_FL_UNCHANGED);
|
||||||
|
if (!image_file)
|
||||||
|
return "cursor" + cursor.path().string() + "failed to load image_file";
|
||||||
|
|
||||||
|
IMAGE->data = new char[1024 * 1024]; /* 1MB should be more than enough, again. This probably should be in the spec. */
|
||||||
|
|
||||||
|
IMAGE->dataLen = zip_fread(image_file, IMAGE->data, 1024 * 1024 - 1);
|
||||||
|
|
||||||
|
zip_fclose(image_file);
|
||||||
|
|
||||||
|
Debug::log(LOG, "Cairo: set up surface read");
|
||||||
|
|
||||||
|
IMAGE->cairoSurface = cairo_image_surface_create_from_png_stream(::readPNG, IMAGE);
|
||||||
|
|
||||||
|
if (const auto STATUS = cairo_surface_status(IMAGE->cairoSurface); STATUS != CAIRO_STATUS_SUCCESS) {
|
||||||
|
delete[] (char*)IMAGE->data;
|
||||||
|
IMAGE->data = nullptr;
|
||||||
|
return "Failed reading cairoSurface, status " + std::to_string((int)STATUS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SHAPE.images.empty())
|
||||||
|
return "meta invalid: no images for shape " + cursor.path().stem().string();
|
||||||
|
|
||||||
|
SHAPE.directory = cursor.path().stem().string();
|
||||||
|
SHAPE.hotspotX = std::any_cast<float>(meta->getConfigValue("hotspot_x"));
|
||||||
|
SHAPE.hotspotY = std::any_cast<float>(meta->getConfigValue("hotspot_y"));
|
||||||
|
SHAPE.resizeAlgo = std::string{std::any_cast<Hyprlang::STRING>(meta->getConfigValue("resize_algorithm"))} == "nearest" ? RESIZE_NEAREST : RESIZE_BILINEAR;
|
||||||
|
|
||||||
|
zip_discard(zip);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
17
libhyprcursor/hyprcursor_c.cpp
Normal file
17
libhyprcursor/hyprcursor_c.cpp
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#include "hyprcursor.h"
|
||||||
|
#include "hyprcursor.hpp"
|
||||||
|
|
||||||
|
using namespace Hyprcursor;
|
||||||
|
|
||||||
|
hyprcursor_manager_t* hyprcursor_manager_create(const char* theme_name) {
|
||||||
|
return (hyprcursor_manager_t*)new CHyprcursorManager(theme_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hyprcursor_manager_free(hyprcursor_manager_t* manager) {
|
||||||
|
delete (CHyprcursorManager*)manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hyprcursor_manager_valid(hyprcursor_manager_t* manager) {
|
||||||
|
const auto MGR = (CHyprcursorManager*)manager;
|
||||||
|
return MGR->valid();
|
||||||
|
}
|
40
libhyprcursor/internalDefines.hpp
Normal file
40
libhyprcursor/internalDefines.hpp
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "internalSharedTypes.hpp"
|
||||||
|
#include <optional>
|
||||||
|
#include <cairo/cairo.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
struct SLoadedCursorImage {
|
||||||
|
~SLoadedCursorImage() {
|
||||||
|
if (data)
|
||||||
|
delete[] (char*)data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read stuff
|
||||||
|
size_t readNeedle = 0;
|
||||||
|
void* data = nullptr;
|
||||||
|
size_t dataLen = 0;
|
||||||
|
|
||||||
|
cairo_surface_t* cairoSurface = nullptr;
|
||||||
|
int side = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SLoadedCursorShape {
|
||||||
|
std::vector<std::unique_ptr<SLoadedCursorImage>> images;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CHyprcursorImplementation {
|
||||||
|
public:
|
||||||
|
std::string themeName;
|
||||||
|
std::string themeFullDir;
|
||||||
|
|
||||||
|
SCursorTheme theme;
|
||||||
|
|
||||||
|
//
|
||||||
|
std::unordered_map<SCursorShape*, SLoadedCursorShape> loadedShapes;
|
||||||
|
|
||||||
|
//
|
||||||
|
std::optional<std::string> loadTheme();
|
||||||
|
};
|
29
libhyprcursor/internalSharedTypes.hpp
Normal file
29
libhyprcursor/internalSharedTypes.hpp
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
enum eOperation {
|
||||||
|
OPERATION_CREATE = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum eResizeAlgo {
|
||||||
|
RESIZE_BILINEAR = 0,
|
||||||
|
RESIZE_NEAREST = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SCursorImage {
|
||||||
|
std::string filename;
|
||||||
|
int size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SCursorShape {
|
||||||
|
std::string directory;
|
||||||
|
float hotspotX = 0, hotspotY = 0;
|
||||||
|
eResizeAlgo resizeAlgo = RESIZE_NEAREST;
|
||||||
|
std::vector<SCursorImage> images;
|
||||||
|
std::vector<std::string> overrides;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SCursorTheme {
|
||||||
|
std::vector<SCursorShape> shapes;
|
||||||
|
};
|
17
tests/test.cpp
Normal file
17
tests/test.cpp
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <hyprcursor.hpp>
|
||||||
|
#include <cairo/cairo.h>
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
Hyprcursor::CHyprcursorManager mgr(nullptr);
|
||||||
|
|
||||||
|
// get cursor for arrow
|
||||||
|
const auto ARROW = mgr.getSurfaceFor("arrow", Hyprcursor::SCursorSurfaceInfo{.size = 64});
|
||||||
|
|
||||||
|
// save to disk
|
||||||
|
const auto RET = cairo_surface_write_to_png(ARROW, "/tmp/arrow.png");
|
||||||
|
|
||||||
|
std::cout << "Cairo returned for write: " << RET << "\n";
|
||||||
|
|
||||||
|
return !mgr.valid();
|
||||||
|
}
|
Loading…
Reference in a new issue