From b97f1fd0ce79db2336b6354b0a4054b4b449ae82 Mon Sep 17 00:00:00 2001 From: TimmyOVO Date: Tue, 23 Apr 2024 02:16:26 +0800 Subject: [PATCH] feat(macos/capture): support for capture display other than main display (#2449) --- cmake/compile_definitions/macos.cmake | 5 +-- cmake/dependencies/macos.cmake | 1 + docs/source/about/advanced_usage.rst | 32 +++++++++++++------ src/platform/linux/x11grab.cpp | 6 ++-- src/platform/macos/av_video.h | 2 ++ src/platform/macos/av_video.m | 13 +++++++- src/platform/macos/display.mm | 23 +++++++------ src/platform/macos/input.cpp | 21 +++++++++++- src_assets/common/assets/web/config.html | 25 +++++++++------ .../assets/web/public/assets/locale/en.json | 4 +-- 10 files changed, 94 insertions(+), 38 deletions(-) diff --git a/cmake/compile_definitions/macos.cmake b/cmake/compile_definitions/macos.cmake index fff301b856a..549ea1d0f1e 100644 --- a/cmake/compile_definitions/macos.cmake +++ b/cmake/compile_definitions/macos.cmake @@ -8,12 +8,13 @@ link_directories(/opt/homebrew/lib) ADD_DEFINITIONS(-DBOOST_LOG_DYN_LINK) list(APPEND SUNSHINE_EXTERNAL_LIBRARIES + ${APP_KIT_LIBRARY} ${APP_SERVICES_LIBRARY} ${AV_FOUNDATION_LIBRARY} ${CORE_MEDIA_LIBRARY} ${CORE_VIDEO_LIBRARY} - ${VIDEO_TOOLBOX_LIBRARY} - ${FOUNDATION_LIBRARY}) + ${FOUNDATION_LIBRARY} + ${VIDEO_TOOLBOX_LIBRARY}) set(PLATFORM_INCLUDE_DIRS ${Boost_INCLUDE_DIR}) diff --git a/cmake/dependencies/macos.cmake b/cmake/dependencies/macos.cmake index 7d8e211e242..61efc6a902b 100644 --- a/cmake/dependencies/macos.cmake +++ b/cmake/dependencies/macos.cmake @@ -1,5 +1,6 @@ # macos specific dependencies +FIND_LIBRARY(APP_KIT_LIBRARY AppKit) FIND_LIBRARY(APP_SERVICES_LIBRARY ApplicationServices) FIND_LIBRARY(AV_FOUNDATION_LIBRARY AVFoundation) FIND_LIBRARY(CORE_MEDIA_LIBRARY CoreMedia) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index b2e64c0140f..ae15a8704ef 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -576,20 +576,29 @@ keybindings .. tip:: To find the name of the appropriate values follow these instructions. **Linux** - During Sunshine startup, you should see the list of detected monitors: + During Sunshine startup, you should see the list of detected displays: .. code-block:: text - Info: Detecting connected monitors - Info: Detected monitor 0: DVI-D-0, connected: false - Info: Detected monitor 1: HDMI-0, connected: true - Info: Detected monitor 2: DP-0, connected: true - Info: Detected monitor 3: DP-1, connected: false - Info: Detected monitor 4: DVI-D-1, connected: false + Info: Detecting displays + Info: Detected display: DVI-D-0 (id: 0) connected: false + Info: Detected display: HDMI-0 (id: 1) connected: true + Info: Detected display: DP-0 (id: 2) connected: true + Info: Detected display: DP-1 (id: 3) connected: false + Info: Detected display: DVI-D-1 (id: 4) connected: false - You need to use the value before the colon in the output, e.g. ``1``. + You need to use the id value inside the parenthesis, e.g. ``1``. - .. todo:: macOS + **macOS** + During Sunshine startup, you should see the list of detected displays: + + .. code-block:: text + + Info: Detecting displays + Info: Detected display: Monitor-0 (id: 3) connected: true + Info: Detected display: Monitor-1 (id: 2) connected: true + + You need to use the id value inside the parenthesis, e.g. ``3``. **Windows** .. code-block:: batch @@ -605,7 +614,10 @@ keybindings output_name = 0 - .. todo:: macOS + **macOS** + .. code-block:: text + + output_name = 3 **Windows** .. code-block:: text diff --git a/src/platform/linux/x11grab.cpp b/src/platform/linux/x11grab.cpp index 0d4c3d38c30..c3b23a4c97e 100644 --- a/src/platform/linux/x11grab.cpp +++ b/src/platform/linux/x11grab.cpp @@ -421,7 +421,7 @@ namespace platf { } if (streamedMonitor != -1) { - BOOST_LOG(info) << "Configuring selected monitor ("sv << streamedMonitor << ") to stream"sv; + BOOST_LOG(info) << "Configuring selected display ("sv << streamedMonitor << ") to stream"sv; screen_res_t screenr { x11::rr::GetScreenResources(xdisplay.get(), xwindow) }; int output = screenr->noutput; @@ -806,7 +806,7 @@ namespace platf { return {}; } - BOOST_LOG(info) << "Detecting monitors"sv; + BOOST_LOG(info) << "Detecting displays"sv; x11::xdisplay_t xdisplay { x11::OpenDisplay(nullptr) }; if (!xdisplay) { @@ -821,7 +821,7 @@ namespace platf { for (int x = 0; x < output; ++x) { output_info_t out_info { x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) }; if (out_info) { - BOOST_LOG(info) << "Detected monitor "sv << monitor << ": "sv << out_info->name << ", connected: "sv << (out_info->connection == RR_Connected); + BOOST_LOG(info) << "Detected display: "sv << out_info->name << " (id: "sv << monitor << ")"sv << out_info->name << " connected: "sv << (out_info->connection == RR_Connected); ++monitor; } } diff --git a/src/platform/macos/av_video.h b/src/platform/macos/av_video.h index 4bfa00ac006..fac5a78c6e6 100644 --- a/src/platform/macos/av_video.h +++ b/src/platform/macos/av_video.h @@ -5,6 +5,7 @@ #pragma once #import +#import struct CaptureSession { AVCaptureVideoDataOutput *output; @@ -29,6 +30,7 @@ typedef bool (^FrameCallbackBlock)(CMSampleBufferRef); @property (nonatomic, assign) NSMapTable *captureSignals; + (NSArray *)displayNames; ++ (NSString *)getDisplayName:(CGDirectDisplayID)displayID; - (id)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate; diff --git a/src/platform/macos/av_video.m b/src/platform/macos/av_video.m index 5cdf5a9898e..874a87f7b18 100644 --- a/src/platform/macos/av_video.m +++ b/src/platform/macos/av_video.m @@ -23,13 +23,24 @@ @implementation AVVideo for (uint32_t i = 0; i < count; i++) { [result addObject:@{ @"id": [NSNumber numberWithUnsignedInt:displays[i]], - @"name": [NSString stringWithFormat:@"%d", displays[i]] + @"name": [NSString stringWithFormat:@"%d", displays[i]], + @"displayName": [self getDisplayName:displays[i]], }]; } return [NSArray arrayWithArray:result]; } ++ (NSString *)getDisplayName:(CGDirectDisplayID)displayID { + NSScreen *screens = [NSScreen screens]; + for (NSScreen *screen in screens) { + if (screen.deviceDescription[@"NSScreenNumber"] == [NSNumber numberWithUnsignedInt:displayID]) { + return screen.localizedName; + } + } + return nil; +} + - (id)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate { self = [super init]; diff --git a/src/platform/macos/display.mm b/src/platform/macos/display.mm index 6d757176006..3468d46f55e 100644 --- a/src/platform/macos/display.mm +++ b/src/platform/macos/display.mm @@ -142,18 +142,23 @@ auto display = std::make_shared(); + // Default to main display display->display_id = CGMainDisplayID(); - if (!display_name.empty()) { - auto display_array = [AVVideo displayNames]; - - for (NSDictionary *item in display_array) { - NSString *name = item[@"name"]; - if (name.UTF8String == display_name) { - NSNumber *display_id = item[@"id"]; - display->display_id = [display_id unsignedIntValue]; - } + + // Print all displays available with it's name and id + auto display_array = [AVVideo displayNames]; + BOOST_LOG(info) << "Detecting displays"sv; + for (NSDictionary *item in display_array) { + NSNumber *display_id = item[@"id"]; + // We need show display's product name and corresponding display number given by user + NSString *name = item[@"displayName"]; + // We are using CGGetActiveDisplayList that only returns active displays so hardcoded connected value in log to true + BOOST_LOG(info) << "Detected display: "sv << name.UTF8String << " (id: "sv << [NSString stringWithFormat:@"%@", display_id].UTF8String << ") connected: true"sv; + if (!display_name.empty() && std::atoi(display_name.c_str()) == [display_id unsignedIntValue]) { + display->display_id = [display_id unsignedIntValue]; } } + BOOST_LOG(info) << "Configuring selected display ("sv << display->display_id << ") to stream"sv; display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:config.framerate]; diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index 2aa6012ef51..273416680ef 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -509,9 +509,28 @@ const KeyCodeMap kKeyCodesMap[] = { auto macos_input = (macos_input_t *) result.get(); - // If we don't use the main display in the future, this has to be adapted + // Default to main display macos_input->display = CGMainDisplayID(); + auto output_name = config::video.output_name; + // If output_name is set, try to find the display with that display id + if (!output_name.empty()) { + uint32_t max_display = 32; + uint32_t display_count; + CGDirectDisplayID displays[max_display]; + if (CGGetActiveDisplayList(max_display, displays, &display_count) != kCGErrorSuccess) { + BOOST_LOG(error) << "Unable to get active display list , error: "sv << std::endl; + } + else { + for (int i = 0; i < display_count; i++) { + CGDirectDisplayID display_id = displays[i]; + if (display_id == std::atoi(output_name.c_str())) { + macos_input->display = display_id; + } + } + } + } + // Input coordinates are based on the virtual resolution not the physical, so we need the scaling factor CGDisplayModeRef mode = CGDisplayCopyDisplayMode(macos_input->display); macos_input->displayScaling = ((CGFloat) CGDisplayPixelsWide(macos_input->display)) / ((CGFloat) CGDisplayModeGetPixelWidth(mode)); diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 859954ac24e..babb1ad4c46 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -392,19 +392,24 @@

tools\dxgi-info.exe
-
- +
+
- {{ $t('config.output_name_desc_linux') }}
+ {{ $t('config.output_name_desc_unix') }}

-
-              Info: Detecting connected monitors
-              Info: Detected monitor 0: DVI-D-0, connected: false
-              Info: Detected monitor 1: HDMI-0, connected: true
-              Info: Detected monitor 2: DP-0, connected: true
-              Info: Detected monitor 3: DP-1, connected: false
-              Info: Detected monitor 4: DVI-D-1, connected: false
+            
+              Info: Detecting displays
+              Info: Detected display: DVI-D-0 (id: 0) connected: false
+              Info: Detected display: HDMI-0 (id: 1) connected: true
+              Info: Detected display: DP-0 (id: 2) connected: true
+              Info: Detected display: DP-1 (id: 3) connected: false
+              Info: Detected display: DVI-D-1 (id: 4) connected: false
+            
+
+              Info: Detecting displays
+              Info: Detected display: Monitor-0 (id: 3) connected: true
+              Info: Detected display: Monitor-1 (id: 2) connected: true
             
diff --git a/src_assets/common/assets/web/public/assets/locale/en.json b/src_assets/common/assets/web/public/assets/locale/en.json index 37e0b41ee61..4f41fb42a5d 100644 --- a/src_assets/common/assets/web/public/assets/locale/en.json +++ b/src_assets/common/assets/web/public/assets/locale/en.json @@ -243,9 +243,9 @@ "origin_web_ui_allowed_lan": "Only those in LAN may access Web UI", "origin_web_ui_allowed_pc": "Only localhost may access Web UI", "origin_web_ui_allowed_wan": "Anyone may access Web UI", - "output_name_desc_linux": "During Sunshine startup, you should see the list of detected monitors. You need to use the value before the colon in the output. e.g.:", + "output_name_desc_unix": "During Sunshine startup, you should see the list of detected displays. Note: You need to use the id value inside the parenthesis.", "output_name_desc_win": "Manually specify a display to use for capture. If unset, the primary display is captured. Note: If you specified a GPU above, this display must be connected to that GPU. The appropriate values can be found using the following command:", - "output_name_linux": "Monitor number", + "output_name_unix": "Display number", "output_name_win": "Output Name", "ping_timeout": "Ping Timeout", "ping_timeout_desc": "How long to wait in milliseconds for data from moonlight before shutting down the stream",