From 68ce5f8290c538752f90b0242cb7b97b5aacc91e Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Tue, 16 Jul 2024 05:33:39 -0600 Subject: [PATCH] Ladybird/AppKit: Port TaskManager window to Swift This is just a direct port of the Objective-C++ code to Swift 6. A future patch should probably update it to actually use SwiftUI. --- .github/actions/setup/action.yml | 2 +- .github/workflows/lagom-template.yml | 10 +++ CMakeLists.txt | 6 ++ .../AppKit/Application/ApplicationDelegate.mm | 13 ++- Ladybird/AppKit/CMakeLists.txt | 55 ++++++++++-- Ladybird/AppKit/UI/TaskManager.swift | 64 ++++++++++++++ Ladybird/AppKit/UI/TaskManagerController.h | 2 +- Ladybird/AppKit/UI/TaskManagerController.mm | 2 +- .../AppKit/UI/TaskManagerController.swift | 50 +++++++++++ Ladybird/AppKit/module.modulemap | 14 +++ Ladybird/cmake/GenerateSwiftHeader.cmake | 85 +++++++++++++++++++ Meta/CMake/common_compile_options.cmake | 16 ++++ Meta/CMake/common_options.cmake | 1 + Userland/Libraries/LibWebView/Application.h | 8 +- 14 files changed, 313 insertions(+), 15 deletions(-) create mode 100644 Ladybird/AppKit/UI/TaskManager.swift create mode 100644 Ladybird/AppKit/UI/TaskManagerController.swift create mode 100644 Ladybird/AppKit/module.modulemap create mode 100644 Ladybird/cmake/GenerateSwiftHeader.cmake diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 4b6ad27c6f1..e515f15cb21 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -50,7 +50,7 @@ runs: shell: bash run: | set -e - sudo xcode-select --switch /Applications/Xcode_15.4.app + sudo xcode-select --switch /Applications/Xcode_16.0.app brew update brew install autoconf autoconf-archive automake coreutils bash ffmpeg ninja wabt ccache unzip qt llvm@18 nasm diff --git a/.github/workflows/lagom-template.yml b/.github/workflows/lagom-template.yml index 8985ee69944..bf3e5fdcc2f 100644 --- a/.github/workflows/lagom-template.yml +++ b/.github/workflows/lagom-template.yml @@ -138,6 +138,16 @@ jobs: working-directory: ${{ github.workspace }}/Build run: cmake --build . + - name: Enable the AppKit chrome with Swift files + if: ${{ inputs.os_name == 'macOS' && inputs.fuzzer == 'NO_FUZZ' }} + working-directory: ${{ github.workspace }} + run: cmake -B Build -DENABLE_QT=OFF -DENABLE_SWIFT=ON + + - name: Build the AppKit chrome with Swift files + if: ${{ inputs.os_name == 'macOS' && inputs.fuzzer == 'NO_FUZZ' }} + working-directory: ${{ github.workspace }}/Build + run: cmake --build . + - name: Save Caches uses: ./.github/actions/cache-save with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e39699d364..1f0489b8905 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,15 +36,20 @@ include(lagom_install_options) if (ENABLE_ADDRESS_SANITIZER) add_cxx_compile_options(-fsanitize=address -fno-omit-frame-pointer) add_cxx_link_options(-fsanitize=address) + add_swift_compile_options(-sanitize=address) + add_swift_link_options(-sanitize=address) endif() if (ENABLE_MEMORY_SANITIZER) add_cxx_compile_options(-fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer) add_cxx_link_options(-fsanitize=memory -fsanitize-memory-track-origins) + add_swift_compile_options(-sanitize=memory) + add_swift_link_options(-sanitize=memory) endif() if (ENABLE_UNDEFINED_SANITIZER) add_cxx_compile_options(-fsanitize=undefined -fno-omit-frame-pointer) + add_swift_compile_options(-sanitize=address) if (UNDEFINED_BEHAVIOR_IS_FATAL) add_cxx_compile_options(-fno-sanitize-recover=undefined) endif() @@ -52,6 +57,7 @@ if (ENABLE_UNDEFINED_SANITIZER) add_cxx_compile_options(-fno-sanitize=function) endif() add_cxx_link_options(-fsanitize=undefined) + add_swift_link_options(-sanitize=address) endif() if (HAIKU) diff --git a/Ladybird/AppKit/Application/ApplicationDelegate.mm b/Ladybird/AppKit/Application/ApplicationDelegate.mm index a966bf5c372..14d94e98c5d 100644 --- a/Ladybird/AppKit/Application/ApplicationDelegate.mm +++ b/Ladybird/AppKit/Application/ApplicationDelegate.mm @@ -11,7 +11,16 @@ #import #import #import -#import + +#if defined(LADYBIRD_USE_SWIFT) +// FIXME: Report this codegen error to Apple +# define StyleMask NSWindowStyleMask +# import +# undef StyleMask +#else +# import +#endif + #import #if !__has_feature(objc_arc) @@ -237,7 +246,7 @@ return; } - self.task_manager_controller = [[TaskManagerController alloc] init:self]; + self.task_manager_controller = [[TaskManagerController alloc] initWithDelegate:self]; [self.task_manager_controller showWindow:nil]; } diff --git a/Ladybird/AppKit/CMakeLists.txt b/Ladybird/AppKit/CMakeLists.txt index a7ed57105ba..3e59281462a 100644 --- a/Ladybird/AppKit/CMakeLists.txt +++ b/Ladybird/AppKit/CMakeLists.txt @@ -1,6 +1,5 @@ -add_executable(ladybird MACOSX_BUNDLE +add_library(ladybird_impl STATIC ${LADYBIRD_SOURCES} - main.mm Application/Application.mm Application/ApplicationBridge.cpp Application/ApplicationDelegate.mm @@ -14,14 +13,52 @@ add_executable(ladybird MACOSX_BUNDLE UI/SearchPanel.mm UI/Tab.mm UI/TabController.mm - UI/TaskManager.mm - UI/TaskManagerController.mm Utilities/Conversions.mm ) -target_include_directories(ladybird PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(ladybird PRIVATE "-framework Cocoa -framework UniformTypeIdentifiers" LibUnicode) -target_compile_options(ladybird PRIVATE - -fobjc-arc - -Wno-deprecated-anon-enum-enum-conversion # Required for CGImageCreate +target_include_directories(ladybird_impl PUBLIC $) + +target_compile_options(ladybird_impl PRIVATE "SHELL:$<$:-Xcc -std=c++23 -cxx-interoperability-mode=default>") +target_compile_options(ladybird_impl PUBLIC + $<$:-fobjc-arc> + $<$:-Wno-deprecated-anon-enum-enum-conversion> # Required for CGImageCreate ) +target_compile_features(ladybird_impl PUBLIC cxx_std_23) + +if (ENABLE_SWIFT) + enable_language(Swift) + if (CMAKE_Swift_COMPILER_VERSION VERSION_LESS 6.0) + message(FATAL_ERROR + "Swift 6.0 or newer is required to parse AK C++ headers in C++23 mode" + ) + endif() + if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + message(FATAL_ERROR + "Swift files must use Clang that was bundled with swiftc" + ) + endif() + include(../cmake/GenerateSwiftHeader.cmake) + + target_sources(ladybird_impl PRIVATE + UI/TaskManager.swift + UI/TaskManagerController.swift + ) + target_compile_definitions(ladybird_impl PUBLIC LADYBIRD_USE_SWIFT) + set_target_properties(ladybird_impl PROPERTIES Swift_MODULE_NAME "SwiftLadybird") + + get_target_property(LADYBIRD_NATIVE_DIRS ladybird_impl INCLUDE_DIRECTORIES) + _swift_generate_cxx_header(ladybird_impl "Ladybird-Swift.h" + SEARCH_PATHS ${LADYBIRD_NATIVE_DIRS} + ) +else() + target_sources(ladybird_impl PRIVATE + UI/TaskManager.mm + UI/TaskManagerController.mm + ) +endif() + +add_executable(ladybird MACOSX_BUNDLE + main.mm +) +target_link_libraries(ladybird PRIVATE "-framework Cocoa -framework UniformTypeIdentifiers" LibUnicode ladybird_impl) + create_ladybird_bundle(ladybird) diff --git a/Ladybird/AppKit/UI/TaskManager.swift b/Ladybird/AppKit/UI/TaskManager.swift new file mode 100644 index 00000000000..a2f737b8221 --- /dev/null +++ b/Ladybird/AppKit/UI/TaskManager.swift @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, Tim Flynn + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +import Foundation +import Ladybird.WebView +import Ladybird.WebViewApplication +import SwiftUI + +public class TaskManager: NSWindow { + + private let WINDOW_WIDTH: CGFloat = 600 + private let WINDOW_HEIGHT: CGFloat = 400 + + var web_view: LadybirdWebView + private var timer: Timer? + + init() { + let tab_rect = NSApplication.shared.keyWindow!.frame + let position_x = tab_rect.origin.x + (tab_rect.size.width - WINDOW_WIDTH) / 2 + let position_y = tab_rect.origin.y + (tab_rect.size.height - WINDOW_HEIGHT) / 2 + + let window_rect = NSMakeRect(position_x, position_y, WINDOW_WIDTH, WINDOW_HEIGHT) + let style_mask = NSWindow.StyleMask.init(arrayLiteral: [ + NSWindow.StyleMask.titled, NSWindow.StyleMask.closable, NSWindow.StyleMask.miniaturizable, + NSWindow.StyleMask.resizable, + ]) + + self.web_view = LadybirdWebView.init(nil) + + super.init( + contentRect: window_rect, styleMask: style_mask, backing: NSWindow.BackingStoreType.buffered, + defer: false) + + self.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in + if let strong_self = self { + strong_self.updateStatistics() + } + } + + self.web_view.postsBoundsChangedNotifications = true + let scroll_view = NSScrollView() + scroll_view.hasVerticalScroller = true + scroll_view.hasHorizontalScroller = true + scroll_view.lineScroll = 24 + + scroll_view.contentView = self.web_view + scroll_view.documentView = NSView() + + self.contentView = scroll_view + self.title = "Task Manager" + self.setIsVisible(true) + + self.updateStatistics() + } + + func updateStatistics() { + WebView.Application.the().update_process_statistics(); + self.web_view.loadHTML(WebView.Application.the().generate_process_statistics_html().__bytes_as_string_viewUnsafe()); + } +} diff --git a/Ladybird/AppKit/UI/TaskManagerController.h b/Ladybird/AppKit/UI/TaskManagerController.h index 52d8dab247f..9875f21f8d0 100644 --- a/Ladybird/AppKit/UI/TaskManagerController.h +++ b/Ladybird/AppKit/UI/TaskManagerController.h @@ -16,6 +16,6 @@ @interface TaskManagerController : NSWindowController -- (instancetype)init:(id)delegate; +- (instancetype)initWithDelegate:(id)delegate; @end diff --git a/Ladybird/AppKit/UI/TaskManagerController.mm b/Ladybird/AppKit/UI/TaskManagerController.mm index 45762cd97ef..4d8bed0d251 100644 --- a/Ladybird/AppKit/UI/TaskManagerController.mm +++ b/Ladybird/AppKit/UI/TaskManagerController.mm @@ -20,7 +20,7 @@ @implementation TaskManagerController -- (instancetype)init:(id)delegate +- (instancetype)initWithDelegate:(id)delegate { if (self = [super init]) { self.delegate = delegate; diff --git a/Ladybird/AppKit/UI/TaskManagerController.swift b/Ladybird/AppKit/UI/TaskManagerController.swift new file mode 100644 index 00000000000..e5f00cad590 --- /dev/null +++ b/Ladybird/AppKit/UI/TaskManagerController.swift @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024, Tim Flynn + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +import Foundation +import SwiftUI + +@objc +public protocol TaskManagerDelegate where Self: NSObject { + func onTaskManagerClosed() +} + +public class TaskManagerController: NSWindowController, NSWindowDelegate { + + private weak var delegate: TaskManagerDelegate? + + @objc + public convenience init(delegate: TaskManagerDelegate) { + self.init() + self.delegate = delegate + } + + @IBAction public override func showWindow(_ sender: Any?) { + self.window = TaskManager.init() + self.window!.delegate = self + self.window!.makeKeyAndOrderFront(sender) + } + + public func windowWillClose(_ sender: Notification) { + self.delegate?.onTaskManagerClosed() + } + + public func windowDidResize(_ sender: Notification) { + guard self.window != nil else { return } + if !self.window!.inLiveResize { + self.taskManager().web_view.handleResize() + } + } + + public func windowDidChangeBackingProperties(_ sender: Notification) { + self.taskManager().web_view.handleDevicePixelRatioChange() + } + + private func taskManager() -> TaskManager { + return self.window as! TaskManager + } +} diff --git a/Ladybird/AppKit/module.modulemap b/Ladybird/AppKit/module.modulemap new file mode 100644 index 00000000000..f2d03c58efe --- /dev/null +++ b/Ladybird/AppKit/module.modulemap @@ -0,0 +1,14 @@ +module Ladybird [system] { + requires cplusplus + requires objc_arc + + explicit module WebView { + header "UI/LadybirdWebView.h" + export * + } + + explicit module WebViewApplication { + header "../../Userland/Libraries/LibWebView/Application.h" + export * + } +} diff --git a/Ladybird/cmake/GenerateSwiftHeader.cmake b/Ladybird/cmake/GenerateSwiftHeader.cmake new file mode 100644 index 00000000000..40477dce76f --- /dev/null +++ b/Ladybird/cmake/GenerateSwiftHeader.cmake @@ -0,0 +1,85 @@ +# This source file is part of the Swift open source project +# +# Copyright (c) 2023 Apple Inc. and the Swift project authors. +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See https://swift.org/LICENSE.txt for license information + + +# Generate the bridging header from Swift to C++ +# +# target: the name of the target to generate headers for. +# This target must build swift source files. +# header: the name of the header file to generate. +# +# NOTE: This logic will eventually be unstreamed into CMake. +function(_swift_generate_cxx_header target header) + if(NOT TARGET ${target}) + message(FATAL_ERROR "Target ${target} not defined.") + endif() + + if(NOT DEFINED CMAKE_Swift_COMPILER) + message(WARNING "Swift not enabled in project. Cannot generate headers for Swift files.") + return() + endif() + + cmake_parse_arguments(ARG "" "" "SEARCH_PATHS;MODULE_NAME;CXX_STD_VERSION" ${ARGN}) + + if(NOT ARG_MODULE_NAME) + set(target_module_name $) + set(ARG_MODULE_NAME $,${target_module_name},${target}>) + endif() + + if(ARG_SEARCH_PATHS) + list(TRANSFORM ARG_SEARCH_PATHS PREPEND "-I") + endif() + + if(APPLE) + set(SDK_FLAGS "-sdk" "${CMAKE_OSX_SYSROOT}") + elseif(WIN32) + set(SDK_FLAGS "-sdk" "$ENV{SDKROOT}") + elseif(DEFINED ${CMAKE_SYSROOT}) + set(SDK_FLAGS "-sdk" "${CMAKE_SYSROOT}") + endif() + + cmake_path(APPEND CMAKE_CURRENT_BINARY_DIR include + OUTPUT_VARIABLE base_path) + + cmake_path(APPEND base_path ${header} + OUTPUT_VARIABLE header_path) + + if (NOT ARG_CXX_STD_VERSION) + set(ARG_CXX_STD_VERSION "14") + get_target_property(CxxFeatures ${target} COMPILE_FEATURES) + list(FILTER CxxFeatures INCLUDE REGEX "cxx_std_[0-9]+$") + if (NOT "${CxxFeatures}" STREQUAL "") + string(SUBSTRING ${CxxFeatures} 8 2 ARG_CXX_STD_VERSION) + endif() + endif() + + set(CXX_STD_FLAGS -Xcc -std=c++${ARG_CXX_STD_VERSION}) + + set(_AllSources $) + set(_SwiftSources $) + add_custom_command(OUTPUT ${header_path} + DEPENDS ${_SwiftSources} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND + ${CMAKE_Swift_COMPILER} -frontend -typecheck + ${ARG_SEARCH_PATHS} + ${_SwiftSources} + ${SDK_FLAGS} + ${CXX_STD_FLAGS} + -module-name "${ARG_MODULE_NAME}" + -cxx-interoperability-mode=default + -emit-clang-header-path ${header_path} + COMMENT + "Generating '${header_path}'" + COMMAND_EXPAND_LISTS) + + # Added to public interface for dependees to find. + target_include_directories(${target} PUBLIC $) + # Added to the target to ensure target rebuilds if header changes and is used + # by sources in the target. + target_sources(${target} PRIVATE ${header_path}) +endfunction() diff --git a/Meta/CMake/common_compile_options.cmake b/Meta/CMake/common_compile_options.cmake index 0e824805597..41f52b3dbb2 100644 --- a/Meta/CMake/common_compile_options.cmake +++ b/Meta/CMake/common_compile_options.cmake @@ -21,6 +21,22 @@ macro(add_cxx_link_options) add_link_options($<$:${args}>) endmacro() +macro(add_swift_compile_options) + set(args "") + foreach(arg ${ARGN}) + string(APPEND args ${arg}$) + endforeach() + add_compile_options($<$:${args}>) +endmacro() + +macro(add_swift_link_options) + set(args "") + foreach(arg ${ARGN}) + string(APPEND args ${arg}$) + endforeach() + add_link_options($<$:${args}>) +endmacro() + if (MSVC) add_cxx_compile_options(/W4) # do not warn about unused function diff --git a/Meta/CMake/common_options.cmake b/Meta/CMake/common_options.cmake index 85983fe20cf..f0dfde662f3 100644 --- a/Meta/CMake/common_options.cmake +++ b/Meta/CMake/common_options.cmake @@ -28,3 +28,4 @@ serenity_option(ENABLE_CLANG_PLUGINS OFF CACHE BOOL "Enable building with the Cl serenity_option(ENABLE_CLANG_PLUGINS_INVALID_FUNCTION_MEMBERS OFF CACHE BOOL "Enable detecting invalid function types as members of GC-allocated objects") serenity_option(ENABLE_GUI_TARGETS ON CACHE BOOL "Enable building GUI targets") +serenity_option(ENABLE_SWIFT OFF CACHE BOOL "Enable building Swift files") diff --git a/Userland/Libraries/LibWebView/Application.h b/Userland/Libraries/LibWebView/Application.h index 3ccbabf6650..36a3c05f1a9 100644 --- a/Userland/Libraries/LibWebView/Application.h +++ b/Userland/Libraries/LibWebView/Application.h @@ -10,6 +10,12 @@ #include #include +#ifdef __swift__ +# include +#else +# define SWIFT_IMMORTAL_REFERENCE +#endif + namespace WebView { class Application { @@ -46,6 +52,6 @@ private: Core::EventLoop m_event_loop; ProcessManager m_process_manager; bool m_in_shutdown { false }; -}; +} SWIFT_IMMORTAL_REFERENCE; }