node-mac-virtual-display is a Native Node.js addon for macOS that enables creation and management of virtual displays. The library interfaces with macOS CoreGraphics and CoreDisplay APIs to provide programmatic control over virtual displays.
Key Information:
- Language: Objective-C++ (
.mm), JavaScript, TypeScript definitions - Platform: macOS 10.14+ only
- Node.js: v12+
- License: MIT
- Version: 1.0.9
- Primary Use Case: Used in Tab Display for tablet-as-monitor functionality
node-mac-virtual-display/
├── src/
│ └── virtual_display.mm # Main C++ native addon implementation
├── test/
│ └── module.spec.js # Mocha test suite
├── .github/
│ ├── workflows/
│ │ └── release-package.yml # CI/CD for package publishing
│ └── FUNDING.yml # Funding configuration
├── index.js # JavaScript wrapper/entry point
├── index.d.ts # TypeScript type definitions
├── binding.gyp # Node-gyp build configuration
├── package.json # NPM package configuration
├── README.md # User-facing documentation
└── LICENSE # MIT License
-
Native C++ Layer (
src/virtual_display.mm)- Implements
VDisplayclass using N-API (Node Addon API) - Interfaces with private macOS frameworks:
CGVirtualDisplay- Main virtual display controllerCGVirtualDisplayDescriptor- Display hardware descriptorCGVirtualDisplaySettings- Display mode settingsCGVirtualDisplayMode- Resolution/refresh rate configuration
- Implements
-
JavaScript Wrapper (
index.js)- Exports
VirtualDisplayconstructor - Provides clean API over native addon
- Three main methods:
createVirtualDisplay()- Create custom displaycloneVirtualDisplay()- Clone main displaydestroyVirtualDisplay()- Remove virtual display
- Exports
-
TypeScript Definitions (
index.d.ts)- Type-safe interface definitions
- Exports
DisplayInfotype
- Object-Oriented Wrapper: JavaScript class wraps native addon instance
- Resource Management: Manual memory management in C++ with explicit cleanup
- Configuration Objects: Options passed as JavaScript objects with destructuring
- Mirror Mode Handling: Post-processing logic to prevent unintended main display changes
When creating a virtual display, the code performs critical post-processing (lines 149-192 in virtual_display.mm):
- Main Display Restoration: If virtual display becomes main display unintentionally, restore original
- Mirror Prevention: Prevent primary display from mirroring virtual display
- Mirror Mode Configuration: Apply user's mirror preference (extend vs mirror mode)
IMPORTANT: This post-processing logic is essential and should NOT be removed or modified without deep understanding of macOS display behavior.
- Refresh Rate: Clamped to 30-60 Hz range
- PPI: Clamped to 72-300 range
- HiDPI Mode: When enabled, creates both full-res and half-res modes
The native addon uses manual memory management:
- Objects allocated with
[[Class alloc] init] - Must be released with
[object release]inDestroyVirtualDisplay - Potential memory leak if display not properly destroyed
Technology: node-gyp (native addon build tool)
Commands:
npm run build # Rebuild native addon (node-gyp rebuild)
npm run clean # Clean build artifacts (node-gyp clean)Build Configuration (binding.gyp):
- Target:
virtual_display.node - Compiler: Clang with C++14 standard
- macOS Deployment Target: 10.14
- Framework Dependencies: StoreKit
- Compiler Flags:
-std=c++14 -stdlib=libc++ - N-API Exception Mode:
NAPI_DISABLE_CPP_EXCEPTIONS
Framework: Mocha + Chai
Command:
npm test # Run test suiteTest Characteristics:
- Located in
test/module.spec.js - Tests create display and wait 10 minutes before cleanup (600000ms timeout)
- Only basic smoke test - verifies module doesn't throw on initialization
- Clone test commented out by default
IMPORTANT: Tests create real virtual displays - must run on macOS with proper permissions.
Linting:
npm run lint # Check formatting (dry-run)
npm run format # Apply formattingTools:
- JavaScript: Prettier (
.jsfiles) - Objective-C++: clang-format (
.mmfiles)
Git Hooks:
- Husky configured for pre-commit hooks
- lint-staged runs formatters automatically:
*.js→ prettier*.mm→ clang-format
Workflow: .github/workflows/release-package.yml
Triggers: On GitHub release creation
Jobs:
-
Build (macOS runner)
- Checkout code
- Setup Node.js 16
- Run
npm build - Run
npm test
-
Publish (macOS runner, requires build success)
- Checkout code
- Setup Node.js 16 with GitHub Package Registry
- Run
npm build - Run
npm publishto GitHub Packages
Registry: GitHub Packages (https://npm.pkg.github.com)
- Variables: camelCase (
displayName,refreshRate) - Classes: PascalCase (
VirtualDisplay,VDisplay) - Constants: Regular case (no SCREAMING_SNAKE_CASE used)
- Private Members: Underscore prefix (
_display,_descriptor,_settings)
JavaScript:
- Double quotes for strings
- Semicolons required
- 2-space indentation
- CommonJS modules (
require/module.exports)
Objective-C++:
- Follows standard Objective-C conventions
- Uses ARC-style manual memory management
- NSLog for debugging output
- JavaScript Layer: Returns results directly, no explicit error handling
- Native Layer: Throws JavaScript exceptions via N-API:
Napi::TypeErrorfor wrong argument countNapi::Errorfor runtime failures
- Returns
null/falseon errors
- Destructured Parameters: Methods accept single config object
- Sensible Defaults: PPI defaults to 81 (FHD monitor standard)
- Return Values: Always return
DisplayInfoobject with{id, width, height}
- Modify native code in
src/virtual_display.mm - Update JavaScript wrapper in
index.jsif needed - Update TypeScript definitions in
index.d.ts - Add tests in
test/module.spec.js - Run
npm run formatto format code - Run
npm run buildto compile - Run
npm testto verify
When adding new display parameters:
- Update native method signature in
VDisplayclass - Update descriptor/settings initialization in
InitializeDescriptor/InitializeSettings - Update JavaScript wrapper parameter destructuring
- Update TypeScript types in
index.d.ts - Maintain parameter order consistency across layers
- Use
NSLog()for console output (visible in terminal) - Logs include:
- Display IDs during creation
- Mirror mode state changes
- Configuration errors
- Check macOS Console app for additional system logs
- macOS Only: This code CANNOT run on Windows/Linux - uses macOS-private APIs
- Requires macOS 10.14+: Older versions lack
CGVirtualDisplayAPIs - Architecture-Specific: x86_64 and arm64 (Apple Silicon) support via node-gyp
DO NOT MODIFY without explicit user request:
-
Post-processing logic (lines 149-192, 239-282 in
virtual_display.mm)- Prevents macOS display configuration bugs
- Essential for maintaining primary display as main
-
Memory management in
DestroyVirtualDisplay- Release order: descriptor → settings → display
- Setting to nil prevents dangling pointers
-
Mirror mode logic
- Complex interplay with macOS display management
- Wrong configuration can cause display issues
- No input validation on dimensions: Width/height not validated beyond type checking
- Extreme values: Could cause system issues (very large displays, extreme PPI)
- Resource limits: No check for maximum displays or system resources
AI Assistant Action: When adding features, validate user inputs for reasonableness.
- Changing parameter order in native methods breaks JavaScript wrapper
- Modifying return object structure breaks TypeScript definitions
- Changing N-API exception handling can crash Node.js process
- Removing memory cleanup causes memory leaks
- Repository can be cloned anywhere
npm installwill fail on non-macOS (node-gyp compilation requires macOS frameworks)- Package.json specifies
"os": ["darwin"]to prevent installation on wrong platforms
- Tests create real virtual displays
- Requires Screen Recording permission on macOS 10.15+
- May affect active displays during development
- 10-minute timeout allows manual inspection of created display
When modifying APIs, update:
README.md- User-facing documentationindex.d.ts- TypeScript definitionsCLAUDE.md- This file (AI assistant guide)- Inline code comments for complex logic
- Forgetting to destroy displays: Memory leaks and orphaned displays
- Modifying mirror logic: Can break display configuration on user's Mac
- Not testing on real macOS: Code must be tested on actual macOS hardware
- Ignoring clang-format: Pre-commit hooks will fail
- Breaking API compatibility: This is a library - semver matters
# Clone repository
git clone https://github.com/ENFP-Dev-Studio/node-mac-virtual-display.git
cd node-mac-virtual-display
# Install dependencies (macOS only)
yarn install
# Build native addon
npm run build
# Run tests (creates real display - be prepared!)
npm test
# Format code
npm run format
# Check formatting
npm run lint- v1.0.9: Current version
- Recent changes focused on:
- Virtual display as main display handling
- HiDPI scaling adjustments
- Bug fixes for display configuration
- Tab Display: https://tab-display.enfpdev.com
- N-API Documentation: https://nodejs.org/api/n-api.html
- node-gyp Guide: https://github.com/nodejs/node-gyp
- macOS CoreGraphics: Apple Developer Documentation (private APIs used)
- Issues: GitHub Issues
- Funding: Buy Me a Coffee, Patreon (see FUNDING.yml)
- Author: ENFP-Dev-Studio (Jake Roh)
Last Updated: 2025-11-15 (Auto-generated by Claude) Repository Version: 1.0.9