Everywhere: Move the Ladybird folder to UI

This commit is contained in:
Timothy Flynn 2024-11-09 12:50:33 -05:00 committed by Andreas Kling
commit db47cc41f8
Notes: github-actions[bot] 2024-11-10 11:51:45 +00:00
203 changed files with 266 additions and 244 deletions

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
#include <LibIPC/Forward.h>
#include <LibMain/Main.h>
#include <LibURL/URL.h>
#include <LibWebView/Forward.h>
#import <Cocoa/Cocoa.h>
namespace Ladybird {
class WebViewBridge;
}
@interface Application : NSApplication
- (void)setupWebViewApplication:(Main::Arguments&)arguments
newTabPageURL:(URL::URL)new_tab_page_url;
- (ErrorOr<void>)launchRequestServer;
- (ErrorOr<void>)launchImageDecoder;
- (ErrorOr<NonnullRefPtr<WebView::WebContentClient>>)launchWebContent:(Ladybird::WebViewBridge&)web_view_bridge;
- (ErrorOr<IPC::File>)launchWebWorker;
@end

View file

@ -0,0 +1,156 @@
/*
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Interface/LadybirdWebViewBridge.h>
#include <LibCore/EventLoop.h>
#include <LibCore/ThreadEventQueue.h>
#include <LibImageDecoderClient/Client.h>
#include <LibRequests/RequestClient.h>
#include <LibWebView/Application.h>
#include <LibWebView/WebContentClient.h>
#include <UI/HelperProcess.h>
#include <UI/Utilities.h>
#include <Utilities/Conversions.h>
#import <Application/Application.h>
#if !__has_feature(objc_arc)
# error "This project requires ARC"
#endif
namespace Ladybird {
class ApplicationBridge : public WebView::Application {
WEB_VIEW_APPLICATION(ApplicationBridge)
private:
virtual Optional<ByteString> ask_user_for_download_folder() const override
{
auto* panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:NO];
[panel setCanChooseDirectories:YES];
[panel setCanChooseFiles:NO];
[panel setMessage:@"Select download directory"];
if ([panel runModal] != NSModalResponseOK)
return {};
return Ladybird::ns_string_to_byte_string([[panel URL] path]);
}
};
ApplicationBridge::ApplicationBridge(Badge<WebView::Application>, Main::Arguments&)
{
}
}
@interface Application ()
{
OwnPtr<Ladybird::ApplicationBridge> m_application_bridge;
RefPtr<Requests::RequestClient> m_request_server_client;
RefPtr<ImageDecoderClient::Client> m_image_decoder_client;
}
@end
@implementation Application
#pragma mark - Public methods
- (void)setupWebViewApplication:(Main::Arguments&)arguments
newTabPageURL:(URL::URL)new_tab_page_url
{
m_application_bridge = Ladybird::ApplicationBridge::create(arguments, move(new_tab_page_url));
}
- (ErrorOr<void>)launchRequestServer
{
auto request_server_paths = TRY(get_paths_for_helper_process("RequestServer"sv));
m_request_server_client = TRY(launch_request_server_process(request_server_paths, s_ladybird_resource_root));
return {};
}
static ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_new_image_decoder()
{
auto image_decoder_paths = TRY(get_paths_for_helper_process("ImageDecoder"sv));
return launch_image_decoder_process(image_decoder_paths);
}
- (ErrorOr<void>)launchImageDecoder
{
m_image_decoder_client = TRY(launch_new_image_decoder());
__weak Application* weak_self = self;
m_image_decoder_client->on_death = [weak_self]() {
Application* self = weak_self;
if (self == nil) {
return;
}
m_image_decoder_client = nullptr;
if (auto err = [self launchImageDecoder]; err.is_error()) {
dbgln("Failed to restart image decoder: {}", err.error());
VERIFY_NOT_REACHED();
}
auto num_clients = WebView::WebContentClient::client_count();
auto new_sockets = m_image_decoder_client->send_sync_but_allow_failure<Messages::ImageDecoderServer::ConnectNewClients>(num_clients);
if (!new_sockets || new_sockets->sockets().size() == 0) {
dbgln("Failed to connect {} new clients to ImageDecoder", num_clients);
VERIFY_NOT_REACHED();
}
WebView::WebContentClient::for_each_client([sockets = new_sockets->take_sockets()](WebView::WebContentClient& client) mutable {
client.async_connect_to_image_decoder(sockets.take_last());
return IterationDecision::Continue;
});
};
return {};
}
- (ErrorOr<NonnullRefPtr<WebView::WebContentClient>>)launchWebContent:(Ladybird::WebViewBridge&)web_view_bridge
{
// FIXME: Fail to open the tab, rather than crashing the whole application if this fails
auto request_server_socket = TRY(connect_new_request_server_client(*m_request_server_client));
auto image_decoder_socket = TRY(connect_new_image_decoder_client(*m_image_decoder_client));
auto web_content_paths = TRY(get_paths_for_helper_process("WebContent"sv));
auto web_content = TRY(launch_web_content_process(web_view_bridge, web_content_paths, move(image_decoder_socket), move(request_server_socket)));
return web_content;
}
- (ErrorOr<IPC::File>)launchWebWorker
{
auto web_worker_paths = TRY(get_paths_for_helper_process("WebWorker"sv));
auto worker_client = TRY(launch_web_worker_process(web_worker_paths, *m_request_server_client));
return worker_client->clone_transport();
}
#pragma mark - NSApplication
- (void)terminate:(id)sender
{
Core::EventLoop::current().quit(0);
}
- (void)sendEvent:(NSEvent*)event
{
if ([event type] == NSEventTypeApplicationDefined) {
Core::ThreadEventQueue::current().process();
} else {
[super sendEvent:event];
}
}
@end

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <AK/StringView.h>
#include <LibURL/URL.h>
#include <LibWeb/CSS/PreferredColorScheme.h>
#include <LibWeb/CSS/PreferredContrast.h>
#include <LibWeb/CSS/PreferredMotion.h>
#include <LibWeb/HTML/ActivateTab.h>
#include <LibWebView/Forward.h>
#import <Cocoa/Cocoa.h>
@class Tab;
@class TabController;
@interface ApplicationDelegate : NSObject <NSApplicationDelegate>
- (nullable instancetype)init;
- (nonnull TabController*)createNewTab:(Optional<URL::URL> const&)url
fromTab:(nullable Tab*)tab
activateTab:(Web::HTML::ActivateTab)activate_tab;
- (nonnull TabController*)createNewTab:(StringView)html
url:(URL::URL const&)url
fromTab:(nullable Tab*)tab
activateTab:(Web::HTML::ActivateTab)activate_tab;
- (nonnull TabController*)createChildTab:(Optional<URL::URL> const&)url
fromTab:(nonnull Tab*)tab
activateTab:(Web::HTML::ActivateTab)activate_tab
pageIndex:(u64)page_index;
- (void)setActiveTab:(nonnull Tab*)tab;
- (nullable Tab*)activeTab;
- (void)removeTab:(nonnull TabController*)controller;
- (Web::CSS::PreferredColorScheme)preferredColorScheme;
- (Web::CSS::PreferredContrast)preferredContrast;
- (Web::CSS::PreferredMotion)preferredMotion;
- (WebView::SearchEngine const&)searchEngine;
@end

View file

@ -0,0 +1,802 @@
/*
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWebView/Application.h>
#include <LibWebView/CookieJar.h>
#include <LibWebView/SearchEngine.h>
#import <Application/ApplicationDelegate.h>
#import <Interface/LadybirdWebView.h>
#import <Interface/Tab.h>
#import <Interface/TabController.h>
#import <LibWebView/UserAgent.h>
#if defined(LADYBIRD_USE_SWIFT)
// FIXME: Report this codegen error to Apple
# define StyleMask NSWindowStyleMask
# import <Ladybird-Swift.h>
# undef StyleMask
#else
# import <Interface/TaskManagerController.h>
#endif
#import <Utilities/Conversions.h>
#if !__has_feature(objc_arc)
# error "This project requires ARC"
#endif
@interface ApplicationDelegate () <TaskManagerDelegate>
{
Web::CSS::PreferredColorScheme m_preferred_color_scheme;
Web::CSS::PreferredContrast m_preferred_contrast;
Web::CSS::PreferredMotion m_preferred_motion;
ByteString m_navigator_compatibility_mode;
WebView::SearchEngine m_search_engine;
}
@property (nonatomic, strong) NSMutableArray<TabController*>* managed_tabs;
@property (nonatomic, weak) Tab* active_tab;
@property (nonatomic, strong) TaskManagerController* task_manager_controller;
- (NSMenuItem*)createApplicationMenu;
- (NSMenuItem*)createFileMenu;
- (NSMenuItem*)createEditMenu;
- (NSMenuItem*)createViewMenu;
- (NSMenuItem*)createSettingsMenu;
- (NSMenuItem*)createHistoryMenu;
- (NSMenuItem*)createInspectMenu;
- (NSMenuItem*)createDebugMenu;
- (NSMenuItem*)createWindowMenu;
- (NSMenuItem*)createHelpMenu;
@end
@implementation ApplicationDelegate
- (instancetype)init
{
if (self = [super init]) {
[NSApp setMainMenu:[[NSMenu alloc] init]];
[[NSApp mainMenu] addItem:[self createApplicationMenu]];
[[NSApp mainMenu] addItem:[self createFileMenu]];
[[NSApp mainMenu] addItem:[self createEditMenu]];
[[NSApp mainMenu] addItem:[self createViewMenu]];
[[NSApp mainMenu] addItem:[self createSettingsMenu]];
[[NSApp mainMenu] addItem:[self createHistoryMenu]];
[[NSApp mainMenu] addItem:[self createInspectMenu]];
[[NSApp mainMenu] addItem:[self createDebugMenu]];
[[NSApp mainMenu] addItem:[self createWindowMenu]];
[[NSApp mainMenu] addItem:[self createHelpMenu]];
self.managed_tabs = [[NSMutableArray alloc] init];
m_preferred_color_scheme = Web::CSS::PreferredColorScheme::Auto;
m_preferred_contrast = Web::CSS::PreferredContrast::Auto;
m_preferred_motion = Web::CSS::PreferredMotion::Auto;
m_navigator_compatibility_mode = "chrome";
m_search_engine = WebView::default_search_engine();
// Reduce the tooltip delay, as the default delay feels quite long.
[[NSUserDefaults standardUserDefaults] setObject:@100 forKey:@"NSInitialToolTipDelay"];
}
return self;
}
#pragma mark - Public methods
- (TabController*)createNewTab:(Optional<URL::URL> const&)url
fromTab:(Tab*)tab
activateTab:(Web::HTML::ActivateTab)activate_tab
{
auto* controller = [self createNewTab:activate_tab fromTab:tab];
if (url.has_value()) {
[controller loadURL:*url];
}
return controller;
}
- (nonnull TabController*)createNewTab:(StringView)html
url:(URL::URL const&)url
fromTab:(nullable Tab*)tab
activateTab:(Web::HTML::ActivateTab)activate_tab
{
auto* controller = [self createNewTab:activate_tab fromTab:tab];
[controller loadHTML:html url:url];
return controller;
}
- (nonnull TabController*)createChildTab:(Optional<URL::URL> const&)url
fromTab:(nonnull Tab*)tab
activateTab:(Web::HTML::ActivateTab)activate_tab
pageIndex:(u64)page_index
{
auto* controller = [self createChildTab:activate_tab fromTab:tab pageIndex:page_index];
if (url.has_value()) {
[controller loadURL:*url];
}
return controller;
}
- (void)setActiveTab:(Tab*)tab
{
self.active_tab = tab;
}
- (Tab*)activeTab
{
return self.active_tab;
}
- (void)removeTab:(TabController*)controller
{
[self.managed_tabs removeObject:controller];
if ([self.managed_tabs count] == 0u) {
if (self.task_manager_controller != nil) {
[self.task_manager_controller.window close];
}
}
}
- (Web::CSS::PreferredColorScheme)preferredColorScheme
{
return m_preferred_color_scheme;
}
- (Web::CSS::PreferredContrast)preferredContrast
{
return m_preferred_contrast;
}
- (Web::CSS::PreferredMotion)preferredMotion
{
return m_preferred_motion;
}
- (WebView::SearchEngine const&)searchEngine
{
return m_search_engine;
}
#pragma mark - Private methods
- (void)openAboutVersionPage:(id)sender
{
auto* current_tab = [NSApp keyWindow];
if (![current_tab isKindOfClass:[Tab class]]) {
return;
}
[self createNewTab:URL::URL("about:version"sv)
fromTab:(Tab*)current_tab
activateTab:Web::HTML::ActivateTab::Yes];
}
- (nonnull TabController*)createNewTab:(Web::HTML::ActivateTab)activate_tab
fromTab:(nullable Tab*)tab
{
auto* controller = [[TabController alloc] init];
[self initializeTabController:controller
activateTab:activate_tab
fromTab:tab];
return controller;
}
- (nonnull TabController*)createChildTab:(Web::HTML::ActivateTab)activate_tab
fromTab:(nonnull Tab*)tab
pageIndex:(u64)page_index
{
auto* controller = [[TabController alloc] initAsChild:tab pageIndex:page_index];
[self initializeTabController:controller
activateTab:activate_tab
fromTab:tab];
return controller;
}
- (void)initializeTabController:(TabController*)controller
activateTab:(Web::HTML::ActivateTab)activate_tab
fromTab:(nullable Tab*)tab
{
[controller showWindow:nil];
if (tab) {
[[tab tabGroup] addWindow:controller.window];
// FIXME: Can we create the tabbed window above without it becoming active in the first place?
if (activate_tab == Web::HTML::ActivateTab::No) {
[tab orderFront:nil];
}
}
if (activate_tab == Web::HTML::ActivateTab::Yes) {
[[controller window] orderFrontRegardless];
}
[self.managed_tabs addObject:controller];
[controller onCreateNewTab];
}
- (void)closeCurrentTab:(id)sender
{
auto* current_window = [NSApp keyWindow];
[current_window close];
}
- (void)openTaskManager:(id)sender
{
if (self.task_manager_controller != nil) {
[self.task_manager_controller.window makeKeyAndOrderFront:sender];
return;
}
self.task_manager_controller = [[TaskManagerController alloc] initWithDelegate:self];
[self.task_manager_controller showWindow:nil];
}
- (void)openLocation:(id)sender
{
auto* current_tab = [NSApp keyWindow];
if (![current_tab isKindOfClass:[Tab class]]) {
return;
}
auto* controller = (TabController*)[current_tab windowController];
[controller focusLocationToolbarItem];
}
- (void)setAutoPreferredColorScheme:(id)sender
{
m_preferred_color_scheme = Web::CSS::PreferredColorScheme::Auto;
[self broadcastPreferredColorSchemeUpdate];
}
- (void)setDarkPreferredColorScheme:(id)sender
{
m_preferred_color_scheme = Web::CSS::PreferredColorScheme::Dark;
[self broadcastPreferredColorSchemeUpdate];
}
- (void)setLightPreferredColorScheme:(id)sender
{
m_preferred_color_scheme = Web::CSS::PreferredColorScheme::Light;
[self broadcastPreferredColorSchemeUpdate];
}
- (void)broadcastPreferredColorSchemeUpdate
{
for (TabController* controller in self.managed_tabs) {
auto* tab = (Tab*)[controller window];
[[tab web_view] setPreferredColorScheme:m_preferred_color_scheme];
}
}
- (void)setAutoPreferredContrast:(id)sender
{
m_preferred_contrast = Web::CSS::PreferredContrast::Auto;
[self broadcastPreferredContrastUpdate];
}
- (void)setLessPreferredContrast:(id)sender
{
m_preferred_contrast = Web::CSS::PreferredContrast::Less;
[self broadcastPreferredContrastUpdate];
}
- (void)setMorePreferredContrast:(id)sender
{
m_preferred_contrast = Web::CSS::PreferredContrast::More;
[self broadcastPreferredContrastUpdate];
}
- (void)setNoPreferencePreferredContrast:(id)sender
{
m_preferred_contrast = Web::CSS::PreferredContrast::NoPreference;
[self broadcastPreferredContrastUpdate];
}
- (void)broadcastPreferredContrastUpdate
{
for (TabController* controller in self.managed_tabs) {
auto* tab = (Tab*)[controller window];
[[tab web_view] setPreferredContrast:m_preferred_contrast];
}
}
- (void)setAutoPreferredMotion:(id)sender
{
m_preferred_motion = Web::CSS::PreferredMotion::Auto;
[self broadcastPreferredMotionUpdate];
}
- (void)setNoPreferencePreferredMotion:(id)sender
{
m_preferred_motion = Web::CSS::PreferredMotion::NoPreference;
[self broadcastPreferredMotionUpdate];
}
- (void)setReducePreferredMotion:(id)sender
{
m_preferred_motion = Web::CSS::PreferredMotion::Reduce;
[self broadcastPreferredMotionUpdate];
}
- (void)broadcastPreferredMotionUpdate
{
for (TabController* controller in self.managed_tabs) {
auto* tab = (Tab*)[controller window];
[[tab web_view] setPreferredMotion:m_preferred_motion];
}
}
- (void)setSearchEngine:(id)sender
{
auto* item = (NSMenuItem*)sender;
auto title = Ladybird::ns_string_to_string([item title]);
if (auto search_engine = WebView::find_search_engine_by_name(title); search_engine.has_value())
m_search_engine = search_engine.release_value();
else
m_search_engine = WebView::default_search_engine();
}
- (void)clearHistory:(id)sender
{
for (TabController* controller in self.managed_tabs) {
[controller clearHistory];
}
}
- (void)dumpCookies:(id)sender
{
WebView::Application::cookie_jar().dump_cookies();
}
- (NSMenuItem*)createApplicationMenu
{
auto* menu = [[NSMenuItem alloc] init];
auto* process_name = [[NSProcessInfo processInfo] processName];
auto* submenu = [[NSMenu alloc] initWithTitle:process_name];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"About %@", process_name]
action:@selector(openAboutVersionPage:)
keyEquivalent:@""]];
[submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"Hide %@", process_name]
action:@selector(hide:)
keyEquivalent:@"h"]];
[submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"Quit %@", process_name]
action:@selector(terminate:)
keyEquivalent:@"q"]];
[menu setSubmenu:submenu];
return menu;
}
- (NSMenuItem*)createFileMenu
{
auto* menu = [[NSMenuItem alloc] init];
auto* submenu = [[NSMenu alloc] initWithTitle:@"File"];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"New Tab"
action:@selector(createNewTab:)
keyEquivalent:@"t"]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Close Tab"
action:@selector(closeCurrentTab:)
keyEquivalent:@"w"]];
[submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Location"
action:@selector(openLocation:)
keyEquivalent:@"l"]];
[menu setSubmenu:submenu];
return menu;
}
- (NSMenuItem*)createEditMenu
{
auto* menu = [[NSMenuItem alloc] init];
auto* submenu = [[NSMenu alloc] initWithTitle:@"Edit"];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Undo"
action:@selector(undo:)
keyEquivalent:@"z"]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Redo"
action:@selector(redo:)
keyEquivalent:@"y"]];
[submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Cut"
action:@selector(cut:)
keyEquivalent:@"x"]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy"
action:@selector(copy:)
keyEquivalent:@"c"]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Paste"
action:@selector(paste:)
keyEquivalent:@"v"]];
[submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Select All"
action:@selector(selectAll:)
keyEquivalent:@"a"]];
[submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Find..."
action:@selector(find:)
keyEquivalent:@"f"]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Find Next"
action:@selector(findNextMatch:)
keyEquivalent:@"g"]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Find Previous"
action:@selector(findPreviousMatch:)
keyEquivalent:@"G"]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Use Selection for Find"
action:@selector(useSelectionForFind:)
keyEquivalent:@"e"]];
[menu setSubmenu:submenu];
return menu;
}
- (NSMenuItem*)createViewMenu
{
auto* menu = [[NSMenuItem alloc] init];
auto* submenu = [[NSMenu alloc] initWithTitle:@"View"];
auto* color_scheme_menu = [[NSMenu alloc] init];
[color_scheme_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Auto"
action:@selector(setAutoPreferredColorScheme:)
keyEquivalent:@""]];
[color_scheme_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Dark"
action:@selector(setDarkPreferredColorScheme:)
keyEquivalent:@""]];
[color_scheme_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Light"
action:@selector(setLightPreferredColorScheme:)
keyEquivalent:@""]];
auto* color_scheme_menu_item = [[NSMenuItem alloc] initWithTitle:@"Color Scheme"
action:nil
keyEquivalent:@""];
[color_scheme_menu_item setSubmenu:color_scheme_menu];
auto* contrast_menu = [[NSMenu alloc] init];
[contrast_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Auto"
action:@selector(setAutoPreferredContrast:)
keyEquivalent:@""]];
[contrast_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Less"
action:@selector(setLessPreferredContrast:)
keyEquivalent:@""]];
[contrast_menu addItem:[[NSMenuItem alloc] initWithTitle:@"More"
action:@selector(setMorePreferredContrast:)
keyEquivalent:@""]];
[contrast_menu addItem:[[NSMenuItem alloc] initWithTitle:@"No Preference"
action:@selector(setNoPreferencePreferredContrast:)
keyEquivalent:@""]];
auto* contrast_menu_item = [[NSMenuItem alloc] initWithTitle:@"Contrast"
action:nil
keyEquivalent:@""];
[contrast_menu_item setSubmenu:contrast_menu];
auto* motion_menu = [[NSMenu alloc] init];
[motion_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Auto"
action:@selector(setAutoPreferredMotion:)
keyEquivalent:@""]];
[motion_menu addItem:[[NSMenuItem alloc] initWithTitle:@"No Preference"
action:@selector(setNoPreferencePreferredMotion:)
keyEquivalent:@""]];
[motion_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Reduce"
action:@selector(setReducePreferredMotion:)
keyEquivalent:@""]];
auto* motion_menu_item = [[NSMenuItem alloc] initWithTitle:@"Motion"
action:nil
keyEquivalent:@""];
[motion_menu_item setSubmenu:motion_menu];
auto* zoom_menu = [[NSMenu alloc] init];
[zoom_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Zoom In"
action:@selector(zoomIn:)
keyEquivalent:@"+"]];
[zoom_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Zoom Out"
action:@selector(zoomOut:)
keyEquivalent:@"-"]];
[zoom_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Actual Size"
action:@selector(resetZoom:)
keyEquivalent:@"0"]];
auto* zoom_menu_item = [[NSMenuItem alloc] initWithTitle:@"Zoom"
action:nil
keyEquivalent:@""];
[zoom_menu_item setSubmenu:zoom_menu];
[submenu addItem:color_scheme_menu_item];
[submenu addItem:contrast_menu_item];
[submenu addItem:motion_menu_item];
[submenu addItem:zoom_menu_item];
[submenu addItem:[NSMenuItem separatorItem]];
[menu setSubmenu:submenu];
return menu;
}
- (NSMenuItem*)createSettingsMenu
{
auto* menu = [[NSMenuItem alloc] init];
auto* submenu = [[NSMenu alloc] initWithTitle:@"Settings"];
auto* search_engine_menu = [[NSMenu alloc] init];
for (auto const& search_engine : WebView::search_engines()) {
[search_engine_menu addItem:[[NSMenuItem alloc] initWithTitle:Ladybird::string_to_ns_string(search_engine.name)
action:@selector(setSearchEngine:)
keyEquivalent:@""]];
}
auto* search_engine_menu_item = [[NSMenuItem alloc] initWithTitle:@"Search Engine"
action:nil
keyEquivalent:@""];
[search_engine_menu_item setSubmenu:search_engine_menu];
[submenu addItem:search_engine_menu_item];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Enable Autoplay"
action:@selector(toggleAutoplay:)
keyEquivalent:@""]];
[menu setSubmenu:submenu];
return menu;
}
- (NSMenuItem*)createHistoryMenu
{
auto* menu = [[NSMenuItem alloc] init];
auto* submenu = [[NSMenu alloc] initWithTitle:@"History"];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Reload Page"
action:@selector(reload:)
keyEquivalent:@"r"]];
[submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Navigate Back"
action:@selector(navigateBack:)
keyEquivalent:@"["]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Navigate Forward"
action:@selector(navigateForward:)
keyEquivalent:@"]"]];
[submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Clear History"
action:@selector(clearHistory:)
keyEquivalent:@""]];
[menu setSubmenu:submenu];
return menu;
}
- (NSMenuItem*)createInspectMenu
{
auto* menu = [[NSMenuItem alloc] init];
auto* submenu = [[NSMenu alloc] initWithTitle:@"Inspect"];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"View Source"
action:@selector(viewSource:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Inspector"
action:@selector(openInspector:)
keyEquivalent:@"I"]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Task Manager"
action:@selector(openTaskManager:)
keyEquivalent:@"M"]];
[menu setSubmenu:submenu];
return menu;
}
- (NSMenuItem*)createDebugMenu
{
auto* menu = [[NSMenuItem alloc] init];
auto* submenu = [[NSMenu alloc] initWithTitle:@"Debug"];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump DOM Tree"
action:@selector(dumpDOMTree:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump Layout Tree"
action:@selector(dumpLayoutTree:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump Paint Tree"
action:@selector(dumpPaintTree:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump Stacking Context Tree"
action:@selector(dumpStackingContextTree:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump Style Sheets"
action:@selector(dumpStyleSheets:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump All Resolved Styles"
action:@selector(dumpAllResolvedStyles:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump History"
action:@selector(dumpHistory:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump Cookies"
action:@selector(dumpCookies:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump Local Storage"
action:@selector(dumpLocalStorage:)
keyEquivalent:@""]];
[submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Show Line Box Borders"
action:@selector(toggleLineBoxBorders:)
keyEquivalent:@""]];
[submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Collect Garbage"
action:@selector(collectGarbage:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Dump GC Graph"
action:@selector(dumpGCGraph:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Clear Cache"
action:@selector(clearCache:)
keyEquivalent:@""]];
[submenu addItem:[NSMenuItem separatorItem]];
auto* spoof_user_agent_menu = [[NSMenu alloc] init];
auto add_user_agent = [spoof_user_agent_menu](ByteString name) {
[spoof_user_agent_menu addItem:[[NSMenuItem alloc] initWithTitle:Ladybird::string_to_ns_string(name)
action:@selector(setUserAgentSpoof:)
keyEquivalent:@""]];
};
add_user_agent("Disabled");
for (auto const& userAgent : WebView::user_agents)
add_user_agent(userAgent.key);
auto* spoof_user_agent_menu_item = [[NSMenuItem alloc] initWithTitle:@"Spoof User Agent"
action:nil
keyEquivalent:@""];
[spoof_user_agent_menu_item setSubmenu:spoof_user_agent_menu];
[submenu addItem:spoof_user_agent_menu_item];
auto* navigator_compatibility_mode_menu = [[NSMenu alloc] init];
auto add_navigator_compatibility_mode = [navigator_compatibility_mode_menu](ByteString name) {
[navigator_compatibility_mode_menu addItem:[[NSMenuItem alloc] initWithTitle:Ladybird::string_to_ns_string(name)
action:@selector(setNavigatorCompatibilityMode:)
keyEquivalent:@""]];
};
add_navigator_compatibility_mode("Chrome");
add_navigator_compatibility_mode("Gecko");
add_navigator_compatibility_mode("WebKit");
auto* navigator_compatibility_mode_menu_item = [[NSMenuItem alloc] initWithTitle:@"Navigator Compatibility Mode"
action:nil
keyEquivalent:@""];
[navigator_compatibility_mode_menu_item setSubmenu:navigator_compatibility_mode_menu];
[submenu addItem:navigator_compatibility_mode_menu_item];
[submenu addItem:[NSMenuItem separatorItem]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Enable Scripting"
action:@selector(toggleScripting:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Block Pop-ups"
action:@selector(togglePopupBlocking:)
keyEquivalent:@""]];
[submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Enable Same-Origin Policy"
action:@selector(toggleSameOriginPolicy:)
keyEquivalent:@""]];
[menu setSubmenu:submenu];
return menu;
}
- (NSMenuItem*)createWindowMenu
{
auto* menu = [[NSMenuItem alloc] init];
auto* submenu = [[NSMenu alloc] initWithTitle:@"Window"];
[NSApp setWindowsMenu:submenu];
[menu setSubmenu:submenu];
return menu;
}
- (NSMenuItem*)createHelpMenu
{
auto* menu = [[NSMenuItem alloc] init];
auto* submenu = [[NSMenu alloc] initWithTitle:@"Help"];
[NSApp setHelpMenu:submenu];
[menu setSubmenu:submenu];
return menu;
}
#pragma mark - NSApplicationDelegate
- (void)applicationDidFinishLaunching:(NSNotification*)notification
{
Tab* tab = nil;
for (auto const& url : WebView::Application::chrome_options().urls) {
auto activate_tab = tab == nil ? Web::HTML::ActivateTab::Yes : Web::HTML::ActivateTab::No;
auto* controller = [self createNewTab:url
fromTab:tab
activateTab:activate_tab];
tab = (Tab*)[controller window];
}
}
- (void)applicationWillTerminate:(NSNotification*)notification
{
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender
{
return YES;
}
- (BOOL)validateMenuItem:(NSMenuItem*)item
{
if ([item action] == @selector(setAutoPreferredColorScheme:)) {
[item setState:(m_preferred_color_scheme == Web::CSS::PreferredColorScheme::Auto) ? NSControlStateValueOn : NSControlStateValueOff];
} else if ([item action] == @selector(setDarkPreferredColorScheme:)) {
[item setState:(m_preferred_color_scheme == Web::CSS::PreferredColorScheme::Dark) ? NSControlStateValueOn : NSControlStateValueOff];
} else if ([item action] == @selector(setLightPreferredColorScheme:)) {
[item setState:(m_preferred_color_scheme == Web::CSS::PreferredColorScheme::Light) ? NSControlStateValueOn : NSControlStateValueOff];
} else if ([item action] == @selector(setAutoPreferredContrast:)) {
[item setState:(m_preferred_contrast == Web::CSS::PreferredContrast::Auto) ? NSControlStateValueOn : NSControlStateValueOff];
} else if ([item action] == @selector(setLessPreferredContrast:)) {
[item setState:(m_preferred_contrast == Web::CSS::PreferredContrast::Less) ? NSControlStateValueOn : NSControlStateValueOff];
} else if ([item action] == @selector(setMorePreferredContrast:)) {
[item setState:(m_preferred_contrast == Web::CSS::PreferredContrast::More) ? NSControlStateValueOn : NSControlStateValueOff];
} else if ([item action] == @selector(setNoPreferencePreferredContrast:)) {
[item setState:(m_preferred_contrast == Web::CSS::PreferredContrast::NoPreference) ? NSControlStateValueOn : NSControlStateValueOff];
} else if ([item action] == @selector(setAutoPreferredMotion:)) {
[item setState:(m_preferred_motion == Web::CSS::PreferredMotion::Auto) ? NSControlStateValueOn : NSControlStateValueOff];
} else if ([item action] == @selector(setNoPreferencePreferredMotion:)) {
[item setState:(m_preferred_motion == Web::CSS::PreferredMotion::NoPreference) ? NSControlStateValueOn : NSControlStateValueOff];
} else if ([item action] == @selector(setReducePreferredMotion:)) {
[item setState:(m_preferred_motion == Web::CSS::PreferredMotion::Reduce) ? NSControlStateValueOn : NSControlStateValueOff];
} else if ([item action] == @selector(setSearchEngine:)) {
auto title = Ladybird::ns_string_to_string([item title]);
[item setState:(m_search_engine.name == title) ? NSControlStateValueOn : NSControlStateValueOff];
}
return YES;
}
#pragma mark - TaskManagerDelegate
- (void)onTaskManagerClosed
{
self.task_manager_controller = nil;
}
@end

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Function.h>
#include <AK/NonnullOwnPtr.h>
#include <LibCore/EventLoopImplementation.h>
namespace Ladybird {
class CFEventLoopManager final : public Core::EventLoopManager {
public:
virtual NonnullOwnPtr<Core::EventLoopImplementation> make_implementation() override;
virtual intptr_t register_timer(Core::EventReceiver&, int interval_milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible) override;
virtual void unregister_timer(intptr_t timer_id) override;
virtual void register_notifier(Core::Notifier&) override;
virtual void unregister_notifier(Core::Notifier&) override;
virtual void did_post_event() override;
virtual int register_signal(int, Function<void(int)>) override;
virtual void unregister_signal(int) override;
};
class CFEventLoopImplementation final : public Core::EventLoopImplementation {
public:
// FIXME: This currently only manages the main NSApp event loop, as that is all we currently
// interact with. When we need multiple event loops, or an event loop that isn't the
// NSApp loop, we will need to create our own CFRunLoop.
static NonnullOwnPtr<CFEventLoopImplementation> create();
virtual int exec() override;
virtual size_t pump(PumpMode) override;
virtual void quit(int) override;
virtual void wake() override;
virtual void post_event(Core::EventReceiver& receiver, NonnullOwnPtr<Core::Event>&&) override;
// FIXME: These APIs only exist for obscure use-cases inside SerenityOS. Try to get rid of them.
virtual void unquit() override { }
virtual bool was_exit_requested() const override { return false; }
virtual void notify_forked_and_in_child() override { }
private:
CFEventLoopImplementation() = default;
int m_exit_code { 0 };
};
}

View file

@ -0,0 +1,414 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/IDAllocator.h>
#include <AK/Singleton.h>
#include <AK/TemporaryChange.h>
#include <LibCore/Event.h>
#include <LibCore/Notifier.h>
#include <LibCore/ThreadEventQueue.h>
#import <Application/EventLoopImplementation.h>
#import <Cocoa/Cocoa.h>
#import <CoreFoundation/CoreFoundation.h>
#include <sys/event.h>
#include <sys/time.h>
#include <sys/types.h>
namespace Ladybird {
struct ThreadData {
static ThreadData& the()
{
static thread_local ThreadData s_thread_data;
return s_thread_data;
}
Core::Notifier& notifier_by_fd(int fd)
{
for (auto notifier : notifiers) {
if (notifier.key->fd() == fd)
return *notifier.key;
}
// If we didn't have a notifier for the provided FD, it should have been unregistered.
VERIFY_NOT_REACHED();
}
IDAllocator timer_id_allocator;
HashMap<int, CFRunLoopTimerRef> timers;
HashMap<Core::Notifier*, CFRunLoopSourceRef> notifiers;
};
class SignalHandlers : public RefCounted<SignalHandlers> {
AK_MAKE_NONCOPYABLE(SignalHandlers);
AK_MAKE_NONMOVABLE(SignalHandlers);
public:
SignalHandlers(int signal_number, CFFileDescriptorCallBack);
~SignalHandlers();
void dispatch();
int add(Function<void(int)>&& handler);
bool remove(int handler_id);
bool is_empty() const
{
if (m_calling_handlers) {
for (auto const& handler : m_handlers_pending) {
if (handler.value)
return false; // an add is pending
}
}
return m_handlers.is_empty();
}
bool have(int handler_id) const
{
if (m_calling_handlers) {
auto it = m_handlers_pending.find(handler_id);
if (it != m_handlers_pending.end()) {
if (!it->value)
return false; // a deletion is pending
}
}
return m_handlers.contains(handler_id);
}
int m_signal_number;
void (*m_original_handler)(int);
HashMap<int, Function<void(int)>> m_handlers;
HashMap<int, Function<void(int)>> m_handlers_pending;
bool m_calling_handlers { false };
CFRunLoopSourceRef m_source { nullptr };
int m_kevent_fd = { -1 };
};
SignalHandlers::SignalHandlers(int signal_number, CFFileDescriptorCallBack handle_signal)
: m_signal_number(signal_number)
, m_original_handler(signal(signal_number, [](int) {}))
{
m_kevent_fd = kqueue();
if (m_kevent_fd < 0) {
dbgln("Unable to create kqueue to register signal {}: {}", signal_number, strerror(errno));
VERIFY_NOT_REACHED();
}
struct kevent changes = {};
EV_SET(&changes, signal_number, EVFILT_SIGNAL, EV_ADD | EV_RECEIPT, 0, 0, nullptr);
if (auto res = kevent(m_kevent_fd, &changes, 1, &changes, 1, NULL); res < 0) {
dbgln("Unable to register signal {}: {}", signal_number, strerror(errno));
VERIFY_NOT_REACHED();
}
CFFileDescriptorContext context = { 0, this, nullptr, nullptr, nullptr };
CFFileDescriptorRef kq_ref = CFFileDescriptorCreate(kCFAllocatorDefault, m_kevent_fd, FALSE, handle_signal, &context);
m_source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, kq_ref, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), m_source, kCFRunLoopDefaultMode);
CFFileDescriptorEnableCallBacks(kq_ref, kCFFileDescriptorReadCallBack);
CFRelease(kq_ref);
}
SignalHandlers::~SignalHandlers()
{
CFRunLoopRemoveSource(CFRunLoopGetMain(), m_source, kCFRunLoopDefaultMode);
CFRelease(m_source);
(void)::signal(m_signal_number, m_original_handler);
::close(m_kevent_fd);
}
struct SignalHandlersInfo {
HashMap<int, NonnullRefPtr<SignalHandlers>> signal_handlers;
int next_signal_id { 0 };
};
static Singleton<SignalHandlersInfo> s_signals;
static SignalHandlersInfo* signals_info()
{
return s_signals.ptr();
}
void SignalHandlers::dispatch()
{
TemporaryChange change(m_calling_handlers, true);
for (auto& handler : m_handlers)
handler.value(m_signal_number);
if (!m_handlers_pending.is_empty()) {
// Apply pending adds/removes
for (auto& handler : m_handlers_pending) {
if (handler.value) {
auto result = m_handlers.set(handler.key, move(handler.value));
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
} else {
m_handlers.remove(handler.key);
}
}
m_handlers_pending.clear();
}
}
int SignalHandlers::add(Function<void(int)>&& handler)
{
int id = ++signals_info()->next_signal_id; // TODO: worry about wrapping and duplicates?
if (m_calling_handlers)
m_handlers_pending.set(id, move(handler));
else
m_handlers.set(id, move(handler));
return id;
}
bool SignalHandlers::remove(int handler_id)
{
VERIFY(handler_id != 0);
if (m_calling_handlers) {
auto it = m_handlers.find(handler_id);
if (it != m_handlers.end()) {
// Mark pending remove
m_handlers_pending.set(handler_id, {});
return true;
}
it = m_handlers_pending.find(handler_id);
if (it != m_handlers_pending.end()) {
if (!it->value)
return false; // already was marked as deleted
it->value = nullptr;
return true;
}
return false;
}
return m_handlers.remove(handler_id);
}
static void post_application_event()
{
auto* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
location:NSMakePoint(0, 0)
modifierFlags:0
timestamp:0
windowNumber:0
context:nil
subtype:0
data1:0
data2:0];
[NSApp postEvent:event atStart:NO];
}
NonnullOwnPtr<Core::EventLoopImplementation> CFEventLoopManager::make_implementation()
{
return CFEventLoopImplementation::create();
}
intptr_t CFEventLoopManager::register_timer(Core::EventReceiver& receiver, int interval_milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible should_fire_when_not_visible)
{
auto& thread_data = ThreadData::the();
auto timer_id = thread_data.timer_id_allocator.allocate();
auto weak_receiver = receiver.make_weak_ptr();
auto interval_seconds = static_cast<double>(interval_milliseconds) / 1000.0;
auto first_fire_time = CFAbsoluteTimeGetCurrent() + interval_seconds;
auto* timer = CFRunLoopTimerCreateWithHandler(
kCFAllocatorDefault, first_fire_time, should_reload ? interval_seconds : 0, 0, 0,
^(CFRunLoopTimerRef) {
auto receiver = weak_receiver.strong_ref();
if (!receiver) {
return;
}
if (should_fire_when_not_visible == Core::TimerShouldFireWhenNotVisible::No) {
if (!receiver->is_visible_for_timer_purposes()) {
return;
}
}
Core::TimerEvent event;
receiver->dispatch_event(event);
});
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
thread_data.timers.set(timer_id, timer);
return timer_id;
}
void CFEventLoopManager::unregister_timer(intptr_t timer_id)
{
auto& thread_data = ThreadData::the();
thread_data.timer_id_allocator.deallocate(static_cast<int>(timer_id));
auto timer = thread_data.timers.take(static_cast<int>(timer_id));
VERIFY(timer.has_value());
CFRunLoopTimerInvalidate(*timer);
CFRelease(*timer);
}
static void socket_notifier(CFSocketRef socket, CFSocketCallBackType notification_type, CFDataRef, void const*, void*)
{
auto& notifier = ThreadData::the().notifier_by_fd(CFSocketGetNative(socket));
// This socket callback is not quite re-entrant. If Core::Notifier::dispatch_event blocks, e.g.
// to wait upon a Core::Promise, this socket will not receive any more notifications until that
// promise is resolved or rejected. So we mark this socket as able to receive more notifications
// before dispatching the event, which allows it to be triggered again.
CFSocketEnableCallBacks(socket, notification_type);
Core::NotifierActivationEvent event(notifier.fd(), notifier.type());
notifier.dispatch_event(event);
// This manual process of enabling the callbacks also seems to require waking the event loop,
// otherwise it hangs indefinitely in any ongoing pump(PumpMode::WaitForEvents) invocation.
post_application_event();
}
void CFEventLoopManager::register_notifier(Core::Notifier& notifier)
{
auto notification_type = kCFSocketNoCallBack;
switch (notifier.type()) {
case Core::Notifier::Type::Read:
notification_type = kCFSocketReadCallBack;
break;
case Core::Notifier::Type::Write:
notification_type = kCFSocketWriteCallBack;
break;
default:
TODO();
break;
}
CFSocketContext context { .version = 0, .info = nullptr, .retain = nullptr, .release = nullptr, .copyDescription = nullptr };
auto* socket = CFSocketCreateWithNative(kCFAllocatorDefault, notifier.fd(), notification_type, &socket_notifier, &context);
CFOptionFlags sockopt = CFSocketGetSocketFlags(socket);
sockopt &= ~kCFSocketAutomaticallyReenableReadCallBack;
sockopt &= ~kCFSocketCloseOnInvalidate;
CFSocketSetSocketFlags(socket, sockopt);
auto* source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
CFRelease(socket);
ThreadData::the().notifiers.set(&notifier, source);
}
void CFEventLoopManager::unregister_notifier(Core::Notifier& notifier)
{
if (auto source = ThreadData::the().notifiers.take(&notifier); source.has_value()) {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), *source, kCFRunLoopCommonModes);
CFRelease(*source);
}
}
void CFEventLoopManager::did_post_event()
{
post_application_event();
}
static void handle_signal(CFFileDescriptorRef f, CFOptionFlags callback_types, void* info)
{
VERIFY(callback_types & kCFFileDescriptorReadCallBack);
auto* signal_handlers = static_cast<SignalHandlers*>(info);
struct kevent event { };
// returns number of events that have occurred since last call
(void)::kevent(CFFileDescriptorGetNativeDescriptor(f), nullptr, 0, &event, 1, nullptr);
CFFileDescriptorEnableCallBacks(f, kCFFileDescriptorReadCallBack);
signal_handlers->dispatch();
}
int CFEventLoopManager::register_signal(int signal_number, Function<void(int)> handler)
{
VERIFY(signal_number != 0);
auto& info = *signals_info();
auto handlers = info.signal_handlers.find(signal_number);
if (handlers == info.signal_handlers.end()) {
auto signal_handlers = adopt_ref(*new SignalHandlers(signal_number, &handle_signal));
auto handler_id = signal_handlers->add(move(handler));
info.signal_handlers.set(signal_number, move(signal_handlers));
return handler_id;
} else {
return handlers->value->add(move(handler));
}
}
void CFEventLoopManager::unregister_signal(int handler_id)
{
VERIFY(handler_id != 0);
int remove_signal_number = 0;
auto& info = *signals_info();
for (auto& h : info.signal_handlers) {
auto& handlers = *h.value;
if (handlers.remove(handler_id)) {
if (handlers.is_empty())
remove_signal_number = handlers.m_signal_number;
break;
}
}
if (remove_signal_number != 0)
info.signal_handlers.remove(remove_signal_number);
}
NonnullOwnPtr<CFEventLoopImplementation> CFEventLoopImplementation::create()
{
return adopt_own(*new CFEventLoopImplementation);
}
int CFEventLoopImplementation::exec()
{
[NSApp run];
return m_exit_code;
}
size_t CFEventLoopImplementation::pump(PumpMode mode)
{
auto* wait_until = mode == PumpMode::WaitForEvents ? [NSDate distantFuture] : [NSDate distantPast];
auto* event = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:wait_until
inMode:NSDefaultRunLoopMode
dequeue:YES];
while (event) {
[NSApp sendEvent:event];
event = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:nil
inMode:NSDefaultRunLoopMode
dequeue:YES];
}
return 0;
}
void CFEventLoopImplementation::quit(int exit_code)
{
m_exit_code = exit_code;
[NSApp stop:nil];
}
void CFEventLoopImplementation::wake()
{
CFRunLoopWakeUp(CFRunLoopGetCurrent());
}
void CFEventLoopImplementation::post_event(Core::EventReceiver& receiver, NonnullOwnPtr<Core::Event>&& event)
{
m_thread_event_queue.post_event(receiver, move(event));
if (&m_thread_event_queue != &Core::ThreadEventQueue::current())
wake();
}
}