Skip to content

Commit

Permalink
feat(macos/capture): support for capture display other than main disp…
Browse files Browse the repository at this point in the history
…lay (#2449)
  • Loading branch information
TimmyOVO authored Apr 22, 2024
1 parent 067efc7 commit 9288775
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 38 deletions.
5 changes: 3 additions & 2 deletions cmake/compile_definitions/macos.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
1 change: 1 addition & 0 deletions cmake/dependencies/macos.cmake
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
32 changes: 22 additions & 10 deletions docs/source/about/advanced_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -605,7 +614,10 @@ keybindings
output_name = 0
.. todo:: macOS
**macOS**
.. code-block:: text
output_name = 3
**Windows**
.. code-block:: text
Expand Down
6 changes: 3 additions & 3 deletions src/platform/linux/x11grab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/platform/macos/av_video.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#import <AVFoundation/AVFoundation.h>
#import <AppKit/AppKit.h>

struct CaptureSession {
AVCaptureVideoDataOutput *output;
Expand All @@ -29,6 +30,7 @@ typedef bool (^FrameCallbackBlock)(CMSampleBufferRef);
@property (nonatomic, assign) NSMapTable<AVCaptureConnection *, dispatch_semaphore_t> *captureSignals;

+ (NSArray<NSDictionary *> *)displayNames;
+ (NSString *)getDisplayName:(CGDirectDisplayID)displayID;

- (id)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate;

Expand Down
13 changes: 12 additions & 1 deletion src/platform/macos/av_video.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down
23 changes: 14 additions & 9 deletions src/platform/macos/display.mm
Original file line number Diff line number Diff line change
Expand Up @@ -142,18 +142,23 @@

auto display = std::make_shared<av_display_t>();

// 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];

Expand Down
21 changes: 20 additions & 1 deletion src/platform/macos/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
25 changes: 15 additions & 10 deletions src_assets/common/assets/web/config.html
Original file line number Diff line number Diff line change
Expand Up @@ -392,19 +392,24 @@ <h2 class="accordion-header">
<pre>tools\dxgi-info.exe</pre>
</div>
</div>
<div class="mb-3" v-if="platform === 'linux'">
<label for="output_name" class="form-label">{{ $t('config.output_name_linux') }}</label>
<div class="mb-3" v-if="platform === 'linux' || platform === 'macos'">
<label for="output_name" class="form-label">{{ $t('config.output_name_unix') }}</label>
<input type="text" class="form-control" id="output_name" placeholder="0" v-model="config.output_name" />
<div class="form-text">
{{ $t('config.output_name_desc_linux') }}<br>
{{ $t('config.output_name_desc_unix') }}<br>
<br>
<pre style="white-space: pre-line;">
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
<pre style="white-space: pre-line;" v-if="platform === 'linux'">
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
</pre>
<pre style="white-space: pre-line;" v-if="platform === 'macos'">
Info: Detecting displays
Info: Detected display: Monitor-0 (id: 3) connected: true
Info: Detected display: Monitor-1 (id: 2) connected: true
</pre>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src_assets/common/assets/web/public/assets/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 9288775

Please sign in to comment.