From da652b68faa7a1c8fd12fdd276c68823b09cc672 Mon Sep 17 00:00:00 2001 From: Dan Widdis Date: Sun, 17 May 2026 17:26:34 +0000 Subject: [PATCH 1/2] Add BluetoothApis to c.s.j.p.win32 Add mappings for the Windows Bluetooth API (BluetoothApis.dll): - BLUETOOTH_ADDRESS union (ullLong / rgBytes[6]) - BLUETOOTH_DEVICE_INFO struct - BLUETOOTH_DEVICE_SEARCH_PARAMS struct - BLUETOOTH_FIND_RADIO_PARAMS struct - BLUETOOTH_RADIO_INFO struct - BluetoothFindFirstRadio / BluetoothFindNextRadio / BluetoothFindRadioClose - BluetoothGetRadioInfo - BluetoothFindFirstDevice / BluetoothFindNextDevice / BluetoothFindDeviceClose --- CHANGES.md | 1 + .../sun/jna/platform/win32/BluetoothApis.java | 271 ++++++++++++++++++ .../jna/platform/win32/BluetoothApisTest.java | 98 +++++++ 3 files changed, 370 insertions(+) create mode 100644 contrib/platform/src/com/sun/jna/platform/win32/BluetoothApis.java create mode 100644 contrib/platform/test/com/sun/jna/platform/win32/BluetoothApisTest.java diff --git a/CHANGES.md b/CHANGES.md index d4752a5d9..cc567970c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ Features * [#1720](https://github.com/java-native-access/jna/pull/1720): Add `groupCount` and `groupMasks` fields to `CACHE_RELATIONSHIP` in `c.s.j.p.win32.WinNT`, matching the updated Windows struct layout - [@dbwiddis](https://github.com/dbwiddis). * [#1719](https://github.com/java-native-access/jna/pull/1719): Add `CoreGraphics` to `c.s.j.p.mac` with Quartz Window Services and Display Services bindings; implement `getAllWindows()` in `MacWindowUtils` - [@dbwiddis](https://github.com/dbwiddis). * [#1723](https://github.com/java-native-access/jna/pull/1723): Add `ProcFdInfo`, `InSockInfo`, `TcpSockInfo`, `proc_pidfdinfo`, `statfs64`, and `vm_deallocate` to `c.s.j.p.mac.SystemB` - [@dbwiddis](https://github.com/dbwiddis). +* [#1725](https://github.com/java-native-access/jna/pull/1725): Add `BluetoothApis` to `c.s.j.p.win32` providing Bluetooth device and radio enumeration via `BluetoothFindFirstRadio`, `BluetoothFindFirstDevice`, and related functions - [@dbwiddis](https://github.com/dbwiddis). Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/win32/BluetoothApis.java b/contrib/platform/src/com/sun/jna/platform/win32/BluetoothApis.java new file mode 100644 index 000000000..07e01399d --- /dev/null +++ b/contrib/platform/src/com/sun/jna/platform/win32/BluetoothApis.java @@ -0,0 +1,271 @@ +/* Copyright (c) 2026 Daniel Widdis, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna.platform.win32; + +import com.sun.jna.Native; +import com.sun.jna.Structure; +import com.sun.jna.Structure.FieldOrder; +import com.sun.jna.Union; +import com.sun.jna.platform.win32.WinBase.SYSTEMTIME; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.ptr.PointerByReference; +import com.sun.jna.win32.StdCallLibrary; +import com.sun.jna.win32.W32APIOptions; + +/** + * Provides mappings for the Windows Bluetooth API functions from {@code BluetoothApis.dll}. + * + * @see BluetoothApis.h + */ +public interface BluetoothApis extends StdCallLibrary { + + /** Instance of BluetoothApis. */ + BluetoothApis INSTANCE = Native.load("BluetoothApis", BluetoothApis.class, W32APIOptions.DEFAULT_OPTIONS); + + /** Maximum Bluetooth device name length. */ + int BLUETOOTH_MAX_NAME_SIZE = 248; + + /** + * The {@code BLUETOOTH_ADDRESS} structure provides the address of a Bluetooth device. The address is stored as + * either a {@code BTH_ADDR} (unsigned 64-bit integer) or a 6-byte array. + * + * @see BLUETOOTH_ADDRESS + */ + class BLUETOOTH_ADDRESS extends Union { + /** The Bluetooth address as a 64-bit unsigned integer (only lower 48 bits used). */ + public long ullLong; + /** The Bluetooth address as a 6-byte array in network byte order. */ + public byte[] rgBytes = new byte[6]; + + /** + * Gets the address as a long. + * + * @return the address + */ + public long getAddress() { + setType("ullLong"); + read(); + return ullLong; + } + + /** + * Gets the address as a 6-byte array. + * + * @return the address bytes + */ + public byte[] getBytes() { + setType("rgBytes"); + read(); + return rgBytes; + } + } + + /** + * The {@code BLUETOOTH_DEVICE_INFO} structure provides information about a Bluetooth device. + * + * @see BLUETOOTH_DEVICE_INFO + */ + @FieldOrder({ "dwSize", "Address", "ulClassofDevice", "fConnected", "fRemembered", "fAuthenticated", "stLastSeen", + "stLastUsed", "szName" }) + class BLUETOOTH_DEVICE_INFO extends Structure { + /** Size of the structure, in bytes. Must be set before calling any function. */ + public int dwSize; + /** Address of the device. */ + public BLUETOOTH_ADDRESS Address; + /** Class of Device (CoD) of the device. */ + public int ulClassofDevice; + /** Whether the device is connected. */ + public boolean fConnected; + /** Whether the device is a remembered device. Not all remembered devices are authenticated. */ + public boolean fRemembered; + /** Whether the device is authenticated, paired, or bonded. All authenticated devices are remembered. */ + public boolean fAuthenticated; + /** Last time the device was seen. */ + public SYSTEMTIME stLastSeen; + /** Last time the device was used. */ + public SYSTEMTIME stLastUsed; + /** Name of the device. */ + public char[] szName = new char[BLUETOOTH_MAX_NAME_SIZE]; + + /** Creates a new instance and sets {@code dwSize}. */ + public BLUETOOTH_DEVICE_INFO() { + dwSize = size(); + } + } + + /** + * The {@code BLUETOOTH_DEVICE_SEARCH_PARAMS} structure specifies search criteria for Bluetooth device searches. + * + * @see BLUETOOTH_DEVICE_SEARCH_PARAMS + */ + @FieldOrder({ "dwSize", "fReturnAuthenticated", "fReturnRemembered", "fReturnUnknown", "fReturnConnected", + "fIssueInquiry", "cTimeoutMultiplier", "hRadio" }) + class BLUETOOTH_DEVICE_SEARCH_PARAMS extends Structure { + /** Size of the structure, in bytes. */ + public int dwSize; + /** Whether to return authenticated devices. */ + public boolean fReturnAuthenticated; + /** Whether to return remembered devices. */ + public boolean fReturnRemembered; + /** Whether to return unknown devices. */ + public boolean fReturnUnknown; + /** Whether to return connected devices. */ + public boolean fReturnConnected; + /** Whether to issue a new inquiry. */ + public boolean fIssueInquiry; + /** Timeout for the inquiry in increments of 1.28 seconds. Maximum value is 48. */ + public byte cTimeoutMultiplier; + /** Handle to the radio on which to perform the inquiry. Set to {@code null} for all radios. */ + public HANDLE hRadio; + + /** Creates a new instance and sets {@code dwSize}. */ + public BLUETOOTH_DEVICE_SEARCH_PARAMS() { + dwSize = size(); + } + } + + /** + * The {@code BLUETOOTH_FIND_RADIO_PARAMS} structure facilitates the enumeration of installed Bluetooth radios. + * + * @see BLUETOOTH_FIND_RADIO_PARAMS + */ + @FieldOrder({ "dwSize" }) + class BLUETOOTH_FIND_RADIO_PARAMS extends Structure { + /** Size of the structure, in bytes. */ + public int dwSize; + + /** Creates a new instance and sets {@code dwSize}. */ + public BLUETOOTH_FIND_RADIO_PARAMS() { + dwSize = size(); + } + } + + /** + * The {@code BLUETOOTH_RADIO_INFO} structure contains information about a Bluetooth radio. + * + * @see BLUETOOTH_RADIO_INFO + */ + @FieldOrder({ "dwSize", "address", "szName", "ulClassofDevice", "lmpSubversion", "manufacturer" }) + class BLUETOOTH_RADIO_INFO extends Structure { + /** Size of the structure, in bytes. */ + public int dwSize; + /** Address of the local Bluetooth radio. */ + public BLUETOOTH_ADDRESS address; + /** Name of the local Bluetooth radio. */ + public char[] szName = new char[BLUETOOTH_MAX_NAME_SIZE]; + /** Class of Device for the local Bluetooth radio. */ + public int ulClassofDevice; + /** Manufacturer-specific subversion data. */ + public short lmpSubversion; + /** Manufacturer of the Bluetooth radio, expressed as a BTH_MFG_Xxx value. */ + public short manufacturer; + + /** Creates a new instance and sets {@code dwSize}. */ + public BLUETOOTH_RADIO_INFO() { + dwSize = size(); + } + } + + /** + * Finds the first Bluetooth radio installed on the system. + * + * @param pbtfrp A pointer to a {@link BLUETOOTH_FIND_RADIO_PARAMS} structure. + * @param phRadio A pointer that receives the handle of the first radio found. + * @return A handle to use with {@link #BluetoothFindNextRadio} and {@link #BluetoothFindRadioClose}, or + * {@code null} on failure. Call {@code GetLastError} for error information. + * @see BluetoothFindFirstRadio + */ + HANDLE BluetoothFindFirstRadio(BLUETOOTH_FIND_RADIO_PARAMS pbtfrp, PointerByReference phRadio); + + /** + * Finds the next installed Bluetooth radio. + * + * @param hFind The handle returned by {@link #BluetoothFindFirstRadio}. + * @param phRadio A pointer that receives the handle of the next radio found. + * @return {@code true} if another radio was found, {@code false} otherwise. + * @see BluetoothFindNextRadio + */ + boolean BluetoothFindNextRadio(HANDLE hFind, PointerByReference phRadio); + + /** + * Closes the enumeration handle for Bluetooth radios. + * + * @param hFind The handle returned by {@link #BluetoothFindFirstRadio}. + * @return {@code true} on success, {@code false} on failure. + * @see BluetoothFindRadioClose + */ + boolean BluetoothFindRadioClose(HANDLE hFind); + + /** + * Retrieves information about a Bluetooth radio. + * + * @param hRadio A handle to the Bluetooth radio obtained from {@link #BluetoothFindFirstRadio}. + * @param pRadioInfo A pointer to a {@link BLUETOOTH_RADIO_INFO} structure to receive the radio information. + * @return {@code ERROR_SUCCESS} (0) on success, or an error code on failure. + * @see BluetoothGetRadioInfo + */ + int BluetoothGetRadioInfo(HANDLE hRadio, BLUETOOTH_RADIO_INFO pRadioInfo); + + /** + * Begins the enumeration of Bluetooth devices. + * + * @param pbtsp A pointer to a {@link BLUETOOTH_DEVICE_SEARCH_PARAMS} structure specifying search criteria. + * @param pbtdi A pointer to a {@link BLUETOOTH_DEVICE_INFO} structure to receive the first device found. + * @return A handle to use with {@link #BluetoothFindNextDevice} and {@link #BluetoothFindDeviceClose}, or + * {@code null} if no devices are found. + * @see BluetoothFindFirstDevice + */ + HANDLE BluetoothFindFirstDevice(BLUETOOTH_DEVICE_SEARCH_PARAMS pbtsp, BLUETOOTH_DEVICE_INFO pbtdi); + + /** + * Finds the next Bluetooth device. + * + * @param hFind The handle returned by {@link #BluetoothFindFirstDevice}. + * @param pbtdi A pointer to a {@link BLUETOOTH_DEVICE_INFO} structure to receive the next device found. + * @return {@code true} if another device was found, {@code false} otherwise. + * @see BluetoothFindNextDevice + */ + boolean BluetoothFindNextDevice(HANDLE hFind, BLUETOOTH_DEVICE_INFO pbtdi); + + /** + * Closes the enumeration handle for Bluetooth devices. + * + * @param hFind The handle returned by {@link #BluetoothFindFirstDevice}. + * @return {@code true} on success, {@code false} on failure. + * @see BluetoothFindDeviceClose + */ + boolean BluetoothFindDeviceClose(HANDLE hFind); +} diff --git a/contrib/platform/test/com/sun/jna/platform/win32/BluetoothApisTest.java b/contrib/platform/test/com/sun/jna/platform/win32/BluetoothApisTest.java new file mode 100644 index 000000000..ddb8d9dbc --- /dev/null +++ b/contrib/platform/test/com/sun/jna/platform/win32/BluetoothApisTest.java @@ -0,0 +1,98 @@ +/* Copyright (c) 2026 Daniel Widdis, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna.platform.win32; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.sun.jna.platform.win32.BluetoothApis.BLUETOOTH_DEVICE_INFO; +import com.sun.jna.platform.win32.BluetoothApis.BLUETOOTH_DEVICE_SEARCH_PARAMS; +import com.sun.jna.platform.win32.BluetoothApis.BLUETOOTH_FIND_RADIO_PARAMS; +import com.sun.jna.platform.win32.BluetoothApis.BLUETOOTH_RADIO_INFO; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.ptr.PointerByReference; + +/** + * Tests for {@link BluetoothApis}. + */ +public class BluetoothApisTest { + + @Test + public void testStructureSizes() { + BLUETOOTH_FIND_RADIO_PARAMS radioParams = new BLUETOOTH_FIND_RADIO_PARAMS(); + assertTrue("BLUETOOTH_FIND_RADIO_PARAMS size should be at least 4", radioParams.dwSize >= 4); + + BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = new BLUETOOTH_DEVICE_SEARCH_PARAMS(); + assertTrue("BLUETOOTH_DEVICE_SEARCH_PARAMS size should be at least 32", searchParams.dwSize >= 32); + + BLUETOOTH_DEVICE_INFO deviceInfo = new BLUETOOTH_DEVICE_INFO(); + assertTrue("BLUETOOTH_DEVICE_INFO size should be at least 560", deviceInfo.dwSize >= 560); + + BLUETOOTH_RADIO_INFO radioInfo = new BLUETOOTH_RADIO_INFO(); + assertTrue("BLUETOOTH_RADIO_INFO size should be at least 520", radioInfo.dwSize >= 520); + } + + @Test + public void testBluetoothFindFirstRadio() { + BLUETOOTH_FIND_RADIO_PARAMS radioParams = new BLUETOOTH_FIND_RADIO_PARAMS(); + PointerByReference phRadio = new PointerByReference(); + + // This may return null if no Bluetooth radio is present, which is acceptable + HANDLE hFind = BluetoothApis.INSTANCE.BluetoothFindFirstRadio(radioParams, phRadio); + if (hFind != null) { + try { + HANDLE hRadio = new HANDLE(phRadio.getValue()); + try { + assertNotNull("Radio handle should not be null", hRadio); + + BLUETOOTH_RADIO_INFO radioInfo = new BLUETOOTH_RADIO_INFO(); + int result = BluetoothApis.INSTANCE.BluetoothGetRadioInfo(hRadio, radioInfo); + assertTrue("BluetoothGetRadioInfo should succeed", result == 0); + + // Enumerate devices + BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = new BLUETOOTH_DEVICE_SEARCH_PARAMS(); + searchParams.fReturnAuthenticated = true; + searchParams.fReturnRemembered = true; + searchParams.fReturnConnected = true; + searchParams.fReturnUnknown = false; + searchParams.fIssueInquiry = false; + searchParams.cTimeoutMultiplier = 0; + searchParams.hRadio = hRadio; + + BLUETOOTH_DEVICE_INFO deviceInfo = new BLUETOOTH_DEVICE_INFO(); + HANDLE hFindDevice = BluetoothApis.INSTANCE.BluetoothFindFirstDevice(searchParams, deviceInfo); + if (hFindDevice != null) { + BluetoothApis.INSTANCE.BluetoothFindDeviceClose(hFindDevice); + } + } finally { + Kernel32.INSTANCE.CloseHandle(hRadio); + } + } finally { + BluetoothApis.INSTANCE.BluetoothFindRadioClose(hFind); + } + } + } +} From 2771c1a9d172f8df4dc908ce8f75180ce6879b28 Mon Sep 17 00:00:00 2001 From: Dan Widdis Date: Tue, 19 May 2026 20:16:21 +0000 Subject: [PATCH 2/2] Address review: use HANDLEByReference and UNICODE_OPTIONS --- .../src/com/sun/jna/platform/win32/BluetoothApis.java | 8 ++++---- .../com/sun/jna/platform/win32/BluetoothApisTest.java | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contrib/platform/src/com/sun/jna/platform/win32/BluetoothApis.java b/contrib/platform/src/com/sun/jna/platform/win32/BluetoothApis.java index 07e01399d..61d32c09f 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/BluetoothApis.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/BluetoothApis.java @@ -29,7 +29,7 @@ import com.sun.jna.Union; import com.sun.jna.platform.win32.WinBase.SYSTEMTIME; import com.sun.jna.platform.win32.WinNT.HANDLE; -import com.sun.jna.ptr.PointerByReference; +import com.sun.jna.platform.win32.WinNT.HANDLEByReference; import com.sun.jna.win32.StdCallLibrary; import com.sun.jna.win32.W32APIOptions; @@ -41,7 +41,7 @@ public interface BluetoothApis extends StdCallLibrary { /** Instance of BluetoothApis. */ - BluetoothApis INSTANCE = Native.load("BluetoothApis", BluetoothApis.class, W32APIOptions.DEFAULT_OPTIONS); + BluetoothApis INSTANCE = Native.load("BluetoothApis", BluetoothApis.class, W32APIOptions.UNICODE_OPTIONS); /** Maximum Bluetooth device name length. */ int BLUETOOTH_MAX_NAME_SIZE = 248; @@ -202,7 +202,7 @@ public BLUETOOTH_RADIO_INFO() { * @see BluetoothFindFirstRadio */ - HANDLE BluetoothFindFirstRadio(BLUETOOTH_FIND_RADIO_PARAMS pbtfrp, PointerByReference phRadio); + HANDLE BluetoothFindFirstRadio(BLUETOOTH_FIND_RADIO_PARAMS pbtfrp, HANDLEByReference phRadio); /** * Finds the next installed Bluetooth radio. @@ -213,7 +213,7 @@ public BLUETOOTH_RADIO_INFO() { * @see BluetoothFindNextRadio */ - boolean BluetoothFindNextRadio(HANDLE hFind, PointerByReference phRadio); + boolean BluetoothFindNextRadio(HANDLE hFind, HANDLEByReference phRadio); /** * Closes the enumeration handle for Bluetooth radios. diff --git a/contrib/platform/test/com/sun/jna/platform/win32/BluetoothApisTest.java b/contrib/platform/test/com/sun/jna/platform/win32/BluetoothApisTest.java index ddb8d9dbc..6d3b2f25a 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/BluetoothApisTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/BluetoothApisTest.java @@ -33,7 +33,7 @@ import com.sun.jna.platform.win32.BluetoothApis.BLUETOOTH_FIND_RADIO_PARAMS; import com.sun.jna.platform.win32.BluetoothApis.BLUETOOTH_RADIO_INFO; import com.sun.jna.platform.win32.WinNT.HANDLE; -import com.sun.jna.ptr.PointerByReference; +import com.sun.jna.platform.win32.WinNT.HANDLEByReference; /** * Tests for {@link BluetoothApis}. @@ -58,13 +58,13 @@ public void testStructureSizes() { @Test public void testBluetoothFindFirstRadio() { BLUETOOTH_FIND_RADIO_PARAMS radioParams = new BLUETOOTH_FIND_RADIO_PARAMS(); - PointerByReference phRadio = new PointerByReference(); + HANDLEByReference phRadio = new HANDLEByReference(); // This may return null if no Bluetooth radio is present, which is acceptable HANDLE hFind = BluetoothApis.INSTANCE.BluetoothFindFirstRadio(radioParams, phRadio); if (hFind != null) { try { - HANDLE hRadio = new HANDLE(phRadio.getValue()); + HANDLE hRadio = phRadio.getValue(); try { assertNotNull("Radio handle should not be null", hRadio);