Merge pull request #3 from ggerganov/feature/file-sharing

ggwave-gui : file sharing support
This commit is contained in:
Georgi Gerganov
2020-12-30 20:09:47 +02:00
committed by GitHub
14 changed files with 2704 additions and 45 deletions

3
.gitmodules vendored
View File

@@ -4,3 +4,6 @@
[submodule "examples/third-party/imgui/imgui"]
path = examples/third-party/imgui/imgui
url = https://github.com/ocornut/imgui
[submodule "examples/third-party/ggsock"]
path = examples/third-party/ggsock
url = https://github.com/ggerganov/ggsock

View File

@@ -1,4 +1,4 @@
add_executable(ggwave-gui main.cpp common.cpp)
add_executable(ggwave-gui main.cpp common.cpp interface.cpp interface-unix.cpp)
target_include_directories(ggwave-gui PRIVATE
..
@@ -9,6 +9,7 @@ target_link_libraries(ggwave-gui PRIVATE
ggwave
ggwave-common
ggwave-common-sdl2
ggsock
imgui-sdl2
${CMAKE_THREAD_LIBS_INIT}
)

View File

@@ -1,14 +1,19 @@
#include "common.h"
#include "ggwave-common.h"
#include "ggwave/ggwave.h"
#include "ggwave-common.h"
#include "ggsock/communicator.h"
#include "ggsock/file-server.h"
#include "ggsock/serialization.h"
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include <SDL.h>
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdio>
@@ -17,6 +22,8 @@
#include <mutex>
#include <thread>
#include <vector>
#include <sstream>
#include <cstring>
#if defined(IOS) || defined(ANDROID)
#include "imgui-wrapper/icons_font_awesome.h"
@@ -29,14 +36,97 @@
#define ICON_FA_PLAY_CIRCLE ""
#define ICON_FA_ARROW_CIRCLE_DOWN "V"
#define ICON_FA_PASTE "P"
#define ICON_FA_FILE ""
#endif
namespace {
char * toTimeString(const std::chrono::system_clock::time_point & tp) {
time_t t = std::chrono::system_clock::to_time_t(tp);
std::tm * ptm = std::localtime(&t);
static char buffer[32];
std::strftime(buffer, 32, "%H:%M:%S", ptm);
return buffer;
}
void ScrollWhenDraggingOnVoid(const ImVec2& delta, ImGuiMouseButton mouse_button) {
ImGuiContext& g = *ImGui::GetCurrentContext();
ImGuiWindow* window = g.CurrentWindow;
bool hovered = false;
bool held = false;
ImGuiButtonFlags button_flags = (mouse_button == 0) ? ImGuiButtonFlags_MouseButtonLeft : (mouse_button == 1) ? ImGuiButtonFlags_MouseButtonRight : ImGuiButtonFlags_MouseButtonMiddle;
if (g.HoveredId == 0) // If nothing hovered so far in the frame (not same as IsAnyItemHovered()!)
ImGui::ButtonBehavior(window->Rect(), window->GetID("##scrolldraggingoverlay"), &hovered, &held, button_flags);
if (held && delta.x != 0.0f)
ImGui::SetScrollX(window, window->Scroll.x + delta.x);
if (held && delta.y != 0.0f)
ImGui::SetScrollY(window, window->Scroll.y + delta.y);
}
}
namespace ImGui {
bool ButtonDisabled(const char* label, const ImVec2& size = ImVec2(0, 0)) {
{
auto col = ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled);
col.x *= 0.8;
col.y *= 0.8;
col.z *= 0.8;
PushStyleColor(ImGuiCol_Button, col);
PushStyleColor(ImGuiCol_ButtonHovered, col);
PushStyleColor(ImGuiCol_ButtonActive, col);
}
{
auto col = ImGui::GetStyleColorVec4(ImGuiCol_Text);
col.x *= 0.75;
col.y *= 0.75;
col.z *= 0.75;
PushStyleColor(ImGuiCol_Text, col);
}
bool result = Button(label, size);
PopStyleColor(4);
return result;
}
bool ButtonDisablable(const char* label, const ImVec2& size = ImVec2(0, 0), bool isDisabled = false) {
if (isDisabled) {
ButtonDisabled(label, size);
return false;
}
return Button(label, size);
}
bool ButtonSelected(const char* label, const ImVec2& size = ImVec2(0, 0)) {
auto col = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive);
PushStyleColor(ImGuiCol_Button, col);
PushStyleColor(ImGuiCol_ButtonHovered, col);
bool result = Button(label, size);
PopStyleColor(2);
return result;
}
bool ButtonSelectable(const char* label, const ImVec2& size = ImVec2(0, 0), bool isSelected = false) {
if (isSelected) return ButtonSelected(label, size);
return Button(label, size);
}
}
static const char * kFileBroadcastPrefix = "\xba\xbc\xbb";
static const int kMaxSimultaneousChunkRequests = 4;
static const float kBroadcastTime_sec = 60.0f;
struct Message {
enum Type {
Text,
FileBroadcast,
};
bool received;
std::chrono::system_clock::time_point timestamp;
std::string data;
int protocolId;
float volume;
Type type;
};
struct GGWaveStats {
@@ -112,36 +202,273 @@ struct Buffer {
Input inputUI;
};
char * toTimeString(const std::chrono::system_clock::time_point & tp) {
time_t t = std::chrono::system_clock::to_time_t(tp);
std::tm * ptm = std::localtime(&t);
static char buffer[32];
std::strftime(buffer, 32, "%H:%M:%S", ptm);
return buffer;
}
void ScrollWhenDraggingOnVoid(const ImVec2& delta, ImGuiMouseButton mouse_button) {
ImGuiContext& g = *ImGui::GetCurrentContext();
ImGuiWindow* window = g.CurrentWindow;
bool hovered = false;
bool held = false;
ImGuiButtonFlags button_flags = (mouse_button == 0) ? ImGuiButtonFlags_MouseButtonLeft : (mouse_button == 1) ? ImGuiButtonFlags_MouseButtonRight : ImGuiButtonFlags_MouseButtonMiddle;
if (g.HoveredId == 0) // If nothing hovered so far in the frame (not same as IsAnyItemHovered()!)
ImGui::ButtonBehavior(window->Rect(), window->GetID("##scrolldraggingoverlay"), &hovered, &held, button_flags);
if (held && delta.x != 0.0f)
ImGui::SetScrollX(window, window->Scroll.x + delta.x);
if (held && delta.y != 0.0f)
ImGui::SetScrollY(window, window->Scroll.y + delta.y);
}
GGWave * g_ggWave;
Buffer g_buffer;
std::atomic<bool> g_isRunning;
// file send data
struct BroadcastInfo {
std::string ip;
int port;
int key;
};
bool g_focusFileSend = false;
float g_tLastBroadcast = -100.0f;
GGSock::FileServer g_fileServer;
// file received data
struct FileInfoExtended {
bool isReceiving = false;
bool readyToShare = false;
bool requestToShare = false;
int nReceivedChunks = 0;
int nRequestedChunks = 0;
std::vector<bool> isChunkRequested;
std::vector<bool> isChunkReceived;
};
bool g_hasRemoteInfo = false;
int g_remotePort = 23045;
std::string g_remoteIP = "127.0.0.1";
bool g_hasReceivedFileInfos = false;
bool g_hasRequestedFileInfos = false;
bool g_hasReceivedFiles = false;
GGSock::FileServer::TFileInfos g_receivedFileInfos;
std::map<GGSock::FileServer::TURI, GGSock::FileServer::FileData> g_receivedFiles;
std::map<GGSock::FileServer::TURI, FileInfoExtended> g_receivedFileInfosExtended;
GGSock::Communicator g_fileClient(false);
// external api
int g_shareId = 0;
ShareInfo g_shareInfo;
int g_deleteId = 0;
DeleteInfo g_deleteInfo;
int g_receivedId = 0;
int getShareId() {
return g_shareId;
}
ShareInfo getShareInfo() {
return g_shareInfo;
}
int getDeleteId() {
return g_deleteId;
}
DeleteInfo getDeleteInfo() {
return g_deleteInfo;
}
int getReceivedId() {
return g_receivedId;
}
std::vector<ReceiveInfo> getReceiveInfos() {
std::vector<ReceiveInfo> result;
for (const auto & file : g_receivedFiles) {
if (g_receivedFileInfosExtended[file.second.info.uri].requestToShare == false ||
g_receivedFileInfosExtended[file.second.info.uri].readyToShare == true) {
continue;
}
result.push_back({
file.second.info.uri.c_str(),
file.second.info.filename.c_str(),
file.second.data.data(),
file.second.data.size(),
});
}
return result;
}
bool confirmReceive(const char * uri) {
if (g_receivedFiles.find(uri) == g_receivedFiles.end()) {
return false;
}
g_receivedFileInfosExtended[uri].readyToShare = true;
g_receivedFiles.erase(uri);
return true;
}
void clearAllFiles() {
g_fileServer.clearAllFiles();
}
void clearFile(const char * uri) {
g_fileServer.clearFile(uri);
}
void addFile(
const char * uri,
const char * filename,
const char * dataBuffer,
size_t dataSize) {
GGSock::FileServer::FileData file;
file.info.uri = uri;
file.info.filename = filename;
file.data.resize(dataSize);
std::memcpy(file.data.data(), dataBuffer, dataSize);
g_fileServer.addFile(std::move(file));
//g_focusFileSend = true;
}
void addFile(
const char * uri,
const char * filename,
std::vector<char> && data) {
GGSock::FileServer::FileData file;
file.info.uri = uri;
file.info.filename = filename;
file.data = std::move(data);
g_fileServer.addFile(std::move(file));
//g_focusFileSend = true;
}
std::string generateFileBroadcastMessage() {
// todo : to binary
std::string result;
int plen = strlen(kFileBroadcastPrefix);
result.resize(plen + 4 + 2 + 2);
char *p = &result[0];
for (int i = 0; i < (int) plen; ++i) {
*p++ = kFileBroadcastPrefix[i];
}
{
auto ip = GGSock::Communicator::getLocalAddress();
std::replace(ip.begin(), ip.end(), '.', ' ');
std::stringstream ss(ip);
{ int b; ss >> b; *p++ = b; }
{ int b; ss >> b; *p++ = b; }
{ int b; ss >> b; *p++ = b; }
{ int b; ss >> b; *p++ = b; }
}
{
uint16_t port = g_fileServer.getParameters().listenPort;
{ int b = port/256; *p++ = b; }
{ int b = port%256; *p++ = b; }
}
{
uint16_t key = rand()%65536;
{ int b = key/256; *p++ = b; }
{ int b = key%256; *p++ = b; }
}
return result;
}
BroadcastInfo parseBroadcastInfo(const std::string & message) {
BroadcastInfo result;
const uint8_t *p = (uint8_t *) message.data();
p += strlen(kFileBroadcastPrefix);
result.ip += std::to_string((uint8_t)(*p++));
result.ip += '.';
result.ip += std::to_string((uint8_t)(*p++));
result.ip += '.';
result.ip += std::to_string((uint8_t)(*p++));
result.ip += '.';
result.ip += std::to_string((uint8_t)(*p++));
result.port = 256*((int)(*p++));
result.port += ((int)(*p++));
result.key = 256*((int)(*p++));
result.key += ((int)(*p++));
return result;
}
bool isFileBroadcastMessage(const std::string & message) {
if (message.size() != strlen(kFileBroadcastPrefix) + 4 + 2 + 2) {
return false;
}
bool result = true;
auto pSrc = kFileBroadcastPrefix;
auto pDst = message.data();
while (*pSrc != 0) {
if (*pDst == 0 || *pSrc++ != *pDst++) {
result = false;
break;
}
}
return result;
}
std::thread initMain() {
g_isRunning = true;
g_ggWave = GGWave_instance();
g_fileServer.init({});
g_fileClient.setErrorCallback([](GGSock::Communicator::TErrorCode code) {
printf("Disconnected with code = %d\n", code);
g_hasReceivedFileInfos = false;
g_hasRequestedFileInfos = false;
g_hasReceivedFiles = false;
});
g_fileClient.setMessageCallback(GGSock::FileServer::MsgFileInfosResponse, [&](const char * dataBuffer, size_t dataSize) {
printf("Received message %d, size = %d\n", GGSock::FileServer::MsgFileInfosResponse, (int) dataSize);
size_t offset = 0;
GGSock::Unserialize()(g_receivedFileInfos, dataBuffer, dataSize, offset);
for (const auto & info : g_receivedFileInfos) {
printf(" - %s : %s (size = %d, chunks = %d)\n", info.second.uri.c_str(), info.second.filename.c_str(), (int) info.second.filesize, (int) info.second.nChunks);
g_receivedFiles[info.second.uri].info = info.second;
g_receivedFiles[info.second.uri].data.resize(info.second.filesize);
g_receivedFileInfosExtended[info.second.uri] = {};
}
g_hasReceivedFileInfos = true;
return 0;
});
g_fileClient.setMessageCallback(GGSock::FileServer::MsgFileChunkResponse, [&](const char * dataBuffer, size_t dataSize) {
GGSock::FileServer::FileChunkResponseData data;
size_t offset = 0;
GGSock::Unserialize()(data, dataBuffer, dataSize, offset);
//printf("Received chunk %d for file '%s', size = %d\n", data.chunkId, data.uri.c_str(), (int) data.data.size());
std::memcpy(g_receivedFiles[data.uri].data.data() + data.pStart, data.data.data(), data.pLen);
g_receivedFileInfosExtended[data.uri].nReceivedChunks++;
g_receivedFileInfosExtended[data.uri].nRequestedChunks--;
g_receivedFileInfosExtended[data.uri].isChunkReceived[data.chunkId] = true;
return 0;
});
return std::thread([&]() {
Input inputCurrent;
@@ -171,14 +498,17 @@ std::thread initMain() {
lastRxDataLength = g_ggWave->takeRxData(lastRxData);
if (lastRxDataLength > 0) {
auto message = std::string((char *) lastRxData.data(), lastRxDataLength);
const Message::Type type = isFileBroadcastMessage(message) ? Message::FileBroadcast : Message::Text;
g_buffer.stateCore.update = true;
g_buffer.stateCore.flags.newMessage = true;
g_buffer.stateCore.message = {
true,
std::chrono::system_clock::now(),
std::string((char *) lastRxData.data(), lastRxDataLength),
std::move(message),
g_ggWave->getRxProtocolId(),
0,
type,
};
}
@@ -214,6 +544,63 @@ std::thread initMain() {
}
void renderMain() {
g_fileServer.update();
if (ImGui::GetTime() - g_tLastBroadcast > kBroadcastTime_sec && g_fileServer.isListening()) {
g_fileServer.stopListening();
}
if (g_fileClient.isConnected()) {
if (!g_hasRequestedFileInfos) {
g_receivedFileInfos.clear();
g_receivedFiles.clear();
g_receivedFileInfosExtended.clear();
g_fileClient.send(GGSock::FileServer::MsgFileInfosRequest);
g_hasRequestedFileInfos = true;
} else {
for (const auto & fileInfo : g_receivedFileInfos) {
const auto & uri = fileInfo.second.uri;
auto & fileInfoExtended = g_receivedFileInfosExtended[uri];
if (fileInfoExtended.isReceiving == false) {
continue;
}
if (fileInfoExtended.nReceivedChunks == fileInfo.second.nChunks) {
continue;
}
int nextChunkId = 0;
while (fileInfoExtended.nRequestedChunks < kMaxSimultaneousChunkRequests) {
if (fileInfoExtended.nReceivedChunks + fileInfoExtended.nRequestedChunks == fileInfo.second.nChunks) {
break;
}
while (fileInfoExtended.isChunkRequested[nextChunkId] == true) {
++nextChunkId;
}
fileInfoExtended.isChunkRequested[nextChunkId] = true;
GGSock::FileServer::FileChunkRequestData data;
data.uri = fileInfo.second.uri;
data.chunkId = nextChunkId;
data.nChunksHave = 0;
data.nChunksExpected = fileInfo.second.nChunks;
GGSock::SerializationBuffer buffer;
GGSock::Serialize()(data, buffer);
g_fileClient.send(GGSock::FileServer::MsgFileChunkRequest, buffer.data(), (int32_t) buffer.size());
g_fileClient.update();
++fileInfoExtended.nRequestedChunks;
}
}
}
}
g_fileClient.update();
static State stateCurrent;
{
@@ -224,15 +611,23 @@ void renderMain() {
enum class WindowId {
Settings,
Messages,
Files,
Spectrum,
};
enum SubWindowId {
Send,
Receive,
};
struct Settings {
int protocolId = 1;
float volume = 0.10f;
};
static WindowId windowId = WindowId::Messages;
static SubWindowId subWindowId = SubWindowId::Send;
static Settings settings;
const double tHoldContextPopup = 0.5f;
@@ -290,6 +685,12 @@ void renderMain() {
stateCurrent.update = false;
}
if (g_focusFileSend) {
windowId = WindowId::Files;
subWindowId = SubWindowId::Send;
g_focusFileSend = false;
}
if (lastMouseButtonLeft == 0 && ImGui::GetIO().MouseDown[0] == 1) {
ImGui::GetIO().MouseDelta = { 0.0, 0.0 };
}
@@ -307,6 +708,8 @@ void renderMain() {
#endif
const float menuButtonHeight = 24.0f + 2.0f*style.ItemSpacing.y;
const auto & mouse_delta = ImGui::GetIO().MouseDelta;
ImGui::SetNextWindowPos({ 0, 0, });
ImGui::SetNextWindowSize(displaySize);
ImGui::Begin("Main", nullptr,
@@ -318,17 +721,22 @@ void renderMain() {
ImGui::InvisibleButton("StatusBar", { ImGui::GetContentRegionAvailWidth(), statusBarHeight });
if (ImGui::Button(ICON_FA_COGS, { menuButtonHeight, menuButtonHeight } )) {
if (ImGui::ButtonSelectable(ICON_FA_COGS, { menuButtonHeight, menuButtonHeight }, windowId == WindowId::Settings )) {
windowId = WindowId::Settings;
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_COMMENT_ALT " Messages", { 0.5f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) {
if (ImGui::ButtonSelectable(ICON_FA_COMMENT_ALT " Messages", { 0.35f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }, windowId == WindowId::Messages)) {
windowId = WindowId::Messages;
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_SIGNAL " Spectrum", { 1.0f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) {
if (ImGui::ButtonSelectable(ICON_FA_FILE " Files", { 0.40f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }, windowId == WindowId::Files)) {
windowId = WindowId::Files;
}
ImGui::SameLine();
if (ImGui::ButtonSelectable(ICON_FA_SIGNAL " Spectrum", { 1.0f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }, windowId == WindowId::Spectrum)) {
windowId = WindowId::Spectrum;
}
@@ -336,7 +744,7 @@ void renderMain() {
ImGui::BeginChild("Settings:main", ImGui::GetContentRegionAvail(), true);
ImGui::Text("%s", "");
ImGui::Text("%s", "");
ImGui::Text("Waver v1.2.0");
ImGui::Text("Waver v1.3.0");
ImGui::Separator();
ImGui::Text("%s", "");
@@ -460,7 +868,6 @@ void renderMain() {
static bool isHoldingInput = false;
static int messageIdHolding = 0;
const auto & mouse_delta = ImGui::GetIO().MouseDelta;
const float tMessageFlyIn = 0.3f;
// we need this because we push messages in the next loop
@@ -493,9 +900,16 @@ void renderMain() {
p0.x -= style.ItemSpacing.x;
p0.y -= 0.5f*style.ItemSpacing.y;
auto col = style.Colors[ImGuiCol_Text];
col.w = interp;
ImGui::TextColored(col, "%s", message.data.c_str());
if (message.type == Message::FileBroadcast) {
auto col = ImVec4 { 0.0f, 1.0f, 1.0f, 1.0f };
col.w = interp;
auto broadcastInfo = parseBroadcastInfo(message.data);
ImGui::TextColored(col, "-=[ File Broadcast from %s:%d ]=-", broadcastInfo.ip.c_str(), broadcastInfo.port);
} else {
auto col = style.Colors[ImGuiCol_Text];
col.w = interp;
ImGui::TextColored(col, "%s", message.data.c_str());
}
auto p1 = ImGui::GetCursorScreenPos();
p1.x = p00.x + ImGui::GetContentRegionAvailWidth();
@@ -534,9 +948,9 @@ void renderMain() {
if (ImGui::BeginPopup("Message options")) {
const auto & messageSelected = messageHistory[messageIdHolding];
if (ImGui::Button("Resend")) {
if (ImGui::ButtonDisablable("Resend", {}, messageSelected.type != Message::Text)) {
g_buffer.inputUI.update = true;
g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), messageSelected.data, messageSelected.protocolId, settings.volume };
g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), messageSelected.data, messageSelected.protocolId, settings.volume, Message::Text };
messageHistory.push_back(g_buffer.inputUI.message);
ImGui::CloseCurrentPopup();
@@ -546,11 +960,35 @@ void renderMain() {
ImGui::TextDisabled("|");
ImGui::SameLine();
if (ImGui::Button("Copy")) {
if (ImGui::ButtonDisablable("Copy", {}, messageSelected.type != Message::Text)) {
SDL_SetClipboardText(messageSelected.data.c_str());
ImGui::CloseCurrentPopup();
}
if (messageSelected.type == Message::FileBroadcast) {
ImGui::SameLine();
ImGui::TextDisabled("|");
ImGui::SameLine();
if (ImGui::ButtonDisablable("Receive", {}, !messageSelected.received || messageSelected.type != Message::FileBroadcast)) {
auto broadcastInfo = parseBroadcastInfo(messageSelected.data);
g_remoteIP = broadcastInfo.ip;
g_remotePort = broadcastInfo.port;
g_hasRemoteInfo = true;
g_fileClient.disconnect();
g_hasReceivedFileInfos = false;
g_hasRequestedFileInfos = false;
g_hasReceivedFiles = false;
windowId = WindowId::Files;
subWindowId = SubWindowId::Receive;
ImGui::CloseCurrentPopup();
}
}
ImGui::EndPopup();
}
@@ -692,7 +1130,7 @@ void renderMain() {
ImGui::SameLine();
if ((ImGui::Button(sendButtonText) || doSendMessage) && inputBuf[0] != 0) {
g_buffer.inputUI.update = true;
g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), std::string(inputBuf), settings.protocolId, settings.volume };
g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), std::string(inputBuf), settings.protocolId, settings.volume, Message::Text };
messageHistory.push_back(g_buffer.inputUI.message);
@@ -707,6 +1145,247 @@ void renderMain() {
}
}
if (windowId == WindowId::Files) {
const float subWindowButtonHeight = menuButtonHeight;
if (ImGui::ButtonSelectable("Send", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, subWindowId == SubWindowId::Send)) {
subWindowId = SubWindowId::Send;
}
ImGui::SameLine();
if (ImGui::ButtonSelectable("Receive", { 1.0f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, subWindowId == SubWindowId::Receive)) {
subWindowId = SubWindowId::Receive;
}
switch (subWindowId) {
case SubWindowId::Send:
{
const float statusWindowHeight = 2*style.ItemInnerSpacing.y + 4*ImGui::GetTextLineHeightWithSpacing();
bool hasAtLeastOneFile = false;
{
const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y - subWindowButtonHeight - statusWindowHeight - 2*style.ItemInnerSpacing.y };
ImGui::BeginChild("Files:Send:fileInfos", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
auto fileInfos = g_fileServer.getFileInfos();
for (const auto & fileInfo : fileInfos) {
hasAtLeastOneFile = true;
ImGui::PushID(fileInfo.first);
ImGui::Text("File: '%s' (%4.2f MB)\n", fileInfo.second.filename.c_str(), float(fileInfo.second.filesize)/1024.0f/1024.0f);
if (ImGui::Button("Save")) {
g_shareInfo.uri = fileInfo.second.uri.data();
g_shareInfo.filename = fileInfo.second.filename.data();
const auto & fileData = g_fileServer.getFileData(g_shareInfo.uri);
g_shareInfo.dataBuffer = fileData.data.data();
g_shareInfo.dataSize = fileData.data.size();
g_shareId++;
}
ImGui::SameLine();
if (ImGui::Button("Clear")) {
g_deleteInfo.uri = fileInfo.second.uri.data();
g_deleteInfo.filename = fileInfo.second.filename.data();
g_deleteId++;
}
ImGui::PopID();
}
ImGui::PushTextWrapPos();
if (hasAtLeastOneFile == false) {
ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "There are currently no files availble to share.");
#if defined(IOS) || defined(ANDROID)
ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "Share some files with this app to be able to broadcast them to nearby devices through sound.");
#else
ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "Drag and drop some files on this window to be able to broadcast them to nearby devices through sound.");
#endif
}
ImGui::PopTextWrapPos();
ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left);
ImGui::EndChild();
}
{
const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y - subWindowButtonHeight - style.ItemInnerSpacing.y };
ImGui::BeginChild("Files:Send:clientInfos", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
if (g_fileServer.isListening() == false) {
ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "Not accepting new connections.");
} else {
ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Accepting new connections at: %s:%d (%4.1f sec)",
GGSock::Communicator::getLocalAddress().c_str(), g_fileServer.getParameters().listenPort, kBroadcastTime_sec - ImGui::GetTime() + g_tLastBroadcast);
}
auto clientInfos = g_fileServer.getClientInfos();
if (clientInfos.empty()) {
ImGui::Text("No peers currently connected ..");
} else {
for (const auto & clientInfo : clientInfos) {
ImGui::Text("Peer: %s\n", clientInfo.second.address.c_str());
}
}
ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left);
ImGui::EndChild();
}
{
if (ImGui::Button("Broadcast", { 0.40f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
g_buffer.inputUI.update = true;
g_buffer.inputUI.message = {
false,
std::chrono::system_clock::now(),
::generateFileBroadcastMessage(),
settings.protocolId,
settings.volume,
Message::FileBroadcast
};
messageHistory.push_back(g_buffer.inputUI.message);
g_tLastBroadcast = ImGui::GetTime();
g_fileServer.startListening();
}
ImGui::SameLine();
if (ImGui::ButtonDisablable("Stop", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, !g_fileServer.isListening())) {
g_fileServer.stopListening();
}
ImGui::SameLine();
if (ImGui::ButtonDisablable("Clear", { 1.0f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, !hasAtLeastOneFile)) {
g_deleteInfo.uri = "###ALL-FILES###";
g_deleteInfo.filename = "";
g_deleteId++;
}
}
}
break;
case SubWindowId::Receive:
{
const float statusWindowHeight = 2*style.ItemInnerSpacing.y + 4*ImGui::GetTextLineHeightWithSpacing();
{
const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y - subWindowButtonHeight - statusWindowHeight - 2*style.ItemInnerSpacing.y };
ImGui::BeginChild("Files:Receive:fileInfos", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
int toErase = -1;
auto fileInfos = g_receivedFileInfos;
for (const auto & fileInfo : fileInfos) {
ImGui::PushID(fileInfo.first);
ImGui::Text("File: '%s' (%4.2f MB)\n", fileInfo.second.filename.c_str(), float(fileInfo.second.filesize)/1024.0f/1024.0f);
const auto & uri = fileInfo.second.uri;
auto & fileInfoExtended = g_receivedFileInfosExtended[uri];
const bool isReceived = fileInfo.second.nChunks == fileInfoExtended.nReceivedChunks;
if (isReceived) {
if (fileInfoExtended.requestToShare == false) {
if (ImGui::Button("To Send")) {
fileInfoExtended.requestToShare = true;
g_receivedId++;
}
}
if (fileInfoExtended.readyToShare) {
ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Ready to share!");
}
} else if (g_fileClient.isConnected() && (fileInfoExtended.isReceiving || fileInfoExtended.nReceivedChunks > 0)) {
if (fileInfoExtended.isReceiving) {
if (ImGui::Button("Pause")) {
fileInfoExtended.isReceiving = false;
}
} else {
if (ImGui::Button("Resume")) {
fileInfoExtended.isReceiving = true;
}
}
ImGui::SameLine();
ImGui::ProgressBar(float(fileInfoExtended.nReceivedChunks)/fileInfo.second.nChunks);
} else if (g_fileClient.isConnected()) {
if (ImGui::Button("Receive")) {
fileInfoExtended.isReceiving = true;
fileInfoExtended.isChunkReceived.resize(fileInfo.second.nChunks);
fileInfoExtended.isChunkRequested.resize(fileInfo.second.nChunks);
}
} else {
ImGui::Text("%s", "");
}
if ((fileInfoExtended.isReceiving == false || isReceived) && fileInfoExtended.requestToShare == false) {
ImGui::SameLine();
if (ImGui::Button("Clear")) {
toErase = fileInfo.first;
}
}
ImGui::PopID();
}
if (toErase != -1) {
g_receivedFiles.erase(g_receivedFileInfos[toErase].uri);
g_receivedFileInfosExtended.erase(g_receivedFileInfos[toErase].uri);
g_receivedFileInfos.erase(toErase);
}
ImGui::EndChild();
}
{
const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y - subWindowButtonHeight - style.ItemInnerSpacing.y };
ImGui::BeginChild("Files:Receive:status", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::PushTextWrapPos();
if (g_hasRemoteInfo == false) {
ImGui::TextColored({ 1.0f, 0.0f, 0.0f, 1.0f }, "There is no broadcast offer selected yet.");
ImGui::TextColored({ 1.0f, 0.0f, 0.0f, 1.0f }, "Wait for a broadcast message to be received first.");
} else {
if (g_fileClient.isConnected()) {
ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Successfully connected to peer:");
} else {
ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "Broadcast offer from the following peer:");
}
ImGui::Text("Remote IP: %s", g_remoteIP.c_str());
ImGui::Text("Remote Port: %d", g_remotePort);
if (g_fileClient.isConnecting()) {
ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "Attempting to connect ...");
}
}
ImGui::PopTextWrapPos();
ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left);
ImGui::EndChild();
}
{
if (g_fileClient.isConnecting() == false && g_fileClient.isConnected() == false) {
if (ImGui::ButtonDisablable("Connect", { 1.00f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, !g_hasRemoteInfo)) {
g_fileClient.connect(g_remoteIP, g_remotePort, 0);
}
}
if (g_fileClient.isConnecting() || g_fileClient.isConnected()) {
if (ImGui::Button("Disconnect", { 1.00f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
g_fileClient.disconnect();
g_hasReceivedFileInfos = false;
g_hasRequestedFileInfos = false;
g_hasReceivedFiles = false;
}
}
}
}
break;
};
}
if (windowId == WindowId::Spectrum) {
ImGui::BeginChild("Spectrum:main", ImGui::GetContentRegionAvail(), true);
ImGui::PushTextWrapPos();

View File

@@ -3,7 +3,59 @@
#include "ggwave-common-sdl2.h"
#include <thread>
#include <vector>
std::thread initMain();
void renderMain();
void deinitMain(std::thread & worker);
// share info
struct ShareInfo {
const char * uri;
const char * filename;
const char * dataBuffer;
size_t dataSize;
};
int getShareId();
ShareInfo getShareInfo();
// delete file
struct DeleteInfo {
std::string uri;
std::string filename;
};
int getDeleteId();
DeleteInfo getDeleteInfo();
// receive
struct ReceiveInfo {
const char * uri;
const char * filename;
const char * dataBuffer;
size_t dataSize;
};
int getReceivedId();
std::vector<ReceiveInfo> getReceiveInfos();
bool confirmReceive(const char * uri);
// input
void clearAllFiles();
void clearFile(const char * uri);
void addFile(
const char * uri,
const char * filename,
const char * dataBuffer,
size_t dataSize);
void addFile(
const char * uri,
const char * filename,
std::vector<char> && data);

View File

@@ -0,0 +1,49 @@
#include "interface.h"
#include "pfd/pfd.h"
#include <fstream>
void interface_addFile(
const char * ,
const char * ,
const char * ,
size_t ) {
}
void interface_loadAllFiles() {
}
void interface_shareFile(
const char * ,
const char * filename,
const char * dataBuffer,
size_t dataSize) {
auto f = pfd::save_file("Save file", filename, { "All Files", "*" }, pfd::opt::none);
if (f.result().empty() == false) {
printf("Saving to: %s\n", f.result().c_str());
std::ofstream fout(f.result(), std::ios::binary);
if (fout.is_open() && fout.good()) {
fout.write(dataBuffer, dataSize);
fout.close();
}
}
}
void interface_deleteFile(
const char * ,
const char * ) {
}
void interface_receiveFile(
const char * uri,
const char * filename,
const char * dataBuffer,
size_t dataSize) {
addFile(uri, filename, dataBuffer, dataSize);
}
bool interface_needReloadFiles() {
return false;
}

View File

@@ -0,0 +1,60 @@
#include "interface.h"
int g_lastShareId = 0;
int g_lastDeleteId = 0;
int g_lastReceivedId = 0;
int g_frameCount = 0;
void updateMain() {
auto curShareId = getShareId();
if (curShareId != g_lastShareId) {
auto shareInfo = getShareInfo();
interface_shareFile(
shareInfo.uri,
shareInfo.filename,
shareInfo.dataBuffer,
shareInfo.dataSize);
g_lastShareId = curShareId;
}
auto curDeleteId = getDeleteId();
if (curDeleteId != g_lastDeleteId) {
auto deleteInfo = getDeleteInfo();
interface_deleteFile(deleteInfo.uri.c_str(), deleteInfo.filename.c_str());
bool isRemoveAll = std::string(deleteInfo.uri) == "###ALL-FILES###";
if (interface_needReloadFiles() || isRemoveAll) {
clearAllFiles();
interface_loadAllFiles();
} else {
clearFile(deleteInfo.uri.c_str());
}
g_lastDeleteId = curDeleteId;
}
auto curReceivedId = getReceivedId();
if (curReceivedId != g_lastReceivedId) {
auto receiveInfos = getReceiveInfos();
int n = (int) receiveInfos.size();
for (int i = 0; i < n; ++i) {
interface_receiveFile(
receiveInfos[i].uri,
receiveInfos[i].filename,
receiveInfos[i].dataBuffer,
receiveInfos[i].dataSize);
confirmReceive(receiveInfos[i].uri);
}
if (interface_needReloadFiles()) {
clearAllFiles();
interface_loadAllFiles();
}
g_lastReceivedId = curReceivedId;
}
}

View File

@@ -0,0 +1,42 @@
#ifndef interface_h
#define interface_h
#ifdef __cplusplus
#include "common.h"
extern "C" {
#endif
void interface_addFile(
const char * uri,
const char * filename,
const char * dataBuffer,
size_t dataSize);
void interface_loadAllFiles();
void interface_shareFile(
const char * uri,
const char * filename,
const char * dataBuffer,
size_t dataSize);
void interface_deleteFile(
const char * uri,
const char * filename);
void interface_receiveFile(
const char * uri,
const char * filename,
const char * dataBuffer,
size_t dataSize);
bool interface_needReloadFiles();
void updateMain();
#ifdef __cplusplus
}
#endif
#endif /* interface_h */

View File

@@ -1,4 +1,4 @@
#include "common.h"
#include "interface.h"
#include "ggwave-common.h"
@@ -7,10 +7,30 @@
#include <SDL.h>
#include <SDL_opengl.h>
// ImGui helpers
#include <fstream>
#include <vector>
#include <iterator>
bool ImGui_BeginFrame(SDL_Window * window);
bool ImGui_EndFrame(SDL_Window * window);
std::vector<char> readFile(const char* filename) {
std::ifstream file(filename, std::ios::binary);
file.unsetf(std::ios::skipws);
std::streampos fileSize;
file.seekg(0, std::ios::end);
fileSize = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> vec;
vec.reserve(fileSize);
vec.insert(vec.begin(),
std::istream_iterator<char>(file),
std::istream_iterator<char>());
return vec;
}
// ImGui helpers
bool ImGui_BeginFrame(SDL_Window * window) {
SDL_Event event;
@@ -19,6 +39,17 @@ bool ImGui_BeginFrame(SDL_Window * window) {
ImGui_ProcessEvent(&event);
if (event.type == SDL_QUIT) return false;
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) return false;
if (event.type == SDL_DROPFILE) {
printf("Dropped file: '%s'\n", event.drop.file);
auto data = readFile(event.drop.file);
std::string uri = event.drop.file;
std::string filename = event.drop.file;
if (uri.find("/") || uri.find("\\")) {
filename = uri.substr(uri.find_last_of("/\\") + 1);
}
addFile(uri.c_str(), filename.c_str(), std::move(data));
break;
}
}
ImGui_NewFrame(window);
@@ -123,31 +154,41 @@ int main(int argc, char** argv) {
return -1;
}
ImGui_PreInit();
int windowX = 400;
int windowY = 600;
const char * windowTitle = "ggwave-gui";
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);
void * gl_context = SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, gl_context);
SDL_GL_SetSwapInterval(1); // Enable vsync
ImGui_Init(window, gl_context);
ImGui_SetStyle();
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
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) {
if (ImGui_BeginFrame(window) == false) {
break;
}
renderMain();
updateMain();
ImGui_EndFrame(window);
}

14
examples/pfd/COPYING Normal file
View File

@@ -0,0 +1,14 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

1704
examples/pfd/pfd.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,3 +2,4 @@ if (NOT EMSCRIPTEN)
endif()
add_subdirectory(imtui)
add_subdirectory(imgui)
add_subdirectory(ggsock)

1
examples/third-party/ggsock vendored Submodule

View File

@@ -17,18 +17,16 @@
#include <cstdio>
ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context) {
bool ImGui_PreInit() {
// Decide GL+GLSL versions
#if __APPLE__
// GL 3.2 Core + GLSL 150
const char* glsl_version = "#version 150";
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
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);
#else
// GL 3.0 + GLSL 130
const char* glsl_version = "#version 130";
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
@@ -40,6 +38,19 @@ ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context) {
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
return true;
}
ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context) {
// Decide GL+GLSL versions
#if __APPLE__
// GL 3.2 Core + GLSL 150
const char* glsl_version = "#version 150";
#else
// GL 3.0 + GLSL 130
const char* glsl_version = "#version 130";
#endif
static bool isInitialized = false;
if (!isInitialized) {
// Initialize OpenGL loader

View File

@@ -14,6 +14,7 @@ struct SDL_Window;
typedef void * SDL_GLContext;
typedef union SDL_Event SDL_Event;
IMGUI_API bool ImGui_PreInit();
IMGUI_API ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context);
void IMGUI_API ImGui_Shutdown();