Merge branch 'master' of https://github.com/ggerganov/ggwave
18
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:
|
||||
|
||||
<a href="https://apps.apple.com/us/app/waver-data-over-sound/id1543607865?itsct=apps_box&itscg=30200&ign-itsct=apps_box#?platform=iphone" style="display: inline-block; overflow: hidden; border-radius: 13px; width: 250px; height: 83px;"><img height="60px" src="https://tools.applemediaservices.com/api/badges/download-on-the-app-store/white/en-US?size=250x83&releaseDate=1607558400&h=8e5fafc57929918f684abc83ff8311ef" alt="Download on the App Store"></a>
|
||||
<a href='https://play.google.com/store/apps/details?id=com.ggerganov.Waver&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://i.imgur.com/BKDCbKv.png' height="60px"/></a>
|
||||
<a href="https://snapcraft.io/waver">
|
||||
<img alt="Get it from the Snap Store" src="https://snapcraft.io/static/images/badges/en/snap-store-black.svg" height="60px"/>
|
||||
</a>
|
||||
|
||||
**Browser demo:** https://ggwave.ggerganov.com
|
||||
|
||||
@@ -105,6 +108,19 @@ emcmake cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
|
||||
## Installing the Waver application
|
||||
|
||||
[](https://snapcraft.io/waver)
|
||||
|
||||
### Linux
|
||||
|
||||
```bash
|
||||
sudo snap install waver
|
||||
sudo snap connect waver:audio-record :audio-record
|
||||
```
|
||||
|
||||
|
||||
## Todo
|
||||
|
||||
- [ ] Improve library interface
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
<a href="https://youtu.be/KWlcgZHJhGQ"><img src="../../media/waver-0-fast.gif"></a>
|
||||
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
|
||||
|
||||
<a href="https://apps.apple.com/us/app/waver-data-over-sound/id1543607865?itsct=apps_box&itscg=30200&ign-itsct=apps_box#?platform=iphone" style="display: inline-block; overflow: hidden; border-radius: 13px; width: 250px; height: 83px;"><img height="60px" src="https://tools.applemediaservices.com/api/badges/download-on-the-app-store/white/en-US?size=250x83&releaseDate=1607558400&h=8e5fafc57929918f684abc83ff8311ef" alt="Download on the App Store"></a>
|
||||
<a href='https://play.google.com/store/apps/details?id=com.ggerganov.Waver&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://i.imgur.com/BKDCbKv.png' height="60px"/></a>
|
||||
<a href="https://snapcraft.io/waver">
|
||||
<img alt="Get it from the Snap Store" src="https://snapcraft.io/static/images/badges/en/snap-store-black.svg" height="60px"/>
|
||||
</a>
|
||||
|
||||
```bash
|
||||
sudo snap install waver
|
||||
sudo snap connect waver:audio-record :audio-record
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
<a href="https://youtu.be/Zcgf77T71QM"><img width="100%" src="../../media/waver-preview0.png"></img></a>
|
||||
|
||||
- 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
|
||||
|
||||
BIN
examples/waver/background-0.png
Normal file
|
After Width: | Height: | Size: 357 KiB |
1
examples/waver/build_timestamp.h.tmpl
Normal file
@@ -0,0 +1 @@
|
||||
static const char * BUILD_TIMESTAMP="@GIT_DATE@ (@GIT_SHA1@)";
|
||||
@@ -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;
|
||||
|
||||
184
examples/waver/index-tmpl.html
Normal file
@@ -0,0 +1,184 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Waver</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/>
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="waver" />
|
||||
<meta name="twitter:description" content="Emscripten port of Waver" />
|
||||
<meta name="twitter:image" content="https://waver.ggerganov.com/waver.png" />
|
||||
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/apple-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/apple-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/apple-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/apple-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/apple-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/apple-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/apple-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/android-icon-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-controls">
|
||||
<div id="description" align="center">
|
||||
<h2>Waver: Data Over Sound</h2>
|
||||
|
||||
<div id="container_status" align="center">
|
||||
<button onClick="doInit()" id="butInit" style="width:60px;height:30px;" disabled>Init</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="container_canvas" hidden>
|
||||
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<div id="footer" class="cell-version">
|
||||
<span>
|
||||
Build time: <span class="nav-link">@GIT_DATE@</span> |
|
||||
Commit hash: <a class="nav-link" href="https://github.com/ggerganov/ggwave/commit/@GIT_SHA1@">@GIT_SHA1@</a> |
|
||||
Commit subject: <span class="nav-link">@GIT_COMMIT_SUBJECT@</span> |
|
||||
</span>
|
||||
</div>
|
||||
<div class="cell-about">
|
||||
<a class="nav-link" href="https://github.com/ggerganov/ggwave/tree/master/examples/waver"><span class="d-none d-sm-inline">View on GitHub </span>
|
||||
<svg version="1.1" width="16" height="16" viewBox="0 0 16 16" class="octicon octicon-mark-github" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<script type='text/javascript'>
|
||||
window.mobilecheck = function() {
|
||||
var check = false;
|
||||
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
|
||||
return check;
|
||||
};
|
||||
|
||||
var isMobile = mobilecheck();
|
||||
var isInitialized = false;
|
||||
|
||||
window.setInterval(function(){
|
||||
if (isInitialized == false) return;
|
||||
//var w = window,
|
||||
// d = document,
|
||||
// e = d.documentElement,
|
||||
// g = d.getElementsByTagName('body')[0],
|
||||
// x = w.innerWidth || e.clientWidth || g.clientWidth,
|
||||
// y = w.innerHeight|| e.clientHeight|| g.clientHeight;
|
||||
//Module._set_window_size(0.99*x, y - 1.40*document.getElementById('footer').clientHeight);
|
||||
}, 500);
|
||||
|
||||
function checkLoop() {
|
||||
setTimeout(checkLoop, 100);
|
||||
}
|
||||
|
||||
function checkSharedArrayBuffer() {
|
||||
try {
|
||||
var a = SharedArrayBuffer;
|
||||
} catch (e) {
|
||||
console.log(e instanceof ReferenceError); // true
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function onkeydown(event) {
|
||||
if (event.keyCode >= 112 && event.keyCode <= 123) {
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.getElementById("butInit").disabled = false;
|
||||
|
||||
window.addEventListener('keydown', onkeydown, true);
|
||||
|
||||
setTimeout(checkLoop, 100);
|
||||
//window.requestAnimationFrame(renderFrame);
|
||||
}
|
||||
|
||||
function doInit() {
|
||||
let constraints = {
|
||||
audio: {
|
||||
sampleRate: 16000,
|
||||
channelCount: 1,
|
||||
echoCancellation: false,
|
||||
autoGainControl: false,
|
||||
noiseSuppression: false
|
||||
}
|
||||
};
|
||||
|
||||
if (isInitialized == false) {
|
||||
Module._do_init();
|
||||
var but = document.getElementById("butInit");
|
||||
but.disabled = true;
|
||||
//but.hidden = true;
|
||||
but.parentNode.removeChild(but);
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
var x = document.getElementById("container_canvas");
|
||||
x.hidden = false;
|
||||
}
|
||||
|
||||
//function renderFrame() {
|
||||
// window.requestAnimationFrame(renderFrame);
|
||||
//}
|
||||
|
||||
var Module = {
|
||||
arguments: [],
|
||||
preRun: [(function() {
|
||||
}) ],
|
||||
postRun: [(function () {
|
||||
init();
|
||||
})
|
||||
],
|
||||
canvas: (function() {
|
||||
var canvas = document.getElementById('canvas');
|
||||
canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
|
||||
|
||||
return canvas;
|
||||
})(),
|
||||
print: (function() {
|
||||
return function(text) {
|
||||
text = Array.prototype.slice.call(arguments).join(' ');
|
||||
console.log(text);
|
||||
};
|
||||
})(),
|
||||
printErr: function(text) {
|
||||
text = Array.prototype.slice.call(arguments).join(' ');
|
||||
console.error(text);
|
||||
},
|
||||
setStatus: function(text) {
|
||||
console.log("status: " + text);
|
||||
},
|
||||
monitorRunDependencies: function(left) {
|
||||
// no run dependencies to log
|
||||
}
|
||||
};
|
||||
window.onerror = function() {
|
||||
console.log("onerror: " + JSON.stringify(event));
|
||||
if (checkSharedArrayBuffer() == false) {
|
||||
document.getElementById('container_status').innerHTML = "It seems your browser does not have SharedArrayBuffer support enabled.<br><br>Try openning this page on a PC with latest Chrome browser.";
|
||||
document.getElementById('container_status').style.color = "#ff0000";
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<script async type="text/javascript" src="waver.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
43
examples/waver/interface-emscripten.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "interface.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
#include "ggwave-common.h"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include "build_timestamp.h"
|
||||
#include "emscripten/emscripten.h"
|
||||
#else
|
||||
#define EMSCRIPTEN_KEEPALIVE
|
||||
#endif
|
||||
|
||||
#include <imgui-extra/imgui_impl.h>
|
||||
|
||||
#include <SDL.h>
|
||||
@@ -13,12 +20,16 @@
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
#include <functional>
|
||||
|
||||
namespace {
|
||||
void dummy() {}
|
||||
}
|
||||
|
||||
std::string getBinaryPath() {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
return "";
|
||||
#endif
|
||||
std::string result;
|
||||
void* p = reinterpret_cast<void*>(dummy);
|
||||
|
||||
@@ -179,16 +190,42 @@ bool ImGui_SetStyle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::function<bool()> g_doInit;
|
||||
static std::function<void(int, int)> g_setWindowSize;
|
||||
static std::function<bool()> 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<char>(1024));
|
||||
//addFile("test1.jpg", "test0.jpg", std::vector<char>(1024*1024 + 624));
|
||||
//addFile("test2.mpv", "test0.mov", std::vector<char>(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;
|
||||
}
|
||||
|
||||
324
examples/waver/style.css
Normal file
@@ -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%);
|
||||
}
|
||||
BIN
media/favicons-waver/android-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
media/favicons-waver/android-icon-192x192.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
media/favicons-waver/android-icon-36x36.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
media/favicons-waver/android-icon-48x48.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
media/favicons-waver/android-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
media/favicons-waver/android-icon-96x96.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
media/favicons-waver/apple-icon-114x114.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
media/favicons-waver/apple-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
media/favicons-waver/apple-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
media/favicons-waver/apple-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
media/favicons-waver/apple-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
media/favicons-waver/apple-icon-57x57.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
media/favicons-waver/apple-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
media/favicons-waver/apple-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
media/favicons-waver/apple-icon-76x76.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
media/favicons-waver/apple-icon-precomposed.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
media/favicons-waver/apple-icon.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
2
media/favicons-waver/browserconfig.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
|
||||
BIN
media/favicons-waver/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 937 B |
BIN
media/favicons-waver/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
media/favicons-waver/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
media/favicons-waver/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
41
media/favicons-waver/manifest.json
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
media/favicons-waver/ms-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
media/favicons-waver/ms-icon-150x150.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
media/favicons-waver/ms-icon-310x310.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
media/favicons-waver/ms-icon-70x70.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |