diff --git a/src/compat/poll.h b/src/compat/poll.h new file mode 100644 index 000000000..2cdc28057 --- /dev/null +++ b/src/compat/poll.h @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef _COMPAT_POLL_H +#define _COMPAT_POLL_H + +#ifdef _WIN32 + +#include +#include + +/* struct pollfd is already defined by winsock2.h/ws2def.h as WSAPOLLFD */ + +#ifndef POLLIN +#define POLLIN 0x0100 +#endif +#ifndef POLLOUT +#define POLLOUT 0x0080 +#endif +#ifndef POLLERR +#define POLLERR 0x0001 +#endif +#ifndef POLLHUP +#define POLLHUP 0x0002 +#endif +#ifndef POLLNVAL +#define POLLNVAL 0x0008 +#endif + +static inline int poll(struct pollfd *fds, unsigned int nfds, int timeout) { + fd_set r, w, e; + FD_ZERO(&r); FD_ZERO(&w); FD_ZERO(&e); + + for (unsigned int i = 0; i < nfds; i++) { + fds[i].revents = 0; + + /* Detect invalid sockets before adding to fd_sets */ + int err = 0; + int errlen = sizeof(err); + if (getsockopt(fds[i].fd, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen) != 0 || err != 0) { + fds[i].revents |= POLLNVAL; + continue; + } + + if (fds[i].events & POLLIN) FD_SET(fds[i].fd, &r); + if (fds[i].events & POLLOUT) FD_SET(fds[i].fd, &w); + FD_SET(fds[i].fd, &e); + } + + timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + int ret = select(0, &r, &w, &e, timeout >= 0 ? &tv : NULL); + + if (ret <= 0) { + return ret; + } + + for (unsigned int i = 0; i < nfds; i++) { + if (fds[i].revents & POLLNVAL) + continue; + + if (FD_ISSET(fds[i].fd, &r)) fds[i].revents |= POLLIN; + if (FD_ISSET(fds[i].fd, &w)) fds[i].revents |= POLLOUT; + + if (FD_ISSET(fds[i].fd, &e)) { + int err = 0; + int errlen = sizeof(err); + getsockopt(fds[i].fd, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen); + if (err == WSAECONNABORTED || err == WSAECONNRESET || err == WSAENOTCONN) { + fds[i].revents |= POLLHUP; + } else { + fds[i].revents |= POLLERR; + } + } else if (!(fds[i].revents & (POLLIN | POLLOUT)) && (fds[i].events & (POLLIN | POLLOUT))) { + /* Socket requested events but none were ready and no error — possible disconnect */ + char buf; + int n = recv(fds[i].fd, &buf, 1, MSG_PEEK); + if (n == 0) { + fds[i].revents |= POLLHUP; + } + } + } + return ret; +} + +#endif /* _WIN32 */ +#endif /* _COMPAT_POLL_H */ diff --git a/src/libdmusic/CMakeLists.txt b/src/libdmusic/CMakeLists.txt index 267835180..dd7164ba6 100644 --- a/src/libdmusic/CMakeLists.txt +++ b/src/libdmusic/CMakeLists.txt @@ -27,8 +27,10 @@ if(VLC_INCLUDE_DIR) include_directories(${VLC_INCLUDE_DIR}/vlc) endif() endif() -# VLC headers use poll() without including poll.h; force-include it -add_definitions(-include poll.h) +# Windows: force-include poll() shim (select-based) so VLC headers compile +if(WIN32) + add_compile_options(-include poll.h) +endif() if(EXISTS "/runtime/include/vlc") include_directories(/runtime/include/vlc) include_directories(/runtime/include/vlc/plugins) @@ -56,6 +58,11 @@ qt_add_resources(QRC_FILES ${QRC_SOURCE_FILES}) add_library(${CMD_NAME} SHARED ${ALL_SOURCES} ${QRC_FILES}) +# Windows: add compat include directory after target creation +if(WIN32) + target_include_directories(${CMD_NAME} PRIVATE "${CMAKE_SOURCE_DIR}/src/compat") +endif() + # On MinGW, export all symbols since VLC wrapper classes lack export macros if(WIN32 AND NOT MSVC) target_link_options(${CMD_NAME} PRIVATE -Wl,--export-all-symbols) @@ -79,5 +86,10 @@ target_link_libraries(${CMD_NAME} Qt6::Core Qt6::Multimedia Qt6::DBus Qt6::Sql Q # qt5_use_modules(${CMD_NAME} ) +# Windows: always link ws2_32 for Winsock APIs (poll shim, networking) +if(WIN32) + target_link_libraries(${CMD_NAME} ws2_32) +endif() + include(GNUInstallDirs) install(TARGETS ${CMD_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR})