Skip to content

Commit 6735b15

Browse files
Merge f92e518 into 530da45
2 parents 530da45 + f92e518 commit 6735b15

File tree

14 files changed

+171
-65
lines changed

14 files changed

+171
-65
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ jobs:
5454
- uses: julia-actions/julia-runtest@v1
5555
with:
5656
prefix: ${{ matrix.prefix }}
57+
env:
58+
JULIA_NUM_THREADS: 2
5759
- uses: julia-actions/[email protected]
5860
continue-on-error: true
5961
- uses: julia-actions/[email protected]

example/gl-area.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,6 @@ showall(win)
4444

4545
# https://stackoverflow.com/a/33571506/1500988
4646
signal_connect(win, :destroy) do widget
47-
Gtk.gtk_quit()
47+
Gtk.gtk_main_quit()
4848
end
4949
Gtk.gtk_main()

src/GLib/GLib.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ cfunction_(@nospecialize(f), r, a::Tuple) = cfunction_(f, r, Tuple{a...})
4141
end
4242
end
4343

44-
const gtk_eventloop_f = Ref{Function}()
45-
4644
# local function, handles Symbol and makes UTF8-strings easier
4745
const AbstractStringLike = Union{AbstractString, Symbol}
4846
bytestring(s) = String(s)
@@ -53,6 +51,8 @@ bytestring(s::Ptr{UInt8}) = unsafe_string(s)
5351
g_malloc(s::Integer) = ccall((:g_malloc, libglib), Ptr{Nothing}, (Csize_t,), s)
5452
g_free(p::Ptr) = ccall((:g_free, libglib), Nothing, (Ptr{Nothing},), p)
5553

54+
main_depth() = ccall((:g_main_depth, libglib), Cint, ())
55+
5656
ccall((:g_type_init, libgobject), Nothing, ())
5757

5858
include("MutableTypes.jl")

src/GLib/signals.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,6 @@ end
381381
@deprecate g_timeout_add(interval, cb, user_data) g_timeout_add(() -> cb(user_data), interval)
382382

383383
function g_idle_add(cb::Function)
384-
gtk_eventloop_f[](true)
385384
callback = @cfunction(_g_callback, Cint, (Ref{Function},))
386385
ref, deref = gc_ref_closure(cb)
387386
return ccall((:g_idle_add_full , libglib),Cint,

src/Gtk.jl

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -159,50 +159,62 @@ function __init__()
159159
C_NULL, C_NULL, "Julia Gtk Bindings", C_NULL, C_NULL, error_check)
160160
end
161161

162-
# if g_main_depth > 0, a glib main-loop is already running.
163-
# unfortunately this call does not reliably reflect the state after the
164-
# loop has been stopped or restarted, so only use it once at the start
165-
gtk_main_running[] = ccall((:g_main_depth, GLib.libglib), Cint, ()) > 0
166-
167-
# Given GLib provides `g_idle_add` to specify what happens during idle, this allows
168-
# that call to also start the eventloop
169-
GLib.gtk_eventloop_f[] = enable_eventloop
170-
171162
auto_idle[] = get(ENV, "GTK_AUTO_IDLE", "true") == "true"
172-
173-
# by default, defer starting the event loop until either `show`, `showall`, or `g_idle_add` is called
174-
enable_eventloop(!auto_idle[])
163+
# by default, defer starting the event loop until widgets are "realized"
164+
if auto_idle[]
165+
# Start-stopping the event loop once makes the auto-stop process
166+
# more stable. Reason unknown
167+
enable_eventloop(true)
168+
enable_eventloop(false)
169+
else
170+
enable_eventloop(true)
171+
end
175172
end
176173

177174
const auto_idle = Ref{Bool}(true) # control default via ENV["GTK_AUTO_IDLE"]
178-
const gtk_main_running = Ref{Bool}(false)
179-
const quit_task = Ref{Task}()
180175
const enable_eventloop_lock = Base.ReentrantLock()
176+
const eventloop_instructed_to_stop = Ref{Bool}(false)
177+
181178
"""
182179
Gtk.enable_eventloop(b::Bool = true)
183180
184181
Set whether Gtk's event loop is running.
185182
"""
186-
function enable_eventloop(b::Bool = true; wait_stopped::Bool = false)
183+
function enable_eventloop(b::Bool = true; wait = true)
187184
lock(enable_eventloop_lock) do # handle widgets that are being shown/destroyed from different threads
188-
isassigned(quit_task) && wait(quit_task[]) # prevents starting while the async is still stopping
189185
if b
190186
if !is_eventloop_running()
187+
eventloop_instructed_to_stop[] = false
191188
global gtk_main_task = schedule(Task(gtk_main))
192-
gtk_main_running[] = true
189+
if !is_eventloop_running() && wait
190+
t = Timer(5) # TODO: replace with Base.timedwait when 1.3 is dropped
191+
while isopen(t) && !is_eventloop_running()
192+
sleep(0.1)
193+
end
194+
isopen(t) || @debug "enable_eventloop: timed-out waiting for eventloop to start"
195+
end
193196
end
194197
else
195198
if is_eventloop_running()
196-
# @async and short sleep is needer on MacOS at least, otherwise
197-
# the window doesn't always finish closing before the eventloop stops.
198-
quit_task[] = @async begin
199-
sleep(0.2)
200-
gtk_quit()
201-
gtk_main_running[] = false
199+
recursive_quit_main()
200+
eventloop_instructed_to_stop[] = true
201+
if is_eventloop_running() && wait
202+
t = Timer(5) # TODO: replace with Base.timedwait when 1.3 is dropped
203+
while isopen(t) && is_eventloop_running()
204+
sleep(0.1)
205+
end
206+
isopen(t) || @debug "enable_eventloop: timed-out waiting for eventloop to stop"
202207
end
203-
wait_stopped && wait(quit_task[])
204208
end
205209
end
210+
return is_eventloop_running()
211+
end
212+
end
213+
214+
function recursive_quit_main()
215+
gtk_main_quit()
216+
if GLib.main_depth() > 1
217+
@idle_add recursive_quit_main()
206218
end
207219
end
208220

@@ -214,8 +226,8 @@ pausing. Respects whether Gtk.jl is configured to allow auto-stopping of the
214226
eventloop, unless `force = true`.
215227
"""
216228
function pause_eventloop(f; force = false)
217-
was_running = is_eventloop_running()
218-
(force || auto_idle[]) && enable_eventloop(false, wait_stopped = true)
229+
was_running = eventloop_instructed_to_stop[] ? false : is_eventloop_running()
230+
(force || auto_idle[]) && enable_eventloop(false)
219231
try
220232
f()
221233
finally
@@ -228,7 +240,8 @@ end
228240
229241
Check whether Gtk's event loop is running.
230242
"""
231-
is_eventloop_running() = gtk_main_running[]
243+
is_eventloop_running() = GLib.main_depth() > 0
244+
232245

233246
const ser_version = Serialization.ser_version
234247
let cachedir = joinpath(splitdir(@__FILE__)[1], "..", "gen")

src/base.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ const shown_widgets = WeakKeyDict()
3232
function handle_auto_idle(w::GtkWidget)
3333
if auto_idle[]
3434
signal_connect(w, :realize) do w
35-
enable_eventloop(true)
35+
enable_eventloop(true, wait = false) # can't wait in a callback, unfortunately
3636
shown_widgets[w] = nothing
37-
signal_connect(w, :destroy, #= after =# true) do w
37+
signal_connect(w, :destroy, #= after =# false) do w
3838
delete!(shown_widgets, w)
39-
isempty(shown_widgets) && enable_eventloop(false)
39+
isempty(shown_widgets) && enable_eventloop(false, wait = false)
4040
end
4141
end
42-
@static Sys.iswindows() && yield() # issue #610
42+
yield() # issue #610
4343
end
4444
end
4545
function show(w::GtkWidget)

src/events.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ gtk_main() = GLib.g_sigatom() do
22
ccall((:gtk_main, libgtk), Nothing, ())
33
end
44

5-
function gtk_quit()
5+
function gtk_main_quit()
66
ccall((:gtk_main_quit, libgtk), Nothing, ())
77
end
88

9+
const gtk_quit = gtk_main_quit # deprecated
10+
911
add_events(widget::GtkWidget, mask::Integer) = ccall((:gtk_widget_add_events, libgtk), Nothing, (Ptr{GObject}, GEnum), widget, mask)
1012

1113
# widget[:event] = function(ptr, obj)

test/eventloop.jl

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
@testset "eventloop" begin
2+
@testset "control" begin
3+
before = Gtk.auto_idle[]
4+
5+
@testset "basics" begin
6+
Gtk.auto_idle[] = true
7+
Gtk.enable_eventloop(false)
8+
@test !Gtk.is_eventloop_running()
9+
Gtk.enable_eventloop(true)
10+
@test Gtk.is_eventloop_running()
11+
Gtk.enable_eventloop(false)
12+
@test !Gtk.is_eventloop_running()
13+
end
14+
15+
@testset "pause_eventloop" begin
16+
17+
@testset "pauses then restarts" begin
18+
Gtk.enable_eventloop(true)
19+
@test Gtk.is_eventloop_running()
20+
Gtk.pause_eventloop() do
21+
@test !Gtk.is_eventloop_running()
22+
end
23+
@test Gtk.is_eventloop_running()
24+
end
25+
26+
@testset "doesn't restart a stopping eventloop" begin
27+
Gtk.enable_eventloop(false)
28+
c = GtkCanvas()
29+
win = GtkWindow(c)
30+
showall(win)
31+
sleep(1)
32+
@test Gtk.is_eventloop_running()
33+
destroy(win)
34+
# the eventloop is likely still stopping here
35+
Gtk.pause_eventloop() do
36+
@test !Gtk.is_eventloop_running()
37+
end
38+
@test !Gtk.is_eventloop_running()
39+
end
40+
41+
@testset "observes auto_idle = false" begin
42+
Gtk.auto_idle[] = false
43+
Gtk.enable_eventloop(true)
44+
Gtk.pause_eventloop() do
45+
@test Gtk.is_eventloop_running()
46+
end
47+
@test Gtk.is_eventloop_running()
48+
end
49+
50+
@testset "observes force = true" begin
51+
Gtk.auto_idle[] = false
52+
Gtk.enable_eventloop(true)
53+
Gtk.pause_eventloop(force = true) do
54+
@test !Gtk.is_eventloop_running()
55+
end
56+
@test Gtk.is_eventloop_running()
57+
end
58+
59+
# Note: Test disabled because this isn't true. The event loop takes some time to stop.
60+
# TODO: Figure out how to wait in the handle_auto_idle callbacks
61+
62+
# @testset "eventloop is stopped immediately after a destroy(win) completes" begin
63+
# c = GtkCanvas()
64+
# win = GtkWindow(c)
65+
# showall(win)
66+
# @test Gtk.is_eventloop_running()
67+
# destroy(win)
68+
# @test !Gtk.is_eventloop_running()
69+
# end
70+
end
71+
72+
Gtk.auto_idle[] = before
73+
end
74+
75+
@testset "Multithreading" begin
76+
@testset "no blocking when eventloop is paused" begin
77+
Gtk.auto_idle[] = true
78+
Threads.nthreads() < 1 && @warn "Threads.nthreads() == 1. Multithread blocking tests are not effective"
79+
80+
function multifoo()
81+
Threads.@threads for _ in 1:Threads.nthreads()
82+
sleep(0.1)
83+
end
84+
end
85+
86+
Gtk.enable_eventloop(false)
87+
win = Gtk.Window("Multithread test", 400, 300)
88+
showall(win)
89+
@test Gtk.is_eventloop_running()
90+
for i in 1:10
91+
Gtk.pause_eventloop() do
92+
@test !Gtk.is_eventloop_running()
93+
t = @elapsed multifoo() # should take slightly more than 0.1 seconds
94+
@test t < 4.5 # given the Glib uv_prepare timeout is 5000 ms
95+
end
96+
end
97+
@test Gtk.is_eventloop_running()
98+
destroy(win)
99+
end
100+
end
101+
end

test/glib.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ repr = Base.print_to_string(wrap) #should display properties
2424

2525
x = Ref{Int}(1)
2626

27+
Gtk.enable_eventloop(true)
28+
2729
function g_timeout_add_cb()
2830
x[] = 2
2931
false
@@ -82,6 +84,8 @@ g_timeout_add(()->g_timeout_add_cb(x), 1)
8284
sleep(0.5)
8385
@test x[] == 2
8486

87+
Gtk.enable_eventloop(false)
88+
8589
end
8690

8791
# TODO

test/glist.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ using Test
77

88
@testset "pointers" begin
99

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

1515
@test eltype(l)==Gtk.GtkWidget
1616

@@ -22,6 +22,9 @@ for item in l
2222
@test item==nb
2323
end
2424

25+
destroy(w1)
26+
destroy(w2)
27+
2528
end
2629

2730
@testset "string" begin

0 commit comments

Comments
 (0)