Compare commits
	
		
			6 Commits
		
	
	
		
			a67b787386
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8859504fed | |||
| 237208d972 | |||
| 42a9de3ba3 | |||
| 6f45a3bc70 | |||
| f61710010d | |||
| d368760f78 | 
| @@ -1,10 +1,25 @@ | ||||
| cmake_minimum_required(VERSION 3.16) | ||||
|  | ||||
| project(waylight LANGUAGES C CXX) | ||||
|  | ||||
| set(CMAKE_CXX_STANDARD 23) | ||||
| set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||||
| set(CMAKE_POSITION_INDEPENDENT_CODE ON) | ||||
|  | ||||
| find_program(cppcheck_exe NAMES cppcheck) | ||||
| if (cppcheck_exe) | ||||
| 	set(cppcheck_opts --enable=all --inline-suppr --quiet --suppressions-list=${PROJECT_SOURCE_DIR}/cppcheck.supp) | ||||
| 	add_custom_target(run_cppcheck | ||||
| 		COMMAND ${cppcheck_exe} | ||||
| 			--std=c++20 --enable=all --inline-suppr --quiet | ||||
| 			--suppressions-list=${PROJECT_SOURCE_DIR}/cppcheck.supp | ||||
| 			--project=${CMAKE_BINARY_DIR}/compile_commands.json | ||||
| 			--check-level=exhaustive | ||||
| 			-i ${CMAKE_BINARY_DIR} | ||||
| 		WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} | ||||
| 	) | ||||
| endif() | ||||
|  | ||||
| find_package(PkgConfig REQUIRED) | ||||
| pkg_check_modules(WAYLAND_CLIENT REQUIRED IMPORTED_TARGET wayland-client) | ||||
| pkg_check_modules(WAYLAND_EGL REQUIRED IMPORTED_TARGET wayland-egl) | ||||
| @@ -60,6 +75,32 @@ FetchContent_Declare( | ||||
| ) | ||||
| FetchContent_MakeAvailable(lunasvg) | ||||
|  | ||||
| FetchContent_Declare( | ||||
| 	SQLiteCpp | ||||
| 	GIT_REPOSITORY https://github.com/SRombauts/SQLiteCpp.git | ||||
| 	GIT_TAG "3.3.3" | ||||
| 	GIT_SHALLOW 1 | ||||
| ) | ||||
| FetchContent_MakeAvailable(SQLiteCpp) | ||||
|  | ||||
| FetchContent_Declare( | ||||
| 	cpptrace | ||||
| 	GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git | ||||
| 	GIT_TAG "v1.0.1" | ||||
| 	GIT_SHALLOW 1 | ||||
| ) | ||||
| FetchContent_MakeAvailable(cpptrace) | ||||
|  | ||||
| FetchContent_Declare( | ||||
| 	tomlplusplus | ||||
| 	GIT_REPOSITORY https://github.com/marzer/tomlplusplus | ||||
| 	GIT_TAG "v3.4.0" | ||||
| 	GIT_SHALLOW 1 | ||||
| ) | ||||
| FetchContent_MakeAvailable(tomlplusplus) | ||||
|  | ||||
| add_subdirectory(vendor) | ||||
|  | ||||
| find_program(WAYLAND_SCANNER wayland-scanner REQUIRED) | ||||
|  | ||||
| pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) | ||||
| @@ -156,7 +197,10 @@ add_custom_target(generate_protocols ALL | ||||
| add_executable(waylight | ||||
| 	${GEN_C_PRIVATES} | ||||
|  | ||||
| 	${CMAKE_CURRENT_SOURCE_DIR}/src/Config.cpp | ||||
| 	${CMAKE_CURRENT_SOURCE_DIR}/src/IconRegistry.cpp | ||||
| 	${CMAKE_CURRENT_SOURCE_DIR}/src/Cache.cpp | ||||
| 	${CMAKE_CURRENT_SOURCE_DIR}/src/InotifyWatcher.cpp | ||||
| 	${CMAKE_CURRENT_SOURCE_DIR}/src/TextRenderer.cpp | ||||
| 	${CMAKE_CURRENT_SOURCE_DIR}/src/ImGui.cpp | ||||
| 	${CMAKE_CURRENT_SOURCE_DIR}/src/App.cpp | ||||
| @@ -180,11 +224,15 @@ target_link_libraries(waylight PRIVATE | ||||
| 	PkgConfig::FONTCONFIG | ||||
| 	PkgConfig::HARFBUZZ | ||||
|  | ||||
| 	tomlplusplus::tomlplusplus | ||||
| 	cpptrace::cpptrace | ||||
| 	tinyfiledialogs | ||||
| 	mINI | ||||
| 	raylib | ||||
| 	msdfgen::msdfgen-core | ||||
| 	msdfgen::msdfgen-ext | ||||
| 	lunasvg::lunasvg | ||||
| 	SQLiteCpp | ||||
|  | ||||
| 	m | ||||
| 	dl | ||||
|   | ||||
							
								
								
									
										8
									
								
								cppcheck.supp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								cppcheck.supp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| unusedFunction | ||||
| shadowFunction | ||||
| missingIncludeSystem | ||||
| ignoredReturnValue | ||||
|  | ||||
| *:build/generated/* | ||||
| *:build/_deps/* | ||||
| *:vendor/* | ||||
| @@ -28,6 +28,9 @@ | ||||
|           libxkbcommon | ||||
|           fontconfig | ||||
|           harfbuzz | ||||
|           sqlite | ||||
| 	  zenity | ||||
| 	  boost | ||||
|         ]; | ||||
|         buildInputs = with pkgs; [ | ||||
|           cmake | ||||
|   | ||||
							
								
								
									
										158
									
								
								src/App.cpp
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								src/App.cpp
									
									
									
									
									
								
							| @@ -8,7 +8,9 @@ | ||||
| #include <cstdlib> | ||||
| #include <cstring> | ||||
| #include <poll.h> | ||||
| #include <print> | ||||
| #include <pthread.h> | ||||
| #include <ranges> | ||||
| #include <signal.h> | ||||
| #include <span> | ||||
| #include <sys/mman.h> | ||||
| @@ -32,6 +34,8 @@ | ||||
| #include "blur-client-protocol.h" | ||||
| #include "ext-background-effect-v1-client-protocol.h" | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| constexpr usize MAX_SURROUNDING_BYTES = 4000; | ||||
| @@ -139,6 +143,73 @@ App::App() | ||||
| 	init_egl(); | ||||
| 	init_signal(); | ||||
| 	init_theme_portal(); | ||||
|  | ||||
| 	{ | ||||
| 		auto const env = getenv("XDG_DATA_HOME"); | ||||
| 		if (env && *env) { | ||||
| 			if (std::filesystem::exists(env)) { | ||||
| 				m_data_home_dir = env; | ||||
| 			} | ||||
| 		} | ||||
| 		if (m_data_home_dir.empty()) { | ||||
| 			auto const home = getenv("HOME"); | ||||
| 			assert(home && *home); | ||||
| 			m_data_home_dir = std::filesystem::path(home) / ".local" / "share"; | ||||
| 			std::filesystem::create_directories(m_data_home_dir); | ||||
| 		} | ||||
| 		m_data_home_dir /= "waylight"; | ||||
| 		std::filesystem::create_directories(m_data_home_dir); | ||||
| 	} | ||||
|  | ||||
| 	{ | ||||
| 		auto const env = getenv("XDG_CONFIG_HOME"); | ||||
| 		if (env && *env) { | ||||
| 			if (std::filesystem::exists(env)) { | ||||
| 				m_config_home_dir = env; | ||||
| 			} | ||||
| 		} | ||||
| 		if (m_config_home_dir.empty()) { | ||||
| 			auto const home = getenv("HOME"); | ||||
| 			assert(home && *home); | ||||
| 			m_config_home_dir = std::filesystem::path(home) / ".config"; | ||||
| 			std::filesystem::create_directories(m_config_home_dir); | ||||
| 		} | ||||
| 		m_config_home_dir /= "waylight"; | ||||
| 		std::filesystem::create_directories(m_config_home_dir); | ||||
|  | ||||
| 		m_config = Config::load(m_config_home_dir); | ||||
| 	} | ||||
|  | ||||
| 	m_db = std::make_shared<SQLite::Database>(m_data_home_dir / "data.db", | ||||
| 	    SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); | ||||
|  | ||||
| 	SQLite::Statement(*m_db, R"( | ||||
| 		CREATE TABLE IF NOT EXISTS ApplicationCache ( | ||||
| 			id INTEGER PRIMARY KEY NOT NULL, | ||||
| 			type INTEGER NOT NULL, | ||||
| 			desktop_entry_path TEXT NOT NULL, | ||||
| 			terminal BOOL NOT NULL, | ||||
| 			no_display BOOL NOT NULL, | ||||
| 			path TEXT, | ||||
| 			comment TEXT, | ||||
| 			dbus_activatable BOOL NOT NULL | ||||
| 		) | ||||
| 	)") | ||||
| 	    .exec(); | ||||
|  | ||||
| 	SQLite::Statement(*m_db, R"( | ||||
| 		CREATE TABLE IF NOT EXISTS ApplicationActionCache ( | ||||
| 			id INTEGER PRIMARY KEY NOT NULL, | ||||
| 			id_app INTEGER NOT NULL, | ||||
| 			name TEXT NOT NULL, | ||||
| 			exec TEXT, | ||||
| 			icon TEXT, | ||||
| 			FOREIGN KEY (id_app) REFERENCES ApplicationCache(id) | ||||
| 		) | ||||
| 	)") | ||||
| 	    .exec(); | ||||
|  | ||||
| 	m_cache.emplace(m_db); | ||||
| } | ||||
|  | ||||
| App::~App() | ||||
| @@ -146,9 +217,8 @@ App::~App() | ||||
| 	if (m_sfd != -1) | ||||
| 		close(m_sfd); | ||||
|  | ||||
| 	for (auto &[_, tex] : m_textures) { | ||||
| 	for (auto &[_, tex] : m_textures) | ||||
| 		UnloadTexture(tex); | ||||
| 	} | ||||
|  | ||||
| 	destroy_layer_surface(); | ||||
|  | ||||
| @@ -383,8 +453,10 @@ auto App::init_wayland() -> void | ||||
|  | ||||
| 	static zwp_text_input_v3_listener text_input_listener {}; | ||||
| 	{ | ||||
| 		auto ti_enter | ||||
| 		    = [](void *data, zwp_text_input_v3 *, wl_surface *surface) -> void { | ||||
| 		auto ti_enter = | ||||
| 		    [](void *data, zwp_text_input_v3 *, | ||||
| 		        wl_surface *surface) // cppcheck-suppress constParameterPointer | ||||
| 		    -> void { | ||||
| 			auto *app { static_cast<App *>(data) }; | ||||
| 			bool const focused_surface | ||||
| 			    = surface && surface == app->m_wayland.surface; | ||||
| @@ -660,6 +732,13 @@ auto App::init_egl() -> void | ||||
|  | ||||
| 	ensure_egl_surface(); | ||||
|  | ||||
| 	{ | ||||
| 		auto const *env = getenv("WAYLIGHT_DEBUG"); | ||||
| 		if (env && *env) { | ||||
| 			SetTraceLogLevel(LOG_DEBUG); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	InitWindow(m_win_w, m_win_h, ""); | ||||
|  | ||||
| 	m_tr = std::make_shared<TextRenderer>(); | ||||
| @@ -1061,17 +1140,17 @@ auto App::update_text_input_state( | ||||
| 		    = std::max(1, static_cast<int32_t>(std::round(rect.width))); | ||||
| 		int32_t const height | ||||
| 		    = std::max(1, static_cast<int32_t>(std::round(rect.height))); | ||||
| 		bool const visible = cursor_info->visible; | ||||
| 		bool const cur_visible = cursor_info->visible; | ||||
|  | ||||
| 		if (rect.x != m_ime.last_cursor_rect.x | ||||
| 		    || rect.y != m_ime.last_cursor_rect.y | ||||
| 		    || rect.width != m_ime.last_cursor_rect.width | ||||
| 		    || rect.height != m_ime.last_cursor_rect.height | ||||
| 		    || visible != m_ime.last_cursor_visible) { | ||||
| 		    || cur_visible != m_ime.last_cursor_visible) { | ||||
| 			zwp_text_input_v3_set_cursor_rectangle( | ||||
| 			    m_wayland.text_input, x, y, width, height); | ||||
| 			m_ime.last_cursor_rect = rect; | ||||
| 			m_ime.last_cursor_visible = visible; | ||||
| 			m_ime.last_cursor_visible = cur_visible; | ||||
| 			state_dirty = true; | ||||
| 		} | ||||
| 	} | ||||
| @@ -1100,7 +1179,7 @@ auto App::pump_events() -> void | ||||
| 	if (ret > 0 && (fds[0].revents & POLLIN)) { | ||||
| 		if (prepared) { | ||||
| 			wl_display_read_events(m_wayland.display); | ||||
| 			prepared = false; | ||||
| 			prepared = false; // cppcheck-suppress unreadVariable | ||||
| 		} | ||||
| 	} else if (prepared) { | ||||
| 		wl_display_cancel_read(m_wayland.display); | ||||
| @@ -1184,3 +1263,66 @@ auto App::clipboard(std::string_view const &str) -> void | ||||
| 	    m_wayland.ddev, m_wayland.curr_source, m_last_serial); | ||||
| 	wl_display_flush(m_wayland.display); | ||||
| } | ||||
|  | ||||
| void App::execute_command(bool terminal, std::string_view const command) | ||||
| { | ||||
| 	constexpr auto resolve_cmdline { [](std::string_view const cmdline, | ||||
| 		                                 std::vector<std::string> &out) { | ||||
| 		std::ranges::copy(cmdline | std::views::split(' ') | ||||
| 		        | std::views::transform( | ||||
| 		            [](auto &&s) { return std::string(s.begin(), s.end()); }), | ||||
| 		    std::back_inserter(out)); | ||||
| 	} }; | ||||
|  | ||||
| 	std::vector<std::string> args; | ||||
| 	if (terminal) { | ||||
| 		resolve_cmdline(m_config.terminal_cmdline, args); | ||||
| 	} else { | ||||
| 		args.push_back("/bin/sh"); | ||||
| 		args.push_back("-c"); | ||||
| 	} | ||||
|  | ||||
| 	args.push_back(std::string(command)); | ||||
| 	if (auto const &exe { args.at(0) }; exe.at(0) != '/') { | ||||
| 		auto const *path_env { getenv("PATH") }; | ||||
| 		if (!(path_env && *path_env)) { | ||||
| 			path_env = "/bin"; | ||||
| 		} | ||||
| 		auto const path { std::string(path_env) }; | ||||
|  | ||||
| 		for (auto const &dir : | ||||
| 		    path | std::views::split(':') | std::views::transform([](auto &&s) { | ||||
| 			    return std::filesystem::path(s.begin(), s.end()); | ||||
| 		    }) | std::views::filter([](auto &&p) { | ||||
| 			    return std::filesystem::is_directory(p); | ||||
| 		    })) { | ||||
| 			auto const path = dir / exe; | ||||
| 			if (std::filesystem::is_regular_file(path)) { | ||||
| 				args[0] = path.string(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	std::print("Final args: "); | ||||
| 	for (auto const &arg : args) { | ||||
| 		std::print("{} ", arg); | ||||
| 	} | ||||
| 	std::println(""); | ||||
|  | ||||
| 	std::vector<char const *> cargs; | ||||
| 	std::transform(args.begin(), args.end(), std::back_inserter(cargs), | ||||
| 	    [](auto &&s) { return s.c_str(); }); | ||||
| 	cargs.push_back(nullptr); | ||||
| 	auto cargsc { const_cast<char *const *>(cargs.data()) }; | ||||
|  | ||||
| 	auto const pid = fork(); | ||||
| 	if (pid == 0) { | ||||
| 		setsid(); | ||||
|  | ||||
| 		execv(args.at(0).c_str(), cargsc); | ||||
| 	} else if (pid < 0) { | ||||
| 		throw std::runtime_error("Failed to fork process"); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| } // namespace Waylight | ||||
|   | ||||
							
								
								
									
										32
									
								
								src/App.hpp
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								src/App.hpp
									
									
									
									
									
								
							| @@ -18,16 +18,21 @@ extern "C" { | ||||
| #include <libportal/settings.h> | ||||
| #undef namespace | ||||
| } | ||||
| #include <SQLiteCpp/SQLiteCpp.h> | ||||
| #include <wayland-client.h> | ||||
| #include <wayland-egl.h> | ||||
| #include <xkbcommon/xkbcommon.h> | ||||
|  | ||||
| #include "Cache.hpp" | ||||
| #include "Config.hpp" | ||||
| #include "IconRegistry.hpp" | ||||
| #include "ImGui.hpp" | ||||
| #include "TextRenderer.hpp" | ||||
| #include "Theme.hpp" | ||||
| #include "common.hpp" | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| struct TypingBuffer : std::pmr::vector<u32> { | ||||
| 	void push_utf8(char const *s); | ||||
| }; | ||||
| @@ -126,13 +131,11 @@ private: | ||||
| 		{ | ||||
| 			if (!xkb_state_v) | ||||
| 				return false; | ||||
| 			for (auto k : held) { | ||||
| 				if (xkb_state_key_get_one_sym( | ||||
| 				        xkb_state_v, static_cast<xkb_keycode_t>(k + 8)) | ||||
| 				    == sym) | ||||
| 					return true; | ||||
| 			} | ||||
| 			return false; | ||||
| 			return std::any_of(held.begin(), held.end(), [&](u32 const k) { | ||||
| 				return (xkb_state_key_get_one_sym( | ||||
| 				            xkb_state_v, static_cast<xkb_keycode_t>(k + 8)) | ||||
| 				    == sym); | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		auto is_sym_pressed(xkb_keysym_t sym) const -> bool | ||||
| @@ -199,9 +202,6 @@ private: | ||||
| 		bool surrounding_dirty { false }; | ||||
| 	} m_ime; | ||||
|  | ||||
| 	// NOTE: Canonicalize first! | ||||
| 	std::unordered_map<std::filesystem::path, Texture2D> m_textures; | ||||
|  | ||||
| 	auto get_texture(std::filesystem::path const &path) -> Texture2D const & | ||||
| 	{ | ||||
| 		if (m_textures.contains(path)) { | ||||
| @@ -216,9 +216,16 @@ private: | ||||
| 		return m_textures[path]; | ||||
| 	} | ||||
|  | ||||
| 	void execute_command(bool terminal, std::string_view const command); | ||||
|  | ||||
| 	// NOTE: Canonicalize first! | ||||
| 	std::unordered_map<std::filesystem::path, Texture2D> m_textures; | ||||
|  | ||||
| 	enum_array<Theme, ColorScheme> m_themes { make_default_themes() }; | ||||
| 	Theme m_active_theme { Theme::Light }; | ||||
| 	IconRegistry m_ir; | ||||
| 	std::optional<Cache> m_cache; | ||||
| 	Config m_config; | ||||
|  | ||||
| 	int m_win_w { 800 }; | ||||
| 	int m_win_h { 600 }; | ||||
| @@ -227,5 +234,10 @@ private: | ||||
|  | ||||
| 	Color m_accent_color { 127, 127, 255, 255 }; | ||||
|  | ||||
| 	std::filesystem::path m_data_home_dir {}; | ||||
| 	std::filesystem::path m_config_home_dir {}; | ||||
| 	std::shared_ptr<SQLite::Database> m_db {}; | ||||
| 	int m_sfd { -1 }; | ||||
| }; | ||||
|  | ||||
| } // namespace Waylight | ||||
|   | ||||
							
								
								
									
										435
									
								
								src/Cache.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										435
									
								
								src/Cache.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,435 @@ | ||||
| #include "Cache.hpp" | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <numeric> | ||||
| #include <ranges> | ||||
| #include <unordered_map> | ||||
|  | ||||
| #include <SQLiteCpp/Statement.h> | ||||
| #include <SQLiteCpp/Transaction.h> | ||||
| #include <mini/ini.h> | ||||
| #include <raylib.h> | ||||
|  | ||||
| #include "common.hpp" | ||||
| #include <sys/inotify.h> | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| void replace_all( | ||||
|     std::string &str, std::string const &from, std::string const &to) | ||||
| { | ||||
| 	if (from.empty()) | ||||
| 		return; | ||||
| 	size_t pos = 0; | ||||
| 	while ((pos = str.find(from, pos)) != std::string::npos) { | ||||
| 		str.replace(pos, from.size(), to); | ||||
| 		pos += to.size(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| Cache::Cache(std::shared_ptr<SQLite::Database> db) | ||||
|     : m_db(db) | ||||
| { | ||||
| 	{ | ||||
| 		auto const *env { getenv("XDG_DATA_DIRS") }; | ||||
| 		if (env && *env) { | ||||
| 			std::ranges::copy(std::string_view(env) | std::views::split(':') | ||||
| 			        | std::views::transform([](auto &&s) { | ||||
| 				          return std::filesystem::path(s.begin(), s.end()) | ||||
| 				              / "applications"; | ||||
| 			          }) | ||||
| 			        | std::views::filter([](auto &&p) { | ||||
| 				          if (!std::filesystem::is_directory(p)) | ||||
| 					          return false; | ||||
| 				          if (std::filesystem::directory_iterator(p) | ||||
| 				              == std::filesystem::directory_iterator {}) | ||||
| 					          return false; | ||||
| 				          return true; | ||||
| 			          }), | ||||
| 			    std::back_inserter(m_app_dirs)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	load(); | ||||
|  | ||||
| 	auto total = std::accumulate(m_app_dirs.begin(), m_app_dirs.end(), | ||||
| 	    static_cast<usize>(0), [](usize acc, auto &&dir) { | ||||
| 		    return acc | ||||
| 		        + std::count_if(std::filesystem::directory_iterator(dir), | ||||
| 		            std::filesystem::directory_iterator {}, [](auto &&entry) { | ||||
| 			            return entry.is_regular_file() | ||||
| 			                && entry.path().extension() == ".desktop"; | ||||
| 		            }); | ||||
| 	    }); | ||||
| 	if (total != m_apps.size()) { | ||||
| 		rescan(); | ||||
| 	} | ||||
|  | ||||
| 	TraceLog(LOG_DEBUG, std::format("Applications in cache:").c_str()); | ||||
| 	for (auto const &app : m_apps) { | ||||
| 		TraceLog(LOG_DEBUG, | ||||
| 		    std::format("{}:", app.desktop_entry_path.string()).c_str()); | ||||
| 		if (app.comment) | ||||
| 			TraceLog( | ||||
| 			    LOG_DEBUG, std::format(" - Comment: {}", *app.comment).c_str()); | ||||
| 		if (app.path) | ||||
| 			TraceLog(LOG_DEBUG, std::format(" - Path: {}", *app.path).c_str()); | ||||
| 		TraceLog( | ||||
| 		    LOG_DEBUG, std::format(" - Terminal: {}", app.terminal).c_str()); | ||||
| 		TraceLog( | ||||
| 		    LOG_DEBUG, std::format(" - NoDisplay: {}", app.no_display).c_str()); | ||||
| 		TraceLog(LOG_DEBUG, std::format(" - Actions:").c_str()); | ||||
| 		for (auto const &action : app.actions) { | ||||
| 			TraceLog( | ||||
| 			    LOG_DEBUG, std::format("   - Name: {}", action.name).c_str()); | ||||
| 			if (action.exec) | ||||
| 				TraceLog(LOG_DEBUG, | ||||
| 				    std::format("     Exec: {}", *action.exec).c_str()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for (auto const &dir : m_app_dirs) { | ||||
| 		m_inotify.watch_path_recursively(dir); | ||||
| 	} | ||||
|  | ||||
| 	m_inotify.set_callback([this](FileWatchEvent const &event) { | ||||
| 		auto const mask = event.mask; | ||||
| 		if (mask & IN_Q_OVERFLOW) { | ||||
| 			rescan(); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		if (mask & IN_ISDIR) { | ||||
| 			rescan(); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		if (mask & (IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_FROM | ||||
| 		        | IN_MOVED_TO | IN_CLOSE_WRITE)) { | ||||
| 			if (event.path.extension() == ".desktop") { | ||||
| 				rescan(); | ||||
| 			} | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	m_inotify_thread = std::thread([this]() { m_inotify.run(); }); | ||||
| } | ||||
|  | ||||
| Cache::~Cache() | ||||
| { | ||||
| 	m_inotify.stop(); | ||||
| 	if (m_inotify_thread.joinable()) | ||||
| 		m_inotify_thread.join(); | ||||
| } | ||||
|  | ||||
| void Cache::rescan() | ||||
| { | ||||
| 	m_apps.clear(); | ||||
|  | ||||
| 	int id = 0; | ||||
| 	for (auto const &dir : m_app_dirs) { | ||||
| 		for (auto const &file : std::filesystem::directory_iterator(dir)) { | ||||
| 			if (!file.is_regular_file()) | ||||
| 				continue; | ||||
|  | ||||
| 			if (file.path().extension() != ".desktop") | ||||
| 				continue; | ||||
|  | ||||
| 			mINI::INIFile ini_file(file.path()); | ||||
| 			mINI::INIStructure ini; | ||||
| 			ini_file.read(ini); | ||||
|  | ||||
| 			constexpr auto read_action = [&](std::string const | ||||
| 			                                     &desktop_file_uri, | ||||
| 			                                 mINI::INIMap<std::string> const | ||||
| 			                                     §ion) { | ||||
| 				auto const name = section.get("Name"); | ||||
| 				auto const icon = [&]() | ||||
| 				    -> std::optional< | ||||
| 				        std::variant<std::filesystem::path, std::string>> { | ||||
| 					if (section.has("Icon")) { | ||||
| 						auto const icon_name = section.get("Icon"); | ||||
| 						if (!icon_name.empty()) { | ||||
| 							if (icon_name[0] == '/') { | ||||
| 								return std::filesystem::path(icon_name); | ||||
| 							} else { | ||||
| 								return icon_name; | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 					return std::nullopt; | ||||
| 				}(); | ||||
|  | ||||
| 				return ApplicationCache::Action { | ||||
| 					.name = name, | ||||
| 					.exec = [&]() -> std::optional<std::string> { | ||||
| 					    if (section.has("Exec")) { | ||||
| 						    auto s = section.get("Exec"); | ||||
| 						    if (!s.empty()) { | ||||
| 							    // Either deprecated or not used... | ||||
| 							    replace_all(s, "%f", ""); | ||||
| 							    replace_all(s, "%F", ""); | ||||
| 							    replace_all(s, "%u", ""); | ||||
| 							    replace_all(s, "%U", ""); | ||||
| 							    replace_all(s, "%d", ""); | ||||
| 							    replace_all(s, "%D", ""); | ||||
| 							    replace_all(s, "%n", ""); | ||||
| 							    replace_all(s, "%N", ""); | ||||
| 							    replace_all(s, "%v", ""); | ||||
| 							    replace_all(s, "%M", ""); | ||||
|  | ||||
| 							    replace_all(s, "%c", name); | ||||
| 							    if (icon) { | ||||
| 								    if (auto const p | ||||
| 								        = std::get_if<std::filesystem::path>( | ||||
| 								            &*icon)) { | ||||
| 									    replace_all(s, "%i", | ||||
| 									        "--icon '" + p->string() + "'"); | ||||
| 								    } else if (auto const n | ||||
| 								        = std::get_if<std::string>(&*icon)) { | ||||
| 									    replace_all( | ||||
| 									        s, "%i", "--icon '" + *n + "'"); | ||||
| 								    } else { | ||||
| 									    replace_all(s, "%i", ""); | ||||
| 								    } | ||||
| 							    } else { | ||||
| 								    replace_all(s, "%i", ""); | ||||
| 							    } | ||||
| 							    replace_all(s, "%k", "'" + desktop_file_uri); | ||||
| 							    return s; | ||||
| 						    } | ||||
| 					    } | ||||
| 					    return std::nullopt; | ||||
| 					}(), | ||||
| 					.icon = icon, | ||||
| 				}; | ||||
| 			}; | ||||
|  | ||||
| 			m_apps.push_back({ | ||||
| 			    .id = id++, | ||||
| 			    .desktop_entry_path = file.path(), | ||||
| 			    .type = | ||||
| 			        [&]() { | ||||
| 				        auto const type_str { ini["Desktop Entry"].get( | ||||
| 					        "Type") }; | ||||
| 				        auto type { ApplicationCache::Type::Application }; | ||||
| 				        if (type_str == "Application") | ||||
| 					        type = ApplicationCache::Type::Application; | ||||
| 				        else if (type_str == "Link") | ||||
| 					        type = ApplicationCache::Type::Link; | ||||
| 				        else if (type_str == "Directory") | ||||
| 					        type = ApplicationCache::Type::Directory; | ||||
| 				        return type; | ||||
| 			        }(), | ||||
| 			    .terminal = | ||||
| 			        [&]() { | ||||
| 				        if (ini["Desktop Entry"].has("Terminal")) { | ||||
| 					        return ini["Desktop Entry"]["Terminal"] == "true" | ||||
| 					            ? true | ||||
| 					            : false; | ||||
| 				        } | ||||
| 				        return false; | ||||
| 			        }(), | ||||
| 			    .no_display = | ||||
| 			        [&]() { | ||||
| 				        if (ini["Desktop Entry"].has("NoDisplay")) { | ||||
| 					        return ini["Desktop Entry"]["NoDisplay"] == "true" | ||||
| 					            ? true | ||||
| 					            : false; | ||||
| 				        } | ||||
| 				        return false; | ||||
| 			        }(), | ||||
| 			    .path = [&]() -> std::optional<std::string> { | ||||
| 				    if (ini["Desktop Entry"].has("Path")) { | ||||
| 					    return ini["Desktop Entry"]["Path"]; | ||||
| 				    } | ||||
| 				    return std::nullopt; | ||||
| 			    }(), | ||||
| 			    .comment = [&]() -> std::optional<std::string> { | ||||
| 				    if (ini["Desktop Entry"].has("Comment")) { | ||||
| 					    return ini["Desktop Entry"]["Comment"]; | ||||
| 				    } | ||||
| 				    return std::nullopt; | ||||
| 			    }(), | ||||
| 			    .actions = | ||||
| 			        [&]() { | ||||
| 				        std::vector<ApplicationCache::Action> actions; | ||||
| 				        for (auto const &[_, v] : ini) { | ||||
| 					        try { | ||||
| 						        auto const action = read_action( | ||||
| 						            std::filesystem::canonical(file.path()) | ||||
| 						                .string(), | ||||
| 						            v); | ||||
| 						        actions.push_back(action); | ||||
| 					        } catch (...) { | ||||
| 					        } | ||||
| 				        } | ||||
| 				        return actions; | ||||
| 			        }(), | ||||
| 			    .dbus_activatable = | ||||
| 			        [&]() { | ||||
| 				        if (ini["Desktop Entry"].has("DBusActivatable")) { | ||||
| 					        return ini["Desktop Entry"]["DBusActivatable"] | ||||
| 					                == "true" | ||||
| 					            ? true | ||||
| 					            : false; | ||||
| 				        } | ||||
| 				        return false; | ||||
| 			        }(), | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	dump(); | ||||
| } | ||||
|  | ||||
| void Cache::dump() | ||||
| { | ||||
| 	SQLite::Transaction tx(*m_db); | ||||
|  | ||||
| 	SQLite::Statement(*m_db, "DELETE FROM ApplicationCache").exec(); | ||||
| 	SQLite::Statement(*m_db, "DELETE FROM ApplicationActionCache").exec(); | ||||
|  | ||||
| 	try { | ||||
| 		SQLite::Statement( | ||||
| 		    *m_db, "DELETE FROM sqlite_sequence WHERE name='ApplicationCache'") | ||||
| 		    .exec(); | ||||
| 		SQLite::Statement(*m_db, | ||||
| 		    "DELETE FROM sqlite_sequence WHERE name='ApplicationActionCache'") | ||||
| 		    .exec(); | ||||
| 	} catch (std::exception const &) { | ||||
| 	} | ||||
|  | ||||
| 	SQLite::Statement ins_app(*m_db, | ||||
| 	    "INSERT INTO ApplicationCache(type, desktop_entry_path, terminal, " | ||||
| 	    "no_display, path, comment, dbus_activatable) VALUES (?,?,?,?,?,?,?)"); | ||||
|  | ||||
| 	SQLite::Statement ins_act(*m_db, | ||||
| 	    "INSERT INTO ApplicationActionCache(id_app, name, exec, icon) VALUES " | ||||
| 	    "(?,?,?,?)"); | ||||
|  | ||||
| 	for (auto &app : m_apps) { | ||||
| 		ins_app.reset(); | ||||
| 		ins_app.clearBindings(); | ||||
| 		ins_app.bind(1, static_cast<int>(app.type)); | ||||
| 		ins_app.bind(2, app.desktop_entry_path.string()); | ||||
| 		ins_app.bind(3, app.terminal ? 1 : 0); | ||||
| 		ins_app.bind(4, app.no_display ? 1 : 0); | ||||
| 		if (app.path) | ||||
| 			ins_app.bind(5, *app.path); | ||||
| 		else | ||||
| 			ins_app.bind(5); | ||||
| 		if (app.comment) | ||||
| 			ins_app.bind(6, *app.comment); | ||||
| 		else | ||||
| 			ins_app.bind(6); | ||||
| 		ins_app.bind(7, app.dbus_activatable ? 1 : 0); | ||||
| 		ins_app.exec(); | ||||
|  | ||||
| 		app.id = m_db->getLastInsertRowid(); | ||||
|  | ||||
| 		for (auto const &action : app.actions) { | ||||
| 			ins_act.reset(); | ||||
| 			ins_act.clearBindings(); | ||||
| 			ins_act.bind(1, app.id); | ||||
| 			ins_act.bind(2, action.name); | ||||
| 			if (action.exec) | ||||
| 				ins_act.bind(3, *action.exec); | ||||
| 			else | ||||
| 				ins_act.bind(3); | ||||
|  | ||||
| 			if (action.icon) { | ||||
| 				std::string str; | ||||
| 				if (auto const *s = std::get_if<std::string>(&*action.icon)) { | ||||
| 					str = *s; | ||||
| 				} else if (auto const *p | ||||
| 				    = std::get_if<std::filesystem::path>(&*action.icon)) { | ||||
| 					str = std::filesystem::canonical(*p).string(); | ||||
| 				} | ||||
| 				ins_act.bind(4, str); | ||||
| 			} else { | ||||
| 				ins_act.bind(4); | ||||
| 			} | ||||
| 			ins_act.exec(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	tx.commit(); | ||||
| } | ||||
|  | ||||
| void Cache::load() | ||||
| { | ||||
| 	m_apps.clear(); | ||||
|  | ||||
| 	SQLite::Statement get_apps(*m_db, | ||||
| 	    "SELECT id, type, desktop_entry_path, terminal, no_display, path, " | ||||
| 	    "comment, dbus_activatable " | ||||
| 	    "FROM ApplicationCache"); | ||||
|  | ||||
| 	std::unordered_map<std::int64_t, std::size_t> id_to_index; | ||||
|  | ||||
| 	while (get_apps.executeStep()) { | ||||
| 		ApplicationCache app {}; | ||||
| 		app.id = get_apps.getColumn(0).getInt64(); | ||||
| 		app.type = static_cast<ApplicationCache::Type>( | ||||
| 		    get_apps.getColumn(1).getInt()); | ||||
| 		app.desktop_entry_path | ||||
| 		    = std::filesystem::path(get_apps.getColumn(2).getString()); | ||||
| 		app.terminal = get_apps.getColumn(3).getInt() != 0; | ||||
| 		app.no_display = get_apps.getColumn(4).getInt() != 0; | ||||
|  | ||||
| 		if (!get_apps.getColumn(5).isNull()) | ||||
| 			app.path = std::string(get_apps.getColumn(5).getString()); | ||||
| 		else | ||||
| 			app.path.reset(); | ||||
|  | ||||
| 		if (!get_apps.getColumn(6).isNull()) | ||||
| 			app.comment = std::string(get_apps.getColumn(6).getString()); | ||||
| 		else | ||||
| 			app.comment.reset(); | ||||
|  | ||||
| 		app.dbus_activatable = get_apps.getColumn(7).getInt() != 0; | ||||
|  | ||||
| 		id_to_index.emplace(app.id, m_apps.size()); | ||||
| 		m_apps.push_back(std::move(app)); | ||||
| 	} | ||||
|  | ||||
| 	if (m_apps.empty()) | ||||
| 		return; | ||||
|  | ||||
| 	SQLite::Statement get_actions(*m_db, | ||||
| 	    "SELECT id_app, name, exec, icon " | ||||
| 	    "FROM ApplicationActionCache " | ||||
| 	    "ORDER BY id_app"); | ||||
|  | ||||
| 	while (get_actions.executeStep()) { | ||||
| 		auto id_app = get_actions.getColumn(0).getInt64(); | ||||
| 		auto it = id_to_index.find(id_app); | ||||
| 		if (it == id_to_index.end()) | ||||
| 			continue; | ||||
|  | ||||
| 		ApplicationCache::Action action {}; | ||||
| 		action.name = std::string(get_actions.getColumn(1).getString()); | ||||
|  | ||||
| 		if (!get_actions.getColumn(2).isNull()) | ||||
| 			action.exec = std::string(get_actions.getColumn(2).getString()); | ||||
| 		else | ||||
| 			action.exec.reset(); | ||||
|  | ||||
| 		if (!get_actions.getColumn(3).isNull()) { | ||||
| 			auto const str = get_actions.getColumn(3).getString(); | ||||
| 			if (str.at(0) == '/') { | ||||
| 				action.icon | ||||
| 				    = std::filesystem::canonical(std::filesystem::path(str)); | ||||
| 			} else { | ||||
| 				action.icon = str; | ||||
| 			} | ||||
| 		} else { | ||||
| 			action.icon.reset(); | ||||
| 		} | ||||
|  | ||||
| 		m_apps[it->second].actions.push_back(std::move(action)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| } // namespace Waylight | ||||
							
								
								
									
										66
									
								
								src/Cache.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/Cache.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <filesystem> | ||||
| #include <optional> | ||||
| #include <string> | ||||
| #include <thread> | ||||
| #include <variant> | ||||
| #include <vector> | ||||
|  | ||||
| #include "InotifyWatcher.hpp" | ||||
| #include <SQLiteCpp/Database.h> | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| struct ApplicationCache { | ||||
| 	enum class Type { | ||||
| 		Application, | ||||
| 		Link, | ||||
| 		Directory, | ||||
| 	}; | ||||
|  | ||||
| 	struct Action { | ||||
| 		std::string name; | ||||
| 		// May not exist if DBusActivable=true | ||||
| 		std::optional<std::string> exec; | ||||
| 		// Freedesktop Desktop Entry Spec 11.2 Table 3 says: | ||||
| 		// | ||||
| 		//   If the name is an absolute path, the given file will be used. | ||||
| 		//   If the name is not an absolute path, the algorithm described in | ||||
| 		//   the Icon Theme Specification will be used to locate the icon. | ||||
| 		// | ||||
| 		// Thus, when deserializing, we will just check if it starts with / | ||||
| 		// to determine type. | ||||
| 		std::optional<std::variant<std::filesystem::path, std::string>> icon; | ||||
| 	}; | ||||
|  | ||||
| 	int id; | ||||
| 	std::filesystem::path desktop_entry_path; | ||||
|  | ||||
| 	Type type { Type::Application }; | ||||
| 	bool terminal { false }; | ||||
| 	bool no_display { false }; | ||||
| 	std::optional<std::string> path; | ||||
| 	std::optional<std::string> comment; | ||||
| 	std::vector<Action> actions; // There should always be at least 1. | ||||
| 	bool dbus_activatable {};    // Unimplemented for now. | ||||
| }; | ||||
|  | ||||
| struct Cache { | ||||
| 	explicit Cache(std::shared_ptr<SQLite::Database> db); | ||||
| 	~Cache(); | ||||
|  | ||||
| 	void rescan(); | ||||
| 	void dump(); | ||||
| 	void load(); | ||||
|  | ||||
| private: | ||||
| 	std::vector<ApplicationCache> m_apps; | ||||
| 	std::vector<std::filesystem::path> m_app_dirs; | ||||
| 	std::shared_ptr<SQLite::Database> m_db; | ||||
|  | ||||
| 	InotifyWatcher m_inotify; | ||||
| 	std::thread m_inotify_thread; | ||||
| }; | ||||
|  | ||||
| } // namespace Waylight | ||||
							
								
								
									
										47
									
								
								src/Config.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/Config.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| #include "Config.hpp" | ||||
|  | ||||
| #include <filesystem> | ||||
| #include <fstream> | ||||
| #include <print> | ||||
|  | ||||
| #include <toml++/toml.hpp> | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| void Config::write(std::filesystem::path const &path) | ||||
| { | ||||
| 	std::ofstream f(path); | ||||
| 	if (!f) { | ||||
| 		throw std::runtime_error("Failed to open config file for writing"); | ||||
| 	} | ||||
| 	std::println(f, "[settings]"); | ||||
| 	std::println(f, "terminal_cmdline=\"{}\"", terminal_cmdline); | ||||
| } | ||||
|  | ||||
| auto Config::load(std::filesystem::path const &config_dir_path) -> Config const | ||||
| { | ||||
| 	if (!std::filesystem::is_directory(config_dir_path)) | ||||
| 		throw std::runtime_error("Provided path is not a directory!"); | ||||
|  | ||||
| 	Config cfg {}; | ||||
|  | ||||
| 	std::filesystem::path path_config { config_dir_path / "config.toml" }; | ||||
| 	if (!std::filesystem::is_regular_file(path_config)) { | ||||
| 		try { | ||||
| 			std::filesystem::remove_all(path_config); | ||||
| 		} catch (std::exception const &e) { | ||||
| 		} | ||||
| 		cfg.write(path_config); | ||||
| 	} | ||||
|  | ||||
| 	std::println("Config file: {}", path_config.string()); | ||||
| 	auto const tbl { toml::parse_file(path_config.string()) }; | ||||
| 	auto const terminal_cmdline { tbl["settings"]["terminal_cmdline"].value_or( | ||||
| 		"kitty -c") }; | ||||
|  | ||||
| 	cfg.terminal_cmdline = terminal_cmdline; | ||||
|  | ||||
| 	return cfg; | ||||
| } | ||||
|  | ||||
| } // namespace Waylight | ||||
							
								
								
									
										17
									
								
								src/Config.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/Config.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <filesystem> | ||||
| #include <string> | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| struct Config { | ||||
| 	std::string terminal_cmdline { "kitty" }; | ||||
|  | ||||
| 	void write(std::filesystem::path const &path); | ||||
|  | ||||
| 	static auto load(std::filesystem::path const &config_dir_path) | ||||
| 	    -> Config const; | ||||
| }; | ||||
|  | ||||
| } // namespace Waylight | ||||
| @@ -13,6 +13,8 @@ | ||||
| #include <lunasvg.h> | ||||
| #include <mini/ini.h> | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| static inline auto color_to_string(Color const &c) -> std::string | ||||
| { | ||||
| 	auto const r { c.r / 255.0 }, g { c.g / 255.0 }, b { c.b / 255.0 }; | ||||
| @@ -70,7 +72,7 @@ static auto kde_get_theme() -> std::string const | ||||
| 		home + "/.config/kdedefaults/kdeglobals", | ||||
| 	}; | ||||
|  | ||||
| 	for (auto p : paths) { | ||||
| 	for (auto const &p : paths) { | ||||
| 		std::ifstream f(p); | ||||
| 		if (!f) | ||||
| 			continue; | ||||
| @@ -326,9 +328,8 @@ IconTheme::IconTheme(std::filesystem::path const &themes_directory_path) | ||||
| } | ||||
|  | ||||
| IconRegistry::IconRegistry() | ||||
|     : m_preferred_theme(get_current_icon_theme()) | ||||
| { | ||||
| 	m_preferred_theme = get_current_icon_theme(); | ||||
|  | ||||
| 	std::vector<std::filesystem::path> theme_directory_paths; | ||||
|  | ||||
| 	{ | ||||
| @@ -346,21 +347,27 @@ IconRegistry::IconRegistry() | ||||
| 			        | std::views::transform([](auto &&s) { | ||||
| 				          return std::filesystem::path(s.begin(), s.end()) | ||||
| 				              / "icons"; | ||||
| 			          }) | ||||
| 			        | std::views::filter([](auto &&p) { | ||||
| 				          if (!std::filesystem::is_directory(p)) | ||||
| 					          return false; | ||||
| 				          if (std::filesystem::directory_iterator(p) | ||||
| 				              == std::filesystem::directory_iterator {}) | ||||
| 					          return false; | ||||
| 				          return true; | ||||
| 			          }), | ||||
| 			    std::back_inserter(theme_directory_paths)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	{ | ||||
| 		std::filesystem::path const paths[] { | ||||
| 		std::array<std::filesystem::path, 3> const paths { | ||||
| 			"/usr/share/pixmaps", | ||||
| 			"/usr/local/share/icons", | ||||
| 			"/usr/share/icons", | ||||
| 		}; | ||||
| 		for (auto const &path : paths) { | ||||
| 			if (std::filesystem::exists(path)) | ||||
| 				theme_directory_paths.push_back(path); | ||||
| 		} | ||||
| 		std::copy_if(paths.begin(), paths.end(), theme_directory_paths.begin(), | ||||
| 		    [](auto const path) { return std::filesystem::exists(path); }); | ||||
| 	} | ||||
|  | ||||
| 	for (auto &&path : theme_directory_paths | ||||
| @@ -368,7 +375,7 @@ IconRegistry::IconRegistry() | ||||
| 		          return std::filesystem::is_directory(path); | ||||
| 	          })) { | ||||
| 		try { | ||||
| 			m_themes.push_back({ path }); | ||||
| 			m_themes.push_back(IconTheme(path)); | ||||
| 		} catch (...) { | ||||
| 		} | ||||
| 	} | ||||
| @@ -383,14 +390,8 @@ IconRegistry::IconRegistry() | ||||
|  | ||||
| 		std::stable_partition( | ||||
| 		    m_themes.begin(), m_themes.end(), [&](auto const &t) { | ||||
| 			    bool found { false }; | ||||
| 			    for (auto const &e : t.names()) { | ||||
| 				    if (e == *m_preferred_theme) { | ||||
| 					    found = true; | ||||
| 					    break; | ||||
| 				    } | ||||
| 			    } | ||||
| 			    return found; | ||||
| 			    return std::any_of(t.names().begin(), t.names().end(), | ||||
| 			        [&](auto const &e) { return e == *m_preferred_theme; }); | ||||
| 		    }); | ||||
| 	} | ||||
| } | ||||
| @@ -444,3 +445,5 @@ auto IconRegistry::lookup_cached(std::string_view const name, | ||||
|  | ||||
| 	throw std::runtime_error(std::format("Failed to find icon `{}`!", name)); | ||||
| } | ||||
|  | ||||
| } // namespace Waylight | ||||
|   | ||||
| @@ -8,6 +8,8 @@ | ||||
|  | ||||
| #include <raylib.h> | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| struct Icon { | ||||
| 	Icon(std::filesystem::path path, Texture2D texture, int size) | ||||
| 	    : m_path(path) | ||||
| @@ -30,7 +32,7 @@ private: | ||||
| }; | ||||
|  | ||||
| struct IconTheme { | ||||
| 	IconTheme(std::filesystem::path const &themes_directory_path); | ||||
| 	explicit IconTheme(std::filesystem::path const &themes_directory_path); | ||||
| 	~IconTheme() = default; | ||||
|  | ||||
| 	constexpr auto inherits() const -> std::span<std::string const> | ||||
| @@ -87,3 +89,5 @@ private: | ||||
| 	std::unordered_map<std::string, Icon> m_cached_icons; | ||||
| 	std::optional<std::string> m_preferred_theme; | ||||
| }; | ||||
|  | ||||
| } // namespace Waylight | ||||
|   | ||||
| @@ -11,6 +11,8 @@ | ||||
|  | ||||
| #include <raylib.h> | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| struct CodepointSpan { | ||||
| @@ -121,18 +123,6 @@ auto clamp_preedit_index(int value, usize text_size) -> usize | ||||
| 	return std::min(as_size, text_size); | ||||
| } | ||||
|  | ||||
| auto slice_bytes(std::string_view text, usize begin, usize end) | ||||
|     -> std::string_view | ||||
| { | ||||
| 	if (begin > text.size()) | ||||
| 		begin = text.size(); | ||||
| 	if (end > text.size()) | ||||
| 		end = text.size(); | ||||
| 	if (end < begin) | ||||
| 		end = begin; | ||||
| 	return std::string_view(text.data() + begin, end - begin); | ||||
| } | ||||
|  | ||||
| constexpr float HORIZONTAL_PADDING = 6.0f; | ||||
| constexpr float VERTICAL_PADDING = 4.0f; | ||||
| constexpr double CARET_BLINK_INTERVAL = 0.5; | ||||
| @@ -228,9 +218,9 @@ void ImGui::ime_delete_surrounding( | ||||
| 	if (it == m_ti_states.end()) | ||||
| 		return; | ||||
| 	auto &state { it->second }; | ||||
| 	usize caret_byte = std::min(state.caret_byte, str.size()); | ||||
| 	usize start = before > caret_byte ? 0 : caret_byte - before; | ||||
| 	usize end = std::min(caret_byte + after, str.size()); | ||||
| 	usize caret_byte { std::min(state.caret_byte, str.size()) }; | ||||
| 	usize start { before > caret_byte ? 0 : caret_byte - before }; | ||||
| 	usize end { std::min(caret_byte + after, str.size()) }; | ||||
| 	if (end > start) { | ||||
| 		str.erase(start, end - start); | ||||
| 		state.caret_byte = start; | ||||
| @@ -289,10 +279,8 @@ void ImGui::ime_clear_preedit() | ||||
|  | ||||
| size_t utf8_length(std::string_view const &s) | ||||
| { | ||||
| 	size_t count = 0; | ||||
| 	for (unsigned char c : s) | ||||
| 		if ((c & 0xC0) != 0x80) | ||||
| 			++count; | ||||
| 	size_t count = std::count_if( | ||||
| 	    s.begin(), s.end(), [](auto const &c) { return (c & 0xC0) != 0x80; }); | ||||
| 	return count; | ||||
| } | ||||
|  | ||||
| @@ -554,9 +542,9 @@ auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec, | ||||
| 				if (!options.multiline) { | ||||
| 					std::string clip2; | ||||
| 					clip2.reserve(m_clipboard.size()); | ||||
| 					for (auto ch : m_clipboard) | ||||
| 						if (ch != '\n' && ch != '\r') | ||||
| 							clip2.push_back(ch); | ||||
| 					std::copy_if(m_clipboard.begin(), m_clipboard.end(), | ||||
| 					    clip2.begin(), | ||||
| 					    [](char ch) { return ch != '\n' && ch != '\r'; }); | ||||
| 					str.insert(caret_byte, clip2); | ||||
| 					state.current_rune_idx += (int)utf8_length(clip2); | ||||
| 				} else { | ||||
| @@ -763,8 +751,8 @@ auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec, | ||||
|  | ||||
| 			if (m_focused_id == id && state.caret_visible) { | ||||
| 				float const caret_x = std::floor(origin + caret_offset + 0.5f); | ||||
| 				Vector2 p0 { caret_x, std::floor(caret_top + 0.5f) }; | ||||
| 				Vector2 p1 { caret_x, | ||||
| 				Vector2 const p0 { caret_x, std::floor(caret_top + 0.5f) }; | ||||
| 				Vector2 const p1 { caret_x, | ||||
| 					std::floor((caret_top + caret_height) + 0.5f) }; | ||||
| 				DrawLineV(p0, p1, text_color); | ||||
| 			} | ||||
| @@ -784,15 +772,12 @@ auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec, | ||||
| auto ImGui::list_view(usize id, Rectangle bounds, usize elements, | ||||
|     std::function<Vector2(usize i)> draw_cb, ListViewOptions options) -> bool | ||||
| { | ||||
| 	auto &state { m_lv_states[id] }; | ||||
| 	auto const &state { m_lv_states[id] }; | ||||
|  | ||||
| 	bool submitted { false }; | ||||
|  | ||||
| 	bool select_next = m_next_lv_next; | ||||
| 	m_next_lv_next = false; | ||||
| 	bool select_previous = m_next_lv_previous; | ||||
| 	m_next_lv_previous = false; | ||||
| 	bool select_clear = m_next_lv_clear; | ||||
| 	m_next_lv_clear = false; | ||||
|  | ||||
| 	BeginScissorMode(bounds.x, bounds.y, bounds.width, bounds.height); | ||||
| @@ -803,3 +788,5 @@ auto ImGui::list_view(usize id, Rectangle bounds, usize elements, | ||||
|  | ||||
| 	return submitted; | ||||
| } | ||||
|  | ||||
| } // namespace Waylight | ||||
|   | ||||
| @@ -12,6 +12,8 @@ | ||||
|  | ||||
| #include "TextRenderer.hpp" | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| constexpr float DEFAULT_FONT_SIZE { 24 }; | ||||
|  | ||||
| struct TextInputOptions { | ||||
| @@ -31,7 +33,7 @@ struct ImGui { | ||||
| 		Color selection_text_color { WHITE }; | ||||
| 	}; | ||||
|  | ||||
| 	ImGui(std::shared_ptr<TextRenderer> text_renderer); | ||||
| 	explicit ImGui(std::shared_ptr<TextRenderer> text_renderer); | ||||
|  | ||||
| 	ImGui(ImGui const &) = delete; | ||||
| 	auto operator=(ImGui const &) -> ImGui & = delete; | ||||
| @@ -158,3 +160,5 @@ struct ImGuiGuard { | ||||
| private: | ||||
| 	std::shared_ptr<ImGui> m_imgui { nullptr }; | ||||
| }; | ||||
|  | ||||
| } // namespace Waylight | ||||
|   | ||||
							
								
								
									
										241
									
								
								src/InotifyWatcher.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								src/InotifyWatcher.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,241 @@ | ||||
| #include "InotifyWatcher.hpp" | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include <cerrno> | ||||
| #include <cstring> | ||||
| #include <poll.h> | ||||
| #include <stdexcept> | ||||
| #include <system_error> | ||||
| #include <unistd.h> | ||||
| #include <utility> | ||||
|  | ||||
| #include <sys/inotify.h> | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| namespace { | ||||
| constexpr std::uint32_t watch_mask { IN_ATTRIB | IN_CREATE | IN_DELETE | ||||
| 	| IN_DELETE_SELF | IN_MODIFY | IN_MOVE_SELF | IN_MOVED_FROM | IN_MOVED_TO | ||||
| 	| IN_CLOSE_WRITE }; | ||||
| constexpr int poll_timeout_ms { 250 }; | ||||
| } // namespace | ||||
|  | ||||
| InotifyWatcher::InotifyWatcher() | ||||
|     : m_fd(inotify_init1(IN_CLOEXEC)) | ||||
| { | ||||
| 	if (m_fd < 0) { | ||||
| 		throw std::system_error( | ||||
| 		    errno, std::generic_category(), "inotify_init1 failed"); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| InotifyWatcher::~InotifyWatcher() | ||||
| { | ||||
| 	stop(); | ||||
|  | ||||
| 	if (m_fd >= 0) { | ||||
| 		::close(m_fd); | ||||
| 		m_fd = -1; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void InotifyWatcher::watch_path_recursively(std::filesystem::path const &path) | ||||
| { | ||||
| 	add_watch_recursively(path); | ||||
| } | ||||
|  | ||||
| void InotifyWatcher::set_callback(callback_t cb) | ||||
| { | ||||
| 	std::scoped_lock lock(m_watch_mutex); | ||||
| 	m_callback = std::move(cb); | ||||
| } | ||||
|  | ||||
| void InotifyWatcher::run() | ||||
| { | ||||
| 	if (m_running.exchange(true)) { | ||||
| 		throw std::runtime_error("InotifyWatcher::run called while running"); | ||||
| 	} | ||||
|  | ||||
| 	std::array<char, 4096> buffer {}; | ||||
| 	while (m_running.load()) { | ||||
| 		pollfd fd_set { | ||||
| 			.fd = m_fd, | ||||
| 			.events = POLLIN, | ||||
| 			.revents = 0, | ||||
| 		}; | ||||
|  | ||||
| 		int const poll_res { ::poll(&fd_set, 1, poll_timeout_ms) }; | ||||
| 		if (!m_running.load()) | ||||
| 			break; | ||||
|  | ||||
| 		if (poll_res < 0) { | ||||
| 			if (errno == EINTR) | ||||
| 				continue; | ||||
| 			if (errno == EBADF || errno == EINVAL) | ||||
| 				break; | ||||
| 			throw std::system_error(errno, std::generic_category(), "poll"); | ||||
| 		} | ||||
|  | ||||
| 		if (poll_res == 0) | ||||
| 			continue; | ||||
|  | ||||
| 		if (fd_set.revents & (POLLERR | POLLHUP | POLLNVAL)) | ||||
| 			break; | ||||
|  | ||||
| 		if (!(fd_set.revents & POLLIN)) | ||||
| 			continue; | ||||
|  | ||||
| 		ssize_t const bytes_read { ::read(m_fd, buffer.data(), buffer.size()) }; | ||||
| 		if (bytes_read < 0) { | ||||
| 			if (errno == EINTR || errno == EAGAIN) | ||||
| 				continue; | ||||
| 			if (errno == EBADF || errno == EINVAL) | ||||
| 				break; | ||||
| 			throw std::system_error(errno, std::generic_category(), "read"); | ||||
| 		} | ||||
|  | ||||
| 		ssize_t offset { 0 }; | ||||
| 		while (offset + static_cast<ssize_t>(sizeof(inotify_event)) | ||||
| 		    <= bytes_read) { | ||||
| 			auto const *event = reinterpret_cast<inotify_event const *>( | ||||
| 			    buffer.data() + offset); | ||||
| 			std::string_view name; | ||||
| 			if (event->len > 0) { | ||||
| 				auto const *raw_name | ||||
| 				    = buffer.data() + offset + sizeof(inotify_event); | ||||
| 				auto const *end | ||||
| 				    = std::find(raw_name, raw_name + event->len, '\0'); | ||||
| 				name = std::string_view(raw_name, end - raw_name); | ||||
| 			} | ||||
|  | ||||
| 			dispatch_event(event->wd, event->mask, name); | ||||
|  | ||||
| 			offset += sizeof(inotify_event) + event->len; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	m_running.store(false); | ||||
| } | ||||
|  | ||||
| void InotifyWatcher::stop() { m_running.store(false); } | ||||
|  | ||||
| void InotifyWatcher::add_watch_for_path(std::filesystem::path const &path) | ||||
| { | ||||
| 	auto normalized { normalize_path(path) }; | ||||
| 	if (normalized.empty()) | ||||
| 		return; | ||||
|  | ||||
| 	auto const key = normalized.string(); | ||||
| 	{ | ||||
| 		std::scoped_lock lock(m_watch_mutex); | ||||
| 		auto it = m_path_to_watch.find(key); | ||||
| 		if (it != m_path_to_watch.end()) | ||||
| 			return; | ||||
| 	} | ||||
|  | ||||
| 	int const wd { ::inotify_add_watch(m_fd, normalized.c_str(), watch_mask) }; | ||||
| 	if (wd < 0) { | ||||
| 		if (errno == ENOENT || errno == ENOTDIR) | ||||
| 			return; | ||||
| 		throw std::system_error(errno, std::generic_category(), | ||||
| 		    "inotify_add_watch failed for " + normalized.string()); | ||||
| 	} | ||||
|  | ||||
| 	std::scoped_lock lock(m_watch_mutex); | ||||
| 	m_watch_to_path.emplace(wd, normalized); | ||||
| 	m_path_to_watch.emplace(key, wd); | ||||
| } | ||||
|  | ||||
| void InotifyWatcher::add_watch_recursively(std::filesystem::path const &path) | ||||
| { | ||||
| 	auto normalized = normalize_path(path); | ||||
| 	if (normalized.empty()) | ||||
| 		return; | ||||
|  | ||||
| 	add_watch_for_path(normalized); | ||||
|  | ||||
| 	std::error_code ec; | ||||
| 	if (!std::filesystem::is_directory(normalized, ec)) | ||||
| 		return; | ||||
|  | ||||
| 	try { | ||||
| 		auto const options { | ||||
| 			std::filesystem::directory_options::follow_directory_symlink | ||||
| 			| std::filesystem::directory_options::skip_permission_denied | ||||
| 		}; | ||||
| 		for (auto const &entry : std::filesystem::recursive_directory_iterator( | ||||
| 		         normalized, options)) { | ||||
| 			std::error_code inner_ec; | ||||
| 			if (!entry.is_directory(inner_ec) || inner_ec) | ||||
| 				continue; | ||||
| 			add_watch_for_path(entry.path()); | ||||
| 		} | ||||
| 	} catch (std::filesystem::filesystem_error const &) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void InotifyWatcher::remove_watch(int wd) | ||||
| { | ||||
| 	std::filesystem::path removed_path; | ||||
| 	{ | ||||
| 		std::scoped_lock lock(m_watch_mutex); | ||||
| 		auto it { m_watch_to_path.find(wd) }; | ||||
| 		if (it == m_watch_to_path.end()) | ||||
| 			return; | ||||
| 		removed_path = it->second; | ||||
| 		m_watch_to_path.erase(it); | ||||
| 		m_path_to_watch.erase(removed_path.string()); | ||||
| 	} | ||||
|  | ||||
| 	::inotify_rm_watch(m_fd, wd); | ||||
| } | ||||
|  | ||||
| void InotifyWatcher::dispatch_event( | ||||
|     int wd, std::uint32_t mask, std::string_view name) | ||||
| { | ||||
| 	std::filesystem::path base_path; | ||||
| 	callback_t callback_copy; | ||||
| 	{ | ||||
| 		std::scoped_lock lock(m_watch_mutex); | ||||
| 		auto it { m_watch_to_path.find(wd) }; | ||||
| 		if (it == m_watch_to_path.end()) | ||||
| 			return; | ||||
| 		base_path = it->second; | ||||
| 		callback_copy = m_callback; | ||||
| 	} | ||||
|  | ||||
| 	auto event_path { base_path }; | ||||
| 	if (!name.empty()) | ||||
| 		event_path /= name; | ||||
|  | ||||
| 	if ((mask & IN_ISDIR) | ||||
| 	    && (mask & (IN_CREATE | IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE))) { | ||||
| 		add_watch_recursively(event_path); | ||||
| 	} | ||||
|  | ||||
| 	if (mask & (IN_DELETE_SELF | IN_MOVE_SELF | IN_IGNORED)) { | ||||
| 		remove_watch(wd); | ||||
| 	} | ||||
|  | ||||
| 	if (callback_copy) { | ||||
| 		callback_copy(FileWatchEvent { | ||||
| 		    .path = std::move(event_path), | ||||
| 		    .mask = mask, | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::filesystem::path InotifyWatcher::normalize_path( | ||||
|     std::filesystem::path const &path) | ||||
| { | ||||
| 	std::error_code ec; | ||||
| 	auto normalized { std::filesystem::weakly_canonical(path, ec) }; | ||||
| 	if (ec) | ||||
| 		normalized = std::filesystem::absolute(path, ec); | ||||
| 	if (ec) | ||||
| 		return {}; | ||||
| 	return normalized; | ||||
| } | ||||
|  | ||||
| } // namespace Waylight | ||||
							
								
								
									
										57
									
								
								src/InotifyWatcher.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/InotifyWatcher.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <atomic> | ||||
| #include <cstdint> | ||||
| #include <filesystem> | ||||
| #include <functional> | ||||
| #include <mutex> | ||||
| #include <string> | ||||
| #include <string_view> | ||||
| #include <unordered_map> | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| struct FileWatchEvent { | ||||
| 	std::filesystem::path path; | ||||
| 	std::uint32_t mask; | ||||
| }; | ||||
|  | ||||
| class InotifyWatcher { | ||||
| public: | ||||
| 	using callback_t = std::function<void(FileWatchEvent const &)>; | ||||
|  | ||||
| 	InotifyWatcher(); | ||||
| 	~InotifyWatcher(); | ||||
|  | ||||
| 	InotifyWatcher(InotifyWatcher const &) = delete; | ||||
| 	InotifyWatcher &operator=(InotifyWatcher const &) = delete; | ||||
|  | ||||
| 	InotifyWatcher(InotifyWatcher &&) = delete; | ||||
| 	InotifyWatcher &operator=(InotifyWatcher &&) = delete; | ||||
|  | ||||
| 	void watch_path_recursively(std::filesystem::path const &path); | ||||
|  | ||||
| 	void set_callback(callback_t cb); | ||||
|  | ||||
| 	void run(); | ||||
| 	void stop(); | ||||
|  | ||||
| private: | ||||
| 	void add_watch_for_path(std::filesystem::path const &path); | ||||
| 	void add_watch_recursively(std::filesystem::path const &path); | ||||
| 	void remove_watch(int wd); | ||||
|  | ||||
| 	void dispatch_event(int wd, std::uint32_t mask, std::string_view name); | ||||
| 	static std::filesystem::path normalize_path( | ||||
| 	    std::filesystem::path const &path); | ||||
|  | ||||
| 	int m_fd; | ||||
| 	std::atomic<bool> m_running { false }; | ||||
| 	callback_t m_callback; | ||||
|  | ||||
| 	std::mutex m_watch_mutex; | ||||
| 	std::unordered_map<int, std::filesystem::path> m_watch_to_path; | ||||
| 	std::unordered_map<std::string, int> m_path_to_watch; | ||||
| }; | ||||
|  | ||||
| } // namespace Waylight | ||||
| @@ -37,6 +37,8 @@ | ||||
| #include <ext/import-font.h> | ||||
| #include <msdfgen.h> | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| constexpr int ATLAS_DIMENSION = 1024; | ||||
| @@ -347,11 +349,11 @@ TextRenderer::TextRenderer() | ||||
| { | ||||
| 	static char const msdf_vs_data[] { | ||||
| #embed "base.vert" | ||||
| 		, 0 | ||||
| 		, 0 // cppcheck-suppress syntaxError | ||||
| 	}; | ||||
| 	static char const msdf_fs_data[] { | ||||
| #embed "msdf.frag" | ||||
| 		, 0 | ||||
| 		, 0 // cppcheck-suppress syntaxError | ||||
| 	}; | ||||
| 	m_msdf_shader = LoadShaderFromMemory(msdf_vs_data, msdf_fs_data); | ||||
| 	assert(IsShaderValid(m_msdf_shader)); | ||||
| @@ -839,3 +841,5 @@ auto find_font_path(std::string_view path) | ||||
| 	} | ||||
| 	return final_path; | ||||
| } | ||||
|  | ||||
| } // namespace Waylight | ||||
|   | ||||
| @@ -21,12 +21,22 @@ namespace msdfgen { | ||||
| class FontHandle; | ||||
| } | ||||
|  | ||||
| struct FontHandle { | ||||
| 	auto operator()() const -> auto const & { return id; } | ||||
| namespace Waylight { | ||||
|  | ||||
| struct FontHandle { // cppcheck-supress noConstructor | ||||
| 	FontHandle() = default; | ||||
|  | ||||
| 	auto operator()() const -> auto const & | ||||
| 	{ | ||||
| 		if (id == 0xffffffff) { | ||||
| 			throw std::runtime_error("Uninitialized FontHandle"); | ||||
| 		} | ||||
| 		return id; | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	friend struct TextRenderer; | ||||
| 	usize id; | ||||
| 	usize id { 0xffffffff }; | ||||
| }; | ||||
|  | ||||
| struct FontRuntime; | ||||
| @@ -137,3 +147,5 @@ private: | ||||
|  | ||||
| auto find_font_path(std::string_view path = "sans-serif:style=Regular") | ||||
|     -> std::optional<std::filesystem::path>; | ||||
|  | ||||
| } // namespace Waylight | ||||
|   | ||||
| @@ -4,6 +4,8 @@ | ||||
|  | ||||
| #include "enum_array.hpp" | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| struct ColorScheme { | ||||
| 	Color foreground; | ||||
| 	Color foreground_preedit; | ||||
| @@ -40,3 +42,5 @@ constexpr auto make_default_themes() -> enum_array<Theme, ColorScheme> const | ||||
| 	}; | ||||
| 	return array; | ||||
| } | ||||
|  | ||||
| } // namespace Waylight | ||||
|   | ||||
							
								
								
									
										21
									
								
								src/Tick.cpp
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								src/Tick.cpp
									
									
									
									
									
								
							| @@ -8,12 +8,14 @@ | ||||
| #include <rlgl.h> | ||||
| #include <xkbcommon/xkbcommon.h> | ||||
|  | ||||
| #include <filesystem> | ||||
| #include <optional> | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| auto App::tick() -> void | ||||
| { | ||||
| 	static std::pmr::string text_input_data {}; | ||||
|  | ||||
| 	m_ime.bound_text = &text_input_data; | ||||
| 	m_ime.bound_id = 1; | ||||
| 	process_pending_text_input(); | ||||
| @@ -58,9 +60,20 @@ auto App::tick() -> void | ||||
| 			static_cast<float>(GetScreenWidth()), | ||||
| 			static_cast<float>(GetScreenHeight()), | ||||
| 		}; | ||||
| 		auto result = m_gui->text_input(1, text_input_data, input_rect); | ||||
| 		if (result.test(1)) | ||||
| 		; | ||||
| 		if (auto const result | ||||
| 		    = m_gui->text_input(1, text_input_data, input_rect); | ||||
| 		    result.test(1)) { | ||||
| 			m_ime.surrounding_dirty = true; | ||||
| 		} else if (result.test(0)) { | ||||
| 			if (text_input_data == "kitty") { | ||||
| 				execute_command(false, "kitty"); | ||||
| 			} else if (text_input_data == "nvim") { | ||||
| 				execute_command(true, "nvim"); | ||||
| 			} | ||||
|  | ||||
| 			text_input_data = ""; | ||||
| 		} | ||||
|  | ||||
| 		update_text_input_state(text_input_data, 1, input_rect); | ||||
| 	} | ||||
| @@ -71,3 +84,5 @@ auto App::tick() -> void | ||||
|  | ||||
| 	eglSwapBuffers(m_gl.edpy, m_gl.esurf); | ||||
| } | ||||
|  | ||||
| } // namespace Waylight | ||||
|   | ||||
| @@ -1,7 +1,11 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include <cstdint> | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| using u8 = std::uint8_t; | ||||
| using i8 = std::int8_t; | ||||
| using u16 = std::uint16_t; | ||||
| @@ -15,9 +19,8 @@ using isize = std::intptr_t; | ||||
|  | ||||
| [[maybe_unused]] static inline auto rune_to_string(uint32_t cp) -> char const * | ||||
| { | ||||
| 	static char utf8[5] = { 0 }; | ||||
| 	for (auto &c : utf8) | ||||
| 		c = 0; | ||||
| 	static std::array<char, 5> utf8 {}; | ||||
| 	std::fill(utf8.begin(), utf8.end(), 0); | ||||
|  | ||||
| 	if (cp < 0x80) { | ||||
| 		utf8[0] = cp; | ||||
| @@ -35,5 +38,7 @@ using isize = std::intptr_t; | ||||
| 		utf8[3] = 0x80 | (cp & 0x3F); | ||||
| 	} | ||||
|  | ||||
| 	return utf8; | ||||
| 	return utf8.data(); | ||||
| } | ||||
|  | ||||
| } // namespace Waylight | ||||
|   | ||||
| @@ -5,14 +5,17 @@ | ||||
| #include <stdexcept> | ||||
| #include <type_traits> | ||||
|  | ||||
| #include "common.hpp" | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| template<class E> struct enum_traits; | ||||
|  | ||||
| template<class E> | ||||
| concept EnumLike = std::is_enum_v<E>; | ||||
|  | ||||
| template<EnumLike E> | ||||
| constexpr usize enum_count_v | ||||
|     = static_cast<usize>(enum_traits<E>::last) | ||||
| constexpr usize enum_count_v = static_cast<usize>(enum_traits<E>::last) | ||||
|     - static_cast<usize>(enum_traits<E>::first) + 1; | ||||
|  | ||||
| template<EnumLike E, class T> struct enum_array { | ||||
| @@ -77,3 +80,5 @@ constexpr auto make_enum_array(T &&first_val, U &&...rest) | ||||
| 	arr._data = { std::forward<T>(first_val), std::forward<U>(rest)... }; | ||||
| 	return arr; | ||||
| } | ||||
|  | ||||
| } // namespace Waylight | ||||
|   | ||||
							
								
								
									
										99
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										99
									
								
								src/main.cpp
									
									
									
									
									
								
							| @@ -1,57 +1,88 @@ | ||||
| #include <algorithm> | ||||
| #include <csignal> | ||||
| #include <cstdio> | ||||
| #include <cstdlib> | ||||
| #include <fcntl.h> | ||||
| #include <iostream> | ||||
| #include <optional> | ||||
| #include <print> | ||||
| #include <signal.h> | ||||
| #include <sys/file.h> | ||||
| #include <sys/types.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| #include <cpptrace/cpptrace.hpp> | ||||
| #include <cpptrace/from_current.hpp> | ||||
| #include <tinyfiledialogs.h> | ||||
|  | ||||
| #include "App.hpp" | ||||
|  | ||||
| namespace Waylight { | ||||
|  | ||||
| bool signal_running(); | ||||
|  | ||||
| std::optional<App> g_app{}; | ||||
| std::optional<App> g_app {}; | ||||
|  | ||||
| auto main() -> int { | ||||
|   if (signal_running()) { | ||||
|     return 0; | ||||
|   } | ||||
| bool signal_running() | ||||
| { | ||||
| 	char const *lock_path = "/tmp/waylight.lock"; | ||||
| 	int fd = open(lock_path, O_CREAT | O_RDWR, 0666); | ||||
| 	if (fd == -1) | ||||
| 		return false; | ||||
|  | ||||
|   std::signal(SIGINT, [](int) { | ||||
|     if (g_app) | ||||
|       g_app->stop(); | ||||
|   }); | ||||
| 	if (flock(fd, LOCK_EX | LOCK_NB) == -1) { | ||||
| 		FILE *f = fopen(lock_path, "r"); | ||||
| 		if (f) { | ||||
| 			pid_t pid; | ||||
| 			if (fscanf(f, "%d", &pid) == 1) | ||||
| 				kill(pid, SIGUSR1); | ||||
| 			fclose(f); | ||||
| 		} | ||||
| 		close(fd); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
|   g_app.emplace(); | ||||
|   g_app->run(); | ||||
| 	if (ftruncate(fd, 0) == -1) { | ||||
| 		close(fd); | ||||
| 		unlink(lock_path); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	dprintf(fd, "%d\n", getpid()); | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool signal_running() { | ||||
|   const char *lock_path = "/tmp/waylight.lock"; | ||||
|   int fd = open(lock_path, O_CREAT | O_RDWR, 0666); | ||||
|   if (fd == -1) | ||||
|     return false; | ||||
| } // namespace Waylight | ||||
|  | ||||
|   if (flock(fd, LOCK_EX | LOCK_NB) == -1) { | ||||
|     FILE *f = fopen(lock_path, "r"); | ||||
|     if (f) { | ||||
|       pid_t pid; | ||||
|       if (fscanf(f, "%d", &pid) == 1) | ||||
|         kill(pid, SIGUSR1); | ||||
|       fclose(f); | ||||
|     } | ||||
|     close(fd); | ||||
|     return true; | ||||
|   } | ||||
| auto main() -> int | ||||
| { | ||||
| 	if (Waylight::signal_running()) { | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
|   if (ftruncate(fd, 0) == -1) { | ||||
|       close(fd); | ||||
|       unlink(lock_path); | ||||
|       return false; | ||||
|   } | ||||
| 	std::signal(SIGINT, [](int) { | ||||
| 		if (Waylight::g_app) | ||||
| 			Waylight::g_app->stop(); | ||||
| 	}); | ||||
|  | ||||
|   dprintf(fd, "%d\n", getpid()); | ||||
|   return false; | ||||
| 	CPPTRACE_TRY | ||||
| 	{ | ||||
| 		Waylight::g_app.emplace(); | ||||
| 		Waylight::g_app->run(); | ||||
| 	} | ||||
| 	CPPTRACE_CATCH(std::exception const &e) | ||||
| 	{ | ||||
| 		std::string what { e.what() }; | ||||
| 		std::ranges::replace(what, '"', '.'); | ||||
| 		std::ranges::replace(what, '\'', '.'); | ||||
| 		std::ranges::replace(what, '`', '.'); | ||||
| 		if (what.empty()) { | ||||
| 			std::println(std::cerr, "Unexpected exception!"); | ||||
| 		} else { | ||||
| 			std::println(std::cerr, "Unexpected exception! Error: {}", what); | ||||
| 		} | ||||
| 		cpptrace::from_current_exception().print(); | ||||
| 		tinyfd_messageBox( | ||||
| 		    "Unexpected exception", what.c_str(), "ok", "error", 1); | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										1
									
								
								vendor/CMakeLists.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								vendor/CMakeLists.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| add_subdirectory(tinyfiledialogs) | ||||
							
								
								
									
										8
									
								
								vendor/tinyfiledialogs/CMakeLists.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								vendor/tinyfiledialogs/CMakeLists.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| cmake_minimum_required(VERSION 3.30) | ||||
|  | ||||
| project(tinyfiledialogs C) | ||||
|  | ||||
| add_library(${PROJECT_NAME} STATIC tinyfiledialogs.c) | ||||
|  | ||||
| target_include_directories(${PROJECT_NAME} PUBLIC include) | ||||
|  | ||||
							
								
								
									
										314
									
								
								vendor/tinyfiledialogs/include/tinyfiledialogs.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										314
									
								
								vendor/tinyfiledialogs/include/tinyfiledialogs.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,314 @@ | ||||
| /* SPDX-License-Identifier: Zlib | ||||
| Copyright (c) 2014 - 2025 Guillaume Vareille http://ysengrin.com | ||||
| 	 ____________________________________________________________________ | ||||
| 	|                                                                    | | ||||
| 	| 100% compatible C C++  ->  You can rename tinfiledialogs.c as .cpp | | ||||
| 	|____________________________________________________________________| | ||||
|  | ||||
| ********* TINY FILE DIALOGS OFFICIAL WEBSITE IS ON SOURCEFORGE ********* | ||||
|   _________ | ||||
|  /         \ tinyfiledialogs.h v3.21.1 [Oct 5, 2025] | ||||
|  |tiny file| Unique header file created [November 9, 2014] | ||||
|  | dialogs | | ||||
|  \____  ___/ http://tinyfiledialogs.sourceforge.net | ||||
|       \|     git clone http://git.code.sf.net/p/tinyfiledialogs/code tinyfd | ||||
|  ____________________________________________ | ||||
| |                                            | | ||||
| |   email: tinyfiledialogs at ysengrin.com   | | ||||
| |____________________________________________| | ||||
|  ________________________________________________________________________________ | ||||
| |  ____________________________________________________________________________  | | ||||
| | |                                                                            | | | ||||
| | |  - in tinyfiledialogs, char is UTF-8 by default (since v3.6)               | | | ||||
| | |                                                                            | | | ||||
| | | on windows:                                                                | | | ||||
| | |  - for UTF-16, use the wchar_t functions at the bottom of the header file  | | | ||||
| | |                                                                            | | | ||||
| | |  - _wfopen() requires wchar_t                                              | | | ||||
| | |  - fopen() uses char but expects ASCII or MBCS (not UTF-8)                 | | | ||||
| | |  - if you want char to be MBCS: set tinyfd_winUtf8 to 0                    | | | ||||
| | |                                                                            | | | ||||
| | |  - alternatively, tinyfiledialogs provides                                 | | | ||||
| | |                        functions to convert between UTF-8, UTF-16 and MBCS | | | ||||
| | |____________________________________________________________________________| | | ||||
| |________________________________________________________________________________| | ||||
|  | ||||
| If you like tinyfiledialogs, please upvote my stackoverflow answer | ||||
| https://stackoverflow.com/a/47651444 | ||||
|  | ||||
| - License - | ||||
| This software is provided 'as-is', without any express or implied | ||||
| warranty.  In no event will the authors be held liable for any damages | ||||
| arising from the use of this software. | ||||
| Permission is granted to anyone to use this software for any purpose, | ||||
| including commercial applications, and to alter it and redistribute it | ||||
| freely, subject to the following restrictions: | ||||
| 1. The origin of this software must not be misrepresented; you must not | ||||
| claim that you wrote the original software.  If you use this software | ||||
| in a product, an acknowledgment in the product documentation would be | ||||
| appreciated but is not required. | ||||
| 2. Altered source versions must be plainly marked as such, and must not be | ||||
| misrepresented as being the original software. | ||||
| 3. This notice may not be removed or altered from any source distribution. | ||||
|  | ||||
|      __________________________________________ | ||||
|     |  ______________________________________  | | ||||
|     | |                                      | | | ||||
|     | | DO NOT USE USER INPUT IN THE DIALOGS | | | ||||
|     | |______________________________________| | | ||||
|     |__________________________________________| | ||||
| */ | ||||
|  | ||||
| #ifndef TINYFILEDIALOGS_H | ||||
| #define TINYFILEDIALOGS_H | ||||
|  | ||||
| #ifdef	__cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| /******************************************************************************************************/ | ||||
| /**************************************** UTF-8 on Windows ********************************************/ | ||||
| /******************************************************************************************************/ | ||||
| #ifdef _WIN32 | ||||
| /* On windows, if you want to use UTF-8 ( instead of the UTF-16/wchar_t functions at the end of this file ) | ||||
| Make sure your code is really prepared for UTF-8 (on windows, functions like fopen() expect MBCS and not UTF-8) */ | ||||
| extern int tinyfd_winUtf8; /* on windows char strings can be 1:UTF-8(default) or 0:MBCS */ | ||||
| /* for MBCS change this to 0, in tinyfiledialogs.c or in your code */ | ||||
|  | ||||
| /* Here are some functions to help you convert between UTF-16 UTF-8 MBSC */ | ||||
| char * tinyfd_utf8toMbcs(char const * aUtf8string); | ||||
| char * tinyfd_utf16toMbcs(wchar_t const * aUtf16string); | ||||
| wchar_t * tinyfd_mbcsTo16(char const * aMbcsString); | ||||
| char * tinyfd_mbcsTo8(char const * aMbcsString); | ||||
| wchar_t * tinyfd_utf8to16(char const * aUtf8string); | ||||
| char * tinyfd_utf16to8(wchar_t const * aUtf16string); | ||||
| #endif | ||||
| /******************************************************************************************************/ | ||||
| /******************************************************************************************************/ | ||||
| /******************************************************************************************************/ | ||||
|  | ||||
| /************* 3 funtions for C# (you don't need this in C or C++) : */ | ||||
| char const * tinyfd_getGlobalChar(char const * aCharVariableName); /* returns NULL on error */ | ||||
| int tinyfd_getGlobalInt(char const * aIntVariableName); /* returns -1 on error */ | ||||
| int tinyfd_setGlobalInt(char const * aIntVariableName, int aValue); /* returns -1 on error */ | ||||
| /* aCharVariableName: "tinyfd_version" "tinyfd_needs" "tinyfd_response" | ||||
|    aIntVariableName : "tinyfd_verbose" "tinyfd_silent" "tinyfd_allowCursesDialogs" | ||||
| 				      "tinyfd_forceConsole" "tinyfd_assumeGraphicDisplay" "tinyfd_winUtf8" | ||||
| **************/ | ||||
|  | ||||
| extern char tinyfd_version[8]; /* contains tinyfd current version number */ | ||||
| extern char tinyfd_needs[]; /* info about requirements */ | ||||
| extern int tinyfd_verbose; /* 0 (default) or 1 : on unix, prints the command line calls */ | ||||
| extern int tinyfd_silent; /* 1 (default) or 0 : on unix, hide errors and warnings from called dialogs */ | ||||
|  | ||||
| /** Curses dialogs are difficult to use and counter-intuitive. | ||||
| On windows they are only ascii and still uses the unix backslash ! **/ | ||||
| extern int tinyfd_allowCursesDialogs; /* 0 (default) or 1 */ | ||||
|  | ||||
| extern int tinyfd_forceConsole;  /* 0 (default) or 1 */ | ||||
| /* for unix & windows: 0 (graphic mode) or 1 (console mode). | ||||
| 0: try to use a graphic solution, if it fails then it uses console mode. | ||||
| 1: forces all dialogs into console mode even when an X server is present. | ||||
|    if enabled, it can use the package Dialog or dialog.exe. | ||||
|    on windows it only make sense for console applications */ | ||||
|  | ||||
| /* extern int tinyfd_assumeGraphicDisplay; */ /* 0 (default) or 1  */ | ||||
| /* some systems don't set the environment variable DISPLAY even when a graphic display is present. | ||||
| set this to 1 to tell tinyfiledialogs to assume the existence of a graphic display */ | ||||
|  | ||||
| extern char tinyfd_response[1024]; | ||||
| /* if you pass "tinyfd_query" as aTitle, | ||||
| the functions will not display the dialogs | ||||
| but will return 0 for console mode, 1 for graphic mode. | ||||
| tinyfd_response is then filled with the retain solution. | ||||
| possible values for tinyfd_response are (all lowercase) | ||||
| for graphic mode: | ||||
|   windows_wchar windows applescript kdialog zenity zenity3 yad matedialog | ||||
|   shellementary qarma shanty boxer python2-tkinter python3-tkinter python-dbus | ||||
|   perl-dbus gxmessage gmessage xmessage xdialog gdialog dunst | ||||
| for console mode: | ||||
|   dialog whiptail basicinput no_solution */ | ||||
|  | ||||
| void tinyfd_beep(void); | ||||
|  | ||||
| int tinyfd_notifyPopup( | ||||
| 	char const * aTitle, /* NULL or "" */ | ||||
| 	char const * aMessage, /* NULL or "" may contain \n \t */ | ||||
| 	char const * aIconType); /* "info" "warning" "error" */ | ||||
| 		/* return has only meaning for tinyfd_query */ | ||||
|  | ||||
| int tinyfd_messageBox( | ||||
| 	char const * aTitle , /* NULL or "" */ | ||||
| 	char const * aMessage , /* NULL or "" may contain \n \t */ | ||||
| 	char const * aDialogType , /* "ok" "okcancel" "yesno" "yesnocancel" */ | ||||
| 	char const * aIconType , /* "info" "warning" "error" "question" */ | ||||
| 	int aDefaultButton ) ; | ||||
| 		/* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ | ||||
|  | ||||
| char * tinyfd_inputBox( | ||||
| 	char const * aTitle , /* NULL or "" */ | ||||
| 	char const * aMessage , /* NULL or "" (\n and \t have no effect) */ | ||||
| 	char const * aDefaultInput ) ;  /* NULL = passwordBox, "" = inputbox */ | ||||
| 		/* returns NULL on cancel */ | ||||
|  | ||||
| char * tinyfd_saveFileDialog( | ||||
| 	char const * aTitle , /* NULL or "" */ | ||||
| 	char const * aDefaultPathAndOrFile , /* NULL or "" , ends with / to set only a directory */ | ||||
| 	int aNumOfFilterPatterns , /* 0  (1 in the following example) */ | ||||
| 	char const * const * aFilterPatterns , /* NULL or char const * lFilterPatterns[1]={"*.txt"} */ | ||||
| 	char const * aSingleFilterDescription ) ; /* NULL or "text files" */ | ||||
| 		/* returns NULL on cancel */ | ||||
|  | ||||
| char * tinyfd_openFileDialog( | ||||
| 	char const * aTitle, /* NULL or "" */ | ||||
| 	char const * aDefaultPathAndOrFile, /* NULL or "" , ends with / to set only a directory */ | ||||
| 	int aNumOfFilterPatterns , /* 0 (2 in the following example) */ | ||||
| 	char const * const * aFilterPatterns, /* NULL or char const * lFilterPatterns[2]={"*.png","*.jpg"}; */ | ||||
| 	char const * aSingleFilterDescription, /* NULL or "image files" */ | ||||
| 	int aAllowMultipleSelects ) ; /* 0 or 1 */ | ||||
| 		/* in case of multiple files, the separator is | */ | ||||
| 		/* returns NULL on cancel */ | ||||
|  | ||||
| char * tinyfd_selectFolderDialog( | ||||
| 	char const * aTitle, /* NULL or "" */ | ||||
| 	char const * aDefaultPath); /* NULL or "" */ | ||||
| 		/* returns NULL on cancel */ | ||||
|  | ||||
| char * tinyfd_colorChooser( | ||||
| 	char const * aTitle, /* NULL or "" */ | ||||
| 	char const * aDefaultHexRGB, /* NULL or "" or "#FF0000" */ | ||||
| 	unsigned char const aDefaultRGB[3] , /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */ | ||||
| 	unsigned char aoResultRGB[3] ) ; /* unsigned char lResultRGB[3]; */ | ||||
| 		/* aDefaultRGB is used only if aDefaultHexRGB is absent */ | ||||
| 		/* aDefaultRGB and aoResultRGB can be the same array */ | ||||
| 		/* returns NULL on cancel */ | ||||
| 		/* returns the hexcolor as a string "#FF0000" */ | ||||
| 		/* aoResultRGB also contains the result */ | ||||
|  | ||||
|  | ||||
| /************ WINDOWS ONLY SECTION ************************/ | ||||
| #ifdef _WIN32 | ||||
|  | ||||
| /* windows only - utf-16 version */ | ||||
| int tinyfd_notifyPopupW( | ||||
| 	wchar_t const * aTitle, /* NULL or L"" */ | ||||
| 	wchar_t const * aMessage, /* NULL or L"" may contain \n \t */ | ||||
| 	wchar_t const * aIconType); /* L"info" L"warning" L"error" */ | ||||
|  | ||||
| /* windows only - utf-16 version */ | ||||
| int tinyfd_messageBoxW( | ||||
| 	wchar_t const * aTitle, /* NULL or L"" */ | ||||
| 	wchar_t const * aMessage, /* NULL or L"" may contain \n \t */ | ||||
| 	wchar_t const * aDialogType, /* L"ok" L"okcancel" L"yesno" */ | ||||
| 	wchar_t const * aIconType, /* L"info" L"warning" L"error" L"question" */ | ||||
| 	int aDefaultButton ); /* 0 for cancel/no , 1 for ok/yes */ | ||||
| 		/* returns 0 for cancel/no , 1 for ok/yes */ | ||||
|  | ||||
| /* windows only - utf-16 version */ | ||||
| wchar_t * tinyfd_inputBoxW( | ||||
| 	wchar_t const * aTitle, /* NULL or L"" */ | ||||
| 	wchar_t const * aMessage, /* NULL or L"" (\n nor \t not respected) */ | ||||
| 	wchar_t const * aDefaultInput); /* NULL passwordBox, L"" inputbox */ | ||||
|  | ||||
| /* windows only - utf-16 version */ | ||||
| wchar_t * tinyfd_saveFileDialogW( | ||||
| 	wchar_t const * aTitle, /* NULL or L"" */ | ||||
| 	wchar_t const * aDefaultPathAndOrFile, /* NULL or L"" , ends with / to set only a directory */ | ||||
| 	int aNumOfFilterPatterns, /* 0 (1 in the following example) */ | ||||
| 	wchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[1]={L"*.txt"} */ | ||||
| 	wchar_t const * aSingleFilterDescription); /* NULL or L"text files" */ | ||||
| 		/* returns NULL on cancel */ | ||||
|  | ||||
| /* windows only - utf-16 version */ | ||||
| wchar_t * tinyfd_openFileDialogW( | ||||
| 	wchar_t const * aTitle, /* NULL or L"" */ | ||||
| 	wchar_t const * aDefaultPathAndOrFile, /* NULL or L"" , ends with / to set only a directory */ | ||||
| 	int aNumOfFilterPatterns , /* 0 (2 in the following example) */ | ||||
| 	wchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[2]={L"*.png","*.jpg"} */ | ||||
| 	wchar_t const * aSingleFilterDescription, /* NULL or L"image files" */ | ||||
| 	int aAllowMultipleSelects ) ; /* 0 or 1 */ | ||||
| 		/* in case of multiple files, the separator is | */ | ||||
| 		/* returns NULL on cancel */ | ||||
|  | ||||
| /* windows only - utf-16 version */ | ||||
| wchar_t * tinyfd_selectFolderDialogW( | ||||
| 	wchar_t const * aTitle, /* NULL or L"" */ | ||||
| 	wchar_t const * aDefaultPath); /* NULL or L"" */ | ||||
| 		/* returns NULL on cancel */ | ||||
|  | ||||
| /* windows only - utf-16 version */ | ||||
| wchar_t * tinyfd_colorChooserW( | ||||
| 	wchar_t const * aTitle, /* NULL or L"" */ | ||||
| 	wchar_t const * aDefaultHexRGB, /* NULL or L"#FF0000" */ | ||||
| 	unsigned char const aDefaultRGB[3], /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */ | ||||
| 	unsigned char aoResultRGB[3]); /* unsigned char lResultRGB[3]; */ | ||||
| 		/* returns the hexcolor as a string L"#FF0000" */ | ||||
| 		/* aoResultRGB also contains the result */ | ||||
| 		/* aDefaultRGB is used only if aDefaultHexRGB is NULL */ | ||||
| 		/* aDefaultRGB and aoResultRGB can be the same array */ | ||||
| 		/* returns NULL on cancel */ | ||||
|  | ||||
| #endif /*_WIN32 */ | ||||
|  | ||||
| #ifdef	__cplusplus | ||||
| } /*extern "C"*/ | ||||
| #endif | ||||
|  | ||||
| #endif /* TINYFILEDIALOGS_H */ | ||||
|  | ||||
| /* | ||||
|  ________________________________________________________________________________ | ||||
| |  ____________________________________________________________________________  | | ||||
| | |                                                                            | | | ||||
| | | on windows:                                                                | | | ||||
| | |  - for UTF-16, use the wchar_t functions at the bottom of the header file  | | | ||||
| | |  - _wfopen() requires wchar_t                                              | | | ||||
| | |                                                                            | | | ||||
| | |  - in tinyfiledialogs, char is UTF-8 by default (since v3.6)               | | | ||||
| | |  - but fopen() expects MBCS (not UTF-8)                                    | | | ||||
| | |  - if you want char to be MBCS: set tinyfd_winUtf8 to 0                    | | | ||||
| | |                                                                            | | | ||||
| | |  - alternatively, tinyfiledialogs provides                                 | | | ||||
| | |                        functions to convert between UTF-8, UTF-16 and MBCS | | | ||||
| | |____________________________________________________________________________| | | ||||
| |________________________________________________________________________________| | ||||
|  | ||||
| - This is not for ios nor android (it works in termux though). | ||||
| - The files can be renamed with extension ".cpp" as the code is 100% compatible C C++ | ||||
|   (just comment out << extern "C" >> in the header file) | ||||
| - Windows is fully supported from XP to 10 (maybe even older versions) | ||||
| - C# & LUA via dll, see files in the folder EXTRAS | ||||
| - OSX supported from 10.4 to latest (maybe even older versions) | ||||
| - Do not use " and ' as the dialogs will be displayed with a warning | ||||
|   instead of the title, message, etc... | ||||
| - There's one file filter only, it may contain several patterns. | ||||
| - If no filter description is provided, | ||||
|   the list of patterns will become the description. | ||||
| - On windows link against Comdlg32.lib and Ole32.lib | ||||
|   (on windows the no linking claim is a lie) | ||||
| - On unix: it tries command line calls, so no such need (NO LINKING). | ||||
| - On unix you need one of the following: | ||||
|   applescript, kdialog, zenity, matedialog, shellementary, qarma, shanty, boxer, | ||||
|   yad, python (2 or 3)/tkinter/python-dbus (optional), Xdialog | ||||
|   or curses dialogs (opens terminal if running without console). | ||||
| - One of those is already included on most (if not all) desktops. | ||||
| - In the absence of those it will use gdialog, gxmessage or whiptail | ||||
|   with a textinputbox. If nothing is found, it switches to basic console input, | ||||
|   it opens a console if needed (requires xterm + bash). | ||||
| - for curses dialogs you must set tinyfd_allowCursesDialogs=1 | ||||
| - You can query the type of dialog that will be used (pass "tinyfd_query" as aTitle) | ||||
| - String memory is preallocated statically for all the returned values. | ||||
| - File and path names are tested before return, they should be valid. | ||||
| - tinyfd_forceConsole=1; at run time, forces dialogs into console mode. | ||||
| - On windows, console mode only make sense for console applications. | ||||
| - On windows, console mode is not implemented for wchar_T UTF-16. | ||||
| - Mutiple selects are not possible in console mode. | ||||
| - The package dialog must be installed to run in curses dialogs in console mode. | ||||
|   It is already installed on most unix systems. | ||||
| - On osx, the package dialog can be installed via | ||||
|   http://macappstore.org/dialog or http://macports.org | ||||
| - On windows, for curses dialogs console mode, | ||||
|   dialog.exe should be copied somewhere on your executable path. | ||||
|   It can be found at the bottom of the following page: | ||||
|   http://andrear.altervista.org/home/cdialog.php | ||||
| */ | ||||
							
								
								
									
										8397
									
								
								vendor/tinyfiledialogs/tinyfiledialogs.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8397
									
								
								vendor/tinyfiledialogs/tinyfiledialogs.c
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user