diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..d2200db --- /dev/null +++ b/.clangd @@ -0,0 +1,14 @@ +CompileFlags: + Add: [-Wall, -Wextra] # flags shared by all files go in the top (unconditional) fragment +--- +If: + PathMatch: .*\.h$ +CompileFlags: + Remove: [-std=*] # strip any -std= picked up from compile_flags.txt + Add: [-xc, -std=c11] # -xc forces C, since a bare .h is ambiguous +--- +If: + PathExclude: .*\.h$ +CompileFlags: + Remove: [-std=*] + Add: [-std=c++20] diff --git a/.gitignore b/.gitignore index a684b7c..d237981 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /oshot_r /locale/ /include/version.h +/*.patch AppDir *.AppImage diff --git a/CMakeLists.txt b/CMakeLists.txt index a79eef5..7b93f3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,11 +10,18 @@ endif() set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") +#add_compile_options($<$:-fsanitize=address>) +#add_compile_options($<$:-fno-omit-frame-pointer>) +#add_link_options($<$:-fsanitize=address>) -set(CMAKE_CXX_VISIBILITY_PRESET hidden) -set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) +# GCC/Clang default Release flags already include -O3; this just makes it +# explicit. Guarded because "-O3" is not a valid cl.exe switch and would +# otherwise spam D9002 (or hard-fail under /WX) on MSVC builds. +if(NOT MSVC) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") +endif() # LTO helper function(enable_lto target) @@ -24,6 +31,9 @@ function(enable_lto target) PRIVATE $<$:-flto=auto;-ffat-lto-objects> ) target_link_options(${target} PRIVATE $<$:-flto=auto>) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + target_compile_options(${target} PRIVATE $<$:-flto>) + target_link_options(${target} PRIVATE $<$:-flto>) elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") target_compile_options( ${target} @@ -34,25 +44,31 @@ function(enable_lto target) endfunction() # https://github.com/libcpr/cpr/blob/5f475522597b8f3721e2440daddeced7a969f24c/CMakeLists.txt#L39 +# NOTE: definitions are collected into OSHOT_OPTION_DEFINITIONS instead of +# using add_definitions(), which is directory-scoped and would otherwise leak +# into every target declared afterward - including vendored third-party +# libs (fmt, imgui, tray, clip, dylib, nvdialog, tiny-process-library). +# They're applied to just ${PROJECT_NAME} once it exists (see below). +set(OSHOT_OPTION_DEFINITIONS "") macro(add_option OPTION_NAME OPTION_TEXT OPTION_DEFAULT DEFINE_OPTION) option(${OPTION_NAME} ${OPTION_TEXT} ${OPTION_DEFAULT}) if(DEFINED ENV{${OPTION_NAME}}) # Allow overriding the option through an environment variable set(${OPTION_NAME} $ENV{${OPTION_NAME}}) endif() - if(${OPTION_NAME} AND DEFINE_OPTION) - add_definitions(-D${OPTION_NAME}=1) + if(${OPTION_NAME} AND ${DEFINE_OPTION}) + list(APPEND OSHOT_OPTION_DEFINITIONS ${OPTION_NAME}=1) endif() message(STATUS " ${OPTION_NAME}: ${${OPTION_NAME}}") endmacro() # Options -add_option(WINDOWS_CMD "Enable terminal support on Windows" OFF OFF) +add_option(WINDOWS_CMD "Enable terminal support on Windows" OFF ON) +add_option(DISABLE_PLUGINS "Disable plugins support" OFF ON) # ----------------------------- # Dependencies # ----------------------------- - find_package(PkgConfig REQUIRED) find_package(Threads REQUIRED) find_package(glfw3 REQUIRED) @@ -68,9 +84,21 @@ else() endif() find_package(OpenGL REQUIRED) -pkg_check_modules(TESSERACT REQUIRED tesseract) -pkg_check_modules(LEPTONICA REQUIRED lept) -pkg_check_modules(ZBAR REQUIRED zbar) +pkg_check_modules(TESSERACT REQUIRED IMPORTED_TARGET tesseract) +pkg_check_modules(LEPTONICA REQUIRED IMPORTED_TARGET lept) +pkg_check_modules(ZBAR REQUIRED IMPORTED_TARGET zbar) + +# Homebrew's lept.pc points Cflags at .../include/leptonica directly (its +# own convention is `#include `), but our code uses +# `#include `, which needs the parent dir on the +# include path too. +message(STATUS "LEPTONICA_INCLUDE_DIRS before: ${LEPTONICA_INCLUDE_DIRS}") +foreach(_lept_dir ${LEPTONICA_INCLUDE_DIRS}) + get_filename_component(_lept_parent_dir "${_lept_dir}" DIRECTORY) + list(APPEND LEPTONICA_INCLUDE_DIRS "${_lept_parent_dir}") +endforeach() +list(REMOVE_DUPLICATES LEPTONICA_INCLUDE_DIRS) +message(STATUS "LEPTONICA_INCLUDE_DIRS after: ${LEPTONICA_INCLUDE_DIRS}") if(UNIX AND NOT APPLE) find_package(X11 REQUIRED) @@ -87,97 +115,115 @@ endif() # ----------------------------- # Main executable # ----------------------------- - -file( - GLOB SRC - src/*.cpp - src/libs/toml++/toml.cpp +add_executable( + ${PROJECT_NAME} + src/main.cpp + src/main_tool_opengl3.cpp src/libs/getopt_port/getopt.c - src/libs/tinyfiledialogs/tinyfiledialogs.c ) - -add_executable(${PROJECT_NAME} ${SRC}) enable_lto(${PROJECT_NAME}) -execute_process( - COMMAND ./scripts/generateVersion.sh +set(VERSION_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/include/version.h) +add_custom_command( + OUTPUT ${VERSION_HEADER} + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generateVersion.sh WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/include/version.h.in + ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generateVersion.sh + COMMENT "Generating version header" ) +add_custom_target(generate_version DEPENDS ${VERSION_HEADER}) +add_dependencies(${PROJECT_NAME} generate_version) target_compile_definitions(${PROJECT_NAME} PRIVATE $<$:DEBUG=1>) target_compile_options( ${PROJECT_NAME} PRIVATE - $<$:-ggdb3;-fno-omit-frame-pointer;-Wall;-Wextra;-pedantic;-Wno-unused-parameter> + -Wall + -Wextra + -Wpedantic + -Wno-unused-parameter + $<$:-ggdb3;-fno-omit-frame-pointer> ) -target_compile_definitions(${PROJECT_NAME} PRIVATE VERSION="${PROJECT_VERSION}") +# oshot_plugin PUBLIC-links oshot_common (see below), so linking oshot_plugin +# alone pulls in oshot_common's usage requirements transitively. This is +# deliberate: it ensures oshot and oshot_plugin.so resolve globals.cpp's +# externs (g_ss_tool, g_config, g_cache, g_plugins, g_current_plugin, ...) +# to the same symbols at the same address. +if(NOT DISABLE_PLUGINS) + target_link_libraries(${PROJECT_NAME} PRIVATE oshot_plugin) +else() + target_link_libraries(${PROJECT_NAME} PRIVATE oshot_common) +endif() -target_include_directories( - ${PROJECT_NAME} - PRIVATE - include/ - include/libs - ${TESSERACT_INCLUDE_DIRS} - ${LEPTONICA_INCLUDE_DIRS} - ${ZBAR_INCLUDE_DIRS} -) +if(WIN32 AND NOT WINDOWS_CMD) + set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE TRUE) +endif() if(UNIX AND NOT APPLE) - target_include_directories( - ${PROJECT_NAME} - PRIVATE ${APPINDICATOR_INCLUDE_DIRS} - ) - target_compile_options(${PROJECT_NAME} PRIVATE ${APPINDICATOR_CFLAGS_OTHER}) + target_link_libraries(${PROJECT_NAME} PRIVATE X11::X11) endif() -target_compile_options( - ${PROJECT_NAME} - PRIVATE - ${TESSERACT_CFLAGS_OTHER} - ${LEPTONICA_CFLAGS_OTHER} - ${ZBAR_CFLAGS_OTHER} -) +if(APPLE) + target_sources(${PROJECT_NAME} PRIVATE src/main_tool_metal.mm) + set(_rpath_origin "@loader_path") +else() + set(_rpath_origin "$ORIGIN") +endif() -target_link_directories( +set_target_properties( ${PROJECT_NAME} - PRIVATE - ${TESSERACT_LIBRARY_DIRS} - ${LEPTONICA_LIBRARY_DIRS} - ${ZBAR_LIBRARY_DIRS} + PROPERTIES + BUILD_RPATH "${_rpath_origin}/../lib;$" + INSTALL_RPATH "${_rpath_origin}/../lib" ) -target_link_libraries( - ${PROJECT_NAME} - PRIVATE - OpenGL::GL - ${GLFW_TARGET} - ${TESSERACT_LIBRARIES} - ${LEPTONICA_LIBRARIES} - ${ZBAR_LIBRARIES} +# ----------------------------- +# Common library for shared code +# ----------------------------- +add_library( + oshot_common + OBJECT + src/cache.cpp + src/clipboard.cpp + src/config.cpp + src/globals.cpp + src/screen_capture.cpp + src/screenshot_tool.cpp + src/text_extraction.cpp + src/util.cpp + src/libs/toml++/toml.cpp + src/libs/tinyfiledialogs/tinyfiledialogs.c ) -if(WIN32) - target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32 shcore d3d11 dxgi) +if(OSHOT_OPTION_DEFINITIONS) + target_compile_definitions(oshot_common PUBLIC ${OSHOT_OPTION_DEFINITIONS}) +endif() - if(NOT WINDOWS_CMD) - set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE TRUE) +if(NOT DISABLE_PLUGINS) + target_sources(oshot_common PRIVATE src/plugins.cpp) +endif() + +enable_lto(oshot_common) + +if(WIN32) + target_link_libraries(oshot_common PRIVATE ws2_32 shcore d3d11 dxgi) + if(MINGW) + target_link_options(${PROJECT_NAME} PRIVATE -Wl,--export-all-symbols) endif() elseif(APPLE) target_compile_options( - ${PROJECT_NAME} + oshot_common PRIVATE -fobjc-arc $<$:-stdlib=libc++> ) - - # Add Metal render loop source - target_sources(${PROJECT_NAME} PRIVATE src/main_tool_metal.mm) - target_link_options( - ${PROJECT_NAME} + oshot_common PRIVATE $<$:-stdlib=libc++> ) target_link_libraries( - ${PROJECT_NAME} + oshot_common PRIVATE "-framework Metal" "-framework QuartzCore" @@ -186,25 +232,136 @@ elseif(APPLE) "-framework IOKit" "-framework OpenGL" ) -elseif(UNIX) - target_link_libraries(${PROJECT_NAME} PRIVATE X11::X11) +else() + target_include_directories( + oshot_common + PRIVATE ${APPINDICATOR_INCLUDE_DIRS} + ) + target_compile_options(oshot_common PRIVATE ${APPINDICATOR_CFLAGS_OTHER}) + + target_link_libraries(oshot_common PRIVATE X11::X11) target_include_directories( - ${PROJECT_NAME} + oshot_common PRIVATE SYSTEM ${GIO_INCLUDE_DIRS} ${XRANDR_INCLUDE_DIRS} ) target_link_libraries( - ${PROJECT_NAME} + oshot_common PRIVATE ${GIO_LIBRARIES} ${XRANDR_LIBRARIES} ) target_compile_options( - ${PROJECT_NAME} + oshot_common PRIVATE ${GIO_CFLAGS_OTHER} ${XRANDR_CFLAGS_OTHER} ) endif() +target_link_libraries( + oshot_common + PUBLIC + fmt + imgui + tray + clip + nvdialog + tiny-process-library + PkgConfig::TESSERACT + PkgConfig::LEPTONICA + PkgConfig::ZBAR + OpenGL::GL + ${GLFW_TARGET} + PRIVATE Threads::Threads +) +if(NOT DISABLE_PLUGINS) + target_link_libraries(oshot_common PUBLIC dylib) +endif() + +target_compile_definitions( + oshot_common + PUBLIC VERSION="${PROJECT_VERSION}" TOML_HEADER_ONLY=0 +) + +# NOTE: PkgConfig::TESSERACT / PkgConfig::ZBAR already propagate their own +# include dirs, link dirs, and compile options as usage requirements via +# target_link_libraries() above. +# LEPTONICA_INCLUDE_DIRS is the one exception: it was patched earlier in this +# file to add the parent directory for Homebrew's lept.pc convention, which +# PkgConfig::LEPTONICA's own interface include dirs don't have. +target_include_directories( + oshot_common + PUBLIC include/ include/libs ${LEPTONICA_INCLUDE_DIRS} +) + +target_compile_features(oshot_common PUBLIC cxx_std_20) + +# ----------------------------- +# oshot's plugin library +# ----------------------------- +if(NOT DISABLE_PLUGINS) + add_library(oshot_plugin SHARED src/plugins/oshot_plugins.cpp) + + target_compile_features(oshot_plugin PUBLIC cxx_std_20) + + target_link_libraries(oshot_plugin PUBLIC oshot_common) + + set_target_properties( + oshot_plugin + PROPERTIES + VERSION 1 + SOVERSION 1 + WINDOWS_EXPORT_ALL_SYMBOLS ON + POSITION_INDEPENDENT_CODE ON + ) +endif() + +# ----------------------------- +# oshot_plugman and oshotpm (oshot plugin manager, CLI) +# ----------------------------- +if(NOT DISABLE_PLUGINS) + add_library( + oshot_plugman + SHARED + oshotpm/src/manifest.cpp + oshotpm/src/plugin_manager.cpp + oshotpm/src/state_manager.cpp + ) + enable_lto(oshot_plugman) + target_compile_definitions(oshot_plugman PRIVATE $<$:DEBUG=1>) + target_compile_options( + oshot_plugman + PRIVATE + -Wall + -Wextra + -Wpedantic + -Wno-unused-parameter + $<$:-ggdb3;-fno-omit-frame-pointer> + ) + + target_include_directories(oshot_plugman PUBLIC oshotpm/include/) + target_compile_features(oshot_plugman PUBLIC cxx_std_20) + target_link_libraries(oshot_plugman PUBLIC oshot_plugin) + + set_target_properties( + oshot_plugman + PROPERTIES + VERSION 1 + SOVERSION 1 + WINDOWS_EXPORT_ALL_SYMBOLS ON + POSITION_INDEPENDENT_CODE ON + ) + + add_executable(oshotpm oshotpm/src/main.cpp) + enable_lto(oshotpm) + set_target_properties( + oshotpm + PROPERTIES + BUILD_RPATH "${_rpath_origin}/../lib;$" + INSTALL_RPATH "${_rpath_origin}/../lib" + ) + target_link_libraries(oshotpm PUBLIC oshot_plugman) +endif() + # ----------------------------- # fmt library # ----------------------------- @@ -217,8 +374,6 @@ target_include_directories(fmt PUBLIC include/libs) target_compile_features(fmt PUBLIC cxx_std_20) target_compile_definitions(fmt PUBLIC FMT_HEADER_ONLY=0) -target_link_libraries(${PROJECT_NAME} PRIVATE fmt) - # ----------------------------- # imgui library # ----------------------------- @@ -228,15 +383,63 @@ if(APPLE) list(APPEND IMGUI_SOURCES src/libs/imgui/imgui_impl_metal.mm) endif() -add_library(imgui STATIC ${IMGUI_SOURCES}) +if(DISABLE_PLUGINS) + add_library(imgui STATIC ${IMGUI_SOURCES}) +else() + add_library(imgui SHARED ${IMGUI_SOURCES}) +endif() enable_lto(imgui) target_include_directories(imgui PUBLIC include/libs/imgui) -target_compile_definitions(imgui PUBLIC IMGUI_IMPL_OPENGL_LOADER_GLAD) +target_compile_definitions( + imgui + PUBLIC IMGUI_IMPL_OPENGL_LOADER_GLAD IMGUI_DISABLE_OBSOLETE_FUNCTIONS +) target_link_libraries(imgui PUBLIC ${GLFW_TARGET}) -target_link_libraries(${PROJECT_NAME} PRIVATE imgui) +if(APPLE) + find_library(METAL_FRAMEWORK Metal REQUIRED) + find_library(FOUNDATION_FRAMEWORK Foundation REQUIRED) + find_library(QUARTZCORE_FRAMEWORK QuartzCore REQUIRED) + target_link_libraries( + imgui + PUBLIC + ${METAL_FRAMEWORK} + ${FOUNDATION_FRAMEWORK} + ${QUARTZCORE_FRAMEWORK} + ) +endif() + +if(NOT DISABLE_PLUGINS) + set_target_properties( + imgui + PROPERTIES + VERSION 1.92.9 + SOVERSION 1 + WINDOWS_EXPORT_ALL_SYMBOLS ON + POSITION_INDEPENDENT_CODE ON + ) +endif() + +# ----------------------------- +# cimgui library (C-ABI plugins only) +# ----------------------------- + +if(NOT DISABLE_PLUGINS) + add_library(cimgui SHARED src/libs/cimgui/cimgui.cpp) + + target_include_directories(cimgui PUBLIC include/libs include/libs/cimgui) + target_link_libraries(cimgui PUBLIC imgui) + set_target_properties( + cimgui + PROPERTIES + VERSION 1 + SOVERSION 1 + WINDOWS_EXPORT_ALL_SYMBOLS ON + POSITION_INDEPENDENT_CODE ON + ) +endif() # ----------------------------- # Tray library (integrated from its own CMakeLists.txt) @@ -247,7 +450,7 @@ if(WIN32) else() if(UNIX) if(APPLE) - find_library(COCOA Cocoa REQUIRED) + find_library(COCOA_FRAMEWORK Cocoa REQUIRED) list(APPEND TRAY_SOURCES src/libs/tray/tray_darwin.mm) else() list(APPEND TRAY_SOURCES src/libs/tray/tray_linux.cpp) @@ -262,12 +465,11 @@ if(WIN32) tray PRIVATE TRAY_WINAPI=1 WIN32_LEAN_AND_MEAN NOMINMAX ) - #target_compile_options(tray PRIVATE "/MT$<$:d>") else() if(UNIX) if(APPLE) target_compile_definitions(tray PRIVATE TRAY_APPKIT=1) - target_link_libraries(tray PRIVATE ${COCOA}) + target_link_libraries(tray PRIVATE ${COCOA_FRAMEWORK}) else() target_compile_options( tray @@ -295,12 +497,6 @@ endif() target_include_directories(tray PUBLIC include/libs) target_compile_features(tray PRIVATE cxx_std_20) -set_target_properties( - tray - PROPERTIES CXX_STANDARD 20 CXX_EXTENSIONS OFF CXX_STANDARD_REQUIRED ON -) - -target_link_libraries(${PROJECT_NAME} PRIVATE tray) # ----------------------------- # clip library (integrated from its own CMakeLists.txt) @@ -333,16 +529,27 @@ endif() target_include_directories(clip PUBLIC include/libs/clip) target_compile_features(clip PRIVATE cxx_std_20) target_compile_definitions(clip PUBLIC CLIP_ENABLE_IMAGE=1) -set_target_properties( - clip - PROPERTIES CXX_STANDARD 20 CXX_EXTENSIONS OFF CXX_STANDARD_REQUIRED ON -) -target_link_libraries(${PROJECT_NAME} PRIVATE clip) +# ----------------------------- +# dylib library (integrated from its own CMakeLists.txt) +# ----------------------------- + +if(NOT DISABLE_PLUGINS) + file(GLOB DYLIB_SOURCES src/libs/dylib/*.cpp) + add_library(dylib STATIC ${DYLIB_SOURCES}) + + if(UNIX) + target_link_libraries(dylib PRIVATE ${CMAKE_DL_LIBS}) + endif() + + target_include_directories(dylib PUBLIC include/libs/) + target_compile_features(dylib PRIVATE cxx_std_20) +endif() # ----------------------------- # nvdialog (integrated from its own CMakeLists.txt) # ----------------------------- + file(GLOB NVD_COMMON_SOURCES src/libs/nvdialog/*.c) if(WIN32) @@ -373,7 +580,7 @@ set_target_properties(nvdialog PROPERTIES C_STANDARD 11 C_STANDARD_REQUIRED ON) if(WIN32) target_link_libraries( nvdialog - PRIVATE comdlg32 shell32 user32 gdi32 ole32 + PUBLIC comdlg32 shell32 user32 gdi32 ole32 ) elseif(APPLE) target_compile_definitions(nvdialog PRIVATE NVD_USE_COCOA=1) @@ -403,8 +610,6 @@ else() target_link_libraries(nvdialog PRIVATE ${GTK_LIBRARIES} ${DBUS_LIBRARIES}) endif() -target_link_libraries(${PROJECT_NAME} PRIVATE nvdialog) - # ----------------------------- # tiny-process-library (integrated from its own CMakeLists.txt) # ----------------------------- @@ -430,7 +635,6 @@ else() endif() if(WIN32) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) target_sources( tiny-process-library PRIVATE "src/libs/tiny-process-library/process_win.cpp" @@ -449,28 +653,35 @@ else() ) endif() -target_link_libraries(tiny-process-library Threads::Threads) +target_link_libraries(tiny-process-library PUBLIC Threads::Threads) target_include_directories( tiny-process-library PUBLIC include/libs/tiny-process-library ) enable_lto(tiny-process-library) -target_link_libraries(${PROJECT_NAME} PRIVATE tiny-process-library) # ----------------------------- # Install # ----------------------------- -install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin COMPONENT runtime) +include(GNUInstallDirs) + +if(DISABLE_PLUGINS) + install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +else() + install( + TARGETS ${PROJECT_NAME} imgui cimgui oshot_plugin + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +endif() install( FILES LICENSE - DESTINATION share/licenses/${PROJECT_NAME} - COMPONENT documentation + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/licenses/${PROJECT_NAME} ) install( FILES oshot.desktop - DESTINATION share/applications - COMPONENT documentation + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications ) diff --git a/Makefile b/Makefile index e224653..e80f3e1 100644 --- a/Makefile +++ b/Makefile @@ -1,143 +1,76 @@ -CXX ?= g++ -TAR ?= bsdtar -PREFIX ?= /usr -VARS ?= -CXXSTD ?= c++20 - -DEBUG ?= 0 -WINDOWS_CMD ?= 0 - -COMPILER := $(shell $(CXX) --version | head -n1) - -ifeq ($(findstring g++,$(COMPILER)),g++) - export LTO_FLAGS = -flto=auto -ffat-lto-objects -else ifeq ($(findstring clang,$(COMPILER)),clang) - export LTO_FLAGS = -flto=thin -else - $(warning Unknown compiler: $(COMPILER). No LTO flags applied.) -endif - -UNAME_S := $(shell uname -s) -ifeq ($(UNAME_S),Linux) - LDLIBS += -lGL - CXXFLAGS += `pkg-config --static --cflags gio-2.0 appindicator3-0.1 x11` -DCLIP_ENABLE_IMAGE=1 - LDLIBS += `pkg-config --static --libs gio-2.0 appindicator3-0.1 x11 xrandr` - -else ifeq ($(UNAME_S),Darwin) #APPLE - LDFLAGS += -L/usr/local/lib -L/opt/local/lib -L/opt/homebrew/lib - LDLIBS += -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo - #LDLIBS += -lglfw3 - #LDLIBS += -lglfw - - CXXFLAGS += -I/usr/local/include -I/opt/local/include -I/opt/homebrew/include - -else ifeq ($(findstring _NT,$(UNAME_S)),_NT) - LDLIBS += -ld3d11 -ldxgi -lgdi32 -lopengl32 -limm32 -lole32 -lcomdlg32 -luuid -lshcore -lshlwapi -lwindowscodecs - ifneq ($(WINDOWS_CMD),1) - CXXFLAGS += -mwindows - LDFLAGS += -mwindows - else - CXXFLAGS += -DWINDOWS_CMD - endif -endif - -# https://stackoverflow.com/a/1079861 -# WAY easier way to build debug and release builds -ifeq ($(DEBUG), 1) - BUILDDIR := build/debug - LTO_FLAGS := -fno-lto - SAN_FLAGS ?= -fsanitize=address -fsanitize=undefined - CXXFLAGS := -ggdb3 -Wall -Wextra -pedantic -Wno-unused-parameter \ - -DDEBUG=1 -fno-omit-frame-pointer $(DEBUG_CXXFLAGS) $(CXXFLAGS) - LDFLAGS += -Wl,-rpath,$(BUILDDIR) +# Thin Makefile wrapper around CMake. +# +# This file does NOT compile anything itself. All build logic (sources, +# per-platform deps, flags, library targets) lives in CMakeLists.txt. This +# exists purely so `make`/`make clean`/`make distclean`/etc. keep working +# for people and CI used to that interface, without maintaining a second, +# independently-drifting set of compile rules alongside CMakeLists.txt. + +CMAKE ?= cmake +PREFIX ?= /usr +JOBS := $(shell echo $(MAKEFLAGS) | grep -oP '(?<=-j)\d+' || nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) + +NAME := oshot +VERSION := 0.4.6 +OLDVERSION := 0.4.5 +TARGET ?= $(NAME) + +DEBUG ?= 0 +WINDOWS_CMD ?= 0 +DISABLE_PLUGINS ?= 0 + +ifeq ($(DEBUG),1) + BUILDDIR := build/debug + BUILD_TYPE := Debug else - # Check if an optimization flag is not already set - ifneq ($(filter -O%,$(CXXFLAGS)),) - $(info Keeping the existing optimization flag in CXXFLAGS) - else - CXXFLAGS := -O3 -march=native $(CXXFLAGS) - endif - BUILDDIR := build/release + BUILDDIR := build/release + BUILD_TYPE := Release endif -NAME = oshot -TARGET ?= $(NAME) -VERSION = 0.4.6 -OLDVERSION = 0.4.5 -SRC = $(wildcard src/*.cpp) -OBJ = $(patsubst src/%.cpp,$(BUILDDIR)/%.o,$(SRC)) -LDFLAGS += -L$(BUILDDIR) $(LTO_FLAGS) -LDLIBS += $(LIBS) `pkg-config --static --libs glfw3 tesseract zbar` -CXXFLAGS += $(LTO_FLAGS) -fvisibility-inlines-hidden -fvisibility=hidden -Iinclude -Iinclude/libs -std=$(CXXSTD) $(VARS) -DVERSION=\"$(VERSION)\" -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS - -LIBS := \ - $(BUILDDIR)/libimgui.a \ - $(BUILDDIR)/libfmt.a \ - $(BUILDDIR)/libclip.a \ - $(BUILDDIR)/libtray.a \ - $(BUILDDIR)/libnvdialog.a \ - $(BUILDDIR)/libtiny-process-library.a - -EXTRA_OBJ := \ - $(BUILDDIR)/toml.o \ - $(BUILDDIR)/tinyfiledialogs.o \ - $(BUILDDIR)/getopt.o - -all: $(BUILDDIR)/$(TARGET) - -$(BUILDDIR): - mkdir -p $(BUILDDIR) +CMAKE_CONFIGURE_FLAGS := \ + -S . -B $(BUILDDIR) \ + -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) \ + -DWINDOWS_CMD=$(WINDOWS_CMD) \ + -DDISABLE_PLUGINS=$(DISABLE_PLUGINS) \ + -DCMAKE_INSTALL_PREFIX=$(PREFIX) -$(BUILDDIR)/%.o: src/%.cpp - @mkdir -p $(dir $@) - $(CXX) $(CXXFLAGS) -c $< -o $@ +.PHONY: all configure build clean distclean dist genver updatever install -$(BUILDDIR)/libimgui.a: | $(BUILDDIR) - $(MAKE) -C src/libs/imgui BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) DEBUG=$(DEBUG) +all: build -$(BUILDDIR)/libfmt.a: | $(BUILDDIR) - $(MAKE) -C src/libs/fmt BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) DEBUG=$(DEBUG) +# Re-configure whenever CMakeLists.txt changes or the build dir doesn't +# exist yet. CMake re-configures itself as needed beyond this too (e.g. if +# a referenced file changes), this just guarantees a fresh checkout works +# with a single `make`. +$(BUILDDIR)/CMakeCache.txt: CMakeLists.txt + $(CMAKE) $(CMAKE_CONFIGURE_FLAGS) -$(BUILDDIR)/toml.o: - $(MAKE) -C src/libs/toml++ BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) DEBUG=$(DEBUG) +configure: $(BUILDDIR)/CMakeCache.txt -$(BUILDDIR)/libclip.a: - $(MAKE) -C src/libs/clip BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) DEBUG=$(DEBUG) - -$(BUILDDIR)/libtray.a: - $(MAKE) -C src/libs/tray BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) DEBUG=$(DEBUG) - -$(BUILDDIR)/libnvdialog.a: - $(MAKE) -C src/libs/nvdialog BUILDDIR=$(BUILDDIR) DEBUG=$(DEBUG) - -$(BUILDDIR)/tinyfiledialogs.o: - $(MAKE) -C src/libs/tinyfiledialogs BUILDDIR=$(BUILDDIR) - -$(BUILDDIR)/libtiny-process-library.a: - $(MAKE) -C src/libs/tiny-process-library BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) DEBUG=$(DEBUG) - -$(BUILDDIR)/getopt.o: - $(MAKE) -C src/libs/getopt_port BUILDDIR=$(BUILDDIR) +build: configure + $(CMAKE) --build $(BUILDDIR) --parallel $(JOBS) +# Generates version info ahead of time; CMakeLists.txt also runs this at +# configure time, so this target is mainly for manual/CI use outside a +# full configure+build cycle. genver: ./scripts/generateVersion.sh -$(BUILDDIR)/$(TARGET): genver $(OBJ) $(EXTRA_OBJ) $(LIBS) - $(CXX) -o $@ $(OBJ) $(EXTRA_OBJ) $(LDFLAGS) $(LDLIBS) - -dist: $(TARGET) - zip -j $(NAME)-v$(VERSION).zip LICENSE README.md $(BUILDDIR)/$(TARGET) - +# Removes built artifacts but keeps the CMake cache/config, so the next +# `make` is a fast incremental rebuild rather than a full re-configure. clean: - rm -rf $(BUILDDIR)/$(TARGET) $(OBJ) + $(CMAKE) --build $(BUILDDIR) --target clean distclean: - rm -rf $(BUILDDIR) $(OBJ) + rm -rf build find . -type f -name "*.tar.gz" -delete - find . -type f -name "*.o" -delete - find . -type f -name "*.a" -delete + find src -type f \( -name "*.o" -o -name "*.a" -o -name "*.so" -o -name "*.dylib" \) -delete + +dist: build + zip -j $(NAME)-v$(VERSION).zip LICENSE README.md $(BUILDDIR)/$(TARGET) + +install: build + $(CMAKE) --install $(BUILDDIR) --prefix $(PREFIX) updatever: sed -i "s#$(OLDVERSION)#$(VERSION)#g" $(wildcard .github/workflows/*.yml scripts/*) CMakeLists.txt compile_flags.txt - -.PHONY: $(TARGET) updatever distclean clean imgui fmt tpl toml getopt-port clip tray dist all diff --git a/README.md b/README.md index 1e8d6e0..46258b3 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,10 @@ https://github.com/user-attachments/assets/800f50b3-95a6-47c4-b9bd-5a90c35941b2 | Shortcut | Action | | -------------- | ----------------------- | | `Ctrl+C/S` | Copy/Save selection as image | -| `Ctrl+A` ` | Select full image | -| `Ctrl+Z` ` | Undo | -| `Ctrl+G` ` | Toggle handles | -| `Ctrl+E` ` | Toggle text editing | +| `Ctrl+A` | Select full image | +| `Ctrl+Z` | Undo | +| `Ctrl+G` | Toggle handles | +| `Ctrl+E` | Toggle text editing | | `Esc` | Close | ## Installation diff --git a/include/cache.hpp b/include/cache.hpp index 10833c3..9302392 100644 --- a/include/cache.hpp +++ b/include/cache.hpp @@ -1,13 +1,15 @@ #ifndef _CACHE_HPP_ #define _CACHE_HPP_ +#include #include -#include -#define TOML_HEADER_ONLY 0 -#include "toml++/toml.hpp" +#include "toml_api.hpp" #include "util.hpp" +// util.hpp +std::string expand_var(std::string ret); + enum class CacheEntry { AnnColor, @@ -15,7 +17,7 @@ enum class CacheEntry COUNT }; -class Cache +class Cache : public TomlAPI { public: Cache(const std::string& cache_dir); @@ -25,51 +27,33 @@ class Cache const std::string& GetCacheDirPath() const { return m_cache_dir_path; } - /** - * Get value of a cache variables - * @param value The cache variable "path" (e.g "cache.source-path") - * @param fallback Default value if couldn't retrive value - */ + using TomlAPI::GetValue; + using TomlAPI::SetValue; + + // CacheEntry convenience overloads are the ONLY thing Cache needs to add now template T GetValue(CacheEntry e, const T& fallback, bool dont_expand_var = false) { - const std::string& key = m_cache_entries.at(e); - const std::optional& ret = m_tbl["cache"][key].value(); - if constexpr (toml::is_string) - if (!dont_expand_var) - return ret ? expand_var(ret.value()) : expand_var(fallback); - else - return ret ? ret.value() : fallback; - else - return ret.value_or(fallback); + return GetValue(mk_cache_entries.at(idx(e)), fallback, dont_expand_var); } - /** - * Set value of a cache variables - * @param path The cache variable "path" (e.g "cache.source-path") - */ template void SetValue(CacheEntry e, const T& value) { - const std::string& key = m_cache_entries.at(e); - auto* section = m_tbl["cache"].as_table(); - if (!section) - { - m_tbl.insert_or_assign("cache", toml::table{}); - section = m_tbl["cache"].as_table(); - } - section->insert_or_assign(key, value); + SetValue(mk_cache_entries.at(idx(e)), value); } +protected: + std::string BuildKey(const std::string_view key) const override { return fmt::format("cache.{}", key); } + private: static constexpr const char* mk_file_path = "cache.toml"; std::string m_cache_dir_path; - toml::table m_tbl; - std::unordered_map m_cache_entries = { - { CacheEntry::AnnColor, "default-color-picker-color" }, - { CacheEntry::ImgSavePath, "last-saved-dir" }, + static constexpr std::array mk_cache_entries = { + "default-color-picker-color", + "last-saved-dir" }; }; diff --git a/include/config.hpp b/include/config.hpp index 979cc51..b3b328c 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -3,35 +3,13 @@ #include #include -#include #include #include "fmt/format.h" +#include "toml_api.hpp" +#include "util.hpp" -// util.hpp -std::string expand_var(std::string ret); -bool hexstr_to_col(const std::string_view hex, uint32_t& out); - -#define TOML_HEADER_ONLY 0 -#include "toml++/toml.hpp" - -enum class ValueType -{ - kNone, - kString, - kBool, - kInt -}; - -struct override_config_value_t -{ - ValueType value_type = ValueType::kNone; - std::string string_value = ""; - bool bool_value = false; - int int_value = 0; -}; - -class Config +class Config : public TomlAPI { public: // Create .config directories and files and load the config file (args or default) @@ -50,19 +28,22 @@ class Config #else std::string ocr_path = "./models"; #endif - std::string ocr_get_repo = "tesseract-ocr/tessdata"; - std::string ocr_model = "eng"; - std::string theme_style = "auto"; - std::string theme_file_path = "theme.toml"; - std::string image_out_fmt = "oshot_{:%F_%H-%M}"; - int delay = 0; - bool allow_out_edit = false; - bool real_full_screen = false; - bool show_text_tools = true; - bool enable_vsync = true; - bool render_anns = true; - bool pref_conf_to_env = false; - bool ctrl_c_copy_img = true; + std::string ocr_get_repo = "tesseract-ocr/tessdata"; + std::string ocr_model = "eng"; + std::string theme_style = "auto"; + std::string theme_file_path = "theme.toml"; + std::string image_out_fmt = "oshot_{:%F_%H-%M}"; + int delay = 0; + int color_picker = 0; // 0 = "Bar - Square"; 1 = "Wheel - Triangle"; + int cpa_mode = 2; // color_picker_alpha_mode + bool allow_out_edit = false; + bool real_full_screen = false; + bool show_text_tools = true; + bool enable_vsync = true; + bool render_anns = true; + bool pref_conf_to_env = false; + bool ctrl_c_copy_img = true; + std::vector fonts; bool operator==(const config_file_t&) const = default; @@ -77,11 +58,7 @@ class Config bool enable_handles = true; bool only_launch_tray = false; bool only_launch_gui = false; -#if DEBUG || (defined(_WIN32) && WINDOWS_CMD) - bool debug_print = true; -#else - bool debug_print = false; -#endif + bool operator==(const runtime_settings_t&) const = default; } Runtime; @@ -128,140 +105,27 @@ class Config */ void GenerateTheme(const std::string& filename, const bool force = false); - /** - * Override a config value from --override - * @param str The value to override. - * Must have a '=' for separating the name and value to override. - * NO spaces between - */ - void OverrideOption(const std::string& opt); + using TomlAPI::GetValue; + using TomlAPI::SetValue; - /** - * Override a config value from --override - * @param key The value name to override. - * Must have a '=' for separating the name and value to override. - * NO spaces between - * @param value The value that will overwrite - */ template - void OverrideOption(const std::string& key, const T& value) - { - override_config_value_t o; - if constexpr (std::is_same_v) - { - o.value_type = ValueType::kBool; - o.bool_value = value; - } - else if constexpr (std::is_convertible_v) - { - o.value_type = ValueType::kString; - o.string_value = value; - } - else if constexpr (std::is_convertible_v) - { - o.value_type = ValueType::kInt; - o.int_value = value; - } - - m_overrides[key] = std::move(o); - } - - /** - * Get value of config variables - * @param value The config variable "path" (e.g "config.source-path") - * @param fallback Default value if couldn't retrive value - */ - template - T GetValue(const std::string_view value, - const T& fallback, - bool dont_expand_var = false, - bool is_theme = false) const + T GetThemeValue(const std::string_view value, const T& fallback, bool dont_expand_var = true) const { - const auto& overridePos = m_overrides.find(value.data()); - - if (overridePos != m_overrides.end()) - { - const auto& ov = overridePos->second; - if constexpr (std::is_same()) - if (ov.value_type == ValueType::kBool) - return ov.bool_value; - if constexpr (std::is_same()) - if (ov.value_type == ValueType::kString) - return ov.string_value; - if constexpr (std::is_same()) - if (ov.value_type == ValueType::kInt) - return ov.int_value; - } - - const std::optional& ret = - is_theme ? m_theme_tbl.at_path(value).value() : m_tbl.at_path(value).value(); - if constexpr (toml::is_string) - if (!dont_expand_var) - return ret ? expand_var(ret.value()) : expand_var(fallback); - else - return ret ? ret.value() : fallback; - else - return ret.value_or(fallback); + return m_theme.GetValue(fmt::format("theme.{}", value), fallback, dont_expand_var); } - std::vector GetValueArrayStr(const std::string_view value, - const std::vector& fallback) const - { - std::vector ret; - - // https://stackoverflow.com/a/78266628 - if (const toml::array* array_it = m_tbl.at_path(value).as_array()) - { - ret.reserve(array_it->size()); - array_it->for_each([&](auto&& el) { - if (const toml::value* str_elem = el.as_string()) - ret.push_back((*str_elem)->data()); - }); - - return ret; - } - else - { - return fallback; - } - } - - /** - * Get the theme style variable and return a rgba type value - * @param value The value we want - * @param fallback The default value if it doesn't exists - * @return rgba type variable - */ template T GetThemeStyleValue(const std::string_view value, const T& fallback, bool dont_expand_var = true) const { - return GetValue(fmt::format("theme.style.{}", value), fallback, dont_expand_var, true); + return m_theme.GetValue(fmt::format("theme.style.{}", value), fallback, dont_expand_var); } - /** - * Get the theme style variable and return a rgba type value - * @param value The value we want - * @param fallback The default value if it doesn't exists - * @return rgba type variable - */ - template - T GetThemeValue(const std::string_view value, const T& fallback, bool dont_expand_var = true) const - { - return GetValue(fmt::format("theme.{}", value), fallback, dont_expand_var, true); - } - - /** - * Get the theme color variable and return a rgba type value - * @param value The value we want - * @param fallback The default value if it doesn't exists - * @return rgba type variable - */ uint32_t GetThemeColorValue(const std::string_view value, const std::string& fallback, bool dont_expand_var = true) const { uint32_t out; - hexstr_to_col(GetValue(fmt::format("theme.colors.{}", value), fallback, dont_expand_var, true), + hexstr_to_col(m_theme.GetValue(fmt::format("theme.colors.{}", value), fallback, dont_expand_var), out); return out; } @@ -271,13 +135,8 @@ class Config const std::string& GetConfigDirPath() const { return m_config_dir_path; } private: - // Parsed config from LoadConfigFile() - toml::table m_tbl; - // Parsed theme from LoadThemeFile() - toml::table m_theme_tbl; - - std::unordered_map m_overrides; + TomlAPI m_theme; std::string m_config_path; std::string m_theme_path; @@ -287,180 +146,4 @@ class Config extern std::unique_ptr g_config; void apply_imgui_theme(); - -// default config -inline constexpr std::string_view AUTOCONFIG = R"#([default] -# Default Path to where we'll use all the '.traineddata' models. -# The TESSDATA_PREFIX environment variable supersedes this. -ocr-path = "{}" - -# Default OCR model. -ocr-model = "{}" - -# GitHub repository from where we are going to -# download an OCR '.traineddata' model. -# The models must be on the root directory of the repository -ocr-repo-downlaod = "{}" - -# Delay the app before acquiring a screenshot (in milliseconds) -# Doesn't affect if opening external image (i.e. -f flag) -delay = {} - -# On some desktop environments (e.g. MATE), the compositor may cause -# the capture window to look grainy or pixelated. Enabling this uses exclusive -# fullscreen mode which bypasses the compositor and fixes it. -# Downside: the window may briefly take over the display on some setups. -real-full-screen = {} - -# Controls vertical sync (VSync). When enabled, the capture window renders in sync -# with your monitor's refresh rate, thus being smoother visually but uses slightly more CPU/GPU. -# Disable if the overlay feels sluggish or unresponsive. -vsync = {} - -# Allow the extracted output to be editable. -allow-text-edit = {} - -# Display the text tools (OCR, Bar/QR code scan) at startup. -show-text-tools = {} - -# Prefer using config variables over environment variable. -config-over-env = {} - -# Consider annotations when scanning (true) -# or only when saving the selection (false). -annotations-in-text-tools = {} - -# Copy image shortcut to use. -# true: CTRL+C -# false: CTRL+SHIFT+C -ctrl-c-copy-img = {} - -# Fonts to use for the application. Can be an absolute path, or just a name. -# You can combine multiple fonts for multiple language support. -# for example, using "Roboto-Regular.ttf" and "RobotoCJK-Regular.ttc" for Chinese, Japanese, and Korean support alongside English support. -# If empty, or non-existent (or commented out), oshot will use the default font for ImGUI. -fonts = [{}] - -# Format of the output image filename when saving. -# The .png extension is appended automatically. -# Uses {{fmt}} chrono specifiers. NOTE: -# the colon inside {{}} is required: {{:%F}} correct, {{%F}} will error. -# -# Default: "oshot_{{:%F_%H-%M}}" -image-out-fmt = "{}" - -# Base UI theme: "auto" (follow OS dark/light), "dark", "light", or "classic". -# Fine-grained overrides live in theme.toml. -theme = "{}" - -# Path to a theme file. Absolute or relative to this config's directory. -# Delete or comment out to use only the base theme above. -theme-file = "{}" -)#"; - -inline constexpr std::string_view AUTOTHEME = (R"( -# Drop this next to config.toml or point theme-file at its path. -# All sections and keys are optional — omit anything you don't want to override. - -[theme] -smooth-animations = false - -# --------------------------------------------------------------- -# Rounding (pixels, 0 = sharp corners, max ~12) -# --------------------------------------------------------------- -[theme.style] -window-rounding = 8.0 -frame-rounding = 4.0 -grab-rounding = 4.0 -tab-rounding = 4.0 - -# Border width in pixels. 0 = none, 1 = thin line. -window-border = 1.0 -frame-border = 0.0 - -# --------------------------------------------------------------- -# Color overrides -# Format: "#RRGGBBAA" -# Only the entries you list here are overridden; -# everything else falls back to the base theme. -# -# Full list of valid names: -# https://github.com/ocornut/imgui/blob/master/imgui.cpp -# (search for "GetStyleColorName") -# --------------------------------------------------------------- -[theme.colors] -# --- Text --- -Text = "#cdd6f4FF" -TextDisabled = "#6c7086FF" - -# --- Backgrounds --- -WindowBg = "#1e1e2eFF" -ChildBg = "#181825FF" -PopupBg = "#1e1e2eFF" -FrameBg = "#313244FF" -FrameBgHovered = "#45475aFF" -FrameBgActive = "#585b70FF" -MenuBarBg = "#181825FF" - -# --- Title bar --- -TitleBg = "#181825FF" -TitleBgActive = "#313244FF" - -# --- Borders --- -Border = "#585b70FF" -BorderShadow = "#00000000" - -# --- Scrollbar --- -ScrollbarBg = "#181825FF" -ScrollbarGrab = "#585b70FF" -ScrollbarGrabHovered = "#6c7086FF" -ScrollbarGrabActive = "#7f849cFF" - -# --- Buttons --- -Button = "#313244FF" -ButtonHovered = "#45475aFF" -ButtonActive = "#585b70FF" - -# --- Headers (selectables, tree nodes, collapsing headers) --- -Header = "#313244FF" -HeaderHovered = "#45475aFF" -HeaderActive = "#585b70FF" - -# --- Sliders / checkmarks --- -CheckMark = "#cba6f7FF" -SliderGrab = "#cba6f7FF" -SliderGrabActive = "#b4befeff" - -# --- Tabs --- -Tab = "#313244FF" -TabHovered = "#cba6f7FF" -TabSelected = "#45475aFF" - -# --- Misc --- -Separator = "#585b70FF" -ResizeGrip = "#cba6f7FF" -ResizeGripHovered = "#cba6f7FF" -ResizeGripActive = "#cba6f7FF" -PlotHistogram = "#2ba2f0FF" # original: "#e6b300FF" -)"); - -inline constexpr std::string_view oshot_help = (R"(Usage: oshot [OPTIONS]... -Lightweight Screenshot tool to extract text on the fly. - -GENERAL OPTIONS: - -h, --help Print this help menu. - -V, --version Print version and other infos about the build. - -f, --source Path to the image to use as background (use '-' for reading from stdin). - -C, --config Path to the config file to use (default: ~/.config/oshot/config.toml). - -O, --override