├── .gitattributes ├── MacAngleConfig.cmake.in ├── .gitignore ├── LICENSE ├── CMakeLists.txt ├── angle.h ├── example.cpp ├── README.md └── angle.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /MacAngleConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # MacBookLidAngle CMake configuration file 2 | 3 | @PACKAGE_INIT@ 4 | 5 | include(CMakeFindDependencyMacro) 6 | 7 | # Find required dependencies 8 | find_dependency(IOKit) 9 | find_dependency(CoreFoundation) 10 | 11 | # Include the exported targets 12 | include("${CMAKE_CURRENT_LIST_DIR}/MacAngleTargets.cmake") 13 | 14 | check_required_components(MacAngle) 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | build/ 3 | Build/ 4 | cmake-build-*/ 5 | 6 | # CMake cache files 7 | CMakeCache.txt 8 | CMakeFiles/ 9 | cmake_install.cmake 10 | Makefile 11 | *.cmake 12 | !CMakeLists.txt 13 | !MacBookLidAngleConfig.cmake.in 14 | 15 | # Compiled binaries 16 | *.a 17 | *.so 18 | *.dylib 19 | *.exe 20 | lid_angle_example 21 | benchmark 22 | 23 | # IDE files 24 | .vscode/ 25 | .idea/ 26 | *.xcodeproj/xcuserdata/ 27 | *.xcworkspace/xcuserdata/ 28 | 29 | # macOS files 30 | .DS_Store 31 | .AppleDouble 32 | .LSOverride 33 | Icon 34 | 35 | # Thumbnails 36 | ._* 37 | 38 | # Files that might appear in the root of a volume 39 | .DocumentRevisions-V100 40 | .fseventsd 41 | .Spotlight-V100 42 | .TemporaryItems 43 | .Trashes 44 | .VolumeIcon.icns 45 | .com.apple.timemachine.donotpresent 46 | 47 | # Directories potentially created on remote AFP share 48 | .AppleDB 49 | .AppleDesktop 50 | Network Trash Folder 51 | Temporary Items 52 | .apdisk 53 | 54 | # Compiler output 55 | *.o 56 | *.obj 57 | *.d -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ming 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | # Project configuration 4 | project(MacBookLidAngle 5 | VERSION 1.0.0 6 | DESCRIPTION "C++ library for reading MacBook lid angle sensor" 7 | LANGUAGES CXX) 8 | 9 | # Set C++ standard 10 | set(CMAKE_CXX_STANDARD 14) 11 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 12 | set(CMAKE_CXX_EXTENSIONS OFF) 13 | 14 | # Only build on macOS 15 | if(NOT APPLE) 16 | message(FATAL_ERROR "This library only supports macOS") 17 | endif() 18 | 19 | # Find required frameworks 20 | find_library(IOKIT_FRAMEWORK IOKit REQUIRED) 21 | find_library(COREFOUNDATION_FRAMEWORK CoreFoundation REQUIRED) 22 | 23 | # Library source files 24 | set(SOURCES 25 | angle.cpp 26 | ) 27 | 28 | set(HEADERS 29 | angle.h 30 | ) 31 | 32 | # Create the library 33 | add_library(lid_angle STATIC ${SOURCES} ${HEADERS}) 34 | 35 | # Set target properties 36 | target_include_directories(lid_angle 37 | PUBLIC 38 | $ 39 | $ 40 | ) 41 | 42 | # Link required frameworks 43 | target_link_libraries(lid_angle 44 | PRIVATE 45 | ${IOKIT_FRAMEWORK} 46 | ${COREFOUNDATION_FRAMEWORK} 47 | ) 48 | 49 | # Compiler flags 50 | target_compile_options(lid_angle PRIVATE 51 | -Wall 52 | -Wextra 53 | -Wpedantic 54 | -Wno-unused-parameter 55 | ) 56 | 57 | # Enable release optimizations by default 58 | if(NOT CMAKE_BUILD_TYPE) 59 | set(CMAKE_BUILD_TYPE Release) 60 | endif() 61 | 62 | # Example program (optional) 63 | option(BUILD_EXAMPLE "Build example program" ON) 64 | if(BUILD_EXAMPLE) 65 | add_executable(lid_angle_example example.cpp) 66 | target_link_libraries(lid_angle_example lid_angle) 67 | endif() 68 | 69 | # Installation 70 | include(GNUInstallDirs) 71 | 72 | install(TARGETS lid_angle 73 | EXPORT MacAngleTargets 74 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 75 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 76 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 77 | ) 78 | 79 | install(FILES ${HEADERS} 80 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 81 | ) 82 | 83 | # Export targets for find_package 84 | install(EXPORT MacAngleTargets 85 | FILE MacAngleTargets.cmake 86 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MacAngle 87 | ) 88 | 89 | # Create config file 90 | include(CMakePackageConfigHelpers) 91 | configure_package_config_file( 92 | "${CMAKE_CURRENT_SOURCE_DIR}/MacAngleConfig.cmake.in" 93 | "${CMAKE_CURRENT_BINARY_DIR}/MacAngleConfig.cmake" 94 | INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MacAngle 95 | ) 96 | 97 | write_basic_package_version_file( 98 | "${CMAKE_CURRENT_BINARY_DIR}/MacAngleConfigVersion.cmake" 99 | VERSION ${PROJECT_VERSION} 100 | COMPATIBILITY AnyNewerVersion 101 | ) 102 | 103 | install(FILES 104 | "${CMAKE_CURRENT_BINARY_DIR}/MacAngleConfig.cmake" 105 | "${CMAKE_CURRENT_BINARY_DIR}/MacAngleConfigVersion.cmake" 106 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MacAngle 107 | ) 108 | -------------------------------------------------------------------------------- /angle.h: -------------------------------------------------------------------------------- 1 | // 2 | // angle.h 3 | // MacBook Lid Angle Sensor C++ Library 4 | // 5 | // A C++ library for reading MacBook lid angle sensor data 6 | // Based on reverse-engineered HID device specifications 7 | // 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | // Forward declaration for IOKit types to avoid exposing IOKit headers in public API 16 | typedef struct __IOHIDDevice * IOHIDDeviceRef; 17 | 18 | namespace MacBookLidAngle { 19 | 20 | /** 21 | * Exception thrown when the lid angle sensor is not supported on this device 22 | */ 23 | class SensorNotSupportedException : public std::exception { 24 | public: 25 | explicit SensorNotSupportedException(const std::string& message) 26 | : message_("Lid angle sensor not supported: " + message) {} 27 | 28 | const char* what() const noexcept override { 29 | return message_.c_str(); 30 | } 31 | 32 | private: 33 | std::string message_; 34 | }; 35 | 36 | /** 37 | * Exception thrown when sensor initialization fails 38 | */ 39 | class SensorInitializationException : public std::exception { 40 | public: 41 | explicit SensorInitializationException(const std::string& message) 42 | : message_("Sensor initialization failed: " + message) {} 43 | 44 | const char* what() const noexcept override { 45 | return message_.c_str(); 46 | } 47 | 48 | private: 49 | std::string message_; 50 | }; 51 | 52 | /** 53 | * Exception thrown when sensor read operation fails 54 | */ 55 | class SensorReadException : public std::exception { 56 | public: 57 | explicit SensorReadException(const std::string& message) 58 | : message_("Sensor read failed: " + message) {} 59 | 60 | const char* what() const noexcept override { 61 | return message_.c_str(); 62 | } 63 | 64 | private: 65 | std::string message_; 66 | }; 67 | 68 | /** 69 | * MacBook Lid Angle Sensor interface 70 | * 71 | * This class provides access to the MacBook's internal lid angle sensor 72 | * through the IOKit HID framework. 73 | * 74 | * Device Specifications: 75 | * - Apple device: VID=0x05AC, PID=0x8104 76 | * - HID Usage: Sensor page (0x0020), Orientation usage (0x008A) 77 | * - Data format: 16-bit angle value in centidegrees (0.01° resolution) 78 | * - Range: 0-360 degrees 79 | * 80 | * Supported devices: 81 | * - MacBook Pro 2019 16-inch and newer 82 | * - MacBook Pro M2/M3/M4 series 83 | * 84 | * Known incompatible devices: 85 | * - M1 MacBook Air/Pro (sensor access limitations) 86 | */ 87 | class LidAngleSensor { 88 | public: 89 | /** 90 | * Constructor - automatically initializes and connects to the sensor 91 | * 92 | * @throws SensorNotSupportedException if sensor hardware is not available 93 | * @throws SensorInitializationException if sensor initialization fails 94 | */ 95 | LidAngleSensor(); 96 | 97 | /** 98 | * Destructor - automatically releases resources 99 | */ 100 | ~LidAngleSensor(); 101 | 102 | // Disable copy constructor and assignment operator 103 | LidAngleSensor(const LidAngleSensor&) = delete; 104 | LidAngleSensor& operator=(const LidAngleSensor&) = delete; 105 | 106 | // Enable move constructor and assignment operator 107 | LidAngleSensor(LidAngleSensor&& other) noexcept; 108 | LidAngleSensor& operator=(LidAngleSensor&& other) noexcept; 109 | 110 | /** 111 | * Check if the sensor is available and ready to read 112 | * 113 | * @return true if sensor is available, false otherwise 114 | */ 115 | bool isAvailable() const noexcept; 116 | 117 | /** 118 | * Read the current lid angle 119 | * 120 | * @return Angle in degrees (0-360) 121 | * @throws SensorReadException if read operation fails 122 | * @throws SensorNotSupportedException if sensor is not available 123 | */ 124 | double readAngle(); 125 | 126 | /** 127 | * Check if this device is expected to have a lid angle sensor 128 | * 129 | * @return true if device model should support the sensor 130 | */ 131 | static bool isDeviceSupported(); 132 | 133 | /** 134 | * Get library version information 135 | * 136 | * @return version string 137 | */ 138 | static std::string getVersion(); 139 | 140 | private: 141 | class Impl; // Forward declaration for PIMPL idiom 142 | std::unique_ptr pImpl; 143 | }; 144 | 145 | } // namespace MacBookLidAngle 146 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // example.cpp 3 | // MacBook Lid Angle Sensor C++ Library Example 4 | // 5 | // Example program demonstrating how to use the MacBook lid angle sensor library 6 | // 7 | 8 | #include "angle.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace MacBookLidAngle; 16 | 17 | void printWelcomeMessage() { 18 | std::cout << "MacBook Lid Angle Sensor C++ Library Example" << std::endl; 19 | std::cout << "===========================================" << std::endl; 20 | std::cout << "Library version: " << LidAngleSensor::getVersion() << std::endl; 21 | std::cout << std::endl; 22 | } 23 | 24 | void checkDeviceCompatibility() { 25 | std::cout << "Checking device compatibility..." << std::endl; 26 | 27 | bool supported = LidAngleSensor::isDeviceSupported(); 28 | if (supported) { 29 | std::cout << "✓ This MacBook appears to support the lid angle sensor." << std::endl; 30 | } else { 31 | std::cout << "✗ This MacBook may not support the lid angle sensor." << std::endl; 32 | std::cout << " Supported devices: MacBook Pro 2019 16-inch and newer" << std::endl; 33 | std::cout << " Known issues: M1 MacBook Air/Pro have compatibility problems" << std::endl; 34 | } 35 | std::cout << std::endl; 36 | } 37 | 38 | void demoBasicReading() { 39 | std::cout << "Demo: Basic angle reading" << std::endl; 40 | std::cout << "------------------------" << std::endl; 41 | 42 | try { 43 | LidAngleSensor sensor; 44 | 45 | if (!sensor.isAvailable()) { 46 | std::cout << "✗ Sensor is not available" << std::endl; 47 | return; 48 | } 49 | 50 | std::cout << "✓ Sensor initialized successfully" << std::endl; 51 | 52 | // Read angle once 53 | double angle = sensor.readAngle(); 54 | std::cout << "Current lid angle: " << std::fixed << std::setprecision(2) 55 | << angle << "°" << std::endl; 56 | 57 | } catch (const SensorNotSupportedException& e) { 58 | std::cout << "✗ Sensor not supported: " << e.what() << std::endl; 59 | } catch (const SensorInitializationException& e) { 60 | std::cout << "✗ Initialization failed: " << e.what() << std::endl; 61 | } catch (const SensorReadException& e) { 62 | std::cout << "✗ Read failed: " << e.what() << std::endl; 63 | } catch (const std::exception& e) { 64 | std::cout << "✗ Unexpected error: " << e.what() << std::endl; 65 | } 66 | 67 | std::cout << std::endl; 68 | } 69 | 70 | void demoContinuousReading() { 71 | std::cout << "Demo: Continuous angle monitoring" << std::endl; 72 | std::cout << "--------------------------------" << std::endl; 73 | std::cout << "Reading lid angle every 0.5 seconds. Press Ctrl+C to stop..." << std::endl; 74 | std::cout << std::endl; 75 | 76 | try { 77 | LidAngleSensor sensor; 78 | 79 | if (!sensor.isAvailable()) { 80 | std::cout << "✗ Sensor is not available for continuous monitoring" << std::endl; 81 | return; 82 | } 83 | 84 | double lastAngle = -1.0; 85 | 86 | while (true) { 87 | try { 88 | double currentAngle = sensor.readAngle(); 89 | 90 | // Only print if angle changed significantly (more than 0.5 degrees) 91 | if (std::abs(currentAngle - lastAngle) > 0.5 || lastAngle == -1.0) { 92 | auto now = std::chrono::system_clock::now(); 93 | auto time_t = std::chrono::system_clock::to_time_t(now); 94 | auto tm = *std::localtime(&time_t); 95 | 96 | std::cout << "[" << std::put_time(&tm, "%H:%M:%S") << "] " 97 | << "Lid angle: " << std::fixed << std::setprecision(2) 98 | << currentAngle << "°"; 99 | 100 | if (lastAngle != -1.0) { 101 | double delta = currentAngle - lastAngle; 102 | std::cout << " (Δ " << std::showpos << std::setprecision(2) 103 | << delta << "°)" << std::noshowpos; 104 | } 105 | 106 | std::cout << std::endl; 107 | lastAngle = currentAngle; 108 | } 109 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 110 | } catch (const SensorReadException& e) { 111 | std::cout << "Read error: " << e.what() << std::endl; 112 | } 113 | } 114 | 115 | } catch (const SensorNotSupportedException& e) { 116 | std::cout << "✗ Sensor not supported: " << e.what() << std::endl; 117 | } catch (const SensorInitializationException& e) { 118 | std::cout << "✗ Initialization failed: " << e.what() << std::endl; 119 | } catch (const std::exception& e) { 120 | std::cout << "✗ Unexpected error: " << e.what() << std::endl; 121 | } 122 | 123 | std::cout << std::endl; 124 | } 125 | 126 | int main(int argc, char* argv[]) { 127 | printWelcomeMessage(); 128 | 129 | // Check command line arguments 130 | bool runContinuous = false; 131 | if (argc > 1 && std::string(argv[1]) == "--continuous") { 132 | runContinuous = true; 133 | } 134 | 135 | checkDeviceCompatibility(); 136 | demoBasicReading(); 137 | 138 | if (runContinuous) { 139 | demoContinuousReading(); 140 | } else { 141 | std::cout << "Tip: Run with --continuous flag for continuous monitoring demo" << std::endl; 142 | std::cout << " ./lid_angle_example --continuous" << std::endl; 143 | } 144 | 145 | return 0; 146 | } 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MacBook Lid Angle Sensor C++ Library 2 | 3 | A C++ library for reading MacBook lid angle sensor data, based on reverse engineering of HID device specifications. 4 | 5 | ## Features 6 | 7 | - 🔍 Direct access to MacBook's built-in lid angle sensor 8 | - 📏 Real-time precise angle measurements (0-360 degree range) 9 | - ⚡ High-performance C++ implementation with modern C++14 standard support 10 | - 🛡️ Comprehensive exception handling mechanism 11 | - 🔧 Clean and easy-to-use API interface 12 | - 📦 CMake build system support 13 | 14 | ## Device Compatibility 15 | 16 | ### Supported Devices 17 | - MacBook Pro 16-inch (2019) and newer models 18 | - MacBook Pro M2/M3/M4 series 19 | 20 | ### Known Incompatible Devices 21 | - M1 MacBook Air/Pro (sensor access restricted) 22 | 23 | ### Technical Specifications 24 | - Device identification: Apple VID=0x05AC, PID=0x8104 25 | - HID usage: Sensor page (0x0020), Orientation usage (0x008A) 26 | - Data format: 16-bit angle value with 0.01-degree precision 27 | - Data range: 0-360 degrees 28 | 29 | ## Quick Start 30 | 31 | ### System Requirements 32 | 33 | - macOS 10.15 or later 34 | - Xcode Command Line Tools 35 | - CMake 3.15 or later 36 | 37 | ### Build and Installation 38 | 39 | ```bash 40 | # Clone the repository 41 | git clone 42 | cd mac-angle 43 | 44 | # Create build directory 45 | mkdir build && cd build 46 | 47 | # Configure build 48 | cmake .. 49 | 50 | # Compile 51 | make 52 | 53 | # Run example program 54 | ./lid_angle_example 55 | ``` 56 | 57 | ### Basic Usage 58 | 59 | ```cpp 60 | #include "angle.h" 61 | #include 62 | 63 | using namespace MacBookLidAngle; 64 | 65 | int main() { 66 | try { 67 | // Create sensor instance 68 | LidAngleSensor sensor; 69 | 70 | // Check if sensor is available 71 | if (sensor.isAvailable()) { 72 | // Read current angle 73 | double angle = sensor.readAngle(); 74 | std::cout << "Current lid angle: " << angle << "°" << std::endl; 75 | } 76 | 77 | } catch (const SensorNotSupportedException& e) { 78 | std::cerr << "Device not supported: " << e.what() << std::endl; 79 | } catch (const SensorInitializationException& e) { 80 | std::cerr << "Initialization failed: " << e.what() << std::endl; 81 | } catch (const SensorReadException& e) { 82 | std::cerr << "Read failed: " << e.what() << std::endl; 83 | } 84 | 85 | return 0; 86 | } 87 | ``` 88 | 89 | ### Continuous Monitoring 90 | 91 | ```cpp 92 | #include "angle.h" 93 | #include 94 | #include 95 | #include 96 | 97 | using namespace MacBookLidAngle; 98 | 99 | int main() { 100 | try { 101 | LidAngleSensor sensor; 102 | 103 | while (true) { 104 | double angle = sensor.readAngle(); 105 | std::cout << "Angle: " << angle << "°" << std::endl; 106 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 107 | } 108 | 109 | } catch (const std::exception& e) { 110 | std::cerr << "Error: " << e.what() << std::endl; 111 | } 112 | 113 | return 0; 114 | } 115 | ``` 116 | 117 | ## API Documentation 118 | 119 | ### Class: `MacBookLidAngle::LidAngleSensor` 120 | 121 | The main sensor interface class providing access to MacBook lid angle sensor. 122 | 123 | #### Constructor 124 | 125 | ```cpp 126 | LidAngleSensor(); 127 | ``` 128 | 129 | Automatically initializes and connects to the sensor. Throws appropriate exceptions if the sensor is unavailable or initialization fails. 130 | 131 | **Exceptions:** 132 | - `SensorNotSupportedException` - Device doesn't support sensor hardware 133 | - `SensorInitializationException` - Sensor initialization failed 134 | 135 | #### Member Functions 136 | 137 | ##### `bool isAvailable() const noexcept` 138 | 139 | Checks if the sensor is available and ready for reading. 140 | 141 | **Returns:** `true` if sensor is available, `false` otherwise 142 | 143 | ##### `double readAngle()` 144 | 145 | Reads the current lid angle. 146 | 147 | **Returns:** Angle value (0-360 degrees) 148 | 149 | **Exceptions:** 150 | - `SensorReadException` - Read operation failed 151 | - `SensorNotSupportedException` - Sensor unavailable 152 | 153 | #### Static Functions 154 | 155 | ##### `static bool isDeviceSupported()` 156 | 157 | Checks if this device should support the lid angle sensor. 158 | 159 | **Returns:** `true` if device model should support the sensor 160 | 161 | ##### `static std::string getVersion()` 162 | 163 | Gets library version information. 164 | 165 | **Returns:** Version string 166 | 167 | ### Exception Classes 168 | 169 | #### `SensorNotSupportedException` 170 | 171 | Thrown when the lid angle sensor is not supported on this device. 172 | 173 | #### `SensorInitializationException` 174 | 175 | Thrown when sensor initialization fails. 176 | 177 | #### `SensorReadException` 178 | 179 | Thrown when sensor read operation fails. 180 | 181 | ## CMake Integration 182 | 183 | ### Using as Subdirectory 184 | 185 | ```cmake 186 | add_subdirectory(macbook-lid-angle) 187 | target_link_libraries(your_target macbook_lid_angle) 188 | ``` 189 | 190 | ### Using After Installation 191 | 192 | ```cmake 193 | find_package(MacBookLidAngle REQUIRED) 194 | target_link_libraries(your_target MacBookLidAngle::macbook_lid_angle) 195 | ``` 196 | 197 | ## Example Program 198 | 199 | The project includes a complete example program `example.cpp` demonstrating: 200 | 201 | - Device compatibility checking 202 | - Basic angle reading 203 | - Continuous monitoring mode 204 | 205 | Run the example: 206 | ```bash 207 | # Basic demonstration 208 | ./lid_angle_example 209 | 210 | # Continuous monitoring demonstration 211 | ./lid_angle_example --continuous 212 | ``` 213 | 214 | ## Troubleshooting 215 | 216 | ### "Sensor not supported" Error 217 | 218 | 1. Confirm your device is in the supported list 219 | 2. Check if other applications are using the sensor 220 | 3. Try restarting your MacBook 221 | 222 | ### "Sensor initialization failed" Error 223 | 224 | 1. Ensure your application has appropriate system permissions 225 | 2. Check macOS version compatibility 226 | 3. Try running with administrator privileges 227 | 228 | ### "Sensor read failed" Error 229 | 230 | 1. Sensor may be temporarily unavailable 231 | 2. Implement retry logic 232 | 3. Check device connection status 233 | 234 | ## Technical Implementation Details 235 | 236 | This library uses macOS IOKit framework to directly access HID devices: 237 | 238 | 1. **Device Discovery**: Match devices by specific VID/PID and HID usage pages 239 | 2. **Device Validation**: Test device response to ensure proper functionality 240 | 3. **Data Reading**: Use Feature Reports to read 16-bit angle data 241 | 4. **Error Handling**: Comprehensive exception handling system 242 | 243 | ## Acknowledgments 244 | 245 | This C++ library is inspired by and based on the original [**Lid Angle Sensor**](https://github.com/samhenrigold/LidAngleSensor) project by **Sam Gold**. We extend our sincere gratitude to Sam for his pioneering work in reverse engineering the MacBook lid angle sensor HID interface and making this functionality accessible to developers. 246 | 247 | The original Objective-C implementation provided the foundation and insights that made this C++ port possible. This project aims to bring the same functionality to C++ developers while maintaining the reliability and accuracy of the original implementation. 248 | 249 | ## License 250 | 251 | This project is developed based on the original LidAngleSensor project and follows open source licensing agreements. 252 | 253 | ## Contributing 254 | 255 | Issues and Pull Requests are welcome! 256 | 257 | ## Related Projects 258 | 259 | - [**Lid Angle Sensor (Objective-C)**](https://github.com/samhenrigold/LidAngleSensor) by Sam Gold - The original inspiration and foundation for this library 260 | - [pybooklid (Python)](https://github.com/tcsenpai/pybooklid) - Python implementation 261 | 262 | ## Author 263 | 264 | C++ port and extensions based on Sam Gold's original work. -------------------------------------------------------------------------------- /angle.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // angle.cpp 3 | // MacBook Lid Angle Sensor C++ Library 4 | // 5 | // Implementation of the MacBook lid angle sensor interface 6 | // 7 | 8 | #include "angle.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace MacBookLidAngle { 17 | 18 | // PIMPL implementation class 19 | class LidAngleSensor::Impl { 20 | public: 21 | Impl(); 22 | ~Impl(); 23 | 24 | bool isAvailable() const noexcept; 25 | double readAngle(); 26 | 27 | private: 28 | IOHIDDeviceRef findLidAngleSensor(); 29 | bool testDevice(IOHIDDeviceRef device); 30 | 31 | IOHIDDeviceRef hidDevice; 32 | bool deviceInitialized; 33 | }; 34 | 35 | LidAngleSensor::Impl::Impl() : hidDevice(nullptr), deviceInitialized(false) { 36 | hidDevice = findLidAngleSensor(); 37 | if (hidDevice) { 38 | IOReturn result = IOHIDDeviceOpen(hidDevice, kIOHIDOptionsTypeNone); 39 | if (result == kIOReturnSuccess) { 40 | deviceInitialized = true; 41 | std::cout << "[MacBookLidAngle] Successfully initialized lid angle sensor" << std::endl; 42 | } else { 43 | CFRelease(hidDevice); 44 | hidDevice = nullptr; 45 | throw SensorInitializationException("Failed to open HID device (IOReturn: " + std::to_string(result) + ")"); 46 | } 47 | } else { 48 | throw SensorNotSupportedException("Lid angle sensor device not found on this MacBook"); 49 | } 50 | } 51 | 52 | LidAngleSensor::Impl::~Impl() { 53 | if (hidDevice) { 54 | if (deviceInitialized) { 55 | IOHIDDeviceClose(hidDevice, kIOHIDOptionsTypeNone); 56 | } 57 | CFRelease(hidDevice); 58 | hidDevice = nullptr; 59 | deviceInitialized = false; 60 | } 61 | } 62 | 63 | bool LidAngleSensor::Impl::isAvailable() const noexcept { 64 | return hidDevice != nullptr && deviceInitialized; 65 | } 66 | 67 | IOHIDDeviceRef LidAngleSensor::Impl::findLidAngleSensor() { 68 | IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); 69 | if (!manager) { 70 | throw SensorInitializationException("Failed to create IOHIDManager"); 71 | } 72 | 73 | if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) { 74 | CFRelease(manager); 75 | throw SensorInitializationException("Failed to open IOHIDManager"); 76 | } 77 | 78 | // Create matching dictionary for the lid angle sensor 79 | // Target: Apple VID=0x05AC, PID=0x8104, Sensor page (0x0020), Orientation usage (0x008A) 80 | CFMutableDictionaryRef matchingDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, 81 | &kCFTypeDictionaryKeyCallBacks, 82 | &kCFTypeDictionaryValueCallBacks); 83 | 84 | // Set vendor ID (Apple) 85 | int vendorIDValue = 0x05AC; 86 | CFNumberRef vendorID = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vendorIDValue); 87 | CFDictionarySetValue(matchingDict, CFSTR(kIOHIDVendorIDKey), vendorID); 88 | CFRelease(vendorID); 89 | 90 | // Set product ID 91 | int productIDValue = 0x8104; 92 | CFNumberRef productID = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &productIDValue); 93 | CFDictionarySetValue(matchingDict, CFSTR(kIOHIDProductIDKey), productID); 94 | CFRelease(productID); 95 | 96 | // Set usage page (Sensor) 97 | int usagePageValue = 0x0020; 98 | CFNumberRef usagePage = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePageValue); 99 | CFDictionarySetValue(matchingDict, CFSTR(kIOHIDPrimaryUsagePageKey), usagePage); 100 | CFRelease(usagePage); 101 | 102 | // Set usage (Orientation) 103 | int usageValue = 0x008A; 104 | CFNumberRef usage = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usageValue); 105 | CFDictionarySetValue(matchingDict, CFSTR(kIOHIDPrimaryUsageKey), usage); 106 | CFRelease(usage); 107 | 108 | IOHIDManagerSetDeviceMatching(manager, matchingDict); 109 | CFRelease(matchingDict); 110 | 111 | CFSetRef devices = IOHIDManagerCopyDevices(manager); 112 | IOHIDDeviceRef foundDevice = nullptr; 113 | 114 | if (devices && CFSetGetCount(devices) > 0) { 115 | std::cout << "[MacBookLidAngle] Found " << CFSetGetCount(devices) << " matching device(s)" << std::endl; 116 | 117 | CFIndex deviceCount = CFSetGetCount(devices); 118 | const void** deviceArray = new const void*[deviceCount]; 119 | CFSetGetValues(devices, deviceArray); 120 | 121 | // Test each matching device to find one that works 122 | for (CFIndex i = 0; i < deviceCount; i++) { 123 | IOHIDDeviceRef candidateDevice = (IOHIDDeviceRef)deviceArray[i]; 124 | if (testDevice(candidateDevice)) { 125 | foundDevice = (IOHIDDeviceRef)CFRetain(candidateDevice); 126 | std::cout << "[MacBookLidAngle] Successfully found working device (index " << i << ")" << std::endl; 127 | break; 128 | } 129 | } 130 | 131 | delete[] deviceArray; 132 | } 133 | 134 | if (devices) { 135 | CFRelease(devices); 136 | } 137 | 138 | IOHIDManagerClose(manager, kIOHIDOptionsTypeNone); 139 | CFRelease(manager); 140 | 141 | return foundDevice; 142 | } 143 | 144 | bool LidAngleSensor::Impl::testDevice(IOHIDDeviceRef device) { 145 | if (IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone) != kIOReturnSuccess) { 146 | return false; 147 | } 148 | 149 | uint8_t testReport[8] = {0}; 150 | CFIndex reportLength = sizeof(testReport); 151 | 152 | IOReturn result = IOHIDDeviceGetReport(device, 153 | kIOHIDReportTypeFeature, 154 | 1, 155 | testReport, 156 | &reportLength); 157 | 158 | IOHIDDeviceClose(device, kIOHIDOptionsTypeNone); 159 | 160 | return (result == kIOReturnSuccess && reportLength >= 3); 161 | } 162 | 163 | double LidAngleSensor::Impl::readAngle() { 164 | if (!isAvailable()) { 165 | throw SensorNotSupportedException("Sensor device is not available"); 166 | } 167 | 168 | uint8_t report[8] = {0}; 169 | CFIndex reportLength = sizeof(report); 170 | 171 | IOReturn result = IOHIDDeviceGetReport(hidDevice, 172 | kIOHIDReportTypeFeature, 173 | 1, 174 | report, 175 | &reportLength); 176 | 177 | if (result != kIOReturnSuccess) { 178 | throw SensorReadException("Failed to read from HID device (IOReturn: " + std::to_string(result) + ")"); 179 | } 180 | 181 | if (reportLength < 3) { 182 | throw SensorReadException("Invalid report length: " + std::to_string(reportLength) + " (expected >= 3)"); 183 | } 184 | 185 | // Parse the 16-bit angle value from bytes 1-2 (skipping report ID at byte 0) 186 | uint16_t rawValue = (report[2] << 8) | report[1]; // High byte, low byte 187 | double angle = static_cast(rawValue); 188 | 189 | return angle; 190 | } 191 | 192 | // Public interface implementation 193 | 194 | LidAngleSensor::LidAngleSensor() : pImpl(std::make_unique()) { 195 | } 196 | 197 | LidAngleSensor::~LidAngleSensor() = default; 198 | 199 | LidAngleSensor::LidAngleSensor(LidAngleSensor&& other) noexcept : pImpl(std::move(other.pImpl)) { 200 | } 201 | 202 | LidAngleSensor& LidAngleSensor::operator=(LidAngleSensor&& other) noexcept { 203 | if (this != &other) { 204 | pImpl = std::move(other.pImpl); 205 | } 206 | return *this; 207 | } 208 | 209 | bool LidAngleSensor::isAvailable() const noexcept { 210 | return pImpl && pImpl->isAvailable(); 211 | } 212 | 213 | double LidAngleSensor::readAngle() { 214 | if (!pImpl) { 215 | throw SensorNotSupportedException("Sensor object not properly initialized"); 216 | } 217 | return pImpl->readAngle(); 218 | } 219 | 220 | bool LidAngleSensor::isDeviceSupported() { 221 | // This is a simple heuristic - a more sophisticated implementation 222 | // could check the actual hardware model 223 | try { 224 | LidAngleSensor testSensor; 225 | return testSensor.isAvailable(); 226 | } catch (const std::exception&) { 227 | return false; 228 | } 229 | } 230 | 231 | std::string LidAngleSensor::getVersion() { 232 | return "1.0.0"; 233 | } 234 | 235 | } // namespace MacBookLidAngle 236 | --------------------------------------------------------------------------------