diff --git a/README.md b/README.md index a09811a..faa9912 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,13 @@ This library allows you to communicate small amounts of data between air-gapped This library is used only to generate and analyze the RAW waveforms that are played and captured from your audio devices (speakers, microphones, etc.). You are free to use any audio backend (e.g. PulseAudio, ALSA, etc.) as long as you provide callbacks for queuing and dequeuing audio samples. -You can easily test the library on your phone with these free mobile apps: +You can easily test the library using the `waver` application: Download on the App Store Get it on Google Play + +Get it from the Snap Store + **Browser demo:** https://ggwave.ggerganov.com @@ -105,6 +108,19 @@ emcmake cmake .. make ``` + +## Installing the Waver application + +[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/waver) + +### Linux + +```bash +sudo snap install waver +sudo snap connect waver:audio-record :audio-record +``` + + ## Todo - [ ] Improve library interface diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index eb80a19..c34ea97 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -6,7 +6,8 @@ if (GGWAVE_SUPPORT_SDL2) # SDL2 if (EMSCRIPTEN) - set(CMAKE_CXX_FLAGS "-s ALLOW_MEMORY_GROWTH=1 -s USE_SDL=2 -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0 -s 'EXTRA_EXPORTED_RUNTIME_METHODS=[\"writeArrayToMemory\"]'") + set(CMAKE_CXX_FLAGS "-s USE_SDL=2 -s USE_PTHREADS=1 -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") + unset(SDL2_INCLUDE_DIRS) unset(SDL2_LIBRARIES) endif() @@ -67,7 +68,11 @@ if (GGWAVE_SUPPORT_SDL2) if (EMSCRIPTEN) # emscripten sdl2 examples + set(CMAKE_CXX_FLAGS "-s ALLOW_MEMORY_GROWTH=1 -s USE_SDL=2 -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0 -s 'EXTRA_EXPORTED_RUNTIME_METHODS=[\"writeArrayToMemory\"]'") add_subdirectory(ggwave-wasm) + + set(CMAKE_CXX_FLAGS "-s TOTAL_MEMORY=67108864 -s USE_SDL=2 -s USE_PTHREADS=1 -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") + add_subdirectory(waver) else() # non-emscripten sdl2 examples diff --git a/examples/third-party/imgui/imgui-extra/imgui_impl.cpp b/examples/third-party/imgui/imgui-extra/imgui_impl.cpp index bc94c39..9c3a196 100644 --- a/examples/third-party/imgui/imgui-extra/imgui_impl.cpp +++ b/examples/third-party/imgui/imgui-extra/imgui_impl.cpp @@ -25,6 +25,13 @@ bool ImGui_PreInit() { SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); +#elif __EMSCRIPTEN__ + const char* glsl_version = "#version 100"; + //const char* glsl_version = "#version 300 es"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #else // GL 3.0 + GLSL 130 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); @@ -46,6 +53,8 @@ ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context) { #if __APPLE__ // GL 3.2 Core + GLSL 150 const char* glsl_version = "#version 150"; +#elif __EMSCRIPTEN__ + const char* glsl_version = "#version 100"; #else // GL 3.0 + GLSL 130 const char* glsl_version = "#version 130"; @@ -83,12 +92,7 @@ ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context) { // Setup Platform/Renderer bindings bool res = true; res &= ImGui_ImplSDL2_InitForOpenGL(window, gl_context); - -#ifdef __EMSCRIPTEN__ - res &= ImGui_ImplOpenGL3_Init("#version 300 es"); -#else res &= ImGui_ImplOpenGL3_Init(glsl_version); -#endif return res ? ctx : nullptr; } diff --git a/examples/waver/CMakeLists.txt b/examples/waver/CMakeLists.txt index 987cfdc..fea657f 100644 --- a/examples/waver/CMakeLists.txt +++ b/examples/waver/CMakeLists.txt @@ -1,17 +1,48 @@ -add_executable(waver main.cpp common.cpp interface.cpp interface-unix.cpp) +set(TARGET waver) -target_include_directories(waver PRIVATE - .. - ${SDL2_INCLUDE_DIRS} - ) +if (EMSCRIPTEN) + add_executable(${TARGET} main.cpp common.cpp interface.cpp interface-emscripten.cpp) -target_link_libraries(waver PRIVATE - ggwave - ggwave-common - ggwave-common-sdl2 - ggsock - imgui-sdl2 - ${CMAKE_THREAD_LIBS_INIT} - ) + target_include_directories(${TARGET} PRIVATE + .. + ${SDL2_INCLUDE_DIRS} + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/ + ) -install(TARGETS waver RUNTIME DESTINATION bin) + target_link_libraries(${TARGET} PRIVATE + ggwave + ggwave-common + ggwave-common-sdl2 + ggsock + imgui-sdl2 + ${CMAKE_THREAD_LIBS_INIT} + ) + + set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \ + -s FORCE_FILESYSTEM=1 \ + --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../assets/fonts@/ \ + ") + + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build_timestamp.h.tmpl ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/build_timestamp.h @ONLY) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css COPYONLY) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/background-0.png ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/background-0.png COPYONLY) +else() + add_executable(${TARGET} main.cpp common.cpp interface.cpp interface-unix.cpp) + + target_include_directories(${TARGET} PRIVATE + .. + ${SDL2_INCLUDE_DIRS} + ) + + target_link_libraries(${TARGET} PRIVATE + ggwave + ggwave-common + ggwave-common-sdl2 + ggsock + imgui-sdl2 + ${CMAKE_THREAD_LIBS_INIT} + ) + + install(TARGETS ${TARGET} RUNTIME DESTINATION bin) +endif() diff --git a/examples/waver/README.md b/examples/waver/README.md index 185d582..643208d 100644 --- a/examples/waver/README.md +++ b/examples/waver/README.md @@ -1,8 +1,39 @@ # waver -Simple GUI program using `ggwave`. The UI is the same as in the `Waver: Data Over Sound` mobile applications. +Waver allows you to send and receive text messages from nearby devices through sound waves. - +This application can be used to communicate with multiple nearby devices at once. Both audible and ultrasound communication protocols are available. The app does not connect to the internet and all information is transmitted only through sound. In order to receive incoming messages you only need to allow access to your device's microphone so that it can record nearby sounds. + +### Install Download on the App Store Get it on Google Play + +Get it from the Snap Store + + +```bash +sudo snap install waver +sudo snap connect waver:audio-record :audio-record +``` + +## How to use + + + +- Before starting - make sure the speaker of your device is enabled and disconnect/unplug any headphones. The app uses your device's speaker to emit sounds when sending a text message +- To send a message - tap on "Messages", enter some text at the bottom of the screen and click "Send" +- Any nearby device that is also running this application can capture the emitted sound and display the received message +- In the settings menu, you can adjust the volume and the transmission protocol that will be used when sending messages. Make sure to adjust the volume level high enough, so the sounds can be picked up by other devices +- Tap on "Spectrum" to see a real-time frequency spectrum of the currently captured audio by your device's microphone + + +## File sharing in a local network + +As of v1.3.0 Waver supports file sharing. It works like this: + +- Add files that you would like to transmit by sharing them with Waver +- In the "Files" menu, click on "Broadcast". This plays an audio message that contains a file broadcast offer +- Nearby devices in the same local network can receive this offer and initiate a TCP/IP connection to your device +- The files are transmitted over TCP/IP. The sound message is used only to initiate the network connections between the devices +- Waver allows sharing multiple files to multiple devices at once diff --git a/examples/waver/background-0.png b/examples/waver/background-0.png new file mode 100644 index 0000000..df85c07 Binary files /dev/null and b/examples/waver/background-0.png differ diff --git a/examples/waver/build_timestamp.h.tmpl b/examples/waver/build_timestamp.h.tmpl new file mode 100644 index 0000000..63cb816 --- /dev/null +++ b/examples/waver/build_timestamp.h.tmpl @@ -0,0 +1 @@ +static const char * BUILD_TIMESTAMP="@GIT_DATE@ (@GIT_SHA1@)"; diff --git a/examples/waver/common.cpp b/examples/waver/common.cpp index e3efec3..d8dda7b 100644 --- a/examples/waver/common.cpp +++ b/examples/waver/common.cpp @@ -438,7 +438,13 @@ std::thread initMain() { g_isRunning = true; g_ggWave = GGWave_instance(); +#ifdef __EMSCRIPTEN__ + GGSock::FileServer::Parameters p; + p.nWorkerThreads = 0; + g_fileServer.init(p); +#else g_fileServer.init({}); +#endif g_fileClient.setErrorCallback([](GGSock::Communicator::TErrorCode code) { printf("Disconnected with code = %d\n", code); @@ -666,6 +672,11 @@ void renderMain() { static bool scrollMessagesToBottom = true; static bool hasAudioCaptureData = false; static bool hasNewMessages = false; +#ifdef __EMSCRIPTEN__ + static bool hasFileSharingSupport = false; +#else + static bool hasFileSharingSupport = true; +#endif static double tStartInput = 0.0f; static double tEndInput = -100.0f; @@ -768,7 +779,14 @@ void renderMain() { } ImGui::SameLine(); - if (ImGui::ButtonSelectable(ICON_FA_FILE " Files", { 0.40f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }, windowId == WindowId::Files)) { + if (!hasFileSharingSupport) { + ImGui::ButtonDisabled(ICON_FA_FILE " Files", { 0.40f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("File sharing is not supported on this platform!"); + ImGui::EndTooltip(); + } + } else if (ImGui::ButtonSelectable(ICON_FA_FILE " Files", { 0.40f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }, windowId == WindowId::Files)) { windowId = WindowId::Files; } ImGui::SameLine(); @@ -1030,7 +1048,7 @@ void renderMain() { ImGui::TextDisabled("|"); ImGui::SameLine(); - if (ImGui::ButtonDisablable("Receive", {}, !messageSelected.received || messageSelected.type != Message::FileBroadcast)) { + if (ImGui::ButtonDisablable("Receive", {}, !messageSelected.received || messageSelected.type != Message::FileBroadcast || !hasFileSharingSupport)) { auto broadcastInfo = parseBroadcastInfo(messageSelected.data); g_remoteIP = broadcastInfo.ip; diff --git a/examples/waver/index-tmpl.html b/examples/waver/index-tmpl.html new file mode 100644 index 0000000..2cf8ed4 --- /dev/null +++ b/examples/waver/index-tmpl.html @@ -0,0 +1,184 @@ + + + + + Waver + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Waver: Data Over Sound

+ +
+ +
+
+ +
+
+ + +
+ View on GitHub + + +
+ + + + + + diff --git a/examples/waver/interface-emscripten.cpp b/examples/waver/interface-emscripten.cpp new file mode 100644 index 0000000..5d367aa --- /dev/null +++ b/examples/waver/interface-emscripten.cpp @@ -0,0 +1,43 @@ +#include "interface.h" + +#include + +void interface_addFile( + const char * , + const char * , + const char * , + size_t ) { +} + +void interface_loadAllFiles() { +} + +void interface_shareFile( + const char * , + const char * , + const char * , + size_t ) { +} + +void interface_openFile( + const char * , + const char * , + const char * , + size_t ) { +} + +void interface_deleteFile( + const char * , + const char * ) { +} + +void interface_receiveFile( + const char * , + const char * , + const char * , + size_t ) { +} + +bool interface_needReloadFiles() { + return false; +} diff --git a/examples/waver/main.cpp b/examples/waver/main.cpp index 5c627c9..f98b11b 100644 --- a/examples/waver/main.cpp +++ b/examples/waver/main.cpp @@ -2,6 +2,13 @@ #include "ggwave-common.h" +#ifdef __EMSCRIPTEN__ +#include "build_timestamp.h" +#include "emscripten/emscripten.h" +#else +#define EMSCRIPTEN_KEEPALIVE +#endif + #include #include @@ -13,12 +20,16 @@ #include #include #include +#include namespace { void dummy() {} } std::string getBinaryPath() { +#ifdef __EMSCRIPTEN__ + return ""; +#endif std::string result; void* p = reinterpret_cast(dummy); @@ -179,16 +190,42 @@ bool ImGui_SetStyle() { return true; } +static std::function g_doInit; +static std::function g_setWindowSize; +static std::function g_mainUpdate; + +void mainUpdate(void *) { + g_mainUpdate(); +} + +// JS interface + +extern "C" { + EMSCRIPTEN_KEEPALIVE + int do_init() { + return g_doInit(); + } + + EMSCRIPTEN_KEEPALIVE + void set_window_size(int sizeX, int sizeY) { + g_setWindowSize(sizeX, sizeY); + } +} + int main(int argc, char** argv) { +#ifdef __EMSCRIPTEN__ + printf("Build time: %s\n", BUILD_TIMESTAMP); + printf("Press the Init button to start\n"); + + if (argv[1]) { + GGWave_setDefaultCaptureDeviceName(argv[1]); + } +#endif + auto argm = parseCmdArguments(argc, argv); int captureId = argm["c"].empty() ? 0 : std::stoi(argm["c"]); int playbackId = argm["p"].empty() ? 0 : std::stoi(argm["p"]); - if (GGWave_init(playbackId, captureId) == false) { - fprintf(stderr, "Failed to initialize GGWave\n"); - return -1; - } - if (SDL_Init(SDL_INIT_VIDEO) != 0) { fprintf(stderr, "Error: %s\n", SDL_GetError()); return -1; @@ -207,8 +244,15 @@ int main(int argc, char** argv) { const char * windowTitle = "Waver"; +#ifdef __EMSCRIPTEN__ + SDL_Renderer * renderer; + SDL_Window * window; + SDL_CreateWindowAndRenderer(windowX, windowY, SDL_WINDOW_OPENGL, &window, &renderer); +#else SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); SDL_Window * window = SDL_CreateWindow(windowTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, windowX, windowY, window_flags); +#endif + void * gl_context = SDL_GL_CreateContext(window); SDL_GL_MakeCurrent(window, gl_context); @@ -216,6 +260,7 @@ int main(int argc, char** argv) { ImGui_Init(window, gl_context); ImGui::GetIO().IniFilename = nullptr; + ImGui::GetIO().Fonts->AddFontFromFileTTF((getBinaryPath() + "DroidSans.ttf").c_str(), 14.0f); ImGui::GetIO().Fonts->AddFontFromFileTTF((getBinaryPath() + "../examples/assets/fonts/DroidSans.ttf").c_str(), 14.0f); ImGui::GetIO().Fonts->AddFontFromFileTTF((getBinaryPath() + "../../examples/assets/fonts/DroidSans.ttf").c_str(), 14.0f); @@ -226,6 +271,7 @@ int main(int argc, char** argv) { config.MergeMode = true; config.GlyphOffset = { 0.0f, 0.0f }; + ImGui::GetIO().Fonts->AddFontFromFileTTF((getBinaryPath() + "fontawesome-webfont.ttf").c_str(), 14.0f, &config, ranges); ImGui::GetIO().Fonts->AddFontFromFileTTF((getBinaryPath() + "../examples/assets/fonts/fontawesome-webfont.ttf").c_str(), 14.0f, &config, ranges); ImGui::GetIO().Fonts->AddFontFromFileTTF((getBinaryPath() + "../../examples/assets/fonts/fontawesome-webfont.ttf").c_str(), 14.0f, &config, ranges); } @@ -237,22 +283,58 @@ int main(int argc, char** argv) { ImGui_NewFrame(window); ImGui::Render(); - auto worker = initMain(); - // tmp //addFile("test0.raw", "test0.raw", std::vector(1024)); //addFile("test1.jpg", "test0.jpg", std::vector(1024*1024 + 624)); //addFile("test2.mpv", "test0.mov", std::vector(1024*1024*234 + 53827)); - while (true) { + bool isInitialized = false; + std::thread worker; + + g_doInit = [&]() { + if (GGWave_init(playbackId, captureId) == false) { + fprintf(stderr, "Failed to initialize GGWave\n"); + return false; + } + + worker = initMain(); + + isInitialized = true; + + return true; + }; + + g_setWindowSize = [&](int sizeX, int sizeY) { + SDL_SetWindowSize(window, sizeX, sizeY); + }; + + g_mainUpdate = [&]() { + if (isInitialized == false) { + return true; + } + if (ImGui_BeginFrame(window) == false) { - break; + return false; } renderMain(); updateMain(); ImGui_EndFrame(window); + + return true; + }; + +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop_arg(mainUpdate, NULL, 0, true); +#else + if (g_doInit() == false) { + printf("Error: failed to initialize audio\n"); + return -2; + } + + while (true) { + if (g_mainUpdate() == false) break; } deinitMain(worker); @@ -265,6 +347,7 @@ int main(int argc, char** argv) { SDL_GL_DeleteContext(gl_context); SDL_DestroyWindow(window); SDL_Quit(); +#endif return 0; } diff --git a/examples/waver/style.css b/examples/waver/style.css new file mode 100644 index 0000000..a7c17b1 --- /dev/null +++ b/examples/waver/style.css @@ -0,0 +1,324 @@ +body { + background-image: url('background-0.png'); + margin: 0; background-color: white; + -webkit-font-smoothing: subpixel-antialiased; + font-smoothing: subpixel-antialiased; +} +#screen { + margin: 0; + padding: 0; + font-size: 13px; + height: 100%; + font: sans-serif; +} +.no-sel { + -moz-user-select: none; + -webkit-user-select: none; + -webkit-touch-callout: none; + -ms-user-select:none; + user-select:none; + -o-user-select:none; +} +.cell { + pointer-events: none; +} +.cell-version { + padding-left: 4px; + padding-top: 0.5em; + text-align: left; + display: inline-block; + float: left; + color: rgba(0, 0, 0, 0.75); +} +.cell-about { + padding-right: 24px; + padding-top: 0.5em; + text-align: right; + display: inline-block; + float: right; +} +.nav-link { + text-decoration: none; + color: rgba(0, 0, 0, 1.0); +} + +#main-container { + font-size:12px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; +} + +textarea { + font-size:12px; + font-family: monospace; +} + +.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } +div.emscripten_border { border: 1px solid black; } + +canvas.emscripten { + border: 0px none; + background-color: black; + text-shadow: 4px 4px #1f1f1fb4; +} + +.spinner { + height: 30px; + width: 30px; + margin: 0; + margin-top: 20px; + margin-left: 20px; + display: inline-block; + vertical-align: top; + + -webkit-animation: rotation .8s linear infinite; + -moz-animation: rotation .8s linear infinite; + -o-animation: rotation .8s linear infinite; + animation: rotation 0.8s linear infinite; + + border-left: 5px solid rgb(235, 235, 235); + border-right: 5px solid rgb(235, 235, 235); + border-bottom: 5px solid rgb(235, 235, 235); + border-top: 5px solid rgb(120, 120, 120); + + border-radius: 100%; + background-color: rgb(189, 215, 46); +} + +@-webkit-keyframes rotation { + from {-webkit-transform: rotate(0deg);} + to {-webkit-transform: rotate(360deg);} +} +@-moz-keyframes rotation { + from {-moz-transform: rotate(0deg);} + to {-moz-transform: rotate(360deg);} +} +@-o-keyframes rotation { + from {-o-transform: rotate(0deg);} + to {-o-transform: rotate(360deg);} +} +@keyframes rotation { + from {transform: rotate(0deg);} + to {transform: rotate(360deg);} +} + +#status { + display: inline-block; + vertical-align: top; + margin-top: 30px; + margin-left: 20px; + font-weight: bold; + color: rgb(120, 120, 120); +} + +#progress { + height: 20px; + width: 30px; +} + +#output { + width: 800px; + height: 200px; + margin: 0 auto; + margin-top: 10px; + border-left: 0px; + border-right: 0px; + padding-left: 0px; + padding-right: 0px; + background-color: black; + color: white; + font-size:10px; + font-family: 'Lucida Console', Monaco, monospace; + outline: none; +} + +.led-box { + height: 30px; + width: 25%; + margin: 10px 0; + float: left; +} + +.led-box p { + font-size: 12px; + text-align: center; + margin: 1em; +} + +.led-red { + margin: 0 auto; + width: 12px; + height: 12px; + background-color: #F00; + border-radius: 50%; + box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 12px; + -webkit-animation: blinkRed 0.5s infinite; + -moz-animation: blinkRed 0.5s infinite; + -ms-animation: blinkRed 0.5s infinite; + -o-animation: blinkRed 0.5s infinite; + animation: blinkRed 0.5s infinite; +} + +@-webkit-keyframes blinkRed { + from { background-color: #F00; } + 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} + to { background-color: #F00; } +} +@-moz-keyframes blinkRed { + from { background-color: #F00; } + 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} + to { background-color: #F00; } +} +@-ms-keyframes blinkRed { + from { background-color: #F00; } + 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} + to { background-color: #F00; } +} +@-o-keyframes blinkRed { + from { background-color: #F00; } + 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} + to { background-color: #F00; } +} +@keyframes blinkRed { + from { background-color: #F00; } + 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} + to { background-color: #F00; } +} + +.led-yellow { + margin: 0 auto; + width: 12px; + height: 12px; + background-color: #FF0; + border-radius: 50%; + box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 12px; + -webkit-animation: blinkYellow 1s infinite; + -moz-animation: blinkYellow 1s infinite; + -ms-animation: blinkYellow 1s infinite; + -o-animation: blinkYellow 1s infinite; + animation: blinkYellow 1s infinite; +} + +@-webkit-keyframes blinkYellow { + from { background-color: #FF0; } + 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } + to { background-color: #FF0; } +} +@-moz-keyframes blinkYellow { + from { background-color: #FF0; } + 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } + to { background-color: #FF0; } +} +@-ms-keyframes blinkYellow { + from { background-color: #FF0; } + 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } + to { background-color: #FF0; } +} +@-o-keyframes blinkYellow { + from { background-color: #FF0; } + 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } + to { background-color: #FF0; } +} +@keyframes blinkYellow { + from { background-color: #FF0; } + 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } + to { background-color: #FF0; } +} + +.led-green { + margin: 0 auto; + width: 12px; + height: 12px; + background-color: #ABFF00; + border-radius: 50%; + box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 9px, #89FF00 0 2px 12px; +} + +.led-blue { + margin: 0 auto; + width: 18px; + height: 18px; + background-color: #24E0FF; + border-radius: 50%; + box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #006 0 -1px 9px, #3F8CFF 0 2px 14px; +} + +table td { + border: 1px solid #e8e8e8; +} +table th, table td { + padding: 10px 10px; +} +table td { + border: 1px solid #e8e8e8; +} +table th, table td { + padding: 10px 10px; +} +td[Attributes Style] { + text-align: -webkit-center; +} +td { + display: table-cell; + vertical-align: inherit; +} +table { + margin-bottom: 30px; + width: 800px; + text-align: left; + color: #3f3f3f; + border-collapse: collapse; + border: 1px solid #e8e8e8; +} +table { + margin-bottom: 30px; + width: 800px; + text-align: left; + color: #3f3f3f; + border-collapse: collapse; + border: 1px solid #e8e8e8; +} +table { + border-collapse: separate; + border-spacing: 2px; +} + +#description { + margin: 10px; + padding: 10px; + color: rgba(255, 255, 255, 1.00); + text-shadow: 2px 2px #1f1f1fb4; +} +.text-body { + color: rgba(255, 255, 255, 1.00); + text-shadow: 2px 2px #1f1f1fb4; +} +.cell-version { + padding-left: 4px; + padding-top: 0.5em; + text-align: left; + display: inline-block; + float: left; + color: rgba(255, 255, 255, 0.75); + text-shadow: 2px 2px #1f1f1fb4; +} +.cell-about { + padding-right: 24px; + padding-top: 0.5em; + text-align: right; + display: inline-block; + float: right; +} +.nav-link { + text-decoration: none; + color: rgba(255, 255, 255, 1.0); + text-shadow: 2px 2px #1f1f1fb4; +} +.nav-link2 { + text-decoration: none; + color: rgba(0, 255, 0, 1.0); + text-shadow: 2px 2px #1f1f1fb4; +} +svg { + -webkit-filter: invert(100%); /* safari 6.0 - 9.0 */ + filter: invert(100%); +} diff --git a/media/favicons-waver/android-icon-144x144.png b/media/favicons-waver/android-icon-144x144.png new file mode 100644 index 0000000..ecaed0f Binary files /dev/null and b/media/favicons-waver/android-icon-144x144.png differ diff --git a/media/favicons-waver/android-icon-192x192.png b/media/favicons-waver/android-icon-192x192.png new file mode 100644 index 0000000..7dfc181 Binary files /dev/null and b/media/favicons-waver/android-icon-192x192.png differ diff --git a/media/favicons-waver/android-icon-36x36.png b/media/favicons-waver/android-icon-36x36.png new file mode 100644 index 0000000..b6b7ace Binary files /dev/null and b/media/favicons-waver/android-icon-36x36.png differ diff --git a/media/favicons-waver/android-icon-48x48.png b/media/favicons-waver/android-icon-48x48.png new file mode 100644 index 0000000..ebb54e2 Binary files /dev/null and b/media/favicons-waver/android-icon-48x48.png differ diff --git a/media/favicons-waver/android-icon-72x72.png b/media/favicons-waver/android-icon-72x72.png new file mode 100644 index 0000000..bb985b3 Binary files /dev/null and b/media/favicons-waver/android-icon-72x72.png differ diff --git a/media/favicons-waver/android-icon-96x96.png b/media/favicons-waver/android-icon-96x96.png new file mode 100644 index 0000000..bda54e7 Binary files /dev/null and b/media/favicons-waver/android-icon-96x96.png differ diff --git a/media/favicons-waver/apple-icon-114x114.png b/media/favicons-waver/apple-icon-114x114.png new file mode 100644 index 0000000..69ed29d Binary files /dev/null and b/media/favicons-waver/apple-icon-114x114.png differ diff --git a/media/favicons-waver/apple-icon-120x120.png b/media/favicons-waver/apple-icon-120x120.png new file mode 100644 index 0000000..128a1a9 Binary files /dev/null and b/media/favicons-waver/apple-icon-120x120.png differ diff --git a/media/favicons-waver/apple-icon-144x144.png b/media/favicons-waver/apple-icon-144x144.png new file mode 100644 index 0000000..ecaed0f Binary files /dev/null and b/media/favicons-waver/apple-icon-144x144.png differ diff --git a/media/favicons-waver/apple-icon-152x152.png b/media/favicons-waver/apple-icon-152x152.png new file mode 100644 index 0000000..1b24cea Binary files /dev/null and b/media/favicons-waver/apple-icon-152x152.png differ diff --git a/media/favicons-waver/apple-icon-180x180.png b/media/favicons-waver/apple-icon-180x180.png new file mode 100644 index 0000000..b1e2ca3 Binary files /dev/null and b/media/favicons-waver/apple-icon-180x180.png differ diff --git a/media/favicons-waver/apple-icon-57x57.png b/media/favicons-waver/apple-icon-57x57.png new file mode 100644 index 0000000..6698f25 Binary files /dev/null and b/media/favicons-waver/apple-icon-57x57.png differ diff --git a/media/favicons-waver/apple-icon-60x60.png b/media/favicons-waver/apple-icon-60x60.png new file mode 100644 index 0000000..0f654a0 Binary files /dev/null and b/media/favicons-waver/apple-icon-60x60.png differ diff --git a/media/favicons-waver/apple-icon-72x72.png b/media/favicons-waver/apple-icon-72x72.png new file mode 100644 index 0000000..bb985b3 Binary files /dev/null and b/media/favicons-waver/apple-icon-72x72.png differ diff --git a/media/favicons-waver/apple-icon-76x76.png b/media/favicons-waver/apple-icon-76x76.png new file mode 100644 index 0000000..e854420 Binary files /dev/null and b/media/favicons-waver/apple-icon-76x76.png differ diff --git a/media/favicons-waver/apple-icon-precomposed.png b/media/favicons-waver/apple-icon-precomposed.png new file mode 100644 index 0000000..830e08b Binary files /dev/null and b/media/favicons-waver/apple-icon-precomposed.png differ diff --git a/media/favicons-waver/apple-icon.png b/media/favicons-waver/apple-icon.png new file mode 100644 index 0000000..830e08b Binary files /dev/null and b/media/favicons-waver/apple-icon.png differ diff --git a/media/favicons-waver/browserconfig.xml b/media/favicons-waver/browserconfig.xml new file mode 100644 index 0000000..c554148 --- /dev/null +++ b/media/favicons-waver/browserconfig.xml @@ -0,0 +1,2 @@ + +#ffffff \ No newline at end of file diff --git a/media/favicons-waver/favicon-16x16.png b/media/favicons-waver/favicon-16x16.png new file mode 100644 index 0000000..3afca45 Binary files /dev/null and b/media/favicons-waver/favicon-16x16.png differ diff --git a/media/favicons-waver/favicon-32x32.png b/media/favicons-waver/favicon-32x32.png new file mode 100644 index 0000000..2f4c8bc Binary files /dev/null and b/media/favicons-waver/favicon-32x32.png differ diff --git a/media/favicons-waver/favicon-96x96.png b/media/favicons-waver/favicon-96x96.png new file mode 100644 index 0000000..bda54e7 Binary files /dev/null and b/media/favicons-waver/favicon-96x96.png differ diff --git a/media/favicons-waver/favicon.ico b/media/favicons-waver/favicon.ico new file mode 100644 index 0000000..4233168 Binary files /dev/null and b/media/favicons-waver/favicon.ico differ diff --git a/media/favicons-waver/manifest.json b/media/favicons-waver/manifest.json new file mode 100644 index 0000000..013d4a6 --- /dev/null +++ b/media/favicons-waver/manifest.json @@ -0,0 +1,41 @@ +{ + "name": "App", + "icons": [ + { + "src": "\/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] +} \ No newline at end of file diff --git a/media/favicons-waver/ms-icon-144x144.png b/media/favicons-waver/ms-icon-144x144.png new file mode 100644 index 0000000..ecaed0f Binary files /dev/null and b/media/favicons-waver/ms-icon-144x144.png differ diff --git a/media/favicons-waver/ms-icon-150x150.png b/media/favicons-waver/ms-icon-150x150.png new file mode 100644 index 0000000..f7141d7 Binary files /dev/null and b/media/favicons-waver/ms-icon-150x150.png differ diff --git a/media/favicons-waver/ms-icon-310x310.png b/media/favicons-waver/ms-icon-310x310.png new file mode 100644 index 0000000..a2bbe63 Binary files /dev/null and b/media/favicons-waver/ms-icon-310x310.png differ diff --git a/media/favicons-waver/ms-icon-70x70.png b/media/favicons-waver/ms-icon-70x70.png new file mode 100644 index 0000000..e020e01 Binary files /dev/null and b/media/favicons-waver/ms-icon-70x70.png differ