Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
root = true

[{src,libaegisub,automation,tests,tools}/**.{c,cpp,h,hpp}]
[{src,libaegisub,automation,tests,tools}/**.{c,cpp,h,hpp,mm}]
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ For personal usage, you can use pip and homebrew to install almost all of Aegisu
export PKG_CONFIG_PATH="/usr/local/opt/icu4c/lib/pkgconfig"

When compiling on Apple Silicon, replace `/usr/local` with `/opt/homebrew`.
When compiling on macOS 15.0 (Sequoia) or later, you may also want to `export MACOS_X_DEPLOYMENT_TARGET=14.0` to make the color picker work.

Once the dependencies are installed, build Aegisub with `meson build && meson compile -C build`.

Expand Down
4 changes: 2 additions & 2 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ if host_machine.system() == 'windows' and cc.has_header('dwrite_3.h')
endif

if host_machine.system() == 'darwin'
frameworks_dep = dependency('appleframeworks', modules : ['CoreText', 'CoreFoundation', 'AppKit', 'Carbon', 'IOKit', 'QuartzCore'])
deps += frameworks_dep
deps += dependency('appleframeworks', modules : ['CoreText', 'CoreFoundation', 'AppKit', 'Carbon', 'IOKit', 'QuartzCore'])
deps += dependency('appleframeworks', modules : ['ScreenCaptureKit'], required : false)
endif

# TODO: OSS
Expand Down
35 changes: 5 additions & 30 deletions src/dialog_colorpicker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
#include <wx/textctrl.h>

#ifdef __WXMAC__
#include <ApplicationServices/ApplicationServices.h>
namespace osx { void DropFromScreen(int x, int y, int resx, int resy, int magnification, wxMemoryDC &capdc); }
#endif

namespace {
Expand Down Expand Up @@ -387,42 +387,17 @@ class ColorPickerScreenDropper final : public wxControl {
void DropFromScreenXY(int x, int y);
};

#ifndef MAC_OS_VERSION_15_0
#define MAC_OS_VERSION_15_0 150000
#endif

void ColorPickerScreenDropper::DropFromScreenXY(int x, int y) {
wxMemoryDC capdc(capture);
capdc.SetPen(*wxTRANSPARENT_PEN);
#if !(defined(__WXMAC__) && (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_15_0))

#ifndef __WXMAC__
wxScreenDC screen;
capdc.StretchBlit(0, 0, resx * magnification, resy * magnification,
&screen, x - resx / 2, y - resy / 2, resx, resy);
#else
// wxScreenDC doesn't work on recent versions of OS X so do it manually

// Doesn't bother handling the case where the rect overlaps two monitors
CGDirectDisplayID display_id;
uint32_t display_count;
CGGetDisplaysWithPoint(CGPointMake(x, y), 1, &display_id, &display_count);

agi::scoped_holder<CGImageRef> img(CGDisplayCreateImageForRect(display_id, CGRectMake(x - resx / 2, y - resy / 2, resx, resy)), CGImageRelease);
size_t width = CGImageGetWidth(img);
size_t height = CGImageGetHeight(img);
std::vector<uint8_t> imgdata(height * width * 4);

agi::scoped_holder<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB(), CGColorSpaceRelease);
agi::scoped_holder<CGContextRef> bmp_context(CGBitmapContextCreate(&imgdata[0], width, height, 8, 4 * width, colorspace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big), CGContextRelease);

CGContextDrawImage(bmp_context, CGRectMake(0, 0, width, height), img);

for (int x = 0; x < resx; x++) {
for (int y = 0; y < resy; y++) {
uint8_t *pixel = &imgdata[y * width * 4 + x * 4];
capdc.SetBrush(wxBrush(wxColour(pixel[0], pixel[1], pixel[2])));
capdc.DrawRectangle(x * magnification, y * magnification, magnification, magnification);
}
}
// wxScreenDC doesn't work on OS X so do it manually
osx::DropFromScreen(x, y, resx, resy, magnification, capdc);
#endif

Refresh(false);
Expand Down
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ if host_machine.system() == 'darwin'
aegisub_src += files(
'font_file_lister_coretext.mm',
'osx/osx_utils.mm',
'osx/screenpicker.mm',
)

if conf.get('WITH_INTERNAL_WXWIDGETS', 0) != 0
Expand Down
134 changes: 134 additions & 0 deletions src/osx/screenpicker.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) 2026, arch1t3cht <arch1t3cht@gmail.com>
// Copyright (c) 2012 Thomas Goyne, <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project https://aegisub.org/

#include <libaegisub/scoped_ptr.h>

#include <wx/dcmemory.h>
#include <wx/thread.h>

#import <ApplicationServices/ApplicationServices.h>
#import <CoreGraphics/CGDirectDisplay.h>
#import <ScreenCaptureKit/ScreenCaptureKit.h>

namespace osx {

namespace {

void CopyToDC(CGImageRef img, wxMemoryDC &capdc, int resx, int resy, int magnification) {
int width = CGImageGetWidth(img);
int height = CGImageGetHeight(img);
std::vector<uint8_t> imgdata(height * width * 4);

agi::scoped_holder<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB(), CGColorSpaceRelease);
agi::scoped_holder<CGContextRef> bmp_context(CGBitmapContextCreate(&imgdata[0], width, height, 8, 4 * width, colorspace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big), CGContextRelease);

CGContextDrawImage(bmp_context, CGRectMake(0, 0, width, height), img);

for (int x = 0; x < resx && x < width; x++) {
for (int y = 0; y < resy && y < height; y++) {
uint8_t *pixel = &imgdata[y * width * 4 + x * 4];
capdc.SetBrush(wxBrush(wxColour(pixel[0], pixel[1], pixel[2])));
capdc.DrawRectangle(x * magnification, y * magnification, magnification, magnification);
}
}
}

}

void DropFromScreen(int x, int y, int resx, int resy, int magnification, wxMemoryDC &capdc) {
CGRect rect = CGRectMake(x - resx / 2., y - resy / 2., resx, resy);

#if MAC_OS_X_VERSION_MIN_REQUIRED < 150000
// Doesn't bother handling the case where the rect overlaps two monitors
CGDirectDisplayID display_id;
uint32_t display_count;
CGGetDisplaysWithPoint(CGPointMake(x, y), 1, &display_id, &display_count);

agi::scoped_holder<CGImageRef> img(CGDisplayCreateImageForRect(display_id, rect), CGImageRelease);
CopyToDC(img, capdc, resx, resy, magnification);
#else
wxSemaphore screenshot_notify{0, 1};
wxSemaphore &screenshot_notify_ref = screenshot_notify;

#if MAC_OS_X_VERSION_MIN_REQUIRED < 152000
// captureImageInRect is only available from 15.2 onward so before that we need to do it the cumbersome (and slow) way.

rect.origin.x = std::roundl(rect.origin.x);
rect.origin.y = std::roundl(rect.origin.y);

[SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent * _Nullable shareableContent, NSError * _Nullable error) {
if (error) {
screenshot_notify_ref.Post();
return;
}

CGDirectDisplayID display_id;
uint32_t display_count;
CGGetDisplaysWithPoint(CGPointMake(x, y), 1, &display_id, &display_count);

CGDisplayModeRef displaymode = CGDisplayCopyDisplayMode(display_id);
int scale_factor = CGDisplayModeGetPixelWidth(displaymode) / CGDisplayModeGetWidth(displaymode);

SCDisplay* point_display = nullptr;

for (SCDisplay *display in shareableContent.displays) {
if (display.displayID == display_id) {
point_display = display;
}
}

if (!point_display) {
screenshot_notify_ref.Post();
return;
}

SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:point_display excludingWindows:@[]];
SCStreamConfiguration *configuration = [[SCStreamConfiguration alloc] init];
configuration.capturesAudio = NO;
configuration.showsCursor = NO;
configuration.captureDynamicRange = SCCaptureDynamicRangeSDR;
configuration.sourceRect = rect;
configuration.width = resx * scale_factor;
configuration.height = resy * scale_factor;

[SCScreenshotManager captureImageWithFilter:filter configuration:configuration completionHandler:^(CGImageRef _Nullable sampleBuffer, NSError * _Nullable error) {
if (error) {
screenshot_notify_ref.Post();
return;
}

CopyToDC(sampleBuffer, capdc, resx, resy, magnification);
screenshot_notify_ref.Post();
}];
}];

#else
[SCScreenshotManager captureImageInRect:rect completionHandler:^(CGImageRef _Nullable image, NSError * _Nullable error){
if (!error)
CopyToDC(image, capdc, resx, resy, magnification);

screenshot_notify_ref.Post();
}];
#endif

screenshot_notify.Wait();
return;
#endif

}

}
Loading