├── .gitignore ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── cmake ├── build_icons.cmake ├── cmake_uninstall.cmake.in ├── compile_po.cmake ├── dist.cmake ├── dist_unix_bundle.cmake ├── dist_unix_bundle_script.cmake.in ├── dist_windows_iss.cmake ├── fixup_gnu_install_dirs.cmake ├── gen_desktop_entry.cmake ├── gen_manual.cmake ├── get_linguas.cmake ├── install.cmake ├── install_unix.cmake ├── install_unix_hicolor_icons.cmake.in ├── install_windows.cmake ├── install_windows_dlls.cmake.in ├── install_windows_msys2.cmake ├── install_windows_msys2_copy_dlls.cmake.in ├── line_endings_conversion.cmake ├── qt_utils.cmake ├── tesseract_utils.cmake ├── uninstall.cmake └── uninstall_unix.cmake ├── data ├── dpscreenocr.desktop ├── icons │ ├── dpscreenocr.ico │ ├── icon-tool.py │ ├── logo-design.svg │ └── sizes │ │ ├── 16 │ │ ├── dpscreenocr-busy.png │ │ ├── dpscreenocr-busy.svg │ │ ├── dpscreenocr-error.png │ │ ├── dpscreenocr-error.svg │ │ ├── dpscreenocr.png │ │ └── dpscreenocr.svg │ │ ├── 24 │ │ ├── dpscreenocr-busy.png │ │ ├── dpscreenocr-error.png │ │ └── dpscreenocr.png │ │ ├── 32 │ │ ├── dpscreenocr-busy.png │ │ ├── dpscreenocr-error.png │ │ └── dpscreenocr.png │ │ ├── 48 │ │ ├── dpscreenocr-busy.png │ │ ├── dpscreenocr-error.png │ │ └── dpscreenocr.png │ │ ├── 64 │ │ ├── dpscreenocr-busy.png │ │ ├── dpscreenocr-error.png │ │ └── dpscreenocr.png │ │ ├── 128 │ │ ├── dpscreenocr-busy.png │ │ ├── dpscreenocr-error.png │ │ └── dpscreenocr.png │ │ └── scalable │ │ ├── dpscreenocr-busy.svg │ │ ├── dpscreenocr-error.svg │ │ └── dpscreenocr.svg └── sounds │ └── done.wav ├── dist └── windows │ └── iss │ ├── inno_setup.iss │ ├── inno_setup_config.isi.in │ ├── wizard.bmp │ └── wizard_small.bmp ├── doc ├── building-unix.txt ├── building-windows-msys2.txt ├── changelog.txt ├── code-notes.txt ├── manual-data │ ├── manual.css │ ├── split.svg │ └── template.html ├── manual-metadata.yaml.in ├── manual.md └── translation-guide.txt ├── po ├── LINGUAS ├── POTFILES.in ├── bg.po ├── ca.po ├── de.po ├── es.po ├── fr.po ├── hr.po ├── nb_NO.po ├── pa_PK.po ├── pl.po ├── pt_BR.po ├── ru.po ├── tr.po ├── uk.po ├── update-po.py └── zh_CN.po ├── src ├── dpso_example │ ├── CMakeLists.txt │ └── main.c ├── dpso_ext │ ├── CMakeLists.txt │ ├── cfg.cpp │ ├── cfg.h │ ├── cfg_ext.cpp │ ├── cfg_ext.h │ ├── dpso_ext.h │ ├── history.cpp │ ├── history.h │ ├── history_export.cpp │ ├── history_export.h │ ├── user_dirs.h │ ├── user_dirs_unix_xdg.cpp │ └── user_dirs_windows.cpp ├── dpso_img │ ├── CMakeLists.txt │ ├── dpso_img.h │ ├── img.cpp │ ├── img.h │ ├── ops.cpp │ ├── ops.h │ ├── ops_utils.h │ ├── pnm.cpp │ ├── pnm.h │ ├── px_format.cpp │ └── px_format.h ├── dpso_intl │ ├── CMakeLists.txt │ ├── bindtextdomain_utf8.cpp │ ├── bindtextdomain_utf8.h │ ├── dpso_intl.h │ ├── helpers.cpp │ └── helpers.h ├── dpso_json │ ├── CMakeLists.txt │ ├── json.cpp │ └── json.h ├── dpso_net │ ├── CMakeLists.txt │ ├── download_file.cpp │ ├── download_file.h │ ├── error.h │ ├── get_data.cpp │ ├── get_data.h │ ├── request.h │ ├── request_curl.cpp │ ├── request_curl_lib.cpp │ ├── request_curl_lib.h │ ├── request_curl_lib_utils.h │ └── request_windows.cpp ├── dpso_ocr │ ├── CMakeLists.txt │ ├── data_lock.cpp │ ├── data_lock.h │ ├── dpso_ocr.h │ ├── engine.cpp │ ├── engine.h │ ├── engine │ │ ├── engine.cpp │ │ ├── engine.h │ │ ├── error.h │ │ ├── lang_code_validator.cpp │ │ ├── lang_code_validator.h │ │ ├── lang_manager.h │ │ ├── lang_manager_error.h │ │ ├── recognizer.h │ │ ├── recognizer_error.h │ │ ├── remote_files_lang_manager.cpp │ │ ├── remote_files_lang_manager.h │ │ └── tesseract │ │ │ ├── engine.cpp │ │ │ ├── engine.h │ │ │ ├── lang_manager.cpp │ │ │ ├── lang_manager.h │ │ │ ├── lang_manager_null.cpp │ │ │ ├── lang_names.cpp │ │ │ ├── lang_names.h │ │ │ ├── lang_utils.cpp │ │ │ ├── lang_utils.h │ │ │ ├── recognizer.cpp │ │ │ ├── recognizer.h │ │ │ ├── utils.cpp │ │ │ └── utils.h │ ├── lang_manager.cpp │ ├── lang_manager.h │ ├── ocr.cpp │ └── ocr.h ├── dpso_sound │ ├── CMakeLists.txt │ ├── error.h │ ├── format_info.h │ ├── sound.h │ ├── sound_null.cpp │ ├── sound_windows.cpp │ └── unix │ │ ├── audio_data.h │ │ ├── lib │ │ ├── pulse.cpp │ │ ├── pulse.h │ │ ├── sndfile.cpp │ │ └── sndfile.h │ │ ├── sndfile.cpp │ │ ├── sndfile.h │ │ └── sound_pulse.cpp ├── dpso_sys │ ├── CMakeLists.txt │ ├── backend │ │ ├── backend.h │ │ ├── backend_error.h │ │ ├── key_manager.h │ │ ├── screenshot_error.h │ │ ├── selection.h │ │ ├── unix │ │ │ ├── backend.cpp │ │ │ └── x11 │ │ │ │ ├── backend.cpp │ │ │ │ ├── backend.h │ │ │ │ ├── backend_component.h │ │ │ │ ├── key_manager.cpp │ │ │ │ ├── key_manager.h │ │ │ │ ├── screenshot.cpp │ │ │ │ ├── screenshot.h │ │ │ │ ├── selection.cpp │ │ │ │ ├── selection.h │ │ │ │ └── utils.h │ │ └── windows │ │ │ ├── backend.cpp │ │ │ ├── execution_layer │ │ │ ├── action_executor.cpp │ │ │ ├── action_executor.h │ │ │ ├── backend_executor.cpp │ │ │ ├── backend_executor.h │ │ │ ├── key_manager_executor.cpp │ │ │ ├── key_manager_executor.h │ │ │ ├── selection_executor.cpp │ │ │ └── selection_executor.h │ │ │ ├── key_manager.cpp │ │ │ ├── key_manager.h │ │ │ ├── screenshot.cpp │ │ │ ├── screenshot.h │ │ │ ├── selection.cpp │ │ │ └── selection.h │ ├── dpso_sys.cpp │ ├── dpso_sys.h │ ├── dpso_sys_fwd.h │ ├── dpso_sys_p.h │ ├── key_manager.cpp │ ├── key_manager.h │ ├── keys.cpp │ ├── keys.h │ ├── screenshot.cpp │ ├── screenshot.h │ ├── selection.cpp │ └── selection.h ├── dpso_utils │ ├── CMakeLists.txt │ ├── byte_order.h │ ├── dpso_utils.h │ ├── error.cpp │ ├── error_get.h │ ├── error_set.h │ ├── geometry.cpp │ ├── geometry.h │ ├── geometry_c.cpp │ ├── geometry_c.h │ ├── line_reader.cpp │ ├── line_reader.h │ ├── os.h │ ├── os_c.cpp │ ├── os_c.h │ ├── os_common.cpp │ ├── os_stdio.h │ ├── os_unix.cpp │ ├── os_windows.cpp │ ├── progress_tracker.cpp │ ├── progress_tracker.h │ ├── scope_exit.h │ ├── sha256.cpp │ ├── sha256.h │ ├── sha256_file.cpp │ ├── sha256_file.h │ ├── str.cpp │ ├── str.h │ ├── str_format_core.h │ ├── stream │ │ ├── file_stream.cpp │ │ ├── file_stream.h │ │ ├── out_newline_conversion_stream.cpp │ │ ├── out_newline_conversion_stream.h │ │ ├── stream.h │ │ ├── string_stream.cpp │ │ ├── string_stream.h │ │ ├── utils.cpp │ │ └── utils.h │ ├── strftime.cpp │ ├── strftime.h │ ├── synchronized.h │ ├── timing.cpp │ ├── timing.h │ ├── unix │ │ ├── dl.h │ │ ├── exe_path.cpp │ │ ├── exe_path.h │ │ ├── path_env_search.cpp │ │ ├── path_env_search.h │ │ ├── xdg_dirs.cpp │ │ └── xdg_dirs.h │ ├── version_cmp.cpp │ ├── version_cmp.h │ └── windows │ │ ├── cmdline.cpp │ │ ├── cmdline.h │ │ ├── com.h │ │ ├── error.cpp │ │ ├── error.h │ │ ├── gdi.h │ │ ├── handle.h │ │ ├── module.h │ │ ├── utf.cpp │ │ ├── utf.h │ │ └── window.h ├── thirdparty │ └── stb_image_resize2 │ │ ├── CMakeLists.txt │ │ ├── stb_image_resize2.c │ │ └── stb_image_resize2.h └── ui │ ├── qt │ ├── CMakeLists.txt │ ├── about.cpp │ ├── about.h │ ├── action_chooser.cpp │ ├── action_chooser.h │ ├── error.h │ ├── history.cpp │ ├── history.h │ ├── hotkey_editor.cpp │ ├── hotkey_editor.h │ ├── lang_browser.cpp │ ├── lang_browser.h │ ├── lang_manager │ │ ├── install_mode.h │ │ ├── install_progress_dialog.cpp │ │ ├── install_progress_dialog.h │ │ ├── lang_list.cpp │ │ ├── lang_list.h │ │ ├── lang_list_sort_filter_proxy.cpp │ │ ├── lang_list_sort_filter_proxy.h │ │ ├── lang_manager.cpp │ │ ├── lang_manager.h │ │ ├── lang_manager_page.cpp │ │ ├── lang_manager_page.h │ │ ├── lang_op_status_error.cpp │ │ ├── lang_op_status_error.h │ │ └── metatypes.h │ ├── main.cpp │ ├── main_window.cpp │ ├── main_window.h │ ├── status.h │ ├── status_indicator.cpp │ ├── status_indicator.h │ ├── update_checker.cpp │ ├── update_checker.h │ ├── utils.cpp │ └── utils.h │ ├── ui_common │ ├── CMakeLists.txt │ ├── app_dirs.h │ ├── app_dirs_unix.cpp │ ├── app_dirs_unix_cfg.h.in │ ├── app_dirs_windows.cpp │ ├── app_info.cpp.in │ ├── app_info.h │ ├── autostart.h │ ├── autostart_default.cpp │ ├── autostart_default.h │ ├── autostart_unix.cpp │ ├── autostart_windows.cpp │ ├── cfg_default_values.cpp │ ├── cfg_default_values.h │ ├── cfg_key_values.txt │ ├── cfg_key_values_gen.py │ ├── cfg_keys.cpp │ ├── cfg_keys.h │ ├── cmdline_cmd_autostart.cpp │ ├── cmdline_cmd_autostart.h │ ├── cmdline_opts.cpp │ ├── cmdline_opts.h │ ├── exe_path.h │ ├── exe_path_unix.cpp │ ├── exe_path_windows.cpp │ ├── file_names.cpp.in │ ├── file_names.h │ ├── init.cpp │ ├── init.h │ ├── init_app_dirs.h │ ├── init_extra.h │ ├── init_extra_unix.cpp │ ├── init_extra_windows.cpp │ ├── init_intl.cpp │ ├── init_intl.h │ ├── init_startup_args.cpp │ ├── init_startup_args.h │ ├── init_user_data.cpp │ ├── init_user_data.h │ ├── ocr_default.cpp │ ├── ocr_default.h │ ├── ocr_default_data_dir.cpp │ ├── ocr_default_data_dir.h │ ├── single_instance_guard.h │ ├── single_instance_guard_unix.cpp │ ├── single_instance_guard_windows.cpp │ ├── sound.cpp │ ├── sound.h │ ├── startup_args.h │ ├── str_nformat.cpp │ ├── str_nformat.h │ ├── taskbar.h │ ├── taskbar_config.h.in │ ├── taskbar_null.cpp │ ├── taskbar_windows.cpp │ ├── toplevel_argv0.cpp │ ├── toplevel_argv0.h │ ├── ui_common.h │ ├── update_checker.cpp │ ├── update_checker.h │ ├── update_checker_default.cpp │ ├── update_checker_default.h │ ├── update_checker_null.cpp │ ├── update_checker_platform.h │ ├── update_checker_platform_generic.cpp │ ├── update_checker_platform_linux.cpp │ ├── update_checker_platform_windows.cpp │ ├── user_agent.cpp │ └── user_agent.h │ └── windows_rc │ ├── CMakeLists.txt │ ├── manifest.xml.in │ └── resource.rc.in ├── tests ├── CMakeLists.txt ├── dpso_ext │ ├── test_cfg.cpp │ ├── test_history.cpp │ └── test_history_export.cpp ├── dpso_ocr │ └── test_tesseract_utils.cpp ├── dpso_sys │ ├── test_keys.cpp │ └── test_windows_action_executor.cpp ├── dpso_utils │ ├── stream │ │ ├── test_out_newline_conversion_stream.cpp │ │ └── test_string_stream.cpp │ ├── test_byte_order.cpp │ ├── test_geometry.cpp │ ├── test_line_reader.cpp │ ├── test_os.cpp │ ├── test_os_stdio.cpp │ ├── test_progress_tracker.cpp │ ├── test_sha256.cpp │ ├── test_sha256_file.cpp │ ├── test_str.cpp │ ├── test_strftime.cpp │ ├── test_version_cmp.cpp │ └── windows │ │ ├── test_cmdline.cpp │ │ └── test_utf.cpp ├── flow.cpp ├── flow.h ├── main.cpp ├── test_c_compilation.c ├── ui │ └── ui_common │ │ └── test_str_nformat.cpp ├── utils.cpp └── utils.h └── tools ├── app_launcher_unix ├── CMakeLists.txt ├── main.c └── readme.txt ├── docker_build_env ├── Dockerfile ├── include │ ├── build-bundle.sh │ └── qt-5.12.12-x11-client-leader-window.patch └── readme.txt ├── gen_test_history.py └── tessdata_info_gen ├── gen_c.py ├── gen_info.py ├── get_data_list.py └── readme.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | po/dpscreenocr.pot 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 3.22 is the version available in Ubuntu 22.04. 2 | cmake_minimum_required(VERSION 3.22) 3 | 4 | project(dpScreenOCR) 5 | 6 | set(DPSO_UI "qt" CACHE STRING "UI to use: qt or none") 7 | set_property(CACHE DPSO_UI PROPERTY STRINGS qt none) 8 | option(DPSO_GEN_HTML_MANUAL "Generate HTML manual using Pandoc" YES) 9 | option(DPSO_BUILD_EXAMPLE "Build example" NO) 10 | option(DPSO_BUILD_TESTS "Build tests" NO) 11 | 12 | set(APP_NAME "dpScreenOCR") 13 | set(APP_FILE_NAME "dpscreenocr") 14 | 15 | set(APP_VERSION_MAJOR "1") 16 | set(APP_VERSION_MINOR "5") 17 | set(APP_VERSION_PATCH "0") 18 | set(APP_VERSION 19 | "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}") 20 | 21 | set(APP_DESCRIPTION "Program to recognize text on screen") 22 | 23 | set(APP_AUTHOR "Daniel Plakhotich") 24 | set(APP_COPYRIGHT_YEAR "2019-2025") 25 | set(APP_URL "https://danpla.github.io/dpscreenocr/") 26 | 27 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") 28 | 29 | include(fixup_gnu_install_dirs) 30 | 31 | if(DPSO_BUILD_EXAMPLE) 32 | add_subdirectory(src/dpso_example) 33 | endif() 34 | 35 | if(DPSO_UI STREQUAL "qt") 36 | add_subdirectory(src/ui/qt) 37 | elseif(NOT DPSO_UI STREQUAL "none") 38 | message(FATAL_ERROR "Unknown UI \"${DPSO_UI}\"") 39 | endif() 40 | 41 | if(DPSO_BUILD_TESTS) 42 | add_subdirectory(tests) 43 | endif() 44 | 45 | include(install) 46 | include(uninstall) 47 | include(dist) 48 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2025 Daniel Plakhotich 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgement in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dpScreenOCR 2 | 3 | dpScreenOCR is a program to recognize text on the screen. 4 | 5 | https://danpla.github.io/dpscreenocr 6 | 7 | 8 | ## Docs 9 | 10 | * [Changelog](doc/changelog.txt) 11 | * [User manual](doc/manual.md) 12 | * Building instructions 13 | * [Unix-like systems](doc/building-unix.txt) 14 | * [Windows (MSYS2)](doc/building-windows-msys2.txt) 15 | 16 | 17 | ## Contributing 18 | 19 | * [Translate](https://hosted.weblate.org/engage/dpscreenocr/) 20 | the program into your language 21 | * Create a package for your system 22 | -------------------------------------------------------------------------------- /cmake/cmake_uninstall.cmake.in: -------------------------------------------------------------------------------- 1 | # Based on: 2 | # https://gitlab.kitware.com/cmake/community/wikis/FAQ#can-i-do-make-uninstall-with-cmake 3 | 4 | if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") 5 | message( 6 | FATAL_ERROR 7 | "@CMAKE_BINARY_DIR@/install_manifest.txt does not exist") 8 | endif() 9 | 10 | file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" FILES) 11 | string(REPLACE "\n" ";" FILES "${FILES}") 12 | 13 | foreach(FILE ${FILES}) 14 | set(FILE_PATH "$ENV{DESTDIR}${FILE}") 15 | 16 | if(NOT IS_SYMLINK "${FILE_PATH}" AND NOT EXISTS "${FILE_PATH}") 17 | message(STATUS "Does not exist: ${FILE_PATH}") 18 | continue() 19 | endif() 20 | 21 | message(STATUS "Uninstalling: ${FILE_PATH}") 22 | 23 | execute_process( 24 | COMMAND "@CMAKE_COMMAND@" -E rm "${FILE_PATH}" 25 | COMMAND_ERROR_IS_FATAL ANY) 26 | endforeach() 27 | -------------------------------------------------------------------------------- /cmake/compile_po.cmake: -------------------------------------------------------------------------------- 1 | include(get_linguas) 2 | 3 | find_program(MSGFMT_EXE msgfmt REQUIRED) 4 | 5 | # The function compiles PO files for languages listed in po/LINGUAS, 6 | # writing MOs to DST_DIR. 7 | # 8 | # The full path of a MO file will be: 9 | # 10 | # DST_DIR/{LANGUAGE}/LC_MESSAGES/${MO_NAME}.mo 11 | function(compile_po DST_DIR MO_NAME) 12 | set(MO_FILES) 13 | get_linguas(LANGS) 14 | foreach(LANG ${LANGS}) 15 | set(PO_FILE "${CMAKE_SOURCE_DIR}/po/${LANG}.po") 16 | set(MO_DIR "${DST_DIR}/${LANG}/LC_MESSAGES") 17 | set(MO_FILE "${MO_DIR}/${MO_NAME}.mo") 18 | 19 | add_custom_command( 20 | OUTPUT "${MO_FILE}" 21 | # msgfmt doesn't create intermediate directories. 22 | COMMAND "${CMAKE_COMMAND}" -E make_directory "${MO_DIR}" 23 | COMMAND "${MSGFMT_EXE}" -o "${MO_FILE}" "${PO_FILE}" 24 | DEPENDS "${PO_FILE}" 25 | VERBATIM) 26 | 27 | list(APPEND MO_FILES "${MO_FILE}") 28 | endforeach() 29 | 30 | add_custom_target(mo_files ALL DEPENDS ${MO_FILES}) 31 | endfunction() 32 | -------------------------------------------------------------------------------- /cmake/dist.cmake: -------------------------------------------------------------------------------- 1 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${APP_DESCRIPTION}") 2 | 3 | set(CPACK_PACKAGE_VERSION_MAJOR "${APP_VERSION_MAJOR}") 4 | set(CPACK_PACKAGE_VERSION_MINOR "${APP_VERSION_MINOR}") 5 | set(CPACK_PACKAGE_VERSION_PATCH "${APP_VERSION_PATCH}") 6 | set(CPACK_PACKAGE_VERSION "${APP_VERSION}") 7 | 8 | set(CPACK_PACKAGE_VENDOR "${APP_AUTHOR}") 9 | set(CPACK_PACKAGE_HOMEPAGE_URL "${APP_URL}") 10 | 11 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE.txt") 12 | 13 | if(UNIX AND NOT APPLE) 14 | include(dist_unix_bundle) 15 | elseif(WIN32) 16 | include(dist_windows_iss) 17 | endif() 18 | 19 | include(CPack) 20 | -------------------------------------------------------------------------------- /cmake/dist_unix_bundle.cmake: -------------------------------------------------------------------------------- 1 | set(BUNDLE_BUILD_SCRIPT "${CMAKE_BINARY_DIR}/build_bundle.cmake") 2 | 3 | configure_file( 4 | "${CMAKE_CURRENT_LIST_DIR}/dist_unix_bundle_script.cmake.in" 5 | "${BUNDLE_BUILD_SCRIPT}" 6 | @ONLY) 7 | 8 | string(TOLOWER "${CMAKE_SYSTEM_NAME}" OS_NAME) 9 | set(BUNDLE_DIR_NAME 10 | "${APP_NAME}-${APP_VERSION}-${OS_NAME}-${CMAKE_SYSTEM_PROCESSOR}") 11 | 12 | set(BUNDLE_BUILD_DIR "${CMAKE_BINARY_DIR}/bundle_build") 13 | set(BUNDLE_DIR "${BUNDLE_BUILD_DIR}/${BUNDLE_DIR_NAME}") 14 | 15 | # Note that we intentionally use targets that are always out of date, 16 | # as CMake does not allow using the ALL target as a dependency. 17 | 18 | add_custom_target( 19 | bundle 20 | COMMAND 21 | "${CMAKE_COMMAND}" 22 | -D "BUNDLE_BUILD_DIR=${BUNDLE_BUILD_DIR}" 23 | -D "BUNDLE_DIR=${BUNDLE_DIR}" 24 | -P "${BUNDLE_BUILD_SCRIPT}" 25 | VERBATIM) 26 | 27 | set(BUNDLE_ARCHIVE_NAME "${BUNDLE_DIR_NAME}.tar.xz") 28 | 29 | add_custom_target( 30 | bundle_archive 31 | COMMENT "Creating ${BUNDLE_ARCHIVE_NAME}" 32 | COMMAND 33 | "${CMAKE_COMMAND}" -E 34 | tar cJ 35 | "${CMAKE_BINARY_DIR}/${BUNDLE_ARCHIVE_NAME}" 36 | "${BUNDLE_DIR_NAME}" 37 | WORKING_DIRECTORY "${BUNDLE_BUILD_DIR}" 38 | VERBATIM) 39 | 40 | add_dependencies(bundle_archive bundle) 41 | -------------------------------------------------------------------------------- /cmake/fixup_gnu_install_dirs.cmake: -------------------------------------------------------------------------------- 1 | # By default, CMAKE_INSTALL_DOCDIR uses PROJECT_NAME, which is in 2 | # title case. We don't depend on PROJECT_NAME and use APP_FILE_NAME 3 | # instead. 4 | # 5 | # Note that we only overwrite the default CMAKE_INSTALL_DOCDIR, i.e. 6 | # when it's not set explicitly. 7 | 8 | if(UNIX AND NOT APPLE AND NOT CMAKE_INSTALL_DOCDIR) 9 | include(GNUInstallDirs) 10 | set(CMAKE_INSTALL_DOCDIR 11 | "${CMAKE_INSTALL_DATADIR}/doc/${APP_FILE_NAME}") 12 | endif() 13 | -------------------------------------------------------------------------------- /cmake/gen_desktop_entry.cmake: -------------------------------------------------------------------------------- 1 | find_program(MSGFMT_EXE msgfmt REQUIRED) 2 | 3 | # Generates a desktop entry "${APP_FILE_NAME}.desktop" in DST_DIR. 4 | function(gen_desktop_entry DST_DIR) 5 | set(ENTRY_NAME "${APP_FILE_NAME}.desktop") 6 | 7 | set(SRC_ENTRY_PATH "${CMAKE_SOURCE_DIR}/data/${ENTRY_NAME}") 8 | set(DST_ENTRY_PATH "${DST_DIR}/${ENTRY_NAME}") 9 | 10 | add_custom_command( 11 | OUTPUT "${DST_ENTRY_PATH}" 12 | # msgfmt doesn't create intermediate directories. 13 | COMMAND "${CMAKE_COMMAND}" -E make_directory "${DST_DIR}" 14 | COMMAND 15 | "${MSGFMT_EXE}" 16 | --desktop 17 | # Disable default keywords since we only want "Comment" to 18 | # be translated. As of version 0.21, msgfmt has two bugs: 19 | # -k is not a recognized option, and --keyword without 20 | # argument doesn't work (so we use an explicit empty 21 | # string as a workaround). 22 | --keyword= 23 | --keyword=Comment 24 | "--template=${SRC_ENTRY_PATH}" 25 | -d "${CMAKE_SOURCE_DIR}/po" 26 | -o "${DST_ENTRY_PATH}" 27 | DEPENDS "${SRC_ENTRY_PATH}" 28 | VERBATIM) 29 | 30 | add_custom_target(desktop_entry ALL DEPENDS "${DST_ENTRY_PATH}") 31 | endfunction() 32 | -------------------------------------------------------------------------------- /cmake/get_linguas.cmake: -------------------------------------------------------------------------------- 1 | # Get the list of languages from po/LINGUAS. 2 | function(get_linguas LANGUAGES) 3 | file( 4 | STRINGS "${CMAKE_SOURCE_DIR}/po/LINGUAS" LANGS 5 | REGEX "^[^#].*") 6 | 7 | set(${LANGUAGES} ${LANGS} PARENT_SCOPE) 8 | endfunction() 9 | -------------------------------------------------------------------------------- /cmake/install.cmake: -------------------------------------------------------------------------------- 1 | if(UNIX AND NOT APPLE) 2 | include(install_unix) 3 | elseif(WIN32) 4 | include(install_windows) 5 | endif() 6 | -------------------------------------------------------------------------------- /cmake/install_windows_dlls.cmake.in: -------------------------------------------------------------------------------- 1 | file(GLOB DLLS "@CMAKE_BINARY_DIR@/*.dll") 2 | file(INSTALL FILES ${DLLS} DESTINATION "${CMAKE_INSTALL_PREFIX}") 3 | -------------------------------------------------------------------------------- /cmake/install_windows_msys2.cmake: -------------------------------------------------------------------------------- 1 | # Instructions specific to MSYS2/MinGW. 2 | 3 | configure_file( 4 | "${CMAKE_CURRENT_LIST_DIR}/install_windows_msys2_copy_dlls.cmake.in" 5 | "${CMAKE_BINARY_DIR}/install_windows_msys2_copy_dlls.cmake" 6 | @ONLY) 7 | 8 | add_custom_target( 9 | copy_dlls 10 | ALL 11 | COMMENT "Copying DLLs" 12 | COMMAND 13 | "${CMAKE_COMMAND}" -P 14 | "${CMAKE_BINARY_DIR}/install_windows_msys2_copy_dlls.cmake" 15 | VERBATIM) 16 | 17 | add_dependencies(copy_dlls "${APP_FILE_NAME}_${DPSO_UI}") 18 | 19 | include(tesseract_utils) 20 | get_tesseract_data_dir_name(TESSERACT_DATA_DIR_NAME) 21 | if(TESSERACT_DATA_DIR_NAME) 22 | copy_tessdata( 23 | "$ENV{MINGW_PREFIX}/share/tessdata" 24 | "${CMAKE_BINARY_DIR}/${TESSERACT_DATA_DIR_NAME}" 25 | LANGUAGES eng 26 | OPTIONAL) 27 | endif() 28 | 29 | if(DPSO_UI STREQUAL "qt") 30 | include(qt_utils) 31 | 32 | copy_qt_windows_plugins( 33 | "$ENV{MINGW_PREFIX}/share/qt${DPSO_QT_VERSION}/plugins" 34 | "${CMAKE_BINARY_DIR}/qt${DPSO_QT_VERSION}/plugins") 35 | 36 | include(get_linguas) 37 | get_linguas(LANGS) 38 | 39 | copy_qt_translations( 40 | "$ENV{MINGW_PREFIX}/share/qt${DPSO_QT_VERSION}/translations" 41 | "${CMAKE_BINARY_DIR}/qt${DPSO_QT_VERSION}/translations" 42 | LANGUAGES ${LANGS} 43 | COMPONENTS qt qtbase) 44 | endif() 45 | -------------------------------------------------------------------------------- /cmake/install_windows_msys2_copy_dlls.cmake.in: -------------------------------------------------------------------------------- 1 | file( 2 | GET_RUNTIME_DEPENDENCIES 3 | EXECUTABLES "@CMAKE_BINARY_DIR@/@APP_FILE_NAME@.exe" 4 | DIRECTORIES "$ENV{MINGW_PREFIX}/bin" 5 | # As of version 3.23, CMake doesn't seem to handle Windows API 6 | # sets, so we filter them manually via PRE_EXCLUDE_REGEXES. 7 | # https://docs.microsoft.com/en-us/windows/win32/apiindex/windows-apisets 8 | # https://gitlab.kitware.com/cmake/cmake/-/issues/22006 9 | PRE_EXCLUDE_REGEXES "api-ms-.*" "ext-ms-.*" 10 | POST_INCLUDE_REGEXES "^$ENV{MINGW_PREFIX}/bin/.*" 11 | POST_EXCLUDE_REGEXES ".*" 12 | RESOLVED_DEPENDENCIES_VAR RESOLVED_DEPENDENCIES 13 | UNRESOLVED_DEPENDENCIES_VAR UNRESOLVED_DEPENDENCIES) 14 | 15 | if(UNRESOLVED_DEPENDENCIES) 16 | message( 17 | SEND_ERROR 18 | "Unresolved MSYS2 dependencies: ${UNRESOLVED_DEPENDENCIES}") 19 | endif() 20 | 21 | file( 22 | COPY ${RESOLVED_DEPENDENCIES} 23 | DESTINATION "@CMAKE_BINARY_DIR@") 24 | -------------------------------------------------------------------------------- /cmake/line_endings_conversion.cmake: -------------------------------------------------------------------------------- 1 | # Convert line endings to NEWLINE_STYLE, which can be any value 2 | # accepted by the same option of file(GENERATE). 3 | function(convert_line_endings IN_FILE OUT_FILE NEWLINE_STYLE) 4 | file( 5 | GENERATE 6 | OUTPUT "${OUT_FILE}" 7 | INPUT "${IN_FILE}" 8 | NEWLINE_STYLE "${NEWLINE_STYLE}") 9 | endfunction() 10 | -------------------------------------------------------------------------------- /cmake/uninstall.cmake: -------------------------------------------------------------------------------- 1 | if(UNIX AND NOT APPLE) 2 | include(uninstall_unix) 3 | endif() 4 | -------------------------------------------------------------------------------- /cmake/uninstall_unix.cmake: -------------------------------------------------------------------------------- 1 | if(NOT TARGET uninstall) 2 | configure_file( 3 | "${CMAKE_CURRENT_LIST_DIR}/cmake_uninstall.cmake.in" 4 | "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" 5 | @ONLY) 6 | 7 | add_custom_target( 8 | uninstall 9 | COMMAND 10 | "${CMAKE_COMMAND}" -P 11 | "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") 12 | endif() 13 | -------------------------------------------------------------------------------- /data/dpscreenocr.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=dpScreenOCR 4 | Exec=dpscreenocr 5 | Icon=dpscreenocr 6 | Categories=Graphics;OCR; 7 | SingleMainWindow=true 8 | Terminal=false 9 | Comment=Program to recognize text on screen 10 | -------------------------------------------------------------------------------- /data/icons/dpscreenocr.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/dpscreenocr.ico -------------------------------------------------------------------------------- /data/icons/sizes/128/dpscreenocr-busy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/128/dpscreenocr-busy.png -------------------------------------------------------------------------------- /data/icons/sizes/128/dpscreenocr-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/128/dpscreenocr-error.png -------------------------------------------------------------------------------- /data/icons/sizes/128/dpscreenocr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/128/dpscreenocr.png -------------------------------------------------------------------------------- /data/icons/sizes/16/dpscreenocr-busy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/16/dpscreenocr-busy.png -------------------------------------------------------------------------------- /data/icons/sizes/16/dpscreenocr-busy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /data/icons/sizes/16/dpscreenocr-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/16/dpscreenocr-error.png -------------------------------------------------------------------------------- /data/icons/sizes/16/dpscreenocr-error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /data/icons/sizes/16/dpscreenocr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/16/dpscreenocr.png -------------------------------------------------------------------------------- /data/icons/sizes/16/dpscreenocr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /data/icons/sizes/24/dpscreenocr-busy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/24/dpscreenocr-busy.png -------------------------------------------------------------------------------- /data/icons/sizes/24/dpscreenocr-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/24/dpscreenocr-error.png -------------------------------------------------------------------------------- /data/icons/sizes/24/dpscreenocr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/24/dpscreenocr.png -------------------------------------------------------------------------------- /data/icons/sizes/32/dpscreenocr-busy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/32/dpscreenocr-busy.png -------------------------------------------------------------------------------- /data/icons/sizes/32/dpscreenocr-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/32/dpscreenocr-error.png -------------------------------------------------------------------------------- /data/icons/sizes/32/dpscreenocr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/32/dpscreenocr.png -------------------------------------------------------------------------------- /data/icons/sizes/48/dpscreenocr-busy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/48/dpscreenocr-busy.png -------------------------------------------------------------------------------- /data/icons/sizes/48/dpscreenocr-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/48/dpscreenocr-error.png -------------------------------------------------------------------------------- /data/icons/sizes/48/dpscreenocr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/48/dpscreenocr.png -------------------------------------------------------------------------------- /data/icons/sizes/64/dpscreenocr-busy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/64/dpscreenocr-busy.png -------------------------------------------------------------------------------- /data/icons/sizes/64/dpscreenocr-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/64/dpscreenocr-error.png -------------------------------------------------------------------------------- /data/icons/sizes/64/dpscreenocr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/icons/sizes/64/dpscreenocr.png -------------------------------------------------------------------------------- /data/icons/sizes/scalable/dpscreenocr-busy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /data/icons/sizes/scalable/dpscreenocr-error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /data/icons/sizes/scalable/dpscreenocr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /data/sounds/done.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/data/sounds/done.wav -------------------------------------------------------------------------------- /dist/windows/iss/inno_setup_config.isi.in: -------------------------------------------------------------------------------- 1 | #cmakedefine APP_AUTHOR "@APP_AUTHOR@" 2 | #cmakedefine APP_COPYRIGHT_YEAR "@APP_COPYRIGHT_YEAR@" 3 | #cmakedefine APP_FILE_NAME "@APP_FILE_NAME@" 4 | #cmakedefine APP_NAME "@APP_NAME@" 5 | #cmakedefine APP_SOURCE_DIR "@APP_SOURCE_DIR@" 6 | #cmakedefine APP_TESSERACT_VERSION_MAJOR "@APP_TESSERACT_VERSION_MAJOR@" 7 | #cmakedefine APP_UI "@APP_UI@" 8 | #cmakedefine APP_URL "@APP_URL@" 9 | #cmakedefine APP_VERSION "@APP_VERSION@" 10 | #cmakedefine01 APP_IS_64_BIT 11 | -------------------------------------------------------------------------------- /dist/windows/iss/wizard.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/dist/windows/iss/wizard.bmp -------------------------------------------------------------------------------- /dist/windows/iss/wizard_small.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danpla/dpscreenocr/6b365f7ffdda4f17ec97459395d221222d358726/dist/windows/iss/wizard_small.bmp -------------------------------------------------------------------------------- /doc/manual-data/manual.css: -------------------------------------------------------------------------------- 1 | body { 2 | max-width: 36em; 3 | margin: 0 auto; 4 | padding: 0 1em 0; 5 | font-family: sans-serif; 6 | line-height: 1.6; 7 | } 8 | 9 | #TOC ul { 10 | list-style-type: none; 11 | } 12 | 13 | code { 14 | font-family: monospace; 15 | background: #ddd; 16 | padding: 0.1em 0.2em; 17 | white-space: pre-wrap; 18 | } 19 | 20 | pre code { 21 | display: block; 22 | padding: 0.8em 1em; 23 | } 24 | 25 | img { 26 | max-width: 100%; 27 | } 28 | -------------------------------------------------------------------------------- /doc/manual-data/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | $for(author-meta)$ 8 | 9 | $endfor$ 10 | $if(date-meta)$ 11 | 12 | $endif$ 13 | $if(keywords)$ 14 | 15 | $endif$ 16 | $if(title-prefix)$$title-prefix$ – $endif$$pagetitle$ 17 | $if(highlighting-css)$ 18 | 21 | $endif$ 22 | $for(css)$ 23 | 24 | $endfor$ 25 | $if(math)$ 26 | $math$ 27 | $endif$ 28 | $for(header-includes)$ 29 | $header-includes$ 30 | $endfor$ 31 | 32 | 33 | $for(include-before)$ 34 | $include-before$ 35 | $endfor$ 36 | $if(title)$ 37 |
38 |

$title$

39 | $if(subtitle)$ 40 |

$subtitle$

41 | $endif$ 42 | $for(author)$ 43 |

$author$

44 | $endfor$ 45 | $if(date)$ 46 |

$date$

47 | $endif$ 48 |
49 | $endif$ 50 | $if(toc)$ 51 | 57 | $endif$ 58 | $body$ 59 | $for(include-after)$ 60 | $include-after$ 61 | $endfor$ 62 | 63 | 64 | -------------------------------------------------------------------------------- /doc/manual-metadata.yaml.in: -------------------------------------------------------------------------------- 1 | --- 2 | lang: en 3 | title: dpScreenOCR User Manual 4 | subtitle: Version @APP_VERSION@ 5 | toc-title: Table of contents 6 | --- 7 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | # Keep this file sorted alphabetically, one language code per line. 2 | bg 3 | ca 4 | de 5 | es 6 | fr 7 | hr 8 | nb_NO 9 | pa_PK 10 | pl 11 | pt_BR 12 | ru 13 | tr 14 | uk 15 | zh_CN 16 | -------------------------------------------------------------------------------- /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | src/dpso_ocr/engine/tesseract/lang_names.cpp 2 | src/ui/qt/about.cpp 3 | src/ui/qt/action_chooser.cpp 4 | src/ui/qt/history.cpp 5 | src/ui/qt/hotkey_editor.cpp 6 | src/ui/qt/lang_browser.cpp 7 | src/ui/qt/lang_manager/install_progress_dialog.cpp 8 | src/ui/qt/lang_manager/lang_list.cpp 9 | src/ui/qt/lang_manager/lang_list_sort_filter_proxy.cpp 10 | src/ui/qt/lang_manager/lang_manager.cpp 11 | src/ui/qt/lang_manager/lang_manager_page.cpp 12 | src/ui/qt/lang_manager/lang_op_status_error.cpp 13 | src/ui/qt/main.cpp 14 | src/ui/qt/main_window.cpp 15 | src/ui/qt/update_checker.cpp 16 | src/ui/qt/utils.cpp 17 | -------------------------------------------------------------------------------- /src/dpso_example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | project(dpso-example) 4 | 5 | add_executable(dpso_example main.c) 6 | 7 | set_target_properties( 8 | dpso_example PROPERTIES 9 | C_STANDARD 99 10 | C_STANDARD_REQUIRED YES 11 | C_EXTENSIONS NO 12 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") 13 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" 14 | OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 15 | target_compile_options( 16 | dpso_example 17 | PRIVATE -Wall -Wextra -pedantic -Wstrict-prototypes) 18 | endif() 19 | 20 | if(NOT TARGET dpso_ocr) 21 | add_subdirectory(../dpso_ocr "${CMAKE_BINARY_DIR}/src/dpso_ocr") 22 | endif() 23 | 24 | if(NOT TARGET dpso_sys) 25 | add_subdirectory(../dpso_sys "${CMAKE_BINARY_DIR}/src/dpso_sys") 26 | endif() 27 | 28 | if(NOT TARGET dpso_utils) 29 | add_subdirectory( 30 | ../dpso_utils "${CMAKE_BINARY_DIR}/src/dpso_utils") 31 | endif() 32 | 33 | target_link_libraries(dpso_example dpso_ocr dpso_sys dpso_utils) 34 | -------------------------------------------------------------------------------- /src/dpso_ext/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | project(dpso_ext) 4 | 5 | add_library( 6 | dpso_ext 7 | 8 | cfg.cpp 9 | cfg_ext.cpp 10 | history.cpp 11 | history_export.cpp) 12 | 13 | if(UNIX AND NOT APPLE) 14 | target_sources( 15 | dpso_ext 16 | PRIVATE 17 | user_dirs_unix_xdg.cpp) 18 | elseif(WIN32) 19 | target_sources( 20 | dpso_ext 21 | PRIVATE 22 | user_dirs_windows.cpp) 23 | else() 24 | message(FATAL_ERROR "${CMAKE_SYSTEM_NAME} is not supported") 25 | endif() 26 | 27 | set_target_properties( 28 | dpso_ext PROPERTIES 29 | CXX_STANDARD 17 30 | CXX_STANDARD_REQUIRED YES 31 | CXX_EXTENSIONS NO 32 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") 33 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" 34 | OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 35 | target_compile_options(dpso_ext PRIVATE -Wall -Wextra -pedantic) 36 | endif() 37 | 38 | target_include_directories(dpso_ext PRIVATE . PUBLIC ..) 39 | 40 | if(NOT TARGET dpso_ocr) 41 | add_subdirectory(../dpso_ocr "${CMAKE_BINARY_DIR}/src/dpso_ocr") 42 | endif() 43 | 44 | if(NOT TARGET dpso_sys) 45 | add_subdirectory(../dpso_sys "${CMAKE_BINARY_DIR}/src/dpso_sys") 46 | endif() 47 | 48 | if(NOT TARGET dpso_utils) 49 | add_subdirectory( 50 | ../dpso_utils "${CMAKE_BINARY_DIR}/src/dpso_utils") 51 | endif() 52 | 53 | target_link_libraries( 54 | dpso_ext PUBLIC dpso_ocr dpso_sys PRIVATE dpso_utils) 55 | -------------------------------------------------------------------------------- /src/dpso_ext/dpso_ext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cfg.h" 4 | #include "cfg_ext.h" 5 | #include "history.h" 6 | #include "history_export.h" 7 | #include "user_dirs.h" 8 | -------------------------------------------------------------------------------- /src/dpso_ext/user_dirs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | typedef enum { 10 | DpsoUserDirConfig, 11 | DpsoUserDirData 12 | } DpsoUserDir; 13 | 14 | 15 | /** 16 | * Get a path to a user directory. 17 | * 18 | * appName becomes a subdirectory of the path. On failure, and sets an 19 | * error message (dpsoGetError()) and returns null. 20 | */ 21 | const char* dpsoGetUserDir(DpsoUserDir userDir, const char* appName); 22 | 23 | 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | -------------------------------------------------------------------------------- /src/dpso_ext/user_dirs_unix_xdg.cpp: -------------------------------------------------------------------------------- 1 | #include "user_dirs.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "dpso_utils/error_set.h" 7 | #include "dpso_utils/os.h" 8 | #include "dpso_utils/unix/xdg_dirs.h" 9 | 10 | 11 | using namespace dpso; 12 | 13 | 14 | static unix::XdgDir getXdgDir(DpsoUserDir userDir) 15 | { 16 | switch (userDir) { 17 | case DpsoUserDirConfig: 18 | return unix::XdgDir::configHome; 19 | case DpsoUserDirData: 20 | return unix::XdgDir::dataHome; 21 | } 22 | 23 | assert(false); 24 | return {}; 25 | } 26 | 27 | 28 | const char* dpsoGetUserDir(DpsoUserDir userDir, const char* appName) 29 | { 30 | static std::string path; 31 | 32 | const auto xdgDir = getXdgDir(userDir); 33 | 34 | try { 35 | path = unix::getXdgDirPath(xdgDir); 36 | } catch (os::Error& e) { 37 | setError("unix::getXdgDirPath({}): {}", xdgDir, e.what()); 38 | return {}; 39 | } 40 | 41 | path += '/'; 42 | path += appName; 43 | 44 | return path.c_str(); 45 | } 46 | -------------------------------------------------------------------------------- /src/dpso_ext/user_dirs_windows.cpp: -------------------------------------------------------------------------------- 1 | #include "user_dirs.h" 2 | 3 | #include 4 | 5 | #include 6 | #define WIN32_LEAN_AND_MEAN 7 | #include 8 | 9 | #include "dpso_utils/error_set.h" 10 | #include "dpso_utils/scope_exit.h" 11 | #include "dpso_utils/windows/error.h" 12 | #include "dpso_utils/windows/utf.h" 13 | 14 | 15 | using namespace dpso; 16 | 17 | 18 | const char* dpsoGetUserDir(DpsoUserDir userDir, const char* appName) 19 | { 20 | // FOLDERID_RoamingAppData is actually better path for config, but 21 | // we keep using FOLDERID_LocalAppData for backward compatibility. 22 | (void)userDir; 23 | 24 | wchar_t* appDataPathUtf16{}; 25 | // Docs say that we should call CoTaskMemFree() even if 26 | // SHGetKnownFolderPath() fails. 27 | const ScopeExit taskMemFree{ 28 | [&]{ CoTaskMemFree(appDataPathUtf16); }}; 29 | 30 | const auto hresult = SHGetKnownFolderPath( 31 | FOLDERID_LocalAppData, 32 | KF_FLAG_DONT_VERIFY, 33 | nullptr, 34 | &appDataPathUtf16); 35 | if (FAILED(hresult)) { 36 | setError( 37 | "SHGetKnownFolderPath(FOLDERID_LocalAppData, ...): {}", 38 | windows::getHresultMessage(hresult)); 39 | return {}; 40 | } 41 | 42 | static std::string path; 43 | try { 44 | path = windows::utf16ToUtf8(appDataPathUtf16); 45 | } catch (windows::CharConversionError& e) { 46 | setError("Can't convert path to UTF-8: {}", e.what()); 47 | return {}; 48 | } 49 | 50 | path += '\\'; 51 | path += appName; 52 | 53 | return path.c_str(); 54 | } 55 | -------------------------------------------------------------------------------- /src/dpso_img/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | project(dpso_img) 4 | 5 | add_library(dpso_img img.cpp ops.cpp pnm.cpp px_format.cpp) 6 | 7 | set_target_properties( 8 | dpso_img PROPERTIES 9 | CXX_STANDARD 17 10 | CXX_STANDARD_REQUIRED YES 11 | CXX_EXTENSIONS NO 12 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") 13 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" 14 | OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 15 | target_compile_options(dpso_img PRIVATE -Wall -Wextra -pedantic) 16 | endif() 17 | 18 | target_include_directories(dpso_img PRIVATE . PUBLIC ..) 19 | 20 | if(NOT TARGET dpso_utils) 21 | add_subdirectory( 22 | ../dpso_utils "${CMAKE_BINARY_DIR}/src/dpso_utils") 23 | endif() 24 | 25 | if(NOT TARGET stb_image_resize2) 26 | add_subdirectory( 27 | ../thirdparty/stb_image_resize2 28 | "${CMAKE_BINARY_DIR}/src/thirdparty/stb_image_resize2") 29 | endif() 30 | 31 | target_link_libraries(dpso_img PRIVATE dpso_utils stb_image_resize2) 32 | -------------------------------------------------------------------------------- /src/dpso_img/dpso_img.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "img.h" 4 | -------------------------------------------------------------------------------- /src/dpso_img/img.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "px_format.h" 6 | 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | 13 | typedef struct DpsoImg DpsoImg; 14 | 15 | 16 | /** 17 | * Create a new image. 18 | * 19 | * The pitch is the byte size of the image row, which should be at 20 | * least w * dpsoPxFormatGetBytesPerPx(pxFormat). You can use 0 to 21 | * automatically calculate and use the minimal pitch. 22 | * 23 | * On failure, sets an error message (dpsoGetError()) and returns 24 | * null. 25 | */ 26 | DpsoImg* dpsoImgCreate( 27 | DpsoPxFormat pxFormat, int w, int h, int pitch); 28 | 29 | 30 | void dpsoImgDelete(DpsoImg* img); 31 | 32 | 33 | DpsoPxFormat dpsoImgGetPxFormat(const DpsoImg* img); 34 | int dpsoImgGetWidth(const DpsoImg* img); 35 | int dpsoImgGetHeight(const DpsoImg* img); 36 | int dpsoImgGetPitch(const DpsoImg* img); 37 | 38 | const uint8_t* dpsoImgGetConstData(const DpsoImg* img); 39 | uint8_t* dpsoImgGetData(DpsoImg* img); 40 | 41 | 42 | #ifdef __cplusplus 43 | } 44 | 45 | 46 | #include 47 | 48 | 49 | namespace dpso::img { 50 | 51 | 52 | struct ImgDeleter { 53 | void operator()(DpsoImg* img) const 54 | { 55 | dpsoImgDelete(img); 56 | } 57 | }; 58 | 59 | 60 | using ImgUPtr = std::unique_ptr; 61 | 62 | 63 | } 64 | 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /src/dpso_img/ops.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "px_format.h" 6 | 7 | 8 | namespace dpso { 9 | 10 | 11 | class ProgressTracker; 12 | 13 | 14 | namespace img { 15 | 16 | 17 | template 18 | T getMaskRightShift(T mask) 19 | { 20 | if (mask == 0) 21 | return 0; 22 | 23 | T shift{}; 24 | for (; !(mask & 1); mask >>= 1) 25 | ++shift; 26 | 27 | return shift; 28 | } 29 | 30 | 31 | void toGray( 32 | const std::uint8_t* src, int srcPitch, DpsoPxFormat srcPxFormat, 33 | std::uint8_t* dst, int dstPitch, 34 | int w, int h); 35 | 36 | 37 | void resize( 38 | const std::uint8_t* src, int srcW, int srcH, int srcPitch, 39 | std::uint8_t* dst, int dstW, int dstH, int dstPitch); 40 | 41 | 42 | void unsharpMask( 43 | const std::uint8_t* src, int srcPitch, 44 | std::uint8_t* dst, int dstPitch, 45 | std::uint8_t* tmp, int tmpPitch, 46 | int w, int h, 47 | int radius, 48 | float amount, 49 | ProgressTracker* progressTracker = nullptr); 50 | 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/dpso_img/ops_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace dpso::img { 5 | 6 | 7 | enum class Axis { 8 | x, 9 | y 10 | }; 11 | 12 | 13 | constexpr Axis getOpposite(Axis axis) 14 | { 15 | return axis == Axis::x ? Axis::y : Axis::x; 16 | } 17 | 18 | 19 | template 20 | int getSize(int w, int h) 21 | { 22 | return axis == Axis::x ? w : h; 23 | } 24 | 25 | 26 | template 27 | class Line { 28 | public: 29 | Line(int idx, Px* data, int pitch) 30 | : line{data + (axis == Axis::x ? idx * pitch : idx)} 31 | , pitch{pitch} 32 | { 33 | } 34 | 35 | Px& operator[](int px) const 36 | { 37 | return line[axis == Axis::x ? px : px * pitch]; 38 | } 39 | private: 40 | Px* line; 41 | int pitch; 42 | }; 43 | 44 | 45 | template 46 | auto makeLine(int idx, Px* data, int pitch) 47 | { 48 | return Line{idx, data, pitch}; 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/dpso_img/pnm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "px_format.h" 6 | 7 | 8 | namespace dpso::img { 9 | 10 | 11 | const char* getPnmExt(DpsoPxFormat pxFormat); 12 | 13 | 14 | void savePnm( 15 | const char* filePath, 16 | DpsoPxFormat pxFormat, 17 | const std::uint8_t* data, 18 | int w, 19 | int h, 20 | int pitch); 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/dpso_img/px_format.cpp: -------------------------------------------------------------------------------- 1 | #include "px_format.h" 2 | 3 | 4 | const char* dpsoPxFormatToStr(DpsoPxFormat pxFormat) 5 | { 6 | switch (pxFormat) { 7 | case DpsoPxFormatGrayscale: 8 | return "Grayscale"; 9 | case DpsoPxFormatRgb: 10 | return "RGB"; 11 | case DpsoPxFormatBgr: 12 | return "BGR"; 13 | case DpsoPxFormatRgba: 14 | return "RGBA"; 15 | case DpsoPxFormatBgra: 16 | return "BGRA"; 17 | case DpsoPxFormatArgb: 18 | return "ARGB"; 19 | case DpsoPxFormatAbgr: 20 | return "ABGR"; 21 | } 22 | 23 | return ""; 24 | } 25 | 26 | 27 | int dpsoPxFormatGetBytesPerPx(DpsoPxFormat pxFormat) 28 | { 29 | switch (pxFormat) { 30 | case DpsoPxFormatGrayscale: 31 | return 1; 32 | case DpsoPxFormatRgb: 33 | case DpsoPxFormatBgr: 34 | return 3; 35 | case DpsoPxFormatRgba: 36 | case DpsoPxFormatBgra: 37 | case DpsoPxFormatArgb: 38 | case DpsoPxFormatAbgr: 39 | return 4; 40 | } 41 | 42 | return {}; 43 | } 44 | -------------------------------------------------------------------------------- /src/dpso_img/px_format.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | typedef enum { 10 | DpsoPxFormatGrayscale, 11 | DpsoPxFormatRgb, 12 | DpsoPxFormatBgr, 13 | DpsoPxFormatRgba, 14 | DpsoPxFormatBgra, 15 | DpsoPxFormatArgb, 16 | DpsoPxFormatAbgr, 17 | } DpsoPxFormat; 18 | 19 | 20 | const char* dpsoPxFormatToStr(DpsoPxFormat pxFormat); 21 | 22 | 23 | int dpsoPxFormatGetBytesPerPx(DpsoPxFormat pxFormat); 24 | 25 | 26 | #ifdef __cplusplus 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /src/dpso_intl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | project(dpso_intl) 4 | 5 | add_library( 6 | dpso_intl STATIC 7 | 8 | bindtextdomain_utf8.cpp 9 | helpers.cpp) 10 | 11 | set_target_properties( 12 | dpso_intl PROPERTIES 13 | CXX_STANDARD 11 14 | CXX_STANDARD_REQUIRED YES 15 | CXX_EXTENSIONS NO) 16 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" 17 | OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 18 | target_compile_options(dpso_intl PRIVATE -Wall -Wextra -pedantic) 19 | endif() 20 | 21 | target_include_directories(dpso_intl PRIVATE . PUBLIC ..) 22 | 23 | if(WIN32) 24 | # We need at least 0.21 for wbindtextdomain() 25 | find_package(Intl 0.21.0 REQUIRED) 26 | else() 27 | find_package(Intl REQUIRED) 28 | endif() 29 | 30 | target_include_directories(dpso_intl PUBLIC ${Intl_INCLUDE_DIRS}) 31 | target_link_libraries(dpso_intl PUBLIC ${Intl_LIBRARIES}) 32 | -------------------------------------------------------------------------------- /src/dpso_intl/bindtextdomain_utf8.cpp: -------------------------------------------------------------------------------- 1 | #include "bindtextdomain_utf8.h" 2 | 3 | #include 4 | 5 | 6 | #ifdef _WIN32 7 | 8 | #include 9 | 10 | #define WIN32_LEAN_AND_MEAN 11 | #include 12 | 13 | 14 | static std::wstring utf8ToUtf16(const char* utf8Str) 15 | { 16 | const auto sizeWithNull = MultiByteToWideChar( 17 | CP_UTF8, MB_ERR_INVALID_CHARS, utf8Str, -1, nullptr, 0); 18 | if (sizeWithNull <= 0) 19 | return {}; 20 | 21 | std::wstring result(sizeWithNull - 1, 0); 22 | 23 | if (!MultiByteToWideChar( 24 | CP_UTF8, MB_ERR_INVALID_CHARS, utf8Str, -1, 25 | &result[0], sizeWithNull)) 26 | return {}; 27 | 28 | return result; 29 | } 30 | 31 | 32 | void bindtextdomainUtf8(const char* domainName, const char* dirName) 33 | { 34 | // dirName can be null to return the previously set directory. Our 35 | // UTF-8 wrapper returns nothing, so skip this case. 36 | if (!dirName) 37 | return; 38 | 39 | wbindtextdomain(domainName, utf8ToUtf16(dirName).c_str()); 40 | } 41 | 42 | 43 | #else // _WIN32 44 | 45 | 46 | void bindtextdomainUtf8(const char* domainName, const char* dirName) 47 | { 48 | bindtextdomain(domainName, dirName); 49 | } 50 | 51 | 52 | #endif // !_WIN32 53 | -------------------------------------------------------------------------------- /src/dpso_intl/bindtextdomain_utf8.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | /** 10 | * bindtextdomain() that accepts dirName in UTF-8. 11 | * 12 | * This function is a wrapper that calls wbindtextdomain() on Windows 13 | * (requires libintl >= 0.21.0) and normal bindtextdomain() on other 14 | * platforms. It's mainly intended to support Windows older than 10; 15 | * since Windows 10 version 1903, the application manifest 16 | * () is a more robust and non-intrusive way to enable 17 | * Unicode support in all third-party libraries. 18 | * 19 | * The (w)bindtextdomain() documentation says that the returned string 20 | * is only invalidated if the function is called with the same domain 21 | * name. Emulating this behavior in our UTF-8 wrapper on Windows would 22 | * require maintaining a mapping of results from UTF-16 to UTF-8. This 23 | * complication is probably not worth the trouble, so the function 24 | * returns nothing. 25 | */ 26 | void bindtextdomainUtf8(const char* domainName, const char* dirName); 27 | 28 | 29 | #ifdef __cplusplus 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /src/dpso_intl/dpso_intl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "bindtextdomain_utf8.h" 6 | #include "helpers.h" 7 | -------------------------------------------------------------------------------- /src/dpso_intl/helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "helpers.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | 8 | static std::string getMsgCtxId(const char* msgCtx, const char* msgId) 9 | { 10 | return std::string{msgCtx} + '\004' + msgId; 11 | } 12 | 13 | 14 | const char* pgettext_aux(const char* msgCtxId, const char* msgId) 15 | { 16 | const auto* translated = gettext(msgCtxId); 17 | return translated == msgCtxId ? msgId : translated; 18 | } 19 | 20 | 21 | const char* pgettext_expr(const char* msgCtx, const char* msgId) 22 | { 23 | return pgettext_aux(getMsgCtxId(msgCtx, msgId).c_str(), msgId); 24 | } 25 | 26 | 27 | const char* npgettext_aux( 28 | const char* msgCtxId, const char* msgId1, const char* msgId2, 29 | unsigned long n) 30 | { 31 | const auto* translated = ngettext(msgCtxId, msgId2, n); 32 | if (translated == msgCtxId || translated == msgId2) 33 | return n == 1 ? msgId1 : msgId2; 34 | 35 | return translated; 36 | } 37 | 38 | 39 | const char* npgettext_expr( 40 | const char* msgCtx, const char* msgId1, const char* msgId2, 41 | unsigned long n) 42 | { 43 | return npgettext_aux( 44 | getMsgCtxId(msgCtx, msgId1).c_str(), msgId1, msgId2, n); 45 | } 46 | -------------------------------------------------------------------------------- /src/dpso_intl/helpers.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Helpers for libintl. 4 | * 5 | * This file provides implementations of functions from gettext.h, 6 | * but only those we actually use. 7 | */ 8 | 9 | #pragma once 10 | 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | 17 | #define pgettext(msgCtx, msgId) \ 18 | pgettext_aux(msgCtx "\004" msgId, msgId) 19 | 20 | 21 | const char* pgettext_aux(const char* msgCtxId, const char* msgId); 22 | 23 | 24 | const char* pgettext_expr(const char* msgCtx, const char* msgId); 25 | 26 | 27 | #define npgettext(msgCtx, msgId1, msgId2, N) \ 28 | npgettext_aux(msgCtx "\004" msgId1, msgId1, msgId2, N) 29 | 30 | 31 | const char* npgettext_aux( 32 | const char* msgCtxId, const char* msgId1, const char* msgId2, 33 | unsigned long n); 34 | 35 | 36 | const char* npgettext_expr( 37 | const char* msgCtx, const char* msgId1, const char* msgId2, 38 | unsigned long n); 39 | 40 | 41 | #ifdef __cplusplus 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /src/dpso_json/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | project(dpso_json) 4 | 5 | add_library(dpso_json json.cpp) 6 | 7 | set_target_properties( 8 | dpso_json PROPERTIES 9 | CXX_STANDARD 17 10 | CXX_STANDARD_REQUIRED YES 11 | CXX_EXTENSIONS NO 12 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") 13 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" 14 | OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 15 | target_compile_options(dpso_json PRIVATE -Wall -Wextra -pedantic) 16 | endif() 17 | 18 | find_package(PkgConfig REQUIRED) 19 | 20 | # json_string_length() was added in 2.7. 21 | pkg_search_module(JANSSON REQUIRED jansson>=2.7) 22 | 23 | if(NOT TARGET dpso_utils) 24 | add_subdirectory( 25 | ../dpso_utils "${CMAKE_BINARY_DIR}/src/dpso_utils") 26 | endif() 27 | 28 | target_include_directories( 29 | dpso_json PUBLIC .. PRIVATE . ${JANSSON_INCLUDE_DIRS}) 30 | 31 | target_link_libraries( 32 | dpso_json PRIVATE dpso_utils ${JANSSON_LIBRARIES}) 33 | -------------------------------------------------------------------------------- /src/dpso_net/download_file.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | namespace dpso::net { 9 | 10 | 11 | // If the file size is unknown (e.g. the server does not provide 12 | // the Content-Length header), the function is called with nullopt 13 | // totalSize. 14 | // 15 | // Returns false to terminate downloading and remove a partially 16 | // downloaded file. 17 | using DownloadProgressHandler = std::function< 18 | bool( 19 | std::int64_t curSize, 20 | std::optional totalSize)>; 21 | 22 | 23 | // The function will not download the file directly to filePath, but 24 | // instead will use a temporary file named as filePath, but with an 25 | // additional extension. Once the temporary file is downloaded, it's 26 | // renamed to filePath, silently rewriting an existing file, if any. 27 | // 28 | // The function does not create the directory chain for filePath. 29 | // 30 | // Throws net::Error. 31 | void downloadFile( 32 | const char* url, 33 | const char* userAgent, 34 | const char* filePath, 35 | const DownloadProgressHandler& progressHandler); 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/dpso_net/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::net { 7 | 8 | 9 | class Error : public std::runtime_error { 10 | using runtime_error::runtime_error; 11 | }; 12 | 13 | 14 | // The connection either could not be established or was terminated. 15 | // This error is intended as a hint to show the end user a more 16 | // friendly error message, such as "check your network connection and 17 | // try again". 18 | class ConnectionError : public Error { 19 | using Error::Error; 20 | }; 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/dpso_net/get_data.cpp: -------------------------------------------------------------------------------- 1 | #include "get_data.h" 2 | 3 | #include 4 | 5 | #include "dpso_utils/str.h" 6 | 7 | #include "error.h" 8 | #include "request.h" 9 | 10 | 11 | namespace dpso::net { 12 | 13 | 14 | std::string getData( 15 | const char* url, const char* userAgent, std::int64_t sizeLimit) 16 | { 17 | if (sizeLimit < 0) 18 | throw Error{"Size limit is < 0"}; 19 | 20 | auto response = makeGetRequest(url, userAgent); 21 | 22 | if (const auto size = response->getSize(); 23 | size && *size > sizeLimit) 24 | throw Error{str::format( 25 | "Response data size ({}) exceeds limit ({})", 26 | *size, sizeLimit)}; 27 | 28 | std::string result; 29 | 30 | char buf[16 * 1024]; 31 | while (true) { 32 | const auto numRead = response->read(buf, sizeof(buf)); 33 | if (numRead == 0) 34 | break; 35 | 36 | if (std::numeric_limits::max() - result.size() 37 | < numRead 38 | || static_cast(result.size() + numRead) 39 | > sizeLimit) 40 | throw Error{str::format( 41 | "Response data size exceeds limit ({})", sizeLimit)}; 42 | 43 | result.append(buf, numRead); 44 | } 45 | 46 | return result; 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/dpso_net/get_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | namespace dpso::net { 8 | 9 | 10 | // Since the function is intended to download relatively small pieces 11 | // of data like JSON responses from various web APIs, it has the 12 | // sizeLimit argument to set a sane size limit on the downloaded data. 13 | // If the response provides the Content-Length header, the limit is 14 | // checked early. At the same time, the function doesn't trust 15 | // Content-Length, and will also check the limit during downloading 16 | // regardless of the presence of the header. 17 | // 18 | // Throws net::Error. 19 | std::string getData( 20 | const char* url, 21 | const char* userAgent, 22 | std::int64_t sizeLimit = 4 * 1024 * 1024); 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/dpso_net/request.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | namespace dpso::net { 10 | 11 | 12 | class Response { 13 | public: 14 | virtual ~Response() = default; 15 | 16 | // Returns size of response data. This is a cached value of the 17 | // Content-Length header, if any. 18 | virtual std::optional getSize() const = 0; 19 | 20 | // Read up to dstSize bytes from the response. Returns 0 if the 21 | // end is reached. Throws net::Error on error. 22 | virtual std::size_t read(void* dst, std::size_t dstSize) = 0; 23 | }; 24 | 25 | 26 | // Make HTTP GET request. 27 | // 28 | // Throws net::Error on error, or on any HTTP response code other than 29 | // 200. 30 | std::unique_ptr makeGetRequest( 31 | const char* url, const char* userAgent); 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/dpso_net/request_curl_lib.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::net { 7 | 8 | 9 | class LibCurl { 10 | LibCurl(); 11 | public: 12 | // Throws net::Error if the curl library or one of its functions 13 | // cannot be loaded. 14 | static const LibCurl& get(); 15 | 16 | #define DECL_FN(NAME) const decltype(&::curl_ ## NAME) NAME 17 | 18 | DECL_FN(easy_cleanup); 19 | DECL_FN(easy_init); 20 | DECL_FN(easy_setopt); 21 | DECL_FN(easy_strerror); 22 | DECL_FN(global_cleanup); 23 | DECL_FN(global_init); 24 | DECL_FN(multi_add_handle); 25 | DECL_FN(multi_cleanup); 26 | DECL_FN(multi_info_read); 27 | DECL_FN(multi_init); 28 | DECL_FN(multi_perform); 29 | DECL_FN(multi_remove_handle); 30 | DECL_FN(multi_strerror); 31 | DECL_FN(multi_wait); 32 | 33 | #undef DECL_FN 34 | }; 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/dpso_ocr/data_lock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | namespace dpso::ocr { 9 | 10 | 11 | class DataLock { 12 | public: 13 | class DataLockedError : public std::runtime_error { 14 | using runtime_error::runtime_error; 15 | }; 16 | 17 | DataLock(); 18 | 19 | // Throws DataLockedError if data is already locked by another 20 | // DataLock. 21 | DataLock(const char* engineId, const char* dataDir); 22 | 23 | ~DataLock(); 24 | 25 | DataLock(const DataLock&) = delete; 26 | DataLock& operator=(const DataLock&) = delete; 27 | 28 | DataLock(DataLock&& other) noexcept; 29 | DataLock& operator=(DataLock&& other) noexcept; 30 | private: 31 | struct Impl; 32 | std::unique_ptr impl; 33 | }; 34 | 35 | 36 | class DataLockObserver { 37 | public: 38 | DataLockObserver(); 39 | DataLockObserver( 40 | const char* engineId, 41 | const char* dataDir, 42 | const std::function& beforeLockCreated, 43 | const std::function& afterLockRemoved); 44 | 45 | ~DataLockObserver(); 46 | 47 | DataLockObserver(const DataLockObserver&) = delete; 48 | DataLockObserver& operator=(const DataLockObserver&) = delete; 49 | 50 | DataLockObserver(DataLockObserver&& other) noexcept; 51 | DataLockObserver& operator=(DataLockObserver&& other) noexcept; 52 | 53 | bool getIsDataLocked() const; 54 | private: 55 | struct Impl; 56 | std::unique_ptr impl; 57 | }; 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/dpso_ocr/dpso_ocr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ocr.h" 4 | #include "engine.h" 5 | #include "lang_manager.h" 6 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine.cpp: -------------------------------------------------------------------------------- 1 | #include "engine.h" 2 | 3 | #include "engine/engine.h" 4 | 5 | 6 | using namespace dpso; 7 | 8 | 9 | int dpsoOcrGetNumEngines(void) 10 | { 11 | return ocr::Engine::getCount(); 12 | } 13 | 14 | 15 | void dpsoOcrGetEngineInfo(int idx, DpsoOcrEngineInfo* info) 16 | { 17 | if (idx < 0 18 | || static_cast(idx) 19 | >= ocr::Engine::getCount() 20 | || !info) 21 | return; 22 | 23 | const auto& internalInfo = ocr::Engine::get(idx).getInfo(); 24 | 25 | DpsoOcrEngineDataDirPreference dataDirPreference{}; 26 | switch (internalInfo.dataDirPreference) { 27 | case ocr::EngineInfo::DataDirPreference::noDataDir: 28 | dataDirPreference = DpsoOcrEngineDataDirPreferenceNoDataDir; 29 | break; 30 | case ocr::EngineInfo::DataDirPreference::preferDefault: 31 | dataDirPreference = 32 | DpsoOcrEngineDataDirPreferencePreferDefault; 33 | break; 34 | case ocr::EngineInfo::DataDirPreference::preferExplicit: 35 | dataDirPreference = 36 | DpsoOcrEngineDataDirPreferencePreferExplicit; 37 | break; 38 | } 39 | 40 | *info = { 41 | internalInfo.id.c_str(), 42 | internalInfo.name.c_str(), 43 | internalInfo.version.c_str(), 44 | dataDirPreference, 45 | internalInfo.hasLangManager}; 46 | } 47 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/engine.cpp: -------------------------------------------------------------------------------- 1 | #include "engine/engine.h" 2 | 3 | #include 4 | 5 | #include "engine/tesseract/engine.h" 6 | 7 | 8 | namespace dpso::ocr { 9 | 10 | 11 | static const Engine* const engines[]{ 12 | &tesseract::getEngine() 13 | }; 14 | 15 | 16 | const Engine& Engine::get(std::size_t idx) 17 | { 18 | return *engines[idx]; 19 | } 20 | 21 | 22 | std::size_t Engine::getCount() 23 | { 24 | return std::size(engines); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/engine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | namespace dpso::ocr { 9 | 10 | 11 | class LangManager; 12 | class Recognizer; 13 | 14 | 15 | // See DpsoOcrEngineInfo 16 | struct EngineInfo { 17 | std::string id; 18 | std::string name; 19 | std::string version; 20 | 21 | // See DpsoOcrEngineDataDirPreference 22 | enum class DataDirPreference { 23 | noDataDir, 24 | preferDefault, 25 | preferExplicit, 26 | }; 27 | 28 | DataDirPreference dataDirPreference; 29 | bool hasLangManager; 30 | }; 31 | 32 | 33 | class Engine { 34 | public: 35 | static const Engine& get(std::size_t idx); 36 | static std::size_t getCount(); 37 | 38 | virtual ~Engine() = default; 39 | 40 | virtual const EngineInfo& getInfo() const = 0; 41 | 42 | // Throws RecognizerError 43 | virtual std::unique_ptr createRecognizer( 44 | const char* dataDir) const = 0; 45 | 46 | // See dpsoOcrLangManagerCreate(). Throws LangManagerError. 47 | virtual std::unique_ptr createLangManager( 48 | const char* dataDir, 49 | const char* userAgent, 50 | const char* infoFileUrl) const = 0; 51 | }; 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::ocr { 7 | 8 | 9 | class Error : public std::runtime_error { 10 | using runtime_error::runtime_error; 11 | }; 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/lang_code_validator.cpp: -------------------------------------------------------------------------------- 1 | #include "engine/lang_code_validator.h" 2 | 3 | #include "dpso_utils/str.h" 4 | 5 | 6 | namespace dpso::ocr { 7 | 8 | 9 | static bool isValidLangCodeChar(char c) 10 | { 11 | return 12 | (c >= '0' && c <= '9') 13 | || (c >= 'a' && c <= 'z') 14 | || (c >= 'A' && c <= 'Z') 15 | || c == '.' 16 | || c == '-' 17 | || c == '_'; 18 | } 19 | 20 | 21 | void validateLangCode(const char* langCode) 22 | { 23 | if (!*langCode) 24 | throw LangCodeError{"Language code is empty"}; 25 | 26 | for (const auto* s = langCode; *s; ++s) 27 | if (!isValidLangCodeChar(*s)) 28 | throw LangCodeError{str::format( 29 | "Invalid character at index {}", s - langCode)}; 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/lang_code_validator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/error.h" 4 | 5 | 6 | namespace dpso::ocr { 7 | 8 | 9 | class LangCodeError : public Error { 10 | using Error::Error; 11 | }; 12 | 13 | 14 | // Throws LangCodeError. 15 | void validateLangCode(const char* langCode); 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/lang_manager_error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/error.h" 4 | 5 | 6 | namespace dpso::ocr { 7 | 8 | 9 | class LangManagerError : public Error { 10 | using Error::Error; 11 | }; 12 | 13 | 14 | // The connection either could not be established or was terminated. 15 | // This error is intended as a hint to show the end user a more 16 | // friendly error message, such as "check your network connection and 17 | // try again". 18 | class LangManagerNetworkConnectionError : public LangManagerError { 19 | using LangManagerError::LangManagerError; 20 | }; 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/recognizer_error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/error.h" 4 | 5 | 6 | namespace dpso::ocr { 7 | 8 | 9 | class RecognizerError : public Error { 10 | using Error::Error; 11 | }; 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/tesseract/engine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace dpso::ocr { 5 | 6 | 7 | class Engine; 8 | 9 | 10 | namespace tesseract { 11 | 12 | 13 | const Engine& getEngine(); 14 | 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/tesseract/lang_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "engine/lang_manager.h" 6 | 7 | 8 | namespace dpso::ocr::tesseract { 9 | 10 | 11 | bool hasLangManager(); 12 | 13 | 14 | std::unique_ptr createLangManager( 15 | const char* dataDir, 16 | const char* userAgent, 17 | const char* infoFileUrl); 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/tesseract/lang_manager_null.cpp: -------------------------------------------------------------------------------- 1 | // This file is used when the language manager is disabled at compile 2 | // time. 3 | 4 | #include "engine/tesseract/lang_manager.h" 5 | 6 | #include "engine/lang_manager_error.h" 7 | 8 | 9 | namespace dpso::ocr::tesseract { 10 | 11 | 12 | bool hasLangManager() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | std::unique_ptr createLangManager( 19 | const char* dataDir, 20 | const char* userAgent, 21 | const char* infoFileUrl) 22 | { 23 | (void)dataDir; 24 | (void)userAgent; 25 | (void)infoFileUrl; 26 | 27 | throw LangManagerError{"Language manager is not available"}; 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/tesseract/lang_names.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace dpso::ocr::tesseract { 5 | 6 | 7 | // Returns null if the language with the given code is not found. 8 | const char* getLangName(const char* langCode); 9 | 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/tesseract/lang_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | namespace dpso::ocr::tesseract { 8 | 9 | 10 | extern const char* const traineddataExt; 11 | 12 | 13 | // Returns true if the language should be ignored, e.g., when it's not 14 | // a real language but an auxiliary data, such as "equ" or "osd". 15 | bool isIgnoredLang(const char* lang); 16 | 17 | 18 | // Returns languages from TessBaseAPI::GetAvailableLanguagesAsVector, 19 | // excluding those that are ignored by isIgnoredLang(). 20 | // 21 | // Throws ocr::Error. 22 | std::vector getAvailableLangs(const char* dataDir); 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/tesseract/recognizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "engine/recognizer.h" 6 | 7 | 8 | namespace dpso::ocr::tesseract { 9 | 10 | 11 | std::unique_ptr createRecognizer(const char* dataDir); 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/dpso_ocr/engine/tesseract/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::ocr::tesseract { 7 | 8 | 9 | // Prettify text returned by Tesseract. 10 | // 11 | // The function will: 12 | // * Trim whitespace. 13 | // * Split fi and fl ligatures. 14 | // * Remove paragraphs consisting of a single space, which are 15 | // sometimes created when page segmentation is enabled. 16 | // 17 | // Returns the new length of the text. 18 | std::size_t prettifyText(char* text); 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/dpso_sound/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | project(dpso_sound) 4 | 5 | add_library(dpso_sound) 6 | 7 | if(UNIX AND NOT APPLE) 8 | target_sources( 9 | dpso_sound 10 | PRIVATE 11 | unix/lib/pulse.cpp 12 | unix/lib/sndfile.cpp 13 | unix/sndfile.cpp 14 | unix/sound_pulse.cpp) 15 | 16 | find_package(Threads REQUIRED) 17 | 18 | target_link_libraries( 19 | dpso_sound PRIVATE dl ${CMAKE_THREAD_LIBS_INIT}) 20 | elseif(WIN32) 21 | target_sources(dpso_sound PRIVATE sound_windows.cpp) 22 | target_link_libraries(dpso_sound PRIVATE winmm) 23 | else() 24 | target_sources(dpso_sound PRIVATE sound_null.cpp) 25 | endif() 26 | 27 | set_target_properties( 28 | dpso_sound PROPERTIES 29 | CXX_STANDARD 17 30 | CXX_STANDARD_REQUIRED YES 31 | CXX_EXTENSIONS NO 32 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") 33 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" 34 | OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 35 | target_compile_options(dpso_sound PRIVATE -Wall -Wextra -pedantic) 36 | endif() 37 | 38 | include(TestBigEndian) 39 | test_big_endian(IS_BIG_ENDIAN) 40 | 41 | target_compile_definitions( 42 | dpso_sound 43 | PRIVATE DPSO_SOUND_IS_BIG_ENDIAN=$) 44 | 45 | if(NOT TARGET dpso_utils) 46 | add_subdirectory( 47 | ../dpso_utils "${CMAKE_BINARY_DIR}/src/dpso_utils") 48 | endif() 49 | 50 | target_include_directories(dpso_sound PUBLIC .. PRIVATE .) 51 | -------------------------------------------------------------------------------- /src/dpso_sound/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::sound { 7 | 8 | 9 | class Error : public std::runtime_error { 10 | using runtime_error::runtime_error; 11 | }; 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/dpso_sound/format_info.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::sound { 7 | 8 | 9 | struct FormatInfo { 10 | const char* name; 11 | // The list of possible file extensions for the format. Each 12 | // extension has a leading period. 13 | std::vector extensions; 14 | }; 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/dpso_sound/sound.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "format_info.h" 7 | 8 | 9 | namespace dpso::sound { 10 | 11 | 12 | bool isAvailable(); 13 | 14 | 15 | // Return a path to a directory containing system sound files, or an 16 | // empty string if no such directory is defined for the current 17 | // platform. The function only returns a string and does not query the 18 | // file system to check if the directory actually exists. 19 | const char* getSystemSoundsDirPath(); 20 | 21 | 22 | // Return the list of audio file formats supported by Player. The list 23 | // will be empty if isAvailable() is false or on error. 24 | const std::vector& getSupportedFormats(); 25 | 26 | 27 | // The Player class is intended to play short notification sounds. 28 | class Player { 29 | public: 30 | // Throws sound:::Error. 31 | static std::unique_ptr create(const char* filePath); 32 | 33 | virtual ~Player() = default; 34 | 35 | // play() will stop a currently playing sound, even if it was 36 | // started by another Player instance. Throws sound:::Error. 37 | virtual void play() = 0; 38 | }; 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/dpso_sound/sound_null.cpp: -------------------------------------------------------------------------------- 1 | #include "sound.h" 2 | 3 | 4 | namespace dpso::sound { 5 | 6 | 7 | bool isAvailable() 8 | { 9 | return false; 10 | } 11 | 12 | 13 | const char* getSystemSoundsDirPath() 14 | { 15 | return ""; 16 | } 17 | 18 | 19 | const std::vector& getSupportedFormats() 20 | { 21 | static const std::vector result; 22 | return result; 23 | } 24 | 25 | 26 | std::unique_ptr Player::create(const char* filePath) 27 | { 28 | (void)filePath; 29 | 30 | throw Error{"Sound library is not available"}; 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/dpso_sound/unix/audio_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::sound { 7 | 8 | 9 | struct AudioData { 10 | int numChannels; 11 | int rate; 12 | std::vector samples; 13 | }; 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/dpso_sound/unix/lib/sndfile.cpp: -------------------------------------------------------------------------------- 1 | #include "unix/lib/sndfile.h" 2 | 3 | #include 4 | 5 | #include "dpso_utils/str.h" 6 | #include "dpso_utils/unix/dl.h" 7 | 8 | #include "error.h" 9 | 10 | 11 | namespace dpso::sound { 12 | namespace { 13 | 14 | 15 | unix::DlHandleUPtr loadLib(const char* soName) 16 | { 17 | if (auto* handle = dlopen(soName, RTLD_NOW | RTLD_LOCAL)) 18 | return unix::DlHandleUPtr{handle}; 19 | 20 | throw Error{str::format("Can't load {}: {}", soName, dlerror())}; 21 | } 22 | 23 | 24 | void* loadFn(const char* name) 25 | { 26 | static const auto* libName = "libsndfile.so.1"; 27 | static const auto dlHandle = loadLib(libName); 28 | 29 | if (auto* result = dlsym(dlHandle.get(), name)) 30 | return result; 31 | 32 | throw Error{str::format( 33 | "dlsym for \"{}\" from \"{}\": {}", 34 | name, libName, dlerror())}; 35 | } 36 | 37 | 38 | } 39 | 40 | 41 | const LibSndfile& LibSndfile::get() 42 | { 43 | static const LibSndfile lib; 44 | return lib; 45 | } 46 | 47 | 48 | #define LOAD_FN(NAME) NAME{(decltype(NAME))loadFn("sf_" #NAME)} 49 | 50 | 51 | LibSndfile::LibSndfile() 52 | : LOAD_FN(open) 53 | , LOAD_FN(command) 54 | , LOAD_FN(readf_short) 55 | , LOAD_FN(close) 56 | , LOAD_FN(strerror) 57 | { 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/dpso_sound/unix/sndfile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "format_info.h" 4 | #include "unix/audio_data.h" 5 | 6 | 7 | namespace dpso::sound::sndfile { 8 | 9 | 10 | // Implementation of sound::getSystemSoundsDirPath(). Doesn't throw. 11 | std::vector getSupportedFormats(); 12 | 13 | 14 | // Throws sound::Error. 15 | AudioData loadAudioData(const char* filePath); 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/backend.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "dpso_img/img.h" 6 | 7 | 8 | namespace dpso { 9 | 10 | 11 | struct Rect; 12 | 13 | 14 | namespace backend { 15 | 16 | 17 | class KeyManager; 18 | class Selection; 19 | class Screenshot; 20 | 21 | 22 | class Backend { 23 | public: 24 | // The concrete backend should provide definition of this method. 25 | // Throws BackendError. 26 | static std::unique_ptr create(); 27 | 28 | virtual ~Backend() = default; 29 | 30 | virtual KeyManager& getKeyManager() = 0; 31 | virtual Selection& getSelection() = 0; 32 | 33 | // The method will clamp the rect to screen. Throws 34 | // ScreenshotError. 35 | virtual img::ImgUPtr takeScreenshot(const Rect& rect) = 0; 36 | 37 | virtual void update() = 0; 38 | }; 39 | 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/backend_error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::backend { 7 | 8 | 9 | class BackendError : public std::runtime_error { 10 | using runtime_error::runtime_error; 11 | }; 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/key_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "keys.h" 4 | 5 | 6 | namespace dpso::backend { 7 | 8 | 9 | struct HotkeyBinding { 10 | DpsoHotkey hotkey; 11 | DpsoHotkeyAction action; 12 | }; 13 | 14 | 15 | // Key manager. 16 | // 17 | // See key_manager.h for more information. Preconditions: 18 | // 19 | // * bindHotkey(): 20 | // * hotkey.key >= 0 and < dpsoNumKeys 21 | // * action >= 0 22 | // * getBinding(), removeBinding(): 23 | // * idx >= 0 and < getNumBindings() 24 | class KeyManager { 25 | public: 26 | virtual ~KeyManager() = default; 27 | 28 | // See dpsoKeyManagerGetIsEnabled() 29 | virtual bool getIsEnabled() const = 0; 30 | 31 | // See dpsoKeyManagerSetIsEnabled() 32 | virtual void setIsEnabled(bool newIsEnabled) = 0; 33 | 34 | // See dpsoKeyManagerGetLastHotkeyAction() 35 | virtual DpsoHotkeyAction getLastHotkeyAction() const = 0; 36 | 37 | // See dpsoKeyManagerBindHotkey() 38 | virtual void bindHotkey( 39 | const DpsoHotkey& hotkey, DpsoHotkeyAction action) = 0; 40 | 41 | virtual int getNumBindings() const = 0; 42 | virtual HotkeyBinding getBinding(int idx) const = 0; 43 | virtual void removeBinding(int idx) = 0; 44 | }; 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/screenshot_error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "backend/backend_error.h" 4 | 5 | 6 | namespace dpso::backend { 7 | 8 | 9 | class ScreenshotError : public BackendError { 10 | using BackendError::BackendError; 11 | }; 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/selection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dpso_utils/geometry.h" 4 | 5 | 6 | namespace dpso::backend { 7 | 8 | 9 | // See selection.h for more information. 10 | class Selection { 11 | public: 12 | // Default border width at base (e.g. 96) DPI. 13 | static const auto defaultBorderWidth = 3; 14 | 15 | // The selection border is filled with a pattern of black and 16 | // white dashes. The dash length defines the number of squares in 17 | // a single dash. 18 | static const auto dashLen = 3; 19 | 20 | virtual ~Selection() = default; 21 | 22 | // See dpsoSelectionGetIsEnabled() 23 | virtual bool getIsEnabled() const = 0; 24 | 25 | // See dpsoSelectionSetIsEnabled() 26 | virtual void setIsEnabled(bool newIsEnabled) = 0; 27 | 28 | // See dpsoSelectionSetBorderWidth() 29 | // 30 | // The method is always called with newBorderWidth > 0. 31 | virtual void setBorderWidth(int newBorderWidth) = 0; 32 | 33 | // See dpsoSelectionGetGeometry() 34 | virtual Rect getGeometry() const = 0; 35 | }; 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/unix/backend.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "backend/backend_error.h" 5 | #include "backend/unix/x11/backend.h" 6 | 7 | 8 | namespace dpso::backend { 9 | 10 | 11 | std::unique_ptr Backend::create() 12 | { 13 | // Although X11 code on Wayland runs on top of XWayland, there 14 | // will still be neither global hotkeys nor screenshots since 15 | // Wayland doesn't support them by design. 16 | // 17 | // While an attempt to take a screenshot will fail explicitly 18 | // (i.e. XGrabImage() will return null), the global hotkeys 19 | // (XGrabKey()) will just silently do nothing. Therefore, it's 20 | // better not to rely on X11 failures and report an explicit error 21 | // as early as possible, especially for the use case when a hotkey 22 | // triggers a screenshot. 23 | const auto* xdgSessionType = std::getenv("XDG_SESSION_TYPE"); 24 | if (xdgSessionType && std::strcmp(xdgSessionType, "wayland") == 0) 25 | throw BackendError( 26 | "Wayland is not supported. Please switch to the X11/Xorg " 27 | "session."); 28 | 29 | return x11::createBackend(); 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/unix/x11/backend.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "backend/backend.h" 4 | 5 | 6 | namespace dpso::backend::x11 { 7 | 8 | 9 | std::unique_ptr createBackend(); 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/unix/x11/backend_component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::backend::x11 { 7 | 8 | 9 | class BackendComponent { 10 | public: 11 | virtual ~BackendComponent() = default; 12 | 13 | virtual void updateStart() {} 14 | virtual void handleEvent(const XEvent& event) { (void)event; } 15 | }; 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/unix/x11/key_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "backend/key_manager.h" 8 | #include "backend/unix/x11/backend_component.h" 9 | 10 | 11 | namespace dpso::backend::x11 { 12 | 13 | 14 | class KeyManager 15 | : public backend::KeyManager 16 | , public BackendComponent { 17 | public: 18 | explicit KeyManager(Display* display); 19 | ~KeyManager(); 20 | 21 | bool getIsEnabled() const override; 22 | void setIsEnabled(bool newIsEnabled) override; 23 | DpsoHotkeyAction getLastHotkeyAction() const override; 24 | 25 | void bindHotkey( 26 | const DpsoHotkey& hotkey, DpsoHotkeyAction action) override; 27 | 28 | int getNumBindings() const override; 29 | HotkeyBinding getBinding(int idx) const override; 30 | void removeBinding(int idx) override; 31 | 32 | void updateStart() override; 33 | void handleEvent(const XEvent& event) override; 34 | private: 35 | Display* display; 36 | bool isEnabled{}; 37 | 38 | struct X11HotkeyBinding { 39 | HotkeyBinding binding; 40 | KeyCode keyCode; 41 | }; 42 | std::vector x11bindings; 43 | 44 | DpsoHotkeyAction hotkeyAction{dpsoNoHotkeyAction}; 45 | 46 | static void changeGrab( 47 | Display* display, 48 | const X11HotkeyBinding& x11binding, 49 | bool grab); 50 | }; 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/unix/x11/screenshot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "dpso_img/img.h" 6 | 7 | 8 | namespace dpso { 9 | 10 | 11 | struct Rect; 12 | 13 | 14 | namespace backend::x11 { 15 | 16 | 17 | img::ImgUPtr takeScreenshot(Display* display, const Rect& rect); 18 | 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/unix/x11/selection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "backend/selection.h" 6 | #include "backend/unix/x11/backend_component.h" 7 | #include "backend/unix/x11/utils.h" 8 | 9 | 10 | namespace dpso::backend::x11 { 11 | 12 | 13 | class Selection : public backend::Selection, public BackendComponent { 14 | public: 15 | explicit Selection(Display* display); 16 | 17 | bool getIsEnabled() const override; 18 | void setIsEnabled(bool newIsEnabled) override; 19 | 20 | void setBorderWidth(int newBorderWidth) override; 21 | 22 | Rect getGeometry() const override; 23 | 24 | void updateStart() override; 25 | void handleEvent(const XEvent& event) override; 26 | private: 27 | Display* display; 28 | WindowHandle window{}; 29 | GcHandle gc{}; 30 | 31 | bool isEnabled{}; 32 | int baseBorderWidth{defaultBorderWidth}; 33 | int borderWidth{baseBorderWidth}; 34 | Point origin; 35 | Rect geom; 36 | 37 | void updateBorderWidth(); 38 | void updateWindowGeometry(); 39 | void updateWindowShape(); 40 | void setGeometry(const Rect& newGeom); 41 | void draw(); 42 | }; 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/windows/execution_layer/action_executor.cpp: -------------------------------------------------------------------------------- 1 | #include "backend/windows/execution_layer/action_executor.h" 2 | 3 | 4 | namespace dpso::backend { 5 | 6 | 7 | ActionExecutor::ActionExecutor() 8 | : thread{&ActionExecutor::threadLoop, this} 9 | { 10 | } 11 | 12 | 13 | ActionExecutor::~ActionExecutor() 14 | { 15 | backend::execute(*this, [&]{ terminate = true; }); 16 | thread.join(); 17 | } 18 | 19 | 20 | void ActionExecutor::execute(Action& action) 21 | { 22 | { 23 | const std::lock_guard guard{mutex}; 24 | currentAction = &action; 25 | } 26 | 27 | actionSetCondVar.notify_one(); 28 | 29 | { 30 | std::unique_lock lock{mutex}; 31 | actionDoneCondVar.wait(lock, [&]{ return !currentAction; }); 32 | } 33 | 34 | if (actionException) 35 | std::rethrow_exception( 36 | std::exchange(actionException, nullptr)); 37 | } 38 | 39 | 40 | void ActionExecutor::threadLoop() 41 | { 42 | // No need to protect "terminate" since it's set from within an 43 | // action. 44 | while (!terminate) { 45 | std::unique_lock lock{mutex}; 46 | actionSetCondVar.wait(lock, [&]{ return currentAction; }); 47 | 48 | try { 49 | currentAction->action(); 50 | } catch (...) { 51 | actionException = std::current_exception(); 52 | } 53 | 54 | currentAction = nullptr; 55 | 56 | // Unlock manually to avoid waking up the caller's thread only 57 | // to block again. 58 | lock.unlock(); 59 | actionDoneCondVar.notify_one(); 60 | } 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/windows/execution_layer/backend_executor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "backend/backend.h" 6 | 7 | 8 | namespace dpso::backend { 9 | 10 | 11 | using BackendCreatorFn = std::unique_ptr (&)(); 12 | std::unique_ptr createBackendExecutor( 13 | BackendCreatorFn creatorFn); 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/windows/execution_layer/key_manager_executor.cpp: -------------------------------------------------------------------------------- 1 | #include "backend/windows/execution_layer/key_manager_executor.h" 2 | 3 | #include "backend/windows/execution_layer/action_executor.h" 4 | 5 | 6 | namespace dpso::backend { 7 | 8 | 9 | #define EXECUTE(CALL) \ 10 | execute(actionExecutor, [&]{ return keyManager.CALL; }) 11 | 12 | 13 | KeyManagerExecutor::KeyManagerExecutor( 14 | KeyManager& keyManager, ActionExecutor& actionExecutor) 15 | : keyManager{keyManager} 16 | , actionExecutor{actionExecutor} 17 | { 18 | } 19 | 20 | 21 | bool KeyManagerExecutor::getIsEnabled() const 22 | { 23 | return EXECUTE(getIsEnabled()); 24 | } 25 | 26 | 27 | void KeyManagerExecutor::setIsEnabled(bool newIsEnabled) 28 | { 29 | EXECUTE(setIsEnabled(newIsEnabled)); 30 | } 31 | 32 | 33 | DpsoHotkeyAction KeyManagerExecutor::getLastHotkeyAction() const 34 | { 35 | return EXECUTE(getLastHotkeyAction()); 36 | } 37 | 38 | 39 | void KeyManagerExecutor::bindHotkey( 40 | const DpsoHotkey& hotkey, DpsoHotkeyAction action) 41 | { 42 | EXECUTE(bindHotkey(hotkey, action)); 43 | } 44 | 45 | 46 | int KeyManagerExecutor::getNumBindings() const 47 | { 48 | return EXECUTE(getNumBindings()); 49 | } 50 | 51 | 52 | HotkeyBinding KeyManagerExecutor::getBinding(int idx) const 53 | { 54 | return EXECUTE(getBinding(idx)); 55 | } 56 | 57 | 58 | void KeyManagerExecutor::removeBinding(int idx) 59 | { 60 | EXECUTE(removeBinding(idx)); 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/windows/execution_layer/key_manager_executor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "backend/key_manager.h" 4 | 5 | 6 | namespace dpso::backend { 7 | 8 | 9 | class ActionExecutor; 10 | 11 | 12 | class KeyManagerExecutor : public KeyManager { 13 | public: 14 | KeyManagerExecutor( 15 | KeyManager& keyManager, ActionExecutor& actionExecutor); 16 | 17 | bool getIsEnabled() const override; 18 | void setIsEnabled(bool newIsEnabled) override; 19 | DpsoHotkeyAction getLastHotkeyAction() const override; 20 | 21 | void bindHotkey( 22 | const DpsoHotkey& hotkey, DpsoHotkeyAction action) override; 23 | 24 | int getNumBindings() const override; 25 | HotkeyBinding getBinding(int idx) const override; 26 | void removeBinding(int idx) override; 27 | private: 28 | KeyManager& keyManager; 29 | ActionExecutor& actionExecutor; 30 | }; 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/windows/execution_layer/selection_executor.cpp: -------------------------------------------------------------------------------- 1 | #include "backend/windows/execution_layer/selection_executor.h" 2 | 3 | #include "backend/windows/execution_layer/action_executor.h" 4 | 5 | 6 | namespace dpso::backend { 7 | 8 | 9 | #define EXECUTE(CALL) \ 10 | execute(actionExecutor, [&]{ return selection.CALL; }) 11 | 12 | 13 | SelectionExecutor::SelectionExecutor( 14 | Selection& selection, ActionExecutor& actionExecutor) 15 | : selection{selection} 16 | , actionExecutor{actionExecutor} 17 | { 18 | } 19 | 20 | 21 | bool SelectionExecutor::getIsEnabled() const 22 | { 23 | return EXECUTE(getIsEnabled()); 24 | } 25 | 26 | 27 | void SelectionExecutor::setIsEnabled(bool newIsEnabled) 28 | { 29 | EXECUTE(setIsEnabled(newIsEnabled)); 30 | } 31 | 32 | 33 | void SelectionExecutor::setBorderWidth(int newBorderWidth) 34 | { 35 | EXECUTE(setBorderWidth(newBorderWidth)); 36 | } 37 | 38 | 39 | Rect SelectionExecutor::getGeometry() const 40 | { 41 | return EXECUTE(getGeometry()); 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/windows/execution_layer/selection_executor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "backend/selection.h" 4 | 5 | 6 | namespace dpso::backend { 7 | 8 | 9 | class ActionExecutor; 10 | 11 | 12 | class SelectionExecutor : public Selection { 13 | public: 14 | SelectionExecutor( 15 | Selection& selection, ActionExecutor& actionExecutor); 16 | 17 | bool getIsEnabled() const override; 18 | void setIsEnabled(bool newIsEnabled) override; 19 | 20 | void setBorderWidth(int newBorderWidth) override; 21 | 22 | Rect getGeometry() const override; 23 | private: 24 | Selection& selection; 25 | ActionExecutor& actionExecutor; 26 | }; 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/windows/key_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define WIN32_LEAN_AND_MEAN 6 | #include 7 | 8 | #include "backend/key_manager.h" 9 | 10 | 11 | namespace dpso::backend::windows { 12 | 13 | 14 | class KeyManager : public backend::KeyManager { 15 | public: 16 | KeyManager() = default; 17 | ~KeyManager(); 18 | 19 | bool getIsEnabled() const override; 20 | void setIsEnabled(bool newIsEnabled) override; 21 | DpsoHotkeyAction getLastHotkeyAction() const override; 22 | 23 | void bindHotkey( 24 | const DpsoHotkey& hotkey, DpsoHotkeyAction action) override; 25 | 26 | int getNumBindings() const override; 27 | HotkeyBinding getBinding(int idx) const override; 28 | void removeBinding(int idx) override; 29 | 30 | void clearLastHotkeyAction(); 31 | void handleWmHotkey(const MSG& msg); 32 | private: 33 | bool isEnabled{}; 34 | std::vector bindings; 35 | DpsoHotkeyAction hotkeyAction{dpsoNoHotkeyAction}; 36 | 37 | HotkeyBinding* findBinding(const DpsoHotkey& hotkey); 38 | }; 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/windows/screenshot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dpso_img/img.h" 4 | 5 | 6 | namespace dpso { 7 | 8 | 9 | struct Rect; 10 | 11 | 12 | namespace backend::windows { 13 | 14 | 15 | img::ImgUPtr takeScreenshot(const Rect& rect); 16 | 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/dpso_sys/backend/windows/selection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | 6 | #include "backend/selection.h" 7 | #include "dpso_utils/windows/gdi.h" 8 | #include "dpso_utils/windows/window.h" 9 | 10 | 11 | namespace dpso::backend::windows { 12 | 13 | 14 | class Selection : public backend::Selection { 15 | public: 16 | explicit Selection(HINSTANCE instance); 17 | ~Selection(); 18 | 19 | bool getIsEnabled() const override; 20 | void setIsEnabled(bool newIsEnabled) override; 21 | 22 | void setBorderWidth(int newBorderWidth) override; 23 | 24 | Rect getGeometry() const override; 25 | 26 | void update(); 27 | private: 28 | bool isEnabled{}; 29 | int dpi{}; 30 | int baseBorderWidth{defaultBorderWidth}; 31 | int borderWidth{baseBorderWidth}; 32 | Point origin; 33 | Rect geom; 34 | 35 | dpso::windows::WindowUPtr window; 36 | 37 | dpso::windows::ObjectUPtr pens[2]; 38 | 39 | static LRESULT CALLBACK wndProc( 40 | HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam); 41 | LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam); 42 | 43 | void updateBorderWidth(); 44 | void updatePens(); 45 | void updateWindowGeometry(); 46 | void updateWindowRegion(); 47 | void setGeometry(const Rect& newGeom); 48 | void draw(HDC dc); 49 | }; 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/dpso_sys/dpso_sys.cpp: -------------------------------------------------------------------------------- 1 | #include "dpso_sys.h" 2 | 3 | #include "dpso_utils/error_set.h" 4 | 5 | #include "backend/backend.h" 6 | #include "backend/backend_error.h" 7 | #include "dpso_sys_fwd.h" 8 | #include "dpso_sys_p.h" 9 | 10 | 11 | DpsoSys* dpsoSysCreate(void) 12 | { 13 | try { 14 | return new DpsoSys{}; 15 | } catch (dpso::backend::BackendError& e) { 16 | dpso::setError("{}", e.what()); 17 | return {}; 18 | } 19 | } 20 | 21 | 22 | void dpsoSysDelete(DpsoSys* sys) 23 | { 24 | delete sys; 25 | } 26 | 27 | 28 | void dpsoSysUpdate(DpsoSys* sys) 29 | { 30 | if (sys) 31 | sys->backend->update(); 32 | } 33 | 34 | 35 | DpsoKeyManager* dpsoSysGetKeyManager(DpsoSys* sys) 36 | { 37 | return sys ? &sys->keyManager : nullptr; 38 | } 39 | 40 | 41 | DpsoSelection* dpsoSysGetSelection(DpsoSys* sys) 42 | { 43 | return sys ? &sys->selection : nullptr; 44 | } 45 | -------------------------------------------------------------------------------- /src/dpso_sys/dpso_sys.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dpso_sys_fwd.h" 4 | #include "key_manager.h" 5 | #include "screenshot.h" 6 | #include "selection.h" 7 | 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | 14 | /** 15 | * Create a system backend. 16 | * 17 | * On failure, sets an error message (dpsoGetError()) and returns 18 | * null. 19 | */ 20 | DpsoSys* dpsoSysCreate(void); 21 | 22 | 23 | void dpsoSysDelete(DpsoSys* sys); 24 | 25 | 26 | /** 27 | * Update the system backend. 28 | * 29 | * The function process system events, like mouse motion, key press, 30 | * etc., for hotkey handling (key_manager.h) and interactive selection 31 | * (selection.h). Call this function at a frequency close to the 32 | * monitor refresh rate, which is usually 60 times per second. 33 | */ 34 | void dpsoSysUpdate(DpsoSys* sys); 35 | 36 | 37 | DpsoKeyManager* dpsoSysGetKeyManager(DpsoSys* sys); 38 | DpsoSelection* dpsoSysGetSelection(DpsoSys* sys); 39 | 40 | 41 | #ifdef __cplusplus 42 | } 43 | 44 | 45 | #include 46 | 47 | 48 | namespace dpso { 49 | 50 | 51 | struct SysDeleter { 52 | void operator()(DpsoSys* sys) const 53 | { 54 | dpsoSysDelete(sys); 55 | } 56 | }; 57 | 58 | 59 | using SysUPtr = std::unique_ptr; 60 | 61 | 62 | } 63 | 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/dpso_sys/dpso_sys_fwd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | typedef struct DpsoSys DpsoSys; 5 | -------------------------------------------------------------------------------- /src/dpso_sys/dpso_sys_p.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "backend/backend.h" 4 | 5 | 6 | struct DpsoKeyManager { 7 | dpso::backend::KeyManager& impl; 8 | }; 9 | 10 | 11 | struct DpsoSelection { 12 | dpso::backend::Selection& impl; 13 | }; 14 | 15 | 16 | struct DpsoSys { 17 | std::unique_ptr backend{ 18 | dpso::backend::Backend::create()}; 19 | DpsoKeyManager keyManager{backend->getKeyManager()}; 20 | DpsoSelection selection{backend->getSelection()}; 21 | }; 22 | -------------------------------------------------------------------------------- /src/dpso_sys/screenshot.cpp: -------------------------------------------------------------------------------- 1 | #include "screenshot.h" 2 | 3 | #include "dpso_utils/error_set.h" 4 | #include "dpso_utils/geometry.h" 5 | 6 | #include "backend/backend.h" 7 | #include "backend/screenshot_error.h" 8 | #include "dpso_sys_p.h" 9 | 10 | 11 | DpsoImg* dpsoTakeScreenshot(DpsoSys* sys, const DpsoRect* rect) 12 | { 13 | if (!sys) { 14 | dpso::setError("sys is null"); 15 | return {}; 16 | } 17 | 18 | if (!rect) { 19 | dpso::setError("rect is null"); 20 | return {}; 21 | } 22 | 23 | try { 24 | return sys->backend->takeScreenshot( 25 | dpso::Rect{*rect}).release(); 26 | } catch (dpso::backend::ScreenshotError& e) { 27 | dpso::setError("Backend::takeScreenshot(): {}", e.what()); 28 | return {}; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/dpso_sys/screenshot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dpso_img/img.h" 4 | #include "dpso_utils/geometry_c.h" 5 | 6 | #include "dpso_sys_fwd.h" 7 | 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | 14 | /** 15 | * Take a screenshot. 16 | * 17 | * The screenshot is be taken from an intersection of the given rect 18 | * with the actual screen geometry. 19 | * 20 | * On failure, sets an error message (dpsoGetError()) and returns 21 | * null. 22 | */ 23 | DpsoImg* dpsoTakeScreenshot(DpsoSys* sys, const DpsoRect* rect); 24 | 25 | 26 | #ifdef __cplusplus 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /src/dpso_sys/selection.cpp: -------------------------------------------------------------------------------- 1 | #include "selection.h" 2 | 3 | #include "backend/selection.h" 4 | #include "dpso_sys_p.h" 5 | 6 | 7 | bool dpsoSelectionGetIsEnabled(const DpsoSelection* selection) 8 | { 9 | return selection && selection->impl.getIsEnabled(); 10 | } 11 | 12 | 13 | void dpsoSelectionSetIsEnabled( 14 | DpsoSelection* selection, bool newIsEnabled) 15 | { 16 | if (selection) 17 | selection->impl.setIsEnabled(newIsEnabled); 18 | } 19 | 20 | 21 | int dpsoSelectionGetDefaultBorderWidth(void) 22 | { 23 | return dpso::backend::Selection::defaultBorderWidth; 24 | } 25 | 26 | 27 | void dpsoSelectionSetBorderWidth( 28 | DpsoSelection* selection, int newBorderWidth) 29 | { 30 | if (selection) 31 | selection->impl.setBorderWidth( 32 | newBorderWidth < 1 ? 1 : newBorderWidth); 33 | } 34 | 35 | 36 | void dpsoSelectionGetGeometry( 37 | const DpsoSelection* selection, DpsoRect* rect) 38 | { 39 | if (!rect) 40 | return; 41 | 42 | *rect = selection 43 | ? toCRect(selection->impl.getGeometry()) : DpsoRect{}; 44 | } 45 | -------------------------------------------------------------------------------- /src/dpso_utils/byte_order.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | namespace dpso { 8 | 9 | 10 | enum class ByteOrder { 11 | little, 12 | big 13 | }; 14 | 15 | 16 | template 17 | void iLoop(Fn fn) 18 | { 19 | if (byteOrder == ByteOrder::little) 20 | for (std::size_t i = 0; i < n; i++) 21 | fn(i); 22 | else 23 | for (auto i = n; i--;) 24 | fn(i); 25 | } 26 | 27 | 28 | template 29 | void store(T v, void* data) 30 | { 31 | const auto uv = static_cast>(v); 32 | auto* dst = static_cast(data); 33 | 34 | iLoop( 35 | [&](std::size_t i) 36 | { 37 | *dst++ = (uv >> (i * 8)) & 0xff; 38 | }); 39 | } 40 | 41 | 42 | template 43 | void load(T& v, const void* data) 44 | { 45 | static_assert(numBytes <= sizeof(T)); 46 | 47 | using UT = std::make_unsigned_t; 48 | const auto* src = static_cast(data); 49 | 50 | UT uv{}; 51 | 52 | iLoop( 53 | [&](std::size_t i) 54 | { 55 | uv |= static_cast(*src++) << (i * 8); 56 | }); 57 | 58 | v = static_cast(uv); 59 | } 60 | 61 | 62 | template 63 | void load(T& v, const void* data) 64 | { 65 | load(v, data); 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/dpso_utils/dpso_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "error_get.h" 4 | #include "geometry_c.h" 5 | #include "os_c.h" 6 | -------------------------------------------------------------------------------- /src/dpso_utils/error.cpp: -------------------------------------------------------------------------------- 1 | #include "error_get.h" 2 | #include "error_set.h" 3 | 4 | #include 5 | 6 | #include "str.h" 7 | 8 | 9 | static thread_local std::string error; 10 | 11 | 12 | const char* dpsoGetError(void) 13 | { 14 | return error.c_str(); 15 | } 16 | 17 | 18 | namespace dpso { 19 | 20 | 21 | void setError( 22 | const char* fmt, std::initializer_list args) 23 | { 24 | error = str::format(fmt, args); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/dpso_utils/error_get.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Error message handling for C APIs. 4 | */ 5 | 6 | #pragma once 7 | 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | 14 | /** 15 | * Get the last error message for the current thread. 16 | * 17 | * The message is only applicable when a function signals an error. By 18 | * convention, if a function doesn't explicitly mention dpsoGetError() 19 | * in its documentation, this implies that the function doesn't set an 20 | * error message on failure and dpsoGetError() should not be used. 21 | */ 22 | const char* dpsoGetError(void); 23 | 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /src/dpso_utils/error_set.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "str.h" 4 | 5 | 6 | namespace dpso { 7 | 8 | 9 | // Set an error message to be returned by dpsoGetError(). 10 | void setError( 11 | const char* fmt, std::initializer_list args); 12 | 13 | 14 | template 15 | void setError(const char* fmt, const Args&... args) 16 | { 17 | setError(fmt, {str::formatArg::get(args)...}); 18 | } 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/dpso_utils/geometry_c.cpp: -------------------------------------------------------------------------------- 1 | #include "geometry_c.h" 2 | 3 | 4 | bool dpsoRectIsEmpty(const DpsoRect* rect) 5 | { 6 | return !rect || rect->w <= 0 || rect->h <= 0; 7 | } 8 | -------------------------------------------------------------------------------- /src/dpso_utils/geometry_c.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | 11 | typedef struct DpsoRect { 12 | int x; 13 | int y; 14 | int w; 15 | int h; 16 | } DpsoRect; 17 | 18 | 19 | /** 20 | * Rect is empty if w or h is <= 0. 21 | */ 22 | bool dpsoRectIsEmpty(const DpsoRect* rect); 23 | 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /src/dpso_utils/line_reader.cpp: -------------------------------------------------------------------------------- 1 | #include "line_reader.h" 2 | 3 | #include 4 | 5 | #include "stream/stream.h" 6 | 7 | 8 | namespace dpso { 9 | 10 | 11 | LineReader::LineReader(Stream& stream) 12 | : stream{stream} 13 | , bufFill{} 14 | , bufPos{} 15 | { 16 | } 17 | 18 | 19 | bool LineReader::readLine(std::string& line) 20 | { 21 | line.clear(); 22 | 23 | auto wasCr = bufPos > 0 && buf[bufPos - 1] == '\r'; 24 | 25 | while (true) { 26 | assert(bufPos <= bufFill); 27 | 28 | if (bufPos == bufFill) { 29 | bufFill = stream.readSome(buf, sizeof(buf)); 30 | bufPos = 0; 31 | 32 | if (bufFill == 0) 33 | break; 34 | } 35 | 36 | if (wasCr) { 37 | wasCr = false; 38 | if (buf[bufPos] == '\n') { 39 | ++bufPos; 40 | continue; 41 | } 42 | } 43 | 44 | auto partEnd = bufPos; 45 | for (; partEnd < bufFill; ++partEnd) 46 | if (buf[partEnd] == '\r' || buf[partEnd] == '\n') 47 | break; 48 | 49 | line.append(buf + bufPos, buf + partEnd); 50 | bufPos = partEnd; 51 | 52 | if (bufPos < bufFill) { 53 | ++bufPos; 54 | break; 55 | } 56 | } 57 | 58 | return !line.empty() || bufFill > 0; 59 | } 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/dpso_utils/line_reader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | namespace dpso { 8 | 9 | 10 | class Stream; 11 | 12 | 13 | class LineReader { 14 | public: 15 | explicit LineReader(Stream& stream); 16 | 17 | // Read the next line from the stream, terminating on either a 18 | // line break (\r, \n, or \r\n) or the end of the stream. Returns 19 | // false if the line cannot be read because the end of the stream 20 | // is reached. 21 | // 22 | // The function clears the line before performing any action. 23 | // 24 | // Throws StreamError. 25 | bool readLine(std::string& line); 26 | private: 27 | Stream& stream; 28 | char buf[1024]; 29 | std::size_t bufFill; 30 | std::size_t bufPos; 31 | }; 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/dpso_utils/os_c.cpp: -------------------------------------------------------------------------------- 1 | #include "os_c.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "error_set.h" 7 | #include "os.h" 8 | 9 | 10 | const char* const dpsoDirSeparators = dpso::os::dirSeparators; 11 | 12 | 13 | void dpsoSleep(int milliseconds) 14 | { 15 | std::this_thread::sleep_for( 16 | std::chrono::milliseconds{milliseconds}); 17 | } 18 | 19 | 20 | bool dpsoExec( 21 | const char* exePath, const char* const args[], size_t numArgs) 22 | { 23 | try { 24 | dpso::os::exec(exePath, args, numArgs); 25 | return true; 26 | } catch (dpso::os::Error& e) { 27 | dpso::setError("{}", e.what()); 28 | return false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/dpso_utils/os_c.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | 12 | /** 13 | * Directory separators for the current platform. 14 | * 15 | * The primary separator is first in the list. 16 | */ 17 | extern const char* const dpsoDirSeparators; 18 | 19 | 20 | /** 21 | * Block the current thread for the given number of milliseconds. 22 | */ 23 | void dpsoSleep(int milliseconds); 24 | 25 | 26 | /** 27 | * Run an executable. 28 | * 29 | * If supported by the platform, exePath may be just the name of the 30 | * executable (e.g., to look up in the PATH environment variable). 31 | * 32 | * The function blocks the caller's thread until the executable exits. 33 | * 34 | * On failure, sets an error message (dpsoGetError()) and returns 35 | * false. 36 | */ 37 | bool dpsoExec( 38 | const char* exePath, const char* const args[], size_t numArgs); 39 | 40 | 41 | #ifdef __cplusplus 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /src/dpso_utils/os_stdio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::os { 7 | 8 | 9 | // fopen() that accepts filePath in UTF-8. 10 | std::FILE* fopen(const char* filePath, const char* mode); 11 | 12 | 13 | // Deleter for a FILE* smart pointer. 14 | struct StdFileCloser { 15 | void operator()(std::FILE* fp) const 16 | { 17 | if (fp) 18 | std::fclose(fp); 19 | } 20 | }; 21 | 22 | 23 | using StdFileUPtr = std::unique_ptr; 24 | 25 | 26 | // Synchronize file state with storage device. 27 | // 28 | // The function transfers all modified data (and possibly file 29 | // attributes) to the permanent storage device. It should normally be 30 | // preceded by fflush(). 31 | // 32 | // Throws os::Error. 33 | void syncFile(std::FILE* fp); 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/dpso_utils/scope_exit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso { 7 | 8 | 9 | template 10 | class ScopeExit { 11 | public: 12 | explicit ScopeExit(Fn&& fn) 13 | : fn{std::move(fn)} 14 | { 15 | } 16 | 17 | ~ScopeExit() 18 | { 19 | fn(); 20 | } 21 | 22 | ScopeExit(const ScopeExit&) = delete; 23 | ScopeExit& operator=(const ScopeExit&) = delete; 24 | 25 | ScopeExit(ScopeExit&&) = delete; 26 | ScopeExit& operator=(ScopeExit&&) = delete; 27 | private: 28 | Fn fn; 29 | }; 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/dpso_utils/sha256.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | namespace dpso { 10 | 11 | 12 | class Sha256 { 13 | public: 14 | static constexpr std::size_t digestSize = 32; 15 | using Digest = std::array; 16 | 17 | void update(const void* data, std::size_t size); 18 | Digest getDigest() const; 19 | private: 20 | class Context { 21 | public: 22 | Context(); 23 | void update(const std::uint8_t* data, std::size_t size); 24 | Digest finalize(); 25 | private: 26 | static constexpr auto stateSize = 8; 27 | static constexpr auto blockSize = 64; 28 | 29 | std::uint32_t state[stateSize]; 30 | std::uint64_t numTransformedBytes; 31 | std::uint8_t buf[blockSize]; 32 | std::uint32_t bufLen; 33 | 34 | void transform(const std::uint8_t block[blockSize]); 35 | }; 36 | 37 | Context context; 38 | }; 39 | 40 | 41 | std::string toHex(const Sha256::Digest& digest); 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/dpso_utils/sha256_file.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | namespace dpso { 8 | 9 | 10 | class Sha256FileError : public std::runtime_error { 11 | using runtime_error::runtime_error; 12 | }; 13 | 14 | 15 | extern const char* const sha256FileExt; 16 | 17 | 18 | // Throws Sha256FileError. 19 | std::string calcFileSha256(const char* filePath); 20 | 21 | 22 | // Save the SHA-256 hex digest of the digestSourceFilePath file to the 23 | // associated ".sha256" file (the path of this file is 24 | // digestSourceFilePath + ".sha256"). 25 | // Throws Sha256FileError. 26 | void saveSha256File( 27 | const char* digestSourceFilePath, const char* digest); 28 | 29 | 30 | // Load the SHA-256 hex digest previously saved by saveSha256File(). 31 | // Returns an empty string if the ".sha256" file for the given file 32 | // does not exist. 33 | // Throws Sha256FileError. 34 | std::string loadSha256File(const char* digestSourceFilePath); 35 | 36 | 37 | // Does nothing if the ".sha256" file for the given file does not 38 | // exist. 39 | // Throws Sha256FileError. 40 | void removeSha256File(const char* digestSourceFilePath); 41 | 42 | 43 | // Get the SHA-256 hex digest of the file from its ".sha256" file, 44 | // creating ".sha256" if it doesn't exist. 45 | // 46 | // The function first tries to load the digest with loadSha256File(). 47 | // If the digest file does not exist, the digest is calculated with 48 | // calcFileSha256(), saved with saveSha256File(), and returned. 49 | // 50 | // Throws Sha256FileError. 51 | std::string getSha256HexDigestWithCaching(const char* filePath); 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/dpso_utils/stream/file_stream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "dpso_utils/stream/stream.h" 6 | 7 | 8 | namespace dpso { 9 | 10 | 11 | class FileStream : public Stream { 12 | public: 13 | enum class Mode { 14 | // Open an existing file for reading. 15 | read, 16 | 17 | // Open a file for writing. If the file already exists, it 18 | // will be truncated. 19 | write, 20 | 21 | // Open a file for appending: all write operations will always 22 | // occur at the end of the file. If the file already exists, 23 | // its contents are preserved. 24 | append 25 | }; 26 | 27 | // Throws os::Error. 28 | FileStream(const char* fileName, Mode mode); 29 | ~FileStream(); 30 | 31 | FileStream(const FileStream&) = delete; 32 | FileStream& operator=(const FileStream&) = delete; 33 | 34 | FileStream(FileStream&&) = delete; 35 | FileStream& operator=(FileStream&&) = delete; 36 | 37 | std::size_t readSome(void* dst, std::size_t dstSize) override; 38 | 39 | void write(const void* src, std::size_t srcSize) override; 40 | 41 | // Synchronize the file state with the storage device. 42 | // 43 | // Throws os::Error. 44 | void sync(); 45 | private: 46 | struct Impl; 47 | std::unique_ptr impl; 48 | }; 49 | 50 | 51 | const char* toStr(FileStream::Mode mode); 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/dpso_utils/stream/out_newline_conversion_stream.cpp: -------------------------------------------------------------------------------- 1 | #include "stream/out_newline_conversion_stream.h" 2 | 3 | #include 4 | 5 | #include "os.h" 6 | 7 | 8 | namespace dpso { 9 | 10 | 11 | OutNewlineConversionStream::OutNewlineConversionStream( 12 | Stream& base, const char* newline) 13 | : base{base} 14 | , newline{newline ? newline : os::newline} 15 | { 16 | } 17 | 18 | 19 | std::size_t OutNewlineConversionStream::readSome( 20 | void* dst, std::size_t dstSize) 21 | { 22 | return base.readSome(dst, dstSize); 23 | } 24 | 25 | 26 | static bool isLf(char c) 27 | { 28 | return c == '\n'; 29 | } 30 | 31 | 32 | void OutNewlineConversionStream::write( 33 | const void* src, std::size_t srcSize) 34 | { 35 | if (newline.size() == 1 && isLf(newline[0])) { 36 | base.write(src, srcSize); 37 | return; 38 | } 39 | 40 | const auto* s = static_cast(src); 41 | const auto* sEnd = s + srcSize; 42 | 43 | while (s < sEnd) { 44 | const auto* lfsBegin = std::find_if(s, sEnd, isLf); 45 | base.write(s, lfsBegin - s); 46 | 47 | const auto* lfsEnd = std::find_if_not(lfsBegin, sEnd, isLf); 48 | 49 | if (!newline.empty()) 50 | for (auto i = lfsEnd - lfsBegin; i--;) 51 | base.write(newline.data(), newline.size()); 52 | 53 | s = lfsEnd; 54 | } 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/dpso_utils/stream/out_newline_conversion_stream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "dpso_utils/stream/stream.h" 6 | 7 | 8 | namespace dpso { 9 | 10 | 11 | // A stream that replaces all line feeds (\n) with the given newline 12 | // string when writing. Does no conversion when reading. 13 | class OutNewlineConversionStream : public Stream { 14 | public: 15 | // The newline can be any string. If null, the native newline for 16 | // the current platform is used. 17 | explicit OutNewlineConversionStream( 18 | Stream& base, const char* newline = nullptr); 19 | 20 | std::size_t readSome(void* dst, std::size_t dstSize) override; 21 | void write(const void* src, std::size_t srcSize) override; 22 | private: 23 | Stream& base; 24 | std::string newline; 25 | }; 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/dpso_utils/stream/stream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | namespace dpso { 8 | 9 | 10 | class StreamError : public std::runtime_error { 11 | using runtime_error::runtime_error; 12 | }; 13 | 14 | 15 | class Stream { 16 | public: 17 | virtual ~Stream() = default; 18 | 19 | // Read up to dstSize bytes from the stream. Returns the number of 20 | // bytes read, which may be less than dstSize if the end of the 21 | // stream is reached. 22 | // 23 | // Throws StreamError. 24 | virtual std::size_t readSome(void* dst, std::size_t dstSize) = 0; 25 | 26 | // Throws StreamError. 27 | virtual void write(const void* src, std::size_t srcSize) = 0; 28 | }; 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/dpso_utils/stream/string_stream.cpp: -------------------------------------------------------------------------------- 1 | #include "stream/string_stream.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | namespace dpso { 9 | 10 | 11 | StringStream::StringStream(const char* str) 12 | : str{str} 13 | { 14 | } 15 | 16 | 17 | StringStream::StringStream(StringStream&& other) noexcept 18 | { 19 | *this = std::move(other); 20 | } 21 | 22 | 23 | StringStream& StringStream::operator=(StringStream&& other) noexcept 24 | { 25 | if (this != &other) { 26 | str = std::exchange(other.str, {}); 27 | pos = std::exchange(other.pos, {}); 28 | } 29 | 30 | return *this; 31 | } 32 | 33 | 34 | const std::string& StringStream::getStr() const 35 | { 36 | return str; 37 | } 38 | 39 | 40 | std::string StringStream::takeStr() 41 | { 42 | pos = 0; 43 | return std::exchange(str, {}); 44 | } 45 | 46 | 47 | std::size_t StringStream::readSome(void* dst, std::size_t dstSize) 48 | { 49 | assert(pos <= str.size()); 50 | const auto numRead = std::min(dstSize, str.size() - pos); 51 | 52 | std::copy_n(str.data() + pos, numRead, static_cast(dst)); 53 | pos += numRead; 54 | 55 | return numRead; 56 | } 57 | 58 | 59 | void StringStream::write(const void* src, std::size_t srcSize) 60 | { 61 | str.resize(std::max(pos + srcSize, str.size())); 62 | std::copy_n( 63 | static_cast(src), srcSize, str.data() + pos); 64 | pos += srcSize; 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/dpso_utils/stream/string_stream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "dpso_utils/stream/stream.h" 7 | 8 | 9 | namespace dpso { 10 | 11 | 12 | class StringStream : public Stream { 13 | public: 14 | StringStream() = default; 15 | 16 | // Construct the stream with initial data. The stream position 17 | // will be 0. 18 | explicit StringStream(const char* str); 19 | ~StringStream() = default; 20 | 21 | StringStream(const StringStream&) = default; 22 | StringStream& operator=(const StringStream&) = default; 23 | 24 | StringStream(StringStream&& other) noexcept; 25 | StringStream& operator=(StringStream&& other) noexcept; 26 | 27 | const std::string& getStr() const; 28 | std::string takeStr(); 29 | 30 | std::size_t readSome(void* dst, std::size_t dstSize) override; 31 | void write(const void* src, std::size_t srcSize) override; 32 | private: 33 | std::string str; 34 | std::size_t pos{}; 35 | }; 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/dpso_utils/stream/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "stream/utils.h" 2 | 3 | #include 4 | 5 | #include "stream/stream.h" 6 | 7 | 8 | namespace dpso { 9 | 10 | 11 | void read(Stream& stream, void* dst, std::size_t dstSize) 12 | { 13 | if (stream.readSome(dst, dstSize) != dstSize) 14 | throw StreamError{"Unexpected end of stream"}; 15 | } 16 | 17 | 18 | void write(Stream& stream, const std::string& str) 19 | { 20 | stream.write(str.data(), str.size()); 21 | } 22 | 23 | 24 | void write(Stream& stream, const char* str) 25 | { 26 | stream.write(str, std::strlen(str)); 27 | } 28 | 29 | 30 | void write(Stream& stream, char c) 31 | { 32 | stream.write(&c, 1); 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/dpso_utils/stream/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | namespace dpso { 8 | 9 | 10 | class Stream; 11 | 12 | 13 | // Read the given number of bytes from a stream. Throws StreamError. 14 | void read(Stream& stream, void* dst, std::size_t dstSize); 15 | 16 | 17 | // Write data to a stream. Throws StreamError. 18 | void write(Stream& stream, const std::string& str); 19 | void write(Stream& stream, const char* str); 20 | void write(Stream& stream, char c); 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/dpso_utils/strftime.cpp: -------------------------------------------------------------------------------- 1 | #include "strftime.h" 2 | 3 | 4 | namespace dpso { 5 | 6 | 7 | std::string strftime(const char* fmt, const std::tm* time) 8 | { 9 | // strftime() does not set errno if the destination buffer is too 10 | // small, making it impossible to distinguish this error from 11 | // cases when the result string is empty (for example, %p yields 12 | // an empty string in many locales). As a workaround, we append an 13 | // extra character to the format string, and then remove it from 14 | // the result before returning. 15 | const auto fmtEx = std::string{fmt} + ' '; 16 | 17 | std::string result; 18 | 19 | while (true) { 20 | // Try to detect the case when strftime() on the current 21 | // system always returns 0 for the given format string, e.g. 22 | // due to unsupported format specifiers. 23 | if (result.size() > fmtEx.size() * 100) 24 | return fmt; 25 | 26 | result.reserve(result.size() + 8); 27 | result.resize(result.capacity()); 28 | 29 | const auto numWritten = std::strftime( 30 | result.data(), result.size(), fmtEx.c_str(), time); 31 | if (numWritten == 0) 32 | continue; 33 | 34 | result.resize(numWritten - 1); 35 | break; 36 | } 37 | 38 | return result; 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/dpso_utils/strftime.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | namespace dpso { 8 | 9 | 10 | #if defined(__GNUC__) || defined(__clang__) 11 | /* Old MinGW versions don't define __MINGW_STRFTIME_FORMAT. */ 12 | #if defined(__MINGW32__) && defined(__MINGW_STRFTIME_FORMAT) 13 | #define DPSO_STRFTIME_FN(N) \ 14 | __attribute__((format(__MINGW_STRFTIME_FORMAT, N, 0))) 15 | #else 16 | #define DPSO_STRFTIME_FN(N) \ 17 | __attribute__((format(strftime, N, 0))) 18 | #endif 19 | #else 20 | #define DPSO_STRFTIME_FN(N) 21 | #endif 22 | 23 | 24 | std::string strftime( 25 | const char* fmt, const std::tm* time) DPSO_STRFTIME_FN(1); 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/dpso_utils/timing.cpp: -------------------------------------------------------------------------------- 1 | #include "timing.h" 2 | 3 | 4 | #if DPSO_FORCE_TIMING || !defined(NDEBUG) 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "str.h" 12 | 13 | 14 | namespace dpso::timing { 15 | 16 | 17 | float getTime() 18 | { 19 | using Clock = std::chrono::steady_clock; 20 | using FloatMs = std::chrono::duration; 21 | 22 | return FloatMs{Clock::now().time_since_epoch()}.count(); 23 | } 24 | 25 | 26 | void report( 27 | float startTime, 28 | const char* fmt, 29 | std::initializer_list args) 30 | { 31 | const auto duration = getTime() - startTime; 32 | 33 | std::fputs("Timing: ", stdout); 34 | std::fputs(str::format(fmt, args).c_str(), stdout); 35 | std::fputs(str::format(": {} ms\n", duration).c_str(), stdout); 36 | 37 | std::fflush(stdout); 38 | } 39 | 40 | 41 | } 42 | 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/dpso_utils/timing.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #if DPSO_FORCE_TIMING || !defined(NDEBUG) 5 | 6 | #include "str.h" 7 | 8 | 9 | namespace dpso::timing { 10 | 11 | 12 | float getTime(); 13 | 14 | void report( 15 | float startTime, 16 | const char* fmt, 17 | std::initializer_list args); 18 | 19 | template 20 | void report(float startTime, const char* fmt, const Args&... args) 21 | { 22 | report(startTime, fmt, {str::formatArg::get(args)...}); 23 | } 24 | 25 | 26 | } 27 | 28 | 29 | #define DPSO_START_TIMING(name) \ 30 | const float name ## TimingStartTime = dpso::timing::getTime(); 31 | 32 | #define DPSO_END_TIMING(name, fmt, ...) \ 33 | dpso::timing::report(name ## TimingStartTime, fmt, __VA_ARGS__) 34 | 35 | 36 | #else 37 | 38 | 39 | #define DPSO_START_TIMING(name) 40 | #define DPSO_END_TIMING(name, fmt, ...) 41 | 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/dpso_utils/unix/dl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | 8 | namespace dpso::unix { 9 | 10 | 11 | struct DlHandleCloser { 12 | void operator()(void* handle) const 13 | { 14 | if (handle) 15 | dlclose(handle); 16 | } 17 | }; 18 | 19 | 20 | using DlHandleUPtr = std::unique_ptr; 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/dpso_utils/unix/exe_path.cpp: -------------------------------------------------------------------------------- 1 | #include "unix/exe_path.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "os.h" 8 | #include "str.h" 9 | #include "unix/path_env_search.h" 10 | 11 | 12 | namespace dpso::unix { 13 | 14 | 15 | std::string getExePath(const char* name) 16 | { 17 | std::string path; 18 | 19 | if (strchr(name, '/')) 20 | path = name; 21 | else { 22 | path = findInPathEnv(name); 23 | if (path.empty()) 24 | throw os::Error{str::format( 25 | "Can't find \"{}\" in $PATH", name)}; 26 | } 27 | 28 | auto* realPath = realpath(path.c_str(), nullptr); 29 | if (!realPath) 30 | throw os::Error{str::format( 31 | "realpath(\"{}\"): {}", path, os::getErrnoMsg(errno))}; 32 | 33 | std::string result = realPath; 34 | free(realPath); 35 | 36 | return result; 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/dpso_utils/unix/exe_path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::unix { 7 | 8 | 9 | // Return a canonical absolute path of the given executable. The name 10 | // can be either a basename (to be searched in $PATH) or a path 11 | // (absolute or relative). Throws os::Error. 12 | std::string getExePath(const char* name); 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/dpso_utils/unix/path_env_search.cpp: -------------------------------------------------------------------------------- 1 | #include "unix/path_env_search.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | namespace dpso::unix { 9 | 10 | 11 | std::string findInPathEnv(const char* name) 12 | { 13 | if (strchr(name, '/')) 14 | return {}; 15 | 16 | const auto* p = getenv("PATH"); 17 | if (!p || !*p) 18 | return {}; 19 | 20 | std::string path; 21 | while (true) { 22 | const auto* pathBegin = p; 23 | while (*p && *p != ':') 24 | ++p; 25 | const auto* pathEnd = p; 26 | 27 | path.assign(pathBegin, pathEnd); 28 | 29 | // An empty path is a legacy feature indicating the current 30 | // working directory. It can appear as two colons in the 31 | // middle of the list, as well a colon at the beginning or end 32 | // of the list. 33 | if (!path.empty() && path.back() != '/') 34 | path += '/'; 35 | 36 | path += name; 37 | 38 | if (access(path.c_str(), X_OK) == 0) 39 | return path; 40 | 41 | if (!*p) 42 | break; 43 | 44 | ++p; 45 | } 46 | 47 | return {}; 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/dpso_utils/unix/path_env_search.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::unix { 7 | 8 | 9 | // Search for the given executable in the paths from the PATH 10 | // environment variable. Returns an empty string if the executable is 11 | // not found, or if the executable name contains slashes. 12 | std::string findInPathEnv(const char* name); 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/dpso_utils/unix/xdg_dirs.cpp: -------------------------------------------------------------------------------- 1 | #include "unix/xdg_dirs.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "os.h" 7 | #include "str.h" 8 | 9 | 10 | namespace dpso::unix { 11 | 12 | 13 | const char* toStr(XdgDir dir) 14 | { 15 | #define CASE(DIR) case DIR: return #DIR 16 | 17 | switch (dir) { 18 | CASE(XdgDir::dataHome); 19 | CASE(XdgDir::configHome); 20 | } 21 | 22 | #undef CASE 23 | 24 | assert(false); 25 | return ""; 26 | } 27 | 28 | 29 | static std::string getDir( 30 | const char* xdgHomeEnv, const char* homeFallbackDir) 31 | { 32 | if (const auto* xdgHome = std::getenv(xdgHomeEnv); 33 | // According to the specification, a XDG_*_HOME variable: 34 | // * Should be treated as unset if empty 35 | // * Invalid if contains a relative path 36 | xdgHome && *xdgHome == '/') 37 | return xdgHome; 38 | 39 | if (const auto* home = std::getenv("HOME")) 40 | return std::string{home} + '/' + homeFallbackDir; 41 | 42 | throw os::Error{str::format( 43 | "Neither {} nor HOME environment variable is set", 44 | xdgHomeEnv)}; 45 | } 46 | 47 | 48 | std::string getXdgDirPath(XdgDir dir) 49 | { 50 | switch (dir) { 51 | case XdgDir::dataHome: 52 | return getDir("XDG_DATA_HOME", ".local/share"); 53 | case XdgDir::configHome: 54 | return getDir("XDG_CONFIG_HOME", ".config"); 55 | } 56 | 57 | assert(false); 58 | return {}; 59 | } 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/dpso_utils/unix/xdg_dirs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace dpso::unix { 7 | 8 | 9 | enum class XdgDir { 10 | dataHome, 11 | configHome 12 | }; 13 | 14 | 15 | const char* toStr(XdgDir dir); 16 | 17 | 18 | // Return an absolute path of a directory defined by XDG Base 19 | // Directory Specification: 20 | // https://specifications.freedesktop.org/basedir-spec/latest/ 21 | // Throws os::Error if neither a corresponding XDG_* nor HOME 22 | // environment variable is set. 23 | std::string getXdgDirPath(XdgDir dir); 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/dpso_utils/version_cmp.cpp: -------------------------------------------------------------------------------- 1 | #include "version_cmp.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | namespace dpso { 9 | 10 | 11 | VersionCmp::VersionCmp(const char* str) 12 | { 13 | const auto* s = str; 14 | const auto* sEnd = s + std::strlen(s); 15 | 16 | while (s < sEnd) { 17 | // Use unsigned so that from_chars() doesn't accept the minus 18 | // sign. 19 | unsigned num; 20 | const auto [end, ec] = std::from_chars(s, sEnd, num); 21 | if (ec != std::errc{}) { 22 | if (s > str) { 23 | // Make the preceding period a part of the extra 24 | // string. 25 | --s; 26 | assert(!nums.empty()); 27 | assert(*s == '.'); 28 | } 29 | 30 | break; 31 | } 32 | 33 | nums.push_back(num); 34 | 35 | s = end; 36 | 37 | if (*s == '.') 38 | ++s; 39 | else 40 | break; 41 | } 42 | 43 | extra = s; 44 | } 45 | 46 | 47 | bool VersionCmp::operator<(const VersionCmp& other) const 48 | { 49 | if (nums < other.nums) 50 | return true; 51 | 52 | if (nums > other.nums) 53 | return false; 54 | 55 | // 1.0-rc1 < 1.0 56 | if (!extra.empty() && other.extra.empty()) 57 | return true; 58 | 59 | // 1.0 > 1.0-rc1 60 | if (extra.empty() && !other.extra.empty()) 61 | return false; 62 | 63 | // 1.0-rc1 < 1.0-rc2 64 | return extra < other.extra; 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/dpso_utils/version_cmp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | namespace dpso { 8 | 9 | 10 | // A helper to compare version strings. 11 | // 12 | // A version string can contain zero or more numbers separated by 13 | // periods, and an optional trailing text. Leading zeros in numbers 14 | // are ignored. Trailing texts are compared lexicographically, but a 15 | // version with extra text is considered to be less than the same 16 | // version without extra text. For example: 17 | // 18 | // 1.0-rc1 < 1.0-rc2 < 1.0 19 | class VersionCmp { 20 | public: 21 | VersionCmp() = default; 22 | explicit VersionCmp(const char* str); 23 | 24 | bool operator<(const VersionCmp& other) const; 25 | private: 26 | std::vector nums; 27 | std::string extra; 28 | }; 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/dpso_utils/windows/cmdline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | namespace dpso::windows { 8 | 9 | 10 | // Create a command line using CommandLineToArgv() rules. 11 | // 12 | // Note that on Windows the program name is parsed differently from 13 | // the other parameters, and therefore comes as a separate programName 14 | // argument in this function. If you need to build the command-line 15 | // arguments without the program name (for example, to be used as 16 | // lpCommandLine for CreateProcess() when lpApplicationName is not 17 | // null), leave programName empty. 18 | std::string createCmdLine( 19 | const char* programName, 20 | const char* const* args, std::size_t numArgs); 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/dpso_utils/windows/error.cpp: -------------------------------------------------------------------------------- 1 | #include "windows/error.h" 2 | 3 | #include "str.h" 4 | 5 | 6 | namespace dpso::windows { 7 | 8 | 9 | std::string getErrorMessage(DWORD error, HMODULE module) 10 | { 11 | char* messageBuf{}; 12 | auto size = FormatMessageA( 13 | FORMAT_MESSAGE_ALLOCATE_BUFFER 14 | | (module ? FORMAT_MESSAGE_FROM_HMODULE : 0) 15 | | FORMAT_MESSAGE_FROM_SYSTEM 16 | | FORMAT_MESSAGE_IGNORE_INSERTS, 17 | module, 18 | error, 19 | MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), 20 | reinterpret_cast(&messageBuf), 21 | 0, nullptr); 22 | 23 | if (size == 0) 24 | return str::format("Windows error {}", error); 25 | 26 | if (size > 1 27 | && messageBuf[size - 2] == '\r' 28 | && messageBuf[size - 1] == '\n') 29 | size -= 2; 30 | 31 | std::string message{messageBuf, size}; 32 | 33 | LocalFree(messageBuf); 34 | 35 | return message; 36 | } 37 | 38 | 39 | std::string getHresultMessage(HRESULT hresult) 40 | { 41 | // It seems that FormatMessage() accepts HRESULT, at least 42 | // _com_error::ErrorMessage() relies on that. Still, this is not 43 | // documented, so we extract the system error code manually. 44 | if (HRESULT_FACILITY(hresult) == FACILITY_WIN32) 45 | return getErrorMessage(HRESULT_CODE(hresult)); 46 | 47 | return 48 | "HRESULT 0x" 49 | + str::rightJustify(str::toStr(hresult, 16), 8, '0'); 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/dpso_utils/windows/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define WIN32_LEAN_AND_MEAN 6 | #include 7 | 8 | 9 | namespace dpso::windows { 10 | 11 | 12 | std::string getErrorMessage(DWORD error, HMODULE module = {}); 13 | std::string getHresultMessage(HRESULT hresult); 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/dpso_utils/windows/module.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define WIN32_LEAN_AND_MEAN 6 | #include 7 | 8 | 9 | namespace dpso::windows { 10 | 11 | 12 | struct LibraryFreer { 13 | using pointer = HMODULE; 14 | 15 | void operator()(HMODULE module) const 16 | { 17 | FreeLibrary(module); 18 | } 19 | }; 20 | 21 | 22 | using ModuleUPtr = std::unique_ptr; 23 | 24 | 25 | // We use an intermediate cast to void* to silence GCC warnings about 26 | // converting FARPROC to an incompatible function type. 27 | #define DPSO_WIN_DLL_FN(MODULE, NAME, SIGN) \ 28 | auto NAME ## Fn = (SIGN)(void*)GetProcAddress(MODULE, #NAME) 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/dpso_utils/windows/utf.cpp: -------------------------------------------------------------------------------- 1 | #include "windows/utf.h" 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | 6 | #include "windows/error.h" 7 | 8 | 9 | namespace dpso::windows { 10 | 11 | 12 | static int utf8ToUtf16(const char* utf8Str, wchar_t* dst, int dstSize) 13 | { 14 | const auto sizeWithNull = MultiByteToWideChar( 15 | CP_UTF8, MB_ERR_INVALID_CHARS, utf8Str, -1, dst, dstSize); 16 | if (sizeWithNull <= 0) 17 | throw CharConversionError(getErrorMessage(GetLastError())); 18 | 19 | return sizeWithNull; 20 | } 21 | 22 | 23 | std::wstring utf8ToUtf16(const char* utf8Str) 24 | { 25 | const auto sizeWithNull = utf8ToUtf16(utf8Str, nullptr, 0); 26 | std::wstring result(sizeWithNull - 1, 0); 27 | utf8ToUtf16(utf8Str, result.data(), sizeWithNull); 28 | return result; 29 | } 30 | 31 | 32 | static int utf16ToUtf8( 33 | const wchar_t* utf16Str, char* dst, int dstSize) 34 | { 35 | const auto sizeWithNull = WideCharToMultiByte( 36 | CP_UTF8, WC_ERR_INVALID_CHARS, 37 | utf16Str, -1, 38 | dst, dstSize, 39 | nullptr, nullptr); 40 | if (sizeWithNull <= 0) 41 | throw CharConversionError(getErrorMessage(GetLastError())); 42 | 43 | return sizeWithNull; 44 | } 45 | 46 | 47 | std::string utf16ToUtf8(const wchar_t* utf16Str) 48 | { 49 | const auto sizeWithNull = utf16ToUtf8(utf16Str, nullptr, 0); 50 | std::string result(sizeWithNull - 1, 0); 51 | utf16ToUtf8(utf16Str, result.data(), sizeWithNull); 52 | return result; 53 | } 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/dpso_utils/windows/utf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | namespace dpso::windows { 8 | 9 | 10 | class CharConversionError : public std::runtime_error { 11 | using runtime_error::runtime_error; 12 | }; 13 | 14 | 15 | // Throws CharConversionError on invalid UTF-8 sequence. 16 | std::wstring utf8ToUtf16(const char* utf8Str); 17 | 18 | 19 | // Throws CharConversionError on invalid UTF-16 sequence. 20 | std::string utf16ToUtf8(const wchar_t* utf16Str); 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/dpso_utils/windows/window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define WIN32_LEAN_AND_MEAN 6 | #include 7 | 8 | 9 | namespace dpso::windows { 10 | 11 | 12 | struct WindowDestroyer { 13 | using pointer = HWND; 14 | 15 | void operator()(HWND window) const 16 | { 17 | DestroyWindow(window); 18 | } 19 | }; 20 | 21 | 22 | using WindowUPtr = std::unique_ptr; 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/thirdparty/stb_image_resize2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(stb_image_resize2 STATIC stb_image_resize2.c) 2 | target_include_directories(stb_image_resize2 PUBLIC .) 3 | -------------------------------------------------------------------------------- /src/thirdparty/stb_image_resize2/stb_image_resize2.c: -------------------------------------------------------------------------------- 1 | // AVX2 is available since 2013, so disable it in case someone runs 2 | // our binaries on an older hardware. 3 | #define STBIR_NO_AVX2 4 | 5 | #define STB_IMAGE_RESIZE_IMPLEMENTATION 6 | #include "stb_image_resize2.h" 7 | -------------------------------------------------------------------------------- /src/ui/qt/about.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | class QTextEdit; 7 | 8 | 9 | namespace ui::qt { 10 | 11 | 12 | class About : public QWidget { 13 | Q_OBJECT 14 | public: 15 | About(); 16 | signals: 17 | void checkUpdates(); 18 | private slots: 19 | void handleLinkActivation(const QString &link); 20 | void handleLinkHover(const QString &link); 21 | private: 22 | QString currentTextLink; 23 | QTextEdit* textEdit; 24 | }; 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/ui/qt/action_chooser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "dpso_ext/dpso_ext.h" 8 | 9 | 10 | class QCheckBox; 11 | class QLineEdit; 12 | 13 | 14 | namespace ui::qt { 15 | 16 | 17 | class ActionChooser : public QWidget { 18 | Q_OBJECT 19 | public: 20 | enum Action { 21 | none = 0, 22 | copyToClipboard = 1 << 0, 23 | addToHistory = 1 << 1, 24 | runExe = 1 << 2 25 | }; 26 | 27 | Q_DECLARE_FLAGS(Actions, Action) 28 | 29 | ActionChooser(); 30 | 31 | Actions getSelectedActions() const; 32 | const char* getExePath() const; 33 | 34 | void loadState(const DpsoCfg* cfg); 35 | void saveState(DpsoCfg* cfg) const; 36 | signals: 37 | void actionsChanged(); 38 | private slots: 39 | void chooseExe(); 40 | private: 41 | QCheckBox* copyToClipboardCheck; 42 | QCheckBox* addToHistoryCheck; 43 | QCheckBox* runExeCheck; 44 | QLineEdit* exeLineEdit; 45 | QByteArray exePath; 46 | }; 47 | 48 | Q_DECLARE_OPERATORS_FOR_FLAGS(ActionChooser::Actions) 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/ui/qt/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace ui::qt { 7 | 8 | 9 | class Error : public std::runtime_error { 10 | using runtime_error::runtime_error; 11 | }; 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/ui/qt/history.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "dpso_ext/dpso_ext.h" 10 | 11 | 12 | class QPushButton; 13 | class QTextEdit; 14 | 15 | 16 | namespace ui::qt { 17 | 18 | 19 | class History : public QWidget { 20 | Q_OBJECT 21 | public: 22 | explicit History(const std::string& dirPath); 23 | 24 | void append(const char* timestamp, const char* text); 25 | 26 | void loadState(const DpsoCfg* cfg); 27 | void saveState(DpsoCfg* cfg) const; 28 | private slots: 29 | void doExport(); 30 | void clear(); 31 | private: 32 | std::string historyFilePath; 33 | dpso::HistoryUPtr history; 34 | 35 | bool wrapWords{}; 36 | 37 | QTextEdit* textEdit; 38 | QTextCharFormat charFormat; 39 | QTextBlockFormat blockFormat; 40 | int blockMargin{}; 41 | 42 | QPushButton* exportButton; 43 | QPushButton* clearButton; 44 | 45 | QString lastDirPath; 46 | QString lastFileName; 47 | QString selectedNameFilter; 48 | 49 | void setButtonsEnabled(bool enabled); 50 | void appendToTextEdit(const char* timestamp, const char* text); 51 | }; 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/ui/qt/lang_browser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "dpso_ext/dpso_ext.h" 6 | #include "dpso_ocr/dpso_ocr.h" 7 | 8 | 9 | class QTreeWidgetItem; 10 | 11 | 12 | namespace ui::qt { 13 | 14 | 15 | class LangBrowser : public QTreeWidget { 16 | Q_OBJECT 17 | public: 18 | explicit LangBrowser(DpsoOcr* ocr); 19 | 20 | void reloadLangs(); 21 | 22 | void loadState(const DpsoCfg* cfg); 23 | void saveState(DpsoCfg* cfg) const; 24 | private slots: 25 | void updateLangState(QTreeWidgetItem* item, int column); 26 | void selectCheckboxColumn(QTreeWidgetItem* current); 27 | private: 28 | DpsoOcr* ocr; 29 | }; 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/ui/qt/lang_manager/install_mode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace ui::qt::langManager { 5 | 6 | 7 | // The installation and update are the same in terms of the language 8 | // manager API. We use this enum when we need to parameterize widgets 9 | // that work with installation and therefore only differ visually, 10 | // e.g. showing different text depending on the mode. 11 | enum class InstallMode { 12 | install, 13 | update 14 | }; 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/ui/qt/lang_manager/install_progress_dialog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dpso_ocr/dpso_ocr.h" 4 | 5 | #include "lang_manager/install_mode.h" 6 | 7 | 8 | class QWidget; 9 | 10 | 11 | namespace ui::qt::langManager { 12 | 13 | 14 | void runInstallProgressDialog( 15 | QWidget* parent, 16 | DpsoOcrLangManager* langManager, 17 | InstallMode installMode); 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/ui/qt/lang_manager/lang_list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "dpso_ocr/dpso_ocr.h" 7 | 8 | 9 | namespace ui::qt::langManager { 10 | 11 | 12 | class LangList : public QAbstractTableModel { 13 | Q_OBJECT 14 | public: 15 | enum { 16 | columnIdxName, 17 | columnIdxCode, 18 | columnIdxState, 19 | columnIdxExternalSize, 20 | columnIdxLocalSize, 21 | }; 22 | 23 | explicit LangList( 24 | DpsoOcrLangManager* langManager, QObject* parent = nullptr); 25 | 26 | DpsoOcrLangManager* getLangManager(); 27 | 28 | void reloadLangs(); 29 | 30 | QVariant data( 31 | const QModelIndex& index, 32 | int role = Qt::DisplayRole) const override; 33 | 34 | QVariant headerData( 35 | int section, 36 | Qt::Orientation orientation, 37 | int role = Qt::DisplayRole) const override; 38 | 39 | int rowCount( 40 | const QModelIndex& parent = QModelIndex()) const override; 41 | 42 | int columnCount( 43 | const QModelIndex& parent = QModelIndex()) const override; 44 | private: 45 | DpsoOcrLangManager* langManager; 46 | 47 | struct LangInfo { 48 | QString name; 49 | QString code; 50 | DpsoOcrLangState state; 51 | DpsoOcrLangSize size; 52 | }; 53 | QList langInfos; 54 | }; 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/ui/qt/lang_manager/lang_list_sort_filter_proxy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace ui::qt::langManager { 7 | 8 | 9 | class LangListSortFilterProxy : public QSortFilterProxyModel { 10 | Q_OBJECT 11 | public: 12 | enum { 13 | columnIdxName, 14 | columnIdxCode, 15 | columnIdxSize, 16 | }; 17 | 18 | enum class LangGroup { 19 | installable, 20 | updatable, 21 | removable 22 | }; 23 | 24 | explicit LangListSortFilterProxy( 25 | LangGroup langGroup, QObject* parent = nullptr); 26 | 27 | QVariant headerData( 28 | int section, 29 | Qt::Orientation orientation, 30 | int role = Qt::DisplayRole) const override; 31 | public slots: 32 | void setFilterText(const QString& newFilterText); 33 | protected: 34 | bool filterAcceptsColumn( 35 | int sourceColumn, 36 | const QModelIndex& sourceParent) const override; 37 | 38 | bool filterAcceptsRow( 39 | int sourceRow, 40 | const QModelIndex& sourceParent) const override; 41 | 42 | bool lessThan( 43 | const QModelIndex& sourceLeft, 44 | const QModelIndex& sourceRight) const override; 45 | private: 46 | LangGroup langGroup; 47 | QString filterText; 48 | }; 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/ui/qt/lang_manager/lang_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | class QWidget; 7 | 8 | 9 | namespace ui::qt::langManager { 10 | 11 | 12 | void runLangManager(QWidget* parent, int ocrEngineIdx); 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/ui/qt/lang_manager/lang_manager_page.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | class QWidget; 5 | 6 | 7 | namespace ui::qt::langManager { 8 | 9 | 10 | class LangList; 11 | 12 | 13 | QWidget* createLangManagerInstallPage(LangList& langList); 14 | QWidget* createLangManagerUpdatePage(LangList& langList); 15 | QWidget* createLangManagerRemovePage(LangList& langList); 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/ui/qt/lang_manager/lang_op_status_error.cpp: -------------------------------------------------------------------------------- 1 | #include "lang_manager/lang_op_status_error.h" 2 | 3 | #include 4 | 5 | #include "dpso_intl/dpso_intl.h" 6 | 7 | #include "utils.h" 8 | 9 | 10 | #define _(S) gettext(S) 11 | 12 | 13 | namespace ui::qt::langManager { 14 | 15 | 16 | void showLangOpStatusError( 17 | QWidget* parent, 18 | const QString& text, 19 | const DpsoOcrLangOpStatus& status) 20 | { 21 | QString informativeText; 22 | 23 | switch (status.code) { 24 | case DpsoOcrLangOpStatusCodeNone: 25 | case DpsoOcrLangOpStatusCodeProgress: 26 | case DpsoOcrLangOpStatusCodeSuccess: 27 | return; 28 | case DpsoOcrLangOpStatusCodeGenericError: 29 | break; 30 | case DpsoOcrLangOpStatusCodeNetworkConnectionError: 31 | informativeText = 32 | _("Check your network connection and try again."); 33 | break; 34 | } 35 | 36 | showError(parent, text, informativeText, status.errorText); 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/ui/qt/lang_manager/lang_op_status_error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dpso_ocr/dpso_ocr.h" 4 | 5 | 6 | class QString; 7 | class QWidget; 8 | 9 | 10 | namespace ui::qt::langManager { 11 | 12 | 13 | // Does nothing if the status code is not an error. 14 | void showLangOpStatusError( 15 | QWidget* parent, 16 | const QString& text, 17 | const DpsoOcrLangOpStatus& status); 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/ui/qt/lang_manager/metatypes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "dpso_ocr/dpso_ocr.h" 6 | 7 | 8 | Q_DECLARE_METATYPE(DpsoOcrLangState) 9 | -------------------------------------------------------------------------------- /src/ui/qt/status.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace ui::qt { 5 | 6 | 7 | enum class Status { 8 | ok, 9 | busy, 10 | error 11 | }; 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/ui/qt/status_indicator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "status.h" 6 | 7 | 8 | namespace ui::qt { 9 | 10 | 11 | class StatusIndicator: public QWidget { 12 | Q_OBJECT 13 | public: 14 | void setStatus(Status newStatus); 15 | 16 | QSize minimumSizeHint() const override; 17 | QSize sizeHint() const override; 18 | protected: 19 | void paintEvent(QPaintEvent* event) override; 20 | private: 21 | Status status{}; 22 | }; 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/ui/qt/update_checker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "dpso_ext/dpso_ext.h" 10 | #include "ui_common/ui_common.h" 11 | 12 | 13 | class QWidget; 14 | 15 | 16 | namespace ui::qt { 17 | 18 | 19 | class UpdateChecker : public QObject { 20 | Q_OBJECT 21 | public: 22 | // UpdateChecker will not display an update info window while a 23 | // modal or popup widget is active. The isBusy() function allows 24 | // you to add additional checks for this case. 25 | explicit UpdateChecker( 26 | QWidget* parent, const std::function& isBusy = {}); 27 | 28 | bool getAutoCheckIsEnabled() const; 29 | int getAutoCheckIntervalDays() const; 30 | 31 | void loadState(const DpsoCfg* cfg); 32 | void saveState(DpsoCfg* cfg) const; 33 | public slots: 34 | void setAutoCheckIsEnabled(bool isEnabled); 35 | void checkUpdates(); 36 | protected: 37 | void timerEvent(QTimerEvent* event) override; 38 | private: 39 | QWidget* parentWidget; 40 | std::function isBusy; 41 | int timerId{}; 42 | UpdateCheckerUPtr autoChecker; 43 | bool autoCheck{}; 44 | int autoCheckIntervalDays{}; 45 | std::optional lastCheckTime; 46 | 47 | void handleAutoCheck(); 48 | void stopAutoCheck(); 49 | }; 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/ui/qt/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "ui_common/ui_common.h" 6 | 7 | #include 8 | 9 | 10 | class QFont; 11 | class QMargins; 12 | class QWidget; 13 | 14 | 15 | // Formatting support for ui::strNFormat(). 16 | std::string toStr(const QString& s); 17 | 18 | 19 | namespace ui::qt { 20 | 21 | 22 | QString strNFormat( 23 | const char* str, std::initializer_list args); 24 | 25 | 26 | QString formatDataSize(qint64 size); 27 | 28 | 29 | QString joinInLayoutDirection( 30 | const QString& separator, const QStringList& list); 31 | 32 | 33 | QMargins makeSubordinateControlMargins(); 34 | 35 | 36 | QIcon getIcon(const QString &name); 37 | 38 | 39 | // The function is the same as getIcon(), except that on Unix-like 40 | // systems it first tries to load the icon from the current theme. 41 | QIcon getThemeIcon(const QString &name); 42 | 43 | 44 | // Display a progress dialog that will be closed when the callback 45 | // returns false. 46 | void showProgressDialog( 47 | QWidget* parent, 48 | const QString& text, 49 | const std::function& callback); 50 | 51 | 52 | bool confirmDestructiveAction( 53 | QWidget* parent, 54 | const QString& question, 55 | const QString& cancelText, 56 | const QString& okText); 57 | 58 | 59 | void showError( 60 | QWidget* parent, 61 | const QString& text, 62 | const QString& informativeText = {}, 63 | const QString& detailedText = {}); 64 | 65 | 66 | void setMonospace(QFont& font); 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/ui/ui_common/app_dirs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | typedef enum { 10 | /** 11 | * General data directory. 12 | */ 13 | UiAppDirData, 14 | 15 | /** 16 | * Directory with documents like the user manual, license, etc. 17 | */ 18 | UiAppDirDoc, 19 | 20 | /** 21 | * Localization data for bindtextdomain(). 22 | */ 23 | UiAppDirLocale 24 | } UiAppDir; 25 | 26 | 27 | /** 28 | * Get app directory path. 29 | * 30 | * The returned string is valid till the next call to uiGetDir(). 31 | */ 32 | const char* uiGetAppDir(UiAppDir dir); 33 | 34 | 35 | #ifdef __cplusplus 36 | } 37 | #endif 38 | -------------------------------------------------------------------------------- /src/ui/ui_common/app_dirs_unix_cfg.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | const char* const unixAppDirData = 5 | "@CMAKE_INSTALL_DATADIR@/@APP_FILE_NAME@"; 6 | const char* const unixAppDirDoc = "@CMAKE_INSTALL_DOCDIR@"; 7 | const char* const unixAppDirLocale = "@CMAKE_INSTALL_LOCALEDIR@"; 8 | -------------------------------------------------------------------------------- /src/ui/ui_common/app_dirs_windows.cpp: -------------------------------------------------------------------------------- 1 | #include "app_dirs.h" 2 | #include "init_app_dirs.h" 3 | 4 | #include "exe_path.h" 5 | 6 | 7 | static std::string baseDirPath; 8 | 9 | 10 | namespace ui { 11 | 12 | 13 | bool initAppDirs() 14 | { 15 | baseDirPath = getExePath(); 16 | 17 | const auto slashPos = baseDirPath.rfind('\\'); 18 | if (slashPos != baseDirPath.npos) 19 | baseDirPath.resize(slashPos); 20 | 21 | return true; 22 | } 23 | 24 | 25 | } 26 | 27 | 28 | const char* uiGetAppDir(UiAppDir dir) 29 | { 30 | static std::string result; 31 | result = baseDirPath; 32 | 33 | switch (dir) { 34 | case UiAppDirData: 35 | break; 36 | case UiAppDirDoc: 37 | result += "\\doc"; 38 | break; 39 | case UiAppDirLocale: 40 | result += "\\locale"; 41 | break; 42 | } 43 | 44 | return result.c_str(); 45 | } 46 | -------------------------------------------------------------------------------- /src/ui/ui_common/app_info.cpp.in: -------------------------------------------------------------------------------- 1 | #include "app_info.h" 2 | 3 | 4 | const char* const uiAppName = "@APP_NAME@"; 5 | const char* const uiAppVersion = "@APP_VERSION@"; 6 | const char* const uiAppWebsite = "@APP_URL@"; 7 | 8 | #define COPYRIGHT "\302\251 @APP_COPYRIGHT_YEAR@ @APP_AUTHOR@" 9 | const char* const uiAppCopyright = COPYRIGHT; 10 | const char* const uiAppLicense = 11 | COPYRIGHT "\n" 12 | "\n" 13 | "This software is provided 'as-is', without any express or implied\n" 14 | "warranty. In no event will the authors be held liable for any damages\n" 15 | "arising from the use of this software.\n" 16 | "\n" 17 | "Permission is granted to anyone to use this software for any purpose,\n" 18 | "including commercial applications, and to alter it and redistribute it\n" 19 | "freely, subject to the following restrictions:\n" 20 | "\n" 21 | "1. The origin of this software must not be misrepresented; you must not\n" 22 | " claim that you wrote the original software. If you use this software\n" 23 | " in a product, an acknowledgement in the product documentation would be\n" 24 | " appreciated but is not required.\n" 25 | "2. Altered source versions must be plainly marked as such, and must not be\n" 26 | " misrepresented as being the original software.\n" 27 | "3. This notice may not be removed or altered from any source distribution.\n" 28 | ; 29 | -------------------------------------------------------------------------------- /src/ui/ui_common/app_info.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | extern const char* const uiAppName; 10 | extern const char* const uiAppVersion; 11 | extern const char* const uiAppWebsite; 12 | extern const char* const uiAppCopyright; 13 | extern const char* const uiAppLicense; 14 | 15 | 16 | #ifdef __cplusplus 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /src/ui/ui_common/autostart_default.cpp: -------------------------------------------------------------------------------- 1 | #include "autostart_default.h" 2 | 3 | #include 4 | 5 | #include "app_info.h" 6 | #include "cmdline_opts.h" 7 | #include "exe_path.h" 8 | #include "file_names.h" 9 | 10 | 11 | UiAutostart* uiAutostartCreateDefault(void) 12 | { 13 | const char* args[]{ 14 | ui::getToplevelExePath().c_str(), ui::cmdLineOptHide}; 15 | 16 | const UiAutostartArgs autostartArgs{ 17 | uiAppName, uiAppFileName, args, std::size(args)}; 18 | 19 | return uiAutostartCreate(&autostartArgs); 20 | } 21 | -------------------------------------------------------------------------------- /src/ui/ui_common/autostart_default.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "autostart.h" 4 | 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | 11 | UiAutostart* uiAutostartCreateDefault(void); 12 | 13 | 14 | #ifdef __cplusplus 15 | } 16 | #endif 17 | -------------------------------------------------------------------------------- /src/ui/ui_common/cfg_default_values.cpp: -------------------------------------------------------------------------------- 1 | /* This file was automatically generated. Do not edit. */ 2 | 3 | #include "cfg_default_values.h" 4 | 5 | 6 | bool const cfgDefaultValueActionAddToHistory = 7 | true; 8 | bool const cfgDefaultValueActionCopyToClipboard = 9 | false; 10 | const char* const cfgDefaultValueActionCopyToClipboardTextSeparator = 11 | "\n\n\n"; 12 | bool const cfgDefaultValueActionRunExecutable = 13 | false; 14 | bool const cfgDefaultValueActionsDonePlaySound = 15 | false; 16 | bool const cfgDefaultValueActionsDonePlaySoundCustom = 17 | false; 18 | bool const cfgDefaultValueHistoryWrapWords = 19 | true; 20 | DpsoHotkey const cfgDefaultValueHotkeyCancelSelection = 21 | {dpsoKeyEscape, dpsoNoKeyMods}; 22 | DpsoHotkey const cfgDefaultValueHotkeyToggleSelection = 23 | {dpsoKeyGrave, dpsoKeyModCtrl}; 24 | bool const cfgDefaultValueOcrAllowQueuing = 25 | true; 26 | bool const cfgDefaultValueOcrSplitTextBlocks = 27 | true; 28 | bool const cfgDefaultValueUiTrayIconVisible = 29 | true; 30 | bool const cfgDefaultValueUiWindowCloseToTray = 31 | false; 32 | bool const cfgDefaultValueUiWindowMinimizeOnStart = 33 | false; 34 | bool const cfgDefaultValueUiWindowMinimizeToTray = 35 | false; 36 | bool const cfgDefaultValueUpdateCheckAuto = 37 | true; 38 | int const cfgDefaultValueUpdateCheckAutoIntervalDays = 39 | 7; 40 | -------------------------------------------------------------------------------- /src/ui/ui_common/cfg_default_values.h: -------------------------------------------------------------------------------- 1 | /* This file was automatically generated. Do not edit. */ 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include "dpso_sys/dpso_sys.h" 8 | 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | 15 | extern bool const cfgDefaultValueActionAddToHistory; 16 | extern bool const cfgDefaultValueActionCopyToClipboard; 17 | extern const char* const cfgDefaultValueActionCopyToClipboardTextSeparator; 18 | extern bool const cfgDefaultValueActionRunExecutable; 19 | extern bool const cfgDefaultValueActionsDonePlaySound; 20 | extern bool const cfgDefaultValueActionsDonePlaySoundCustom; 21 | extern bool const cfgDefaultValueHistoryWrapWords; 22 | extern DpsoHotkey const cfgDefaultValueHotkeyCancelSelection; 23 | extern DpsoHotkey const cfgDefaultValueHotkeyToggleSelection; 24 | extern bool const cfgDefaultValueOcrAllowQueuing; 25 | extern bool const cfgDefaultValueOcrSplitTextBlocks; 26 | extern bool const cfgDefaultValueUiTrayIconVisible; 27 | extern bool const cfgDefaultValueUiWindowCloseToTray; 28 | extern bool const cfgDefaultValueUiWindowMinimizeOnStart; 29 | extern bool const cfgDefaultValueUiWindowMinimizeToTray; 30 | extern bool const cfgDefaultValueUpdateCheckAuto; 31 | extern int const cfgDefaultValueUpdateCheckAutoIntervalDays; 32 | 33 | 34 | #ifdef __cplusplus 35 | } 36 | #endif 37 | -------------------------------------------------------------------------------- /src/ui/ui_common/cfg_key_values.txt: -------------------------------------------------------------------------------- 1 | action_add_to_history | bool | true 2 | action_copy_to_clipboard | bool | false 3 | action_copy_to_clipboard_text_separator | const char* | "\n\n\n" 4 | action_run_executable | bool | false 5 | action_run_executable_path 6 | actions_done_play_sound | bool | false 7 | actions_done_play_sound_custom | bool | false 8 | actions_done_play_sound_custom_path 9 | history_export_dir 10 | history_wrap_words | bool | true 11 | hotkey_cancel_selection | DpsoHotkey | {dpsoKeyEscape, dpsoNoKeyMods} 12 | hotkey_toggle_selection | DpsoHotkey | {dpsoKeyGrave, dpsoKeyModCtrl} 13 | ocr_allow_queuing | bool | true 14 | ocr_languages 15 | ocr_split_text_blocks | bool | true 16 | selection_border_width 17 | ui_active_tab 18 | ui_languages_sort_column 19 | ui_languages_sort_descending 20 | ui_tray_icon_visible | bool | true 21 | ui_window_close_to_tray | bool | false 22 | ui_window_height 23 | ui_window_maximized 24 | ui_window_minimize_on_start | bool | false 25 | ui_window_minimize_to_tray | bool | false 26 | ui_window_width 27 | ui_window_x 28 | ui_window_y 29 | update_check_auto | bool | true 30 | update_check_auto_interval_days | int | 7 31 | update_check_last_time 32 | -------------------------------------------------------------------------------- /src/ui/ui_common/cmdline_cmd_autostart.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace ui { 5 | 6 | 7 | // On failure, sets an error message (dpsoGetError()) and returns 8 | // false. 9 | bool cmdLineCmdAutostart(const char* argv0, const char* action); 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/ui/ui_common/cmdline_opts.cpp: -------------------------------------------------------------------------------- 1 | #include "cmdline_opts.h" 2 | 3 | 4 | namespace ui { 5 | 6 | 7 | const char* const cmdLineOptHide = "-hide"; 8 | 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/ui/ui_common/cmdline_opts.h: -------------------------------------------------------------------------------- 1 | // Names of command line options used in more than one file. 2 | 3 | #pragma once 4 | 5 | 6 | namespace ui { 7 | 8 | 9 | extern const char* const cmdLineOptHide; 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/ui/ui_common/exe_path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace ui { 7 | 8 | 9 | // This function is called as a part of uiInit(). On failure, sets an 10 | // error message (dpsoGetError()) and returns false. 11 | bool initExePath(const char* argv0); 12 | 13 | 14 | // Return a canonical absolute path of the toplevel executable 15 | // launched by the user. 16 | // 17 | // On some platforms, an application may be packaged in a way that 18 | // requires the use of a launcher program that sets up an environment 19 | // for the final executable. This function is intended for cases where 20 | // it's important to use the path of the launcher rather than the 21 | // final executable, such as when adding the application to the 22 | // autostart. 23 | // 24 | // If the application doesn't use a launcher, the function returns the 25 | // path of the running executable, i.e. the same as getExePath(). 26 | const std::string& getToplevelExePath(); 27 | 28 | 29 | // Return a canonical absolute path of the current executable, or an 30 | // empty string if uiInit() wasn't called. 31 | const std::string& getExePath(); 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/ui/ui_common/exe_path_unix.cpp: -------------------------------------------------------------------------------- 1 | #include "exe_path.h" 2 | 3 | #include "dpso_utils/error_set.h" 4 | #include "dpso_utils/os.h" 5 | #include "dpso_utils/unix/exe_path.h" 6 | 7 | #include "toplevel_argv0.h" 8 | 9 | 10 | namespace ui { 11 | namespace { 12 | 13 | 14 | std::string toplevelExePath; 15 | std::string exePath; 16 | 17 | 18 | bool initExePath(std::string& path, const char* argv0) 19 | { 20 | try { 21 | path = dpso::unix::getExePath(argv0); 22 | return true; 23 | } catch (dpso::os::Error& e) { 24 | dpso::setError( 25 | "unix::getExePath(\"{}\"): {}", argv0, e.what()); 26 | return false; 27 | } 28 | } 29 | 30 | 31 | } 32 | 33 | 34 | bool initExePath(const char* argv0) 35 | { 36 | return 37 | initExePath(toplevelExePath, getToplevelArgv0(argv0)) 38 | && initExePath(exePath, argv0); 39 | } 40 | 41 | 42 | const std::string& getToplevelExePath() 43 | { 44 | return toplevelExePath; 45 | } 46 | 47 | 48 | const std::string& getExePath() 49 | { 50 | return exePath; 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/ui/ui_common/exe_path_windows.cpp: -------------------------------------------------------------------------------- 1 | #include "exe_path.h" 2 | 3 | #include 4 | 5 | #define WIN32_LEAN_AND_MEAN 6 | #include 7 | 8 | #include "dpso_utils/error_set.h" 9 | #include "dpso_utils/windows/error.h" 10 | #include "dpso_utils/windows/utf.h" 11 | 12 | 13 | namespace ui { 14 | 15 | 16 | static std::string exePath; 17 | 18 | 19 | bool initExePath(const char* argv0) 20 | { 21 | (void)argv0; 22 | 23 | std::wstring path; 24 | 25 | while (true) { 26 | path.reserve(path.size() + 32); 27 | path.resize(path.capacity()); 28 | 29 | const auto size = GetModuleFileNameW( 30 | nullptr, path.data(), path.size()); 31 | 32 | if (size == 0) { 33 | dpso::setError( 34 | "GetModuleFileNameW(): {}", 35 | dpso::windows::getErrorMessage(GetLastError())); 36 | return false; 37 | } 38 | 39 | if (size < path.size()) { 40 | path.resize(size); 41 | break; 42 | } 43 | } 44 | 45 | exePath = dpso::windows::utf16ToUtf8(path.c_str()); 46 | return true; 47 | } 48 | 49 | 50 | const std::string& getToplevelExePath() 51 | { 52 | return exePath; 53 | } 54 | 55 | 56 | const std::string& getExePath() 57 | { 58 | return exePath; 59 | } 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/ui/ui_common/file_names.cpp.in: -------------------------------------------------------------------------------- 1 | #include "file_names.h" 2 | 3 | 4 | #define APP_FILE_NAME "@APP_FILE_NAME@" 5 | 6 | const char* const uiAppFileName = APP_FILE_NAME; 7 | const char* const uiCfgFileName = "settings.cfg"; 8 | const char* const uiHistoryFileName = "history.txt"; 9 | 10 | const char* const uiIconNameApp = APP_FILE_NAME; 11 | const char* const uiIconNameAppBusy = APP_FILE_NAME "-busy"; 12 | const char* const uiIconNameAppError = APP_FILE_NAME "-error"; 13 | -------------------------------------------------------------------------------- /src/ui/ui_common/file_names.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | 9 | extern const char* const uiAppFileName; 10 | extern const char* const uiCfgFileName; 11 | extern const char* const uiHistoryFileName; 12 | 13 | /* Main app icon. */ 14 | extern const char* const uiIconNameApp; 15 | 16 | /* uiIconNameApp variants adjusted to display status. */ 17 | extern const char* const uiIconNameAppBusy; 18 | extern const char* const uiIconNameAppError; 19 | 20 | 21 | #ifdef __cplusplus 22 | } 23 | #endif 24 | -------------------------------------------------------------------------------- /src/ui/ui_common/init.cpp: -------------------------------------------------------------------------------- 1 | #include "init.h" 2 | 3 | #include "exe_path.h" 4 | #include "init_app_dirs.h" 5 | #include "init_extra.h" 6 | #include "init_intl.h" 7 | #include "init_startup_args.h" 8 | #include "init_user_data.h" 9 | 10 | #include "dpso_utils/error_get.h" 11 | #include "dpso_utils/error_set.h" 12 | 13 | 14 | bool uiInit(int argc, char* argv[], UiStartupArgs* startupArgs) 15 | { 16 | if (!startupArgs) { 17 | dpso::setError("startupArgs is null"); 18 | return false; 19 | } 20 | 21 | *startupArgs = ui::initStartupArgs(argc, argv); 22 | 23 | if (!ui::initStart(argc, argv)) { 24 | dpso::setError("ui::initStart: {}", dpsoGetError()); 25 | return false; 26 | } 27 | 28 | if (!ui::initExePath(argv[0])) { 29 | dpso::setError("ui::initExePath: {}", dpsoGetError()); 30 | return false; 31 | } 32 | 33 | if (!ui::initAppDirs()) { 34 | dpso::setError("ui::initAppDirs: {}", dpsoGetError()); 35 | return false; 36 | } 37 | 38 | ui::initIntl(); 39 | 40 | if (!ui::initUserData()) { 41 | dpso::setError("ui::initUserData: {}", dpsoGetError()); 42 | return false; 43 | } 44 | 45 | if (!ui::initEnd(argc, argv)) { 46 | dpso::setError("ui::initEnd: {}", dpsoGetError()); 47 | return false; 48 | } 49 | 50 | return true; 51 | } 52 | -------------------------------------------------------------------------------- /src/ui/ui_common/init.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "startup_args.h" 6 | 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | 13 | /** 14 | * Initialize the UI library. 15 | * 16 | * On failure, sets an error message (dpsoGetError()) and returns 17 | * false. 18 | */ 19 | bool uiInit(int argc, char* argv[], UiStartupArgs* startupArgs); 20 | 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | -------------------------------------------------------------------------------- /src/ui/ui_common/init_app_dirs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace ui { 5 | 6 | 7 | // Initialize app directory paths. 8 | // 9 | // On failure, sets an error message (dpsoGetError()) and returns 10 | // false. 11 | bool initAppDirs(); 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/ui/ui_common/init_extra.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace ui { 5 | 6 | 7 | // The initStart/End() functions are the place for additional 8 | // platform-specific logic during uiInit(). initStart() is the first 9 | // function called by uiInit(); initEnd() is the last. On failure, 10 | // both should set an error message (dpsoGetError()) and return false. 11 | bool initStart(int argc, char* argv[]); 12 | bool initEnd(int argc, char* argv[]); 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/ui/ui_common/init_extra_windows.cpp: -------------------------------------------------------------------------------- 1 | #include "init_extra.h" 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | 6 | 7 | namespace ui { 8 | 9 | 10 | // The main goal of registering restart is to give the installer 11 | // (e.g. Inno Setup) an ability to restart our application in case it 12 | // was automatically closed before installing an update. 13 | static void registerApplicationRestart() 14 | { 15 | const auto* cmdLine = GetCommandLineW(); 16 | // The command line is actually never empty, but check anyway for 17 | // the code below. 18 | if (!*cmdLine) 19 | return; 20 | 21 | // RegisterApplicationRestart() doesn't need the path to the 22 | // executable, so skip it. It may be in double quotes if it 23 | // contains spaces. 24 | const auto endChar = *cmdLine++ == L'\"' ? L'\"' : L' '; 25 | while (*cmdLine) 26 | if (*cmdLine++ == endChar) 27 | break; 28 | 29 | while (*cmdLine == L' ') 30 | ++cmdLine; 31 | 32 | RegisterApplicationRestart( 33 | cmdLine, RESTART_NO_CRASH | RESTART_NO_HANG); 34 | } 35 | 36 | 37 | bool initStart(int argc, char* argv[]) 38 | { 39 | (void)argc; 40 | (void)argv; 41 | 42 | return true; 43 | } 44 | 45 | 46 | bool initEnd(int argc, char* argv[]) 47 | { 48 | (void)argc; 49 | (void)argv; 50 | 51 | registerApplicationRestart(); 52 | return true; 53 | } 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/ui/ui_common/init_intl.cpp: -------------------------------------------------------------------------------- 1 | #include "init_intl.h" 2 | 3 | // On Windows, libintl patches setlocale with a macro that expands to 4 | // libintl_setlocale. std::setlocale becomes std::libintl_setlocale, 5 | // so we must use rather than . 6 | #include 7 | 8 | #include "dpso_intl/dpso_intl.h" 9 | 10 | #include "app_dirs.h" 11 | #include "file_names.h" 12 | 13 | 14 | namespace ui { 15 | 16 | 17 | void initIntl(void) 18 | { 19 | setlocale(LC_ALL, ""); 20 | 21 | bindtextdomainUtf8(uiAppFileName, uiGetAppDir(UiAppDirLocale)); 22 | bind_textdomain_codeset(uiAppFileName, "UTF-8"); 23 | textdomain(uiAppFileName); 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/ui/ui_common/init_intl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace ui { 5 | 6 | 7 | // Initialize libintl. 8 | // 9 | // The function performs all steps needed to set up libintl, such as 10 | // calling setlocale(), bindtextdomain(), etc. It depends on 11 | // UiAppDirLocale, so uiInitAppDirs() must be called before use. 12 | void initIntl(); 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/ui/ui_common/init_startup_args.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "startup_args.h" 4 | 5 | 6 | namespace ui { 7 | 8 | 9 | UiStartupArgs initStartupArgs(int argc, char* argv[]); 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/ui/ui_common/init_user_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace ui { 5 | 6 | 7 | // Initialize default data in the user data directory, such as OCR 8 | // engine language files. On failure, sets an error message 9 | // (dpsoGetError()) and returns false. 10 | bool initUserData(); 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/ui/ui_common/ocr_default.cpp: -------------------------------------------------------------------------------- 1 | #include "ocr_default.h" 2 | 3 | #include 4 | 5 | #include "app_info.h" 6 | #include "ocr_default_data_dir.h" 7 | #include "user_agent.h" 8 | 9 | 10 | DpsoOcr* dpsoOcrCreateDefault(int engineIdx) 11 | { 12 | DpsoOcrEngineInfo engineInfo; 13 | dpsoOcrGetEngineInfo(engineIdx, &engineInfo); 14 | 15 | const auto dataDir = ui::getDefaultOcrDataDir(engineInfo); 16 | if (!dataDir) 17 | return {}; 18 | 19 | return dpsoOcrCreate(engineIdx, dataDir->c_str()); 20 | } 21 | 22 | 23 | static std::string getInfoFileUrl(const DpsoOcrEngineInfo& engineInfo) 24 | { 25 | std::string result{uiAppWebsite}; 26 | assert(!result.empty()); 27 | 28 | if (result.back() != '/') 29 | result += '/'; 30 | 31 | result += "ocr_engine_data/"; 32 | result += engineInfo.id; 33 | result += "_data.json"; 34 | 35 | return result; 36 | } 37 | 38 | 39 | DpsoOcrLangManager* dpsoOcrLangManagerCreateDefault(int engineIdx) 40 | { 41 | DpsoOcrEngineInfo engineInfo; 42 | dpsoOcrGetEngineInfo(engineIdx, &engineInfo); 43 | 44 | const auto dataDir = ui::getDefaultOcrDataDir(engineInfo); 45 | if (!dataDir) 46 | return {}; 47 | 48 | return dpsoOcrLangManagerCreate( 49 | engineIdx, 50 | dataDir->c_str(), 51 | ui::getUserAgent().c_str(), 52 | getInfoFileUrl(engineInfo).c_str()); 53 | } 54 | -------------------------------------------------------------------------------- /src/ui/ui_common/ocr_default.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dpso_ocr/dpso_ocr.h" 4 | 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | 11 | DpsoOcr* dpsoOcrCreateDefault(int engineIdx); 12 | DpsoOcrLangManager* dpsoOcrLangManagerCreateDefault(int engineIdx); 13 | 14 | 15 | #ifdef __cplusplus 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /src/ui/ui_common/ocr_default_data_dir.cpp: -------------------------------------------------------------------------------- 1 | #include "ocr_default_data_dir.h" 2 | 3 | #include "dpso_ext/dpso_ext.h" 4 | #include "dpso_utils/error_get.h" 5 | #include "dpso_utils/error_set.h" 6 | #include "dpso_utils/os.h" 7 | 8 | #include "file_names.h" 9 | 10 | 11 | namespace ui { 12 | 13 | 14 | std::optional getDefaultOcrDataDir( 15 | const DpsoOcrEngineInfo& engineInfo) 16 | { 17 | std::string result; 18 | 19 | switch (engineInfo.dataDirPreference) { 20 | case DpsoOcrEngineDataDirPreferenceNoDataDir: 21 | case DpsoOcrEngineDataDirPreferencePreferDefault: 22 | break; 23 | case DpsoOcrEngineDataDirPreferencePreferExplicit: { 24 | const auto* dataPath = dpsoGetUserDir( 25 | DpsoUserDirData, uiAppFileName); 26 | if (!dataPath) { 27 | dpso::setError( 28 | "Can't get user data dir: {}", dpsoGetError()); 29 | return {}; 30 | } 31 | 32 | result = dataPath; 33 | result += *dpso::os::dirSeparators; 34 | result += engineInfo.id; 35 | result += "_data"; 36 | break; 37 | } 38 | } 39 | 40 | return result; 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/ui/ui_common/ocr_default_data_dir.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "dpso_ocr/engine.h" 7 | 8 | 9 | namespace ui { 10 | 11 | 12 | // Return an absolute path of the OCR data directory for the given 13 | // engine. The path will be empty if 14 | // DpsoOcrEngineInfo::dataDirPreference is not 15 | // DpsoOcrEngineDataDirPreferencePreferExplicit. 16 | // 17 | // On failure, sets an error message (dpsoGetError()) and returns 18 | // nullopt. 19 | std::optional getDefaultOcrDataDir( 20 | const DpsoOcrEngineInfo& engineInfo); 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/ui/ui_common/single_instance_guard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | 11 | /** 12 | * A single instance guard allows to check whether the application is 13 | * a primary or a secondary instance. 14 | */ 15 | typedef struct UiSingleInstanceGuard UiSingleInstanceGuard; 16 | 17 | 18 | /** 19 | * Create a single instance guard. 20 | * 21 | * Id is a unique identifier of the application (like application name 22 | * or UUID) that should be the same for all instances. 23 | * 24 | * On failure, sets an error message (dpsoGetError()) and returns 25 | * null. 26 | */ 27 | UiSingleInstanceGuard* uiSingleInstanceGuardCreate(const char* id); 28 | 29 | 30 | void uiSingleInstanceGuardDelete(UiSingleInstanceGuard* guard); 31 | 32 | 33 | bool uiSingleInstanceGuardIsPrimary( 34 | const UiSingleInstanceGuard* guard); 35 | 36 | 37 | #ifdef __cplusplus 38 | } 39 | 40 | 41 | #include 42 | 43 | 44 | namespace ui { 45 | 46 | 47 | struct SingleInstanceGuardDeleter { 48 | void operator()(UiSingleInstanceGuard* guard) const 49 | { 50 | uiSingleInstanceGuardDelete(guard); 51 | } 52 | }; 53 | 54 | 55 | using SingleInstanceGuardUPtr = 56 | std::unique_ptr< 57 | UiSingleInstanceGuard, SingleInstanceGuardDeleter>; 58 | 59 | 60 | } 61 | 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /src/ui/ui_common/startup_args.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | /** 7 | * Program startup arguments from the command line options. 8 | */ 9 | typedef struct UiStartupArgs { 10 | /** 11 | * Start the program with the hidden window. 12 | */ 13 | bool hide; 14 | } UiStartupArgs; 15 | -------------------------------------------------------------------------------- /src/ui/ui_common/str_nformat.cpp: -------------------------------------------------------------------------------- 1 | #include "str_nformat.h" 2 | 3 | #include "dpso_utils/str_format_core.h" 4 | 5 | 6 | namespace ui { 7 | 8 | 9 | std::string strNFormat( 10 | const char* str, std::initializer_list args) 11 | { 12 | return dpso::str::format( 13 | str, 14 | [&](const auto* name, std::size_t nameLen) -> const char* 15 | { 16 | for (const auto& arg : args) 17 | if (dpso::str::cmpSubStr( 18 | arg.name, name, nameLen) == 0) 19 | return arg.str.c_str(); 20 | 21 | return {}; 22 | }); 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/ui/ui_common/str_nformat.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "dpso_utils/str.h" 7 | 8 | 9 | namespace ui { 10 | 11 | 12 | struct StrNFormatArg { 13 | StrNFormatArg(const char* name, const char* str) 14 | : name{name} 15 | , str{str} 16 | { 17 | } 18 | 19 | StrNFormatArg(const char* name, char* str) 20 | : name{name} 21 | , str{str} 22 | { 23 | } 24 | 25 | StrNFormatArg(const char* name, std::string str) 26 | : name{name} 27 | , str{std::move(str)} 28 | { 29 | } 30 | 31 | template 32 | StrNFormatArg(const char* name, const T& v) 33 | : name{name} 34 | { 35 | using namespace dpso::str; 36 | str = toStr(v); 37 | } 38 | 39 | const char* name; 40 | std::string str; 41 | }; 42 | 43 | 44 | // Python-style named string formatting. 45 | // 46 | // The function formats string similar to Python's str.format(), 47 | // except it only supports named arguments. A partition in the str 48 | // enclosed in braces defines a name to be replaced with the 49 | // corresponding entry in the args array. To insert a brace as is, 50 | // mention it twice. 51 | std::string strNFormat( 52 | const char* str, std::initializer_list args); 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/ui/ui_common/taskbar_config.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #cmakedefine01 UI_TASKBAR_WIN 5 | #cmakedefine01 UI_TASKBAR_NULL 6 | -------------------------------------------------------------------------------- /src/ui/ui_common/taskbar_null.cpp: -------------------------------------------------------------------------------- 1 | #include "taskbar.h" 2 | 3 | 4 | void uiTaskbarDelete(UiTaskbar* tb) 5 | { 6 | (void)tb; 7 | } 8 | 9 | 10 | void uiTaskbarSetState(UiTaskbar* tb, UiTaskbarState newState) 11 | { 12 | (void)tb; 13 | (void)newState; 14 | } 15 | 16 | 17 | void uiTaskbarSetProgress(UiTaskbar* tb, int newProgress) 18 | { 19 | (void)tb; 20 | (void)newProgress; 21 | } 22 | -------------------------------------------------------------------------------- /src/ui/ui_common/toplevel_argv0.cpp: -------------------------------------------------------------------------------- 1 | #include "toplevel_argv0.h" 2 | 3 | #include 4 | 5 | 6 | namespace ui { 7 | 8 | 9 | const char* getToplevelArgv0(const char* argv0) 10 | { 11 | const auto* launcherArgv0 = std::getenv("LAUNCHER_ARGV0"); 12 | return launcherArgv0 && *launcherArgv0 ? launcherArgv0 : argv0; 13 | } 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/ui/ui_common/toplevel_argv0.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace ui { 5 | 6 | 7 | // Return a value of the LAUNCHER_ARGV0 environment variable, or argv0 8 | // if LAUNCHER_ARGV0 either not set or empty. 9 | const char* getToplevelArgv0(const char* argv0); 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/ui/ui_common/ui_common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "app_dirs.h" 4 | #include "app_info.h" 5 | #include "autostart.h" 6 | #include "autostart_default.h" 7 | #include "cfg_default_values.h" 8 | #include "cfg_keys.h" 9 | #include "file_names.h" 10 | #include "init.h" 11 | #include "ocr_default.h" 12 | #include "single_instance_guard.h" 13 | #include "sound.h" 14 | #include "taskbar.h" 15 | #include "update_checker.h" 16 | #include "update_checker_default.h" 17 | 18 | #ifdef __cplusplus 19 | #include "str_nformat.h" 20 | #endif 21 | -------------------------------------------------------------------------------- /src/ui/ui_common/update_checker_default.cpp: -------------------------------------------------------------------------------- 1 | #include "update_checker_default.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "app_info.h" 7 | #include "user_agent.h" 8 | 9 | 10 | static std::string getInfoFileUrl() 11 | { 12 | if (!uiUpdateCheckerIsAvailable()) 13 | return {}; 14 | 15 | std::string result = uiAppWebsite; 16 | assert(!result.empty()); 17 | 18 | if (result.back() != '/') 19 | result += '/'; 20 | 21 | result += "version_info/"; 22 | result += uiUpdateCheckerGetPlatformId(); 23 | result += ".json"; 24 | 25 | return result; 26 | } 27 | 28 | 29 | UiUpdateChecker* uiUpdateCheckerCreateDefault(void) 30 | { 31 | return uiUpdateCheckerCreate( 32 | uiAppVersion, 33 | ui::getUserAgent().c_str(), 34 | getInfoFileUrl().c_str()); 35 | } 36 | -------------------------------------------------------------------------------- /src/ui/ui_common/update_checker_default.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "update_checker.h" 4 | 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | 11 | UiUpdateChecker* uiUpdateCheckerCreateDefault(void); 12 | 13 | 14 | #ifdef __cplusplus 15 | } 16 | #endif 17 | -------------------------------------------------------------------------------- /src/ui/ui_common/update_checker_null.cpp: -------------------------------------------------------------------------------- 1 | #include "update_checker.h" 2 | 3 | #include "dpso_utils/error_set.h" 4 | 5 | 6 | bool uiUpdateCheckerIsAvailable(void) 7 | { 8 | return false; 9 | } 10 | 11 | 12 | const char* uiUpdateCheckerGetPlatformId(void) 13 | { 14 | return ""; 15 | } 16 | 17 | 18 | struct UiUpdateChecker { 19 | }; 20 | 21 | 22 | UiUpdateChecker* uiUpdateCheckerCreate( 23 | const char* appVersion, 24 | const char* userAgent, 25 | const char* infoFileUrl) 26 | { 27 | (void)appVersion; 28 | (void)userAgent; 29 | (void)infoFileUrl; 30 | 31 | return new UiUpdateChecker{}; 32 | } 33 | 34 | 35 | void uiUpdateCheckerDelete(UiUpdateChecker* updateChecker) 36 | { 37 | delete updateChecker; 38 | } 39 | 40 | 41 | void uiUpdateCheckerStartCheck(UiUpdateChecker* updateChecker) 42 | { 43 | (void)updateChecker; 44 | } 45 | 46 | 47 | bool uiUpdateCheckerIsCheckInProgress( 48 | const UiUpdateChecker* updateChecker) 49 | { 50 | (void)updateChecker; 51 | return false; 52 | } 53 | 54 | 55 | UiUpdateCheckerStatus uiUpdateCheckerGetUpdateInfo( 56 | UiUpdateChecker* updateChecker, 57 | UiUpdateCheckerUpdateInfo* updateInfo) 58 | { 59 | (void)updateChecker; 60 | (void)updateInfo; 61 | 62 | dpso::setError("Update checker was disabled at compile time"); 63 | return UiUpdateCheckerStatusGenericError; 64 | } 65 | -------------------------------------------------------------------------------- /src/ui/ui_common/update_checker_platform.h: -------------------------------------------------------------------------------- 1 | // This file contains platform-specific parts of the update checker. 2 | // The corresponding CPP is also a good place for the definition of 3 | // uiUpdateCheckerGetPlatformId(). 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "dpso_json/json.h" 12 | 13 | 14 | namespace ui::updateChecker { 15 | 16 | 17 | class RequirementsError : public std::runtime_error { 18 | using runtime_error::runtime_error; 19 | }; 20 | 21 | 22 | // See UiUpdateCheckerUnmetRequirement. 23 | struct UnmetRequirement { 24 | std::string required; 25 | std::string actual; 26 | }; 27 | 28 | 29 | // Process platform-specific minimum requirements. Returns a list of 30 | // unmet requirements, or an empty list if all requirements are met. 31 | // Throws RequirementsError on any errors during checking. For 32 | // convenience, can also throw dpso::json::Error on JSON-specific 33 | // errors. 34 | std::vector processRequirements( 35 | const dpso::json::Object& requirements); 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/ui/ui_common/update_checker_platform_generic.cpp: -------------------------------------------------------------------------------- 1 | #include "update_checker_platform.h" 2 | 3 | #include "update_checker.h" 4 | 5 | 6 | const char* uiUpdateCheckerGetPlatformId(void) 7 | { 8 | return "generic"; 9 | } 10 | 11 | 12 | namespace ui::updateChecker { 13 | 14 | 15 | std::vector processRequirements( 16 | const dpso::json::Object& requirements) 17 | { 18 | (void)requirements; 19 | return {}; 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/ui/ui_common/update_checker_platform_linux.cpp: -------------------------------------------------------------------------------- 1 | #include "update_checker_platform.h" 2 | 3 | #include 4 | 5 | #include "dpso_utils/version_cmp.h" 6 | 7 | #include "update_checker.h" 8 | 9 | 10 | const char* uiUpdateCheckerGetPlatformId(void) 11 | { 12 | return "linux"; 13 | } 14 | 15 | 16 | namespace ui::updateChecker { 17 | 18 | 19 | std::vector processRequirements( 20 | const dpso::json::Object& requirements) 21 | { 22 | const auto requiredVersion = requirements.getStr("glibc"); 23 | const auto* actualVersion = gnu_get_libc_version(); 24 | 25 | if (dpso::VersionCmp{actualVersion} 26 | < dpso::VersionCmp{requiredVersion.c_str()}) { 27 | return { 28 | { 29 | "glibc " + requiredVersion, 30 | "glibc " + std::string{actualVersion}}}; 31 | } 32 | 33 | return {}; 34 | } 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/ui/ui_common/user_agent.cpp: -------------------------------------------------------------------------------- 1 | #include "user_agent.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "app_info.h" 7 | 8 | 9 | namespace ui { 10 | namespace { 11 | 12 | 13 | // See RFC 1945. 14 | bool isValidTokenChar(char c) 15 | { 16 | return 17 | c > 31 18 | && c < 127 19 | && !std::strchr("()<>@,;:\\\"/[]?={} \t", c); 20 | } 21 | 22 | 23 | void appendAsToken(std::string& result, const char* str) 24 | { 25 | for (const char* s = str; *s; ++s) 26 | if (isValidTokenChar(*s)) 27 | result += *s; 28 | } 29 | 30 | 31 | } 32 | 33 | 34 | std::string getUserAgent() 35 | { 36 | std::string result; 37 | 38 | appendAsToken(result, uiAppName); 39 | result += '/'; 40 | appendAsToken(result, uiAppVersion); 41 | 42 | return result; 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/ui/ui_common/user_agent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | namespace ui { 7 | 8 | 9 | // Get a string for the User-Agent HTTP header. The string is built 10 | // from uiAppName and uiAppVersion. 11 | std::string getUserAgent(); 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/ui/windows_rc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | configure_file(manifest.xml.in manifest.xml @ONLY) 2 | 3 | set(RC_DST_PATH "${CMAKE_CURRENT_BINARY_DIR}/resource.rc") 4 | 5 | configure_file(resource.rc.in "${RC_DST_PATH}" @ONLY) 6 | 7 | add_library(windows_rc OBJECT "${RC_DST_PATH}") 8 | -------------------------------------------------------------------------------- /src/ui/windows_rc/manifest.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | UTF-8 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/ui/windows_rc/resource.rc.in: -------------------------------------------------------------------------------- 1 | #pragma code_page(65001) // UTF-8 2 | 3 | #include "winuser.h" 4 | 5 | CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "@CMAKE_CURRENT_BINARY_DIR@/manifest.xml" 6 | 7 | 1 ICON "@CMAKE_SOURCE_DIR@/data/icons/@APP_FILE_NAME@.ico" 8 | 9 | #define APP_VERSION_NUM @APP_VERSION_MAJOR@,@APP_VERSION_MINOR@,@APP_VERSION_PATCH@,0 10 | 11 | 1 VERSIONINFO 12 | FILEVERSION APP_VERSION_NUM 13 | PRODUCTVERSION APP_VERSION_NUM 14 | BEGIN 15 | BLOCK "StringFileInfo" 16 | BEGIN 17 | BLOCK "040904E4" 18 | BEGIN 19 | VALUE "CompanyName", "@APP_AUTHOR@" 20 | // In some contexts, the description is the only thing that is 21 | // shown to the user, so we should include the application name. 22 | // An example is Restart Manager API 23 | // (RM_PROCESS_INFO::strAppName) that installers use to get the 24 | // names of running applications that need to be closed before 25 | // installing an update. 26 | VALUE "FileDescription", "@APP_NAME@ - @APP_DESCRIPTION@" 27 | VALUE "FileVersion", "@APP_VERSION@" 28 | VALUE "InternalName", "@APP_FILE_NAME@" 29 | VALUE "LegalCopyright", "© @APP_COPYRIGHT_YEAR@ @APP_AUTHOR@" 30 | VALUE "OriginalFilename", "@APP_FILE_NAME@.exe" 31 | VALUE "ProductName", "@APP_NAME@" 32 | VALUE "ProductVersion", "@APP_VERSION@" 33 | END 34 | END 35 | BLOCK "VarFileInfo" 36 | BEGIN 37 | VALUE "Translation", 0x409, 1252 38 | END 39 | END 40 | -------------------------------------------------------------------------------- /tests/dpso_sys/test_windows_action_executor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "dpso_sys/backend/windows/execution_layer/action_executor.h" 4 | 5 | #include "flow.h" 6 | 7 | 8 | namespace { 9 | 10 | 11 | void testExceptions() 12 | { 13 | using namespace dpso::backend; 14 | 15 | class TestException : public std::runtime_error { 16 | using runtime_error::runtime_error; 17 | }; 18 | 19 | ActionExecutor actionExecutor; 20 | 21 | try { 22 | execute(actionExecutor, []{ throw TestException{""}; }); 23 | test::failure( 24 | "testExceptions: Exception was not propagated by the " 25 | "executor"); 26 | return; 27 | } catch (TestException&) { 28 | } 29 | 30 | // There was a bug where an exception stored in std::exception_ptr 31 | // under the hood was not cleared before being rethrown, and thus 32 | // thrown for all further successful execute() calls. 33 | try { 34 | execute(actionExecutor, []{}); 35 | } catch (TestException&) { 36 | test::failure( 37 | "testExceptions: The previously propagated exception was " 38 | "rethrown on a successful call"); 39 | } 40 | } 41 | 42 | 43 | void testWindowsActionExecutor() 44 | { 45 | testExceptions(); 46 | } 47 | 48 | 49 | } 50 | 51 | 52 | REGISTER_TEST(testWindowsActionExecutor); 53 | -------------------------------------------------------------------------------- /tests/dpso_utils/stream/test_out_newline_conversion_stream.cpp: -------------------------------------------------------------------------------- 1 | #include "flow.h" 2 | #include "utils.h" 3 | 4 | #include "dpso_utils/os.h" 5 | #include "dpso_utils/stream/out_newline_conversion_stream.h" 6 | #include "dpso_utils/stream/string_stream.h" 7 | #include "dpso_utils/stream/utils.h" 8 | 9 | 10 | namespace { 11 | 12 | 13 | void testOutNewlineConversionStream() 14 | { 15 | const auto* input = " \n \r \r\n "; 16 | 17 | const struct { 18 | const char* outNewline; 19 | std::string expected; 20 | } tests[]{ 21 | {nullptr, test::utils::lfToNativeNewline(input)}, 22 | {"", " \r \r "}, 23 | {"\n", input}, 24 | {"abc", " abc \r \rabc "}, 25 | }; 26 | 27 | for (const auto& test : tests) { 28 | dpso::StringStream strStream; 29 | dpso::OutNewlineConversionStream convStream{ 30 | strStream, test.outNewline}; 31 | 32 | dpso::write(convStream, input); 33 | 34 | if (strStream.getStr() != test.expected) 35 | test::failure( 36 | "Converting {} to {} newlines: expected {}, got {}", 37 | test::utils::toStr(input), 38 | test.outNewline 39 | ? test::utils::toStr(test.outNewline) : "OS", 40 | test::utils::toStr(test.expected), 41 | test::utils::toStr(strStream.getStr())); 42 | } 43 | } 44 | 45 | 46 | } 47 | 48 | 49 | REGISTER_TEST(testOutNewlineConversionStream); 50 | -------------------------------------------------------------------------------- /tests/dpso_utils/test_os_stdio.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "dpso_utils/os.h" 6 | #include "dpso_utils/os_stdio.h" 7 | 8 | #include "flow.h" 9 | #include "utils.h" 10 | 11 | 12 | using namespace dpso; 13 | 14 | 15 | namespace { 16 | 17 | 18 | const auto* const testUnicodeFileName = 19 | // 汉语.txt 20 | "\346\261\211\350\257\255.txt"; 21 | 22 | 23 | void testFopen() 24 | { 25 | os::StdFileUPtr fp{os::fopen(testUnicodeFileName, "wb")}; 26 | if (!fp) { 27 | test::failure( 28 | "os::fopen(\"{}\"): {}", 29 | testUnicodeFileName, os::getErrnoMsg(errno)); 30 | return; 31 | } 32 | 33 | fp.reset(); 34 | test::utils::removeFile(testUnicodeFileName); 35 | } 36 | 37 | 38 | void testSyncFile() 39 | { 40 | const auto* fileName = "test_sync_file.txt"; 41 | 42 | os::StdFileUPtr fp{os::fopen(fileName, "wb")}; 43 | if (!fp) 44 | test::fatalError( 45 | "testSyncFile: os::fopen(\"{}\"): {}", 46 | fileName, os::getErrnoMsg(errno)); 47 | 48 | try { 49 | os::syncFile(fp.get()); 50 | } catch (os::Error& e) { 51 | test::failure("os::syncFile(): {}", e.what()); 52 | } 53 | 54 | fp.reset(); 55 | test::utils::removeFile(fileName); 56 | } 57 | 58 | 59 | void testOsStdio() 60 | { 61 | testFopen(); 62 | testSyncFile(); 63 | } 64 | 65 | 66 | } 67 | 68 | 69 | REGISTER_TEST(testOsStdio); 70 | -------------------------------------------------------------------------------- /tests/dpso_utils/test_strftime.cpp: -------------------------------------------------------------------------------- 1 | #include "dpso_utils/strftime.h" 2 | 3 | #include "flow.h" 4 | 5 | 6 | static void testStrftime() 7 | { 8 | std::tm tm{}; 9 | tm.tm_year = 2022 - 1900; 10 | tm.tm_mon = 10; 11 | tm.tm_mday = 17; 12 | tm.tm_hour = 23; 13 | tm.tm_min = 58; 14 | tm.tm_sec = 59; 15 | 16 | const struct { 17 | const char* format; 18 | const char* result; 19 | } tests[]{ 20 | {"", ""}, 21 | {"%Y-%m-%d %H:%M:%S", "2022-11-17 23:58:59"}, 22 | }; 23 | 24 | for (const auto& test : tests) { 25 | const auto result = dpso::strftime(test.format, &tm); 26 | if (result != test.result) 27 | test::failure( 28 | "dpso::strftime(\"{}\"): Expected \"{}\", got \"{}\"", 29 | test.format, 30 | test.result, 31 | result); 32 | } 33 | } 34 | 35 | 36 | REGISTER_TEST(testStrftime); 37 | -------------------------------------------------------------------------------- /tests/dpso_utils/test_version_cmp.cpp: -------------------------------------------------------------------------------- 1 | #include "dpso_utils/version_cmp.h" 2 | 3 | #include "flow.h" 4 | #include "utils.h" 5 | 6 | 7 | static void testVersionCmp() 8 | { 9 | const struct { 10 | const char* strA; 11 | const char* strB; 12 | bool isLess; 13 | } tests [] = { 14 | {"", "1", true}, 15 | {"1", "", false}, 16 | 17 | {"1.0", "1.0.1", true}, 18 | {"1.0.1", "1.0", false}, 19 | 20 | {"1.0", "1.1", true}, 21 | {"1.1", "1.0", false}, 22 | 23 | {"1.1", "1.10", true}, 24 | {"1.10", "1.1", false}, 25 | 26 | {"1.2.3", "1.2.3.1", true}, 27 | {"1.2.3.1", "1.2.3", false}, 28 | 29 | {"1.2.3", "01.002.0003", false}, 30 | {"01.002.0003", "1.2.3", false}, 31 | 32 | {"1.0-rc1", "1.0-rc1", false}, 33 | 34 | {"1.1-rc1", "1.0-rc2", false}, 35 | {"1.0-rc2", "1.1-rc1", true}, 36 | 37 | {"1.0-rc1", "1.0-rc2", true}, 38 | {"1.0-rc2", "1.0-rc1", false}, 39 | 40 | {"1.0-rc1", "1.0", true}, 41 | {"1.0", "1.0-rc1", false}, 42 | }; 43 | 44 | for (const auto& test : tests) { 45 | const dpso::VersionCmp a{test.strA}; 46 | const dpso::VersionCmp b{test.strB}; 47 | 48 | const auto isLess = a < b; 49 | if (isLess == test.isLess) 50 | continue; 51 | 52 | test::failure( 53 | "\"{}\" < \"{}\" expected to be {}\n", 54 | test.strA, 55 | test.strB, 56 | test::utils::toStr(test.isLess)); 57 | } 58 | } 59 | 60 | 61 | REGISTER_TEST(testVersionCmp); 62 | -------------------------------------------------------------------------------- /tests/flow.cpp: -------------------------------------------------------------------------------- 1 | #include "flow.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | namespace test { 9 | 10 | 11 | const Runner* Runner::getFirst() 12 | { 13 | return list; 14 | } 15 | 16 | 17 | const Runner* Runner::getNext() const 18 | { 19 | return next; 20 | } 21 | 22 | 23 | int Runner::getNumRunners() 24 | { 25 | return numRunners; 26 | } 27 | 28 | 29 | Runner* Runner::list; 30 | int Runner::numRunners; 31 | 32 | 33 | Runner::Runner(const char* name, void (&fn)()) 34 | : name{name} 35 | , fn{fn} 36 | , next{} 37 | { 38 | // Link alphabetically. 39 | auto** pos = &list; 40 | while (*pos && std::strcmp((*pos)->name, name) < 0) 41 | pos = &(*pos)->next; 42 | 43 | next = *pos; 44 | *pos = this; 45 | 46 | ++numRunners; 47 | } 48 | 49 | 50 | const char* Runner::getName() const 51 | { 52 | return name; 53 | } 54 | 55 | 56 | void Runner::run() const 57 | { 58 | fn(); 59 | } 60 | 61 | 62 | static int numFailures; 63 | 64 | 65 | void failure(const char* fmt, std::initializer_list args) 66 | { 67 | ++numFailures; 68 | std::fputs(dpso::str::format(fmt, args).c_str(), stderr); 69 | std::fputc('\n', stderr); 70 | } 71 | 72 | 73 | int getNumFailures() 74 | { 75 | return numFailures; 76 | } 77 | 78 | 79 | void fatalError( 80 | const char* fmt, std::initializer_list args) 81 | { 82 | std::fputs("FATAL ERROR\n", stderr); 83 | std::fputs(dpso::str::format(fmt, args).c_str(), stderr); 84 | std::fputc('\n', stderr); 85 | std::exit(EXIT_FAILURE); 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /tests/flow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dpso_utils/str.h" 4 | 5 | 6 | namespace test { 7 | 8 | 9 | class Runner { 10 | public: 11 | static const Runner* getFirst(); 12 | const Runner* getNext() const; 13 | 14 | static int getNumRunners(); 15 | 16 | Runner(const char* name, void (&fn)()); 17 | 18 | const char* getName() const; 19 | void run() const; 20 | private: 21 | static Runner* list; 22 | static int numRunners; 23 | 24 | const char* name; 25 | void (*fn)(); 26 | Runner* next; 27 | }; 28 | 29 | 30 | #define REGISTER_TEST(FN) \ 31 | static test::Runner FN ## TestRunner(#FN, FN) 32 | 33 | 34 | // Report a test case failure, increment the failure counter, and 35 | // continue. 36 | void failure( 37 | const char* fmt, std::initializer_list args); 38 | 39 | template 40 | void failure(const char* fmt, const Args&... args) 41 | { 42 | failure(fmt, {dpso::str::formatArg::get(args)...}); 43 | } 44 | 45 | 46 | // Return the number of failure() calls. 47 | int getNumFailures(); 48 | 49 | 50 | // Report an abnormal error and exit. The function is intended for 51 | // errors that are not directly related to test cases, e.g. an IO 52 | // error when setting up a test. 53 | [[noreturn]] 54 | void fatalError( 55 | const char* fmt, std::initializer_list args); 56 | 57 | template 58 | [[noreturn]] 59 | void fatalError(const char* fmt, const Args&... args) 60 | { 61 | fatalError(fmt, {dpso::str::formatArg::get(args)...}); 62 | } 63 | 64 | 65 | } 66 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "dpso_utils/str.h" 5 | 6 | #include "flow.h" 7 | 8 | 9 | int main() 10 | { 11 | int curRunnerNum{}; 12 | 13 | for (const auto* runner = test::Runner::getFirst(); 14 | runner; 15 | runner = runner->getNext()) { 16 | std::printf( 17 | "%2i/%i: %s\n", 18 | ++curRunnerNum, test::Runner::getNumRunners(), 19 | runner->getName()); 20 | // Flush to make sure that failure() messages (written to 21 | // stderr) are nested under the test name. 22 | std::fflush(stdout); 23 | runner->run(); 24 | } 25 | 26 | std::printf("===\n"); 27 | const auto numFailures = test::getNumFailures(); 28 | if (numFailures == 0) 29 | std::printf("Everything is OK\n"); 30 | else 31 | std::printf( 32 | "%i failure%s\n", 33 | numFailures, 34 | numFailures > 1 ? "s" : ""); 35 | 36 | return numFailures == 0 ? EXIT_SUCCESS : EXIT_FAILURE; 37 | } 38 | -------------------------------------------------------------------------------- /tests/test_c_compilation.c: -------------------------------------------------------------------------------- 1 | #include "dpso_ext/dpso_ext.h" 2 | #include "dpso_img/dpso_img.h" 3 | #include "dpso_intl/dpso_intl.h" 4 | #include "dpso_ocr/dpso_ocr.h" 5 | #include "dpso_sys/dpso_sys.h" 6 | #include "dpso_utils/dpso_utils.h" 7 | #include "ui_common/ui_common.h" 8 | 9 | 10 | int main(void) 11 | { 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /tools/app_launcher_unix/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(launcher C) 4 | 5 | add_executable(launcher main.c) 6 | 7 | set_target_properties( 8 | launcher PROPERTIES 9 | C_STANDARD 99 10 | C_STANDARD_REQUIRED YES 11 | C_EXTENSIONS NO) 12 | 13 | target_compile_options( 14 | launcher PRIVATE -Wall -Wextra -pedantic -Wstrict-prototypes) 15 | 16 | target_link_libraries(launcher dl) 17 | -------------------------------------------------------------------------------- /tools/docker_build_env/include/build-bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This is a simple helper script to create a bundle with a single 4 | # "docker run [...] build-bundle" call, without having to go into a 5 | # shell inside the container and mess around with CMake. 6 | 7 | set -eu 8 | 9 | printHelp() 10 | ( 11 | cat << EOF 12 | Usage: $0 SOURCE_CODE_DIR 13 | 14 | Build a bundle from the program source code in SOURCE_CODE_DIR. The 15 | TAR.XZ archive will be written to the current working directory. 16 | EOF 17 | ) 18 | 19 | if [ "$#" -ne 1 ] || [ -z "$1" ]; then 20 | printHelp 21 | exit 1 22 | fi 23 | 24 | SOURCE_CODE_DIR="$1" 25 | 26 | # Note we are using "/tmp" that is inside the container. 27 | BUILD_DIR="/tmp/$(basename $0)-build-dir" 28 | 29 | cmake \ 30 | -S "$SOURCE_CODE_DIR" \ 31 | -B "$BUILD_DIR" \ 32 | -DCMAKE_BUILD_TYPE=Release \ 33 | -DDPSO_USE_DEFAULT_TESSERACT_DATA_PATH=No \ 34 | -DDPSO_DYNAMIC_CURL=Yes \ 35 | -DDPSO_ENABLE_UPDATE_CHECKER=Yes 36 | 37 | # There's no need for --parallel here, because the "bundle_archive" 38 | # target is already built in the parallel mode under the hood. 39 | cmake --build "$BUILD_DIR" --target bundle_archive 40 | 41 | mv --force "$BUILD_DIR"/*".tar.xz" . 42 | -------------------------------------------------------------------------------- /tools/docker_build_env/include/qt-5.12.12-x11-client-leader-window.patch: -------------------------------------------------------------------------------- 1 | diff -Naur a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp 2 | --- a/src/plugins/platforms/xcb/qxcbconnection.cpp 2021-11-16 07:41:20.000000000 +0100 3 | +++ b/src/plugins/platforms/xcb/qxcbconnection.cpp 2023-09-09 12:43:44.168887885 +0200 4 | @@ -862,7 +862,7 @@ 5 | 6 | 7 | QXcbWindow::setWindowTitle(connection(), m_clientLeader, 8 | - QStringLiteral("Qt Client Leader Window")); 9 | + QGuiApplication::applicationDisplayName()); 10 | 11 | xcb_change_property(xcb_connection(), 12 | XCB_PROP_MODE_REPLACE, 13 | -------------------------------------------------------------------------------- /tools/docker_build_env/readme.txt: -------------------------------------------------------------------------------- 1 | This directory contains files to create a Docker image intended to be 2 | used as a build environment for a bundle or AppImage. The image 3 | contains all the libraries and tools needed for the task. 4 | 5 | To build the image, use: 6 | 7 | docker build --force-rm -t dpscreenocr-build-env . 8 | 9 | To build a TAR.XZ bundle archive, run: 10 | 11 | docker run --rm -v "$PWD:/workspace" dpscreenocr-build-env \ 12 | build-bundle SOURCE_CODE_DIR 13 | 14 | Where SOURCE_CODE_DIR is a path to the dpScreenOCR source code 15 | directory, relative to the current working directory. 16 | 17 | If you need full control over the build process, use: 18 | 19 | docker run --rm -it -v "$PWD:/workspace" dpscreenocr-build-env 20 | 21 | The above command will drop you into the bash shell, with the current 22 | working directory mounted as the "/workspace" directory inside the 23 | container. Follow the instructions in the "doc/building-unix.txt" file 24 | in the dpScreenOCR source code directory, then type "exit" to exit the 25 | container when you are done. 26 | -------------------------------------------------------------------------------- /tools/tessdata_info_gen/gen_c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from operator import itemgetter 4 | import argparse 5 | import json 6 | 7 | 8 | def format_line(info, max_data_name_len): 9 | formatted_data_name = '"{}",'.format(info['data_name']) 10 | # + 3 for 2 quotes and a comma 11 | formatted_data_name_fill_len = max_data_name_len + 3 12 | 13 | name = '; '.join(info['names']) 14 | name_infos = info['name_infos'] 15 | if name_infos: 16 | name += ' ({})'.format(', '.join(name_infos)) 17 | 18 | return ' {{{:<{}} N_("{}")}},\n'.format( 19 | formatted_data_name, formatted_data_name_fill_len, name) 20 | 21 | 22 | def main(): 23 | parser = argparse.ArgumentParser(description='Generate C source.') 24 | 25 | parser.add_argument( 26 | 'info_file', 27 | help='Path to a JSON file generated by gen_info.py.') 28 | parser.add_argument('out_file', help='Output C file.') 29 | 30 | args = parser.parse_args() 31 | 32 | with open(args.info_file, 'r', encoding='utf-8') as f: 33 | infos = json.load(f) 34 | 35 | infos.sort(key=itemgetter('data_name')) 36 | 37 | with open(args.out_file, 'w', encoding='utf-8', newline='') as f: 38 | max_data_name_len = 0 39 | for info in infos: 40 | max_data_name_len = max( 41 | max_data_name_len, len(info['data_name'])) 42 | 43 | for info in infos: 44 | f.write(format_line(info, max_data_name_len)) 45 | 46 | 47 | if __name__ == '__main__': 48 | main() 49 | -------------------------------------------------------------------------------- /tools/tessdata_info_gen/readme.txt: -------------------------------------------------------------------------------- 1 | This directory contains Python scripts to generate language tables for 2 | Tesseract data files. 3 | 4 | get_data_list.py fetches names of Tesseract data files from one or 5 | more GitHub repositories. 6 | 7 | gen_info.py takes the list of names (e.g. from get_data_list.py) and 8 | creates a JSON file with various information (like language name, ISO 9 | 639-3 code, etc.) based on ISO 639-3 code tables from 10 | https://iso639-3.sil.org/code_tables/download_tables. Other scripts 11 | can use this JSON for file generation. gen_c.py is an example of such 12 | a script; it creates a language code table for C/C++ source. 13 | --------------------------------------------------------------------------------