Skip to content

Commit 458c9b4

Browse files
committed
Add Linux support with Nix packaging, Flatpak, desktop integration, and CI/CD
1 parent 3b01b67 commit 458c9b4

File tree

21 files changed

+680
-57
lines changed

21 files changed

+680
-57
lines changed

.envrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Optional: Automatically load Nix development environment when entering this directory
2+
# Requires direnv (https://direnv.net/) and nix-direnv for automatic shell activation
3+
# If you don't use direnv, run 'nix develop' manually instead
4+
use flake

.github/workflows/build.yml

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
permissions:
66
contents: write
77
jobs:
8-
build:
8+
build-windows:
99
runs-on: windows-latest
1010
steps:
1111
- name: Checkout repository
@@ -64,11 +64,51 @@ jobs:
6464
- name: Upload artifacts
6565
uses: actions/upload-artifact@v4
6666
with:
67-
name: paperback-build
67+
name: paperback-windows
6868
path: |
6969
build/paperback.zip
7070
build/paperback_setup.exe
7171
retention-days: 30
72+
73+
build-linux-flatpak:
74+
runs-on: ubuntu-latest
75+
container:
76+
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-49
77+
options: --privileged
78+
steps:
79+
- uses: actions/checkout@v4
80+
with:
81+
submodules: recursive
82+
- name: Generate cargo sources for Flatpak
83+
run: |
84+
pip install aiohttp tomlkit
85+
curl -sSL https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/cargo/flatpak-cargo-generator.py -o flatpak-cargo-generator.py
86+
python3 flatpak-cargo-generator.py lib/Cargo.lock -o cargo-sources.json
87+
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
88+
with:
89+
bundle: paperback.flatpak
90+
manifest-path: io.github.trypsynth.Paperback.yaml
91+
cache-key: flatpak-builder-${{ github.sha }}
92+
93+
release:
94+
needs: [build-windows, build-linux-flatpak]
95+
runs-on: ubuntu-latest
96+
if: github.event_name == 'push'
97+
steps:
98+
- name: Checkout repository
99+
uses: actions/checkout@v4
100+
with:
101+
fetch-depth: 0
102+
- name: Download Windows artifacts
103+
uses: actions/download-artifact@v4
104+
with:
105+
name: paperback-windows
106+
path: windows-build
107+
- name: Download Linux Flatpak
108+
uses: actions/download-artifact@v4
109+
with:
110+
name: paperback-x86_64.flatpak
111+
path: linux-build
72112
- name: Get latest tag reachable from HEAD
73113
id: get_tag
74114
shell: bash
@@ -99,8 +139,9 @@ jobs:
99139
## Commits since last release
100140
${{ steps.release_notes.outputs.commits }}
101141
files: |
102-
build/paperback.zip
103-
build/paperback_setup.exe
142+
windows-build/paperback.zip
143+
windows-build/paperback_setup.exe
144+
linux-build/paperback.flatpak
104145
prerelease: true
105146
env:
106147
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@ build/
33
lib/target/
44
vcpkg/bin/
55
web/_site/
6+
result
7+
.direnv/
8+
.flatpak-builder/
9+
*.flatpak
10+
repo-flatpak/
11+
cargo-sources.json

CMakeLists.txt

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ cmake_minimum_required(VERSION 3.21)
22
if(POLICY CMP0144)
33
cmake_policy(SET CMP0144 NEW)
44
endif()
5+
6+
# Option to use system libraries instead of vcpkg (for Nix, Linux distros, etc.)
7+
option(USE_SYSTEM_LIBS "Use system libraries instead of vcpkg" OFF)
8+
9+
if(NOT USE_SYSTEM_LIBS)
510
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/vcpkg/bin/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")
611
set(VCPKG_MANIFEST_DIR "${CMAKE_SOURCE_DIR}/vcpkg")
712
set(VCPKG_OVERLAY_TRIPLETS "${CMAKE_SOURCE_DIR}/vcpkg/triplets")
@@ -12,6 +17,7 @@ elseif(APPLE)
1217
else()
1318
set(VCPKG_TARGET_TRIPLET "x64-linux" CACHE STRING "Vcpkg triplet")
1419
endif()
20+
endif()
1521
project(paperback VERSION 0.6.1 LANGUAGES CXX)
1622

1723
if("${PROJECT_VERSION_TWEAK}" STREQUAL "")
@@ -29,8 +35,28 @@ if(MSVC)
2935
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
3036
endif()
3137

32-
find_package(Gettext REQUIRED)
33-
find_package(wxWidgets CONFIG REQUIRED COMPONENTS webview)
38+
find_program(CLANG_FORMAT_EXE NAMES clang-format)
39+
if(CLANG_FORMAT_EXE)
40+
file(GLOB_RECURSE ALL_SOURCE_FILES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/app/*.cpp ${CMAKE_SOURCE_DIR}/app/*.hpp)
41+
add_custom_target(format COMMAND ${CLANG_FORMAT_EXE} -i ${ALL_SOURCE_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
42+
endif()
43+
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
44+
if(CLANG_TIDY_EXE)
45+
file(GLOB_RECURSE ALL_SOURCE_FILES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/app/*.cpp ${CMAKE_SOURCE_DIR}/app/*.hpp)
46+
add_custom_target(lint COMMAND ${CLANG_TIDY_EXE} -warnings-as-errors=* --header-filter=^${CMAKE_SOURCE_DIR}/app/ --extra-arg=-std=c++20 ${ALL_SOURCE_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} USES_TERMINAL)
47+
endif()
48+
49+
if(USE_SYSTEM_LIBS)
50+
# Find system libraries (for Nix, system package managers, etc.)
51+
# Most dependencies are now handled by the Rust library
52+
find_package(wxWidgets REQUIRED base core net webview)
53+
find_library(PDFIUM_LIBRARY NAMES pdfium REQUIRED)
54+
find_path(PDFIUM_INCLUDE_DIR NAMES fpdfview.h REQUIRED)
55+
else()
56+
find_package(wxWidgets CONFIG REQUIRED COMPONENTS webview)
57+
endif()
58+
59+
find_package(Gettext)
3460

3561
if(GETTEXT_FOUND)
3662
find_program(XGETTEXT_EXECUTABLE NAMES xgettext)
@@ -149,17 +175,33 @@ else()
149175
endif()
150176
add_dependencies(paperback libpaperback_rust)
151177
target_include_directories(paperback PRIVATE ${CMAKE_SOURCE_DIR}/app ${CMAKE_SOURCE_DIR} ${PAPERBACK_GENERATED_DIR} ${RUST_TARGET_DIR}/cxxbridge)
152-
target_link_directories(paperback PRIVATE ${PDFIUM_LIB_DIR})
153-
target_link_directories(paperback PRIVATE "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/lib")
154-
target_link_libraries(paperback PRIVATE
155-
${RUST_LIB_OUTPUT}
156-
pdfium
157-
wx::base
158-
wx::core
159-
wx::net
160-
wx::webview
161-
)
162-
if (WIN32)
178+
179+
if(USE_SYSTEM_LIBS)
180+
target_include_directories(paperback PRIVATE
181+
${PDFIUM_INCLUDE_DIR}
182+
${wxWidgets_INCLUDE_DIRS}
183+
)
184+
target_compile_options(paperback PRIVATE ${wxWidgets_CXX_FLAGS})
185+
target_compile_definitions(paperback PRIVATE ${wxWidgets_DEFINITIONS})
186+
target_link_libraries(paperback PRIVATE
187+
${RUST_LIB_OUTPUT}
188+
${PDFIUM_LIBRARY}
189+
${wxWidgets_LIBRARIES}
190+
)
191+
else()
192+
target_link_directories(paperback PRIVATE ${PDFIUM_LIB_DIR})
193+
target_link_directories(paperback PRIVATE "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/lib")
194+
target_link_libraries(paperback PRIVATE
195+
${RUST_LIB_OUTPUT}
196+
pdfium
197+
wx::base
198+
wx::core
199+
wx::net
200+
wx::webview
201+
)
202+
endif()
203+
204+
if(WIN32)
163205
target_link_libraries(paperback PRIVATE bcrypt ntdll)
164206
endif()
165207

@@ -238,3 +280,37 @@ if(WIN32)
238280
else()
239281
add_custom_target(release DEPENDS package)
240282
endif()
283+
284+
# Installation rules (Linux desktop integration)
285+
if(UNIX AND NOT APPLE)
286+
# Install binary
287+
install(TARGETS paperback DESTINATION bin)
288+
289+
# Install desktop file
290+
install(FILES ${CMAKE_SOURCE_DIR}/paperback.desktop
291+
DESTINATION share/applications)
292+
293+
# Install icons
294+
foreach(size 16 32 48 64 128 256)
295+
install(FILES ${CMAKE_SOURCE_DIR}/icons/hicolor/${size}x${size}/apps/paperback.png
296+
DESTINATION share/icons/hicolor/${size}x${size}/apps)
297+
endforeach()
298+
299+
# Install documentation
300+
if(TARGET doc)
301+
install(FILES ${CMAKE_BINARY_DIR}/readme.html
302+
DESTINATION share/doc/paperback)
303+
endif()
304+
305+
# Install translations
306+
if(MO_FILES)
307+
foreach(mo_file ${MO_FILES})
308+
get_filename_component(mo_dir ${mo_file} DIRECTORY)
309+
get_filename_component(lang_dir ${mo_dir} DIRECTORY)
310+
get_filename_component(lang ${lang_dir} NAME)
311+
install(FILES ${mo_file}
312+
DESTINATION share/locale/${lang}/LC_MESSAGES
313+
RENAME paperback.mo)
314+
endforeach()
315+
endif()
316+
endif()

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
## Building
1818

19+
### Windows (VCPKG)
20+
1921
We use VCPKG for managing dependencies. Currently we manage our own VCPKG installation through a submodule. As such, make sure to clone Paperback recursively:
2022

2123
```batch
@@ -45,6 +47,50 @@ Optional tools:
4547
* `gettext` tools (`xgettext`, `msgfmt`, `msgmerge`) on your `PATH` to generate the translation template and compile translations.
4648
* InnoSetup installed to create the installer with the `release` target.
4749

50+
### Linux
51+
52+
For building with CMake, you'll need CMake 3.21+, a C++20 compiler, and dependencies:
53+
- wxWidgets 3.2+
54+
- chmlib, lexbor, mbedtls, pdfium, pugixml, nlohmann-json
55+
56+
```bash
57+
cmake -B build -DUSE_SYSTEM_LIBS=ON -DCMAKE_BUILD_TYPE=Release
58+
cmake --build build
59+
sudo cmake --install build
60+
```
61+
62+
Optional tools:
63+
- `pandoc` for HTML readme generation
64+
- `gettext` tools for translations
65+
66+
### Linux (Nix)
67+
68+
**Run directly:**
69+
```bash
70+
nix run github:trypsynth/paperback
71+
```
72+
73+
**Install to profile:**
74+
```bash
75+
nix profile install github:trypsynth/paperback
76+
```
77+
78+
**Build from source:**
79+
```bash
80+
# Clone repository
81+
git clone --recursive https://github.com/trypsynth/paperback
82+
cd paperback
83+
84+
# Build and run
85+
nix run .#paperback
86+
87+
# Or build specific outputs:
88+
nix build .#paperback # Nix derivation
89+
90+
# Build Flatpak:
91+
flatpak-builder --force-clean --repo=repo build io.github.trypsynth.Paperback.yaml
92+
```
93+
4894
## Contributing
4995

5096
Contributions are welcome! Whether through issues, pull requests, discussions, or other means, your interest is most certainly appreciated.

app/config_manager.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ wxArrayString to_wx_array(const rust::Vec<rust::String>& rust_vec) {
2020
return result;
2121
}
2222

23-
std::vector<long> to_long_vector(const rust::Vec<long long>& values) {
23+
std::vector<long> to_long_vector(const rust::Vec<std::int64_t>& values) {
2424
std::vector<long> result(values.size());
25-
std::transform(values.begin(), values.end(), result.begin(), [](long long value) {
25+
std::transform(values.begin(), values.end(), result.begin(), [](std::int64_t value) {
2626
return static_cast<long>(value);
2727
});
2828
return result;
@@ -116,10 +116,10 @@ long config_manager::get_document_position(const wxString& path) const {
116116

117117
void config_manager::set_navigation_history(const wxString& path, const std::vector<long>& history, size_t history_index) {
118118
if (!is_initialized()) return;
119-
rust::Vec<long long> rust_history;
119+
rust::Vec<std::int64_t> rust_history;
120120
rust_history.reserve(history.size());
121121
std::transform(history.begin(), history.end(), std::back_inserter(rust_history), [](long entry) {
122-
return static_cast<long long>(entry);
122+
return static_cast<std::int64_t>(entry);
123123
});
124124
rust::Slice<const std::int64_t> history_slice(rust_history.data(), rust_history.size());
125125
config_manager_set_navigation_history(backend_mut(), to_utf8(path), history_slice, history_index);

app/dialogs.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ void bookmark_dialog::repopulate_list(long current_pos) {
433433
}
434434
}
435435

436-
document_info_dialog::document_info_dialog(wxWindow* parent, session_document* session_doc, const wxString& file_path, config_manager& cfg_mgr) : dialog(parent, _("Document Info"), dialog_button_config::ok_only), config_mgr{cfg_mgr}, doc_path{file_path} {
436+
document_info_dialog::document_info_dialog(wxWindow* parent, session_document* session_doc, const wxString& file_path, [[maybe_unused]] config_manager& cfg_mgr) : dialog(parent, _("Document Info"), dialog_button_config::ok_only), doc_path{file_path} {
437437
constexpr int info_width = 600;
438438
constexpr int info_height = 400;
439439
info_text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(info_width, info_height), wxTE_MULTILINE | wxTE_READONLY);

app/dialogs.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ class document_info_dialog : public dialog {
130130

131131
private:
132132
wxTextCtrl* info_text_ctrl{nullptr};
133-
config_manager& config_mgr;
134133
wxString doc_path;
135134
};
136135

app/document_manager.cpp

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,32 +38,12 @@ bool supports_feature(uint32_t flags, uint32_t feature) {
3838
return (flags & feature) != 0;
3939
}
4040

41-
constexpr uint32_t PARSER_SUPPORTS_SECTIONS = 1 << 0;
4241
constexpr uint32_t PARSER_SUPPORTS_TOC = 1 << 1;
43-
constexpr uint32_t PARSER_SUPPORTS_PAGES = 1 << 2;
44-
constexpr uint32_t PARSER_SUPPORTS_LISTS = 1 << 3;
4542

4643
int to_rust_marker(marker_type type) {
4744
return static_cast<int>(type);
4845
}
4946

50-
std::vector<long> to_long_vector(const rust::Vec<long long>& values) {
51-
std::vector<long> result(values.size());
52-
std::transform(values.begin(), values.end(), result.begin(), [](long long value) {
53-
return static_cast<long>(value);
54-
});
55-
return result;
56-
}
57-
58-
rust::Vec<long long> to_rust_history(const std::vector<long>& history) {
59-
rust::Vec<long long> rust_history;
60-
rust_history.reserve(history.size());
61-
std::transform(history.begin(), history.end(), std::back_inserter(rust_history), [](long value) {
62-
return static_cast<long long>(value);
63-
});
64-
return rust_history;
65-
}
66-
6747
void populate_toc_items(std::vector<std::unique_ptr<toc_item>>& toc_items, const rust::Vec<FfiTocItemWithParent>& ffi_toc_items) {
6848
if (ffi_toc_items.empty()) return;
6949
std::vector<toc_item*> item_ptrs;
@@ -92,12 +72,6 @@ void populate_toc_items(std::vector<std::unique_ptr<toc_item>>& toc_items, const
9272
}
9373
}
9474

95-
void ensure_toc_loaded(session_document& session_doc) {
96-
if (session_doc.toc_loaded) return;
97-
session_doc.toc_loaded = true;
98-
const DocumentHandle& handle = session_doc.get_handle();
99-
populate_toc_items(session_doc.toc_items, document_toc_items_with_parents(handle));
100-
}
10175
} // namespace
10276

10377
void session_document::ensure_toc_loaded() {
@@ -184,9 +158,9 @@ bool document_manager::create_document_tab(const wxString& path, bool set_focus,
184158
size_t history_index = 0;
185159
config.get_navigation_history(path, history, history_index);
186160
if (!history.empty()) {
187-
rust::Vec<long long> rust_history;
161+
rust::Vec<std::int64_t> rust_history;
188162
rust_history.reserve(history.size());
189-
for (long pos : history) rust_history.push_back(static_cast<long long>(pos));
163+
for (long pos : history) rust_history.push_back(static_cast<std::int64_t>(pos));
190164
rust::Slice<const std::int64_t> history_slice(rust_history.data(), rust_history.size());
191165
session_set_history(*session, history_slice, history_index);
192166
}

0 commit comments

Comments
 (0)