├── res ├── main.rc ├── slide.icns ├── slide.ico ├── slide.png ├── slide.desktop └── Info.plist ├── src ├── color.cc ├── main.cc ├── debug_print.h ├── styles.cc ├── base64.h ├── styles.h ├── dimenions.h ├── cairo_wrapper.h ├── color.h ├── slide.h ├── base64.cc ├── CMakeLists.txt ├── documents.h ├── app.h ├── cairo_wrapper.cc ├── documents.cc ├── app.cc ├── slide.cc ├── ui.h └── webview.h ├── test ├── CMakeLists.txt └── slide_test.cc ├── .gitignore ├── CMakeLists.txt ├── .travis.yml ├── README.md ├── .clang-format ├── cmake └── FindCairo.cmake └── LICENSE /res/main.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "slide.ico" 2 | -------------------------------------------------------------------------------- /res/slide.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zserge/slide/HEAD/res/slide.icns -------------------------------------------------------------------------------- /res/slide.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zserge/slide/HEAD/res/slide.ico -------------------------------------------------------------------------------- /res/slide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zserge/slide/HEAD/res/slide.png -------------------------------------------------------------------------------- /src/color.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Patrick Mintram on 28/11/2017. 3 | // 4 | 5 | #include "color.h" 6 | -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | slide::App app; 5 | app.Run(); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | enable_testing() 2 | 3 | add_executable(slide_test slide_test.cc) 4 | target_link_libraries(slide_test PUBLIC slide-core) 5 | 6 | add_test(NAME slide_test COMMAND slide_test catch) 7 | -------------------------------------------------------------------------------- /res/slide.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=0.1.1 3 | Name=Slide 4 | Comment=Minimal Presentation Maker. 5 | Exec=slide 6 | Icon=slide 7 | Terminal=false 8 | Type=Application 9 | Categories=Utility 10 | MimeType=application/x-slide-item; 11 | GenericName=Slide Editor 12 | -------------------------------------------------------------------------------- /src/debug_print.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIDE_DEBUG_PRINT_H 2 | #define SLIDE_DEBUG_PRINT_H 3 | 4 | #include 5 | 6 | #ifdef DEBUG 7 | #define debug_print(a) std::cerr << a << std::endl; 8 | #else 9 | #define debug_print(a) 10 | #endif 11 | 12 | #endif //SLIDE_DEBUG_PRINT_H 13 | -------------------------------------------------------------------------------- /src/styles.cc: -------------------------------------------------------------------------------- 1 | #include "styles.h" 2 | 3 | namespace slide{ 4 | namespace Style{ 5 | const std::string &to_str(const Style &s) { 6 | static const std::array arr{"Normal", "Strong", 7 | "Header", "Monospace"}; 8 | return arr[s]; 9 | } 10 | } 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/base64.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIDE_ENCODING_H 2 | #define SLIDE_ENCODING_H 3 | 4 | #include 5 | 6 | namespace slide { 7 | class Base64 { 8 | public: 9 | static std::string Encode(unsigned char const *bytes_to_encode, 10 | unsigned int in_len, bool url); 11 | 12 | private: 13 | static const std::string base64_chars_; 14 | }; 15 | } // namespace slide 16 | 17 | #endif // SLIDE_ENCODING_H 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # CMake 35 | /build/ 36 | 37 | # Mac hidden files 38 | *.DS_Store 39 | 40 | # CLion dirs 41 | *.idea/ 42 | -------------------------------------------------------------------------------- /src/styles.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIDE_STYLES_H 2 | #define SLIDE_STYLES_H 3 | 4 | #include 5 | #include 6 | namespace slide { 7 | 8 | /** 9 | * @brief The different style of text which can appear on the slide 10 | */ 11 | namespace Style { 12 | enum Style { Normal, Strong, Header, Monospace }; 13 | 14 | const std::string &to_str(const Style &s); 15 | 16 | /** 17 | * @brief The format specifiers for the different styles 18 | */ 19 | constexpr struct { 20 | public: 21 | char Bold = '*'; 22 | char Heading = '#'; 23 | char ImagePath = '@'; 24 | } Markers; 25 | }; // namespace Style 26 | }; // namespace slide 27 | 28 | #endif // SLIDE_STYLES_H 29 | -------------------------------------------------------------------------------- /src/dimenions.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SLIDE_DIMENIONS_H 3 | #define SLIDE_DIMENIONS_H 4 | 5 | #include 6 | namespace slide { 7 | class Dimensions { 8 | public: 9 | Dimensions(const int w, const int h) : w_(w), h_(h) {} 10 | 11 | int &Width(void) { return w_; } 12 | 13 | int &Height(void) { return h_; } 14 | 15 | int Width(void) const { return w_; } 16 | 17 | int Height(void) const { return h_; } 18 | 19 | friend std::ostream &operator<<(std::ostream &out, const Dimensions &d) { 20 | out << "(w_: " << d.w_ << ", h_: " << d.h_ << ")"; 21 | return out; 22 | } 23 | 24 | private: 25 | int w_; 26 | int h_; 27 | }; 28 | } // namespace slide 29 | 30 | #endif // SLIDE_DIMENIONS_H 31 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(Slide) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | 8 | add_subdirectory(src) 9 | add_subdirectory(test) 10 | 11 | # 12 | # Packaging 13 | # 14 | if(APPLE) 15 | set(CPACK_GENERATOR "DragNDrop") 16 | set(CPACK_PACKAGE_FILE_NAME "slide-macos") 17 | elseif(WIN32) 18 | set(CPACK_GENERATOR "ZIP") 19 | set(CPACK_PACKAGE_FILE_NAME "slide-win32") 20 | else() 21 | set(CPACK_GENERATOR "TGZ;DEB") 22 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Serge Zaitsev") 23 | SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libcairo2, libwebkitgtk-3.0-0") 24 | set(CPACK_SET_DESTDIR ON) 25 | set(CPACK_PACKAGE_FILE_NAME "slide-linux") 26 | endif() 27 | 28 | include(CPack) 29 | -------------------------------------------------------------------------------- /res/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleDisplayName 8 | Slide 9 | CFBundleExecutable 10 | slide 11 | CFBundleGetInfoString 12 | 13 | CFBundleIconFile 14 | slide 15 | CFBundleIdentifier 16 | co.trikita.slide 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | Slide 21 | CFBundlePackageType 22 | APPL 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/cairo_wrapper.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIDE_WCAIRO_H 2 | #define SLIDE_WCAIRO_H 3 | 4 | #include "color.h" 5 | #include "styles.h" 6 | #include 7 | #include 8 | #include 9 | 10 | // cairo wrapper class 11 | namespace slide { 12 | class CairoWrapper { 13 | public: 14 | static cairo_surface_t * 15 | CreateSurface(int width, int height, 16 | cairo_format_t format = CAIRO_FORMAT_ARGB32); 17 | 18 | static cairo_surface_t *CreatePDFSurface(const std::string &filename, 19 | const int width, const int height); 20 | 21 | static cairo_t *Create(cairo_surface_t *target); 22 | 23 | static void SetFont(cairo_t *cr, Style::Style style, float scale); 24 | 25 | static void SetSourceColor(cairo_t *cr, const Color &color); 26 | 27 | static int TextHeight(cairo_t *cr, Style::Style style, float scale); 28 | 29 | static int TextWidth(cairo_t *cr, const std::string &text, Style::Style style, 30 | float scale); 31 | 32 | static void Background(cairo_t *cr, int w, int h, const Color &color); 33 | 34 | static void Text(cairo_t *cr, const std::string &text, const Color &color, 35 | int x, int y, Style::Style style, float scale); 36 | 37 | static void ShowPage(cairo_t *cr); 38 | 39 | static void DestroySurface(cairo_surface_t *psurf); 40 | 41 | static void Destroy(cairo_t *pcr); 42 | 43 | static void WriteToPng(cairo_surface_t *surface, const char *filename); 44 | 45 | static void WriteToPngStream(cairo_surface_t *surface, 46 | cairo_write_func_t write_func, void *closure); 47 | }; 48 | } // namespace slide 49 | #endif // SLIDE_WCAIRO_H 50 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | 3 | language: cpp 4 | 5 | matrix: 6 | include: 7 | - os: linux 8 | env: 9 | - COMPILER=g++-7 10 | - CMAKE=cmake 11 | - PACKAGE="slide-linux.*" 12 | addons: 13 | apt: 14 | sources: 15 | - ubuntu-toolchain-r-test 16 | packages: 17 | - g++-7 18 | - cmake 19 | - libcairo2-dev 20 | - libwebkitgtk-3.0-dev 21 | - os: linux 22 | env: 23 | - PATH="/usr/lib/mxe/usr/bin/:$PATH" 24 | - COMPILER=i686-w64-mingw32.static-g++ 25 | - CMAKE=i686-w64-mingw32.static-cmake 26 | - SKIP_TESTS=true 27 | - PACKAGE=slide-win32.zip 28 | addons: 29 | apt: 30 | sources: 31 | - sourceline: 'deb http://pkg.mxe.cc/repos/apt/debian wheezy main' 32 | key_url: 'http://pkg.mxe.cc/repos/apt/conf/mxeapt.gpg' 33 | packages: 34 | - mxe-i686-w64-mingw32.static-gcc 35 | - mxe-i686-w64-mingw32.static-freetype 36 | - mxe-i686-w64-mingw32.static-cairo 37 | - mxe-x86-64-unknown-linux-gnu-cmake 38 | - os: osx 39 | osx_image: xcode9.1 40 | env: 41 | - CMAKE=cmake 42 | - PACKAGE=slide-macos.dmg 43 | before_install: 44 | - brew install cairo 45 | 46 | script: 47 | - if [[ "${COMPILER}" != "" ]]; then export CXX=${COMPILER}; fi 48 | - mkdir build 49 | - cd build 50 | - ${CMAKE} .. 51 | - make 52 | - make package 53 | - if [ -z "${SKIP_TESTS}" ] ; then ./test/slide_test ; fi 54 | 55 | deploy: 56 | provider: releases 57 | api_key: $GITHUB_ACCESS_TOKEN 58 | file_glob: true 59 | file: $PACKAGE 60 | skip_cleanup: true 61 | on: 62 | tags: true 63 | -------------------------------------------------------------------------------- /src/color.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIDE_COLOUR_T_H 2 | #define SLIDE_COLOUR_T_H 3 | 4 | #include 5 | #include 6 | 7 | namespace slide { 8 | class Color { 9 | public: 10 | Color(void) : argb_(0xffeeeeee) { /* Intentionally Blank */ 11 | } 12 | 13 | Color(const uint32_t argb) : argb_(argb) { /* Intentionally Blank */ 14 | } 15 | 16 | Color(const int argb) : argb_(argb) { /* Intentionally Blank */ 17 | } 18 | 19 | double Red(void) const { return ((argb_ >> 16) & 0xff) / 255.0; } 20 | 21 | double Green(void) const { return ((argb_ >> 8) & 0xff) / 255.0; } 22 | 23 | double Blue(void) const { return ((argb_)&0xff) / 255.0; } 24 | 25 | double Alpha(void) const { return ((argb_ >> 24) & 0xff) / 255.0; } 26 | 27 | uint32_t Raw(void) const { return argb_; } 28 | 29 | void operator=(const uint32_t &rhs) { argb_ = rhs; } 30 | 31 | void operator=(const Color &rhs) { argb_ = rhs.argb_; } 32 | 33 | void operator=(const int &rhs) { argb_ = rhs; } 34 | 35 | bool operator==(const uint32_t &rhs) const { return rhs == argb_; } 36 | 37 | bool operator==(const Color &rhs) const { return rhs.argb_ == argb_; } 38 | 39 | bool operator!=(const uint32_t &rhs) const { return rhs != argb_; } 40 | 41 | bool operator!=(const Color &rhs) const { return argb_ != rhs.argb_; } 42 | 43 | bool operator==(const int &rhs) const { return rhs == (int)argb_; } 44 | 45 | bool operator!=(const int &rhs) const { return rhs != (int)argb_; } 46 | 47 | bool operator!=(int &rhs) const { return rhs != (int)argb_; } 48 | 49 | friend std::ostream& operator << (std::ostream& out, const Color& c){ 50 | out << c.argb_; 51 | return out; 52 | } 53 | 54 | protected: 55 | uint32_t argb_; 56 | }; 57 | } // namespace slide 58 | 59 | #endif // SLIDE_COLOUR_T_H 60 | -------------------------------------------------------------------------------- /src/slide.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIDE_HPP 2 | #define SLIDE_HPP 3 | 4 | #include "cairo_wrapper.h" 5 | #include "color.h" 6 | #include "dimenions.h" 7 | #include "documents.h" 8 | #include "styles.h" 9 | #include 10 | #include 11 | #include 12 | namespace slide { 13 | 14 | class Token { 15 | public: 16 | Token(const long p, const int l, const Style::Style s, const std::string t) 17 | : position_(p), line_(l), style_(s), text_(t) { /* Intentionally blank */ 18 | } 19 | long position(void) const { return position_; } 20 | int line(void) const { return line_; } 21 | Style::Style style(void) const { return style_; } 22 | const std::string &text(void) const { return text_; } 23 | 24 | bool operator==(const Token &rhs) const { 25 | return (style_ == rhs.style_ && text_ == rhs.text_); 26 | } 27 | 28 | bool operator==(const Style::Style &rhs) const { return style_ == rhs; } 29 | 30 | bool operator==(const std::string &rhs) const { return text_ == rhs; } 31 | 32 | friend std::ostream &operator<<(std::ostream &out, const Token &rhs) { 33 | out << rhs.position_ << ", " << rhs.line_ << ", " 34 | << Style::to_str(rhs.style_) << ", " << rhs.text_; 35 | return out; 36 | } 37 | 38 | protected: 39 | const long position_; 40 | const int line_; 41 | const Style::Style style_; 42 | const std::string text_; 43 | }; 44 | 45 | using Slide = std::vector; 46 | using Deck = std::vector; 47 | 48 | // Static functions only 49 | Deck Parse(const std::string &text); 50 | std::pair RenderScale(Page &p, Slide &slide, const Color &color, 51 | int xoff, int yoff, float scale); 52 | void Render(Page &page, Slide &slide, const Color &fg, const Color &bg); 53 | 54 | } // namespace slide 55 | 56 | #endif // SLIDE_HPP 57 | -------------------------------------------------------------------------------- /src/base64.cc: -------------------------------------------------------------------------------- 1 | #include "base64.h" 2 | namespace slide { 3 | const std::string Base64::base64_chars_ = 4 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 5 | 6 | std::string Base64::Encode(unsigned char const *bytes_to_encode, 7 | unsigned int in_len, bool url) { 8 | std::string ret; 9 | int i = 0; 10 | int j = 0; 11 | unsigned char char_array_3[3]; 12 | unsigned char char_array_4[4]; 13 | while (in_len--) { 14 | char_array_3[i++] = *(bytes_to_encode++); 15 | if (i == 3) { 16 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 17 | char_array_4[1] = 18 | ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 19 | char_array_4[2] = 20 | ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 21 | char_array_4[3] = char_array_3[2] & 0x3f; 22 | for (i = 0; (i < 4); i++) { 23 | char c = base64_chars_[char_array_4[i]]; 24 | if (url && c == '+') { 25 | ret += "%2B"; 26 | } else if (url && c == '/') { 27 | ret += "%2F"; 28 | } else { 29 | ret += c; 30 | } 31 | } 32 | i = 0; 33 | } 34 | } 35 | if (i) { 36 | for (j = i; j < 3; j++) { 37 | char_array_3[j] = '\0'; 38 | } 39 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 40 | char_array_4[1] = 41 | ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 42 | char_array_4[2] = 43 | ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 44 | char_array_4[3] = char_array_3[2] & 0x3f; 45 | for (j = 0; (j < i + 1); j++) { 46 | char c = base64_chars_[char_array_4[i]]; 47 | if (url && c == '+') { 48 | ret += "%2B"; 49 | } else if (url && c == '/') { 50 | ret += "%2F"; 51 | } else { 52 | ret += c; 53 | } 54 | } 55 | while ((i++ < 3)) { 56 | if (url) { 57 | ret += "%3D"; 58 | } else { 59 | ret += "="; 60 | } 61 | } 62 | } 63 | return ret; 64 | } 65 | 66 | } // namespace slide -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slide - a minimal presentation tool 2 | 3 | [![Build Status](https://travis-ci.org/zserge/slide.svg?branch=master)](https://travis-ci.org/zserge/slide) 4 | 5 |
6 | logo 7 |

A pure and simple presentation tool for Linux/MacOS/Windows inspired by Takahashi method. 8 | With simple markup language you can easily make modern-looking text slides. The main idea of such a technique is to visualize speech keynote with no distracting details. Slides with only a few words shown can easily keep the audience aware of the speaker's point. 9 |

10 |
11 | 12 | ## Syntax 13 | 14 | **General** 15 | 16 | - Each paragraph is a slide. Blank lines separate slides 17 | - Text is rendered using the largest possible font size to fit in the slide bounds 18 | - Single-line text is centered, otherwise it's left-aligned 19 | - Dot at the beginning of a line is ignored. It's helpful to "escape" blank lines or special symbols inside a slide 20 | 21 | **Text style** 22 | 23 | - Text surrounded with `*` is rendered as bold (emphasized). Use `*` without a matching end marker to render a normal `*` sign 24 | 25 | | Text | Text on slide | 26 | |---|---| 27 | |`* hello` | `* hello` | 28 | |`*hello`| `*hello` | 29 | |`* hello*` | `* hello*` | 30 | |`**hello**`| `**hello**` | 31 | | `*hello*` | **hello** | 32 | | `*hello *` | **hello**| 33 | | `***hello***` | **`**hello**`** | 34 | 35 | - Line starting with `#` is a header 36 | - Line starting with `␣␣` (two spaces) is rendered as code (monospace). 37 | 38 | **Images** 39 | 40 | - Line starting with `@` inserts an image from the URL (web, dropbox, local device storage) 41 | - Images may be scaled, e.g `@http://example.com/bg.png 30%` 42 | - Images may have certain gravity, e.g. `@http://example.com/logo.png top` 43 | 44 | ## Download 45 | 46 | See Github [Releases](https://github.com/zserge/slide/releases/latest). 47 | 48 | ## Dependency instructions 49 | 50 | ```bash 51 | # Install Cairo 52 | $ sudo apt-get install libcairo2-dev 53 | or 54 | $ brew install cairo 55 | ``` 56 | 57 | ## Building 58 | 59 | ### Debug 60 | ```bash 61 | $ mkdir build 62 | $ cd build 63 | $ cmake .. 64 | $ make 65 | ``` 66 | ### Release 67 | ```bash 68 | $ mkdir build 69 | $ cd build 70 | $ cmake -D CMAKE_BUILD_TYPE=Release .. 71 | $ make 72 | ``` 73 | 74 | ## Other versions 75 | 76 | Originally Slide was written for Android: https://github.com/trikita/slide 77 | 78 | There is also a single-page HTML that can be edited to make a presentation: https://github.com/trikita/slide-html 79 | 80 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig) 2 | pkg_check_modules(CAIRO REQUIRED cairo) 3 | 4 | add_library(slide-core slide.cc color.cc cairo_wrapper.cc documents.cc base64.cc styles.cc) 5 | target_include_directories(slide-core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CAIRO_INCLUDE_DIRS}) 6 | target_link_libraries(slide-core PUBLIC ${CAIRO_LIBRARIES}) 7 | 8 | add_library(app app.cc color.cc) 9 | target_include_directories(app INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ${CAIRO_INCLUDE_DIRS}) 10 | target_link_libraries(app PRIVATE webview slide-core) 11 | 12 | add_library(webview INTERFACE) 13 | target_include_directories(webview INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}") 14 | 15 | 16 | if(CMAKE_BUILD_TYPE MATCHES Debug) 17 | message("Debug build defined, re-run cmake with -D CMAKE_BUILD_TYPE=Release for release build") 18 | target_compile_definitions(app PRIVATE "-DDEBUG") 19 | endif() 20 | 21 | if(APPLE) 22 | message(STATUS "Apple detected") 23 | target_compile_definitions(webview INTERFACE "-DWEBVIEW_COCOA=1") 24 | target_compile_options(webview INTERFACE "-x" "objective-c++") 25 | target_link_libraries(webview INTERFACE "-framework Cocoa" "-framework WebKit") 26 | elseif(WIN32) 27 | message(STATUS "Windows detected") 28 | target_compile_definitions(webview INTERFACE "-DWEBVIEW_WINAPI=1") 29 | target_link_libraries(webview INTERFACE ole32 comctl32 oleaut32 uuid) 30 | else() 31 | message(STATUS "Neither Apple nor Windows detected") 32 | target_compile_definitions(webview INTERFACE "-DWEBVIEW_GTK=1") 33 | find_package(PkgConfig REQUIRED) 34 | pkg_check_modules(GTK3 REQUIRED gtk+-3.0) 35 | pkg_check_modules(WEBKIT REQUIRED webkitgtk-3.0) 36 | target_include_directories(webview INTERFACE ${GTK3_INCLUDE_DIRS} ${WEBKIT_INCLUDE_DIRS} ${PROJECT_SOURCE_DIR}) 37 | target_link_libraries(webview INTERFACE ${GTK3_LIBRARIES} ${WEBKIT_LIBRARIES}) 38 | endif() 39 | 40 | add_library(json INTERFACE) 41 | target_include_directories(json INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ${CAIRO_INCLUDE_DIRS}) 42 | 43 | if(WIN32) 44 | add_executable(slide WIN32 main.cc ${CMAKE_SOURCE_DIR}/res/main.rc) 45 | elseif(APPLE) 46 | add_executable(slide main.cc ${CMAKE_SOURCE_DIR}/res/main.rc) 47 | set_source_files_properties(main.cc PROPERTIES COMPILE_FLAGS "-x objective-c++") 48 | else() 49 | add_executable(slide main.cc ${CMAKE_SOURCE_DIR}/res/main.rc) 50 | endif() 51 | target_link_libraries(slide PRIVATE slide-core webview json app) 52 | 53 | if(APPLE) 54 | install(TARGETS slide RUNTIME DESTINATION "slide.app/Contents/MacOS") 55 | install(FILES ${CMAKE_SOURCE_DIR}/res/Info.plist DESTINATION "slide.app/Contents") 56 | install(FILES ${CMAKE_SOURCE_DIR}/res/slide.icns DESTINATION "slide.app/Contents/Resources") 57 | elseif(WIN32) 58 | install(TARGETS slide RUNTIME DESTINATION .) 59 | else() 60 | install(TARGETS slide RUNTIME DESTINATION bin) 61 | install(FILES ${CMAKE_SOURCE_DIR}/res/slide.desktop DESTINATION "/usr/share/applications") 62 | install(FILES ${CMAKE_SOURCE_DIR}/res/slide.png DESTINATION "/usr/share/icons") 63 | endif() 64 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | BeforeCatch: false 33 | BeforeElse: false 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Attach 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | ColumnLimit: 80 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 49 | IncludeCategories: 50 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 51 | Priority: 2 52 | - Regex: '^(<|"(gtest|isl|json)/)' 53 | Priority: 3 54 | - Regex: '.*' 55 | Priority: 1 56 | IndentCaseLabels: false 57 | IndentWidth: 2 58 | IndentWrappedFunctionNames: false 59 | KeepEmptyLinesAtTheStartOfBlocks: true 60 | MacroBlockBegin: '' 61 | MacroBlockEnd: '' 62 | MaxEmptyLinesToKeep: 1 63 | NamespaceIndentation: None 64 | ObjCBlockIndentWidth: 2 65 | ObjCSpaceAfterProperty: false 66 | ObjCSpaceBeforeProtocolList: true 67 | PenaltyBreakBeforeFirstCallParameter: 19 68 | PenaltyBreakComment: 300 69 | PenaltyBreakFirstLessLess: 120 70 | PenaltyBreakString: 1000 71 | PenaltyExcessCharacter: 1000000 72 | PenaltyReturnTypeOnItsOwnLine: 60 73 | PointerAlignment: Right 74 | ReflowComments: true 75 | SortIncludes: true 76 | SpaceAfterCStyleCast: false 77 | SpaceBeforeAssignmentOperators: true 78 | SpaceBeforeParens: ControlStatements 79 | SpaceInEmptyParentheses: false 80 | SpacesBeforeTrailingComments: 1 81 | SpacesInAngles: false 82 | SpacesInContainerLiterals: true 83 | SpacesInCStyleCastParentheses: false 84 | SpacesInParentheses: false 85 | SpacesInSquareBrackets: false 86 | Standard: Cpp11 87 | TabWidth: 8 88 | UseTab: Never 89 | ... 90 | 91 | -------------------------------------------------------------------------------- /src/documents.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIDE_DOCUMENTS_H 2 | #define SLIDE_DOCUMENTS_H 3 | 4 | #include "cairo_wrapper.h" 5 | #include "dimenions.h" 6 | #include "styles.h" 7 | 8 | namespace slide { 9 | 10 | // abstract 11 | class Page { 12 | public: 13 | Page(const int w, const int h, cairo_surface_t *const psurf, 14 | cairo_t *const pcr, const std::string &name); 15 | 16 | Page(const int w, const int h, const std::string &name = "Name Not Set"); 17 | 18 | Page(void); 19 | 20 | virtual ~Page(void); 21 | 22 | virtual const Dimensions &Size(void) { return size_; } 23 | 24 | virtual int TextHeight(Style::Style style, float scale) = 0; 25 | 26 | virtual int TextWidth(const std::string &text, Style::Style style, 27 | float scale) = 0; 28 | 29 | virtual void Background(const Color &color) = 0; 30 | 31 | virtual void Text(const std::string &text, const Color &color, int x, int y, 32 | Style::Style style, float scale) = 0; 33 | 34 | protected: 35 | const Dimensions size_; 36 | cairo_surface_t *surface_; 37 | cairo_t *cr_; 38 | const std::string name_; 39 | 40 | virtual void InitialiseContext(void) = 0; 41 | virtual void EnsureInitialised(void); 42 | }; 43 | 44 | // Abstract 45 | class Document : public Page { 46 | public: 47 | Document(const int w, const int h, cairo_surface_t *const psurf, 48 | cairo_t *const pcr, const std::string &name); 49 | 50 | Document(const int w, const int h, const std::string &name); 51 | 52 | virtual ~Document(void) { /* Intentionally Blank */ 53 | } 54 | 55 | virtual void BeginPage(void) = 0; 56 | 57 | virtual void EndPage(void) = 0; 58 | }; 59 | 60 | class PNG : public Page { 61 | public: 62 | PNG(const int w, const int h); 63 | 64 | PNG(const int w, const int h, const std::string &name); 65 | 66 | ~PNG(void); 67 | 68 | int TextHeight(Style::Style style, float scale); 69 | 70 | int TextWidth(const std::string &text, Style::Style style, float scale); 71 | 72 | void Background(const Color &color); 73 | 74 | void Text(const std::string &text, const Color &color, int x, int y, 75 | Style::Style style, float scale); 76 | 77 | void Save(const std::string filename); 78 | 79 | std::string DataUri(void); 80 | 81 | protected: 82 | void InitialiseContext(void); 83 | }; 84 | 85 | class PDF : public Document { 86 | public: 87 | PDF(const std::string name, const int w, const int h); 88 | 89 | ~PDF(void); 90 | 91 | void BeginPage(void); 92 | 93 | void EndPage(void); 94 | 95 | int TextHeight(Style::Style style, float scale); 96 | 97 | int TextWidth(const std::string &text, Style::Style style, float scale); 98 | 99 | void Background(const Color &color); 100 | 101 | void Text(const std::string &text, const Color &color, int x, int y, 102 | Style::Style style, float scale); 103 | 104 | protected: 105 | void InitialiseContext(void); 106 | }; 107 | } // namespace slide 108 | 109 | #endif // SLIDE_DOCUMENTS_H 110 | -------------------------------------------------------------------------------- /src/app.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIDE_APP_H 2 | #define SLIDE_APP_H 3 | 4 | #include "color.h" 5 | #include "dimenions.h" 6 | #include "json.hpp" 7 | #include "slide.h" 8 | #include "ui.h" 9 | #include "webview.h" 10 | #include 11 | #include 12 | #include 13 | 14 | namespace slide { 15 | class CommandInterface; 16 | 17 | class App { 18 | public: 19 | App(void); 20 | 21 | void Run(void); 22 | 23 | void RenderCurrentSlide(void); 24 | 25 | void Render(void); 26 | 27 | struct webview &webview(void) { 28 | return wview_; 29 | } 30 | 31 | std::string &CurrentFile(void) { return current_file_; } 32 | 33 | slide::Deck &Deck(void) { return deck_; } 34 | 35 | std::string &CurrentText(void) { return current_text_; } 36 | 37 | Color &Foreground(void) { return foreground_; } 38 | 39 | Color &Background(void) { return background_; } 40 | 41 | Dimensions &PreviewSize(void) { return preview_size_; } 42 | 43 | int &CurrentSlide(void) { return current_slide_; } 44 | 45 | friend std::ostream &operator<<(std::ostream &out, const App &a) { 46 | 47 | out << "deck_ size: " << a.deck_.size() << std::endl 48 | << "current_slide_: " << a.current_slide_ << std::endl 49 | << "current_file_: " << a.current_file_ << std::endl 50 | << "current_text_ and preview_data_uri_ omitted" 51 | << std::endl 52 | //<< "current_text_: " << a.current_text_ << std::endl 53 | //<< "preview_data_uri: " << a.preview_data_uri_ << std::endl 54 | << "preview_data_size: " << a.preview_size_ << std::endl 55 | << "foreground_: " << a.foreground_ << std::endl 56 | << "background_: " << a.background_ << std::endl 57 | << "wview_: " << a.wview_.title << std::endl 58 | << "commands_map_: "; 59 | 60 | for (auto cmd : a.cmds_map_) { 61 | out << cmd.first << ", "; 62 | } 63 | return out; 64 | } 65 | 66 | private: 67 | void HandleCommand(const std::string &data); 68 | 69 | slide::Deck deck_; 70 | int current_slide_ = -1; 71 | std::string current_file_; 72 | std::string current_text_; 73 | std::string preview_data_uri_; 74 | Dimensions preview_size_ = {320, 240}; 75 | Color foreground_ = 0xffeeeeee; 76 | Color background_ = 0xff333333; 77 | struct webview wview_ = {}; 78 | std::unordered_map cmds_map_; 79 | }; 80 | 81 | class CommandInterface { 82 | public: 83 | virtual void Execute(App &app, nlohmann::json &json) = 0; 84 | }; 85 | 86 | class CreateFileCmd : public CommandInterface { 87 | public: 88 | void Execute(App &app, nlohmann::json &json); 89 | }; 90 | 91 | class OpenFileCmd : public CommandInterface { 92 | public: 93 | void Execute(App &app, nlohmann::json &json); 94 | }; 95 | 96 | class ExportPdfCmd : public CommandInterface { 97 | public: 98 | void Execute(App &app, nlohmann::json &json); 99 | }; 100 | 101 | class SetPreviewSizeCmd : public CommandInterface { 102 | public: 103 | void Execute(App &app, nlohmann::json &json); 104 | }; 105 | 106 | class SetPaletteCmd : public CommandInterface { 107 | public: 108 | void Execute(App &app, nlohmann::json &json); 109 | }; 110 | 111 | class SetTextCmd : public CommandInterface { 112 | public: 113 | void Execute(App &app, nlohmann::json &json); 114 | }; 115 | 116 | class SetCursorCmd : public CommandInterface { 117 | public: 118 | void Execute(App &app, nlohmann::json &json); 119 | }; 120 | } // namespace slide 121 | #endif // SLIDE_APP_H 122 | -------------------------------------------------------------------------------- /cmake/FindCairo.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find Cairo 2 | # Once done, this will define 3 | # 4 | # CAIRO_FOUND - system has Cairo 5 | # CAIRO_INCLUDE_DIRS - the Cairo include directories 6 | # CAIRO_LIBRARIES - link these to use Cairo 7 | # 8 | # Copyright (C) 2012 Raphael Kubo da Costa 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions 12 | # are met: 13 | # 1. Redistributions of source code must retain the above copyright 14 | # notice, this list of conditions and the following disclaimer. 15 | # 2. Redistributions in binary form must reproduce the above copyright 16 | # notice, this list of conditions and the following disclaimer in the 17 | # documentation and/or other materials provided with the distribution. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS 20 | # IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 21 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS 23 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 26 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 27 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 28 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 29 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | FIND_PACKAGE(PkgConfig) 32 | PKG_CHECK_MODULES(PC_CAIRO cairo) # FIXME: After we require CMake 2.8.2 we can pass QUIET to this call. 33 | 34 | FIND_PATH(CAIRO_INCLUDE_DIRS 35 | NAMES cairo.h 36 | HINTS ${PC_CAIRO_INCLUDEDIR} 37 | ${PC_CAIRO_INCLUDE_DIRS} 38 | PATH_SUFFIXES cairo 39 | ) 40 | 41 | FIND_LIBRARY(CAIRO_LIBRARIES 42 | NAMES cairo 43 | HINTS ${PC_CAIRO_LIBDIR} 44 | ${PC_CAIRO_LIBRARY_DIRS} 45 | ) 46 | 47 | IF (CAIRO_INCLUDE_DIRS) 48 | IF (EXISTS "${CAIRO_INCLUDE_DIRS}/cairo-version.h") 49 | FILE(READ "${CAIRO_INCLUDE_DIRS}/cairo-version.h" CAIRO_VERSION_CONTENT) 50 | 51 | STRING(REGEX MATCH "#define +CAIRO_VERSION_MAJOR +([0-9]+)" _dummy "${CAIRO_VERSION_CONTENT}") 52 | SET(CAIRO_VERSION_MAJOR "${CMAKE_MATCH_1}") 53 | 54 | STRING(REGEX MATCH "#define +CAIRO_VERSION_MINOR +([0-9]+)" _dummy "${CAIRO_VERSION_CONTENT}") 55 | SET(CAIRO_VERSION_MINOR "${CMAKE_MATCH_1}") 56 | 57 | STRING(REGEX MATCH "#define +CAIRO_VERSION_MICRO +([0-9]+)" _dummy "${CAIRO_VERSION_CONTENT}") 58 | SET(CAIRO_VERSION_MICRO "${CMAKE_MATCH_1}") 59 | 60 | SET(CAIRO_VERSION "${CAIRO_VERSION_MAJOR}.${CAIRO_VERSION_MINOR}.${CAIRO_VERSION_MICRO}") 61 | ENDIF () 62 | ENDIF () 63 | 64 | # FIXME: Should not be needed anymore once we start depending on CMake 2.8.3 65 | SET(VERSION_OK TRUE) 66 | IF (Cairo_FIND_VERSION) 67 | IF (Cairo_FIND_VERSION_EXACT) 68 | IF ("${Cairo_FIND_VERSION}" VERSION_EQUAL "${CAIRO_VERSION}") 69 | # FIXME: Use IF (NOT ...) with CMake 2.8.2+ to get rid of the ELSE block 70 | ELSE () 71 | SET(VERSION_OK FALSE) 72 | ENDIF () 73 | ELSE () 74 | IF ("${Cairo_FIND_VERSION}" VERSION_GREATER "${CAIRO_VERSION}") 75 | SET(VERSION_OK FALSE) 76 | ENDIF () 77 | ENDIF () 78 | ENDIF () 79 | 80 | INCLUDE(FindPackageHandleStandardArgs) 81 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(Cairo DEFAULT_MSG CAIRO_INCLUDE_DIRS CAIRO_LIBRARIES VERSION_OK) -------------------------------------------------------------------------------- /src/cairo_wrapper.cc: -------------------------------------------------------------------------------- 1 | #include "cairo_wrapper.h" 2 | #include 3 | 4 | #define print(a) /* std::cerr << a << std::endl */ 5 | 6 | namespace slide { 7 | void CairoWrapper::SetFont(cairo_t *cr, Style::Style style, float scale) { 8 | switch (style) { 9 | case Style::Normal: 10 | cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, 11 | CAIRO_FONT_WEIGHT_NORMAL); 12 | break; 13 | case Style::Strong: 14 | cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, 15 | CAIRO_FONT_WEIGHT_BOLD); 16 | break; 17 | case Style::Header: 18 | cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, 19 | CAIRO_FONT_WEIGHT_BOLD); 20 | scale = scale * 1.6f; 21 | break; 22 | case Style::Monospace: 23 | cairo_select_font_face(cr, "monospace", CAIRO_FONT_SLANT_NORMAL, 24 | CAIRO_FONT_WEIGHT_NORMAL); 25 | break; 26 | } 27 | cairo_set_font_size(cr, 16 * scale); 28 | } 29 | 30 | void CairoWrapper::SetSourceColor(cairo_t *cr, const Color &color) { 31 | cairo_set_source_rgba(cr, color.Red(), color.Green(), color.Blue(), 32 | color.Alpha()); 33 | } 34 | 35 | int CairoWrapper::TextHeight(cairo_t *cr, Style::Style style, float scale) { 36 | cairo_font_extents_t fe = {}; 37 | SetFont(cr, style, scale); 38 | cairo_font_extents(cr, &fe); 39 | return (int)(fe.height); 40 | } 41 | 42 | int CairoWrapper::TextWidth(cairo_t *cr, const std::string &text, 43 | Style::Style style, float scale) { 44 | cairo_text_extents_t te = {}; 45 | SetFont(cr, style, scale); 46 | cairo_text_extents(cr, text.c_str(), &te); 47 | return (int)(te.x_advance); 48 | } 49 | 50 | void CairoWrapper::Background(cairo_t *cr, int w, int h, const Color &color) { 51 | SetSourceColor(cr, color); 52 | cairo_rectangle(cr, 0, 0, w, h); 53 | cairo_fill(cr); 54 | } 55 | 56 | void CairoWrapper::Text(cairo_t *cr, const std::string &text, 57 | const Color &color, int x, int y, Style::Style style, 58 | float scale) { 59 | cairo_font_extents_t fe = {}; 60 | SetSourceColor(cr, color); 61 | SetFont(cr, style, scale); 62 | cairo_font_extents(cr, &fe); 63 | cairo_move_to(cr, x, y + fe.ascent); 64 | cairo_show_text(cr, text.c_str()); 65 | } 66 | 67 | void CairoWrapper::ShowPage(cairo_t *cr) { cairo_show_page(cr); } 68 | 69 | void CairoWrapper::DestroySurface(cairo_surface_t *psurf) { 70 | print("Attempting to destroy surface at " << psurf); 71 | if (nullptr != psurf) { 72 | cairo_surface_destroy(psurf); 73 | } 74 | } 75 | 76 | void CairoWrapper::Destroy(cairo_t *pcr) { 77 | print("Attempting to destroy cairo at " << pcr); 78 | if (nullptr != pcr) { 79 | cairo_destroy(pcr); 80 | } 81 | } 82 | 83 | cairo_surface_t *CairoWrapper::CreateSurface(int width, int height, 84 | cairo_format_t format) { 85 | cairo_surface_t *rc = cairo_image_surface_create(format, width, height); 86 | print("Surface created at " << rc); 87 | return rc; 88 | } 89 | 90 | cairo_t *CairoWrapper::Create(cairo_surface_t *target) { 91 | cairo_t *rc = cairo_create(target); 92 | print("Cairo created at " << rc); 93 | return rc; 94 | } 95 | 96 | void CairoWrapper::WriteToPng(cairo_surface_t *surface, const char *filename) { 97 | auto rc = cairo_surface_write_to_png(surface, filename); 98 | print("cairo_surface_write_to_png returned " << rc); 99 | } 100 | 101 | void CairoWrapper::WriteToPngStream(cairo_surface_t *surface, 102 | cairo_write_func_t write_func, 103 | void *closure) { 104 | auto rc = cairo_surface_write_to_png_stream(surface, write_func, closure); 105 | print("cairo_surface_write_to_png_stream returned " << rc); 106 | } 107 | 108 | cairo_surface_t *CairoWrapper::CreatePDFSurface(const std::string &filename, 109 | const int width, 110 | const int height) { 111 | auto rc = cairo_pdf_surface_create(filename.c_str(), width, height); 112 | print("cairo_pdf_surface_create returned " << rc); 113 | return rc; 114 | } 115 | } // namespace slide -------------------------------------------------------------------------------- /src/documents.cc: -------------------------------------------------------------------------------- 1 | #include "documents.h" 2 | #include "base64.h" 3 | #include 4 | #include 5 | #include "debug_print.h" 6 | 7 | namespace slide { 8 | Page::Page(const int w, const int h, const std::string &name) 9 | : size_(w, h), name_(name), surface_(nullptr), cr_(nullptr) {} 10 | 11 | Page::~Page(void) { 12 | debug_print("Page_b::dtor destroying _surface and _cr"); 13 | CairoWrapper::DestroySurface(surface_); 14 | CairoWrapper::Destroy(cr_); 15 | } 16 | 17 | Document::Document(const int w, const int h, cairo_surface_t *const psurf, 18 | cairo_t *const pcr, const std::string &name) 19 | : Page(w, h, psurf, pcr, name) { /* intentionally blank */ 20 | } 21 | 22 | Document::Document(const int w, const int h, const std::string &name) 23 | : Page(w, h, name) { /* intentionally blank */ 24 | } 25 | 26 | Page::Page(const int w, const int h, cairo_surface_t *const psurf, 27 | cairo_t *const pcr, const std::string &name) 28 | : size_(w, h), surface_(psurf), cr_(pcr), 29 | name_(name) { /* intentionally blank */ 30 | } 31 | 32 | Page::Page(void) 33 | : size_(0, 0), surface_(0), cr_(0), 34 | name_("No Name Set") { /* intentionally blank */ 35 | } 36 | 37 | void Page::EnsureInitialised(void) { 38 | if (cr_ == nullptr || surface_ == nullptr) { 39 | InitialiseContext(); 40 | } 41 | } 42 | 43 | PNG::PNG(const int w, const int h) 44 | : Page(w, h, "No Name Set") { /* Intentionally Blank */ 45 | } 46 | 47 | PNG::PNG(const int w, const int h, const std::string &name) 48 | : Page(w, h, name) { /* Intentionally Blank */ 49 | } 50 | 51 | PNG::~PNG(void) { /* Intentionally Blank */ 52 | } 53 | 54 | int PNG::TextHeight(Style::Style style, float scale) { 55 | EnsureInitialised(); 56 | return CairoWrapper::TextHeight(cr_, style, scale); 57 | } 58 | 59 | int PNG::TextWidth(const std::string &text, Style::Style style, float scale) { 60 | EnsureInitialised(); 61 | return CairoWrapper::TextWidth(cr_, text, style, scale); 62 | } 63 | 64 | void PNG::Background(const Color &color) { 65 | EnsureInitialised(); 66 | CairoWrapper::Background(cr_, size_.Width(), size_.Width(), color); 67 | } 68 | 69 | void PNG::Text(const std::string &text, const Color &color, int x, int y, 70 | Style::Style style, float scale) { 71 | EnsureInitialised(); 72 | CairoWrapper::Text(cr_, text, color, x, y, style, scale); 73 | } 74 | 75 | void PNG::Save(const std::string filename) { 76 | EnsureInitialised(); 77 | CairoWrapper::WriteToPng(surface_, filename.c_str()); 78 | } 79 | 80 | std::string PNG::DataUri(void) { 81 | EnsureInitialised(); 82 | std::vector raw; 83 | CairoWrapper::WriteToPngStream( 84 | surface_, 85 | [](void *closure, const unsigned char *data, unsigned int length) { 86 | std::vector *raw = 87 | (std::vector *)(closure); 88 | for (unsigned int i = 0; i < length; i++) { 89 | raw->push_back(data[i]); 90 | } 91 | return CAIRO_STATUS_SUCCESS; 92 | }, 93 | &raw); 94 | return "data:image/png;base64," + 95 | Base64::Encode(raw.data(), raw.size(), true); 96 | } 97 | 98 | void PNG::InitialiseContext(void) { 99 | // std::cerr << "PNG::ctor creating surface_ and cr_" << std::endl; 100 | surface_ = CairoWrapper::CreateSurface(size_.Width(), size_.Height()); 101 | cr_ = CairoWrapper::Create(surface_); 102 | } 103 | 104 | PDF::PDF(const std::string name, const int w, const int h) 105 | : Document(w, h, name) { 106 | /* Intentionally Blank */ 107 | } 108 | 109 | PDF::~PDF(void) { 110 | // Intentionally blank 111 | } 112 | 113 | void PDF::BeginPage(void) { 114 | // intentionally blank 115 | } 116 | 117 | void PDF::EndPage(void) { CairoWrapper::ShowPage(cr_); } 118 | 119 | int PDF::TextHeight(Style::Style style, float scale) { 120 | EnsureInitialised(); 121 | return CairoWrapper::TextHeight(cr_, style, scale); 122 | } 123 | 124 | int PDF::TextWidth(const std::string &text, Style::Style style, float scale) { 125 | EnsureInitialised(); 126 | return CairoWrapper::TextWidth(cr_, text, style, scale); 127 | } 128 | 129 | void PDF::Background(const Color &color) { 130 | EnsureInitialised(); 131 | CairoWrapper::Background(cr_, size_.Width(), size_.Height(), color); 132 | } 133 | 134 | void PDF::Text(const std::string &text, const Color &color, int x, int y, 135 | Style::Style style, float scale) { 136 | EnsureInitialised(); 137 | CairoWrapper::Text(cr_, text, color, x, y, style, scale); 138 | } 139 | 140 | void PDF::InitialiseContext(void) { 141 | debug_print("PDF::ctor creating surface_ and cr_"); 142 | surface_ = 143 | CairoWrapper::CreatePDFSurface(name_, size_.Width(), size_.Height()); 144 | cr_ = CairoWrapper::Create(surface_); 145 | } 146 | } // namespace slide -------------------------------------------------------------------------------- /test/slide_test.cc: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch.hpp" 3 | 4 | #include 5 | namespace slide { 6 | TEST_CASE("Empty deck", "[slide::parse]") { 7 | slide::Deck deck; 8 | 9 | // An empty deck is actually 1 slide with no text on 10 | deck = slide::Parse(""); 11 | REQUIRE(deck.size() == 1); 12 | deck = slide::Parse("\n"); 13 | REQUIRE(deck.size() == 1); 14 | deck = slide::Parse("\n\n"); 15 | REQUIRE(deck.size() == 1); 16 | } 17 | 18 | TEST_CASE("Slide boundaries", "[slide::parse]") { 19 | slide::Deck deck; 20 | 21 | // Single-slide decks 22 | deck = slide::Parse("foo"); 23 | REQUIRE(deck.size() == 1); 24 | deck = slide::Parse("foo\n"); 25 | REQUIRE(deck.size() == 1); 26 | deck = slide::Parse("foo\n\n\n"); 27 | REQUIRE(deck.size() == 1); 28 | deck = slide::Parse("\nfoo"); 29 | REQUIRE(deck.size() == 1); 30 | deck = slide::Parse("\n\nfoo\n"); 31 | REQUIRE(deck.size() == 1); 32 | deck = slide::Parse("\n\nfoo\n\n\n"); 33 | REQUIRE(deck.size() == 1); 34 | 35 | // Two-slide decks 36 | deck = slide::Parse("foo\n\nbar"); 37 | REQUIRE(deck.size() == 2); 38 | deck = slide::Parse("foo\n\nbar\n"); 39 | REQUIRE(deck.size() == 2); 40 | deck = slide::Parse("foo\n\nbar\n\n"); 41 | REQUIRE(deck.size() == 2); 42 | deck = slide::Parse("\nfoo\n\nbar\n\n"); 43 | REQUIRE(deck.size() == 2); 44 | deck = slide::Parse("\nfoo\nbar\n\nbaz\n\n"); 45 | REQUIRE(deck.size() == 2); 46 | } 47 | 48 | TEST_CASE("Slide tokens", "[slide::parse]") { 49 | slide::Deck deck; 50 | deck = slide::Parse(R"( 51 | # Header 1 52 | Foo 53 | Bar 54 | 55 | 56 | 57 | #Header 2 58 | 59 | #include 60 | 61 | int main() { 62 | int n; /* comment */ 63 | int *i = &n; *i = 0; 64 | return 0; 65 | } 66 | 67 | # Header 3 68 | 69 | # Header 4 70 | . 71 | .# normal line 72 | . normal line 73 | Кириллица. € ß 74 | Some *bold* text 75 | Some *other bold* text)"); 76 | 77 | REQUIRE(deck.size() == 4); 78 | 79 | REQUIRE(deck[0].size() == 3); 80 | REQUIRE(deck[0][0].text() == "Header 1"); 81 | REQUIRE(deck[0][1].text() == "Foo"); 82 | REQUIRE(deck[0][2].text() == "Bar"); 83 | REQUIRE(deck[0][0].style() == Style::Header); 84 | REQUIRE(deck[0][1].style() == Style::Normal); 85 | REQUIRE(deck[0][2].style() == Style::Normal); 86 | REQUIRE(deck[0][0].line() == 1); 87 | REQUIRE(deck[0][1].line() == 2); 88 | REQUIRE(deck[0][2].line() == 3); 89 | 90 | REQUIRE(deck[1].size() == 9); 91 | REQUIRE(deck[1][0].text() == "Header 2"); 92 | 93 | REQUIRE(deck[2].size() == 1); 94 | REQUIRE(deck[2][0].text() == "Header 3"); 95 | 96 | REQUIRE(deck[3].size() == 11); 97 | REQUIRE(deck[3][1].text() == ""); 98 | REQUIRE(deck[3][0].text() == "Header 4"); 99 | REQUIRE(deck[3][2].text() == "# normal line"); 100 | REQUIRE(deck[3][3].text() == " normal line"); 101 | REQUIRE(deck[3][4].text() == "Кириллица. € ß"); 102 | REQUIRE(deck[3][5].text() == "Some "); 103 | REQUIRE(deck[3][6].text() == "bold"); 104 | REQUIRE(deck[3][6].style() == Style::Strong); 105 | REQUIRE(deck[3][7].text() == " text"); 106 | REQUIRE(deck[3][8].text() == "Some "); 107 | REQUIRE(deck[3][9].text() == "other bold"); 108 | REQUIRE(deck[3][9].style() == Style::Strong); 109 | REQUIRE(deck[3][10].text() == " text"); 110 | } 111 | 112 | TEST_CASE("Asterisks", "[slide::parse]") { 113 | slide::Deck deck; 114 | deck = slide::Parse(R"( 115 | foo *bar* baz 116 | *foo bar* baz 117 | foo *bar baz* 118 | foo **bar baz 119 | * foo bar baz* 120 | **notbold** 121 | *bold * 122 | * not bold * 123 | *not bold 124 | * not bold 125 | )"); 126 | 127 | CHECK(deck[0].size() == 14); 128 | CHECK(deck[0][0] == Token(0, 0, Style::Normal, "foo ")); 129 | CHECK(deck[0][1] == Token(0, 0, Style::Strong, "bar")); 130 | CHECK(deck[0][2] == Token(0, 0, Style::Normal, " baz")); 131 | CHECK(deck[0][3] == Token(0, 0, Style::Strong, "foo bar")); 132 | CHECK(deck[0][4] == Token(0, 0, Style::Normal, " baz")); 133 | CHECK(deck[0][5] == Token(0, 0, Style::Normal, "foo ")); 134 | CHECK(deck[0][6] == Token(0, 0, Style::Strong, "bar baz")); 135 | CHECK(deck[0][7] == Token(0, 0, Style::Normal, "foo **bar baz")); 136 | CHECK(deck[0][8] == Token(0, 0, Style::Normal, "* foo bar baz*")); 137 | CHECK(deck[0][9] == Token(0, 0, Style::Normal, "**notbold**")); 138 | CHECK(deck[0][10] == Token(0, 0, Style::Strong, "bold ")); 139 | CHECK(deck[0][11] == Token(0, 0, Style::Normal, "* not bold *")); 140 | CHECK(deck[0][12] == Token(0, 0, Style::Normal, "*not bold")); 141 | CHECK(deck[0][13] == Token(0, 0, Style::Normal, "* not bold")); 142 | } 143 | 144 | TEST_CASE("Render", "[slide::render]") { 145 | auto deck = slide::Parse(R"( 146 | # Hello 147 | foo *bar* baz 148 | )"); 149 | class MockPage : public Page { 150 | public: 151 | MockPage(void) : Page(100, 50) { /* Intentionally Blank */ 152 | } 153 | 154 | int TextHeight(Style::Style style, float scale) { 155 | called_["text_height"]++; 156 | return (int)(scale * 10); 157 | } 158 | 159 | int TextWidth(const std::string &s, Style::Style style, float scale) { 160 | called_["text_width"]++; 161 | return (int)(s.size() * scale * 5); 162 | } 163 | 164 | void Background(const Color &c) { 165 | called_["bg"]++; 166 | REQUIRE(c == 0xff00ff00); 167 | } 168 | 169 | void Text(const std::string &s, const Color &c, int x, int y, Style::Style, 170 | float scale) { 171 | called_["text::" + s]++; 172 | REQUIRE((s == "Hello" || s == "foo " || s == "bar" || s == " baz")); 173 | REQUIRE((scale == 1.f || (scale > 1.44f && scale < 1.46f))); 174 | } 175 | 176 | void Check(void) { 177 | REQUIRE(called_["bg"] > 0); 178 | REQUIRE(called_["text_width"] > 0); 179 | REQUIRE(called_["text_height"] > 0); 180 | REQUIRE(called_["text::Hello"] > 0); 181 | REQUIRE(called_["text::foo "] > 0); 182 | REQUIRE(called_["text::bar"] > 0); 183 | REQUIRE(called_["text:: baz"] > 0); 184 | } 185 | 186 | private: 187 | std::map called_; 188 | void InitialiseContext(void) { 189 | // Do nothing 190 | } 191 | }; 192 | 193 | MockPage mockPage; 194 | Render(mockPage, deck[0], 0xffff0000, 0xff00ff00); 195 | mockPage.Check(); 196 | } 197 | } // namespace slide 198 | -------------------------------------------------------------------------------- /src/app.cc: -------------------------------------------------------------------------------- 1 | #include "app.h" 2 | #include "debug_print.h" 3 | namespace slide { 4 | App::App(void) { 5 | wview_.title = "Slide"; 6 | wview_.url = html_data_uri; 7 | wview_.width = 480; 8 | wview_.height = 320; 9 | wview_.resizable = 1; 10 | wview_.userdata = this; 11 | 12 | // The callback for when a webview event is invoked is the handleCommand 13 | // method 14 | wview_.external_invoke_cb = [](struct webview *w, const char *data) { 15 | App *app = static_cast(w->userdata); 16 | app->HandleCommand(data); 17 | }; 18 | 19 | // Command Pattern used. 20 | // These must be static to prevent segfault 21 | // If new commands are required, just make it here, then 22 | // add it's command string to the map as done here. 23 | static CreateFileCmd create; 24 | static OpenFileCmd open; 25 | static ExportPdfCmd pdf; 26 | static SetPreviewSizeCmd prev; 27 | static SetTextCmd text; 28 | static SetPaletteCmd pal; 29 | static SetCursorCmd cur; 30 | 31 | cmds_map_["create_file"] = &create; 32 | cmds_map_["open_file"] = &open; 33 | cmds_map_["export_pdf"] = &pdf; 34 | cmds_map_["set_preview_size"] = &prev; 35 | cmds_map_["set_palette"] = &pal; 36 | cmds_map_["set_text"] = &text; 37 | cmds_map_["set_cursor"] = &cur; 38 | } 39 | 40 | void App::Run(void) { 41 | webview_init(&wview_); 42 | webview_dispatch(&wview_, 43 | [](struct webview *w, void *arg) { 44 | App *app = static_cast(w->userdata); 45 | webview_eval(w, CssInject(styles_css).c_str()); 46 | webview_eval(w, picodom_js); 47 | webview_eval(w, app_js); 48 | }, 49 | nullptr); 50 | 51 | while (webview_loop(&wview_, 1) == 0) { /* Intentionally Blank */ 52 | } 53 | webview_exit(&wview_); 54 | } 55 | 56 | void App::HandleCommand(const std::string &data) { 57 | 58 | auto json = nlohmann::json::parse(data); 59 | auto cmd = json.at("cmd").get(); 60 | 61 | // If the command isn't found, but we try to access it the command_map will 62 | // add the key with a null value, so before accessing confirm it's in the map 63 | if (cmds_map_.find(cmd) == cmds_map_.end()) { 64 | debug_print(cmd << " is not a valid command"); 65 | return; 66 | } 67 | 68 | cmds_map_[cmd]->Execute(*this, json); 69 | 70 | Render(); 71 | } 72 | 73 | void App::RenderCurrentSlide(void) { 74 | if (current_slide_ != -1) { 75 | debug_print("App::RenderCurrentSlide()"); 76 | debug_print("app instance details: " << *this); 77 | PNG png(preview_size_.Width(), preview_size_.Height()); 78 | slide::Render(png, deck_[current_slide_], foreground_, background_); 79 | debug_print("App::RenderCurrentSlide() render complete"); 80 | preview_data_uri_ = png.DataUri(); 81 | } 82 | } 83 | 84 | void App::Render(void) { 85 | auto json = nlohmann::json({}); 86 | json["text"] = current_text_; 87 | json["previewDataURI"] = preview_data_uri_; 88 | // std::cerr << "JSON Dump: " << json.dump() << std::endl; 89 | webview_eval( 90 | &wview_, 91 | ("window.app.state=" + json.dump() + "; window.render()").c_str()); 92 | } 93 | 94 | //----------- Below here is the command implementations for each key in the 95 | // command map. 96 | // If adding a new command simply add it here. 97 | 98 | void CreateFileCmd::Execute(App &app, nlohmann::json &json) { 99 | // std::cerr << "Creating File" << std::endl; 100 | 101 | char path[PATH_MAX]; 102 | webview_dialog(&app.webview(), WEBVIEW_DIALOG_TYPE_SAVE, 0, 103 | "New presentation...", nullptr, path, PATH_MAX - 1); 104 | if (std::string(path).length() != 0) { 105 | app.CurrentFile() = std::string(path); 106 | webview_set_title(&app.webview(), ("Slide - " + app.CurrentFile()).c_str()); 107 | } 108 | } 109 | 110 | void OpenFileCmd::Execute(App &app, nlohmann::json &json) { 111 | // std::cerr << "Opening File" << std::endl; 112 | 113 | char path[PATH_MAX]; 114 | webview_dialog(&app.webview(), WEBVIEW_DIALOG_TYPE_OPEN, 0, 115 | "Open presentation...", nullptr, path, sizeof(path) - 1); 116 | if (std::string(path).length() != 0) { 117 | app.CurrentFile() = path; 118 | webview_set_title(&app.webview(), ("Slide - " + app.CurrentFile()).c_str()); 119 | std::ifstream ifs(app.CurrentFile()); 120 | std::string text((std::istreambuf_iterator(ifs)), 121 | (std::istreambuf_iterator())); 122 | app.CurrentText() = text; 123 | app.Deck() = slide::Parse(app.CurrentText()); 124 | } 125 | } 126 | 127 | void ExportPdfCmd::Execute(App &app, nlohmann::json &json) { 128 | // std::cerr << "Exporting to PDF" << std::endl; 129 | char path[PATH_MAX]; 130 | webview_dialog(&app.webview(), WEBVIEW_DIALOG_TYPE_SAVE, 0, "Export PDF...", 131 | nullptr, path, sizeof(path) - 1); 132 | if (strlen(path) != 0) { 133 | std::string path_str(path); 134 | const std::string extension(".pdf"); 135 | if (path_str.length() <= extension.length() || 136 | path_str.compare(path_str.length() - extension.length(), 137 | extension.length(), extension)) { 138 | path_str += extension; 139 | } 140 | // std::cerr << "writing to " << path_str << std::endl; 141 | PDF pdf(path_str, 640, 480); 142 | for (auto slide : app.Deck()) { 143 | pdf.BeginPage(); 144 | slide::Render(pdf, slide, app.Foreground(), app.Background()); 145 | pdf.EndPage(); 146 | } 147 | } 148 | } 149 | 150 | void SetPreviewSizeCmd::Execute(App &app, nlohmann::json &json) { 151 | // std::cerr << "Setting Preview Size" << std::endl; 152 | app.PreviewSize().Width() = json.at("w").get(); 153 | app.PreviewSize().Height() = json.at("h").get(); 154 | app.RenderCurrentSlide(); 155 | } 156 | 157 | void SetPaletteCmd::Execute(App &app, nlohmann::json &json) { 158 | // std::cerr << "Setting palette" << std::endl; 159 | app.Foreground() = json.at("fg").get(); 160 | app.Background() = json.at("bg").get(); 161 | app.RenderCurrentSlide(); 162 | } 163 | 164 | void SetTextCmd::Execute(App &app, nlohmann::json &json) { 165 | // std::cerr << "Setting Text" << std::endl; 166 | app.CurrentText() = json.at("text").get(); 167 | app.Deck() = slide::Parse(app.CurrentText()); 168 | std::ofstream file(app.CurrentFile()); 169 | file << app.CurrentText(); 170 | app.RenderCurrentSlide(); 171 | } 172 | 173 | void SetCursorCmd::Execute(App &app, nlohmann::json &json) { 174 | // std::cerr << "Setting Cursor" << std::endl; 175 | auto cursor = json.at("cursor").get(); 176 | app.CurrentSlide() = -1; 177 | for (int i = 0; app.CurrentSlide() == -1 && i < app.Deck().size(); i++) { 178 | for (auto token : app.Deck()[i]) { 179 | if (token.position() >= cursor) { 180 | app.CurrentSlide() = i; 181 | break; 182 | } 183 | } 184 | } 185 | app.RenderCurrentSlide(); 186 | } 187 | } // namespace slide -------------------------------------------------------------------------------- /src/slide.cc: -------------------------------------------------------------------------------- 1 | #include "slide.h" 2 | #include "styles.h" 3 | #include 4 | #include "debug_print.h" 5 | 6 | std::pair slide::RenderScale(Page &p, Slide &slide, 7 | const Color &color, int xoff, int yoff, 8 | float scale) { 9 | int w = 0; 10 | int h = 0; 11 | int line_height = 0; 12 | int line_width = 0; 13 | int prev_line = -1; 14 | for (auto token : slide) { 15 | if (token.line() != prev_line) { 16 | h = h + line_height; 17 | line_width = 0; 18 | prev_line = token.line(); 19 | } 20 | line_height = p.TextHeight(token.style(), scale); 21 | int token_width = p.TextWidth(token.text(), token.style(), scale); 22 | int x = xoff + line_width; 23 | int y = yoff + h; 24 | if (color != 0) { 25 | p.Text(token.text(), color, x, y, token.style(), scale); 26 | } 27 | line_width = line_width + token_width; 28 | if (line_width > w) { 29 | w = line_width; 30 | } 31 | } 32 | h = h + line_height; 33 | return std::make_pair(xoff + w, yoff + h); 34 | } 35 | 36 | void slide::Render(Page &page, Slide &slide, const Color &fg, const Color &bg) { 37 | debug_print("slide::Render(slide.size() = " << slide.size() << ")"); 38 | if (slide.empty()) { 39 | return; 40 | } 41 | float scale = 1.f; 42 | auto size = RenderScale(page, slide, 0, 0, 0, scale); 43 | scale = (float)std::min(page.Size().Width() * 0.8 / size.first, 44 | page.Size().Height() * 0.8 / size.second); 45 | size = RenderScale(page, slide, 0, 0, 0, scale); 46 | 47 | page.Background(bg); 48 | RenderScale(page, slide, fg, (page.Size().Width() - size.first) / 2, 49 | (page.Size().Height() - size.second) / 2, scale); 50 | 51 | debug_print("slide::Render complete"); 52 | } 53 | 54 | slide::Deck slide::Parse(const std::string &text) { 55 | Deck deck; 56 | auto line_num = 0; 57 | auto begin = text.cbegin(); 58 | 59 | debug_print("------------------------"); 60 | debug_print("Parsing text [" << text.length() << "]:\t" << text); 61 | 62 | for (auto it = begin, end = text.cend(); it < end;) { 63 | // Skip leading newlines 64 | for (; *it == '\n' && it < end; ++it, ++line_num) 65 | ; 66 | 67 | // Parse next slide 68 | Slide slide; 69 | 70 | for (; *it != '\n' && it < end; ++it, ++line_num) { 71 | std::string previous_text; 72 | 73 | Style::Style current_text_style = Style::Normal; 74 | debug_print("Top of loop: " << *it); 75 | 76 | switch (*it) { 77 | case Style::Markers.ImagePath: 78 | ++it; 79 | // TODO: parse image path and geometry 80 | for (; it < end && *it != '\n'; ++it) { 81 | } 82 | break; 83 | 84 | case Style::Markers.Heading: 85 | ++it; 86 | current_text_style = Style::Header; 87 | for (; it < end && *it == ' '; ++it) 88 | ; // advance past whitespace 89 | break; 90 | 91 | case ' ': 92 | ++it; // advance past whitespace 93 | if (it < end && *it == ' ') { 94 | // doublespace is code 95 | ++it; 96 | current_text_style = Style::Monospace; 97 | } else { 98 | previous_text += ' '; 99 | } 100 | break; 101 | case '.': 102 | ++it; 103 | break; 104 | default: 105 | break; 106 | } 107 | 108 | debug_print("After switch: " << *it); 109 | 110 | bool insert_empty_token = true; 111 | 112 | for (; it < end && *it != '\n'; ++it) { 113 | 114 | if (current_text_style == Style::Normal && *it == Style::Markers.Bold) { 115 | ++it; 116 | debug_print("Bold start marker found and it's normal"); 117 | 118 | // first char is * 119 | if (it < end && *it != ' ' && *it != Style::Markers.Bold && 120 | *it != '\n') { 121 | debug_print("Second character isn't whitespace or a bold marker or a " 122 | "newline\t" 123 | << *it); 124 | 125 | // not a "**text" or a "* text" 126 | std::string bold_text; 127 | 128 | for (; it < end && *it != Style::Markers.Bold && *it != '\n'; 129 | ++it) { 130 | // Add the character while it's not a '*' or newline eg 131 | // "*hello*" 132 | bold_text += *it; 133 | } 134 | if (*it == '\n') { 135 | debug_print("Newline found before end of bold capture, so stopping " 136 | "bold capture and addding it as normal text"); 137 | previous_text = Style::Markers.Bold + bold_text; 138 | break; 139 | } 140 | 141 | debug_print("Text to be bold:\t" << bold_text); 142 | debug_print("it:\t" << *it); 143 | 144 | // end of a bold marker found 145 | if (*it == Style::Markers.Bold) { 146 | debug_print("End of bold marker found"); 147 | 148 | if (!previous_text.empty()) { 149 | // There's already something in the inner_text before this level 150 | // of bold so add that before adding the bold stuff 151 | debug_print("Emplacing a non empty string:\t" << previous_text); 152 | slide.emplace_back(std::distance(begin, it), line_num, 153 | current_text_style, previous_text); 154 | } 155 | debug_print("Emplacing a bold text:\t" << bold_text); 156 | slide.emplace_back(std::distance(begin, it), line_num, 157 | Style::Strong, bold_text); 158 | 159 | insert_empty_token = false; 160 | previous_text = ""; 161 | } else { 162 | debug_print("Not end of bold marker found"); 163 | previous_text += Style::Markers.Bold + bold_text; 164 | } 165 | } else { 166 | debug_print("(1) Second character is a bold marker or whitespace:\t" 167 | << *it); 168 | previous_text += Style::Markers.Bold; 169 | if (*it == '\n') { 170 | debug_print("Newline found immediately after a first bold marker so " 171 | "starting a new token in the current slide."); 172 | break; 173 | } 174 | previous_text += *it; 175 | debug_print("(1a): " << previous_text); 176 | } 177 | } 178 | 179 | else { 180 | debug_print( 181 | "No bold marker found, or currently not processing normal text:\t" 182 | << *it); 183 | previous_text += *it; 184 | } 185 | } 186 | if (insert_empty_token || !previous_text.empty()) { 187 | debug_print("Adding empty token:\t" << previous_text); 188 | slide.emplace_back(std::distance(begin, it), line_num, 189 | current_text_style, previous_text); 190 | } 191 | } 192 | 193 | // Skip trailing newlines 194 | for (; *it == '\n' && it < end; ++it, ++line_num) 195 | ; 196 | 197 | if (!slide.empty()) { 198 | debug_print("Adding slide"); 199 | debug_print("------------------------"); 200 | deck.push_back(slide); 201 | } 202 | } 203 | 204 | // Need to add a dummy slide so that if there are none the preview will still 205 | // display a slide with no text 206 | if (deck.empty()) { 207 | debug_print("Adding dummy slide"); 208 | Slide s = {Token(0, 0, Style::Normal, "")}; 209 | deck.push_back(s); 210 | } 211 | return deck; 212 | } -------------------------------------------------------------------------------- /src/ui.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_HPP 2 | #define UI_HPP 3 | 4 | static inline std::string CssInject(const std::string &css) { 5 | std::string esc; 6 | const auto hex = "0123456789ABCDEF"; 7 | for (auto c : css) { 8 | esc = esc + "\\x" + hex[((c >> 4) & 0xf)] + hex[c & 0xf]; 9 | } 10 | std::string js = R"( 11 | (function(css) { 12 | var style = document.createElement('style'); 13 | var head = document.head || document.getElementsByTagName('head')[0]; 14 | style.setAttribute('type', 'text/css'); 15 | if (style.styleSheet) { 16 | style.styleSheet.cssText = css; 17 | } else { 18 | style.appendChild(document.createTextNode(css)); 19 | } 20 | head.appendChild(style); 21 | })(")" + esc + R"("))"; 22 | return js; 23 | } 24 | 25 | static const char *html_data_uri = 26 | "data:text/" 27 | "html,%3C%21DOCTYPE%20html%3E%0A%3Chtml%20lang=%22en%22%3E%0A%3Chead%3E%" 28 | "3Cmeta%20charset=%22utf-8%22%3E%3Cmeta%20http-equiv=%22X-UA-Compatible%22%" 29 | "20content=%22IE=edge%22%3E%3C%2Fhead%3E%0A%3Cbody%3E%3Cdiv%20id=%22app%22%" 30 | "3E%3C%2Fdiv%3E%3Cscript%20type=%22text%2Fjavascript%22%3E%3C%2Fscript%3E%" 31 | "3C%2Fbody%3E%0A%3C%2Fhtml%3E"; 32 | 33 | static const char *styles_css = R"stylescss( 34 | * { 35 | margin: 0; 36 | padding: 0; 37 | box-sizing: border-box; 38 | } 39 | body, html, .container { height: 100%; overflow: hidden; } 40 | 41 | .editor { 42 | margin: 0; 43 | padding: 1em; 44 | font-family: monospace; 45 | width: 100%; 46 | height: 100%; 47 | resize: none; 48 | border: none; 49 | outline: none; 50 | overflow: auto; 51 | background-color: #ffffff; 52 | } 53 | 54 | .icon-bar { 55 | position: fixed; 56 | top: 0; 57 | right: 0; 58 | margin: 1em; 59 | height: 32px; 60 | border-radius: 16px; 61 | background-color: #fff; 62 | } 63 | 64 | .icon { 65 | display: inline-block; 66 | width: 32px; 67 | height: 32px; 68 | cursor: pointer; 69 | border-radius: 50%; 70 | background-repeat: no-repeat; 71 | background-position: center; 72 | } 73 | .icon:not(:first-child) { 74 | margin-left: 0.5em; 75 | } 76 | .icon-create-file { 77 | background-image: url("data:image/svg+xml,%3Csvg fill='%23cccccc' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 78 | } 79 | .icon-open-file { 80 | background-image: url("data:image/svg+xml,%3Csvg fill='%23cccccc' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10z'/%3E%3C/svg%3E"); 81 | } 82 | .icon-export-pdf { 83 | background-image: url("data:image/svg+xml,%3Csvg fill='%23cccccc' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M5 4v2h14V4H5zm0 10h4v6h6v-6h4l-7-7-7 7z'/%3E%3C/svg%3E"); 84 | } 85 | .icon-settings { 86 | background-image: url("data:image/svg+xml,%3Csvg fill='%23cccccc' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 87 | } 88 | .icon-create-file:hover { 89 | background-image: url("data:image/svg+xml,%3Csvg fill='%23444444' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 90 | } 91 | .icon-open-file:hover { 92 | background-image: url("data:image/svg+xml,%3Csvg fill='%23444444' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10z'/%3E%3C/svg%3E"); 93 | } 94 | .icon-export-pdf:hover { 95 | background-image: url("data:image/svg+xml,%3Csvg fill='%23444444' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M5 4v2h14V4H5zm0 10h4v6h6v-6h4l-7-7-7 7z'/%3E%3C/svg%3E"); 96 | } 97 | .icon-settings:hover { 98 | background-image: url("data:image/svg+xml,%3Csvg fill='%23444444' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 99 | } 100 | )stylescss"; 101 | 102 | static const char *picodom_js = R"picodomjs( 103 | !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r(e.picodom={})}(this,function(e){"use strict";function r(e,r){var n,t=[];for(s=arguments.length;s-- >2;)c.push(arguments[s]);for(;c.length;)if(Array.isArray(n=c.pop()))for(s=n.length;s--;)c.push(n[s]);else null!=n&&!0!==n&&!1!==n&&t.push("number"==typeof n?n+="":n);return"string"==typeof e?{type:e,props:r||{},children:t}:e(r||{},t)}function n(e,r,n,t){for(var o=l(n||(n=document.body),n.children[0],e,r);t=a.pop();)t();return o}function t(e,r){var n={};for(var t in e)n[t]=e[t];for(var t in r)n[t]=r[t];return n}function o(e,r){if("string"==typeof e)var n=document.createTextNode(e);else{var n=(r=r||"svg"===e.type)?document.createElementNS("http://www.w3.org/2000/svg",e.type):document.createElement(e.type);e.props&&e.props.oncreate&&a.push(function(){e.props.oncreate(n)});for(var t=0;t 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show claw'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /src/webview.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBVIEW_H 2 | #define WEBVIEW_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #if defined(WEBVIEW_GTK) 9 | #include 10 | #include 11 | #include 12 | 13 | struct webview_priv { 14 | GtkWidget *window; 15 | GtkWidget *scroller; 16 | GtkWidget *webview; 17 | int should_exit; 18 | }; 19 | #elif defined(WEBVIEW_WINAPI) 20 | #define CINTERFACE 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | struct webview_priv { 32 | HWND hwnd; 33 | IOleObject **browser; 34 | }; 35 | #elif defined(WEBVIEW_COCOA) 36 | #import 37 | #import 38 | #import 39 | 40 | struct webview_priv { 41 | NSAutoreleasePool *pool; 42 | NSWindow *window; 43 | WebView *webview; 44 | id windowDelegate; 45 | int should_exit; 46 | }; 47 | #else 48 | #error "Define one of: WEBVIEW_GTK, WEBVIEW_COCOA or WEBVIEW_WINAPI" 49 | #endif 50 | 51 | struct webview; 52 | 53 | typedef void (*webview_external_invoke_cb_t)(struct webview *w, 54 | const char *arg); 55 | 56 | struct webview { 57 | const char *url; 58 | const char *title; 59 | int width; 60 | int height; 61 | int resizable; 62 | webview_external_invoke_cb_t external_invoke_cb; 63 | struct webview_priv priv; 64 | void *userdata; 65 | }; 66 | 67 | enum webview_dialog_type { 68 | WEBVIEW_DIALOG_TYPE_OPEN = 0, 69 | WEBVIEW_DIALOG_TYPE_SAVE = 1, 70 | WEBVIEW_DIALOG_TYPE_ALERT = 2 71 | }; 72 | 73 | typedef void (*webview_dispatch_fn)(struct webview *w, void *arg); 74 | 75 | struct webview_dispatch_arg { 76 | webview_dispatch_fn fn; 77 | struct webview *w; 78 | void *arg; 79 | }; 80 | 81 | static int webview_init(struct webview *w); 82 | static int webview_loop(struct webview *w, int blocking); 83 | static int webview_eval(struct webview *w, const char *js); 84 | static void webview_set_title(struct webview *w, const char *title); 85 | static void webview_dialog(struct webview *w, enum webview_dialog_type dlgtype, 86 | int flags, const char *title, const char *arg, 87 | char *result, size_t resultsz); 88 | static void webview_dispatch(struct webview *w, webview_dispatch_fn fn, 89 | void *arg); 90 | static void webview_terminate(struct webview *w); 91 | static void webview_exit(struct webview *w); 92 | 93 | static int webview(const char *title, const char *url, int w, int h, 94 | int resizable) { 95 | struct webview webview = {0}; 96 | webview.title = title; 97 | webview.url = url; 98 | webview.width = w; 99 | webview.height = h; 100 | webview.resizable = resizable; 101 | int r = webview_init(&webview); 102 | if (r != 0) { 103 | return r; 104 | } 105 | while (webview_loop(&webview, 1) == 0) 106 | ; 107 | webview_exit(&webview); 108 | return 0; 109 | } 110 | 111 | #if defined(WEBVIEW_GTK) 112 | static JSValueRef 113 | webview_external_invoke_cb(JSContextRef context, JSObjectRef fn, 114 | JSObjectRef thisObject, size_t argc, 115 | const JSValueRef args[], JSValueRef *err) { 116 | (void)fn; 117 | (void)thisObject; 118 | (void)argc; 119 | (void)args; 120 | (void)err; 121 | struct webview *w = (struct webview *)JSObjectGetPrivate(thisObject); 122 | if (w->external_invoke_cb != NULL && argc == 1) { 123 | JSStringRef js = JSValueToStringCopy(context, args[0], NULL); 124 | size_t n = JSStringGetMaximumUTF8CStringSize(js); 125 | char *s = g_new(char, n); 126 | JSStringGetUTF8CString(js, s, n); 127 | w->external_invoke_cb(w, s); 128 | JSStringRelease(js); 129 | g_free(s); 130 | } 131 | return JSValueMakeUndefined(context); 132 | } 133 | static const JSStaticFunction webview_external_static_funcs[] = { 134 | {"invoke_", webview_external_invoke_cb, kJSPropertyAttributeReadOnly}, 135 | {NULL, NULL, 0}, 136 | }; 137 | 138 | static const JSClassDefinition webview_external_def = { 139 | 0, kJSClassAttributeNone, 140 | "external", NULL, 141 | NULL, webview_external_static_funcs, 142 | NULL, NULL, 143 | NULL, NULL, 144 | NULL, NULL, 145 | NULL, NULL, 146 | NULL, NULL, 147 | NULL, 148 | }; 149 | 150 | static void webview_desroy_cb(GtkWidget *widget, gpointer arg) { 151 | (void)widget; 152 | struct webview *w = (struct webview *)arg; 153 | webview_terminate(w); 154 | } 155 | 156 | static gboolean webview_context_menu_cb(WebKitWebView *webview, 157 | GtkWidget *default_menu, 158 | WebKitHitTestResult *hit_test_result, 159 | gboolean triggered_with_keyboard, 160 | gpointer userdata) { 161 | (void)webview; 162 | (void)default_menu; 163 | (void)hit_test_result; 164 | (void)triggered_with_keyboard; 165 | (void)userdata; 166 | return TRUE; 167 | } 168 | 169 | static void webview_window_object_cleared_cb(WebKitWebView *webview, 170 | WebKitWebFrame *frame, 171 | gpointer context_ptr, 172 | gpointer window_object, 173 | gpointer arg) { 174 | (void)webview; 175 | (void)frame; 176 | (void)window_object; 177 | JSContextRef context = (JSContextRef)context_ptr; 178 | JSClassRef cls = JSClassCreate(&webview_external_def); 179 | JSObjectRef obj = JSObjectMake(context, cls, arg); 180 | JSObjectRef glob = JSContextGetGlobalObject(context); 181 | JSStringRef s = JSStringCreateWithUTF8CString("external"); 182 | JSObjectSetProperty(context, glob, s, obj, kJSPropertyAttributeNone, NULL); 183 | } 184 | 185 | static int webview_init(struct webview *w) { 186 | if (gtk_init_check(0, NULL) == FALSE) { 187 | return -1; 188 | } 189 | 190 | w->priv.should_exit = 0; 191 | w->priv.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 192 | gtk_window_set_title(GTK_WINDOW(w->priv.window), w->title); 193 | 194 | if (w->resizable) { 195 | gtk_window_set_default_size(GTK_WINDOW(w->priv.window), w->width, 196 | w->height); 197 | } else { 198 | gtk_widget_set_size_request(w->priv.window, w->width, w->height); 199 | } 200 | gtk_window_set_resizable(GTK_WINDOW(w->priv.window), !!w->resizable); 201 | gtk_window_set_position(GTK_WINDOW(w->priv.window), GTK_WIN_POS_CENTER); 202 | 203 | w->priv.scroller = gtk_scrolled_window_new(NULL, NULL); 204 | gtk_container_add(GTK_CONTAINER(w->priv.window), w->priv.scroller); 205 | 206 | w->priv.webview = webkit_web_view_new(); 207 | webkit_web_view_load_uri(WEBKIT_WEB_VIEW(w->priv.webview), w->url); 208 | gtk_container_add(GTK_CONTAINER(w->priv.scroller), w->priv.webview); 209 | 210 | gtk_widget_show_all(w->priv.window); 211 | 212 | g_signal_connect(G_OBJECT(w->priv.window), "destroy", 213 | G_CALLBACK(webview_desroy_cb), w); 214 | g_signal_connect(G_OBJECT(w->priv.webview), "context-menu", 215 | G_CALLBACK(webview_context_menu_cb), w); 216 | g_signal_connect(G_OBJECT(w->priv.webview), "window-object-cleared", 217 | G_CALLBACK(webview_window_object_cleared_cb), w); 218 | return 0; 219 | } 220 | 221 | static int webview_loop(struct webview *w, int blocking) { 222 | gtk_main_iteration_do(blocking); 223 | return w->priv.should_exit; 224 | } 225 | 226 | static void webview_set_title(struct webview *w, const char *title) { 227 | gtk_window_set_title(GTK_WINDOW(w->priv.window), title); 228 | } 229 | 230 | static void webview_dialog(struct webview *w, enum webview_dialog_type dlgtype, 231 | int flags, const char *title, const char *arg, 232 | char *result, size_t resultsz) { 233 | GtkWidget *dlg; 234 | if (result != NULL) { 235 | result[0] = '\0'; 236 | } 237 | if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN || 238 | dlgtype == WEBVIEW_DIALOG_TYPE_SAVE) { 239 | dlg = gtk_file_chooser_dialog_new( 240 | title, GTK_WINDOW(w->priv.window), 241 | (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ? GTK_FILE_CHOOSER_ACTION_OPEN 242 | : GTK_FILE_CHOOSER_ACTION_SAVE), 243 | "_Cancel", GTK_RESPONSE_CANCEL, 244 | (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ? "_Open" : "_Save"), 245 | GTK_RESPONSE_ACCEPT, NULL); 246 | gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dlg), FALSE); 247 | gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dlg), FALSE); 248 | gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dlg), TRUE); 249 | gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dlg), TRUE); 250 | gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dlg), TRUE); 251 | gint response = gtk_dialog_run(GTK_DIALOG(dlg)); 252 | if (response == GTK_RESPONSE_ACCEPT) { 253 | gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg)); 254 | g_strlcpy(result, filename, resultsz); 255 | g_free(filename); 256 | } 257 | gtk_widget_destroy(dlg); 258 | } else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT) { 259 | dlg = 260 | gtk_message_dialog_new(GTK_WINDOW(w->priv.window), GTK_DIALOG_MODAL, 261 | GTK_MESSAGE_OTHER, GTK_BUTTONS_OK, "%s", title); 262 | gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dlg), "%s", 263 | arg); 264 | gtk_dialog_run(GTK_DIALOG(dlg)); 265 | gtk_widget_destroy(dlg); 266 | } 267 | } 268 | 269 | static int webview_eval(struct webview *w, const char *js) { 270 | webkit_web_view_execute_script(WEBKIT_WEB_VIEW(w->priv.webview), js); 271 | return 0; 272 | } 273 | 274 | static gboolean webview_dispatch_wrapper(gpointer userdata) { 275 | struct webview_dispatch_arg *arg = (struct webview_dispatch_arg *)userdata; 276 | (arg->fn)(arg->w, arg->arg); 277 | g_free(arg); 278 | return FALSE; 279 | } 280 | 281 | static void webview_dispatch(struct webview *w, webview_dispatch_fn fn, 282 | void *arg) { 283 | struct webview_dispatch_arg *context = 284 | (struct webview_dispatch_arg *)g_new(struct webview_dispatch_arg *, 1); 285 | context->w = w; 286 | context->arg = arg; 287 | context->fn = fn; 288 | gdk_threads_add_idle(webview_dispatch_wrapper, context); 289 | } 290 | 291 | static void webview_terminate(struct webview *w) { w->priv.should_exit = 1; } 292 | 293 | static void webview_exit(struct webview *w) {} 294 | 295 | #endif /* WEBVIEW_GTK */ 296 | 297 | #if defined(WEBVIEW_WINAPI) 298 | 299 | #pragma comment(lib, "user32.lib") 300 | #pragma comment(lib, "ole32.lib") 301 | #pragma comment(lib, "oleaut32.lib") 302 | 303 | #define WM_WEBVIEW_DISPATCH (WM_APP + 1) 304 | 305 | typedef struct { 306 | IOleInPlaceFrame frame; 307 | HWND window; 308 | } _IOleInPlaceFrameEx; 309 | 310 | typedef struct { 311 | IOleInPlaceSite inplace; 312 | _IOleInPlaceFrameEx frame; 313 | } _IOleInPlaceSiteEx; 314 | 315 | typedef struct { IDocHostUIHandler ui; } _IDocHostUIHandlerEx; 316 | 317 | typedef struct { 318 | IOleClientSite client; 319 | _IOleInPlaceSiteEx inplace; 320 | _IDocHostUIHandlerEx ui; 321 | IDispatch external; 322 | } _IOleClientSiteEx; 323 | 324 | #ifdef __cplusplus 325 | #define iid_ref(x) &(x) 326 | #define iid_unref(x) *(x) 327 | #else 328 | #define iid_ref(x) (x) 329 | #define iid_unref(x) (x) 330 | #endif 331 | 332 | static inline WCHAR *webview_to_utf16(const char *s) { 333 | DWORD size = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0); 334 | WCHAR *ws = (WCHAR *)GlobalAlloc(GMEM_FIXED, sizeof(WCHAR) * size); 335 | if (ws == NULL) { 336 | return NULL; 337 | } 338 | MultiByteToWideChar(CP_UTF8, 0, s, -1, ws, size); 339 | return ws; 340 | } 341 | 342 | static inline char *webview_from_utf16(WCHAR *ws) { 343 | int n = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL); 344 | char *s = (char *)GlobalAlloc(GMEM_FIXED, n); 345 | if (s == NULL) { 346 | return NULL; 347 | } 348 | WideCharToMultiByte(CP_UTF8, 0, ws, -1, s, n, NULL, NULL); 349 | return s; 350 | } 351 | 352 | static int iid_eq(REFIID a, const IID *b) { 353 | return memcmp((const void *)iid_ref(a), (const void *)b, sizeof(GUID)) == 0; 354 | } 355 | 356 | static HRESULT STDMETHODCALLTYPE JS_QueryInterface(IDispatch FAR *This, 357 | REFIID riid, 358 | LPVOID FAR *ppvObj) { 359 | if (iid_eq(riid, &IID_IDispatch)) { 360 | *ppvObj = This; 361 | return S_OK; 362 | } 363 | *ppvObj = 0; 364 | return E_NOINTERFACE; 365 | } 366 | static ULONG STDMETHODCALLTYPE JS_AddRef(IDispatch FAR *This) { return 1; } 367 | static ULONG STDMETHODCALLTYPE JS_Release(IDispatch FAR *This) { return 1; } 368 | static HRESULT STDMETHODCALLTYPE JS_GetTypeInfoCount(IDispatch FAR *This, 369 | UINT *pctinfo) { 370 | return S_OK; 371 | } 372 | static HRESULT STDMETHODCALLTYPE JS_GetTypeInfo(IDispatch FAR *This, 373 | UINT iTInfo, LCID lcid, 374 | ITypeInfo **ppTInfo) { 375 | return S_OK; 376 | } 377 | #define WEBVIEW_JS_INVOKE_ID 0x1000 378 | static HRESULT STDMETHODCALLTYPE JS_GetIDsOfNames(IDispatch FAR *This, 379 | REFIID riid, 380 | LPOLESTR *rgszNames, 381 | UINT cNames, LCID lcid, 382 | DISPID *rgDispId) { 383 | if (cNames != 1) { 384 | return S_FALSE; 385 | } 386 | if (wcscmp(rgszNames[0], L"invoke_") == 0) { 387 | rgDispId[0] = WEBVIEW_JS_INVOKE_ID; 388 | return S_OK; 389 | } 390 | return S_FALSE; 391 | } 392 | 393 | static HRESULT STDMETHODCALLTYPE 394 | JS_Invoke(IDispatch FAR *This, DISPID dispIdMember, REFIID riid, LCID lcid, 395 | WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, 396 | EXCEPINFO *pExcepInfo, UINT *puArgErr) { 397 | size_t offset = (size_t) & ((_IOleClientSiteEx *)NULL)->external; 398 | _IOleClientSiteEx *ex = (_IOleClientSiteEx *)((char *)(This)-offset); 399 | struct webview *w = (struct webview *)GetWindowLongPtr( 400 | ex->inplace.frame.window, GWLP_USERDATA); 401 | if (pDispParams->cArgs == 1 && pDispParams->rgvarg[0].vt == VT_BSTR) { 402 | BSTR bstr = pDispParams->rgvarg[0].bstrVal; 403 | char *s = webview_from_utf16(bstr); 404 | if (s != NULL) { 405 | if (dispIdMember == WEBVIEW_JS_INVOKE_ID) { 406 | if (w->external_invoke_cb != NULL) { 407 | w->external_invoke_cb(w, s); 408 | } 409 | } else { 410 | return S_FALSE; 411 | } 412 | GlobalFree(s); 413 | } 414 | } 415 | return S_OK; 416 | } 417 | 418 | static IDispatchVtbl ExternalDispatchTable = { 419 | JS_QueryInterface, JS_AddRef, JS_Release, JS_GetTypeInfoCount, 420 | JS_GetTypeInfo, JS_GetIDsOfNames, JS_Invoke}; 421 | 422 | static ULONG STDMETHODCALLTYPE Site_AddRef(IOleClientSite FAR *This) { 423 | return 1; 424 | } 425 | static ULONG STDMETHODCALLTYPE Site_Release(IOleClientSite FAR *This) { 426 | return 1; 427 | } 428 | static HRESULT STDMETHODCALLTYPE Site_SaveObject(IOleClientSite FAR *This) { 429 | return E_NOTIMPL; 430 | } 431 | static HRESULT STDMETHODCALLTYPE Site_GetMoniker(IOleClientSite FAR *This, 432 | DWORD dwAssign, 433 | DWORD dwWhichMoniker, 434 | IMoniker **ppmk) { 435 | return E_NOTIMPL; 436 | } 437 | static HRESULT STDMETHODCALLTYPE 438 | Site_GetContainer(IOleClientSite FAR *This, LPOLECONTAINER FAR *ppContainer) { 439 | *ppContainer = 0; 440 | return E_NOINTERFACE; 441 | } 442 | static HRESULT STDMETHODCALLTYPE Site_ShowObject(IOleClientSite FAR *This) { 443 | return NOERROR; 444 | } 445 | static HRESULT STDMETHODCALLTYPE Site_OnShowWindow(IOleClientSite FAR *This, 446 | BOOL fShow) { 447 | return E_NOTIMPL; 448 | } 449 | static HRESULT STDMETHODCALLTYPE 450 | Site_RequestNewObjectLayout(IOleClientSite FAR *This) { 451 | return E_NOTIMPL; 452 | } 453 | static HRESULT STDMETHODCALLTYPE Site_QueryInterface(IOleClientSite FAR *This, 454 | REFIID riid, 455 | void **ppvObject) { 456 | if (iid_eq(riid, &IID_IUnknown) || iid_eq(riid, &IID_IOleClientSite)) 457 | *ppvObject = &((_IOleClientSiteEx *)This)->client; 458 | else if (iid_eq(riid, &IID_IOleInPlaceSite)) 459 | *ppvObject = &((_IOleClientSiteEx *)This)->inplace; 460 | else if (iid_eq(riid, &IID_IDocHostUIHandler)) 461 | *ppvObject = &((_IOleClientSiteEx *)This)->ui; 462 | else { 463 | *ppvObject = 0; 464 | return (E_NOINTERFACE); 465 | } 466 | return S_OK; 467 | } 468 | static HRESULT STDMETHODCALLTYPE InPlace_QueryInterface( 469 | IOleInPlaceSite FAR *This, REFIID riid, LPVOID FAR *ppvObj) { 470 | return (Site_QueryInterface( 471 | (IOleClientSite *)((char *)This - sizeof(IOleClientSite)), riid, ppvObj)); 472 | } 473 | static ULONG STDMETHODCALLTYPE InPlace_AddRef(IOleInPlaceSite FAR *This) { 474 | return 1; 475 | } 476 | static ULONG STDMETHODCALLTYPE InPlace_Release(IOleInPlaceSite FAR *This) { 477 | return 1; 478 | } 479 | static HRESULT STDMETHODCALLTYPE InPlace_GetWindow(IOleInPlaceSite FAR *This, 480 | HWND FAR *lphwnd) { 481 | *lphwnd = ((_IOleInPlaceSiteEx FAR *)This)->frame.window; 482 | return S_OK; 483 | } 484 | static HRESULT STDMETHODCALLTYPE 485 | InPlace_ContextSensitiveHelp(IOleInPlaceSite FAR *This, BOOL fEnterMode) { 486 | return E_NOTIMPL; 487 | } 488 | static HRESULT STDMETHODCALLTYPE 489 | InPlace_CanInPlaceActivate(IOleInPlaceSite FAR *This) { 490 | return S_OK; 491 | } 492 | static HRESULT STDMETHODCALLTYPE 493 | InPlace_OnInPlaceActivate(IOleInPlaceSite FAR *This) { 494 | return S_OK; 495 | } 496 | static HRESULT STDMETHODCALLTYPE 497 | InPlace_OnUIActivate(IOleInPlaceSite FAR *This) { 498 | return S_OK; 499 | } 500 | static HRESULT STDMETHODCALLTYPE InPlace_GetWindowContext( 501 | IOleInPlaceSite FAR *This, LPOLEINPLACEFRAME FAR *lplpFrame, 502 | LPOLEINPLACEUIWINDOW FAR *lplpDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, 503 | LPOLEINPLACEFRAMEINFO lpFrameInfo) { 504 | *lplpFrame = (LPOLEINPLACEFRAME) & ((_IOleInPlaceSiteEx *)This)->frame; 505 | *lplpDoc = 0; 506 | lpFrameInfo->fMDIApp = FALSE; 507 | lpFrameInfo->hwndFrame = ((_IOleInPlaceFrameEx *)*lplpFrame)->window; 508 | lpFrameInfo->haccel = 0; 509 | lpFrameInfo->cAccelEntries = 0; 510 | return S_OK; 511 | } 512 | static HRESULT STDMETHODCALLTYPE InPlace_Scroll(IOleInPlaceSite FAR *This, 513 | SIZE scrollExtent) { 514 | return E_NOTIMPL; 515 | } 516 | static HRESULT STDMETHODCALLTYPE 517 | InPlace_OnUIDeactivate(IOleInPlaceSite FAR *This, BOOL fUndoable) { 518 | return S_OK; 519 | } 520 | static HRESULT STDMETHODCALLTYPE 521 | InPlace_OnInPlaceDeactivate(IOleInPlaceSite FAR *This) { 522 | return S_OK; 523 | } 524 | static HRESULT STDMETHODCALLTYPE 525 | InPlace_DiscardUndoState(IOleInPlaceSite FAR *This) { 526 | return E_NOTIMPL; 527 | } 528 | static HRESULT STDMETHODCALLTYPE 529 | InPlace_DeactivateAndUndo(IOleInPlaceSite FAR *This) { 530 | return E_NOTIMPL; 531 | } 532 | static HRESULT STDMETHODCALLTYPE 533 | InPlace_OnPosRectChange(IOleInPlaceSite FAR *This, LPCRECT lprcPosRect) { 534 | IOleObject *browserObject; 535 | IOleInPlaceObject *inplace; 536 | browserObject = *((IOleObject **)((char *)This - sizeof(IOleObject *) - 537 | sizeof(IOleClientSite))); 538 | if (!browserObject->lpVtbl->QueryInterface(browserObject, 539 | iid_unref(&IID_IOleInPlaceObject), 540 | (void **)&inplace)) { 541 | inplace->lpVtbl->SetObjectRects(inplace, lprcPosRect, lprcPosRect); 542 | inplace->lpVtbl->Release(inplace); 543 | } 544 | return S_OK; 545 | } 546 | static HRESULT STDMETHODCALLTYPE Frame_QueryInterface( 547 | IOleInPlaceFrame FAR *This, REFIID riid, LPVOID FAR *ppvObj) { 548 | return E_NOTIMPL; 549 | } 550 | static ULONG STDMETHODCALLTYPE Frame_AddRef(IOleInPlaceFrame FAR *This) { 551 | return 1; 552 | } 553 | static ULONG STDMETHODCALLTYPE Frame_Release(IOleInPlaceFrame FAR *This) { 554 | return 1; 555 | } 556 | static HRESULT STDMETHODCALLTYPE Frame_GetWindow(IOleInPlaceFrame FAR *This, 557 | HWND FAR *lphwnd) { 558 | *lphwnd = ((_IOleInPlaceFrameEx *)This)->window; 559 | return S_OK; 560 | } 561 | static HRESULT STDMETHODCALLTYPE 562 | Frame_ContextSensitiveHelp(IOleInPlaceFrame FAR *This, BOOL fEnterMode) { 563 | return E_NOTIMPL; 564 | } 565 | static HRESULT STDMETHODCALLTYPE Frame_GetBorder(IOleInPlaceFrame FAR *This, 566 | LPRECT lprectBorder) { 567 | return E_NOTIMPL; 568 | } 569 | static HRESULT STDMETHODCALLTYPE Frame_RequestBorderSpace( 570 | IOleInPlaceFrame FAR *This, LPCBORDERWIDTHS pborderwidths) { 571 | return E_NOTIMPL; 572 | } 573 | static HRESULT STDMETHODCALLTYPE Frame_SetBorderSpace( 574 | IOleInPlaceFrame FAR *This, LPCBORDERWIDTHS pborderwidths) { 575 | return E_NOTIMPL; 576 | } 577 | static HRESULT STDMETHODCALLTYPE Frame_SetActiveObject( 578 | IOleInPlaceFrame FAR *This, IOleInPlaceActiveObject *pActiveObject, 579 | LPCOLESTR pszObjName) { 580 | return S_OK; 581 | } 582 | static HRESULT STDMETHODCALLTYPE 583 | Frame_InsertMenus(IOleInPlaceFrame FAR *This, HMENU hmenuShared, 584 | LPOLEMENUGROUPWIDTHS lpMenuWidths) { 585 | return E_NOTIMPL; 586 | } 587 | static HRESULT STDMETHODCALLTYPE Frame_SetMenu(IOleInPlaceFrame FAR *This, 588 | HMENU hmenuShared, 589 | HOLEMENU holemenu, 590 | HWND hwndActiveObject) { 591 | return S_OK; 592 | } 593 | static HRESULT STDMETHODCALLTYPE Frame_RemoveMenus(IOleInPlaceFrame FAR *This, 594 | HMENU hmenuShared) { 595 | return E_NOTIMPL; 596 | } 597 | static HRESULT STDMETHODCALLTYPE Frame_SetStatusText(IOleInPlaceFrame FAR *This, 598 | LPCOLESTR pszStatusText) { 599 | return S_OK; 600 | } 601 | static HRESULT STDMETHODCALLTYPE 602 | Frame_EnableModeless(IOleInPlaceFrame FAR *This, BOOL fEnable) { 603 | return S_OK; 604 | } 605 | static HRESULT STDMETHODCALLTYPE 606 | Frame_TranslateAccelerator(IOleInPlaceFrame FAR *This, LPMSG lpmsg, WORD wID) { 607 | return E_NOTIMPL; 608 | } 609 | static HRESULT STDMETHODCALLTYPE UI_QueryInterface(IDocHostUIHandler FAR *This, 610 | REFIID riid, 611 | LPVOID FAR *ppvObj) { 612 | return (Site_QueryInterface((IOleClientSite *)((char *)This - 613 | sizeof(IOleClientSite) - 614 | sizeof(_IOleInPlaceSiteEx)), 615 | riid, ppvObj)); 616 | } 617 | static ULONG STDMETHODCALLTYPE UI_AddRef(IDocHostUIHandler FAR *This) { 618 | return 1; 619 | } 620 | static ULONG STDMETHODCALLTYPE UI_Release(IDocHostUIHandler FAR *This) { 621 | return 1; 622 | } 623 | static HRESULT STDMETHODCALLTYPE UI_ShowContextMenu( 624 | IDocHostUIHandler FAR *This, DWORD dwID, POINT __RPC_FAR *ppt, 625 | IUnknown __RPC_FAR *pcmdtReserved, IDispatch __RPC_FAR *pdispReserved) { 626 | return S_OK; 627 | } 628 | static HRESULT STDMETHODCALLTYPE 629 | UI_GetHostInfo(IDocHostUIHandler FAR *This, DOCHOSTUIINFO __RPC_FAR *pInfo) { 630 | pInfo->cbSize = sizeof(DOCHOSTUIINFO); 631 | pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER; 632 | pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT; 633 | return S_OK; 634 | } 635 | static HRESULT STDMETHODCALLTYPE UI_ShowUI( 636 | IDocHostUIHandler FAR *This, DWORD dwID, 637 | IOleInPlaceActiveObject __RPC_FAR *pActiveObject, 638 | IOleCommandTarget __RPC_FAR *pCommandTarget, 639 | IOleInPlaceFrame __RPC_FAR *pFrame, IOleInPlaceUIWindow __RPC_FAR *pDoc) { 640 | return S_OK; 641 | } 642 | static HRESULT STDMETHODCALLTYPE UI_HideUI(IDocHostUIHandler FAR *This) { 643 | return S_OK; 644 | } 645 | static HRESULT STDMETHODCALLTYPE UI_UpdateUI(IDocHostUIHandler FAR *This) { 646 | return S_OK; 647 | } 648 | static HRESULT STDMETHODCALLTYPE UI_EnableModeless(IDocHostUIHandler FAR *This, 649 | BOOL fEnable) { 650 | return S_OK; 651 | } 652 | static HRESULT STDMETHODCALLTYPE 653 | UI_OnDocWindowActivate(IDocHostUIHandler FAR *This, BOOL fActivate) { 654 | return S_OK; 655 | } 656 | static HRESULT STDMETHODCALLTYPE 657 | UI_OnFrameWindowActivate(IDocHostUIHandler FAR *This, BOOL fActivate) { 658 | return S_OK; 659 | } 660 | static HRESULT STDMETHODCALLTYPE 661 | UI_ResizeBorder(IDocHostUIHandler FAR *This, LPCRECT prcBorder, 662 | IOleInPlaceUIWindow __RPC_FAR *pUIWindow, BOOL fRameWindow) { 663 | return S_OK; 664 | } 665 | static HRESULT STDMETHODCALLTYPE 666 | UI_TranslateAccelerator(IDocHostUIHandler FAR *This, LPMSG lpMsg, 667 | const GUID __RPC_FAR *pguidCmdGroup, DWORD nCmdID) { 668 | return S_FALSE; 669 | } 670 | static HRESULT STDMETHODCALLTYPE UI_GetOptionKeyPath( 671 | IDocHostUIHandler FAR *This, LPOLESTR __RPC_FAR *pchKey, DWORD dw) { 672 | return S_FALSE; 673 | } 674 | static HRESULT STDMETHODCALLTYPE UI_GetDropTarget( 675 | IDocHostUIHandler FAR *This, IDropTarget __RPC_FAR *pDropTarget, 676 | IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget) { 677 | return S_FALSE; 678 | } 679 | static HRESULT STDMETHODCALLTYPE UI_GetExternal( 680 | IDocHostUIHandler FAR *This, IDispatch __RPC_FAR *__RPC_FAR *ppDispatch) { 681 | *ppDispatch = (IDispatch *)(This + 1); 682 | return S_OK; 683 | } 684 | static HRESULT STDMETHODCALLTYPE UI_TranslateUrl( 685 | IDocHostUIHandler FAR *This, DWORD dwTranslate, OLECHAR __RPC_FAR *pchURLIn, 686 | OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut) { 687 | *ppchURLOut = 0; 688 | return S_FALSE; 689 | } 690 | static HRESULT STDMETHODCALLTYPE 691 | UI_FilterDataObject(IDocHostUIHandler FAR *This, IDataObject __RPC_FAR *pDO, 692 | IDataObject __RPC_FAR *__RPC_FAR *ppDORet) { 693 | *ppDORet = 0; 694 | return S_FALSE; 695 | } 696 | 697 | static const TCHAR *classname = "WebView"; 698 | static const SAFEARRAYBOUND ArrayBound = {1, 0}; 699 | 700 | static IOleClientSiteVtbl MyIOleClientSiteTable = { 701 | Site_QueryInterface, Site_AddRef, Site_Release, 702 | Site_SaveObject, Site_GetMoniker, Site_GetContainer, 703 | Site_ShowObject, Site_OnShowWindow, Site_RequestNewObjectLayout}; 704 | static IOleInPlaceSiteVtbl MyIOleInPlaceSiteTable = { 705 | InPlace_QueryInterface, 706 | InPlace_AddRef, 707 | InPlace_Release, 708 | InPlace_GetWindow, 709 | InPlace_ContextSensitiveHelp, 710 | InPlace_CanInPlaceActivate, 711 | InPlace_OnInPlaceActivate, 712 | InPlace_OnUIActivate, 713 | InPlace_GetWindowContext, 714 | InPlace_Scroll, 715 | InPlace_OnUIDeactivate, 716 | InPlace_OnInPlaceDeactivate, 717 | InPlace_DiscardUndoState, 718 | InPlace_DeactivateAndUndo, 719 | InPlace_OnPosRectChange}; 720 | 721 | static IOleInPlaceFrameVtbl MyIOleInPlaceFrameTable = { 722 | Frame_QueryInterface, 723 | Frame_AddRef, 724 | Frame_Release, 725 | Frame_GetWindow, 726 | Frame_ContextSensitiveHelp, 727 | Frame_GetBorder, 728 | Frame_RequestBorderSpace, 729 | Frame_SetBorderSpace, 730 | Frame_SetActiveObject, 731 | Frame_InsertMenus, 732 | Frame_SetMenu, 733 | Frame_RemoveMenus, 734 | Frame_SetStatusText, 735 | Frame_EnableModeless, 736 | Frame_TranslateAccelerator}; 737 | 738 | static IDocHostUIHandlerVtbl MyIDocHostUIHandlerTable = { 739 | UI_QueryInterface, 740 | UI_AddRef, 741 | UI_Release, 742 | UI_ShowContextMenu, 743 | UI_GetHostInfo, 744 | UI_ShowUI, 745 | UI_HideUI, 746 | UI_UpdateUI, 747 | UI_EnableModeless, 748 | UI_OnDocWindowActivate, 749 | UI_OnFrameWindowActivate, 750 | UI_ResizeBorder, 751 | UI_TranslateAccelerator, 752 | UI_GetOptionKeyPath, 753 | UI_GetDropTarget, 754 | UI_GetExternal, 755 | UI_TranslateUrl, 756 | UI_FilterDataObject}; 757 | 758 | static void UnEmbedBrowserObject(struct webview *w) { 759 | if (w->priv.browser != NULL) { 760 | (*w->priv.browser)->lpVtbl->Close(*w->priv.browser, OLECLOSE_NOSAVE); 761 | (*w->priv.browser)->lpVtbl->Release(*w->priv.browser); 762 | GlobalFree(w->priv.browser); 763 | w->priv.browser = NULL; 764 | } 765 | } 766 | 767 | static int EmbedBrowserObject(struct webview *w) { 768 | RECT rect; 769 | IWebBrowser2 *webBrowser2 = NULL; 770 | LPCLASSFACTORY pClassFactory = NULL; 771 | _IOleClientSiteEx *_iOleClientSiteEx = NULL; 772 | IOleObject **browser = (IOleObject **)GlobalAlloc( 773 | GMEM_FIXED, sizeof(IOleObject *) + sizeof(_IOleClientSiteEx)); 774 | if (browser == NULL) { 775 | goto error; 776 | } 777 | w->priv.browser = browser; 778 | 779 | _iOleClientSiteEx = (_IOleClientSiteEx *)(browser + 1); 780 | _iOleClientSiteEx->client.lpVtbl = &MyIOleClientSiteTable; 781 | _iOleClientSiteEx->inplace.inplace.lpVtbl = &MyIOleInPlaceSiteTable; 782 | _iOleClientSiteEx->inplace.frame.frame.lpVtbl = &MyIOleInPlaceFrameTable; 783 | _iOleClientSiteEx->inplace.frame.window = w->priv.hwnd; 784 | _iOleClientSiteEx->ui.ui.lpVtbl = &MyIDocHostUIHandlerTable; 785 | _iOleClientSiteEx->external.lpVtbl = &ExternalDispatchTable; 786 | 787 | if (CoGetClassObject(iid_unref(&CLSID_WebBrowser), 788 | CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, NULL, 789 | iid_unref(&IID_IClassFactory), 790 | (void **)&pClassFactory) != S_OK) { 791 | goto error; 792 | } 793 | 794 | if (pClassFactory == NULL) { 795 | goto error; 796 | } 797 | 798 | if (pClassFactory->lpVtbl->CreateInstance(pClassFactory, 0, 799 | iid_unref(&IID_IOleObject), 800 | (void **)browser) != S_OK) { 801 | goto error; 802 | } 803 | pClassFactory->lpVtbl->Release(pClassFactory); 804 | if ((*browser)->lpVtbl->SetClientSite( 805 | *browser, (IOleClientSite *)_iOleClientSiteEx) != S_OK) { 806 | goto error; 807 | } 808 | (*browser)->lpVtbl->SetHostNames(*browser, L"My Host Name", 0); 809 | 810 | if (OleSetContainedObject((struct IUnknown *)(*browser), TRUE) != S_OK) { 811 | goto error; 812 | } 813 | GetClientRect(w->priv.hwnd, &rect); 814 | if ((*browser)->lpVtbl->DoVerb((*browser), OLEIVERB_SHOW, NULL, 815 | (IOleClientSite *)_iOleClientSiteEx, -1, 816 | w->priv.hwnd, &rect) != S_OK) { 817 | goto error; 818 | } 819 | if ((*browser)->lpVtbl->QueryInterface((*browser), 820 | iid_unref(&IID_IWebBrowser2), 821 | (void **)&webBrowser2) != S_OK) { 822 | goto error; 823 | } 824 | 825 | webBrowser2->lpVtbl->put_Left(webBrowser2, 0); 826 | webBrowser2->lpVtbl->put_Top(webBrowser2, 0); 827 | webBrowser2->lpVtbl->put_Width(webBrowser2, rect.right); 828 | webBrowser2->lpVtbl->put_Height(webBrowser2, rect.bottom); 829 | webBrowser2->lpVtbl->Release(webBrowser2); 830 | 831 | return 0; 832 | error: 833 | UnEmbedBrowserObject(w); 834 | if (pClassFactory != NULL) { 835 | pClassFactory->lpVtbl->Release(pClassFactory); 836 | } 837 | if (browser != NULL) { 838 | GlobalFree(browser); 839 | } 840 | return -1; 841 | } 842 | 843 | #define WEBVIEW_DATA_URL_PREFIX "data:text/html," 844 | static long DisplayHTMLPage(struct webview *w) { 845 | IWebBrowser2 *webBrowser2; 846 | VARIANT myURL; 847 | LPDISPATCH lpDispatch; 848 | IHTMLDocument2 *htmlDoc2; 849 | BSTR bstr; 850 | IOleObject *browserObject; 851 | SAFEARRAY *sfArray; 852 | VARIANT *pVar; 853 | browserObject = *w->priv.browser; 854 | int isDataURL = 0; 855 | if (!browserObject->lpVtbl->QueryInterface( 856 | browserObject, iid_unref(&IID_IWebBrowser2), (void **)&webBrowser2)) { 857 | LPCSTR webPageName; 858 | isDataURL = (strncmp(w->url, WEBVIEW_DATA_URL_PREFIX, 859 | strlen(WEBVIEW_DATA_URL_PREFIX)) == 0); 860 | if (isDataURL) { 861 | webPageName = "about:blank"; 862 | } else { 863 | webPageName = (LPCSTR)w->url; 864 | } 865 | VariantInit(&myURL); 866 | myURL.vt = VT_BSTR; 867 | #ifndef UNICODE 868 | { 869 | wchar_t *buffer = webview_to_utf16(webPageName); 870 | if (buffer == NULL) { 871 | goto badalloc; 872 | } 873 | myURL.bstrVal = SysAllocString(buffer); 874 | GlobalFree(buffer); 875 | } 876 | #else 877 | myURL.bstrVal = SysAllocString(webPageName); 878 | #endif 879 | if (!myURL.bstrVal) { 880 | badalloc: 881 | webBrowser2->lpVtbl->Release(webBrowser2); 882 | return (-6); 883 | } 884 | webBrowser2->lpVtbl->Navigate2(webBrowser2, &myURL, 0, 0, 0, 0); 885 | VariantClear(&myURL); 886 | if (!isDataURL) { 887 | return 0; 888 | } 889 | 890 | char *url = (char *)calloc(1, strlen(w->url) + 1); 891 | char *q = url; 892 | for (const char *p = w->url + strlen(WEBVIEW_DATA_URL_PREFIX); *q = *p; 893 | p++, q++) { 894 | if (*q == '%' && *(p + 1) && *(p + 2)) { 895 | sscanf(p + 1, "%02x", q); 896 | p = p + 2; 897 | } 898 | } 899 | 900 | if (webBrowser2->lpVtbl->get_Document(webBrowser2, &lpDispatch) == S_OK) { 901 | if (lpDispatch->lpVtbl->QueryInterface(lpDispatch, 902 | iid_unref(&IID_IHTMLDocument2), 903 | (void **)&htmlDoc2) == S_OK) { 904 | if ((sfArray = SafeArrayCreate(VT_VARIANT, 1, 905 | (SAFEARRAYBOUND *)&ArrayBound))) { 906 | if (!SafeArrayAccessData(sfArray, (void **)&pVar)) { 907 | pVar->vt = VT_BSTR; 908 | #ifndef UNICODE 909 | { 910 | wchar_t *buffer = webview_to_utf16(url); 911 | if (buffer == NULL) { 912 | goto release; 913 | } 914 | bstr = SysAllocString(buffer); 915 | GlobalFree(buffer); 916 | } 917 | #else 918 | bstr = SysAllocString(string); 919 | #endif 920 | if ((pVar->bstrVal = bstr)) { 921 | htmlDoc2->lpVtbl->write(htmlDoc2, sfArray); 922 | htmlDoc2->lpVtbl->close(htmlDoc2); 923 | } 924 | } 925 | SafeArrayDestroy(sfArray); 926 | } 927 | release: 928 | free(url); 929 | htmlDoc2->lpVtbl->Release(htmlDoc2); 930 | } 931 | lpDispatch->lpVtbl->Release(lpDispatch); 932 | } 933 | webBrowser2->lpVtbl->Release(webBrowser2); 934 | return (0); 935 | } 936 | return (-5); 937 | } 938 | 939 | static LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, 940 | LPARAM lParam) { 941 | struct webview *w = (struct webview *)GetWindowLongPtr(hwnd, GWLP_USERDATA); 942 | switch (uMsg) { 943 | case WM_CREATE: 944 | w = (struct webview *)((CREATESTRUCT *)lParam)->lpCreateParams; 945 | w->priv.hwnd = hwnd; 946 | return EmbedBrowserObject(w); 947 | case WM_DESTROY: 948 | UnEmbedBrowserObject(w); 949 | PostQuitMessage(0); 950 | return TRUE; 951 | case WM_SIZE: { 952 | IWebBrowser2 *webBrowser2; 953 | IOleObject *browser = *w->priv.browser; 954 | if (browser->lpVtbl->QueryInterface(browser, iid_unref(&IID_IWebBrowser2), 955 | (void **)&webBrowser2) == S_OK) { 956 | RECT rect; 957 | GetClientRect(hwnd, &rect); 958 | webBrowser2->lpVtbl->put_Width(webBrowser2, rect.right); 959 | webBrowser2->lpVtbl->put_Height(webBrowser2, rect.bottom); 960 | } 961 | return TRUE; 962 | } 963 | case WM_WEBVIEW_DISPATCH: { 964 | webview_dispatch_fn f = (webview_dispatch_fn)wParam; 965 | void *arg = (void *)lParam; 966 | (*f)(w, arg); 967 | return TRUE; 968 | } 969 | } 970 | return DefWindowProc(hwnd, uMsg, wParam, lParam); 971 | } 972 | 973 | #define WEBVIEW_KEY_FEATURE_BROWSER_EMULATION \ 974 | "Software\\Microsoft\\Internet " \ 975 | "Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION" 976 | 977 | static int webview_fix_ie_compat_mode() { 978 | HKEY hKey; 979 | DWORD ie_version = 11000; 980 | TCHAR appname[MAX_PATH + 1]; 981 | TCHAR *p; 982 | if (GetModuleFileName(NULL, appname, MAX_PATH + 1) == 0) { 983 | return -1; 984 | } 985 | for (p = &appname[strlen(appname) - 1]; p != appname && *p != '\\'; p--) 986 | ; 987 | p++; 988 | if (RegCreateKey(HKEY_CURRENT_USER, WEBVIEW_KEY_FEATURE_BROWSER_EMULATION, 989 | &hKey) != ERROR_SUCCESS) { 990 | return -1; 991 | } 992 | if (RegSetValueEx(hKey, p, 0, REG_DWORD, (BYTE *)&ie_version, 993 | sizeof(ie_version)) != ERROR_SUCCESS) { 994 | RegCloseKey(hKey); 995 | return -1; 996 | } 997 | RegCloseKey(hKey); 998 | return 0; 999 | } 1000 | 1001 | static int webview_init(struct webview *w) { 1002 | WNDCLASSEX wc; 1003 | HINSTANCE hInstance; 1004 | STARTUPINFO info; 1005 | DWORD style; 1006 | RECT clientRect; 1007 | RECT rect; 1008 | 1009 | if (webview_fix_ie_compat_mode() < 0) { 1010 | return -1; 1011 | } 1012 | 1013 | hInstance = GetModuleHandle(NULL); 1014 | if (hInstance == NULL) { 1015 | return -1; 1016 | } 1017 | GetStartupInfo(&info); 1018 | if (OleInitialize(NULL) != S_OK) { 1019 | return -1; 1020 | } 1021 | ZeroMemory(&wc, sizeof(WNDCLASSEX)); 1022 | wc.cbSize = sizeof(WNDCLASSEX); 1023 | wc.hInstance = hInstance; 1024 | wc.lpfnWndProc = wndproc; 1025 | wc.lpszClassName = classname; 1026 | RegisterClassEx(&wc); 1027 | 1028 | style = WS_OVERLAPPEDWINDOW; 1029 | if (!w->resizable) { 1030 | style = WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU; 1031 | } 1032 | 1033 | rect.left = 0; 1034 | rect.top = 0; 1035 | rect.right = w->width; 1036 | rect.bottom = w->height; 1037 | AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0); 1038 | 1039 | GetClientRect(GetDesktopWindow(), &clientRect); 1040 | int left = (clientRect.right / 2) - ((rect.right - rect.left) / 2); 1041 | int top = (clientRect.bottom / 2) - ((rect.bottom - rect.top) / 2); 1042 | rect.right = rect.right - rect.left + left; 1043 | rect.left = left; 1044 | rect.bottom = rect.bottom - rect.top + top; 1045 | rect.top = top; 1046 | 1047 | w->priv.hwnd = 1048 | CreateWindowEx(0, classname, w->title, style, rect.left, rect.top, 1049 | rect.right - rect.left, rect.bottom - rect.top, 1050 | HWND_DESKTOP, NULL, hInstance, (void *)w); 1051 | if (w->priv.hwnd == 0) { 1052 | OleUninitialize(); 1053 | return -1; 1054 | } 1055 | 1056 | SetWindowLongPtr(w->priv.hwnd, GWLP_USERDATA, (LONG_PTR)w); 1057 | 1058 | DisplayHTMLPage(w); 1059 | 1060 | SetWindowText(w->priv.hwnd, w->title); 1061 | ShowWindow(w->priv.hwnd, info.wShowWindow); 1062 | UpdateWindow(w->priv.hwnd); 1063 | SetFocus(w->priv.hwnd); 1064 | 1065 | return 0; 1066 | } 1067 | 1068 | static int webview_loop(struct webview *w, int blocking) { 1069 | MSG msg; 1070 | if (blocking) { 1071 | GetMessage(&msg, 0, 0, 0); 1072 | } else { 1073 | PeekMessage(&msg, 0, 0, 0, PM_REMOVE); 1074 | } 1075 | switch (msg.message) { 1076 | case WM_QUIT: 1077 | return -1; 1078 | case WM_COMMAND: 1079 | case WM_KEYDOWN: 1080 | case WM_KEYUP: { 1081 | HRESULT r = S_OK; 1082 | IWebBrowser2 *webBrowser2; 1083 | IOleObject *browser = *w->priv.browser; 1084 | if (browser->lpVtbl->QueryInterface(browser, iid_unref(&IID_IWebBrowser2), 1085 | (void **)&webBrowser2) == S_OK) { 1086 | IOleInPlaceActiveObject *pIOIPAO; 1087 | if (browser->lpVtbl->QueryInterface( 1088 | browser, iid_unref(&IID_IOleInPlaceActiveObject), 1089 | (void **)&pIOIPAO) == S_OK) { 1090 | r = pIOIPAO->lpVtbl->TranslateAccelerator(pIOIPAO, &msg); 1091 | pIOIPAO->lpVtbl->Release(pIOIPAO); 1092 | } 1093 | webBrowser2->lpVtbl->Release(webBrowser2); 1094 | } 1095 | if (r != S_FALSE) { 1096 | break; 1097 | } 1098 | } 1099 | default: 1100 | TranslateMessage(&msg); 1101 | DispatchMessage(&msg); 1102 | } 1103 | return 0; 1104 | } 1105 | 1106 | static int webview_eval(struct webview *w, const char *js) { 1107 | IWebBrowser2 *webBrowser2; 1108 | IHTMLDocument2 *htmlDoc2; 1109 | IDispatch *docDispatch; 1110 | IDispatch *scriptDispatch; 1111 | if ((*w->priv.browser) 1112 | ->lpVtbl->QueryInterface((*w->priv.browser), 1113 | iid_unref(&IID_IWebBrowser2), 1114 | (void **)&webBrowser2) != S_OK) { 1115 | return -1; 1116 | } 1117 | 1118 | if (webBrowser2->lpVtbl->get_Document(webBrowser2, &docDispatch) != S_OK) { 1119 | return -1; 1120 | } 1121 | if (docDispatch->lpVtbl->QueryInterface(docDispatch, 1122 | iid_unref(&IID_IHTMLDocument2), 1123 | (void **)&htmlDoc2) != S_OK) { 1124 | return -1; 1125 | } 1126 | if (htmlDoc2->lpVtbl->get_Script(htmlDoc2, &scriptDispatch) != S_OK) { 1127 | return -1; 1128 | } 1129 | DISPID dispid; 1130 | BSTR evalStr = SysAllocString(L"eval"); 1131 | if (scriptDispatch->lpVtbl->GetIDsOfNames( 1132 | scriptDispatch, iid_unref(&IID_NULL), &evalStr, 1, 1133 | LOCALE_SYSTEM_DEFAULT, &dispid) != S_OK) { 1134 | SysFreeString(evalStr); 1135 | return -1; 1136 | } 1137 | SysFreeString(evalStr); 1138 | 1139 | DISPPARAMS params; 1140 | VARIANT arg; 1141 | VARIANT result; 1142 | EXCEPINFO excepInfo; 1143 | UINT nArgErr = (UINT)-1; 1144 | params.cArgs = 1; 1145 | params.cNamedArgs = 0; 1146 | params.rgvarg = &arg; 1147 | arg.vt = VT_BSTR; 1148 | static const char *prologue = "(function(){"; 1149 | static const char *epilogue = ";})();"; 1150 | int n = strlen(prologue) + strlen(epilogue) + strlen(js) + 1; 1151 | char *eval = (char *)malloc(n); 1152 | strcpy(eval, prologue); 1153 | strcat(eval, js); 1154 | strcat(eval, epilogue); 1155 | wchar_t *buf = webview_to_utf16(eval); 1156 | if (buf == NULL) { 1157 | return -1; 1158 | } 1159 | arg.bstrVal = SysAllocString(buf); 1160 | if (scriptDispatch->lpVtbl->Invoke( 1161 | scriptDispatch, dispid, iid_unref(&IID_NULL), 0, DISPATCH_METHOD, 1162 | ¶ms, &result, &excepInfo, &nArgErr) != S_OK) { 1163 | return -1; 1164 | } 1165 | SysFreeString(arg.bstrVal); 1166 | free(eval); 1167 | scriptDispatch->lpVtbl->Release(scriptDispatch); 1168 | htmlDoc2->lpVtbl->Release(htmlDoc2); 1169 | docDispatch->lpVtbl->Release(docDispatch); 1170 | return 0; 1171 | } 1172 | 1173 | static void webview_dispatch(struct webview *w, webview_dispatch_fn fn, 1174 | void *arg) { 1175 | PostMessageW(w->priv.hwnd, WM_WEBVIEW_DISPATCH, (WPARAM)fn, (LPARAM)arg); 1176 | } 1177 | 1178 | static void webview_set_title(struct webview *w, const char *title) { 1179 | SetWindowText(w->priv.hwnd, title); 1180 | } 1181 | 1182 | /* These are missing parts from MinGW */ 1183 | #ifndef __IFileDialog_INTERFACE_DEFINED__ 1184 | #define __IFileDialog_INTERFACE_DEFINED__ 1185 | enum _FILEOPENDIALOGOPTIONS { 1186 | FOS_OVERWRITEPROMPT = 0x2, 1187 | FOS_STRICTFILETYPES = 0x4, 1188 | FOS_NOCHANGEDIR = 0x8, 1189 | FOS_PICKFOLDERS = 0x20, 1190 | FOS_FORCEFILESYSTEM = 0x40, 1191 | FOS_ALLNONSTORAGEITEMS = 0x80, 1192 | FOS_NOVALIDATE = 0x100, 1193 | FOS_ALLOWMULTISELECT = 0x200, 1194 | FOS_PATHMUSTEXIST = 0x800, 1195 | FOS_FILEMUSTEXIST = 0x1000, 1196 | FOS_CREATEPROMPT = 0x2000, 1197 | FOS_SHAREAWARE = 0x4000, 1198 | FOS_NOREADONLYRETURN = 0x8000, 1199 | FOS_NOTESTFILECREATE = 0x10000, 1200 | FOS_HIDEMRUPLACES = 0x20000, 1201 | FOS_HIDEPINNEDPLACES = 0x40000, 1202 | FOS_NODEREFERENCELINKS = 0x100000, 1203 | FOS_DONTADDTORECENT = 0x2000000, 1204 | FOS_FORCESHOWHIDDEN = 0x10000000, 1205 | FOS_DEFAULTNOMINIMODE = 0x20000000, 1206 | FOS_FORCEPREVIEWPANEON = 0x40000000 1207 | }; 1208 | typedef DWORD FILEOPENDIALOGOPTIONS; 1209 | typedef enum FDAP { FDAP_BOTTOM = 0, FDAP_TOP = 1 } FDAP; 1210 | DEFINE_GUID(IID_IFileDialog, 0x42f85136, 0xdb7e, 0x439c, 0x85, 0xf1, 0xe4, 0x07, 1211 | 0x5d, 0x13, 0x5f, 0xc8); 1212 | typedef struct IFileDialogVtbl { 1213 | BEGIN_INTERFACE 1214 | HRESULT(STDMETHODCALLTYPE *QueryInterface) 1215 | (IFileDialog *This, REFIID riid, void **ppvObject); 1216 | ULONG(STDMETHODCALLTYPE *AddRef)(IFileDialog *This); 1217 | ULONG(STDMETHODCALLTYPE *Release)(IFileDialog *This); 1218 | HRESULT(STDMETHODCALLTYPE *Show)(IFileDialog *This, HWND hwndOwner); 1219 | HRESULT(STDMETHODCALLTYPE *SetFileTypes) 1220 | (IFileDialog *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec); 1221 | HRESULT(STDMETHODCALLTYPE *SetFileTypeIndex) 1222 | (IFileDialog *This, UINT iFileType); 1223 | HRESULT(STDMETHODCALLTYPE *GetFileTypeIndex) 1224 | (IFileDialog *This, UINT *piFileType); 1225 | HRESULT(STDMETHODCALLTYPE *Advise) 1226 | (IFileDialog *This, IFileDialogEvents *pfde, DWORD *pdwCookie); 1227 | HRESULT(STDMETHODCALLTYPE *Unadvise)(IFileDialog *This, DWORD dwCookie); 1228 | HRESULT(STDMETHODCALLTYPE *SetOptions) 1229 | (IFileDialog *This, FILEOPENDIALOGOPTIONS fos); 1230 | HRESULT(STDMETHODCALLTYPE *GetOptions) 1231 | (IFileDialog *This, FILEOPENDIALOGOPTIONS *pfos); 1232 | HRESULT(STDMETHODCALLTYPE *SetDefaultFolder) 1233 | (IFileDialog *This, IShellItem *psi); 1234 | HRESULT(STDMETHODCALLTYPE *SetFolder)(IFileDialog *This, IShellItem *psi); 1235 | HRESULT(STDMETHODCALLTYPE *GetFolder)(IFileDialog *This, IShellItem **ppsi); 1236 | HRESULT(STDMETHODCALLTYPE *GetCurrentSelection) 1237 | (IFileDialog *This, IShellItem **ppsi); 1238 | HRESULT(STDMETHODCALLTYPE *SetFileName)(IFileDialog *This, LPCWSTR pszName); 1239 | HRESULT(STDMETHODCALLTYPE *GetFileName)(IFileDialog *This, LPWSTR *pszName); 1240 | HRESULT(STDMETHODCALLTYPE *SetTitle)(IFileDialog *This, LPCWSTR pszTitle); 1241 | HRESULT(STDMETHODCALLTYPE *SetOkButtonLabel) 1242 | (IFileDialog *This, LPCWSTR pszText); 1243 | HRESULT(STDMETHODCALLTYPE *SetFileNameLabel) 1244 | (IFileDialog *This, LPCWSTR pszLabel); 1245 | HRESULT(STDMETHODCALLTYPE *GetResult)(IFileDialog *This, IShellItem **ppsi); 1246 | HRESULT(STDMETHODCALLTYPE *AddPlace) 1247 | (IFileDialog *This, IShellItem *psi, FDAP fdap); 1248 | HRESULT(STDMETHODCALLTYPE *SetDefaultExtension) 1249 | (IFileDialog *This, LPCWSTR pszDefaultExtension); 1250 | HRESULT(STDMETHODCALLTYPE *Close)(IFileDialog *This, HRESULT hr); 1251 | HRESULT(STDMETHODCALLTYPE *SetClientGuid)(IFileDialog *This, REFGUID guid); 1252 | HRESULT(STDMETHODCALLTYPE *ClearClientData)(IFileDialog *This); 1253 | HRESULT(STDMETHODCALLTYPE *SetFilter) 1254 | (IFileDialog *This, IShellItemFilter *pFilter); 1255 | END_INTERFACE 1256 | } IFileDialogVtbl; 1257 | interface IFileDialog { 1258 | CONST_VTBL IFileDialogVtbl *lpVtbl; 1259 | }; 1260 | DEFINE_GUID(IID_IFileOpenDialog, 0xd57c7288, 0xd4ad, 0x4768, 0xbe, 0x02, 0x9d, 1261 | 0x96, 0x95, 0x32, 0xd9, 0x60); 1262 | DEFINE_GUID(IID_IFileSaveDialog, 0x84bccd23, 0x5fde, 0x4cdb, 0xae, 0xa4, 0xaf, 1263 | 0x64, 0xb8, 0x3d, 0x78, 0xab); 1264 | #endif 1265 | 1266 | static void webview_dialog(struct webview *w, enum webview_dialog_type dlgtype, 1267 | int flags, const char *title, const char *arg, 1268 | char *result, size_t resultsz) { 1269 | if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN || 1270 | dlgtype == WEBVIEW_DIALOG_TYPE_SAVE) { 1271 | IFileDialog *dlg = NULL; 1272 | IShellItem *res = NULL; 1273 | WCHAR *ws = NULL; 1274 | char *s = NULL; 1275 | FILEOPENDIALOGOPTIONS opts, add_opts; 1276 | if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN) { 1277 | if (CoCreateInstance( 1278 | iid_unref(&CLSID_FileOpenDialog), NULL, CLSCTX_INPROC_SERVER, 1279 | iid_unref(&IID_IFileOpenDialog), (void **)&dlg) != S_OK) { 1280 | goto error_dlg; 1281 | } 1282 | add_opts |= FOS_NOCHANGEDIR | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE | 1283 | FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_SHAREAWARE | 1284 | FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS | 1285 | FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE; 1286 | } else { 1287 | if (CoCreateInstance( 1288 | iid_unref(&CLSID_FileSaveDialog), NULL, CLSCTX_INPROC_SERVER, 1289 | iid_unref(&IID_IFileSaveDialog), (void **)&dlg) != S_OK) { 1290 | goto error_dlg; 1291 | } 1292 | add_opts |= FOS_OVERWRITEPROMPT | FOS_NOCHANGEDIR | 1293 | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE | FOS_SHAREAWARE | 1294 | FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS | 1295 | FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE; 1296 | } 1297 | if (dlg->lpVtbl->GetOptions(dlg, &opts) != S_OK) { 1298 | goto error_dlg; 1299 | } 1300 | opts &= ~FOS_NOREADONLYRETURN; 1301 | opts |= add_opts; 1302 | if (dlg->lpVtbl->SetOptions(dlg, opts) != S_OK) { 1303 | goto error_dlg; 1304 | } 1305 | if (dlg->lpVtbl->Show(dlg, w->priv.hwnd) != S_OK) { 1306 | goto error_dlg; 1307 | } 1308 | if (dlg->lpVtbl->GetResult(dlg, &res) != S_OK) { 1309 | goto error_dlg; 1310 | } 1311 | if (res->lpVtbl->GetDisplayName(res, SIGDN_FILESYSPATH, &ws) != S_OK) { 1312 | goto error_result; 1313 | } 1314 | s = webview_from_utf16(ws); 1315 | strncpy(result, s, resultsz); 1316 | result[resultsz - 1] = '\0'; 1317 | CoTaskMemFree(ws); 1318 | error_result: 1319 | res->lpVtbl->Release(res); 1320 | error_dlg: 1321 | dlg->lpVtbl->Release(dlg); 1322 | return; 1323 | } else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT) { 1324 | #if 0 1325 | /* MinGW often doesn't contain TaskDialog, so we'll use MessageBox for now */ 1326 | WCHAR *wtitle = webview_to_utf16(title); 1327 | WCHAR *warg = webview_to_utf16(arg); 1328 | TaskDialog(w->priv.hwnd, NULL, NULL, wtitle, warg, 0, NULL, NULL); 1329 | GlobalFree(warg); 1330 | GlobalFree(wtitle); 1331 | #else 1332 | MessageBox(w->priv.hwnd, arg, title, MB_OK); 1333 | #endif 1334 | } 1335 | } 1336 | 1337 | static void webview_terminate(struct webview *w) { PostQuitMessage(0); } 1338 | static void webview_exit(struct webview *w) { OleUninitialize(); } 1339 | 1340 | #endif /* WEBVIEW_WINAPI */ 1341 | 1342 | #if defined(WEBVIEW_COCOA) 1343 | #if (!defined MAC_OS_X_VERSION_10_12) || \ 1344 | MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12 1345 | #define NSWindowStyleMaskResizable NSResizableWindowMask 1346 | #define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask 1347 | #define NSWindowStyleMaskTitled NSTitledWindowMask 1348 | #define NSWindowStyleMaskClosable NSClosableWindowMask 1349 | #define NSEventMaskAny NSAnyEventMask 1350 | #define NSEventModifierFlagCommand NSCommandKeyMask 1351 | #define NSEventModifierFlagOption NSAlternateKeyMask 1352 | #define NSAlertStyleInformational NSInformationalAlertStyle 1353 | #endif /* MAC_OS_X_VERSION_10_12 */ 1354 | #if (!defined MAC_OS_X_VERSION_10_13) || \ 1355 | MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13 1356 | #define NSModalResponseOK NSFileHandlingPanelOKButton 1357 | #endif /* MAC_OS_X_VERSION_10_12, MAC_OS_X_VERSION_10_13 */ 1358 | static void webview_window_will_close(id self, SEL cmd, id notification) { 1359 | struct webview *w = 1360 | (struct webview *)objc_getAssociatedObject(self, "webview"); 1361 | webview_terminate(w); 1362 | } 1363 | 1364 | static BOOL webview_is_selector_excluded_from_web_script(id self, SEL cmd, 1365 | SEL selector) { 1366 | return selector != @selector(invoke:); 1367 | } 1368 | 1369 | static void webview_did_clear_window_object(id self, SEL cmd, id webview, 1370 | id script, id frame) { 1371 | [script setValue:self forKey:@"external"]; 1372 | } 1373 | 1374 | static void webview_external_invoke(id self, SEL cmd, id arg) { 1375 | struct webview *w = 1376 | (struct webview *)objc_getAssociatedObject(self, "webview"); 1377 | if (w == NULL || w->external_invoke_cb == NULL) { 1378 | return; 1379 | } 1380 | if ([arg isKindOfClass:[NSString class]] == NO) { 1381 | return; 1382 | } 1383 | w->external_invoke_cb(w, [(NSString *)(arg)UTF8String]); 1384 | } 1385 | 1386 | static int webview_init(struct webview *w) { 1387 | w->priv.pool = [[NSAutoreleasePool alloc] init]; 1388 | [NSApplication sharedApplication]; 1389 | 1390 | Class webViewDelegateClass = 1391 | objc_allocateClassPair([NSObject class], "WebViewDelegate", 0); 1392 | class_addMethod(webViewDelegateClass, sel_registerName("windowWillClose:"), 1393 | (IMP)webview_window_will_close, "v@:@"); 1394 | class_addMethod(object_getClass(webViewDelegateClass), 1395 | sel_registerName("isSelectorExcludedFromWebScript:"), 1396 | (IMP)webview_is_selector_excluded_from_web_script, "c@::"); 1397 | class_addMethod(webViewDelegateClass, 1398 | sel_registerName("webView:didClearWindowObject:forFrame:"), 1399 | (IMP)webview_did_clear_window_object, "v@:@@@"); 1400 | class_addMethod(webViewDelegateClass, sel_registerName("invoke:"), 1401 | (IMP)webview_external_invoke, "v@:@"); 1402 | objc_registerClassPair(webViewDelegateClass); 1403 | 1404 | w->priv.windowDelegate = [[webViewDelegateClass alloc] init]; 1405 | objc_setAssociatedObject(w->priv.windowDelegate, "webview", (id)(w), 1406 | OBJC_ASSOCIATION_ASSIGN); 1407 | 1408 | NSString *nsTitle = [NSString stringWithUTF8String:w->title]; 1409 | NSRect r = NSMakeRect(0, 0, w->width, w->height); 1410 | NSUInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | 1411 | NSWindowStyleMaskMiniaturizable; 1412 | if (w->resizable) { 1413 | style = style | NSWindowStyleMaskResizable; 1414 | } 1415 | w->priv.window = [[NSWindow alloc] initWithContentRect:r 1416 | styleMask:style 1417 | backing:NSBackingStoreBuffered 1418 | defer:NO]; 1419 | [w->priv.window autorelease]; 1420 | [w->priv.window setTitle:nsTitle]; 1421 | [w->priv.window setDelegate:w->priv.windowDelegate]; 1422 | [w->priv.window center]; 1423 | 1424 | w->priv.webview = 1425 | [[WebView alloc] initWithFrame:r frameName:@"WebView" groupName:nil]; 1426 | NSURL *nsURL = [NSURL URLWithString:[NSString stringWithUTF8String:w->url]]; 1427 | [[w->priv.webview mainFrame] loadRequest:[NSURLRequest requestWithURL:nsURL]]; 1428 | 1429 | [w->priv.webview setAutoresizesSubviews:YES]; 1430 | [w->priv.webview 1431 | setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; 1432 | w->priv.webview.frameLoadDelegate = w->priv.windowDelegate; 1433 | [[w->priv.window contentView] addSubview:w->priv.webview]; 1434 | [w->priv.window orderFrontRegardless]; 1435 | 1436 | [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 1437 | [NSApp finishLaunching]; 1438 | [NSApp activateIgnoringOtherApps:YES]; 1439 | 1440 | NSMenu *menubar = [[[NSMenu alloc] initWithTitle:@""] autorelease]; 1441 | 1442 | NSString *appName = [[NSProcessInfo processInfo] processName]; 1443 | NSMenuItem *appMenuItem = 1444 | [[[NSMenuItem alloc] initWithTitle:appName action:NULL keyEquivalent:@""] 1445 | autorelease]; 1446 | NSMenu *appMenu = [[[NSMenu alloc] initWithTitle:appName] autorelease]; 1447 | [appMenuItem setSubmenu:appMenu]; 1448 | [menubar addItem:appMenuItem]; 1449 | 1450 | NSString *title = [@"Hide " stringByAppendingString:appName]; 1451 | NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:title 1452 | action:@selector(hide:) 1453 | keyEquivalent:@"h"] autorelease]; 1454 | [appMenu addItem:item]; 1455 | item = [[[NSMenuItem alloc] initWithTitle:@"Hide Others" 1456 | action:@selector(hideOtherApplications:) 1457 | keyEquivalent:@"h"] autorelease]; 1458 | [item setKeyEquivalentModifierMask:(NSEventModifierFlagOption | 1459 | NSEventModifierFlagCommand)]; 1460 | [appMenu addItem:item]; 1461 | item = [[[NSMenuItem alloc] initWithTitle:@"Show All" 1462 | action:@selector(unhideAllApplications:) 1463 | keyEquivalent:@""] autorelease]; 1464 | [appMenu addItem:item]; 1465 | [appMenu addItem:[NSMenuItem separatorItem]]; 1466 | 1467 | title = [@"Quit " stringByAppendingString:appName]; 1468 | item = [[[NSMenuItem alloc] initWithTitle:title 1469 | action:@selector(terminate:) 1470 | keyEquivalent:@"q"] autorelease]; 1471 | [appMenu addItem:item]; 1472 | 1473 | [NSApp setMainMenu:menubar]; 1474 | 1475 | w->priv.should_exit = 0; 1476 | return 0; 1477 | } 1478 | 1479 | static int webview_loop(struct webview *w, int blocking) { 1480 | NSDate *until = (blocking ? [NSDate distantFuture] : [NSDate distantPast]); 1481 | NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny 1482 | untilDate:until 1483 | inMode:NSDefaultRunLoopMode 1484 | dequeue:YES]; 1485 | if (event) { 1486 | [NSApp sendEvent:event]; 1487 | } 1488 | return w->priv.should_exit; 1489 | } 1490 | 1491 | static int webview_eval(struct webview *w, const char *js) { 1492 | NSString *nsJS = [NSString stringWithUTF8String:js]; 1493 | [[w->priv.webview windowScriptObject] evaluateWebScript:nsJS]; 1494 | return 0; 1495 | } 1496 | 1497 | static void webview_set_title(struct webview *w, const char *title) { 1498 | NSString *nsTitle = [NSString stringWithUTF8String:title]; 1499 | [w->priv.window setTitle:nsTitle]; 1500 | } 1501 | 1502 | static void webview_dialog(struct webview *w, enum webview_dialog_type dlgtype, 1503 | int flags, const char *title, const char *arg, 1504 | char *result, size_t resultsz) { 1505 | if (result != NULL) { 1506 | result[0] = '\0'; 1507 | } 1508 | if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN || 1509 | dlgtype == WEBVIEW_DIALOG_TYPE_SAVE) { 1510 | NSSavePanel *panel; 1511 | if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN) { 1512 | NSOpenPanel *openPanel = [NSOpenPanel openPanel]; 1513 | [openPanel setCanChooseFiles:YES]; 1514 | [openPanel setCanChooseDirectories:NO]; 1515 | [openPanel setResolvesAliases:NO]; 1516 | [openPanel setAllowsMultipleSelection:NO]; 1517 | panel = openPanel; 1518 | } else { 1519 | panel = [NSSavePanel savePanel]; 1520 | } 1521 | [panel setCanCreateDirectories:YES]; 1522 | [panel setShowsHiddenFiles:YES]; 1523 | [panel setExtensionHidden:NO]; 1524 | [panel setCanSelectHiddenExtension:NO]; 1525 | [panel setTreatsFilePackagesAsDirectories:YES]; 1526 | [panel beginSheetModalForWindow:w->priv.window 1527 | completionHandler:^(NSInteger result) { 1528 | [NSApp stopModalWithCode:result]; 1529 | }]; 1530 | if ([NSApp runModalForWindow:panel] == NSModalResponseOK) { 1531 | const char *filename = [[[panel URL] path] UTF8String]; 1532 | strlcpy(result, filename, resultsz); 1533 | } 1534 | } else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT) { 1535 | NSAlert *a = [NSAlert new]; 1536 | [a setAlertStyle:NSAlertStyleInformational]; 1537 | [a setShowsHelp:NO]; 1538 | [a setShowsSuppressionButton:NO]; 1539 | [a setMessageText:[NSString stringWithUTF8String:title]]; 1540 | [a setInformativeText:[NSString stringWithUTF8String:arg]]; 1541 | [a addButtonWithTitle:@"OK"]; 1542 | [a runModal]; 1543 | [a release]; 1544 | } 1545 | } 1546 | 1547 | static void webview_dispatch_cb(void *arg) { 1548 | struct webview_dispatch_arg *context = (struct webview_dispatch_arg *)arg; 1549 | (context->fn)(context->w, context->arg); 1550 | free(context); 1551 | } 1552 | 1553 | static void webview_dispatch(struct webview *w, webview_dispatch_fn fn, 1554 | void *arg) { 1555 | struct webview_dispatch_arg *context = (struct webview_dispatch_arg *)malloc( 1556 | sizeof(struct webview_dispatch_arg)); 1557 | context->w = w; 1558 | context->arg = arg; 1559 | context->fn = fn; 1560 | dispatch_async_f(dispatch_get_main_queue(), context, webview_dispatch_cb); 1561 | } 1562 | 1563 | static void webview_terminate(struct webview *w) { w->priv.should_exit = 1; } 1564 | static void webview_exit(struct webview *w) { [NSApp terminate:NSApp]; } 1565 | 1566 | #endif /* WEBVIEW_COCOA */ 1567 | 1568 | #ifdef __cplusplus 1569 | } 1570 | #endif 1571 | 1572 | #endif /* WEBVIEW_H */ 1573 | --------------------------------------------------------------------------------