diff --git a/serialport/CMakeLists.txt b/serialport/CMakeLists.txt new file mode 100644 index 0000000..f77db5f --- /dev/null +++ b/serialport/CMakeLists.txt @@ -0,0 +1,63 @@ +cmake_minimum_required (VERSION 3.15) + +project (serialport) + +set (ADMAKE_DISABLE_ADDRESS_SANITIZER ON) + +include (CMakeListsPub) + +set (TARGET_NAME ${CMAKE_PROJECT_NAME}) + +file (GLOB_RECURSE SRC + "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") + +add_library (${TARGET_NAME}_OBJ OBJECT ${SRC}) + +if (APPLE) + +elseif (ANDROID) + add_compile_options (-ffunction-sections -fdata-sections -fvisibility=hidden -Wl,--gc-sections) +elseif (UNIX) + +elseif (WIN32) + # Disable the lib export by DLL + set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF) +endif () + +target_include_directories (${TARGET_NAME}_OBJ + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_SOURCE_DIR}/source") + +add_library (${TARGET_NAME} STATIC $) +add_library (${TARGET_NAME}_SO SHARED $) + +if (WIN32) + set_target_properties (${TARGET_NAME}_SO PROPERTIES + OUTPUT_NAME "serialportdll") + target_link_libraries (${TARGET_NAME}_SO PUBLIC user32) +elseif (APPLE) + set (DEP_FRAMEWOKS Foundation) + + macro (add_osx_framework target fwname) + find_library (FRAMEWORK_${fwname} + NAMES ${fwname} + PATHS ${CMAKE_OSX_SYSROOT}/System/Library + PATH_SUFFIXES Frameworks + NO_DEFAULT_PATH) + if (${FRAMEWORK_${fwname}} STREQUAL FRAMEWORK_${fwname}-NOTFOUND) + message (ERROR ": Framework ${fwname} not found") + else () + message (STATUS "Framework ${fwname} found at ${FRAMEWORK_${fwname}}") + target_link_libraries (${target} "-framework ${fwname}") + endif () + endmacro () + + foreach (framework ${DEP_FRAMEWOKS}) + add_osx_framework (${TARGET_NAME}_SO ${framework}) + endforeach () + +endif () + +if (DEFINED ADMAKE_BUILD_TEST) +endif () diff --git a/serialport/include/serialport.h b/serialport/include/serialport.h new file mode 100644 index 0000000..e69de29 diff --git a/serialport/source/platform/linux.cpp b/serialport/source/platform/linux.cpp new file mode 100644 index 0000000..2bfbec3 --- /dev/null +++ b/serialport/source/platform/linux.cpp @@ -0,0 +1,3 @@ +#if defined(OS_LINUX) || defined(OS_ANDROID) + +#endif /* OS_LINUX || OS_ANDROID */ diff --git a/serialport/source/platform/mac.cpp b/serialport/source/platform/mac.cpp new file mode 100644 index 0000000..736a7c3 --- /dev/null +++ b/serialport/source/platform/mac.cpp @@ -0,0 +1,182 @@ +#if defined(OS_MACOS) + +// macOS +#include +#include +#include + +#include /**< std::shared_ptr */ +#include /**< std::thread */ + +class SerialPortHotPlugDelegate { +public: + virtual void OnConnected() = 0; + virtual void OnDisconnected() = 0; +}; + +class SerialPortHotPlugMonitor { + SerialPortHotPlugMonitor(std::shared_ptr delegate) + : notification_port_(nullptr), serial_port_add_iter_(0), + serial_port_remove_iter_(0), delegate_(delegate) { + Init(); + } + + ~SerialPortHotPlugMonitor() { + if (thread_) { + if (thread_.joinable()) { + thread_.join(); + } + } + + if (notification_port_ != nullptr) { + IONotificationPortDestroy(notification_port_); + } + + if (serial_port_add_iter_) { + IOObjectRelease(serial_port_add_iter_); + } + + if (serial_port_remove_iter_) { + IOObjectRelease(serial_port_remove_iter_); + } + } + + int connectHotPlugEvent(itas109::CSerialPortHotPlugListener *event) { + if (event) { + p_listener = event; + return 0; + } else { + return -1; + } + return 0; + } + int disconnectHotPlugEvent() { + p_listener = NULL; + return 0; + } + +private: + inline void operator()() { + // create notificaiton + notification_port_ = IONotificationPortCreate(kIOMasterPortDefault); + if (!notification_port_) { + return; + } + + // create run loop + CFRunLoopSourceRef runLoopSource = + IONotificationPortGetRunLoopSource(notification_port_); + CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, + kCFRunLoopDefaultMode); + + kern_return_t result; + + // match add serial + CFMutableDictionaryRef matching_add_dict = + IOServiceMatching(kIOSerialBSDServiceValue); + if (!matching_add_dict) { + IONotificationPortDestroy(notification_port_); + return; + } + + // register serial add event + result = IOServiceAddMatchingNotification( + notification_port_, kIOPublishNotification, matching_add_dict, + SerialPortAdd, this, &serial_port_add_iter_); + if (result != KERN_SUCCESS) { + // fprintf(stderr, "Failed to add publish notification: %d.\n", result); + CFRelease(matching_add_dict); + IONotificationPortDestroy(notification_port_); + return; + } + // deal current device + SerialPortAdd(this, serial_port_add_iter_); + // clear dirct + // CFRelease(matchingAddDict); // TODO + + // match remove serial + CFMutableDictionaryRef matching_remove_dict = + IOServiceMatching(kIOSerialBSDServiceValue); + if (!matching_remove_dict) { + // fprintf(stderr, "Failed to create matching add dictionary.\n"); + IONotificationPortDestroy(notification_port_); + return; + } + // register serial remove event + result = IOServiceAddMatchingNotification( + notification_port_, kIOTerminatedNotification, matching_remove_dict, + SerialPortRemove, this, &serial_port_remove_iter_); + if (result != KERN_SUCCESS) { + // fprintf(stderr, "Failed to add terminated notification: %d.\n", + // result); + CFRelease(matching_remove_dict); + IONotificationPortDestroy(notification_port_); + return; + } + // deal current device + SerialPortRemove(this, serial_port_remove_iter_); + // clear dict + // CFRelease(matchingRemoveDict); // TODO + + // message loop (could not delete) + CFRunLoopRun(); + } + +private: + bool Init() { + thread_ = std::thread(&SerialPortHotPlugMonitor::operator(), this); + return true; + } + +private: + static void SerialPortAdd(void *refcon, io_iterator_t iterator) { + SerialPortHotPlugMonitor *monitor = reinterpret_cast()refcon); + if (monitor) { + io_object_t serial_port; + while ((serial_port = IOIteratorNext(iterator))) { + char port_name[256]; + CFStringRef cf_string = (CFStringRef)IORegistryEntryCreateCFProperty( + serial_port, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0); + if (cf_string != nullptr) { + CFStringGetCString(cf_string, port_name, sizeof(port_name), + kCFStringEncodingUTF8); + CFRelease(cf_string); + if (monitor->delegate_) { + monitor->delegate_->OnConnected(port_name, 1); + } + } + IOObjectRelease(serial_port); + } + } + } + + static void SerialPortRemove(void *refcon, io_iterator_t iterator) { + SerialPortHotPlugMonitor *monitor = reinterpret_cast()refcon); + if (monitor) { + io_object_t serial_port; + while ((serial_port = IOIteratorNext(iterator))) { + char port_name[256]; + CFStringRef cf_string = (CFStringRef)IORegistryEntryCreateCFProperty( + serial_port, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0); + if (cf_string != nullptr) { + CFStringGetCString(cf_string, port_name, sizeof(port_name), + kCFStringEncodingUTF8); + CFRelease(cf_string); + if (monitor->delegate_) { + monitor->delegate_->OnDisconnected(port_name, 0); + } + } + IOObjectRelease(serial_port); + } + } + } + +private: + IONotificationPortRef notification_port_; + io_iterator_t serial_port_add_iter_; + io_iterator_t serial_port_remove_iter_; + + std::thread thread_; + std::shared_ptr delegate_; +}; +#endif /* OS_MACOS */ diff --git a/serialport/source/platform/win.cpp b/serialport/source/platform/win.cpp new file mode 100644 index 0000000..d334094 --- /dev/null +++ b/serialport/source/platform/win.cpp @@ -0,0 +1,156 @@ +#if defined(OS_WINDOWS) + +// CreateWindowEx GetMessage DispatchMessage RegisterClassEx UnregisterClass +// DefWindowProc SetWindowLong GetWindowLong +#include +#include /**< _T */ +#include + +#include /**< std::shared_ptr */ +#include /**< std::thread */ + +#define CLASS_NAME _T("SerialPortHotPlugMonitorWnd") + +class SerialPortHotPlugDelegate { +public: + virtual void OnConnected() = 0; + virtual void OnDisconnected() = 0; +}; + +class SerialPortHotPlugMonitor { + SerialPortHotPlugMonitor(std::shared_ptr delegate) + : delegate(delegate_) { + Init(); + } + + ~SerialPortHotPlugMonitor() { + if (thread_) { + if (thread_.joinable()) { + thread_.join(); + } + } + UnregisterClass(CLASS_NAME, GetModuleHandle(NULL)); + } + +private: + inline void operator()() { + // create hidden window to receive device change messages + if (nullptr == CreateWindowEx(0, CLASS_NAME, 0, 0, 0, 0, 0, 0, + 0 /*HWND_MESSAGE*/, 0, + GetModuleHandle(nullptr), this)) { + return; + } + + // message loop + MSG msg; + while (GetMessage(&msg, nullptr, 0, 0)) { + DispatchMessage(&msg); + } + } + +private: + bool Init() { + // https://docs.microsoft.com/zh-cn/windows/win32/devio/registering-for-device-notification + WNDCLASSEX wnd_cls{0}; + + HINSTANCE instance = GetModuleHandle(nullptr); + + if (nullptr == instance) { + return false; + } + + wnd_cls.cbSize = sizeof(WNDCLASSEX); + wnd_cls.lpszClassName = CLASS_NAME; + wnd_cls.lpfnWndProc = reinterpret_cast(WinProc); + wnd_cls.hInstance = instance; + + if (!RegisterClassEx(&wnd_cls)) { + return false; + } + + thread_ = std::thread(&SerialPortHotPlugMonitor::operator(), this); + return true; + } + +private: + static LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, + LPARAM lParam) { + switch (message) { + case WM_CREATE: + SetWindowLongPtr(hWnd, GWLP_USERDATA, + (LONG_PTR)((CREATESTRUCT *)lParam)->lpCreateParams); + break; + case WM_DEVICECHANGE: { + PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; + + if (lpdb && DBT_DEVTYP_PORT == lpdb->dbch_devicetype) { + SerialPortHotPlugMonitor *monitor = + (SerialPortHotPlugMonitor *)GetWindowLongPtr(hWnd, GWLP_USERDATA); + if (base && base->delegate) { + PDEV_BROADCAST_PORT devinfo = (PDEV_BROADCAST_PORT)lpdb; + + if (DBT_DEVICEARRIVAL == wParam) { +#ifdef UNICODE + char port_name[256]; +#ifdef CSERIALPORT_USE_UTF8 + monirot->delegate_->OnConnected( + WCharToUTF8(port_name, 256, devinfo->dbcp_name), 1); +#else + monitor->delegate_->OnConnected( + WCharToNativeMB(port_name, 256, devinfo->dbcp_name), 1); +#endif +#else +#ifdef CSERIALPORT_USE_UTF8 + char portNameUTF8[256]; + wchar_t portNameWChar[256]; + // ANSI to WChar + NativeMBToWChar(portNameWChar, 256, devinfo->dbcp_name); + // WChar to UTF8 + monitor->delegate_->OnConnected( + WCharToUTF8(portNameUTF8, 256, portNameWChar), 1); +#else + monitor->delegate_->OnConnected(devinfo->dbcp_name, 1); +#endif +#endif + } else if (DBT_DEVICEREMOVECOMPLETE == wParam) { +#ifdef UNICODE + char portName[256]; +#ifdef CSERIALPORT_USE_UTF8 + monitor->delegate_->OnConnected( + WCharToUTF8(portName, 256, devinfo->dbcp_name), 0); +#else + monitor->delegate_->OnConnected( + WCharToNativeMB(portName, 256, devinfo->dbcp_name), 0); +#endif +#else +#ifdef CSERIALPORT_USE_UTF8 + char portNameUTF8[256]; + wchar_t portNameWChar[256]; + // ANSI to WChar + NativeMBToWChar(portNameWChar, 256, pDevInf->dbcp_name); + // WChar to UTF8 + monitor->delegate_->OnConnected( + WCharToUTF8(portNameUTF8, 256, portNameWChar), 0); +#else + monitor->delegate_->OnConnected(devinfo->dbcp_name, 0); +#endif +#endif + } else { + } + } + } + } break; + default: + break; + } + + // send all other messages on to the default windows handler + return DefWindowProc(hWnd, message, wParam, lParam); + } + +private: + std::thread thread_; + std::shared_ptr delegate_; +}; + +#endif /* OS_WINDOWS */ diff --git a/serialport/source/serialport.cpp b/serialport/source/serialport.cpp new file mode 100644 index 0000000..e69de29