├── .gitattributes ├── .gitignore ├── .idea ├── .name └── gradle.xml ├── LICENSE ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── native-lib.cpp │ │ └── nimbase.h │ ├── java │ │ └── com │ │ │ └── nimviewAndroid │ │ │ ├── MainActivity.kt │ │ │ └── NativeCpp.kt │ ├── nim │ │ ├── custom_nimview.nim │ │ ├── custom_nimview.nimble │ │ └── nakefile.nim │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── nimview │ ├── .github │ └── workflows │ │ ├── codeql-analysis.yml │ │ ├── linux.yml │ │ ├── macos.yml │ │ └── windows.yml │ ├── .gitignore │ ├── .vscode │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json │ ├── LICENSE │ ├── MANIFEST.in │ ├── README.md │ ├── bdist_wheel.py │ ├── docs │ ├── nimdoc.out.css │ └── nimview.html │ ├── examples │ ├── __init__.py │ ├── c_sample.c │ ├── cpp_sample.cpp │ ├── demo.nim │ ├── minimal_sample.nim │ ├── minimal_ui_sample │ │ └── index.html │ ├── msvc │ │ └── minimal.vcxproj │ ├── pySample.py │ ├── svelte.nim │ ├── svelte │ │ ├── .gitignore │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── favicon.png │ │ │ ├── global.css │ │ │ └── index.html │ │ ├── rollup.config.js │ │ └── src │ │ │ ├── App.svelte │ │ │ ├── components │ │ │ ├── Navbar.svelte │ │ │ └── Sample.svelte │ │ │ └── main.js │ ├── vue.nim │ └── vue │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ └── logo.png │ │ ├── components │ │ │ ├── Navbar.vue │ │ │ └── Sample.vue │ │ └── main.js │ │ └── vue.config.js │ ├── nakefile.nim │ ├── nimview.nimble │ ├── setup.cfg │ ├── setup.py │ ├── src │ ├── backend-helper.js │ ├── globalToken.nim │ ├── nimview.hpp │ ├── nimview.nim │ ├── nimview_c.nim │ └── std │ │ ├── readme.md │ │ └── sysrand.nim │ └── tests │ ├── __init__.py │ ├── c_test.c │ ├── desktopSample.nim │ ├── httpSample.nim │ ├── pyTest.py │ ├── pyWebViewSample.py │ └── test.dump ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | app/src/nimview/* linguist-vendored=true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /app/.cxx 13 | /captures 14 | .externalNativeBuild 15 | .cxx* 16 | .cxx 17 | *.exe 18 | app/src/main/cpp/x86* 19 | app/src/main/cpp/x86_64* 20 | app/src/main/cpp/armeabi-v7a* 21 | app/src/main/cpp/arm64-v8a* 22 | app/src/main/node_modules* 23 | */build* 24 | app/src/main/assets/* -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | nimviewAndroid -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 marcomq 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | The source code has been moved to the normal nimview repository: https://github.com/marcomq/nimview 4 | 5 | You can still get the code from this repo by performing 6 | `npx degit marcomq/nimview/examples/android nimview_android` 7 | 8 | -------------- 9 | 10 | 11 | 12 | A Nim/Webview based helper to create Android applications with Nim/C/C++ and HTML/CSS 13 | 14 | Android Studio implementation of [Nimview](https://github.com/marcomq/nimview) 15 | 16 | This project uses Android Webview as UI layer. The back-end is supposed to be written in Nim, C/C++ 17 | or - if it doesn't need to be ported to other platforms - Kotlin or Java. 18 | As Android Webview doesn't has as much debugging capabilities as Chrome or Firefox, it would be recommended to write most of the UI in a web application 19 | with nimview in debug mode + npm autoreload first and then test these changes on Android later. 20 | 21 | The recommended workflow would be: 22 | ``` 23 | (installation) 24 | git clone https://github.com/marcomq/nimview_android.git 25 | cd app/src/main/nim 26 | nimble install -d -y 27 | cd ../.. 28 | cd nimview/examples/svelte 29 | npm install 30 | cd ../../../../.. 31 | 32 | (development) 33 | (open two terminals/shells, t1 and t2) 34 | t1: nim cpp -r -d:useServer app/src/main/nim/custom_nimview.nim 35 | t2: npm run dev --prefix app/src/nimview/examples/svelte 36 | open browser at http://localhost:5000 37 | (optional) modify nim / html / js code in VSCode 38 | restart nim server to apply changes on nim code 39 | press F5 if browser doesn't refresh automatically after js changes 40 | 41 | (release and testing) 42 | compile project with android studio, test, perform changes if required 43 | 44 | ``` 45 | 46 | The nimview directory is a git subtree of https://github.com/marcomq/nimview. The steps performed were: 47 | ``` 48 | git subtree add --prefix app/src/nimview https://github.com/marcomq/nimview.git main --squash 49 | git subtree pull --prefix app/src/nimview --squash https://github.com/marcomq/nimview.git main 50 | ``` 51 | To updated to the latest Nimview version, perform the `git subtree pull ...` command above. 52 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | 8 | defaultConfig { 9 | applicationId "com.nimviewAndroid" 10 | minSdkVersion 21 11 | targetSdkVersion 29 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | externalNativeBuild { 17 | cmake { 18 | cppFlags "-std=c++17" 19 | } 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | 29 | debug { 30 | applicationIdSuffix ".debug" 31 | debuggable true 32 | } 33 | } 34 | externalNativeBuild { 35 | cmake { 36 | path "src/main/cpp/CMakeLists.txt" 37 | version "3.10.2" 38 | } 39 | } 40 | ndkVersion '21.0.6113669' 41 | } 42 | 43 | dependencies { 44 | implementation fileTree(dir: "libs", include: ["*.jar"]) 45 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 46 | implementation 'androidx.core:core-ktx:1.3.1' 47 | implementation 'androidx.appcompat:appcompat:1.2.0' 48 | implementation 'androidx.constraintlayout:constraintlayout:2.0.1' 49 | testImplementation 'junit:junit:4.12' 50 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 51 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | -keepattributes SetJavaScriptEnabled 15 | -keepattributes JavascriptInterface 16 | -keepclassmembers class **.*$NativeCpp { 17 | *; 18 | } 19 | 20 | -keepclassmembers class com.nimviewAndroid.NativeCpp { 21 | public *; 22 | } 23 | 24 | # Uncomment this to preserve the line number information for 25 | # debugging stack traces. 26 | #-keepattributes SourceFile,LineNumberTable 27 | 28 | # If you keep the line number information, uncomment this to 29 | # hide the original source file name. 30 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html 3 | 4 | # Sets the minimum version of CMake required to build the native library. 5 | 6 | cmake_minimum_required(VERSION 3.5) 7 | 8 | include_directories(. ${ANDROID_ABI} ${CMAKE_CURRENT_SOURCE_DIR}/../../nimview/src/) 9 | # include_directories(C:/nim-1.2.6/lib ${ANDROID_ABI} ) 10 | add_definitions(-DNIMVIEW_CUSTOM_LIB -DJUST_CORE) 11 | 12 | AUX_SOURCE_DIRECTORY(${ANDROID_ABI} ALL_NIM_C_FILES) 13 | 14 | add_custom_target (nimToCAndJs ALL 15 | # OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/${ANDROID_ABI}/custom_nimview.h 16 | COMMAND nake 17 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../nim/) 18 | 19 | # Creates and names a library, sets it as either STATIC 20 | # or SHARED, and provides the relative paths to its source code. 21 | # You can define multiple libraries, and CMake builds them for you. 22 | # Gradle automatically packages shared libraries with your APK. 23 | add_library( # Sets the name of the library. 24 | native-lib 25 | # Sets the library as a shared library. 26 | SHARED 27 | # Provides a relative path to your source file(s). 28 | native-lib.cpp ${ALL_NIM_C_FILES}) 29 | 30 | add_dependencies(native-lib nimToCAndJs) 31 | 32 | find_library( # Sets the name of the path variable. 33 | log-lib 34 | # Specifies the name of the NDK library that 35 | # you want CMake to locate. 36 | log ) 37 | 38 | # Specifies libraries CMake should link to your target library. You 39 | # can link multiple libraries, such as libraries you define in this 40 | # build script, prebuilt third-party libraries, or system libraries. 41 | 42 | target_link_libraries( # Specifies the target library. 43 | native-lib 44 | # Links the target library to the log library 45 | # included in the NDK. 46 | ${log-lib} ) -------------------------------------------------------------------------------- /app/src/main/cpp/native-lib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "custom_nimview.h" 5 | #include "nimview.hpp" 6 | 7 | #define THIS_PROJECT_PREFIX Java_com_nimviewAndroid 8 | extern "C" JNIEXPORT jstring JNICALL 9 | Java_com_nimviewAndroid_NativeCpp_callNim( 10 | JNIEnv* env, 11 | jobject /* this */, jstring request, jstring value) { 12 | char* cRequest = const_cast(env->GetStringUTFChars(request, nullptr)); 13 | char* cValue = const_cast(env->GetStringUTFChars(value, nullptr)); 14 | std::string result(nimview::dispatchRequest(cRequest, cValue)); 15 | env->ReleaseStringUTFChars(request, cRequest); 16 | env->ReleaseStringUTFChars(value, cValue); 17 | return env->NewStringUTF(result.c_str()); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/cpp/nimbase.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Nim's Runtime Library 4 | (c) Copyright 2015 Andreas Rumpf 5 | 6 | See the file "copying.txt", included in this 7 | distribution, for details about the copyright. 8 | */ 9 | 10 | /* compiler symbols: 11 | __BORLANDC__ 12 | _MSC_VER 13 | __WATCOMC__ 14 | __LCC__ 15 | __GNUC__ 16 | __DMC__ 17 | __POCC__ 18 | __TINYC__ 19 | __clang__ 20 | __AVR__ 21 | */ 22 | 23 | 24 | #ifndef NIMBASE_H 25 | #define NIMBASE_H 26 | 27 | /*------------ declaring a custom attribute to support using LLVM's Address Sanitizer ------------ */ 28 | 29 | /* 30 | This definition exists to provide support for using the LLVM ASAN (Address SANitizer) tooling with Nim. This 31 | should only be used to mark implementations of the GC system that raise false flags with the ASAN tooling, or 32 | for functions that are hot and need to be disabled for performance reasons. Based on the official ASAN 33 | documentation, both the clang and gcc compilers are supported. In addition to that, a check is performed to 34 | verify that the necessary attribute is supported by the compiler. 35 | 36 | To flag a proc as ignored, append the following code pragma to the proc declaration: 37 | {.codegenDecl: "CLANG_NO_SANITIZE_ADDRESS $# $#$#".} 38 | 39 | For further information, please refer to the official documentation: 40 | https://github.com/google/sanitizers/wiki/AddressSanitizer 41 | */ 42 | #define CLANG_NO_SANITIZE_ADDRESS 43 | #if defined(__clang__) 44 | # if __has_attribute(no_sanitize_address) 45 | # undef CLANG_NO_SANITIZE_ADDRESS 46 | # define CLANG_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) 47 | # endif 48 | #endif 49 | 50 | 51 | /* ------------ ignore typical warnings in Nim-generated files ------------- */ 52 | #if defined(__GNUC__) || defined(__clang__) 53 | # pragma GCC diagnostic ignored "-Wpragmas" 54 | # pragma GCC diagnostic ignored "-Wwritable-strings" 55 | # pragma GCC diagnostic ignored "-Winvalid-noreturn" 56 | # pragma GCC diagnostic ignored "-Wformat" 57 | # pragma GCC diagnostic ignored "-Wlogical-not-parentheses" 58 | # pragma GCC diagnostic ignored "-Wlogical-op-parentheses" 59 | # pragma GCC diagnostic ignored "-Wshadow" 60 | # pragma GCC diagnostic ignored "-Wunused-function" 61 | # pragma GCC diagnostic ignored "-Wunused-variable" 62 | # pragma GCC diagnostic ignored "-Winvalid-offsetof" 63 | # pragma GCC diagnostic ignored "-Wtautological-compare" 64 | # pragma GCC diagnostic ignored "-Wswitch-bool" 65 | # pragma GCC diagnostic ignored "-Wmacro-redefined" 66 | # pragma GCC diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers" 67 | # pragma GCC diagnostic ignored "-Wpointer-bool-conversion" 68 | # pragma GCC diagnostic ignored "-Wconstant-conversion" 69 | #endif 70 | 71 | #if defined(_MSC_VER) 72 | # pragma warning(disable: 4005 4100 4101 4189 4191 4200 4244 4293 4296 4309) 73 | # pragma warning(disable: 4310 4365 4456 4477 4514 4574 4611 4668 4702 4706) 74 | # pragma warning(disable: 4710 4711 4774 4800 4809 4820 4996 4090 4297) 75 | #endif 76 | /* ------------------------------------------------------------------------- */ 77 | 78 | #if defined(__GNUC__) 79 | # define _GNU_SOURCE 1 80 | #endif 81 | 82 | #if defined(__TINYC__) 83 | /*# define __GNUC__ 3 84 | # define GCC_MAJOR 4 85 | # define __GNUC_MINOR__ 4 86 | # define __GNUC_PATCHLEVEL__ 5 */ 87 | # define __DECLSPEC_SUPPORTED 1 88 | #endif 89 | 90 | /* calling convention mess ----------------------------------------------- */ 91 | #if defined(__GNUC__) || defined(__LCC__) || defined(__POCC__) \ 92 | || defined(__TINYC__) 93 | /* these should support C99's inline */ 94 | /* the test for __POCC__ has to come before the test for _MSC_VER, 95 | because PellesC defines _MSC_VER too. This is brain-dead. */ 96 | # define N_INLINE(rettype, name) inline rettype name 97 | #elif defined(__BORLANDC__) || defined(_MSC_VER) 98 | /* Borland's compiler is really STRANGE here; note that the __fastcall 99 | keyword cannot be before the return type, but __inline cannot be after 100 | the return type, so we do not handle this mess in the code generator 101 | but rather here. */ 102 | # define N_INLINE(rettype, name) __inline rettype name 103 | #elif defined(__DMC__) 104 | # define N_INLINE(rettype, name) inline rettype name 105 | #elif defined(__WATCOMC__) 106 | # define N_INLINE(rettype, name) __inline rettype name 107 | #else /* others are less picky: */ 108 | # define N_INLINE(rettype, name) rettype __inline name 109 | #endif 110 | 111 | #define N_INLINE_PTR(rettype, name) rettype (*name) 112 | 113 | #if defined(__POCC__) 114 | # define NIM_CONST /* PCC is really picky with const modifiers */ 115 | # undef _MSC_VER /* Yeah, right PCC defines _MSC_VER even if it is 116 | not that compatible. Well done. */ 117 | #elif defined(__cplusplus) 118 | # define NIM_CONST /* C++ is picky with const modifiers */ 119 | #else 120 | # define NIM_CONST const 121 | #endif 122 | 123 | /* 124 | NIM_THREADVAR declaration based on 125 | http://stackoverflow.com/questions/18298280/how-to-declare-a-variable-as-thread-local-portably 126 | */ 127 | #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112 && !defined __STDC_NO_THREADS__ 128 | # define NIM_THREADVAR _Thread_local 129 | #elif defined _WIN32 && ( \ 130 | defined _MSC_VER || \ 131 | defined __ICL || \ 132 | defined __DMC__ || \ 133 | defined __BORLANDC__ ) 134 | # define NIM_THREADVAR __declspec(thread) 135 | #elif defined(__TINYC__) || defined(__GENODE__) 136 | # define NIM_THREADVAR 137 | /* note that ICC (linux) and Clang are covered by __GNUC__ */ 138 | #elif defined __GNUC__ || \ 139 | defined __SUNPRO_C || \ 140 | defined __xlC__ 141 | # define NIM_THREADVAR __thread 142 | #else 143 | # error "Cannot define NIM_THREADVAR" 144 | #endif 145 | 146 | /* --------------- how int64 constants should be declared: ----------- */ 147 | #if defined(__GNUC__) || defined(__LCC__) || \ 148 | defined(__POCC__) || defined(__DMC__) || defined(_MSC_VER) 149 | # define IL64(x) x##LL 150 | #else /* works only without LL */ 151 | # define IL64(x) ((NI64)x) 152 | #endif 153 | 154 | /* ---------------- casting without correct aliasing rules ----------- */ 155 | 156 | #if defined(__GNUC__) 157 | # define NIM_CAST(type, ptr) (((union{type __x__;}*)(ptr))->__x__) 158 | #else 159 | # define NIM_CAST(type, ptr) ((type)(ptr)) 160 | #endif 161 | 162 | 163 | /* ------------------------------------------------------------------- */ 164 | #ifdef __cplusplus 165 | # define NIM_EXTERNC extern "C" 166 | #else 167 | # define NIM_EXTERNC 168 | #endif 169 | 170 | #if defined(WIN32) || defined(_WIN32) /* only Windows has this mess... */ 171 | # define N_LIB_PRIVATE 172 | # define N_CDECL(rettype, name) rettype __cdecl name 173 | # define N_STDCALL(rettype, name) rettype __stdcall name 174 | # define N_SYSCALL(rettype, name) rettype __syscall name 175 | # define N_FASTCALL(rettype, name) rettype __fastcall name 176 | # define N_THISCALL(rettype, name) rettype __thiscall name 177 | # define N_SAFECALL(rettype, name) rettype __stdcall name 178 | /* function pointers with calling convention: */ 179 | # define N_CDECL_PTR(rettype, name) rettype (__cdecl *name) 180 | # define N_STDCALL_PTR(rettype, name) rettype (__stdcall *name) 181 | # define N_SYSCALL_PTR(rettype, name) rettype (__syscall *name) 182 | # define N_FASTCALL_PTR(rettype, name) rettype (__fastcall *name) 183 | # define N_THISCALL_PTR(rettype, name) rettype (__thiscall *name) 184 | # define N_SAFECALL_PTR(rettype, name) rettype (__stdcall *name) 185 | 186 | # ifdef __cplusplus 187 | # define N_LIB_EXPORT NIM_EXTERNC __declspec(dllexport) 188 | # else 189 | # define N_LIB_EXPORT NIM_EXTERNC __declspec(dllexport) 190 | # endif 191 | # define N_LIB_EXPORT_VAR __declspec(dllexport) 192 | # define N_LIB_IMPORT extern __declspec(dllimport) 193 | #else 194 | # define N_LIB_PRIVATE __attribute__((visibility("hidden"))) 195 | # if defined(__GNUC__) 196 | # define N_CDECL(rettype, name) rettype name 197 | # define N_STDCALL(rettype, name) rettype name 198 | # define N_SYSCALL(rettype, name) rettype name 199 | # define N_FASTCALL(rettype, name) __attribute__((fastcall)) rettype name 200 | # define N_SAFECALL(rettype, name) rettype name 201 | /* function pointers with calling convention: */ 202 | # define N_CDECL_PTR(rettype, name) rettype (*name) 203 | # define N_STDCALL_PTR(rettype, name) rettype (*name) 204 | # define N_SYSCALL_PTR(rettype, name) rettype (*name) 205 | # define N_FASTCALL_PTR(rettype, name) __attribute__((fastcall)) rettype (*name) 206 | # define N_SAFECALL_PTR(rettype, name) rettype (*name) 207 | # else 208 | # define N_CDECL(rettype, name) rettype name 209 | # define N_STDCALL(rettype, name) rettype name 210 | # define N_SYSCALL(rettype, name) rettype name 211 | # define N_FASTCALL(rettype, name) rettype name 212 | # define N_SAFECALL(rettype, name) rettype name 213 | /* function pointers with calling convention: */ 214 | # define N_CDECL_PTR(rettype, name) rettype (*name) 215 | # define N_STDCALL_PTR(rettype, name) rettype (*name) 216 | # define N_SYSCALL_PTR(rettype, name) rettype (*name) 217 | # define N_FASTCALL_PTR(rettype, name) rettype (*name) 218 | # define N_SAFECALL_PTR(rettype, name) rettype (*name) 219 | # endif 220 | # define N_LIB_EXPORT NIM_EXTERNC __attribute__((visibility("default"))) 221 | # define N_LIB_EXPORT_VAR __attribute__((visibility("default"))) 222 | # define N_LIB_IMPORT extern 223 | #endif 224 | 225 | #define N_NOCONV(rettype, name) rettype name 226 | /* specify no calling convention */ 227 | #define N_NOCONV_PTR(rettype, name) rettype (*name) 228 | 229 | #if defined(__GNUC__) || defined(__ICC__) 230 | # define N_NOINLINE(rettype, name) rettype __attribute__((__noinline__)) name 231 | #elif defined(_MSC_VER) 232 | # define N_NOINLINE(rettype, name) __declspec(noinline) rettype name 233 | #else 234 | # define N_NOINLINE(rettype, name) rettype name 235 | #endif 236 | 237 | #define N_NOINLINE_PTR(rettype, name) rettype (*name) 238 | 239 | #if defined(__BORLANDC__) || defined(__WATCOMC__) || \ 240 | defined(__POCC__) || defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) 241 | /* these compilers have a fastcall so use it: */ 242 | # ifdef __TINYC__ 243 | # define N_NIMCALL(rettype, name) rettype __attribute((__fastcall)) name 244 | # define N_NIMCALL_PTR(rettype, name) rettype (__attribute((__fastcall)) *name) 245 | # define N_RAW_NIMCALL __attribute((__fastcall)) 246 | # else 247 | # define N_NIMCALL(rettype, name) rettype __fastcall name 248 | # define N_NIMCALL_PTR(rettype, name) rettype (__fastcall *name) 249 | # define N_RAW_NIMCALL __fastcall 250 | # endif 251 | #else 252 | # define N_NIMCALL(rettype, name) rettype name /* no modifier */ 253 | # define N_NIMCALL_PTR(rettype, name) rettype (*name) 254 | # define N_RAW_NIMCALL 255 | #endif 256 | 257 | #define N_CLOSURE(rettype, name) N_NIMCALL(rettype, name) 258 | #define N_CLOSURE_PTR(rettype, name) N_NIMCALL_PTR(rettype, name) 259 | 260 | /* ----------------------------------------------------------------------- */ 261 | 262 | #define COMMA , 263 | 264 | #include 265 | #include 266 | 267 | // define NIM_STATIC_ASSERT 268 | // example use case: CT sizeof for importc types verification 269 | // where we have {.completeStruct.} (or lack of {.incompleteStruct.}) 270 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) 271 | #define NIM_STATIC_ASSERT(x, msg) _Static_assert((x), msg) 272 | #elif defined(__cplusplus) 273 | #define NIM_STATIC_ASSERT(x, msg) static_assert((x), msg) 274 | #else 275 | #define NIM_STATIC_ASSERT(x, msg) typedef int NIM_STATIC_ASSERT_AUX[(x) ? 1 : -1]; 276 | // On failure, your C compiler will say something like: 277 | // "error: 'NIM_STATIC_ASSERT_AUX' declared as an array with a negative size" 278 | // we could use a better fallback to also show line number, using: 279 | // http://www.pixelbeat.org/programming/gcc/static_assert.html 280 | #endif 281 | 282 | /* C99 compiler? */ 283 | #if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901)) 284 | # define HAVE_STDINT_H 285 | #endif 286 | 287 | /* Known compiler with stdint.h that doesn't fit the general pattern? */ 288 | #if defined(__LCC__) || defined(__DMC__) || defined(__POCC__) || \ 289 | defined(__AVR__) || (defined(__cplusplus) && (__cplusplus < 201103)) 290 | # define HAVE_STDINT_H 291 | #endif 292 | 293 | #if (!defined(HAVE_STDINT_H) && defined(__cplusplus) && (__cplusplus >= 201103)) 294 | # define HAVE_CSTDINT 295 | #endif 296 | 297 | 298 | /* wrap all Nim typedefs into namespace Nim */ 299 | #ifdef USE_NIM_NAMESPACE 300 | #ifdef HAVE_CSTDINT 301 | #include 302 | #else 303 | #include 304 | #endif 305 | namespace USE_NIM_NAMESPACE { 306 | #endif 307 | 308 | // preexisting check, seems paranoid, maybe remove 309 | #if defined(NIM_TRUE) || defined(NIM_FALSE) || defined(NIM_BOOL) 310 | #error "nim reserved preprocessor macros clash" 311 | #endif 312 | 313 | /* bool types (C++ has it): */ 314 | #ifdef __cplusplus 315 | #define NIM_BOOL bool 316 | #elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901) 317 | // see #13798: to avoid conflicts for code emitting `#include ` 318 | #define NIM_BOOL _Bool 319 | #else 320 | typedef unsigned char NIM_BOOL; // best effort 321 | #endif 322 | 323 | NIM_STATIC_ASSERT(sizeof(NIM_BOOL) == 1, ""); // check whether really needed 324 | 325 | #define NIM_TRUE true 326 | #define NIM_FALSE false 327 | 328 | #ifdef __cplusplus 329 | # if __cplusplus >= 201103L 330 | # /* nullptr is more type safe (less implicit conversions than 0) */ 331 | # define NIM_NIL nullptr 332 | # else 333 | # // both `((void*)0)` and `NULL` would cause codegen to emit 334 | # // error: assigning to 'Foo *' from incompatible type 'void *' 335 | # // but codegen could be fixed if need. See also potential caveat regarding 336 | # // NULL. 337 | # // However, `0` causes other issues, see #13798 338 | # define NIM_NIL 0 339 | # endif 340 | #else 341 | # include 342 | # define NIM_NIL ((void*)0) /* C's NULL is fucked up in some C compilers, so 343 | the generated code does not rely on it anymore */ 344 | #endif 345 | 346 | #if defined(__BORLANDC__) || defined(__DMC__) \ 347 | || defined(__WATCOMC__) || defined(_MSC_VER) 348 | typedef signed char NI8; 349 | typedef signed short int NI16; 350 | typedef signed int NI32; 351 | typedef __int64 NI64; 352 | /* XXX: Float128? */ 353 | typedef unsigned char NU8; 354 | typedef unsigned short int NU16; 355 | typedef unsigned int NU32; 356 | typedef unsigned __int64 NU64; 357 | #elif defined(HAVE_STDINT_H) 358 | #ifndef USE_NIM_NAMESPACE 359 | # include 360 | #endif 361 | typedef int8_t NI8; 362 | typedef int16_t NI16; 363 | typedef int32_t NI32; 364 | typedef int64_t NI64; 365 | typedef uint8_t NU8; 366 | typedef uint16_t NU16; 367 | typedef uint32_t NU32; 368 | typedef uint64_t NU64; 369 | #elif defined(HAVE_CSTDINT) 370 | #ifndef USE_NIM_NAMESPACE 371 | # include 372 | #endif 373 | typedef std::int8_t NI8; 374 | typedef std::int16_t NI16; 375 | typedef std::int32_t NI32; 376 | typedef std::int64_t NI64; 377 | typedef std::uint8_t NU8; 378 | typedef std::uint16_t NU16; 379 | typedef std::uint32_t NU32; 380 | typedef std::uint64_t NU64; 381 | #else 382 | /* Unknown compiler/version, do our best */ 383 | #ifdef __INT8_TYPE__ 384 | typedef __INT8_TYPE__ NI8; 385 | #else 386 | typedef signed char NI8; 387 | #endif 388 | #ifdef __INT16_TYPE__ 389 | typedef __INT16_TYPE__ NI16; 390 | #else 391 | typedef signed short int NI16; 392 | #endif 393 | #ifdef __INT32_TYPE__ 394 | typedef __INT32_TYPE__ NI32; 395 | #else 396 | typedef signed int NI32; 397 | #endif 398 | #ifdef __INT64_TYPE__ 399 | typedef __INT64_TYPE__ NI64; 400 | #else 401 | typedef long long int NI64; 402 | #endif 403 | /* XXX: Float128? */ 404 | #ifdef __UINT8_TYPE__ 405 | typedef __UINT8_TYPE__ NU8; 406 | #else 407 | typedef unsigned char NU8; 408 | #endif 409 | #ifdef __UINT16_TYPE__ 410 | typedef __UINT16_TYPE__ NU16; 411 | #else 412 | typedef unsigned short int NU16; 413 | #endif 414 | #ifdef __UINT32_TYPE__ 415 | typedef __UINT32_TYPE__ NU32; 416 | #else 417 | typedef unsigned int NU32; 418 | #endif 419 | #ifdef __UINT64_TYPE__ 420 | typedef __UINT64_TYPE__ NU64; 421 | #else 422 | typedef unsigned long long int NU64; 423 | #endif 424 | #endif 425 | 426 | #ifdef NIM_INTBITS 427 | # if NIM_INTBITS == 64 428 | typedef NI64 NI; 429 | typedef NU64 NU; 430 | # elif NIM_INTBITS == 32 431 | typedef NI32 NI; 432 | typedef NU32 NU; 433 | # elif NIM_INTBITS == 16 434 | typedef NI16 NI; 435 | typedef NU16 NU; 436 | # elif NIM_INTBITS == 8 437 | typedef NI8 NI; 438 | typedef NU8 NU; 439 | # else 440 | # error "invalid bit width for int" 441 | # endif 442 | #endif 443 | 444 | // for now there isn't an easy way for C code to reach the program result 445 | // when hot code reloading is ON - users will have to: 446 | // load the nimhcr.dll, get the hcrGetGlobal proc from there and use it 447 | #ifndef NIM_HOT_CODE_RELOADING 448 | extern NI nim_program_result; 449 | #endif 450 | 451 | typedef float NF32; 452 | typedef double NF64; 453 | typedef double NF; 454 | 455 | typedef char NIM_CHAR; 456 | typedef char* NCSTRING; 457 | 458 | #ifdef NIM_BIG_ENDIAN 459 | # define NIM_IMAN 1 460 | #else 461 | # define NIM_IMAN 0 462 | #endif 463 | 464 | #define NIM_STRLIT_FLAG ((NU)(1) << ((NIM_INTBITS) - 2)) /* This has to be the same as system.strlitFlag! */ 465 | 466 | #define STRING_LITERAL(name, str, length) \ 467 | static const struct { \ 468 | TGenericSeq Sup; \ 469 | NIM_CHAR data[(length) + 1]; \ 470 | } name = {{length, (NI) ((NU)length | NIM_STRLIT_FLAG)}, str} 471 | 472 | /* declared size of a sequence/variable length array: */ 473 | #if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER) 474 | # define SEQ_DECL_SIZE /* empty is correct! */ 475 | #else 476 | # define SEQ_DECL_SIZE 1000000 477 | #endif 478 | 479 | #define ALLOC_0(size) calloc(1, size) 480 | #define DL_ALLOC_0(size) dlcalloc(1, size) 481 | 482 | #define paramCount() cmdCount 483 | 484 | // NAN definition copied from math.h included in the Windows SDK version 10.0.14393.0 485 | #ifndef NAN 486 | # ifndef _HUGE_ENUF 487 | # define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow 488 | # endif 489 | # define NAN_INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF)) 490 | # define NAN ((float)(NAN_INFINITY * 0.0F)) 491 | #endif 492 | 493 | #ifndef INF 494 | # ifdef INFINITY 495 | # define INF INFINITY 496 | # elif defined(HUGE_VAL) 497 | # define INF HUGE_VAL 498 | # elif defined(_MSC_VER) 499 | # include 500 | # define INF (DBL_MAX+DBL_MAX) 501 | # else 502 | # define INF (1.0 / 0.0) 503 | # endif 504 | #endif 505 | 506 | typedef struct TFrame_ TFrame; 507 | struct TFrame_ { 508 | TFrame* prev; 509 | NCSTRING procname; 510 | NI line; 511 | NCSTRING filename; 512 | NI16 len; 513 | NI16 calldepth; 514 | NI frameMsgLen; 515 | }; 516 | 517 | #define NIM_POSIX_INIT __attribute__((constructor)) 518 | 519 | #ifdef __GNUC__ 520 | # define NIM_LIKELY(x) __builtin_expect(x, 1) 521 | # define NIM_UNLIKELY(x) __builtin_expect(x, 0) 522 | /* We need the following for the posix wrapper. In particular it will give us 523 | POSIX_SPAWN_USEVFORK: */ 524 | # ifndef _GNU_SOURCE 525 | # define _GNU_SOURCE 526 | # endif 527 | #else 528 | # define NIM_LIKELY(x) (x) 529 | # define NIM_UNLIKELY(x) (x) 530 | #endif 531 | 532 | #if 0 // defined(__GNUC__) || defined(__clang__) 533 | // not needed anymore because the stack marking cares about 534 | // interior pointers now 535 | static inline void GCGuard (void *ptr) { asm volatile ("" :: "X" (ptr)); } 536 | # define GC_GUARD __attribute__ ((cleanup(GCGuard))) 537 | #else 538 | # define GC_GUARD 539 | #endif 540 | 541 | // Test to see if Nim and the C compiler agree on the size of a pointer. 542 | NIM_STATIC_ASSERT(sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8, ""); 543 | 544 | #ifdef USE_NIM_NAMESPACE 545 | } 546 | #endif 547 | 548 | #if defined(_MSC_VER) 549 | # define NIM_ALIGN(x) __declspec(align(x)) 550 | # define NIM_ALIGNOF(x) __alignof(x) 551 | #else 552 | # define NIM_ALIGN(x) __attribute__((aligned(x))) 553 | # define NIM_ALIGNOF(x) __alignof__(x) 554 | #endif 555 | 556 | /* ---------------- platform specific includes ----------------------- */ 557 | 558 | /* VxWorks related includes */ 559 | #if defined(__VXWORKS__) 560 | # include 561 | # include 562 | # include 563 | #elif defined(__FreeBSD__) 564 | # include 565 | #endif 566 | 567 | /* these exist to make the codegen logic simpler */ 568 | #define nimModInt(a, b, res) (((*res) = (a) % (b)), 0) 569 | #define nimModInt64(a, b, res) (((*res) = (a) % (b)), 0) 570 | 571 | #if (!defined(_MSC_VER) || defined(__clang__)) && !defined(NIM_EmulateOverflowChecks) 572 | /* these exist because we cannot have .compilerProcs that are importc'ed 573 | by a different name */ 574 | 575 | #define nimAddInt64(a, b, res) __builtin_saddll_overflow(a, b, (long long int*)res) 576 | #define nimSubInt64(a, b, res) __builtin_ssubll_overflow(a, b, (long long int*)res) 577 | #define nimMulInt64(a, b, res) __builtin_smulll_overflow(a, b, (long long int*)res) 578 | 579 | #if NIM_INTBITS == 32 580 | #define nimAddInt(a, b, res) __builtin_sadd_overflow(a, b, res) 581 | #define nimSubInt(a, b, res) __builtin_ssub_overflow(a, b, res) 582 | #define nimMulInt(a, b, res) __builtin_smul_overflow(a, b, res) 583 | #else 584 | /* map it to the 'long long' variant */ 585 | #define nimAddInt(a, b, res) __builtin_saddll_overflow(a, b, (long long int*)res) 586 | #define nimSubInt(a, b, res) __builtin_ssubll_overflow(a, b, (long long int*)res) 587 | #define nimMulInt(a, b, res) __builtin_smulll_overflow(a, b, (long long int*)res) 588 | #endif 589 | #endif 590 | 591 | #define NIM_NOALIAS __restrict 592 | /* __restrict is said to work for all the C(++) compilers out there that we support */ 593 | 594 | #endif /* NIMBASE_H */ 595 | -------------------------------------------------------------------------------- /app/src/main/java/com/nimviewAndroid/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.nimviewAndroid 2 | 3 | import android.os.Bundle 4 | import android.view.Window 5 | import android.webkit.JsResult 6 | import android.webkit.WebChromeClient 7 | import android.webkit.WebView 8 | import androidx.annotation.NonNull 9 | import androidx.appcompat.app.AppCompatActivity 10 | 11 | 12 | class MainActivity : AppCompatActivity() { 13 | 14 | private lateinit var webView: WebView 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | this.requestWindowFeature(Window.FEATURE_NO_TITLE) 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.activity_main) 19 | this.getSupportActionBar()?.hide() 20 | 21 | webView = findViewById(R.id.webview) 22 | /* webView.webViewClient = object : WebViewClient() { 23 | override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { 24 | if(Uri.parse(url).getHost().isNullOrEmpty()) { 25 | return false; 26 | } 27 | view?.loadUrl(url) 28 | return true 29 | } 30 | }*/ 31 | val settings = webView.getSettings() 32 | settings.setJavaScriptEnabled(true) 33 | settings.setDomStorageEnabled(true) 34 | var nativeCpp = NativeCpp() 35 | webView.addJavascriptInterface(nativeCpp, "backend") 36 | webView.webChromeClient = object : WebChromeClient() { 37 | //Other methods for your WebChromeClient here, if needed.. 38 | override fun onJsAlert( 39 | view: WebView, 40 | url: String, 41 | message: String, 42 | result: JsResult 43 | ): Boolean { 44 | return super.onJsAlert(view, url, message, result) 45 | } 46 | } 47 | nativeCpp.init(webView) 48 | webView.loadUrl("file:///android_asset/index.html"); 49 | 50 | } 51 | 52 | companion object { 53 | // Used to load the 'native-lib' library on application startup. 54 | init { 55 | System.loadLibrary("native-lib") 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/nimviewAndroid/NativeCpp.kt: -------------------------------------------------------------------------------- 1 | package com.nimviewAndroid 2 | import android.webkit.WebView 3 | import org.json.JSONObject 4 | 5 | public class NativeCpp { 6 | private var mAppView: WebView? = null 7 | fun init(appView: WebView?) { 8 | this.mAppView = appView 9 | } 10 | /** 11 | * A native method that is implemented by the 'native-lib' native library, 12 | * which is packaged with this application. 13 | */ 14 | @SuppressWarnings("unused") 15 | external fun callNim(request: String, value: String): String 16 | 17 | @SuppressWarnings("unused") 18 | @android.webkit.JavascriptInterface 19 | fun call(command: String): String { 20 | try { 21 | val jsonMessage = JSONObject(command) 22 | // val responseId = jsonMessage.getInt("responseId") 23 | val request = jsonMessage.getString("request") 24 | var value = jsonMessage.getString("value") 25 | if (value == "") { 26 | value = jsonMessage.getJSONObject("value").toString() 27 | } 28 | var result = this.callNim(request, value) 29 | // val evalJsCode = "window.ui.applyResponse('" + result.replace("\\", "\\\\").replace("\'", "\\'") + "'," + responseId + ");" 30 | // this.mAppView?.evaluateJavascript(evalJsCode, null) 31 | return result // .replace("\\", "\\\\").replace("\'", "\\'") 32 | } 33 | catch (e: Exception) { 34 | // do nothing 35 | } 36 | return "" 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/nim/custom_nimview.nim: -------------------------------------------------------------------------------- 1 | import ../../nimview/src/nimview 2 | import ../../nimview/src/nimview_c 3 | 4 | when not defined(just_core): 5 | import os 6 | # type StdString* {.importcpp: "std::string", header: "string".} = object 7 | 8 | nimview.addRequest("appendSomething", proc (value: string): string = # nimview.addRequest 9 | result = "'" & value & "' modified by Nim Backend") 10 | 11 | proc developStart() = 12 | when not defined(just_core): 13 | let argv = os.commandLineParams() 14 | for arg in argv: 15 | nimview.readAndParseJsonCmdFile(arg) 16 | echo "starting nim" 17 | nimview.start("../../nimview/examples/svelte/public/index.html") 18 | else: 19 | echo "cannot start with -d:just_core" 20 | 21 | when isMainModule: 22 | developStart() -------------------------------------------------------------------------------- /app/src/main/nim/custom_nimview.nimble: -------------------------------------------------------------------------------- 1 | # This specific file is based on https://github.com/yglukhov/nimpy/blob/master/nimpy.nimble 2 | 3 | version = "0.1.0" 4 | author = "Marco Mengelkoch" 5 | description = "Nim / C library to run webview with HTML/JS as UI" 6 | license = "MIT" 7 | 8 | # Dependencies 9 | # you may skip jester, nimpy and webview when compiling with nim c -d:just_core 10 | # alternatively, you still can just skip webkit by compiling with -d:useServer 11 | 12 | # Currently, Webview requires gcc and doesn't work with vcc or clang 13 | 14 | requires "nim >= 0.17.0", "jester >= 0.5.0", "nimpy >= 0.1.1", "webview == 0.1.0", "nake >= 1.9.0" 15 | const application = "custom_nimview" 16 | bin = @[application] 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/nim/nakefile.nim: -------------------------------------------------------------------------------- 1 | import nake 2 | import os, strutils, system 3 | 4 | const application = "custom_nimview" 5 | const uiDir = "../../nimview/examples/svelte" 6 | const mainApp = application & ".nim" 7 | const libraryFile = mainApp 8 | 9 | 10 | let nimbleDir = parentDir(parentDir(os.findExe("nimble"))) 11 | var nimbaseDir = parentDir(nimbleDir) & "/lib" 12 | if (not os.fileExists(nimbaseDir & "/nimbase.h")): 13 | nimbaseDir = parentDir(parentDir(os.findExe("makelink"))) & "/lib" 14 | if (not os.fileExists(nimbaseDir & "/nimbase.h")): 15 | nimbaseDir = parentDir(parentDir(parentDir(parentDir(os.findExe("gcc"))))) & "/lib" 16 | if (not os.fileExists(nimbaseDir & "/nimbase.h")): 17 | nimbaseDir = parentDir(nimbleDir) & "/.choosenim/toolchains/nim-" & system.NimVersion & "/lib" 18 | 19 | # echo "nimbaseDir: " & nimbaseDir 20 | 21 | proc execCmd(command: string) = 22 | echo "running: " & command 23 | doAssert 0 == os.execShellCmd(command) 24 | 25 | proc buildCForArch(cpu, path: string) = 26 | let cppPath = "../cpp" / path 27 | let headerFile = cppPath / application & ".h" 28 | if (headerfile.needsRefresh(mainApp)): 29 | os.removeDir(cppPath) 30 | const stdOptions = "--header:" & application & ".h --app:staticlib -d:just_core -d:noSignalHandler -d:release -d:androidNDK -d:noMain --os:android --threads:on " 31 | execCmd(nimexe & " cpp -c " & stdOptions & "--cpu:" & cpu & " --nimcache:" & cppPath & " " & mainApp) 32 | 33 | proc buildC() = 34 | ## creates python and C/C++ libraries 35 | buildCForArch("arm64", "arm64-v8a") 36 | buildCForArch("arm", "armeabi-v7a") 37 | buildCForArch("i386", "x86") 38 | buildCForArch("amd64", "x86_64") 39 | 40 | proc buildJs() = 41 | var src: seq[string] = @[] 42 | for path in walkDirRec(uiDir / "src"): 43 | src.add(path) 44 | if ((uiDir / "public/build/bundle.js").needsRefresh(src)): 45 | # let oldDir = thisDir() 46 | # cd uiDir 47 | # execCmd("npm install") 48 | # cd oldDir 49 | execCmd("npm run build --prefix " & uiDir) 50 | # cpFile("../../nimview/src/backend-helper.js", uiDir & "/dist/backend-helper.js") 51 | os.removeDir("../assets") 52 | os.createDir("../assets") 53 | os.copyDir(uiDir & "/public", "../assets") 54 | os.copyFile(system.currentSourcePath().parentDir() / "../../nimview/src/backend-helper.js", "../assets/backend-helper.js") 55 | 56 | task "serve", "Serve NPM": 57 | doAssert 0 == os.execShellCmd("npm run serve --prefix " & uiDir) 58 | 59 | 60 | 61 | task defaultTask, "Compiles to C": 62 | buildC() 63 | buildJs() 64 | os.copyFile(nimbaseDir / "nimbase.h", system.currentSourcePath().parentDir() / "../cpp" / "nimbase.h") -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Nimview 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/nimview/.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '34 13 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'javascript', 'python' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | # - name: Autobuild 53 | # uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /app/src/nimview/.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI linux 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the main branch 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This workflow contains a single job called "build" 17 | build: 18 | # The type of runner that the job will run on 19 | runs-on: ubuntu-latest 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v2 24 | - uses: iffy/install-nim@v3 25 | - name: InstallLinuxDependencies 26 | if: runner.os == 'Linux' 27 | run: | 28 | sudo apt-get update && sudo apt install -y gcc npm libwebkit2gtk-4.0-dev curl python3 29 | 30 | - name: InstallMacOSDependencies 31 | if: runner.os == 'macOS' 32 | run: | 33 | brew install gcc python && pip install pathlib 34 | # Runs a set of commands using the runners shell 35 | - name: Test 36 | run: | 37 | nim --version 38 | nimble install -d -y --noSSLCheck --verbose 39 | nimble test -y 40 | 41 | - name: Release 42 | uses: softprops/action-gh-release@v1 43 | if: startsWith(github.ref, 'refs/tags/') 44 | with: 45 | files: | 46 | build/*.exe 47 | build/*.dll 48 | build/*.a 49 | build/*.pyd 50 | build/*.so 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | 54 | # uses: actions/upload-artifact@v2 55 | # with: 56 | # name: binaries 57 | # retention-days: 5 58 | # path: | 59 | -------------------------------------------------------------------------------- /app/src/nimview/.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI MacOS 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the main branch 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: macOS-latest 19 | # Steps represent a sequence of tasks that will be executed as part of the job 20 | steps: 21 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 22 | - uses: actions/checkout@v2 23 | - uses: iffy/install-nim@v3 24 | - name: InstallLinuxDependencies 25 | if: runner.os == 'Linux' 26 | run: | 27 | sudo apt-get update && sudo apt install -y gcc npm libwebkit2gtk-4.0-dev curl python3 28 | 29 | - name: InstallMacOSDependencies 30 | if: runner.os == 'macOS' 31 | run: | 32 | brew install gcc python && pip install pathlib 33 | # Runs a set of commands using the runners shell 34 | - name: Test 35 | run: | 36 | nim --version 37 | nimble install -d -y --noSSLCheck --verbose 38 | nimble test -y 39 | 40 | - name: Release 41 | uses: softprops/action-gh-release@v1 42 | if: startsWith(github.ref, 'refs/tags/') 43 | with: 44 | files: | 45 | build/*.exe 46 | build/*.dll 47 | build/*.a 48 | build/*.pyd 49 | build/*.so 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | 53 | # uses: actions/upload-artifact@v2 54 | # with: 55 | # name: binaries 56 | # retention-days: 5 57 | # path: | 58 | -------------------------------------------------------------------------------- /app/src/nimview/.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI Windows 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the main branch 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: windows-latest 19 | # Steps represent a sequence of tasks that will be executed as part of the job 20 | steps: 21 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 22 | - uses: actions/checkout@v2 23 | - uses: iffy/install-nim@v3 24 | - name: InstallLinuxDependencies 25 | if: runner.os == 'Linux' 26 | run: | 27 | sudo apt-get update && sudo apt install -y gcc npm libwebkit2gtk-4.0-dev curl python3 28 | 29 | - name: InstallMacOSDependencies 30 | if: runner.os == 'macOS' 31 | run: | 32 | brew install gcc python && pip install pathlib 33 | # Runs a set of commands using the runners shell 34 | - name: Test 35 | run: | 36 | nim --version 37 | nimble install -d -y --noSSLCheck --verbose 38 | nimble test -y 39 | 40 | - name: Release 41 | uses: softprops/action-gh-release@v1 42 | if: startsWith(github.ref, 'refs/tags/') 43 | with: 44 | files: | 45 | build/*.exe 46 | build/*.dll 47 | build/*.a 48 | build/*.pyd 49 | build/*.so 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | 53 | # uses: actions/upload-artifact@v2 54 | # with: 55 | # name: binaries 56 | # retention-days: 5 57 | # path: | 58 | -------------------------------------------------------------------------------- /app/src/nimview/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | **/dist 3 | **/node_modules 4 | tmp_c* 5 | **/x64/* 6 | debug.log 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | pnpm-debug.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | *.pdb 26 | *.ipch 27 | *.ipdb 28 | *.iobj 29 | *.ilk 30 | *.user 31 | *.VC.db 32 | *.bak 33 | 34 | # binaries 35 | *.o 36 | *.obj 37 | *.a 38 | *.dll 39 | *.lib 40 | *.so 41 | *.pyd 42 | *.pyc 43 | 44 | # Executables 45 | *.exe 46 | *.app -------------------------------------------------------------------------------- /app/src/nimview/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["ms-vscode.cpptools", "damiankoper.gdb-debug", "kosz78.nim", "xaver.clang-format"] 3 | } -------------------------------------------------------------------------------- /app/src/nimview/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "nim debug (gdb) ", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/nimview_debug.exe", 12 | "args": ["dir"], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [], 16 | "externalConsole": false, 17 | "MIMode": "gdb", 18 | "includePath": ["src/tmp_c"], 19 | // "miDebuggerPath": "C:/nim-1.2.6/dist/mingw64/bin/gdb.exe", 20 | "setupCommands": [ 21 | { 22 | "description": "Automatische Strukturierung und Einrückung für \"gdb\" aktivieren", 23 | "text": "-enable-pretty-printing", 24 | "ignoreFailures": true 25 | } 26 | ], 27 | "preLaunchTask": "nimble debug" 28 | } 29 | 30 | 31 | ] 32 | } -------------------------------------------------------------------------------- /app/src/nimview/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "iostream": "c", 4 | "xlocale": "c", 5 | "xstring": "cpp", 6 | "variant": "cpp", 7 | "nimview.h": "c" 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/nimview/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "2.0.0", 6 | "tasks": [ 7 | { 8 | "label": "nimble debug", 9 | "command": "nimble debug --threads:on --debugger:native", 10 | "options": { 11 | "cwd": "${workspaceRoot}" 12 | }, 13 | "type": "shell", 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /app/src/nimview/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 marcomq 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/nimview/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include nimview.nimble nakefile.nim src/nimview.nim src/backend-helper.js nimview.pyd nimview.so -------------------------------------------------------------------------------- /app/src/nimview/README.md: -------------------------------------------------------------------------------- 1 | # Nimview 2 | ![License](https://img.shields.io/github/license/marcomq/nimview) 3 | [![Build Status](https://github.com/marcomq/nimview/actions/workflows/linux.yml/badge.svg?branch=main)](https://github.com/marcomq/nimview/actions/workflows/linux.yml) 4 | [![Build Status](https://github.com/marcomq/nimview/actions/workflows/windows.yml/badge.svg?branch=main)](https://github.com/marcomq/nimview/actions/workflows/windows.yml) 5 | [![Build Status](https://github.com/marcomq/nimview/actions/workflows/macos.yml/badge.svg?branch=main)](https://github.com/marcomq/nimview/actions/workflows/macos.yml) 6 | 7 | 8 | A lightweight cross platform UI library for Nim, C, C++ or Python. The main purpose is to simplify creation of online / offline applications based on a HTML/CSS/JS layer to be displayed with Webview or a browser. The application can run on cloud and on desktop with the same binary application. 9 | 10 | ## Table of Contents 11 | - [About](#about) 12 | - [Demo binary](#demo-binary) 13 | - [Minimal Python Example](#minimal-python-example) 14 | - [Minimal Nim example](#minimal-nim-example) 15 | - [Javascript and HTML UI](#javascript-and-html-ui) 16 | - [Exchange data with UI](#exchange-data-with-ui) 17 | - [Development workflow](#development-workflow) 18 | - [Why Nim](#why-nim) 19 | - [Which JS framework for UI](#which-js-framework-for-ui) 20 | - [Why not Electron or CEF](#why-not-electron-or-cef) 21 | - [Difference to Eel](#difference-to-eel) 22 | - [Difference to Flask](#difference-to-flask) 23 | - [CSRF and Security](#csrf-and-security) 24 | - [Multithreading](#multithreading) 25 | - [Using UI from existing web-applications](#using-ui-from-existing-web-applications) 26 | - [Setup from source](#setup-from-source) 27 | - [Documentation](#documnentation) 28 | 29 | ## About 30 | 31 | The target of this project was to have a simple, ultra lightweight cross platform, cross programming language UI layer for desktop, cloud and moblile applications. Nimview applications have just a few MB in static executable size and are targeted to be easy to write, easy to read, stable and easy to test. The RAM consumption of basic Nimview applications is usually less than 20 MB. 32 | 33 | This project is mostly a wrapper of two other great Nim projects: [Webview](https://github.com/oskca/webview) and [Jester](https://github.com/dom96/jester) 34 | 35 | While Webview is used to display HTML as a simple desktop window, Jester is used as a webserver to serve the HTML to a browser. Nimview is just an interface to interact with Nim/C/C++/Python code from UI Javascript in the same way for Webview desktop applications and Jester web applications. There is also a specific Android project to interact with android applications [here](https://github.com/marcomq/nimview_android). 36 | 37 | Technically, the UI layer will be completely HTML/CSS/JS based and the back-end should be using either Nim, C/C++ or Python code directly. 38 | Nim mostly acts as a "glue" layer as it can create python and C libraries easily. As long as you write Nim code, you might integrate the code in C/C++, Python or even Android. 39 | The final result should be a binary executable that runs on Linux, Windows or MacOS Desktop and even [Android](https://github.com/marcomq/nimview_android). IOS wasn't tested yet. 40 | 41 | The application later doesn't require a webserver, but it is recommended to use the webserver during development or debug mode - or if there is no desktop environment available. 42 | Make sure to use an additional authentication and some security reverse proxy layer when running on cloud for production. 43 | 44 | Node.js is recommended if you want to build your Javascript UI layer with Svelte/Vue/React or the framework of your choice. 45 | The HTTP server will run in debug mode by default, so you can use all your usual debugging and development tools in Chrome or Firefox. 46 | Webview on its own is a mess if you want to debug your Javascript issues. You might use it for production and testing, but you shouldn't focus Javascript UI development on Webview. 47 | 48 | This project is not intended to have any kind of forms, inputs or any additional helpers to create the UI. 49 | If you need HTML generators or helpers, there are widely used open source frameworks available, for example Vue-Bootstrap (https://bootstrap-vue.org/). 50 | 51 | ## Demo binary 52 | There is a pre-build demo windows x64 binary available that uses a simple Svelte UI. 53 | To make it work, you need to unzip everything before running the binaries. The zip contains two .exe files, 54 | one desktop application and one HTTP server application that can be reached at 55 | http://localhost:8000. 56 | - [demo.zip](https://github.com/marcomq/nimview/files/6236907/demo.zip) 57 | 58 | sha256sum ce6ecfad7d6f7d2610af89b868a69dae8de11a67bd8871d7d97bab9a08ddae9e 59 | 60 | If you want to build this demo from source, you need to run `nake demo` on the 61 | Nimview source folder. 62 | 63 | ## Minimal Python example 64 | The project is available for python via `pip install nimview` and for nim via `nimble install nimview`. 65 | If you just want to display some simple static HTML (or alternatively a jpg), you can run: 66 | 67 | ``` 68 | import nimview 69 | nimview.start("hello_world.html") 70 | ``` 71 | 72 | If you want to actually trigger some server code from a button, you can do following in Python: 73 | 74 | ``` 75 | import nimview 76 | def echoAndModify(value): 77 | print ("From front-end: " + value) 78 | return (value + " appended") 79 | 80 | nimview.addRequest("echoAndModify", echoAndModify) 81 | nimview.start("minimal_ui_sample/index.html") 82 | ``` 83 | 84 | The same in nim: 85 | 86 | ## Minimal Nim example 87 | ``` 88 | import nimview 89 | nimview.addRequest("echoAndModify", proc (value: string): string = 90 | echo "From front-end: " & value 91 | result = "'" & value & "' modified by back-end") 92 | nimview.start("minimal_ui_sample/index.html") 93 | ``` 94 | 95 | These examples will take the "minimal_ui_sample/index.html" file relative to the binary / python file. 96 | It's parent folder "minimal_ui_sample" will act as root "/" for all URLs. 97 | Keep in mind that the webserver will expose all files and subfolders that in the same directory as the index.html. 98 | 99 | ## Javascript and HTML UI 100 | (simlified, using a button, only the important part) 101 | ``` 102 | 103 | 110 | 111 | 112 | ``` 113 | 114 | `window.ui.backend(request, value, callback)` can take up to 3 parameters: 115 | - the first one is the request function that is registered on back-end. 116 | - the second one is the value that is sent to the back-end 117 | - the third value is a callback function. 118 | 119 | An alternative signature, optimized for Vue.js is following: 120 | `window.ui.backend(request, object, key)` 121 | In this case, `object[key]` will be sent to back-end and there is an automated callback that will update `object[key]` with the back-end response. 122 | This is not expected to work with Svelte, as the modification of the object would be hidden for Svelte and doesn't add the reactivity you might expect. 123 | 124 | You need to include your own error handler in the callback, as there is no separate error callback function. 125 | There probably will not be any separate error callback to keep it simple. 126 | 127 | 128 | ## Exchange data with UI 129 | Nimview just has a single string as input and return parameter. It is therefore recommended to use Json to encode your values on the client. 130 | Use a parser on the back-end to read all values and send Json back to the client. By this, you have an unlimited amount of input and output 131 | parameter values. 132 | This is amazingly easy when using python or Nim as back-end. This may also simplify automated testing, as you can store the specific strings as Json and only check specific Json values. 133 | In case you want to use C++ - don't write your own C++ Json parser. Feel free to use https://github.com/nlohmann/json. You might re-use it in other code locations. 134 | 135 | ## Development workflow 136 | You need to compile the back-end and usually the front-end too, when using vue or svelte. While this seems unnecessary complicated in the beginning, 137 | you will have the freedom to only restart the back-end if you have back-end changes and 138 | use some autoreload feature of webpack (vue) and rollit (svelte) for the frontend. 139 | 140 | The setup/install after installing nim would be: 141 | - `nimble install nimview` 142 | - `npm install --prefix ` 143 | 144 | The development workflow would be: 145 | - start your back-end in debug mode with vs code or terminal, run: `nake debug && ./nimview_debug` 146 | - start your frontend npm in autoreload with vs code or terminal, run `npm run dev --prefix ` 147 | - open a browser with url http://localhost:5000 to see the current front-end code result that is served by node.js 148 | - change your front-end code, the page will reload automatically 149 | - change your back-end code and use the debug restart button in vs code when finished 150 | - keep in mind that http://localhost:5000 is only a development url, the Javascript generated for production would be reachable by default at http://localhost:8000 151 | 152 | ### Why Nim 153 | Nim is actually some great "batteries included" helper. It is similar readable as python, has some cool Json / HTTP Server / Webview modules 154 | but creates plain C Code that can be compiled by gcc compilers to optimized machine code. 155 | You can also include C/C++ code as the output of Nim is just plain C. Additionally, it can be compiled to a python library easily. 156 | (https://robert-mcdermott.gitlab.io/posts/speeding-up-python-with-nim/). 157 | 158 | ### Which JS framework for UI 159 | There are many JS frameworks to choose to create responsive user interfaces. 160 | Svelte will create the fastest and best readable front-end code. But it is completely up to you which framework you will choose, as Vue and React have much more plugins and add-ons. 161 | 162 | There is an example for Vue + Bootstrap in tests/vue and one for Svelte in tests/svelte. 163 | I already used to work with React and Redux. I really liked the advantage of using modules and using webpack, 164 | but I didn't like the verbosity of React or writing map-reducers for Redux, so I didn't add an example for React yet. 165 | The main logic is in nimview.nim and backend-helper.js. Make sure to include backend-helper.js either in the static HTML includes. 166 | There is a minimal sample in tests/minimal_sample.nim that doesn't need any additionl JS library. 167 | 168 | ### Why not Electron or CEF 169 | Electron and CEF are great frameworks and both were an inspiration to this helper here. 170 | However, the output binary of electron or CEF is usually more than 100 MB and getting started with a new project can also take some time. 171 | Both CEF and Electron might be great for large Desktop projects that don't need to care about RAM + Disk or that need some additional custom window color, 172 | task symbols or other features. But setting those up and deploying them takes a lot of time, which you can safe by using this helper here. 173 | The binary output of this tool is usually less than 2 MB. If you zip the binary, you even have less than 1 MB for a desktop application with UI. It also might just run in the Cloud as there is an included webserver 174 | - you might easily run the app in Docker. Getting started might just take some minutes and it will consume less RAM, 175 | less system resources and will start much quicker than an Electron or CEF App. 176 | Also, you will have all the included features of nim if you decide to build a C++ Code. 177 | You might write the same Code and the same UI for your Cloud application as for your Desktop App. 178 | 179 | ### Difference to Eel and Neel 180 | There are some cool similar frameworks: The very popular framework [eel](https://github.com/ChrisKnott/Eel) for python 181 | and its cousin [neel](https://github.com/Niminem/Neel) for nim. 182 | There are 2 major differences: 183 | - Both eel and neel make it easy to call back-end side functions from Javascript and also call exposed Javascript from back-end. 184 | This is not any goal here with Nimview. 185 | Nimview will just make it easy to trigger back-end routes from Javascript but will not expose Javascript functions to the back-end side. 186 | If you want to do so, you need to parse the back-end’s response and call the function with this data. 187 | This makes it possible to use multiple HTML / JS user interfaces for the same back-end code without worrying about javascript functions. 188 | - With Nimview, you also don't need a webserver running that might take requests from any other user on localhost. 189 | This improves security and makes it possible to run multiple applications without having port conflicts. 190 | 191 | ### Difference to Flask 192 | [Flask](https://github.com/pallets/flask) is probably the most popular python framework to create micro services and Nimview/Jester probably cannot compete with the completeness of Flask for simple python cloud applications. Nimview for example will not support server side template engines as flask does. 193 | But Nimview is written in Nim and creates static binaries that can run in a minimal tiny Docker container that doesn't need an installed python environment. So you might create containers for your application that have just a few MB. So those deploy and startup much faster than Flask applications. Make sure to avoid building with Webview when creating binaries for Docker by compiling with `-d:useServer`, or you need to include GTK libraries in your container. 194 | 195 | ### CSRF and Security 196 | Nimview was made with security in mind. For the Webview `startDesktop` mode, no network ports are opened to display the UI. The webserver is mostly just for debugging, 197 | so the application doesn't need to be checked for several common attack vectors 198 | of web-applications as long as Webview is used. 199 | 200 | However, if you create a web-application, you need perform most security mitigations by yourself, by middleware or by the javascript framework you are using. 201 | You may check [owasp.org](owasp.org) 202 | Following CSRF protections are already included in Nimview: 203 | - Jester, the webserver of Nimview includes a "SameSite" directive for cookies. 204 | - Nimview stores 5 global random non-session tokens that renew each other every 60 seconds. A valid token is required for any Ajax request except "getGlobalToken". 205 | The token is queried automatically with a "getGlobalToken" request when the application starts. If the token is missing or wrong, there is a "403" error for ajax requests. 206 | 207 | This isn't a full CSRF protection, as the token isn't bound to a session and all users that can read responses from localhost can also use this token and perform an attack. 208 | But together with the "SameSite" directive of Jester, this might already prevent most common CSRF attacks. 209 | The token check can also be disabled with `nimview.setUseGlobalToken(false)` for debugging, 210 | or in case that there is already a session-based CSRF mitigation used by middleware. 211 | 212 | ### Multithreading 213 | Nim has a thread local heap and most variables in Nimview are declared thread local. Check the Nim manual on how to deal with multithreading and sharing data, for example with Channels. 214 | 215 | ### Using UI from existing web-applications 216 | For Desktop applications, it is required to use relative file paths in all your HTML. The paths must point to a directory relative of the binary to the given index html file. 217 | It is not possible to use a subdirectory of the index file. You can also not use a web URL as startpoint, as this must be an existing file. 218 | It is also not recommended to load anything via an URL from any existing internet source, as this could easily cause a security breach. 219 | The Desktop mode is using IE11 as platform, so there might be security issues when loading uncontrolled content from internet. 220 | 221 | ## Setup from source 222 | Nimview is available via nimble after installing nim. 223 | If you don't want to use `nimble install nimview` but build everything from scratch: 224 | ``` 225 | - install nim (https://nim-lang.org/install.html) 226 | avoid white-space in nim install folder name when using windows 227 | add path by running nim "finish" in the nim install directory, so you have nimble available 228 | restart or open new shell to have nimble available 229 | - (linux: yum install gcc webkitgtk4-devel npm apt install gcc libwebkit2gtk-4.0-dev npm) 230 | - (windows: install node > 12.19 (https://nodejs.org/en/download/) 231 | - git clone https://github.com/marcomq/nimview 232 | - cd nimview 233 | - nimble install -d -y 234 | - run "cd examples/svelte", "npm install" and "cd ../.." 235 | - nake debug && ./out/nimview_debug.exe 236 | - (open new console) 237 | - npm run dev --prefix examples/svelte 238 | - (open a browser with localhost:5000) 239 | ``` 240 | 241 | ### Documentation 242 | A documentation is [here](https://htmlpreview.github.io/?https://github.com/marcomq/nimview/blob/master/docs/nimview.html) 243 | -------------------------------------------------------------------------------- /app/src/nimview/bdist_wheel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # nake pyLib 5 | # python bdist_wheel.py bdist_wheel --plat-name win-amd64 6 | # /opt/python/cp37-cp37m/bin/python bdist_wheel.py bdist_wheel --plat-name linux-x86_64 7 | # auditwheel repair --plat manylinux2014_x86_64 dist/nimview-0.*.*-py3-none-linux_x86_64.whl 8 | from setuptools import setup, Distribution 9 | import os 10 | from shutil import copy, rmtree 11 | 12 | this_directory = os.path.abspath(os.path.dirname(__file__)) 13 | targetDir = "nimview" 14 | rmtree(targetDir, ignore_errors=True) 15 | os.makedirs(targetDir, exist_ok=True) 16 | if os.name == 'nt': 17 | fileName = "out/nimview.pyd" 18 | package = ["nimview.pyd"] 19 | else: 20 | fileName = "out/nimview.so" 21 | package = ["nimview.so"] 22 | fullFileName = os.path.join(this_directory, fileName) 23 | if os.path.isfile(fullFileName): 24 | print("copy " + fullFileName + " => " + targetDir) 25 | copy(fullFileName, targetDir) 26 | 27 | with open(targetDir + "/__init__.py", "w") as text_file: 28 | text_file.write("from nimview.nimview import *") 29 | 30 | class BinaryDistribution(Distribution): 31 | """Distribution which always forces a binary package with platform name""" 32 | def has_ext_modules(self): 33 | return False 34 | 35 | setup( 36 | distclass=BinaryDistribution, 37 | package_data={ 38 | "nimview": package 39 | } 40 | ) 41 | 42 | -------------------------------------------------------------------------------- /app/src/nimview/docs/nimview.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | nimview 21 | 22 | 23 | 24 | 25 | 60 | 61 | 62 | 63 |
64 |
65 |

nimview

66 |
67 |
68 |
69 | 73 |     Dark Mode 74 |
75 | 82 |
83 | Search: 85 |
86 |
87 | Group by: 88 | 92 |
93 | 183 | 184 |
185 |
186 |
187 | 188 |

189 |
190 |

Types

191 |
192 | 193 |
ReqUnknownException = object of CatchableError
194 |
195 | 196 | 197 | 198 |
199 | 200 |
201 |
202 |

Vars

203 |
204 | 205 |
useServer = false or (false or fileExists("/.dockerenv"))
206 |
207 | 208 | 209 | 210 |
211 | 212 |
213 |
214 |

Procs

215 |
216 | 217 |
proc enableRequestLogger() {...}{.raises: [Exception, IOError],
218 |                              tags: [RootEffect, ReadDirEffect].}
219 |
220 | 221 | Start to log all requests with content, even passwords, into file "requests.log". The file can be used for automated tests, to archive and replay all actions. 222 | 223 |
224 | 225 |
proc disableRequestLogger() {...}{.raises: [], tags: [].}
226 |
227 | 228 | Will stop to log to "requests.log" (default) 229 | 230 |
231 | 232 |
proc addRequest(request: string;
233 |                 callback: proc (value: string): string {...}{.gcsafe.}) {...}{.
234 |     raises: [Exception], tags: [RootEffect].}
235 |
236 | 237 | This will register a function "callback" that can run on back-end. "addRequest" will be performed with "value" each time the javascript client calls: window.ui.backend(request, value, function(response) {...}) with the specific "request" value. There is a wrapper for python, C and C++ to handle strings in each specific programming language 238 | 239 |
240 | 241 |
proc dispatchRequest(request: string; value: string): string {...}{.
242 |     raises: [Exception, ReqUnknownException], tags: [RootEffect].}
243 |
244 | 245 | Global string dispatcher that will trigger a previously registered functions 246 | 247 |
248 | 249 |
proc dispatchJsonRequest(jsonMessage: JsonNode): string {...}{.
250 |     raises: [KeyError, Exception, ReqUnknownException], tags: [RootEffect].}
251 |
252 | 253 | Global json dispatcher that will be called from webview AND jester This will extract specific values that were prepared by backend-helper.js and forward those values to the string dispatcher. 254 | 255 |
256 | 257 |
proc dispatchCommandLineArg(escapedArgv: string): string {...}{.raises: [Exception],
258 |     tags: [ReadIOEffect, WriteIOEffect, RootEffect].}
259 |
260 | 261 | Will handle previously logged request json and forward those to registered functions. 262 | 263 |
264 | 265 |
proc readAndParseJsonCmdFile(filename: string) {...}{.raises: [Exception, IOError],
266 |     tags: [ReadDirEffect, RootEffect, ReadIOEffect, WriteIOEffect].}
267 |
268 | 269 | Will open, parse a file of previously logged requests and re-runs those requests. 270 | 271 |
272 | 273 |
proc dispatchHttpRequest(jsonMessage: JsonNode; headers: HttpHeaders): string {...}{.
274 |     raises: [KeyError, Exception, ReqUnknownException], tags: [RootEffect].}
275 |
276 | 277 | Modify this, if you want to add some authentication, input format validation or if you want to process HttpHeaders. 278 | 279 |
280 | 281 |
proc startHttpServer(indexHtmlFile: string; port: int = 8000;
282 |                      bindAddr: string = "localhost") {...}{.
283 |     raises: [Exception, OSError, IOError, ValueError],
284 |     tags: [ReadIOEffect, RootEffect, ReadDirEffect, WriteIOEffect, TimeEffect].}
285 |
286 | 287 | Start Http server (Jester) in blocking mode. indexHtmlFile will displayed for "/". Files in parent folder or sub folders may be accessed without further check. Will run forever. 288 | 289 |
290 | 291 |
proc stopDesktop() {...}{.raises: [Exception], tags: [RootEffect].}
292 |
293 | 294 | Will stop the Http server - may trigger application exit. 295 | 296 |
297 | 298 |
proc startDesktop(indexHtmlFile: string; title: string = "nimview";
299 |                   width: int = 640; height: int = 480; resizable: bool = true;
300 |                   debug: bool = defined release) {...}{.raises: [Exception, OSError,
301 |     IOError, KeyError, ValueError, JsonParsingError, ReqUnknownException],
302 |     tags: [ReadIOEffect, RootEffect, ReadDirEffect, WriteIOEffect].}
303 |
304 | 305 | Will start Webview Desktop UI to display the index.hmtl file in blocking mode. 306 | 307 |
308 | 309 |
proc start(indexHtmlFile: string; port: int = 8000;
310 |            bindAddr: string = "localhost"; title: string = "nimview";
311 |            width: int = 640; height: int = 480; resizable: bool = true) {...}{.raises: [
312 |     Exception, OSError, IOError, ValueError, KeyError, JsonParsingError,
313 |     ReqUnknownException],
314 |     tags: [ReadIOEffect, RootEffect, ReadDirEffect, WriteIOEffect, TimeEffect].}
315 |
316 | 317 | Tries to automatically select the Http server in debug mode or when no UI available and the Webview Desktop App in Release mode, if UI available. Notice that this may take the debug mode information of the dll at dll compile timenim. 318 | 319 |
320 | 321 |
322 | 323 |
324 |
325 | 326 |
327 | 332 |
333 |
334 |
335 | 336 | 337 | 338 | -------------------------------------------------------------------------------- /app/src/nimview/examples/__init__.py: -------------------------------------------------------------------------------- 1 | import sys, os, pathlib 2 | # print(pathlib.Path(os.path.abspath(__file__)).parent.parent) 3 | sys.path.append(str(pathlib.Path(os.path.abspath(__file__)).parent.parent) + "/out") 4 | import nimview -------------------------------------------------------------------------------- /app/src/nimview/examples/c_sample.c: -------------------------------------------------------------------------------- 1 | /** Nimview UI Library 2 | * Copyright (C) 2020, 2021, by Marco Mengelkoch 3 | * Licensed under MIT License, see License file for more details 4 | * git clone https://github.com/marcomq/nimview 5 | **/ 6 | // Important Notice: You should use --threads:on AND you need to avoid --gc:arc ; I had crashes on windows otherwise with NIM 1.4 when starting webview 7 | 8 | #include "../out/tmp_c/nimview.h" 9 | #include 10 | #include 11 | 12 | char* echoAndModify(char* something) { 13 | const char* appendString = " modified by C"; 14 | char* result = malloc(strlen(something) + strlen(appendString) + 1); // +1 for the null-terminator, strlen is unchecked! "something" needs 0 termination 15 | if (result) { 16 | strcpy(result, something); // safe, result just created 17 | strcat(result, appendString); // safe, result just created with len 18 | } 19 | else { 20 | return ""; // "" will not be freed 21 | } 22 | return result; 23 | } 24 | int main(int argc, char* argv[]) { 25 | printf(" starting c code\n"); 26 | NimMain(); 27 | nimview_addRequest("echoAndModify", echoAndModify, free); 28 | #ifdef _DEBUG 29 | nimview_startHttpServer("../examples/minimal_ui_sample/index.html", 8000, "localhost"); 30 | #else 31 | nimview_startDesktop("../examples/minimal_ui_sample/index.html", "c_sample", 640, 480, 1, 0); 32 | #endif 33 | } 34 | -------------------------------------------------------------------------------- /app/src/nimview/examples/cpp_sample.cpp: -------------------------------------------------------------------------------- 1 | /** Nimview UI Library 2 | * Copyright (C) 2020, 2021, by Marco Mengelkoch 3 | * Licensed under MIT License, see License file for more details 4 | * git clone https://github.com/marcomq/nimview 5 | **/ 6 | 7 | #include 8 | #include "../src/nimview.hpp" 9 | 10 | std::string echoAndModify(const std::string& something) { 11 | return (std::string(something) + " appended to string"); 12 | } 13 | 14 | std::string echoAndModify2(const std::string& something) { 15 | return (std::string(something) + " appended 2 string"); 16 | } 17 | 18 | int main(int argc, char* argv[]) { 19 | nimview::nimMain(); 20 | nimview::addRequest("echoAndModify", echoAndModify); 21 | nimview::addRequest("echoAndModify2", echoAndModify2); 22 | nimview::addRequest("appendSomething", echoAndModify2); 23 | // nimview::start("../examples/svelte/public/index.html", 8000, "localhost"); 24 | nimview::start("../examples/minimal_ui_sample/index.html", 8000, "localhost"); 25 | } -------------------------------------------------------------------------------- /app/src/nimview/examples/demo.nim: -------------------------------------------------------------------------------- 1 | import os 2 | import ../src/nimview 3 | # start with "nimble svelte" from parent directory 4 | 5 | proc appendSomething(value: string): string = 6 | nimview.enableRequestLogger() # this will skip the first request, but log all further ones 7 | result = "'" & value & "' modified by svelte sample" 8 | 9 | proc main() = 10 | nimview.addRequest("appendSomething", appendSomething) 11 | let argv = os.commandLineParams() 12 | for arg in argv: 13 | nimview.readAndParseJsonCmdFile(arg) 14 | nimview.start("ui/index.html") 15 | 16 | when isMainModule: 17 | main() -------------------------------------------------------------------------------- /app/src/nimview/examples/minimal_sample.nim: -------------------------------------------------------------------------------- 1 | # Nimview UI Library 2 | # Copyright (C) 2020, 2021, by Marco Mengelkoch 3 | # Licensed under MIT License, see License file for more details 4 | # git clone https://github.com/marcomq/nimview 5 | 6 | import ../src/nimview 7 | 8 | nimview.addRequest("echoAndModify", proc (value: string): string = 9 | echo "From Frontend: " & value 10 | result = "'" & value & "' modified by Backend") 11 | nimview.start("../examples/minimal_ui_sample/index.html") -------------------------------------------------------------------------------- /app/src/nimview/examples/minimal_ui_sample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Minimal Example 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/nimview/examples/msvc/minimal.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DebuggableRelease 6 | Win32 7 | 8 | 9 | DebuggableRelease 10 | x64 11 | 12 | 13 | Debug 14 | Win32 15 | 16 | 17 | Release 18 | Win32 19 | 20 | 21 | Debug 22 | x64 23 | 24 | 25 | Release 26 | x64 27 | 28 | 29 | 30 | 31 | 32 | 33 | 16.0 34 | Win32Proj 35 | {2bc9aa2c-3b77-4ac5-b2f8-6ca4c3a2f576} 36 | minimal 37 | 10.0 38 | 39 | 40 | 41 | Application 42 | true 43 | v142 44 | Unicode 45 | 46 | 47 | Application 48 | false 49 | v142 50 | true 51 | Unicode 52 | 53 | 54 | Application 55 | false 56 | v142 57 | true 58 | Unicode 59 | 60 | 61 | Application 62 | true 63 | v142 64 | Unicode 65 | 66 | 67 | Application 68 | false 69 | v142 70 | true 71 | Unicode 72 | 73 | 74 | Application 75 | false 76 | v142 77 | true 78 | Unicode 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | true 106 | C:\Users\Mmengelkoch\.nimble\pkgs\webview-0.1.0\webview;C:\Users\Mmengelkoch\.choosenim\toolchains\nim-1.4.2\lib;$(IncludePath) 107 | 108 | 109 | false 110 | C:\Users\Mmengelkoch\.nimble\pkgs\webview-0.1.0\webview;C:\Users\Mmengelkoch\.choosenim\toolchains\nim-1.4.2\lib;$(IncludePath) 111 | 112 | 113 | false 114 | C:\Users\Mmengelkoch\.nimble\pkgs\webview-0.1.0\webview;C:\Users\Mmengelkoch\.choosenim\toolchains\nim-1.4.2\lib;$(IncludePath) 115 | 116 | 117 | true 118 | $(IncludePath);$(ProjectDir);$(SolutionDir)/../../out/tmp_dll 119 | $(SolutionDir)/../../out;$(LibraryPath) 120 | $(SolutionDir)/../../out 121 | cpp_vs_sample 122 | 123 | 124 | false 125 | $(IncludePath);$(ProjectDir);$(SolutionDir)/../../out/tmp_dll 126 | $(SolutionDir)/../../out;$(LibraryPath) 127 | $(SolutionDir)/../../out 128 | cpp_vs_sample 129 | 130 | 131 | false 132 | $(IncludePath);$(ProjectDir);$(SolutionDir)/../../out/tmp_dll 133 | $(SolutionDir)/../../out;$(LibraryPath) 134 | $(SolutionDir)/../../out 135 | cpp_vs_sample 136 | 137 | 138 | 139 | Level3 140 | true 141 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 142 | true 143 | 144 | 145 | Console 146 | true 147 | 148 | 149 | 150 | 151 | Level3 152 | true 153 | true 154 | true 155 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 156 | true 157 | 158 | 159 | Console 160 | true 161 | true 162 | true 163 | 164 | 165 | 166 | 167 | Level3 168 | true 169 | true 170 | true 171 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 172 | true 173 | 174 | 175 | Console 176 | true 177 | true 178 | true 179 | 180 | 181 | 182 | 183 | Level3 184 | true 185 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 186 | false 187 | MultiThreadedDebug 188 | stdcpp17 189 | stdc17 190 | true 191 | 192 | 193 | Console 194 | true 195 | %(AdditionalLibraryDirectories) 196 | libnimview.a;%(AdditionalDependencies) 197 | $(OutDir)$(TargetName)_debug.exe 198 | 199 | 200 | 201 | 202 | Level3 203 | true 204 | true 205 | true 206 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 207 | false 208 | /Gs999999 %(AdditionalOptions) 209 | true 210 | MultiThreadedDLL 211 | stdcpp17 212 | stdc17 213 | 214 | 215 | Windows 216 | true 217 | true 218 | false 219 | %(AdditionalLibraryDirectories) 220 | libnimview.a;%(AdditionalDependencies) 221 | $(OutDir)$(TargetName).exe 222 | /ENTRY:mainCRTStartup %(AdditionalOptions) 223 | mainCRTStartup 224 | 225 | 226 | true 227 | 228 | 229 | 230 | 231 | Level3 232 | true 233 | true 234 | true 235 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 236 | false 237 | /Gs999999 %(AdditionalOptions) 238 | true 239 | MultiThreadedDLL 240 | stdcpp17 241 | stdc17 242 | 243 | 244 | Console 245 | true 246 | true 247 | true 248 | %(AdditionalLibraryDirectories) 249 | libnimview.a;%(AdditionalDependencies) 250 | $(OutDir)$(TargetName).exe 251 | /ENTRY:mainCRTStartup %(AdditionalOptions) 252 | mainCRTStartup 253 | 254 | 255 | true 256 | 257 | 258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /app/src/nimview/examples/pySample.py: -------------------------------------------------------------------------------- 1 | # Nimview UI Library 2 | # Copyright (C) 2020, 2021, by Marco Mengelkoch 3 | # Licensed under MIT License, see License file for more details 4 | 5 | # pylint: disable=import-error 6 | import __init__, nimview 7 | def echoAndModify(value): 8 | print (value) 9 | return (value + " appended by python") 10 | 11 | nimview.addRequest("echoAndModify", echoAndModify) 12 | nimview.useServer = True 13 | nimview.start("minimal_ui_sample/index.html") # current dir needs to be relative to this -------------------------------------------------------------------------------- /app/src/nimview/examples/svelte.nim: -------------------------------------------------------------------------------- 1 | import os 2 | import ../src/nimview 3 | # start with "nimble svelte" from parent directory 4 | 5 | proc appendSomething(value: string): string {.noSideEffect.} = 6 | result = "'" & value & "' modified by svelte sample" 7 | 8 | proc main() = 9 | nimview.addRequest("appendSomething", appendSomething) 10 | let argv = os.commandLineParams() 11 | for arg in argv: 12 | nimview.readAndParseJsonCmdFile(arg) 13 | nimview.start("examples/svelte/public/index.html") 14 | 15 | when isMainModule: 16 | main() -------------------------------------------------------------------------------- /app/src/nimview/examples/svelte/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /public/build/ 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /app/src/nimview/examples/svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "1.0.0", 4 | "devDependencies": { 5 | "@babel/core": "^7.12.13", 6 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 7 | "@babel/plugin-transform-runtime": "^7.12.15", 8 | "@babel/preset-env": "^7.12.13", 9 | "@babel/runtime": "^7.12.13", 10 | "npm-run-all": "^4.1.5", 11 | "rollup": "^1.10.1", 12 | "rollup-plugin-babel": "^4.4.0", 13 | "rollup-plugin-commonjs": "^10.0.4", 14 | "rollup-plugin-copy": "^3.3.0", 15 | "rollup-plugin-dev": "^1.1.3", 16 | "rollup-plugin-json": "^4.0.0", 17 | "rollup-plugin-livereload": "^1.0.0", 18 | "rollup-plugin-node-resolve": "^4.2.3", 19 | "rollup-plugin-svelte": "^6.1.1", 20 | "rollup-plugin-terser": "^7.0.2", 21 | "sirv-cli": "^1.0.0", 22 | "svelte": "^3.0.0" 23 | }, 24 | "scripts": { 25 | "build": "rollup -c", 26 | "autobuild": "rollup -c -w", 27 | "dev": "run-p start:dev autobuild", 28 | "start": "sirv public", 29 | "start:dev": "sirv public --dev" 30 | }, 31 | "dependencies": { 32 | "bootstrap": "^4.6.0", 33 | "core-js": "3", 34 | "d3-dsv": "^2.0.0", 35 | "jquery": "^3.5.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/nimview/examples/svelte/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/nimview/examples/svelte/public/favicon.png -------------------------------------------------------------------------------- /app/src/nimview/examples/svelte/public/global.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/nimview/examples/svelte/public/global.css -------------------------------------------------------------------------------- /app/src/nimview/examples/svelte/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/nimview/examples/svelte/rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte' 2 | import resolve from 'rollup-plugin-node-resolve' 3 | import commonjs from 'rollup-plugin-commonjs' 4 | import livereload from 'rollup-plugin-livereload' 5 | import { terser } from 'rollup-plugin-terser' 6 | import dev from 'rollup-plugin-dev' 7 | import json from 'rollup-plugin-json' 8 | import babel from 'rollup-plugin-babel' 9 | import copy from 'rollup-plugin-copy' 10 | 11 | 12 | const production = !process.env.ROLLUP_WATCH 13 | 14 | export default { 15 | input: 'src/main.js', 16 | output: { 17 | sourcemap: true, 18 | format: 'iife', 19 | name: 'app', 20 | file: 'public/build/bundle.js' 21 | }, 22 | plugins: [ 23 | svelte({ 24 | // enable run-time checks when not in production 25 | dev: !production, 26 | // we'll extract any component CSS out into 27 | // a separate file - better for performance 28 | css: css => { 29 | css.write('bundle.css') 30 | } 31 | }), 32 | 33 | // If you have external dependencies installed from 34 | // npm, you'll most likely need these plugins. In 35 | // some cases you'll need additional configuration 36 | // consult the documentation for details: 37 | // https://github.com/rollup/rollup-plugin-commonjs 38 | resolve(), 39 | commonjs(), 40 | // added by angelo 41 | json(), 42 | 43 | // Watch the `public` directory and refresh the 44 | // browser on changes when not in production 45 | !production && livereload('public'), 46 | !production && dev({ 47 | dirs: ['public'], 48 | port: 5000, 49 | proxy: { 50 | '*': 'localhost:8000', 51 | '/*': 'localhost:8000/', 52 | } 53 | }), 54 | copy({ 55 | targets: [{ 56 | src: ['node_modules/bootstrap/dist/js/*.min.*', 'node_modules/bootstrap/dist/css/*.min.*'], 57 | dest: 'public/vendor/bootstrap' 58 | },{ 59 | src: ['node_modules/jquery/dist/*.min.*'], 60 | dest: 'public/vendor/jquery' 61 | }], 62 | copyOnce: true 63 | }), 64 | 65 | // added by angelo 66 | // compile to good old IE11 compatible ES5 67 | babel({ 68 | extensions: [ '.js', '.mjs', '.html', '.svelte' ], 69 | runtimeHelpers: true, 70 | exclude: [ 'node_modules/@babel/**', 'node_modules/core-js/**' ], 71 | presets: [ 72 | [ 73 | '@babel/preset-env', 74 | { 75 | targets: { 76 | ie: '11' 77 | }, 78 | useBuiltIns: 'usage', 79 | corejs: 3 80 | } 81 | ] 82 | ], 83 | plugins: [ 84 | '@babel/plugin-syntax-dynamic-import', 85 | [ 86 | '@babel/plugin-transform-runtime', 87 | { 88 | useESModules: true 89 | } 90 | ] 91 | ] 92 | }), 93 | 94 | // If we're building for production (npm run build 95 | // instead of npm run dev), minify 96 | production && terser() 97 | ], 98 | watch: { 99 | clearScreen: false 100 | } 101 | } -------------------------------------------------------------------------------- /app/src/nimview/examples/svelte/src/App.svelte: -------------------------------------------------------------------------------- 1 | 5 |
6 |
7 | 8 | 9 |
10 |
-------------------------------------------------------------------------------- /app/src/nimview/examples/svelte/src/components/Navbar.svelte: -------------------------------------------------------------------------------- 1 |
2 | 27 |
-------------------------------------------------------------------------------- /app/src/nimview/examples/svelte/src/components/Sample.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 |
  • 14 |
    15 | 16 | 17 |
    18 |
  • 19 |
    20 | {#if ((typeof elements !== "undefined") && (elements.length > 0))} 21 | {#each elements as el} 22 |
    23 | {el.text} 24 |
    25 | {/each} 26 | {:else} 27 |

    No data available yet

    28 | {/if} 29 |
    30 |
    31 | 32 | -------------------------------------------------------------------------------- /app/src/nimview/examples/svelte/src/main.js: -------------------------------------------------------------------------------- 1 | 2 | import App from './App.svelte'; 3 | 4 | const app = new App({ 5 | target: document.body, 6 | }); 7 | 8 | export default app; -------------------------------------------------------------------------------- /app/src/nimview/examples/vue.nim: -------------------------------------------------------------------------------- 1 | import os 2 | import ../src/nimview 3 | # start with "nimble vue" from parent directory 4 | proc appendSomething(value: string): string {.noSideEffect.} = 5 | result = "'" & value & "' modified by vue sample" 6 | 7 | proc main() = 8 | nimview.addRequest("appendSomething", appendSomething) 9 | let argv = os.commandLineParams() 10 | for arg in argv: 11 | nimview.readAndParseJsonCmdFile(arg) 12 | nimview.start("examples/vue/dist/index.html") 13 | 14 | when isMainModule: 15 | main() -------------------------------------------------------------------------------- /app/src/nimview/examples/vue/README.md: -------------------------------------------------------------------------------- 1 | ## build this using following commands 2 | npm install 3 | npm run build 4 | 5 | The output will be written to the "dist" folder -------------------------------------------------------------------------------- /app/src/nimview/examples/vue/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /app/src/nimview/examples/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nimview-bootstrapvue", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "bootstrap": "^4.6.0", 13 | "bootstrap-vue": "^2.21.2", 14 | "core-js": "^3.10.0", 15 | "vue": "^2.6.11" 16 | }, 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "^4.5.12", 19 | "@vue/cli-plugin-eslint": "^4.5.12", 20 | "@vue/cli-service": "^4.5.12", 21 | "babel-eslint": "^10.1.0", 22 | "eslint": "^6.7.2", 23 | "eslint-plugin-vue": "^6.2.2", 24 | "vue-template-compiler": "^2.6.11" 25 | }, 26 | "eslintConfig": { 27 | "root": true, 28 | "env": { 29 | "node": true 30 | }, 31 | "extends": [ 32 | "plugin:vue/essential", 33 | "eslint:recommended" 34 | ], 35 | "parserOptions": { 36 | "parser": "babel-eslint" 37 | }, 38 | "rules": {} 39 | }, 40 | "browserslist": [ 41 | "> 1%", 42 | "last 2 versions", 43 | "not dead" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /app/src/nimview/examples/vue/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/nimview/examples/vue/public/favicon.ico -------------------------------------------------------------------------------- /app/src/nimview/examples/vue/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= htmlWebpackPlugin.options.title %> 10 | 11 | 12 | 15 |
    16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/nimview/examples/vue/src/App.vue: -------------------------------------------------------------------------------- 1 | //App.vue 2 | 8 | -------------------------------------------------------------------------------- /app/src/nimview/examples/vue/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomq/nimview_android/70015384c679345fa3bcb548a58e6c4dd9ff190d/app/src/nimview/examples/vue/src/assets/logo.png -------------------------------------------------------------------------------- /app/src/nimview/examples/vue/src/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | //src/components/Navbar.vue 2 | -------------------------------------------------------------------------------- /app/src/nimview/examples/vue/src/components/Sample.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/nimview/examples/vue/src/main.js: -------------------------------------------------------------------------------- 1 | /** Nimview UI Library 2 | * Copyright (C) 2020, 2021, by Marco Mengelkoch 3 | * Licensed under MIT License, see License file for more details 4 | * git clone https://github.com/marcomq/nimview 5 | **/ 6 | import Vue from 'vue' 7 | import App from './App.vue' 8 | import BootstrapVue from 'bootstrap-vue' 9 | 10 | import 'bootstrap/dist/css/bootstrap.css' 11 | import 'bootstrap-vue/dist/bootstrap-vue.css' 12 | // backend-helper.js is loaded via HTML tag 13 | 14 | Vue.use(BootstrapVue) 15 | Vue.config.productionTip = false 16 | 17 | 18 | // make this.backend() available for Vue 19 | Vue.mixin({ 20 | methods: { 21 | alert: str => window.ui.alert(str + ""), 22 | backend: function(request, data, callBackOrKey) { 23 | window.ui.backend(request, data , callBackOrKey); 24 | // alert('called nim'); 25 | }, 26 | } 27 | }) 28 | 29 | 30 | new Vue({ 31 | render: h => h(App), 32 | }).$mount('#app') 33 | -------------------------------------------------------------------------------- /app/src/nimview/examples/vue/vue.config.js: -------------------------------------------------------------------------------- 1 | // vue.config.js 2 | module.exports = { 3 | publicPath: '.', 4 | filenameHashing: false, 5 | devServer: { 6 | port: 5000, 7 | proxy: 'http://127.0.0.1:8000' 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/nimview/nakefile.nim: -------------------------------------------------------------------------------- 1 | import nake 2 | import os, strutils, system 3 | 4 | let application = "nimview" 5 | let srcDir = "src" 6 | let vueDir = "examples/vue" 7 | let svelteDir = "examples/svelte" 8 | let mainApp = srcDir / application & ".nim" 9 | let libraryFile = srcDir / application & "_c.nim" 10 | let srcFiles = [mainApp, libraryFile] 11 | let buildDir = "out" 12 | let thisDir = system.currentSourcePath().parentDir() 13 | 14 | os.createDir buildDir 15 | 16 | var nimbleDir = parentDir(parentDir(os.findExe("nimble"))) 17 | var nimbaseDir = parentDir(nimbleDir) & "/lib" 18 | if (not os.fileExists(nimbaseDir & "/nimbase.h")): 19 | nimbaseDir = parentDir(parentDir(os.findExe("makelink"))) & "/lib" 20 | if (not os.fileExists(nimbaseDir & "/nimbase.h")): 21 | nimbaseDir = parentDir(parentDir(parentDir(parentDir(os.findExe("gcc"))))) & "/lib" 22 | if (not os.fileExists(nimbaseDir & "/nimbase.h")): 23 | nimbaseDir = parentDir(nimbleDir) & "/.choosenim/toolchains/nim-" & system.NimVersion & "/lib" 24 | 25 | const webviewlLibs = when defined(windows): 26 | "-lole32 -lcomctl32 -loleaut32 -luuid -lgdi32" 27 | elif defined(macosx): 28 | "-framework Cocoa -framework WebKit" 29 | else: 30 | system.staticExec("pkg-config --libs gtk+-3.0 webkit2gtk-4.0") & " -ldl" 31 | 32 | const webviewIncludes = when defined(windows): 33 | "-DWEBVIEW_WINAPI=1 -mno-ms-bitfields -DWIN32_LEAN_AND_MEAN " 34 | elif defined(macosx): 35 | "-DWEBVIEW_COCOA=1 -x objective-c" 36 | else: 37 | "-DWEBVIEW_GTK=1 " & staticExec("pkg-config --cflags gtk+-3.0 webkit2gtk-4.0") 38 | 39 | proc execCmd(command: string) = 40 | echo "running: " & command 41 | doAssert 0 == os.execShellCmd(command) 42 | 43 | proc execNim(command: string) = 44 | echo "running: nim " & command 45 | execCmd nimexe & " " & command 46 | 47 | proc buildPyLib() = 48 | ## creates python lib 49 | let pyDllExtension = when defined(windows): "pyd" else: "so" 50 | let outputLib = buildDir / application & "." & pyDllExtension 51 | if outputLib.needsRefresh(srcFiles): 52 | os.removeDir(buildDir / "tmp_py") 53 | execNim "c -d:release -d:useStdLib -d:noMain --nimcache=./" & buildDir & "/tmp_py --out:" & outputLib & 54 | " --app:lib " & " " & mainApp & " " # creates python lib, header file not usable 55 | 56 | proc buildLibs() = 57 | ## C/C++ libraries 58 | buildPyLib() 59 | let cDllExtension = when defined(windows): "dll" else: "c.so" 60 | let headerFile = thisDir / buildDir / "tmp_dll" / application & ".h" 61 | let outputLib = buildDir / application & "." & cDllExtension 62 | if headerFile.needsRefresh(srcFiles): 63 | os.removeDir(buildDir / "tmp_dll") 64 | execNim "c --passC:-fpic -d:release -d:useStdLib --noMain:on -d:noMain --nimcache=./" & buildDir & "/tmp_dll" & 65 | " --app:lib --noLinking:on --header:" & application & ".h --compileOnly:off " & " " & libraryFile # creates header and compiled .o files 66 | os.copyFile(headerFile, thisDir / srcDir / application & ".h") 67 | 68 | let minGwSymbols = when defined(windows): 69 | " -Wl,--out-implib," & buildDir & "/lib" & application & 70 | ".a -Wl,--export-all-symbols -Wl,--enable-auto-import -Wl,--whole-archive " & buildDir & "/tmp_dll/*.o -Wl,--no-whole-archive " 71 | elif defined(linux): 72 | " -Wl,--out-implib," & buildDir & "/lib" & application & ".a -Wl,--whole-archive " & buildDir & "/tmp_dll/*.o -Wl,--no-whole-archive " 73 | else: 74 | " " & buildDir & "/tmp_dll/*.o " 75 | execCmd "gcc -shared -o " & outputLib & " -I" & buildDir & "/tmp_dll/" & " " & minGwSymbols & webviewlLibs # generate .dll and .a 76 | echo "Python and shared C libraries build completed. Files have been created in build folder." 77 | 78 | proc buildRelease() = 79 | execNim "c --app:gui -d:release -d:useStdLib --out:" & buildDir / application & " " & " " & mainApp 80 | 81 | proc buildDebug() = 82 | execNim "c --verbosity:2 --app:console -d:debug --debuginfo --debugger:native --out:" & buildDir / application & "_debug " & " " & mainApp 83 | 84 | 85 | proc buildCSample() = 86 | execCmd "gcc -c -w -o " & buildDir & "/tmp_o/c_sample.o -fmax-errors=3 -DWEBVIEW_STATIC -DWEBVIEW_IMPLEMENTATION -O3 -fno-strict-aliasing -fno-ident " & 87 | webviewIncludes & " -I" & nimbaseDir & " -I" & nimbleDir & "/pkgs/webview-0.1.0/webview -I. -I" & buildDir & "/tmp_c examples/c_sample.c" 88 | execCmd "gcc -w -o " & buildDir & "/c_sample.exe " & buildDir & "/tmp_c/*.o " & buildDir & "/tmp_o/c_sample.o " & webviewlLibs 89 | 90 | proc buildCppSample() = 91 | execCmd "g++ -c -w -std=c++17 -o " & buildDir & "/tmp_o/cpp_sample.o -fmax-errors=3 -DWEBVIEW_STATIC -DWEBVIEW_IMPLEMENTATION -O3 -fno-strict-aliasing -fno-ident " & 92 | webviewIncludes & " -I" & nimbaseDir & " -I" & nimbleDir & "/pkgs/webview-0.1.0/webview -I. -I" & buildDir & "/tmp_c examples/cpp_sample.cpp" 93 | execCmd "g++ -w -o " & buildDir & "/cpp_sample.exe " & buildDir & "/tmp_c/*.o " & buildDir & "/tmp_o/cpp_sample.o " & webviewlLibs 94 | 95 | proc buildCTest() = 96 | execCmd "gcc -c -w -o " & buildDir & "/tmp_o/c_test.o -fmax-errors=3 -DWEBVIEW_STATIC -DWEBVIEW_IMPLEMENTATION -O3 -fno-strict-aliasing -fno-ident " & 97 | webviewIncludes & " -I" & nimbaseDir & " -I" & nimbleDir & "/pkgs/webview-0.1.0/webview -I. -I" & buildDir & "/tmp_c tests/c_test.c" 98 | execCmd "gcc -w -o " & buildDir & "/c_test.exe " & buildDir & "/tmp_c/*.o " & buildDir & "/tmp_o/c_test.o " & webviewlLibs 99 | 100 | proc buildGenericObjects() = 101 | os.removeDir(buildDir / "tmp_o") 102 | os.createDir(buildDir / "tmp_o") 103 | let headerFile = thisDir / buildDir / "tmp_c" / application & ".h" 104 | if headerFile.needsRefresh(srcFiles): 105 | os.removeDir(buildDir / "tmp_c") 106 | execNim "c -d:release -d:useStdLib --noMain:on -d:noMain --noLinking --header:" & application & ".h --nimcache=./" & buildDir & 107 | "/tmp_c --app:staticLib --out:" & buildDir / application & " " & " " & libraryFile 108 | 109 | proc runTests() = 110 | buildLibs() 111 | buildGenericObjects() 112 | buildCSample() 113 | if not defined(macosx): 114 | buildCppSample() 115 | buildCTest() 116 | execCmd os.getCurrentDir() / buildDir / "c_test.exe" 117 | execCmd "python tests/pyTest.py" 118 | execCmd "nimble install -y" 119 | 120 | proc generateDocs() = 121 | execNim "doc -d:useStdLib -o:docs/" & application & ".html " & mainApp 122 | 123 | task "libs", "Build Libs": 124 | buildLibs() 125 | 126 | task "pyLib", "Build python lib": 127 | buildPyLib() 128 | 129 | task "dev", "Serve NPM": 130 | execCmd("npm run dev --prefix " & svelteDir) 131 | 132 | task "debug", "Build " & application & " debug": 133 | buildDebug() 134 | # exec "./" & application & "_debug & npm run serve --prefix " & uiDir 135 | 136 | task "svelte", "build svelte example in release mode": 137 | execCmd "npm run build --prefix " & svelteDir 138 | execNim "c -r --app:gui -d:release -d:useStdLib --out:svelte.exe examples/svelte.nim" 139 | 140 | task "demo", "build svelte example in release mode": 141 | os.removeDir(buildDir / "demo") 142 | os.createDir(buildDir / "demo/ui") 143 | execCmd "npm run build --prefix " & svelteDir 144 | os.copyDir(svelteDir / "public", buildDir / "demo/ui") 145 | execNim "c --app:console -d:useServer -d:release -d:useStdLib --out:" & buildDir / "demo/server_demo.exe examples/demo.nim" 146 | execNim "c -r --app:gui -d:release -d:useStdLib --out:" & buildDir / "demo/desktop_demo.exe examples/demo.nim" 147 | 148 | task "vue", "build vue example in release mode": 149 | execCmd "npm run build --prefix " & vueDir 150 | execNim "c -r --app:gui -d:release -d:useStdLib --out:vue.exe examples/svelte.nim" 151 | 152 | task "release", "Build npm and Run with webview": 153 | buildRelease() 154 | 155 | task "docs", "Generate doc": 156 | generateDocs() 157 | 158 | task "test", "Run tests": 159 | runTests() 160 | echo "all tests passed" 161 | # generateDocs() 162 | # execCmd "npm run build --prefix " & svelteDir -------------------------------------------------------------------------------- /app/src/nimview/nimview.nimble: -------------------------------------------------------------------------------- 1 | version = "0.1.2" 2 | author = "Marco Mengelkoch" 3 | description = "Nim / Python / C library to run webview with HTML/JS as UI" 4 | license = "MIT" 5 | bin = @["nimview"] 6 | srcDir = "src" 7 | 8 | import os, strutils 9 | # Dependencies 10 | # you may skip jester, nimpy and webview when compiling with nim c -d:just_core 11 | # Currently, Webview requires gcc and doesn't work with vcc or clang 12 | 13 | when system.NimMinor > 2: 14 | requires "nim >= 1.0.0", "jester >= 0.5.0", "nimpy >= 0.1.1", "webview == 0.1.0", "nake >= 1.9.0" 15 | else: 16 | echo "####-----------------------------------------------------####" 17 | echo "You probably need to run " 18 | echo "'sudo apt install libwebkit2gtk-4.0-dev'" 19 | echo "'nimble install jester && nimble install nimpy && nimble install webview@0.1.0'" 20 | echo "first. Older nimble versions didn't install dependencies." 21 | echo "Ignore this text if these packages already have been installed." 22 | echo "####-----------------------------------------------------####" 23 | requires "nim >= 0.17.0", "jester >= 0.5.0", "nimpy >= 0.1.1", "webview >= 0.1.0", "nake >= 1.9.0" 24 | 25 | when defined(nimdistros): 26 | import distros 27 | # no foreignDep required for Windows 28 | if detectOs(Ubuntu): 29 | foreignDep "libwebkit2gtk-4.0-dev" 30 | elif detectOs(CentOS) or detectOs(RedHat) or detectOs(Fedora): 31 | foreignDep "webkitgtk4-devel" 32 | if not detectOs(Windows): 33 | echo "In case of trouble, you may need to install following dependencies:" 34 | echo "" 35 | echoForeignDeps() 36 | echo "" 37 | else: 38 | echo "no nimdistros" 39 | 40 | task test, "Run tests": 41 | let nake = system.findExe("nake") 42 | exec nake & " test" -------------------------------------------------------------------------------- /app/src/nimview/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = nimview 3 | version = 0.1.2 4 | author = Marco Mengelkoch 5 | author_email = MMengelkoch@gmx.de 6 | description = A lightwight cross platform UI library for Nim, C, C++ or Python. The main purpose is to simplify creation of Desktop applications based on a HTML/CSS/JS layer that is displayed with Webview. 7 | long_description = file: README.md, LICENSE 8 | long_description_content_type = text/markdown 9 | keywords = nim, user-interface, webview, html, css, javascript, http-server 10 | url = https://github.com/marcomq/nimview 11 | license = MIT 12 | classifiers = 13 | Development Status :: 4 - Beta 14 | Natural Language :: English 15 | Operating System :: POSIX :: Linux 16 | Operating System :: Microsoft :: Windows 17 | Operating System :: MacOS :: MacOS X 18 | Environment :: Console 19 | Environment :: Other Environment 20 | Intended Audience :: Developers 21 | Programming Language :: Python 22 | Programming Language :: Python :: 3 23 | Programming Language :: Python :: 3 :: Only 24 | Programming Language :: Python :: 3.7 25 | Programming Language :: Python :: 3.8 26 | Programming Language :: Python :: 3.9 27 | Topic :: Software Development 28 | License :: OSI Approved :: MIT License 29 | 30 | [options] 31 | packages = nimview -------------------------------------------------------------------------------- /app/src/nimview/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # python setup.py sdist 5 | from setuptools import setup, Extension 6 | from setuptools.command.build_ext import build_ext 7 | from subprocess import check_call 8 | import os 9 | from shutil import copy, rmtree 10 | 11 | this_directory = os.path.abspath(os.path.dirname(__file__)) 12 | targetDir = "nimview" 13 | 14 | # create another nimview subfolder as setup.py is much friendlier if you do so 15 | rmtree(targetDir, ignore_errors=True) 16 | os.makedirs(targetDir, exist_ok=True) 17 | os.makedirs(targetDir + "/src", exist_ok=True) 18 | srcFiles = [ "src/nimview.nim", "src/backend-helper.js", "nimview.nimble", "nakefile.nim", "LICENSE", "README.md"] 19 | for index, fileName in enumerate(srcFiles): 20 | fullFileName = os.path.join(this_directory, fileName) 21 | if os.path.isfile(fullFileName): 22 | copy(fullFileName, targetDir + "/" + fileName) 23 | 24 | 25 | class NimExtension(Extension): 26 | def __init__(self, name, sourcedir=''): 27 | Extension.__init__(self, name, sources=[]) 28 | self.sourcedir = os.path.abspath(sourcedir) 29 | 30 | class NimBuild(build_ext): 31 | def run(self): 32 | for ext in self.extensions: 33 | self.build_extension(ext) 34 | 35 | def build_extension(self, ext): 36 | print("=> build_extension") 37 | os.makedirs(self.build_temp, exist_ok=True) 38 | os.makedirs(self.build_temp + "/src", exist_ok=True) 39 | 40 | extdir = self.get_ext_fullpath(ext.name) 41 | os.makedirs(extdir + "/src", exist_ok=True) 42 | 43 | for fileName in srcFiles: 44 | fullFileName = os.path.join(targetDir, fileName) 45 | if os.path.isfile(fullFileName): 46 | target = self.build_temp + "/" + fileName 47 | print("copy " + fullFileName + " => " + target) 48 | copy(fullFileName, target) 49 | 50 | check_call(['nimble', 'install', '-dy'], cwd=self.build_temp) 51 | print("=> dependencies installed") 52 | check_call(['nake', 'pyLib'], cwd=self.build_temp, shell=True) 53 | print("=> pyLib created") 54 | libFiles = [ "out/nimview.so", "out/nimview.pyd"] 55 | install_target = os.path.abspath(os.path.dirname(extdir)) 56 | os.makedirs(install_target + "/src", exist_ok=True) 57 | 58 | for fileName in libFiles: 59 | fullFileName = os.path.join(self.build_temp, fileName) 60 | if os.path.isfile(fullFileName): 61 | print("copy " + fullFileName + " => " + install_target) 62 | copy(fullFileName, install_target) 63 | 64 | setup( 65 | ext_modules=[NimExtension('.')], 66 | cmdclass={ 67 | 'build_ext': NimBuild, 68 | }, 69 | package_data={ 70 | "nimview": srcFiles + ["nimview.so", "nimview.pyd"] 71 | }, 72 | install_requires=[ 73 | "choosenim_install" # Auto-installs Nim compiler 74 | ] 75 | ) 76 | -------------------------------------------------------------------------------- /app/src/nimview/src/backend-helper.js: -------------------------------------------------------------------------------- 1 | // This file is supposed to be copied automatically to the UI folder, if it doesn't exists there yet 2 | 3 | let ui = {}; 4 | let defaultPostTarget = ""; 5 | let host = ""; // might cause "cors" errors if defined 6 | ui.responseStorage = {}; 7 | ui.responseCounter = 0; 8 | /*** 9 | * Generalized request pre-processing 10 | * Creates a standartized json object to be sent to server and also stores an internal object to handle the response 11 | * 12 | * request: will be sent to server as json ".request" 13 | * data: can be either following: 14 | * 1. a normal value, could be even json, which is then transmitted normally 15 | * 2. a json object, which will require the "callbackFunction" to be the key and a non-function value. This will automatically create a callback that sets data[key] = response 16 | * 3. a function, in case you don't need to send data 17 | * callbackFunction: will be a generalized callback for success and error. Will have the backend response as parameter. You will need to handle error and success manually. 18 | ***/ 19 | ui.createRequest = function(request, data, callbackFunction) { 20 | var key = request; 21 | switch (typeof data) { 22 | case 'object': 23 | if ((typeof callbackFunction !== 'undefined') && 24 | (typeof callbackFunction !== 'function') && 25 | (callbackFunction in data)) { 26 | var key = callbackFunction; 27 | var outputValueObj = data; 28 | callbackFunction = function(response) { outputValueObj[key] = response; }; 29 | data = data[key]; 30 | } 31 | else { 32 | data = JSON.stringify(data); 33 | } 34 | break; 35 | case 'function': 36 | callbackFunction = data; 37 | data = ''; 38 | break; 39 | case 'undefined': 40 | data = ''; 41 | break; 42 | default: 43 | data = '' + data; 44 | break; 45 | } 46 | if (ui.responseCounter >= Number.MAX_SAFE_INTEGER-1) { 47 | ui.responseCounter = 0; 48 | } 49 | var storageIndex = ui.responseCounter++; 50 | ui.responseStorage[storageIndex] = new Object( 51 | {'request': request, 'responseId': storageIndex, 'callbackFunction': callbackFunction} 52 | ); 53 | var jsonRequest = {'request': request, 'value': data, 'responseId': storageIndex, 'key': key}; 54 | return jsonRequest; 55 | }; 56 | 57 | /*** 58 | * Generalized request post-processing 59 | * Maps the previous requestId to an object and applies the (async) response to this object 60 | ***/ 61 | ui.applyResponse = function(value, responseId) { 62 | var storedObject = ui.responseStorage[responseId]; 63 | var result; 64 | if (typeof storedObject.callbackFunction === 'function') { 65 | result = storedObject.callbackFunction(value); 66 | } 67 | delete ui.responseStorage[responseId]; 68 | return result 69 | }; 70 | 71 | /*global backend*/ 72 | ui.alert = function (str) { 73 | if (typeof backend === 'undefined') { 74 | alert(str); 75 | } 76 | else { 77 | backend.alert(str); 78 | } 79 | } 80 | /*** 81 | * Send something to backend. Will automatically chose webview if available. * 82 | * request: will be sent to server as json ".request" 83 | * data: can be either following: 84 | * 1. a normal value, could be even json, which is then transmitted normally 85 | * 2. a json object, which will require the "callbackFunction" to be the key and a non-function value. 86 | * This will automatically create a callback that sets data[key] = response 87 | * 3. a function, in case you don't need to send data 88 | * callbackFunction: will be a generalized callback for success and error. Will have the backend response as parameter. 89 | * You will need to handle error and success manually. 90 | ***/ 91 | ui.backend = function (request, data, callbackFunction) { 92 | var jsonRequest = { responseId: 0 }; 93 | if (typeof backend === 'undefined') { 94 | // Simulate running in Webview, but using a HTTP server 95 | // It will not be possible to use MS Edge for debugging, as this has similar identifiers as Webview on Windows 96 | // query server with HTTP instead of calling webview callback 97 | jsonRequest = ui.createRequest(request, data, callbackFunction); 98 | var postData = JSON.stringify(jsonRequest); 99 | if (defaultPostTarget == "") { 100 | var url = request; // Not required. Just for easier debugging 101 | } 102 | else { 103 | var url = defaultPostTarget; 104 | } 105 | var responseHandler = function(response) { 106 | var key = jsonRequest.key; 107 | if ((typeof response === "object") && (key in response)) { 108 | ui.applyResponse(response[key], jsonRequest.responseId); 109 | } 110 | else { 111 | ui.applyResponse(response, jsonRequest.responseId); 112 | } 113 | 114 | }; 115 | if (typeof fetch !== "undefined") { // chrome and other modern browsers 116 | var opts = { 117 | method: 'POST', // always use AJAX post for simplicity with special chars 118 | mode: 'cors', 119 | cache: 'no-cache', 120 | headers: {'Content-Type': 'application/json'}, 121 | body: postData 122 | }; 123 | if (ui.globalToken && (ui.globalToken.length > 0)) { 124 | opts.headers["global-token"] = ui.globalToken; 125 | } 126 | fetch(host + "/" + url, opts).then(function(response) { 127 | if (response) { 128 | var globalToken = response.headers.get("global-token"); 129 | if (globalToken && (globalToken.length > 0)) { 130 | ui.globalToken = globalToken; 131 | ui.lastToken = Date.now(); 132 | } 133 | if (response.json) { 134 | return response.json(); 135 | } 136 | } 137 | return response; 138 | }).then(responseHandler).catch(function(err) { 139 | if (console && console.log) { 140 | console.log(err); 141 | } 142 | }); 143 | } 144 | else { // IE11 145 | let xhr = new XMLHttpRequest(); 146 | xhr.open('POST', host + "/" + url, true); 147 | if (ui.globalToken && (ui.globalToken.length > 0)) { 148 | xhr.setRequestHeader("global-token", ui.globalToken); 149 | } 150 | xhr.responseType = "json"; 151 | xhr.onreadystatechange = function() { 152 | if (xhr.readyState !== 4) { 153 | return; // not finished yet, check next state 154 | } 155 | if (xhr.status === 200) { 156 | // request successful - show response 157 | var response = xhr.response; 158 | var globalToken = xhr.getResponseHeader("global-token"); 159 | if (globalToken && (globalToken.length > 0)) { 160 | ui.globalToken = globalToken; 161 | } 162 | if (typeof response === "string") { 163 | responseHandler(JSON.parse(response)); 164 | } 165 | else { 166 | responseHandler(response); 167 | } 168 | } 169 | else { 170 | // request error 171 | if (console && console.log) { 172 | console.log('HTTP error', xhr.status, xhr.statusText); 173 | } 174 | } 175 | }; 176 | xhr.send(postData); 177 | } 178 | } 179 | else { 180 | // This part of the function is intendend to interact directly with webview. No HTTP server involved. 181 | // There will be an async call on the backend server, which is then triggering to call javascript from webview. 182 | // This callback function will be stored in a container ui.responseStorage. Nim Webview had issues calling javascript on Windows 183 | // at the time of development and therefore required an async approach that doesn't use the async keyword and can't await. 184 | jsonRequest = ui.createRequest(request, data, callbackFunction); 185 | var response = backend.call(JSON.stringify(jsonRequest)); 186 | if (typeof response !== "undefined") { 187 | var key = jsonRequest.key; 188 | if ((typeof response === "object") && (key in response)) { 189 | ui.applyResponse(response[key], jsonRequest.responseId); 190 | } 191 | else { 192 | ui.applyResponse(response, jsonRequest.responseId); 193 | } 194 | } 195 | 196 | } 197 | } 198 | // "import from" doesn't seem to work with webview here... So add this as global variable 199 | ui.getGlobalToken = function() { 200 | if (typeof backend === 'undefined') { 201 | if ((typeof ui.lastToken === 'undefined') || 202 | (ui.lastToken + (60 * 1000) <= Date.now())) { 203 | ui.backend("getGlobalToken"); 204 | } 205 | } 206 | }; 207 | window.setTimeout(ui.getGlobalToken, 150); 208 | window.setInterval(ui.getGlobalToken, 60 * 1000); 209 | window.ui = ui; -------------------------------------------------------------------------------- /app/src/nimview/src/globalToken.nim: -------------------------------------------------------------------------------- 1 | # Nimview UI Library 2 | # Copyright (C) 2021, by Marco Mengelkoch 3 | # Licensed under MIT License, see License file for more details 4 | # git clone https://github.com/marcomq/nimview 5 | 6 | import times, jester, std/sysrand, base64, locks 7 | 8 | var L: Lock 9 | initLock(L) 10 | # generate 5 tokens that rotate 11 | var tokens: array[5, tuple[ 12 | token: array[32, byte], 13 | generated: times.DateTime]] 14 | 15 | proc checkIfTokenExists(token: array[32, byte]): bool = 16 | # Very unlikely, but it may be necessary to also lock here 17 | for i in 0 ..< globalToken.tokens.len: 18 | if token == globalToken.tokens[i].token: 19 | return true 20 | return false 21 | 22 | proc byteToString*(token: array[32, byte]): string = 23 | result = base64.encode(token) 24 | 25 | proc stringToByte*(token: string): array[32, byte] = 26 | let tokenString = base64.decode(token) 27 | if (tokenString.len > 31): 28 | system.copyMem(result[0].addr, tokenString[0].unsafeAddr, 32) 29 | else: 30 | raise newException(CatchableError, "token too short") 31 | 32 | proc checkToken*(headers: HttpHeaders): bool = 33 | var headerToken: string 34 | if headers.hasKey("global-token"): 35 | headerToken = $headers["global-token"] 36 | if headerToken.len > 31: 37 | var headerTokenArray = globalToken.stringToByte(headerToken) 38 | return globalToken.checkIfTokenExists(headerTokenArray) 39 | return false 40 | 41 | proc getFreshToken*(): array[32, byte] = 42 | var currentTime = times.now() 43 | const interval = 60 44 | let frame = (currentTime.minute * 60 + currentTime.second).div(interval) mod 5 # a new token every interval seconds 45 | var currentToken = addr globalToken.tokens[frame] 46 | var tokenPlusInterval = currentTime - interval.seconds 47 | try: 48 | tokenPlusInterval = currentToken[].generated + interval.seconds 49 | except: 50 | discard 51 | withLock(L): 52 | if tokenPlusInterval < currentTime: 53 | let randomValue = sysrand.urandom(32) 54 | for i in 0 ..< randomValue.len: 55 | result[i] = randomValue[i] 56 | currentToken[].generated.swap(currentTime) 57 | currentToken[].token.swap(result) 58 | result = currentToken[].token 59 | -------------------------------------------------------------------------------- /app/src/nimview/src/nimview.hpp: -------------------------------------------------------------------------------- 1 | /** Nimview UI Library 2 | * Copyright (C) 2020, 2021, by Marco Mengelkoch 3 | * Licensed under MIT License, see License file for more details 4 | * git clone https://github.com/marcomq/nimview 5 | **/ 6 | #pragma once 7 | #ifndef NIMVIEW_CUSTOM_LIB 8 | extern "C" { 9 | #include "nimview.h" 10 | } 11 | #endif 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #ifdef _MSC_VER 18 | #include 19 | #endif 20 | #define addRequest(a,b) addRequestImpl<__COUNTER__>(a,b) 21 | 22 | typedef void (*requestFunction)(const char*); 23 | 24 | template 25 | union FunctionStorage { 26 | FunctionStorage() {}; 27 | std::decay_t lambdaFunction; 28 | ~FunctionStorage() {}; 29 | }; 30 | 31 | 32 | template 33 | auto FunctionPointerWrapper(Lambda&& callback, Result(*)(Args...)) { 34 | static FunctionStorage storage; 35 | using type = decltype(storage.lambdaFunction); 36 | 37 | static bool used = false; 38 | if (used) { 39 | storage.lambdaFunction.~type(); // overwrite 40 | } 41 | new (&storage.lambdaFunction) type(std::forward(callback)); 42 | used = true; 43 | 44 | return [](Args... args)->Result { 45 | return Result(storage.lambdaFunction(std::forward(args)...)); 46 | }; 47 | } 48 | 49 | template 50 | Fn* castToFunction(Lambda&& memberFunction) { 51 | return FunctionPointerWrapper(std::forward(memberFunction), (Fn*)nullptr); 52 | } 53 | 54 | namespace nimview { 55 | thread_local bool nimInitialized = false; 56 | auto nimview_nimMain = NimMain; 57 | void nimMain() { 58 | if (!nimInitialized) { 59 | nimview_nimMain(); 60 | nimInitialized = true; 61 | } 62 | }; 63 | template 64 | void addRequestImpl(const std::string& request, const std::function &callback) { 65 | nimMain(); 66 | auto lambda = [&, callback](char* input) { 67 | std::string result = callback(input); 68 | if (result == "") { 69 | return const_cast(""); // "" will not be freed 70 | } 71 | else { 72 | char* newChars = static_cast(calloc(result.length() + 1, 1)); 73 | result.copy(newChars, result.length()); 74 | return newChars; 75 | } 76 | }; 77 | auto cFunc = castToFunction(lambda); 78 | nimview_addRequest(const_cast(request.c_str()), cFunc, free); 79 | } 80 | 81 | #ifndef JUST_CORE 82 | void start(const char* folder, int port = 8000, const char* bindAddr = "localhost", const char* title = "nimview", int width = 640, int height = 480, bool resizable = true) { 83 | nimMain(); 84 | #ifdef _WIN32 85 | bool runWithGui = true; 86 | #else 87 | bool runWithGui = (NULL != getenv("DISPLAY")); 88 | #endif 89 | #ifdef _DEBUG 90 | runWithGui = false; 91 | #endif 92 | if (runWithGui) { 93 | nimview_startDesktop(const_cast(folder), const_cast(title), width, height, resizable, false); 94 | } 95 | else { 96 | nimview_startHttpServer(const_cast(folder), port, const_cast(bindAddr)); 97 | } 98 | } 99 | void startDesktop(const char* folder, const char* title = "nimview", int width = 640, int height = 480, bool resizable = true, bool debug = false) { 100 | nimMain(); 101 | nimview_startDesktop(const_cast(folder), const_cast(title), width, height, resizable, debug); 102 | }; 103 | void startHttpServer(const char* folder, int port = 8000, const char* bindAddr = "localhost") { 104 | nimMain(); 105 | nimview_startHttpServer(const_cast(folder), port, const_cast(bindAddr)); 106 | }; 107 | #endif 108 | char* dispatchRequest(char* request, char* value) { 109 | nimMain(); 110 | return nimview_dispatchRequest(request, value); 111 | }; 112 | std::string dispatchRequest(const std::string &request, const std::string &value) { 113 | nimMain(); 114 | // free of return value should be performed by nim gc 115 | return nimview_dispatchRequest(const_cast(request.c_str()), const_cast(value.c_str())); 116 | }; 117 | auto dispatchCommandLineArg = nimview_dispatchCommandLineArg; 118 | auto readAndParseJsonCmdFile = nimview_readAndParseJsonCmdFile; 119 | 120 | } 121 | -------------------------------------------------------------------------------- /app/src/nimview/src/nimview.nim: -------------------------------------------------------------------------------- 1 | # Nimview UI Library 2 | # Copyright (C) 2021, by Marco Mengelkoch 3 | # Licensed under MIT License, see License file for more details 4 | # git clone https://github.com/marcomq/nimview 5 | 6 | import os, system, tables 7 | import json, logging 8 | 9 | # run "nimble release" or "nimble debug" to compile 10 | 11 | when not defined(just_core): 12 | const compileWithWebview = defined(useWebview) or not defined(useServer) 13 | import strutils, uri 14 | import nimpy 15 | import jester 16 | import globalToken 17 | # import browsers 18 | when compileWithWebview: 19 | import webview except debug 20 | var myWebView: Webview 21 | var responseHttpHeader {.threadVar.}: seq[tuple[key, val: string]] # will be set when starting Jester 22 | else: 23 | const compileWithWebview = false 24 | # Just core features. Disable jester, webview nimpy and exportpy 25 | macro exportpy(def: untyped): untyped = 26 | result = def 27 | 28 | type ReqUnknownException* = object of CatchableError 29 | type ReqDeniedException* = object of CatchableError 30 | 31 | var reqMap {.threadVar.}: Table[string, proc(value: string): string {.gcsafe.}] 32 | var requestLogger {.threadVar.}: FileLogger 33 | var useServer* = not compileWithWebview or 34 | (defined(useServer) or defined(debug) or (os.fileExists("/.dockerenv"))) 35 | var useGlobalToken* = true 36 | 37 | proc setUseServer*(val: bool) {.exportpy.} = 38 | useServer = val 39 | 40 | proc setUseGlobalToken*(val: bool) {.exportpy.} = 41 | useGlobalToken = val 42 | 43 | logging.addHandler(newConsoleLogger()) 44 | 45 | proc enableRequestLogger*() {.exportpy.} = 46 | ## Start to log all requests with content, even passwords, into file "requests.log". 47 | ## The file can be used for automated tests, to archive and replay all actions. 48 | if nimview.requestLogger.isNil: 49 | debug "creating request logger, further requests will be logged to file and flushed at application end" 50 | if not os.fileExists("requests.log"): 51 | var createFile = system.open("requests.log", system.fmWrite) 52 | createFile.close() 53 | var requestLoggerTmp = newFileLogger("requests.log", fmtStr = "") 54 | 55 | nimview.requestLogger.swap(requestLoggerTmp) 56 | nimview.requestLogger.levelThreshold = logging.lvlAll 57 | 58 | proc disableRequestLogger*() {.exportpy.} = 59 | ## Will stop to log to "requests.log" (default) 60 | if not requestLogger.isNil: 61 | requestLogger.levelThreshold = logging.lvlNone 62 | 63 | proc addRequest*(request: string, callback: proc(value: string): string {.gcsafe.}) {.exportpy.} = 64 | ## This will register a function "callback" that can run on back-end. 65 | ## "addRequest" will be performed with "value" each time the javascript client calls: 66 | ## `window.ui.backend(request, value, function(response) {...})` 67 | ## with the specific "request" value. 68 | ## There is a wrapper for python, C and C++ to handle strings in each specific programming language 69 | nimview.reqMap[request] = callback 70 | 71 | proc dispatchRequest*(request: string, value: string): string {.exportpy.} = 72 | ## Global string dispatcher that will trigger a previously registered functions 73 | nimview.reqMap.withValue(request, callbackFunc) do: # if request available, run request callback 74 | result = callbackFunc[](value) 75 | do: 76 | raise newException(ReqUnknownException, "404 - Request unknown") 77 | 78 | proc dispatchJsonRequest*(jsonMessage: JsonNode): string = 79 | ## Global json dispatcher that will be called from webview AND jester 80 | ## This will extract specific values that were prepared by backend-helper.js 81 | ## and forward those values to the string dispatcher. 82 | let request = $jsonMessage["request"].getStr() 83 | if request == "getGlobalToken": 84 | return 85 | var value = $jsonMessage["value"].getStr() 86 | if (value == ""): 87 | value = $jsonMessage["value"] 88 | if not requestLogger.isNil: 89 | requestLogger.log(logging.lvlInfo, $jsonMessage) 90 | result = dispatchRequest(request, value) 91 | 92 | proc dispatchCommandLineArg*(escapedArgv: string): string {.exportpy.} = 93 | ## Will handle previously logged request json and forward those to registered functions. 94 | try: 95 | let jsonMessage = parseJson(escapedArgv) 96 | result = dispatchJsonRequest(jsonMessage) 97 | except ReqUnknownException: 98 | warn "Request is unknown in " & escapedArgv 99 | except: 100 | warn "Couldn't parse specific line arg: " & escapedArgv 101 | 102 | proc readAndParseJsonCmdFile*(filename: string) {.exportpy.} = 103 | ## Will open, parse a file of previously logged requests and re-runs those requests. 104 | if (os.fileExists(filename)): 105 | debug "opening file for parsing: " & filename 106 | let file = system.open(filename, system.FileMode.fmRead) 107 | var line: TaintedString 108 | while (file.readLine(line)): 109 | # TODO: escape line if source file cannot be trusted 110 | let retVal = nimview.dispatchCommandLineArg(line.string) 111 | debug retVal 112 | close(file) 113 | else: 114 | logging.error "File does not exist: " & filename 115 | 116 | when not defined(just_core): 117 | when defined release: 118 | const backendHelperJs = system.staticRead("backend-helper.js") 119 | else: 120 | const backendHelperJsStatic = system.staticRead("backend-helper.js") 121 | var backendHelperJs {.threadVar.}: string 122 | 123 | proc dispatchHttpRequest*(jsonMessage: JsonNode, headers: HttpHeaders): string = 124 | ## Modify this, if you want to add some authentication, input format validation 125 | ## or if you want to process HttpHeaders. 126 | if not nimview.useGlobalToken or globalToken.checkToken(headers): 127 | return dispatchJsonRequest(jsonMessage) 128 | else: 129 | let request = $jsonMessage["request"].getStr() 130 | if request != "getGlobalToken": 131 | raise newException(ReqDeniedException, "403 - Token expired") 132 | 133 | template respond(code: untyped, header: untyped, message: untyped): untyped = 134 | mixin resp 135 | jester.resp code, header, message 136 | 137 | proc handleRequest(request: Request): Future[ResponseData] {.async.} = 138 | ## used by HttpServer 139 | block route: 140 | var response: string 141 | var requestPath: string = request.pathInfo 142 | var resultId = 0 143 | case requestPath 144 | of "/backend-helper.js": 145 | var header = @{"Content-Type": "application/javascript"} 146 | header.add(nimview.responseHttpHeader) 147 | respond Http200, header, nimview.backendHelperJs 148 | else: 149 | try: 150 | let separatorFound = requestPath.rfind({'#', '?'}) 151 | if separatorFound != -1: 152 | requestPath = requestPath[0 ..< separatorFound] 153 | if (requestPath == "/"): 154 | requestPath = "/index.html" 155 | 156 | var potentialFilename = request.getStaticDir() & "/" & 157 | requestPath.replace("..", "") 158 | if os.fileExists(potentialFilename): 159 | debug "Sending " & potentialFilename 160 | # jester.sendFile(potentialFilename) 161 | let fileData = splitFile(potentialFilename) 162 | let contentType = case fileData.ext: 163 | of ".json": "application/json;charset=utf-8" 164 | of ".js": "text/javascript;charset=utf-8" 165 | of ".css": "text/css;charset=utf-8" 166 | of ".jpg": "image/jpeg" 167 | of ".txt": "text/plain;charset=utf-8" 168 | of ".map": "application/octet-stream" 169 | else: "text/html;charset=utf-8" 170 | var header = @{"Content-Type": contentType} 171 | header.add(nimview.responseHttpHeader) 172 | respond Http200, header, system.readFile(potentialFilename) 173 | else: 174 | if (request.body == ""): 175 | raise newException(ReqUnknownException, "404 - File not found") 176 | 177 | # if not a file, assume this is a json request 178 | var jsonMessage: JsonNode 179 | debug request.body 180 | if unlikely(request.body == ""): 181 | jsonMessage = parseJson(uri.decodeUrl(requestPath)) 182 | else: 183 | jsonMessage = parseJson(request.body) 184 | resultId = jsonMessage["responseId"].getInt() 185 | {.gcsafe.}: 186 | var currentToken = globalToken.byteToString(globalToken.getFreshToken()) 187 | response = dispatchHttpRequest(jsonMessage, request.headers) 188 | let jsonResponse = %* { ($jsonMessage["key"]).unescape(): response} 189 | var header = @{"Global-Token": currentToken} 190 | respond Http200, header, $jsonResponse 191 | 192 | except ReqUnknownException: 193 | respond Http404, nimview.responseHttpHeader, $ %* {"error": "404", 194 | "value": getCurrentExceptionMsg(), "resultId": resultId} 195 | 196 | except ReqDeniedException: 197 | respond Http403, nimview.responseHttpHeader, $ %* {"error": "403", 198 | "value": getCurrentExceptionMsg(), "resultId": resultId} 199 | except: 200 | respond Http500, nimview.responseHttpHeader, $ %* {"error": "500", 201 | "value": "request doesn't contain valid json", 202 | "resultId": resultId} 203 | 204 | proc getCurrentAppDir(): string = 205 | let applicationName = os.getAppFilename().extractFilename() 206 | debug applicationName 207 | if (applicationName.startsWith("python") or applicationName.startsWith("platform-python")): 208 | result = os.getCurrentDir() 209 | else: 210 | result = os.getAppDir() 211 | 212 | proc copyBackendHelper (indexHtml: string) = 213 | let folder = indexHtml.parentDir() 214 | let targetJs = folder / "backend-helper.js" 215 | try: 216 | if not os.fileExists(targetJs) and indexHtml.endsWith(".html"): 217 | # read index html file and check if it actually requires backend helper 218 | let indexHtmlContent = system.readFile(indexHtml) 219 | if indexHtmlContent.contains("backend-helper.js"): 220 | let sourceJs = nimview.getCurrentAppDir() / "../src/backend-helper.js" 221 | if (not os.fileExists(sourceJs) or ((system.hostOS == "windows") and defined(debug))): 222 | debug "writing to " & targetJs 223 | if nimview.backendHelperJs != "": 224 | system.writeFile(targetJs, nimview.backendHelperJs) 225 | elif (os.fileExists(sourceJs)): 226 | debug "symlinking to " & targetJs 227 | os.createSymlink(sourceJs, targetJs) 228 | except: 229 | logging.error "backend-helper.js not copied" 230 | 231 | proc getAbsPath(indexHtmlFile: string): (string, string) = 232 | let separatorFound = indexHtmlFile.rfind({'#', '?'}) 233 | if separatorFound == -1: 234 | result[0] = indexHtmlFile 235 | else: 236 | result[0] = indexHtmlFile[0 ..< separatorFound] 237 | result[1] = indexHtmlFile[separatorFound .. ^1] 238 | if (not os.isAbsolute(result[0])): 239 | result[0] = nimview.getCurrentAppDir() / indexHtmlFile 240 | 241 | proc checkFileExists(filePath: string, message: string) = 242 | if not os.fileExists(filePath): 243 | raise newException(IOError, message) 244 | 245 | proc startHttpServer*(indexHtmlFile: string, port: int = 8000, 246 | bindAddr: string = "localhost") {.exportpy.} = 247 | ## Start Http server (Jester) in blocking mode. indexHtmlFile will displayed for "/". 248 | ## Files in parent folder or sub folders may be accessed without further check. Will run forever. 249 | var (indexHtmlPath, parameter) = nimview.getAbsPath(indexHtmlFile) 250 | discard parameter # needs to be inserted into url manually 251 | nimview.checkFileExists(indexHtmlPath, "Required file index.html not found at " & indexHtmlPath & 252 | "; cannot start UI; the UI folder needs to be relative to the binary") 253 | when not defined release: 254 | nimview.backendHelperJs = nimview.backendHelperJsStatic 255 | try: 256 | nimview.backendHelperJs = system.readFile(nimview.getCurrentAppDir() / "../src/backend-helper.js") 257 | except: 258 | discard 259 | nimview.copyBackendHelper(indexHtmlPath) 260 | var origin = "http://" & bindAddr 261 | if (bindAddr == "0.0.0.0"): 262 | origin = "*" 263 | nimview.responseHttpHeader = @{"Access-Control-Allow-Origin": origin} 264 | let settings = jester.newSettings( 265 | port = Port(port), 266 | bindAddr = bindAddr, 267 | staticDir = indexHtmlPath.parentDir()) 268 | var myJester = jester.initJester(nimview.handleRequest, settings = settings) 269 | # debug "open default browser" 270 | # browsers.openDefaultBrowser("http://" & bindAddr & ":" & $port / parameter) 271 | myJester.serve() 272 | 273 | proc stopDesktop*() {.exportpy.} = 274 | ## Will stop the Http server - may trigger application exit. 275 | when compileWithWebview: 276 | debug "stopping ..." 277 | if not myWebView.isNil(): 278 | myWebView.terminate() 279 | 280 | proc startDesktop*(indexHtmlFile: string, title: string = "nimview", 281 | width: int = 640, height: int = 480, resizable: bool = true, 282 | debug: bool = defined release) {.exportpy.} = 283 | ## Will start Webview Desktop UI to display the index.hmtl file in blocking mode. 284 | when compileWithWebview: 285 | var (indexHtmlPath, parameter) = nimview.getAbsPath(indexHtmlFile) 286 | nimview.checkFileExists(indexHtmlPath, "Required file index.html not found at " & indexHtmlPath & 287 | "; cannot start UI; the UI folder needs to be relative to the binary") 288 | nimview.copyBackendHelper(indexHtmlPath) 289 | # var fullScreen = true 290 | myWebView = webview.newWebView(title, "file://" / indexHtmlPath & parameter, width, 291 | height, resizable = resizable, debug = debug) 292 | myWebView.bindProc("backend", "alert", proc (message: string) = 293 | {.gcsafe.}: 294 | myWebView.info("alert", message)) 295 | myWebView.bindProc("backend", "call", proc (message: string) = 296 | info message 297 | let jsonMessage = json.parseJson(message) 298 | let resonseId = jsonMessage["responseId"].getInt() 299 | let response = dispatchJsonRequest(jsonMessage) 300 | let evalJsCode = "window.ui.applyResponse('" & 301 | response.replace("\\", "\\\\").replace("\'", "\\'") & 302 | "'," & $resonseId & ");" 303 | {.gcsafe.}: 304 | let responseCode = myWebView.eval(evalJsCode) 305 | discard responseCode 306 | ) 307 | #[ proc changeColor() = myWebView.setColor(210,210,210,100) 308 | proc toggleFullScreen() = fullScreen = not myWebView.setFullscreen(fullScreen) ]# 309 | myWebView.run() 310 | myWebView.exit() 311 | dealloc(myWebView) 312 | 313 | proc start*(indexHtmlFile: string, port: int = 8000, bindAddr: string = "localhost", title: string = "nimview", 314 | width: int = 640, height: int = 480, resizable: bool = true) {.exportpy.} = 315 | ## Tries to automatically select the Http server in debug mode or when no UI available 316 | ## and the Webview Desktop App in Release mode, if UI available. 317 | ## The debug mode information will not be available for python or dll. 318 | let displayAvailable = 319 | when (system.hostOS == "windows"): true 320 | else: ( os.getEnv("DISPLAY") != "") 321 | if useServer or not displayAvailable: 322 | startHttpServer(indexHtmlFile, port, bindAddr) 323 | else: 324 | startDesktop(indexHtmlFile, title, width, height, resizable) 325 | 326 | proc main() = 327 | when not defined(noMain): 328 | debug "starting nim main" 329 | when system.appType != "lib" and not defined(just_core): 330 | nimview.addRequest("appendSomething", proc (value: string): string = 331 | result = "'" & value & "' modified by Nim Backend") 332 | 333 | let argv = os.commandLineParams() 334 | for arg in argv: 335 | nimview.readAndParseJsonCmdFile(arg) 336 | # let indexHtmlFile = "../examples/vue/dist/index.html" 337 | let indexHtmlFile = "../examples/svelte/public/index.html" 338 | nimview.enableRequestLogger() 339 | # nimview.startDesktop(indexHtmlFile) 340 | # nimview.startHttpServer(indexHtmlFile) 341 | nimview.startHttpServer(indexHtmlFile) 342 | 343 | when isMainModule: 344 | main() 345 | -------------------------------------------------------------------------------- /app/src/nimview/src/nimview_c.nim: -------------------------------------------------------------------------------- 1 | # Nimview UI Library 2 | # Copyright (C) 2020, 2021, by Marco Mengelkoch 3 | # Licensed under MIT License, see License file for more details 4 | # git clone https://github.com/marcomq/nimview 5 | 6 | import logging 7 | 8 | import nimview 9 | export nimview 10 | # in case you need to create your own C library with custom code, 11 | # just add import nimview_c to your custom module 12 | 13 | proc free_c(somePtr: pointer) {.cdecl, importc: "free".} 14 | 15 | proc nimview_addRequest*(request: cstring, callback: proc( 16 | value: cstring): cstring {.cdecl.}, 17 | freeFunc: proc(value: pointer) {.cdecl.} = free_c) {.exportc.} = 18 | nimview.addRequest($request, proc (nvalue: string): string = 19 | {.gcsafe.}: # we need to assume that the c function is gc safe 20 | debug "calling nim from c interface with: " & nvalue 21 | var resultPtr: cstring = "" 22 | try: 23 | resultPtr = callback(nvalue) 24 | result = $resultPtr 25 | finally: 26 | if (resultPtr != ""): 27 | freeFunc(resultPtr) 28 | ) 29 | 30 | proc nimview_dispatchRequest*(request, value: cstring): cstring {.exportc.} = 31 | result = $nimview.dispatchRequest($request, $value) 32 | 33 | proc nimview_dispatchCommandLineArg*(escapedArgv: cstring): cstring {.exportc.} = 34 | result = $nimview.dispatchCommandLineArg($escapedArgv) 35 | 36 | proc nimview_readAndParseJsonCmdFile*(filename: cstring) {.exportc.} = 37 | nimview.readAndParseJsonCmdFile($filename) 38 | 39 | when not defined(just_core): 40 | 41 | proc nimview_startHttpServer*(folder: cstring, port: cint = 8000, 42 | bindAddr: cstring = "localhost") {.exportc.} = 43 | nimview.startHttpServer($folder, int(port), $bindAddr) 44 | 45 | proc nimview_startDesktop*(folder: cstring, title: cstring = "nimview", 46 | width: cint = 640, height: cint = 480, resizable: bool = true, 47 | debug: bool = false) {.exportc.} = 48 | # NimMain() 49 | debug "starting C webview" 50 | nimview.startDesktop($folder, $title, width, height, resizable, debug) 51 | debug "leaving C webview" 52 | 53 | proc nimview_stopDesktop*() {.exportc.} = nimview.stopDesktop() 54 | 55 | proc nimview_start*(folder: cstring, port: cint = 8000, 56 | bindAddr: cstring = "localhost", title: cstring = "nimview", 57 | width: cint = 640, height: cint = 480, resizable: bool = true) {.exportc.} = 58 | nimview.start($folder, port, $bindAddr, $title, width, height, resizable) 59 | -------------------------------------------------------------------------------- /app/src/nimview/src/std/readme.md: -------------------------------------------------------------------------------- 1 | This specific file sysrand.nim is part of Nim 1.6 source code 2 | https://github.com/nim-lang/Nim/blob/devel/lib/std/sysrand.nim 3 | 4 | It was copied here as Nim 1.6 was not available yet at time of writing. 5 | It will be removed later when Nim 1.6 is widely available and stable. -------------------------------------------------------------------------------- /app/src/nimview/src/std/sysrand.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Nim's Runtime Library 4 | # (c) Copyright 2021 Nim contributors 5 | # 6 | # See the file "copying.txt", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | ## .. warning:: This module was added in Nim 1.6. If you are using it for cryptographic purposes, 11 | ## keep in mind that so far this has not been audited by any security professionals, 12 | ## therefore may not be secure. 13 | ## 14 | ## `std/sysrand` generates random numbers from a secure source provided by the operating system. 15 | ## It is also called Cryptographically secure pseudorandom number generator. 16 | ## It should be unpredictable enough for cryptographic applications, 17 | ## though its exact quality depends on the OS implementation. 18 | ## 19 | ## | Targets | Implementation| 20 | ## | :--- | ----: | 21 | ## | Windows | `BCryptGenRandom`_ | 22 | ## | Linux | `getrandom`_ | 23 | ## | MacOSX | `getentropy`_ | 24 | ## | IOS | `SecRandomCopyBytes`_ | 25 | ## | OpenBSD | `getentropy openbsd`_ | 26 | ## | FreeBSD | `getrandom freebsd`_ | 27 | ## | JS(Web Browser) | `getRandomValues`_ | 28 | ## | Nodejs | `randomFillSync`_ | 29 | ## | Other Unix platforms | `/dev/urandom`_ | 30 | ## 31 | ## .. _BCryptGenRandom: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom 32 | ## .. _getrandom: https://man7.org/linux/man-pages/man2/getrandom.2.html 33 | ## .. _getentropy: https://www.unix.com/man-page/mojave/2/getentropy 34 | ## .. _SecRandomCopyBytes: https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc 35 | ## .. _getentropy openbsd: https://man.openbsd.org/getentropy.2 36 | ## .. _getrandom freebsd: https://www.freebsd.org/cgi/man.cgi?query=getrandom&manpath=FreeBSD+12.0-stable 37 | ## .. _getRandomValues: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues 38 | ## .. _randomFillSync: https://nodejs.org/api/crypto.html#crypto_crypto_randomfillsync_buffer_offset_size 39 | ## .. _/dev/urandom: https://en.wikipedia.org/wiki//dev/random 40 | ## 41 | 42 | runnableExamples: 43 | doAssert urandom(0).len == 0 44 | doAssert urandom(113).len == 113 45 | doAssert urandom(1234) != urandom(1234) # unlikely to fail in practice 46 | 47 | ## 48 | ## See also 49 | ## ======== 50 | ## * `random module `_ 51 | ## 52 | 53 | 54 | when not defined(js): 55 | import std/os 56 | 57 | when defined(posix): 58 | import std/posix 59 | 60 | const 61 | batchImplOS = defined(freebsd) or defined(openbsd) or (defined(macosx) and not defined(ios)) 62 | batchSize {.used.} = 256 63 | 64 | when batchImplOS: 65 | template batchImpl(result: var int, dest: var openArray[byte], getRandomImpl) = 66 | let size = dest.len 67 | if size == 0: 68 | return 69 | 70 | let 71 | chunks = (size - 1) div batchSize 72 | left = size - chunks * batchSize 73 | 74 | for i in 0 ..< chunks: 75 | let readBytes = getRandomImpl(addr dest[result], batchSize) 76 | if readBytes < 0: 77 | return readBytes 78 | inc(result, batchSize) 79 | 80 | result = getRandomImpl(addr dest[result], left) 81 | 82 | when defined(js): 83 | import std/private/jsutils 84 | 85 | when defined(nodejs): 86 | {.emit: "const _nim_nodejs_crypto = require('crypto');".} 87 | 88 | proc randomFillSync(p: Uint8Array) {.importjs: "_nim_nodejs_crypto.randomFillSync(#)".} 89 | 90 | template urandomImpl(result: var int, dest: var openArray[byte]) = 91 | let size = dest.len 92 | if size == 0: 93 | return 94 | 95 | var src = newUint8Array(size) 96 | randomFillSync(src) 97 | for i in 0 ..< size: 98 | dest[i] = src[i] 99 | 100 | else: 101 | proc getRandomValues(p: Uint8Array) {.importjs: "window.crypto.getRandomValues(#)".} 102 | # The requested length of `p` must not be more than 65536. 103 | 104 | proc assign(dest: var openArray[byte], src: Uint8Array, base: int, size: int) = 105 | getRandomValues(src) 106 | for j in 0 ..< size: 107 | dest[base + j] = src[j] 108 | 109 | template urandomImpl(result: var int, dest: var openArray[byte]) = 110 | let size = dest.len 111 | if size == 0: 112 | return 113 | 114 | if size <= batchSize: 115 | var src = newUint8Array(size) 116 | assign(dest, src, 0, size) 117 | return 118 | 119 | let 120 | chunks = (size - 1) div batchSize 121 | left = size - chunks * batchSize 122 | 123 | var srcArray = newUint8Array(batchSize) 124 | for i in 0 ..< chunks: 125 | assign(dest, srcArray, result, batchSize) 126 | inc(result, batchSize) 127 | 128 | var leftArray = newUint8Array(left) 129 | assign(dest, leftArray, result, left) 130 | 131 | elif defined(windows): 132 | type 133 | PVOID = pointer 134 | BCRYPT_ALG_HANDLE = PVOID 135 | PUCHAR = ptr cuchar 136 | NTSTATUS = clong 137 | ULONG = culong 138 | 139 | const 140 | STATUS_SUCCESS = 0x00000000 141 | BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002 142 | 143 | proc bCryptGenRandom( 144 | hAlgorithm: BCRYPT_ALG_HANDLE, 145 | pbBuffer: PUCHAR, 146 | cbBuffer: ULONG, 147 | dwFlags: ULONG 148 | ): NTSTATUS {.stdcall, importc: "BCryptGenRandom", dynlib: "Bcrypt.dll".} 149 | 150 | 151 | proc randomBytes(pbBuffer: pointer, cbBuffer: Natural): int {.inline.} = 152 | bCryptGenRandom(nil, cast[PUCHAR](pbBuffer), ULONG(cbBuffer), 153 | BCRYPT_USE_SYSTEM_PREFERRED_RNG) 154 | 155 | template urandomImpl(result: var int, dest: var openArray[byte]) = 156 | let size = dest.len 157 | if size == 0: 158 | return 159 | 160 | result = randomBytes(addr dest[0], size) 161 | 162 | elif defined(linux): 163 | # TODO using let, pending bootstrap >= 1.4.0 164 | var SYS_getrandom {.importc: "SYS_getrandom", header: "".}: clong 165 | const syscallHeader = """#include 166 | #include """ 167 | 168 | proc syscall( 169 | n: clong, buf: pointer, bufLen: cint, flags: cuint 170 | ): clong {.importc: "syscall", header: syscallHeader.} 171 | # When reading from the urandom source (GRND_RANDOM is not set), 172 | # getrandom() will block until the entropy pool has been 173 | # initialized (unless the GRND_NONBLOCK flag was specified). If a 174 | # request is made to read a large number of bytes (more than 256), 175 | # getrandom() will block until those bytes have been generated and 176 | # transferred from kernel memory to buf. 177 | 178 | template urandomImpl(result: var int, dest: var openArray[byte]) = 179 | let size = dest.len 180 | if size == 0: 181 | return 182 | 183 | while result < size: 184 | let readBytes = syscall(SYS_getrandom, addr dest[result], cint(size - result), 0).int 185 | if readBytes == 0: 186 | doAssert false 187 | elif readBytes > 0: 188 | inc(result, readBytes) 189 | else: 190 | if osLastError().int in {EINTR, EAGAIN}: 191 | discard 192 | else: 193 | result = -1 194 | break 195 | 196 | elif defined(openbsd): 197 | proc getentropy(p: pointer, size: cint): cint {.importc: "getentropy", header: "".} 198 | # fills a buffer with high-quality entropy, 199 | # which can be used as input for process-context pseudorandom generators like `arc4random`. 200 | # The maximum buffer size permitted is 256 bytes. 201 | 202 | proc getRandomImpl(p: pointer, size: int): int {.inline.} = 203 | result = getentropy(p, cint(size)).int 204 | 205 | elif defined(freebsd): 206 | type cssize_t {.importc: "ssize_t", header: "".} = int 207 | 208 | proc getrandom(p: pointer, size: csize_t, flags: cuint): cssize_t {.importc: "getrandom", header: "".} 209 | # Upon successful completion, the number of bytes which were actually read 210 | # is returned. For requests larger than 256 bytes, this can be fewer bytes 211 | # than were requested. Otherwise, -1 is returned and the global variable 212 | # errno is set to indicate the error. 213 | 214 | proc getRandomImpl(p: pointer, size: int): int {.inline.} = 215 | result = getrandom(p, csize_t(size), 0) 216 | 217 | elif defined(ios): 218 | {.passL: "-framework Security".} 219 | 220 | const errSecSuccess = 0 ## No error. 221 | 222 | type 223 | SecRandom {.importc: "struct __SecRandom".} = object 224 | 225 | SecRandomRef = ptr SecRandom 226 | ## An abstract Core Foundation-type object containing information about a random number generator. 227 | 228 | proc secRandomCopyBytes( 229 | rnd: SecRandomRef, count: csize_t, bytes: pointer 230 | ): cint {.importc: "SecRandomCopyBytes", header: "".} 231 | ## https://developer.apple.com/documentation/security/1399291-secrandomcopybytes 232 | 233 | template urandomImpl(result: var int, dest: var openArray[byte]) = 234 | let size = dest.len 235 | if size == 0: 236 | return 237 | 238 | result = secRandomCopyBytes(nil, csize_t(size), addr dest[0]) 239 | 240 | elif defined(macosx): 241 | const sysrandomHeader = """#include 242 | #include 243 | """ 244 | 245 | proc getentropy(p: pointer, size: csize_t): cint {.importc: "getentropy", header: sysrandomHeader.} 246 | # getentropy() fills a buffer with random data, which can be used as input 247 | # for process-context pseudorandom generators like arc4random(3). 248 | # The maximum buffer size permitted is 256 bytes. 249 | 250 | proc getRandomImpl(p: pointer, size: int): int {.inline.} = 251 | result = getentropy(p, csize_t(size)).int 252 | 253 | else: 254 | template urandomImpl(result: var int, dest: var openArray[byte]) = 255 | let size = dest.len 256 | if size == 0: 257 | return 258 | 259 | # see: https://www.2uo.de/myths-about-urandom/ which justifies using urandom instead of random 260 | let fd = posix.open("/dev/urandom", O_RDONLY) 261 | 262 | if fd < 0: 263 | result = -1 264 | else: 265 | try: 266 | var stat: Stat 267 | if fstat(fd, stat) != -1 and S_ISCHR(stat.st_mode): 268 | let 269 | chunks = (size - 1) div batchSize 270 | left = size - chunks * batchSize 271 | 272 | for i in 0 ..< chunks: 273 | let readBytes = posix.read(fd, addr dest[result], batchSize) 274 | if readBytes < 0: 275 | return readBytes 276 | inc(result, batchSize) 277 | 278 | result = posix.read(fd, addr dest[result], left) 279 | else: 280 | result = -1 281 | finally: 282 | discard posix.close(fd) 283 | 284 | proc urandomInternalImpl(dest: var openArray[byte]): int {.inline.} = 285 | when batchImplOS: 286 | batchImpl(result, dest, getRandomImpl) 287 | else: 288 | urandomImpl(result, dest) 289 | 290 | proc urandom*(dest: var openArray[byte]): bool = 291 | ## Fills `dest` with random bytes suitable for cryptographic use. 292 | ## If the call succeeds, returns `true`. 293 | ## 294 | ## If `dest` is empty, `urandom` immediately returns success, 295 | ## without calling underlying operating system api. 296 | ## 297 | ## .. warning:: The code hasn't been audited by cryptography experts and 298 | ## is provided as-is without guarantees. Use at your own risks. For production 299 | ## systems we advise you to request an external audit. 300 | result = true 301 | when defined(js): discard urandomInternalImpl(dest) 302 | else: 303 | let ret = urandomInternalImpl(dest) 304 | when defined(windows): 305 | if ret != STATUS_SUCCESS: 306 | result = false 307 | else: 308 | if ret < 0: 309 | result = false 310 | 311 | proc urandom*(size: Natural): seq[byte] {.inline.} = 312 | ## Returns random bytes suitable for cryptographic use. 313 | ## 314 | ## .. warning:: The code hasn't been audited by cryptography experts and 315 | ## is provided as-is without guarantees. Use at your own risks. For production 316 | ## systems we advise you to request an external audit. 317 | result = newSeq[byte](size) 318 | when defined(js): discard urandomInternalImpl(result) 319 | else: 320 | if not urandom(result): 321 | raiseOSError(osLastError()) -------------------------------------------------------------------------------- /app/src/nimview/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import sys, os, pathlib 2 | # print(pathlib.Path(os.path.abspath(__file__)).parent.parent) 3 | sys.path.append(str(pathlib.Path(os.path.abspath(__file__)).parent.parent) + "/out") 4 | import nimview -------------------------------------------------------------------------------- /app/src/nimview/tests/c_test.c: -------------------------------------------------------------------------------- 1 | /** Nimview UI Library 2 | * Copyright (C) 2020, 2021, by Marco Mengelkoch 3 | * Licensed under MIT License, see License file for more details 4 | * git clone https://github.com/marcomq/nimview 5 | **/ 6 | // Important Notice: You should use --threads:on AND you need to avoid --gc:arc ; I had crashes on windows otherwise with NIM 1.4 when starting webview 7 | 8 | #include "../out/tmp_c/nimview.h" 9 | #include 10 | #include 11 | 12 | char* echoAndModify(char* something) { 13 | const char* appendString = " modified by C"; 14 | char* result = malloc(strlen(something) + strlen(appendString) + 1); // +1 for the null-terminator, strlen is unchecked! "something" needs 0 termination 15 | if (result) { 16 | strcpy(result, something); // safe, result just created 17 | strcat(result, appendString); // safe, result just created with len 18 | } 19 | else { 20 | return ""; // "" will not be freed 21 | } 22 | return result; 23 | } 24 | 25 | char* stopNimview(char* something) { 26 | nimview_stopDesktop(); 27 | return ""; 28 | } 29 | int main(int argc, char* argv[]) { 30 | printf(" starting c code\n"); 31 | NimMain(); 32 | nimview_addRequest("echoAndModify", echoAndModify, free); 33 | nimview_addRequest("stopNimview", stopNimview, free); 34 | 35 | nimview_dispatchCommandLineArg("{\"request\":\"echoAndModify\",\"value\":\"this is a test\",\"responseId\":0,\"responseKey\":\"test\"}"); 36 | nimview_dispatchCommandLineArg("{\"request\":\"stopNimview\",\"value\":\"\",\"responseId\":1,\"responseKey\":\"test\"}"); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /app/src/nimview/tests/desktopSample.nim: -------------------------------------------------------------------------------- 1 | # Nimview UI Library 2 | # Copyright (C) 2020, 2021, by Marco Mengelkoch 3 | # Licensed under MIT License, see License file for more details 4 | # git clone https://github.com/marcomq/nimview 5 | 6 | import ../src/nimview 7 | 8 | nimview.addRequest("appendSomething", proc (value: string): string = 9 | echo value 10 | result = "'" & value & "' modified by Webview Backend") 11 | nimview.startDesktop("../examples/vue/dist/index.html") -------------------------------------------------------------------------------- /app/src/nimview/tests/httpSample.nim: -------------------------------------------------------------------------------- 1 | # Nimview UI Library 2 | # Copyright (C) 2020, 2021, by Marco Mengelkoch 3 | # Licensed under MIT License, see License file for more details 4 | # git clone https://github.com/marcomq/nimview 5 | 6 | import ../src/nimview 7 | 8 | nimview.addRequest("appendSomething", proc (value: string): string = 9 | echo value 10 | result = "'" & value & "' modified by Jester Backend") 11 | nimview.startHttpServer("../examples/vue/dist/index.html") -------------------------------------------------------------------------------- /app/src/nimview/tests/pyTest.py: -------------------------------------------------------------------------------- 1 | # Nimview UI Library 2 | # Copyright (C) 2020, 2021, by Marco Mengelkoch 3 | # Licensed under MIT License, see License file for more details 4 | 5 | # pylint: disable=import-error 6 | import __init__, nimview 7 | def echoAndModify(value): 8 | print (value) 9 | return (value + " appended by python") 10 | 11 | def stopNimview(value): 12 | nimview.stopDesktop() 13 | return "" 14 | 15 | nimview.addRequest("echoAndModify", echoAndModify) 16 | nimview.addRequest("stopNimview", stopNimview) 17 | 18 | nimview.dispatchCommandLineArg("{\"request\":\"echoAndModify\",\"value\":\"this is a test\",\"responseId\":0,\"responseKey\":\"test\"}") 19 | nimview.dispatchCommandLineArg("{\"request\":\"stopNimview\",\"value\":\"\",\"responseId\":1,\"responseKey\":\"test\"}") 20 | # nimview.startDesktop("tests/minimal_ui_sample/index.html") 21 | -------------------------------------------------------------------------------- /app/src/nimview/tests/pyWebViewSample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # pylint: disable=no-member 3 | import nimview 4 | import os 5 | nimview.startWebview("vue/dist/index.html") -------------------------------------------------------------------------------- /app/src/nimview/tests/test.dump: -------------------------------------------------------------------------------- 1 | {"request":"appendSomething","value":"gg","responseId":0,"responseKey":"search"} 2 | {"request":"appendSomething","value":"ff","responseId":1,"responseKey":"search"} 3 | {"request":"appendSomething","value":"vv","responseId":2,"responseKey":"search"} -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | ext.kotlin_version = "1.4.10" 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.0.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | task clean(type: Delete) { 25 | delete rootProject.buildDir 26 | } 27 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Sep 13 14:49:58 CEST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name = "nimviewAndroid" --------------------------------------------------------------------------------