Skip to content

Commit

Permalink
Support multiple windows. (#15)
Browse files Browse the repository at this point in the history
* Support multiple windows.

* Bump version. Fix for Linux.

* Refactor.

* Fix typo.

* Fix bind/unbind on Windows.

* Fix for Windows.

* Add a note in `run`'s docs.
  • Loading branch information
sunoru authored Jul 10, 2023
1 parent b62d0dd commit b28fb63
Show file tree
Hide file tree
Showing 19 changed files with 155 additions and 71 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,8 @@ jobs:
fail-fast: false
matrix:
julia-arch: [x64]
julia-version: ["1.8", "~1.9.0-0", "nightly"]
julia-version: ["1.8", "1.9", "nightly"]
os: [ubuntu-latest, windows-latest, macOS-latest]
exclude:
- os: ubuntu-latest
julia-version: "1.8"
include:
- os: ubuntu-latest
prefix: xvfb-run
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Webviews"
uuid = "b7e775b7-052a-4229-9250-fe08ae90f9c6"
authors = ["Sunoru <[email protected]>"]
version = "1.1.1"
version = "1.2.0"

[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
Expand All @@ -18,7 +18,7 @@ WebIO = "0f1e0344-ec1d-5b48-a673-e5cf874b6c29"
[compat]
Downloads = "1.6"
FunctionWrappers = "1.1"
JSON3 = "1.12"
JSON3 = "1.13"
Reexport = "1.2"
WebIO = "0.8"
julia = "1.8"
2 changes: 1 addition & 1 deletion examples/bind.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ bind(webview, "resize") do _
end
bind(println, webview, "log")
bind(webview, "terminate") do _
terminate(webview)
terminate()
end

run(webview)
2 changes: 1 addition & 1 deletion examples/multiple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ navigate!(webview1, "https://julialang.org/")
webview2 = Webview()
navigate!(webview2, "https://google.com/")

# This will show both windows.
run(webview1)
run(webview2)
14 changes: 9 additions & 5 deletions src/API.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ export terminate,
using Base64: Base64
using JSON3: JSON3

using ..Webviews: Webviews
using ..Consts
using ..Consts: WebviewStatus, WEBVIEW_PENDING, WEBVIEW_RUNNING, WEBVIEW_DESTORYED
using ..Utils
abstract type AbstractWebview end
abstract type AbstractPlatformImpl end

Expand All @@ -35,13 +37,11 @@ macro forward(expr)
end

"""
terminate(w::Webview)
terminate()
Stops the main loop. It is safe to call this function from another other background thread.
**Note:** This function is not working on macOS.
"""
@forward terminate(w)
terminate() = Webviews.PlatformImpl.terminate()

"""
close(w::Webview)
Expand All @@ -59,12 +59,14 @@ Destroys the webview and closes the window along with freeing all internal resou
"""
function destroy(w::AbstractWebview)
w.status WEBVIEW_DESTORYED && return
window = window_handle(w.platform)
for key in keys(w.callback_handler.callbacks)
unbind(w, key)
end
is_shown(w.platform) && terminate(w)
is_shown(w.platform) && close(w)
destroy(w.platform)
w.status = WEBVIEW_DESTORYED
Webviews.on_window_destroy(window)
nothing
end
destroy(::AbstractPlatformImpl) = nothing
Expand All @@ -74,6 +76,8 @@ destroy(::AbstractPlatformImpl) = nothing
Runs the webview event loop. Runs the main event loop until it's terminated.
After this function exits, the webview is automatically destroyed.
**Note**: This function will show all webview windows that were created.
"""
function Base.run(w::AbstractWebview)
w.status WEBVIEW_DESTORYED && error("Webview is already destroyed")
Expand Down
6 changes: 2 additions & 4 deletions src/Utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ module Utils
using FunctionWrappers: FunctionWrapper
using JSON3: JSON3

using ..API

const MessageCallback = FunctionWrapper{Nothing,Tuple{Int,Vector{Any}}}

# JuliaLang/julia #269
Expand Down Expand Up @@ -37,7 +35,7 @@ function on_message(ch::CallbackHandler, s::Ptr{Cchar})
nothing
end

function API.bind_raw(f::Function, ch::CallbackHandler, name::AbstractString)
function bind_raw(f::Function, ch::CallbackHandler, name::AbstractString)
haskey(ch.callbacks, name) && return
ch.callbacks[name] = MessageCallback() do seq, args
f(seq, args)
Expand All @@ -46,7 +44,7 @@ function API.bind_raw(f::Function, ch::CallbackHandler, name::AbstractString)
nothing
end

function API.unbind(ch::CallbackHandler, name::AbstractString)
function unbind(ch::CallbackHandler, name::AbstractString)
delete!(ch.callbacks, name)
nothing
end
Expand Down
41 changes: 37 additions & 4 deletions src/Webviews.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ using JSON3: JSON3
export Webview

include("./Consts.jl")
include("./API.jl")
include("./Utils.jl")
include("./API.jl")

# Platform-specific implementations
@static if Sys.isapple()
Expand All @@ -32,7 +32,15 @@ using .Consts: WebviewStatus, WEBVIEW_PENDING, WEBVIEW_RUNNING, WEBVIEW_DESTORYE
@reexport using .API

"""
Webview(size=(1024, 768); title="", debug=false, size_fixed=false, unsafe_window_handle=C_NULL)
Webview(
size=(1024, 768);
title="",
debug=false,
size_fixed=false,
unsafe_window_handle=C_NULL,
enable_webio=true,
auto_terminate=true
)
Webview(width, height; kwargs...)
Create a new webview instance with `size` and `title`.
Expand All @@ -42,11 +50,14 @@ Create a new webview instance with `size` and `title`.
If it's non-null - then child WebView is embedded into the given parent window.
Otherwise a new window is created. Depending on the platform,
a `GtkWindow`, `NSWindow` or `HWND` pointer can be passed here.
- If `enable_webio` is true, then WebIO will be enabled.
- The process will be terminated when all webviews with `auto_terminate=true` are destroyed.
"""
mutable struct Webview <: API.AbstractWebview
const platform::PlatformImpl.Webview
const callback_handler::Utils.CallbackHandler
const webio_enabled::Bool
const auto_terminate::Bool
status::Consts.WebviewStatus
function Webview(
size::Tuple{Integer,Integer}=(1024, 768);
Expand All @@ -55,16 +66,23 @@ mutable struct Webview <: API.AbstractWebview
size_fixed::Bool=false,
unsafe_window_handle::Ptr{Cvoid}=C_NULL,
enable_webio::Bool=true,
auto_terminate::Bool=true
)
ch = Utils.CallbackHandler()
platform = PlatformImpl.Webview(ch, debug, unsafe_window_handle)
window_handle(platform) C_NULL && error("Failed to create webview window")
w = new(platform, ch, enable_webio, WEBVIEW_PENDING)
window = window_handle(platform)
window C_NULL && error("Failed to create webview window")
w = new(platform, ch, enable_webio, auto_terminate, WEBVIEW_PENDING)
API.resize!(w, size; hint=size_fixed ? WEBVIEW_HINT_FIXED : WEBVIEW_HINT_NONE)
title!(w, title)
if enable_webio
webio_init!(w)
end
if auto_terminate
lock(ActiveWindowsLock) do
ActiveWindows[window] = w
end
end
finalizer(destroy, w)
end
end
Expand All @@ -85,6 +103,21 @@ function Base.show(io::IO, w::Webview)
)
end

# Window => Webview
const ActiveWindows = Dict{Ptr{Cvoid}, Webview}()
const ActiveWindowsLock = ReentrantLock()

function on_window_destroy(w::Ptr{Cvoid})
lock(ActiveWindowsLock) do
haskey(ActiveWindows, w) || return
webview = pop!(ActiveWindows, w)
destroy(webview)
if isempty(ActiveWindows)
terminate()
end
end
end

function __init__()
if PlatformImpl.check_dependency()
PlatformImpl.setup_platform()
Expand Down
22 changes: 5 additions & 17 deletions src/platforms/apple/Impl.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
module AppleImpl
# TODO

include("../common.jl")
include("../common_bind.jl")
Expand Down Expand Up @@ -32,6 +31,9 @@ function setup_platform()
C_NULL,
true::Bool
)
delegate = create_app_delegate()
app = get_shared_application()
@msg_send Cvoid app a"setDelegate:"sel delegate
prepare_timeout()
nothing
end
Expand Down Expand Up @@ -59,27 +61,13 @@ function Webview(
debug,
callback_handler
)
this_ptr = pointer_from_objref(w)
app = get_shared_application()
delegate = create_app_delegate(w)
@ccall objc_setAssociatedObject(
delegate::ID,
ASSOCIATED_KEY::Cstring,
this_ptr::ID,
0::UInt # OBJC_ASSOCIATION_ASSIGN
)::ID
@msg_send Cvoid app a"setDelegate:"sel delegate
if unsafe_window_handle C_NULL
@msg_send Cvoid app a"run"sel
else
on_application_did_finish_launching(w, delegate, app)
end
on_application_did_finish_launching(w, app)
w
end

API.window_handle(w::Webview) = w.window
# TODO: support multiple windows.
API.terminate(::Webview) =
terminate() =
let app = get_shared_application()
# Stop the main event loop instead of terminating the process.
@msg_send Cvoid app a"stop:"sel C_NULL
Expand Down
75 changes: 54 additions & 21 deletions src/platforms/apple/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function get_associated_webview(self::ID)
unsafe_pointer_to_objref(Ptr{Webview}(w))
end

function create_app_delegate(w::Webview)
function create_app_delegate()
cls = @ccall objc_allocateClassPair(a"NSResponder"cls::ID, "WebviewAppDelegate"::Cstring, 0::Int)::ID
@ccall class_addProtocol(
cls::ID,
Expand All @@ -19,34 +19,56 @@ function create_app_delegate(w::Webview)
a"applicationShouldTerminateAfterLastWindowClosed:"sel::SEL,
@cfunction(
(self, _2, _3) -> begin
w = get_associated_webview(self)
terminate(w)
_terminate()
false
end,
Bool, (ID, SEL, ID)
)::Ptr{Cvoid},
"c@:@"::Cstring
)::Bool
if w.parent_window C_NULL
@ccall class_addMethod(
cls::ID,
a"applicationDidFinishLaunching:"sel::SEL,
@cfunction(
(self, _, notification) -> begin
app = @msg_send ID notification a"object"sel
w = get_associated_webview(self)
on_application_did_finish_launching(w, self, app)
end,
Cvoid, (ID, SEL, ID)
)::Ptr{Cvoid},
"v@:@"::Cstring
)::Bool
end
@ccall objc_registerClassPair(cls::ID)::Ptr{Cvoid}
@msg_send ID cls a"new"sel
end

function create_webkit_ui_delegate()
function get_window_delegate_class()
cls = @ccall objc_getClass("WebviewWindowDelegate"::Cstring)::ID
cls == C_NULL || return cls
cls = @ccall objc_allocateClassPair(a"NSResponder"cls::ID, "WebviewWindowDelegate"::Cstring, 0::Int)::ID
@ccall class_addProtocol(
cls::ID,
(@ccall objc_getProtocol("NSWindowDelegate"::Cstring)::ID)::ID
)::Bool
@ccall class_addMethod(
cls::ID,
a"windowWillClose:"sel::SEL,
@cfunction(
(self, _2, _3) -> begin
w = get_associated_webview(self)
window = window_handle(w)
Webviews.on_window_close(window)
end,
Cvoid, (ID, SEL, ID)
)::Ptr{Cvoid},
"v@:@"::Cstring
)::Bool
@ccall objc_registerClassPair(cls::ID)::Cvoid
cls
end
function create_window_delegate(w::Webview)
cls = get_window_delegate_class()
instance = @msg_send ID cls a"new"sel
@ccall objc_setAssociatedObject(
instance::ID,
ASSOCIATED_KEY::Cstring,
pointer_from_objref(w)::ID,
0::UInt # OBJC_ASSOCIATION_ASSIGN
)::ID
instance
end

function get_webkit_ui_delegate_class()
cls = @ccall objc_getClass("WebkitUIDelegate"::Cstring)::ID
cls == C_NULL || return cls
cls = @ccall objc_allocateClassPair(a"NSObject"cls::ID, "WebkitUIDelegate"::Cstring, 0::Int)::ID
@ccall class_addProtocol(
cls::ID,
Expand Down Expand Up @@ -83,10 +105,16 @@ function create_webkit_ui_delegate()
"v@:@@@@"::Cstring
)::Bool
@ccall objc_registerClassPair(cls::ID)::Cvoid
cls
end
function create_webkit_ui_delegate()
cls = get_webkit_ui_delegate_class()
@msg_send ID cls a"new"sel
end

function create_script_message_handler(w::Webview)
function get_script_message_handler_class()
cls = @ccall objc_getClass("WebkitScriptMessageHandler"::Cstring)::ID
cls == C_NULL || return cls
cls = @ccall objc_allocateClassPair(a"NSResponder"cls::ID, "WebkitScriptMessageHandler"::Cstring, 0::Int)::ID
@ccall class_addProtocol(
cls::ID,
Expand All @@ -110,6 +138,10 @@ function create_script_message_handler(w::Webview)
"v@:@@"::Cstring
)::Bool
@ccall objc_registerClassPair(cls::ID)::Cvoid
cls
end
function create_script_message_handler(w::Webview)
cls = get_script_message_handler_class()
instance = @msg_send ID cls a"new"sel
@ccall objc_setAssociatedObject(
instance::ID,
Expand All @@ -129,7 +161,7 @@ function is_app_bundled()
bundled = @msg_send Bool bundle_path a"hasSuffix:"sel a".app"str
end

function on_application_did_finish_launching(w::Webview, _self::ID, app::ID)
function on_application_did_finish_launching(w::Webview, app::ID)
if w.parent_window C_NULL
@msg_send Cvoid app a"stop:"sel C_NULL
end
Expand All @@ -152,6 +184,7 @@ function on_application_did_finish_launching(w::Webview, _self::ID, app::ID)
else
w.parent_window
end
create_window_delegate(w)

# Webview
config = w.config = @msg_send ID a"WKWebViewConfiguration"cls a"new"sel
Expand Down
Loading

2 comments on commit b28fb63

@sunoru
Copy link
Owner Author

@sunoru sunoru commented on b28fb63 Jul 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/87145

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.2.0 -m "<description of version>" b28fb63300ac68a8f1a098e53237f1af46f99133
git push origin v1.2.0

Please sign in to comment.