feat: initial commit (migrate from old flowy monorepo)

This commit is contained in:
talksik
2025-03-27 11:58:49 -07:00
commit dedeb9cd78
8 changed files with 614 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
BasedOnStyle: Microsoft
TabWidth: 2
IndentWidth: 2
+84
View File
@@ -0,0 +1,84 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
*.qbs.user*
CMakeLists.txt.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe
# Directories with generated files
.moc/
.obj/
.pch/
.rcc/
.uic/
/build*/
.cache/
+35
View File
@@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.16)
project(mdns VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILATION_DATABASE ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
# https://doc.qt.io/qt-6/qt-cmake-policy-qtp0003.html
option(BUILD_SHARED_LIBS "Build shared instead of static library" ON)
find_package(Qt6 6.5 REQUIRED COMPONENTS Core)
qt_standard_project_setup(REQUIRES 6.5)
qt_policy(SET QTP0003 NEW)
set(SOURCES
src/broadcaster.h
src/broadcaster.cpp
src/scanner.h
src/scanner.cpp
)
qt_add_library(mdns ${SOURCES})
target_include_directories(mdns PUBLIC src/)
target_link_libraries(mdns PRIVATE Qt6::Core)
include(GNUInstallDirs)
install(TARGETS mdns
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
+1
View File
@@ -0,0 +1 @@
This package is a Qt wrapper over [zeroconf](https://github.com/yvz/zeroconf).
+167
View File
@@ -0,0 +1,167 @@
#include "broadcaster.h"
#include <QDebug>
#include <cstring>
#include <dns_sd.h>
#include <iostream>
ServiceInfo::ServiceInfo(QString sn, QString st, uint16_t p) : service_name(sn), service_type(st), service_port(p)
{
}
void BroadcastWorker::static_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
const char *name, const char *regtype, const char *domain, void *context)
{
// Cast the context back to our class instance
BroadcastWorker *self = static_cast<BroadcastWorker *>(context);
if (errorCode == kDNSServiceErr_NoError)
{
std::cout << "Service registered successfully!" << std::endl;
std::cout << "Service Name: " << name << std::endl;
std::cout << "Service Type: " << regtype << std::endl;
std::cout << "Domain: " << domain << std::endl;
emit self->service_registered();
}
else
{
std::cerr << "Error registering service: " << errorCode << std::endl;
emit self->error_occurred("Error registering service.");
}
}
BroadcastWorker::BroadcastWorker(QObject *parent, ServiceInfo *service) : QThread(parent), m_service_info(service)
{
}
void BroadcastWorker::run()
{
DNSServiceRef serviceRef;
DNSServiceErrorType error;
// Service parameters
std::string service_name = m_service_info->service_name.toStdString();
std::string service_type = m_service_info->service_type.toStdString();
const char *serviceName = service_name.c_str(); // Can be NULL for automatic naming
const char *serviceType = service_type.c_str(); // Service type and protocol
const char *serviceDomain = NULL; // NULL for default domain
const char *serviceHost = NULL; // NULL for default host
uint16_t servicePort = m_service_info->service_port; // Service port in network byte order
uint16_t txtLen = 0; // Length of TXT record
const void *txtRecord = NULL; // TXT record (can be NULL)
// Convert port to network byte order
servicePort = htons(servicePort);
// Register the service
error = DNSServiceRegister(&serviceRef,
0, // Default flags
0, // Default interface
serviceName, serviceType, serviceDomain, serviceHost, servicePort, txtLen, txtRecord,
BroadcastWorker::static_callback,
this); // Context pointer
if (error != kDNSServiceErr_NoError)
{
std::cerr << "Error registering service: " << error << std::endl;
emit error_occurred("Error registering service.");
return;
}
// Process the service registration
int dns_sd_fd = DNSServiceRefSockFD(serviceRef);
fd_set readfds;
struct timeval tv;
int result;
// Keep the service registered until the user terminates the program
std::cout << "Service is being broadcasted." << std::endl;
while (1)
{
if (isInterruptionRequested())
{
// Clean up
DNSServiceRefDeallocate(serviceRef);
return;
}
FD_ZERO(&readfds);
FD_SET(dns_sd_fd, &readfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
result = select(dns_sd_fd + 1, &readfds, NULL, NULL, &tv);
if (result > 0)
{
DNSServiceProcessResult(serviceRef);
}
}
}
Broadcaster::Broadcaster(QObject *parent) : QObject(parent)
{
}
Broadcaster::~Broadcaster()
{
if (m_worker)
{
m_worker->requestInterruption();
}
}
void Broadcaster::start_broadcasting(ServiceInfo *service)
{
if (m_worker)
{
qWarning() << "Already have a worker for broadcasting";
emit error_occurred("Already have a worker for broadcasting. Please stop first.");
return;
}
m_worker = new BroadcastWorker(this, service);
connect(m_worker, &BroadcastWorker::error_occurred, this, [this](const QString &message) {
emit broadcasting_failure();
emit error_occurred(message);
});
connect(m_worker, &BroadcastWorker::started, this, [this]() { qDebug() << "Broadcast worker started"; });
connect(m_worker, &BroadcastWorker::service_registered, this, [this] {
set_is_broadcasting(true);
qDebug() << "Service registered";
});
connect(m_worker, &BroadcastWorker::finished, [this] {
qDebug() << "Broadcast worker finished";
m_worker->deleteLater();
m_worker = nullptr;
set_is_broadcasting(false);
});
m_worker->start();
}
void Broadcaster::stop_broadcasting()
{
if (!m_worker || !m_worker->isRunning())
{
qWarning() << "Not broadcasting";
emit error_occurred("Not broadcasting, can't stop");
return;
}
m_worker->requestInterruption();
m_worker->wait();
}
bool Broadcaster::is_broadcasting() const
{
return m_is_broadcasting;
}
void Broadcaster::set_is_broadcasting(bool new_val)
{
if (m_is_broadcasting == new_val)
return;
m_is_broadcasting = new_val;
emit is_broadcasting_changed();
}
+82
View File
@@ -0,0 +1,82 @@
#ifndef BROADCASTER_H
#define BROADCASTER_H
#include <QObject>
#include <QThread>
#include <dns_sd.h>
struct ServiceInfo
{
QString service_name;
/**
* @brief e.g. _http._tcp
*/
QString service_type;
uint16_t service_port;
ServiceInfo(QString sn, QString st, uint16_t sp);
};
/**
* @class BroadcastAdvertiser
* @brief Runs in another thread to broadcast a given service
*
*/
class BroadcastWorker : public QThread
{
Q_OBJECT
public:
explicit BroadcastWorker(QObject *parent, ServiceInfo *service_info);
private:
ServiceInfo *m_service_info;
void run() override;
// Static callback wrapper that will be called by the C API
static void static_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
const char *name, const char *regtype, const char *domain, void *context);
signals:
void error_occurred(const QString &message);
void service_registered();
};
/**
* @class Broadcaster
* @brief Broadcasts a service with given port. It does so continuously in another thread.
*/
class Broadcaster : public QObject
{
Q_OBJECT
public:
explicit Broadcaster(QObject *parent = nullptr);
~Broadcaster();
bool is_broadcasting() const;
public slots:
/**
* @brief Starts broadcasting using the information from the given service.
*/
void start_broadcasting(ServiceInfo *);
/**
* @brief Stops broadcasting and does cleanup. Gracefully handles if not currently broadcasting.
*/
void stop_broadcasting();
signals:
void error_occurred(const QString &);
void broadcasting_failure();
void is_broadcasting_changed();
private:
BroadcastWorker *m_worker;
bool m_is_broadcasting;
void set_is_broadcasting(bool);
};
#endif // BROADCASTER_H
+186
View File
@@ -0,0 +1,186 @@
#include "scanner.h"
#include <arpa/inet.h> // For inet_ntop
#include <netinet/in.h>
#include <iostream>
Scanner::Scanner(QString query, QObject *parent) : m_query(query), QThread(parent)
{
}
void Scanner::run()
{
DNSServiceRef serviceRef;
std::string query = m_query.toStdString();
DNSServiceErrorType error =
DNSServiceBrowse(&serviceRef, 0, 0, query.c_str(), NULL, Scanner::service_browse_callback, this);
if (error != kDNSServiceErr_NoError)
{
fprintf(stderr, "Error browsing services: %d\n", error);
emit error_occurred("Error browing services");
return;
}
// Process the service registration
int dns_sd_fd = DNSServiceRefSockFD(serviceRef);
fd_set readfds;
struct timeval tv;
int result;
// Keep the service registered until the user terminates the program
std::cout << "Service is being broadcasted." << std::endl;
while (1)
{
if (isInterruptionRequested())
{
// Clean up
DNSServiceRefDeallocate(serviceRef);
return;
}
FD_ZERO(&readfds);
FD_SET(dns_sd_fd, &readfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
result = select(dns_sd_fd + 1, &readfds, NULL, NULL, &tv);
if (result > 0)
{
DNSServiceProcessResult(serviceRef);
}
}
}
void Scanner::service_resolve_callback(DNSServiceRef service, DNSServiceFlags flags, uint32_t interface_index,
DNSServiceErrorType error_code, const char *full_name, const char *host_target,
uint16_t port, uint16_t txt_len, const unsigned char *txt_record, void *context)
{
Scanner *self = static_cast<Scanner *>(context);
if (error_code == kDNSServiceErr_NoError)
{
printf("Resolved Service: %s\n", full_name);
printf("Host: %s\n", host_target);
printf("Port: %u\n", ntohs(port)); // Convert the port to host byte order
//
self->m_current_service_name = QString(full_name);
self->m_current_host_target = QString(host_target);
self->m_current_port = ntohs(port);
DNSServiceRef addrRef;
DNSServiceErrorType addrError =
DNSServiceGetAddrInfo(&addrRef, 0, interface_index, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6,
host_target, Scanner::address_info_callback, self);
if (addrError != kDNSServiceErr_NoError)
{
printf("Error getting address info: %d\n", addrError);
emit self->error_occurred("Error resolving IP address");
}
else
{
// Process the result to get the IP
DNSServiceProcessResult(addrRef);
DNSServiceRefDeallocate(addrRef);
}
}
else
{
printf("Failed to resolve service: %d\n", error_code);
emit self->error_occurred("Error resolving service");
}
}
void Scanner::address_info_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
DNSServiceErrorType errorCode, const char *hostname, const struct sockaddr *address,
uint32_t ttl, void *context)
{
Scanner *self = static_cast<Scanner *>(context);
if (errorCode == kDNSServiceErr_NoError)
{
// Convert sockaddr to readable IP address
char ip_str[INET6_ADDRSTRLEN]; // Big enough for both IPv4 and IPv6
bool is_good_address = false;
if (address->sa_family == AF_INET)
{
// IPv4
struct sockaddr_in *addr_in = (struct sockaddr_in *)address;
inet_ntop(AF_INET, &(addr_in->sin_addr), ip_str, INET_ADDRSTRLEN);
// Skip link-local addresses (169.254.x.x)
uint8_t first_octet = addr_in->sin_addr.s_addr & 0xFF;
uint8_t second_octet = (addr_in->sin_addr.s_addr >> 8) & 0xFF;
if (!(first_octet == 169 && second_octet == 254))
{
is_good_address = true;
}
}
else if (address->sa_family == AF_INET6)
{
// IPv6
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)address;
inet_ntop(AF_INET6, &(addr_in6->sin6_addr), ip_str, INET6_ADDRSTRLEN);
// Skip link-local addresses (fe80::)
if (!IN6_IS_ADDR_LINKLOCAL(&addr_in6->sin6_addr))
{
// Only use IPv6 if we have no IPv4 (lower priority)
is_good_address = true;
}
}
else
{
strcpy(ip_str, "Unknown address type");
}
printf("IP Address: %s\n", ip_str);
// Only emit the signal if this is a good address
if (is_good_address)
{
emit self->service_found(self->m_current_service_name, QString(ip_str), QString(hostname), self->m_current_port);
}
}
else
{
printf("Failed to resolve address: %d\n", errorCode);
emit self->error_occurred("Error resolving IP address");
}
}
void Scanner::service_browse_callback(DNSServiceRef service, DNSServiceFlags flags, uint32_t interface_index,
DNSServiceErrorType error_code, const char *service_name, const char *regtype,
const char *reply_domain, void *context)
{
Scanner *self = static_cast<Scanner *>(context);
if (error_code == kDNSServiceErr_NoError)
{
printf("Service found: %s.%s%s\n", service_name, regtype, reply_domain ? reply_domain : "");
// Now resolve the service to get port and other details
DNSServiceRef resolveRef;
DNSServiceErrorType resolveError = DNSServiceResolve(&resolveRef, 0, interface_index, service_name, regtype,
reply_domain, Scanner::service_resolve_callback, self);
if (resolveError != kDNSServiceErr_NoError)
{
printf("Error resolving service: %d\n", resolveError);
self->error_occurred("Error resolving service");
}
else
{
// Process the resolved service
DNSServiceProcessResult(resolveRef);
DNSServiceRefDeallocate(resolveRef);
}
}
else
{
self->error_occurred("Error browsing dns");
}
}
+56
View File
@@ -0,0 +1,56 @@
#ifndef SCANNER_H
#define SCANNER_H
#include <QObject>
#include <QThread>
#include <dns_sd.h>
struct Record
{
};
/**
* @class Scanner
* @brief A thread that handles scanning with given mDNS query and returns.
*
*/
class Scanner : public QThread
{
Q_OBJECT
public:
/**
* @param query: e.g. "_http._tcp"
*/
explicit Scanner(QString query, QObject *parent = nullptr);
private:
void run() override;
QString m_query;
// Caching values to use across callbacks
// TODO: pass a context struct around instead of this? or does every callback run sequentially?
QString m_current_service_name;
QString m_current_host_target;
uint16_t m_current_port;
static void DNSSD_API service_resolve_callback(DNSServiceRef service, DNSServiceFlags flags, uint32_t interface_index,
DNSServiceErrorType error_code, const char *full_name,
const char *host_target, uint16_t port, uint16_t txt_len,
const unsigned char *txt_record, void *context);
static void DNSSD_API service_browse_callback(DNSServiceRef service, DNSServiceFlags flags, uint32_t interface_index,
DNSServiceErrorType error_code, const char *service_name,
const char *regtype, const char *reply_domain, void *context);
static void DNSSD_API address_info_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
DNSServiceErrorType errorCode, const char *hostname,
const struct sockaddr *address, uint32_t ttl, void *context);
public slots:
signals:
void service_found(const QString &service_name, const QString &ip, const QString &host, const uint16_t port);
void error_occurred(const QString &message);
};
#endif // SCANNER_H