ggwave-gui : working file sharing

This commit is contained in:
Georgi Gerganov
2020-12-30 17:37:55 +02:00
parent 82b0748830
commit d620f5c15b
10 changed files with 2166 additions and 95 deletions

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
..

View File

@@ -1,9 +1,9 @@
#include "common.h"
#include "ggwave/ggwave.h"
#include "ggwave-common.h"
#include "ggwave/ggwave.h"
#include "ggsock/communicator.h"
#include "ggsock/file-server.h"
#include "ggsock/serialization.h"
@@ -63,6 +63,7 @@ void ScrollWhenDraggingOnVoid(const ImVec2& delta, ImGuiMouseButton mouse_button
}
static const char * kFileBroadcastPrefix = "\xbc";
static const int kMaxSimultaneousChunkRequests = 4;
struct Message {
enum Type {
@@ -155,24 +156,41 @@ GGWave * g_ggWave;
Buffer g_buffer;
std::atomic<bool> g_isRunning;
// file send data
bool g_focusFileSend = false;
GGSock::FileServer g_fileServer;
bool g_hasFileInfos = false;
bool g_hasRequestedFiles = false;
GGSock::FileServer::TFileInfos g_fileInfos;
std::map<GGSock::FileServer::TURI, GGSock::FileServer::FileData> g_receivedFiles;
// 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;
std::string g_shareFilename;
ShareInfo g_shareInfo;
int g_deleteId = 0;
std::string g_deleteFilename;
DeleteInfo g_deleteInfo;
int g_receivedId = 0;
@@ -180,56 +198,60 @@ int getShareId() {
return g_shareId;
}
const char * getShareFilename() {
return g_shareFilename.data();
ShareInfo getShareInfo() {
return g_shareInfo;
}
int getDeleteId() {
return g_deleteId;
}
const char * getDeleteFilename() {
return g_deleteFilename.data();
DeleteInfo getDeleteInfo() {
return g_deleteInfo;
}
int getReceivedId() {
return g_receivedId;
}
std::vector<const char*> getReceivedFilename() {
std::vector<const char*> result;
std::vector<ReceiveInfo> getReceiveInfos() {
std::vector<ReceiveInfo> result;
for (const auto & file : g_receivedFiles) {
result.push_back(file.second.info.filename.c_str());
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;
}
std::vector<const char *> getReceivedDataBuffer() {
std::vector<const char*> result;
for (const auto & file : g_receivedFiles) {
result.push_back(file.second.data.data());
bool confirmReceive(const char * uri) {
if (g_receivedFiles.find(uri) == g_receivedFiles.end()) {
return false;
}
return result;
g_receivedFiles.erase(uri);
g_receivedFileInfosExtended[uri].readyToShare = true;
return true;
}
std::vector<size_t> getReceivedDataSize() {
std::vector<size_t> result;
for (const auto & file : g_receivedFiles) {
result.push_back(file.second.data.size());
}
return result;
}
void clearFiles() {
void clearAllFiles() {
g_fileServer.clearAllFiles();
}
void clearFile(const char * uri) {
g_fileServer.clearFile(uri);
}
void addFile(
const char * uri,
const char * filename,
@@ -294,24 +316,30 @@ std::thread initMain() {
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_fileInfos, dataBuffer, dataSize, offset);
GGSock::Unserialize()(g_receivedFileInfos, dataBuffer, dataSize, offset);
for (const auto & info : g_fileInfos) {
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_hasFileInfos = true;
g_hasReceivedFileInfos = true;
return 0;
});
@@ -325,6 +353,10 @@ std::thread initMain() {
//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;
});
@@ -405,31 +437,73 @@ void renderMain() {
g_fileServer.update();
if (g_fileClient.isConnected()) {
if (!g_hasFileInfos) {
if (!g_hasRequestedFileInfos) {
g_receivedFileInfos.clear();
g_receivedFiles.clear();
g_receivedFileInfosExtended.clear();
g_fileClient.send(GGSock::FileServer::MsgFileInfosRequest);
} else if (g_hasRequestedFiles == false) {
printf("Requesting files ...\n");
for (const auto & fileInfo : g_fileInfos) {
for (int i = 0; i < fileInfo.second.nChunks; ++i) {
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 = i;
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_hasRequestedFiles = true;
g_receivedId++;
//for (const auto & fileInfo : g_receivedFileInfos) {
// for (int i = 0; i < fileInfo.second.nChunks; ++i) {
// GGSock::FileServer::FileChunkRequestData data;
// data.uri = fileInfo.second.uri;
// data.chunkId = i;
// 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();
// }
//}
//g_hasReceivedFiles = true;
//g_receivedId++;
}
}
g_fileClient.update();
static State stateCurrent;
{
@@ -554,17 +628,17 @@ void renderMain() {
windowId = WindowId::Settings;
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_COMMENT_ALT " Messages", { 0.35f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) {
windowId = WindowId::Messages;
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FILE " Files", { 0.40f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) {
windowId = WindowId::Files;
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_SIGNAL " Spectrum", { 1.0f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) {
windowId = WindowId::Spectrum;
}
@@ -803,13 +877,19 @@ void renderMain() {
if (ImGui::Button("Receive")) {
std::string tmp = messageSelected.data.data() + strlen(kFileBroadcastPrefix);
std::stringstream ss(tmp);
ss >> g_remoteIP;
ss >> g_remotePort;
g_hasRemoteInfo = true;
g_fileClient.disconnect();
g_hasFileInfos = false;
g_hasRequestedFiles = false;
g_hasReceivedFileInfos = false;
g_hasRequestedFileInfos = false;
g_hasReceivedFiles = false;
windowId = WindowId::Files;
subWindowId = SubWindowId::Receive;
ImGui::CloseCurrentPopup();
}
}
@@ -996,16 +1076,21 @@ void renderMain() {
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_shareFilename = fileInfo.second.filename;
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_deleteFilename = fileInfo.second.filename;
g_deleteInfo.uri = fileInfo.second.uri.data();
g_deleteInfo.filename = fileInfo.second.filename.data();
g_deleteId++;
}
ImGui::PopID();
}
//ImGui::PopTextWrapPos();
@@ -1029,14 +1114,15 @@ void renderMain() {
g_fileServer.startListening();
}
ImGui::SameLine();
if (ImGui::Button("Stop", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
g_fileServer.stopListening();
}
ImGui::SameLine();
if (ImGui::Button("Clear", { 1.0f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
g_deleteFilename = "###ALL-FILES###";
g_deleteInfo.uri = "###ALL-FILES###";
g_deleteInfo.filename = "";
g_deleteId++;
}
}
@@ -1051,7 +1137,7 @@ void renderMain() {
ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Accepting new connections at %s:%d",
GGSock::Communicator::getLocalAddress().c_str(), g_fileServer.getParameters().listenPort);
}
auto clientInfos = g_fileServer.getClientInfos();
if (clientInfos.empty()) {
ImGui::Text("No peers currently connected ..");
@@ -1068,28 +1154,116 @@ void renderMain() {
break;
case SubWindowId::Receive:
{
const auto wSize = ImGui::GetContentRegionAvail();
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:main", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::Text("Remote IP: %s", g_remoteIP.c_str());
ImGui::Text("Remote Port: %d", g_remotePort);
if (ImGui::Button("Connect", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
if (g_fileClient.connect(g_remoteIP, g_remotePort, 0)) {
printf("Started connecting ...\n");
}
}
ImGui::SameLine();
if (ImGui::Button("Disconnect", { 1.00f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
if (g_fileClient.disconnect()) {
printf("Stopped connecting\n");
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 (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 (ImGui::Button("Receive")) {
fileInfoExtended.isReceiving = true;
fileInfoExtended.isChunkReceived.resize(fileInfo.second.nChunks);
fileInfoExtended.isChunkRequested.resize(fileInfo.second.nChunks);
}
}
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_hasRemoteInfo && g_fileClient.isConnecting() == false && g_fileClient.isConnected() == false) {
if (ImGui::Button("Connect", { 1.00f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
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;
};

View File

@@ -9,18 +9,45 @@ 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();
const char * getShareFilename();
ShareInfo getShareInfo();
// delete file
struct DeleteInfo {
std::string uri;
std::string filename;
};
int getDeleteId();
const char * getDeleteFilename();
DeleteInfo getDeleteInfo();
// receive
struct ReceiveInfo {
const char * uri;
const char * filename;
const char * dataBuffer;
size_t dataSize;
};
int getReceivedId();
std::vector<const char *> getReceivedFilename();
std::vector<const char *> getReceivedDataBuffer();
std::vector<size_t> getReceivedDataSize();
std::vector<ReceiveInfo> getReceiveInfos();
bool confirmReceive(const char * uri);
void clearFiles();
// input
void clearAllFiles();
void clearFile(const char * uri);
void addFile(
const char * uri,

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"
@@ -12,24 +12,17 @@
#include <iterator>
std::vector<char> readFile(const char* filename) {
// open the file:
std::ifstream file(filename, std::ios::binary);
// Stop eating new lines in binary mode!!!
file.unsetf(std::ios::skipws);
// get its size:
std::streampos fileSize;
file.seekg(0, std::ios::end);
fileSize = file.tellg();
file.seekg(0, std::ios::beg);
// reserve capacity
std::vector<char> vec;
vec.reserve(fileSize);
// read the data:
vec.insert(vec.begin(),
std::istream_iterator<char>(file),
std::istream_iterator<char>());
@@ -49,7 +42,9 @@ bool ImGui_BeginFrame(SDL_Window * window) {
if (event.type == SDL_DROPFILE) {
printf("Dropped file: '%s'\n", event.drop.file);
auto data = readFile(event.drop.file);
addFile(event.drop.file, event.drop.file, std::move(data));
std::string uri = event.drop.file;
auto filename = uri.substr(uri.find_last_of("/\\") + 1);
addFile(uri.c_str(), filename.c_str(), std::move(data));
break;
}
}
@@ -179,12 +174,18 @@ int main(int argc, char** argv) {
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