Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

Commit

Permalink
Merge f92e518 into 530da45
Browse files Browse the repository at this point in the history
  • Loading branch information
IanButterworth authored Jan 19, 2022
2 parents 530da45 + f92e518 commit 6735b15
Show file tree
Hide file tree
Showing 14 changed files with 171 additions and 65 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ jobs:
- uses: julia-actions/julia-runtest@v1
with:
prefix: ${{ matrix.prefix }}
env:
JULIA_NUM_THREADS: 2
- uses: julia-actions/[email protected]
continue-on-error: true
- uses: julia-actions/[email protected]
Expand Down
2 changes: 1 addition & 1 deletion example/gl-area.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ showall(win)

# https://stackoverflow.com/a/33571506/1500988
signal_connect(win, :destroy) do widget
Gtk.gtk_quit()
Gtk.gtk_main_quit()
end
Gtk.gtk_main()
4 changes: 2 additions & 2 deletions src/GLib/GLib.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ cfunction_(@nospecialize(f), r, a::Tuple) = cfunction_(f, r, Tuple{a...})
end
end

const gtk_eventloop_f = Ref{Function}()

# local function, handles Symbol and makes UTF8-strings easier
const AbstractStringLike = Union{AbstractString, Symbol}
bytestring(s) = String(s)
Expand All @@ -53,6 +51,8 @@ bytestring(s::Ptr{UInt8}) = unsafe_string(s)
g_malloc(s::Integer) = ccall((:g_malloc, libglib), Ptr{Nothing}, (Csize_t,), s)
g_free(p::Ptr) = ccall((:g_free, libglib), Nothing, (Ptr{Nothing},), p)

main_depth() = ccall((:g_main_depth, libglib), Cint, ())

ccall((:g_type_init, libgobject), Nothing, ())

include("MutableTypes.jl")
Expand Down
1 change: 0 additions & 1 deletion src/GLib/signals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,6 @@ end
@deprecate g_timeout_add(interval, cb, user_data) g_timeout_add(() -> cb(user_data), interval)

function g_idle_add(cb::Function)
gtk_eventloop_f[](true)
callback = @cfunction(_g_callback, Cint, (Ref{Function},))
ref, deref = gc_ref_closure(cb)
return ccall((:g_idle_add_full , libglib),Cint,
Expand Down
67 changes: 40 additions & 27 deletions src/Gtk.jl
Original file line number Diff line number Diff line change
Expand Up @@ -159,50 +159,62 @@ function __init__()
C_NULL, C_NULL, "Julia Gtk Bindings", C_NULL, C_NULL, error_check)
end

# if g_main_depth > 0, a glib main-loop is already running.
# unfortunately this call does not reliably reflect the state after the
# loop has been stopped or restarted, so only use it once at the start
gtk_main_running[] = ccall((:g_main_depth, GLib.libglib), Cint, ()) > 0

# Given GLib provides `g_idle_add` to specify what happens during idle, this allows
# that call to also start the eventloop
GLib.gtk_eventloop_f[] = enable_eventloop

auto_idle[] = get(ENV, "GTK_AUTO_IDLE", "true") == "true"

# by default, defer starting the event loop until either `show`, `showall`, or `g_idle_add` is called
enable_eventloop(!auto_idle[])
# by default, defer starting the event loop until widgets are "realized"
if auto_idle[]
# Start-stopping the event loop once makes the auto-stop process
# more stable. Reason unknown
enable_eventloop(true)
enable_eventloop(false)
else
enable_eventloop(true)
end
end

const auto_idle = Ref{Bool}(true) # control default via ENV["GTK_AUTO_IDLE"]
const gtk_main_running = Ref{Bool}(false)
const quit_task = Ref{Task}()
const enable_eventloop_lock = Base.ReentrantLock()
const eventloop_instructed_to_stop = Ref{Bool}(false)

"""
Gtk.enable_eventloop(b::Bool = true)
Set whether Gtk's event loop is running.
"""
function enable_eventloop(b::Bool = true; wait_stopped::Bool = false)
function enable_eventloop(b::Bool = true; wait = true)
lock(enable_eventloop_lock) do # handle widgets that are being shown/destroyed from different threads
isassigned(quit_task) && wait(quit_task[]) # prevents starting while the async is still stopping
if b
if !is_eventloop_running()
eventloop_instructed_to_stop[] = false
global gtk_main_task = schedule(Task(gtk_main))
gtk_main_running[] = true
if !is_eventloop_running() && wait
t = Timer(5) # TODO: replace with Base.timedwait when 1.3 is dropped
while isopen(t) && !is_eventloop_running()
sleep(0.1)
end
isopen(t) || @debug "enable_eventloop: timed-out waiting for eventloop to start"
end
end
else
if is_eventloop_running()
# @async and short sleep is needer on MacOS at least, otherwise
# the window doesn't always finish closing before the eventloop stops.
quit_task[] = @async begin
sleep(0.2)
gtk_quit()
gtk_main_running[] = false
recursive_quit_main()
eventloop_instructed_to_stop[] = true
if is_eventloop_running() && wait
t = Timer(5) # TODO: replace with Base.timedwait when 1.3 is dropped
while isopen(t) && is_eventloop_running()
sleep(0.1)
end
isopen(t) || @debug "enable_eventloop: timed-out waiting for eventloop to stop"
end
wait_stopped && wait(quit_task[])
end
end
return is_eventloop_running()
end
end

function recursive_quit_main()
gtk_main_quit()
if GLib.main_depth() > 1
@idle_add recursive_quit_main()
end
end

Expand All @@ -214,8 +226,8 @@ pausing. Respects whether Gtk.jl is configured to allow auto-stopping of the
eventloop, unless `force = true`.
"""
function pause_eventloop(f; force = false)
was_running = is_eventloop_running()
(force || auto_idle[]) && enable_eventloop(false, wait_stopped = true)
was_running = eventloop_instructed_to_stop[] ? false : is_eventloop_running()
(force || auto_idle[]) && enable_eventloop(false)
try
f()
finally
Expand All @@ -228,7 +240,8 @@ end
Check whether Gtk's event loop is running.
"""
is_eventloop_running() = gtk_main_running[]
is_eventloop_running() = GLib.main_depth() > 0


const ser_version = Serialization.ser_version
let cachedir = joinpath(splitdir(@__FILE__)[1], "..", "gen")
Expand Down
8 changes: 4 additions & 4 deletions src/base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ const shown_widgets = WeakKeyDict()
function handle_auto_idle(w::GtkWidget)
if auto_idle[]
signal_connect(w, :realize) do w
enable_eventloop(true)
enable_eventloop(true, wait = false) # can't wait in a callback, unfortunately
shown_widgets[w] = nothing
signal_connect(w, :destroy, #= after =# true) do w
signal_connect(w, :destroy, #= after =# false) do w
delete!(shown_widgets, w)
isempty(shown_widgets) && enable_eventloop(false)
isempty(shown_widgets) && enable_eventloop(false, wait = false)
end
end
@static Sys.iswindows() && yield() # issue #610
yield() # issue #610
end
end
function show(w::GtkWidget)
Expand Down
4 changes: 3 additions & 1 deletion src/events.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ gtk_main() = GLib.g_sigatom() do
ccall((:gtk_main, libgtk), Nothing, ())
end

function gtk_quit()
function gtk_main_quit()
ccall((:gtk_main_quit, libgtk), Nothing, ())
end

const gtk_quit = gtk_main_quit # deprecated

add_events(widget::GtkWidget, mask::Integer) = ccall((:gtk_widget_add_events, libgtk), Nothing, (Ptr{GObject}, GEnum), widget, mask)

# widget[:event] = function(ptr, obj)
Expand Down
101 changes: 101 additions & 0 deletions test/eventloop.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
@testset "eventloop" begin
@testset "control" begin
before = Gtk.auto_idle[]

@testset "basics" begin
Gtk.auto_idle[] = true
Gtk.enable_eventloop(false)
@test !Gtk.is_eventloop_running()
Gtk.enable_eventloop(true)
@test Gtk.is_eventloop_running()
Gtk.enable_eventloop(false)
@test !Gtk.is_eventloop_running()
end

@testset "pause_eventloop" begin

@testset "pauses then restarts" begin
Gtk.enable_eventloop(true)
@test Gtk.is_eventloop_running()
Gtk.pause_eventloop() do
@test !Gtk.is_eventloop_running()
end
@test Gtk.is_eventloop_running()
end

@testset "doesn't restart a stopping eventloop" begin
Gtk.enable_eventloop(false)
c = GtkCanvas()
win = GtkWindow(c)
showall(win)
sleep(1)
@test Gtk.is_eventloop_running()
destroy(win)
# the eventloop is likely still stopping here
Gtk.pause_eventloop() do
@test !Gtk.is_eventloop_running()
end
@test !Gtk.is_eventloop_running()
end

@testset "observes auto_idle = false" begin
Gtk.auto_idle[] = false
Gtk.enable_eventloop(true)
Gtk.pause_eventloop() do
@test Gtk.is_eventloop_running()
end
@test Gtk.is_eventloop_running()
end

@testset "observes force = true" begin
Gtk.auto_idle[] = false
Gtk.enable_eventloop(true)
Gtk.pause_eventloop(force = true) do
@test !Gtk.is_eventloop_running()
end
@test Gtk.is_eventloop_running()
end

# Note: Test disabled because this isn't true. The event loop takes some time to stop.
# TODO: Figure out how to wait in the handle_auto_idle callbacks

# @testset "eventloop is stopped immediately after a destroy(win) completes" begin
# c = GtkCanvas()
# win = GtkWindow(c)
# showall(win)
# @test Gtk.is_eventloop_running()
# destroy(win)
# @test !Gtk.is_eventloop_running()
# end
end

Gtk.auto_idle[] = before
end

@testset "Multithreading" begin
@testset "no blocking when eventloop is paused" begin
Gtk.auto_idle[] = true
Threads.nthreads() < 1 && @warn "Threads.nthreads() == 1. Multithread blocking tests are not effective"

function multifoo()
Threads.@threads for _ in 1:Threads.nthreads()
sleep(0.1)
end
end

Gtk.enable_eventloop(false)
win = Gtk.Window("Multithread test", 400, 300)
showall(win)
@test Gtk.is_eventloop_running()
for i in 1:10
Gtk.pause_eventloop() do
@test !Gtk.is_eventloop_running()
t = @elapsed multifoo() # should take slightly more than 0.1 seconds
@test t < 4.5 # given the Glib uv_prepare timeout is 5000 ms
end
end
@test Gtk.is_eventloop_running()
destroy(win)
end
end
end
4 changes: 4 additions & 0 deletions test/glib.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ repr = Base.print_to_string(wrap) #should display properties

x = Ref{Int}(1)

Gtk.enable_eventloop(true)

function g_timeout_add_cb()
x[] = 2
false
Expand Down Expand Up @@ -82,6 +84,8 @@ g_timeout_add(()->g_timeout_add_cb(x), 1)
sleep(0.5)
@test x[] == 2

Gtk.enable_eventloop(false)

end

# TODO
Expand Down
9 changes: 6 additions & 3 deletions test/glist.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ using Test

@testset "pointers" begin

w = Window("Window", 400, 300)
w1 = Window("Window", 400, 300)
nb = Notebook()
w = push!(Window("Notebook"),nb)
l = ccall((:gtk_container_get_children,Gtk.libgtk),Ptr{Gtk._GList{Gtk.GtkWidget}},(Ptr{Gtk.GObject},),w)
w2 = push!(Window("Notebook"),nb)
l = ccall((:gtk_container_get_children,Gtk.libgtk),Ptr{Gtk._GList{Gtk.GtkWidget}},(Ptr{Gtk.GObject},),w2)

@test eltype(l)==Gtk.GtkWidget

Expand All @@ -22,6 +22,9 @@ for item in l
@test item==nb
end

destroy(w1)
destroy(w2)

end

@testset "string" begin
Expand Down
1 change: 1 addition & 0 deletions test/gui.jl
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ end
@test mtrx.xx == 300
@test mtrx.yy == 280
@test mtrx.xy == mtrx.yx == mtrx.x0 == mtrx.y0 == 0
destroy(win)
end

@testset "Menus" begin
Expand Down
28 changes: 3 additions & 25 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,8 @@ destroy(win)

@test isa(Gtk.GdkEventKey(), Gtk.GdkEventKey)

@testset "Eventloop control" begin
before = Gtk.auto_idle[]

Gtk.enable_eventloop(true)
@test Gtk.is_eventloop_running()

Gtk.auto_idle[] = true
Gtk.pause_eventloop() do
@test !Gtk.is_eventloop_running()
end
@test Gtk.is_eventloop_running()

Gtk.auto_idle[] = false
Gtk.pause_eventloop() do
@test Gtk.is_eventloop_running()
end
@test Gtk.is_eventloop_running()

Gtk.pause_eventloop(force = true) do
@test !Gtk.is_eventloop_running()
end
@test Gtk.is_eventloop_running()

Gtk.auto_idle[] = before
end
# make sure all shown widgets have been destroyed, otherwise the eventloop
# won't stop automatically
@test length(Gtk.shown_widgets) == 0

end
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ include("gui.jl")
include("list.jl")
include("misc.jl")
include("text.jl")
include("eventloop.jl")

end
4 changes: 3 additions & 1 deletion test/tree.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,6 @@ select!(selection, iter)
@test length(selection) == 1

# this crashes
# iters = Gtk.selected_rows(selection)
# iters = Gtk.selected_rows(selection)

destroy(window)

0 comments on commit 6735b15

Please sign in to comment.