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

add option for "simple" main loop iteration #628

Closed
wants to merge 10 commits into from
7 changes: 6 additions & 1 deletion src/GLib/signals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,11 @@ macro idle_add(ex)
end
end

const simple_loop = Ref{Bool}(false)

const exiting = Ref(false)
function __init__()
simple_loop[] = get(ENV, "GTK_SIMPLE_LOOP", "true") == "true"
if isdefined(GLib, :__init__bindeps__)
GLib.__init__bindeps__()
end
Expand All @@ -410,6 +413,8 @@ function __init__()
exiting[] = false
atexit(() -> (exiting[] = true))
__init__gtype__()
__init__gmainloop__()
if !simple_loop[]
__init__gmainloop__()
end
nothing
end
74 changes: 64 additions & 10 deletions src/Gtk.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ include("gio.jl")
include("application.jl")

function __init__()
Sys.iswindows() && (ENV["GTK_CSD"] = 0)
# Set XDG_DATA_DIRS so that Gtk can find its icons and schemas
ENV["XDG_DATA_DIRS"] = join(filter(x -> x !== nothing, [
dirname(adwaita_icons_dir),
Expand Down Expand Up @@ -168,12 +169,46 @@ function __init__()
# that call to also start the eventloop
GLib.gtk_eventloop_f[] = enable_eventloop

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

# by default, defer starting the event loop until either `show`, `showall`, or `g_idle_add` is called
enable_eventloop(!auto_idle[])
end

"""
Gtk.iteration(may_block)

Run a single interation of the Gtk event loop. If `may_block` is true, this
function will wait until events are ready to be processed. Otherwise, it will
return immediately if no events need to be processed. Returns `true` if events
were processed.
"""
function iteration(may_block::Bool)
while events_pending()
ccall((:g_main_context_iteration, libglib), Cint, (Ptr{Cvoid}, Cint), C_NULL, may_block)
end
end

const pause_loop = Ref{Bool}(false)

iterate(timer) = pause_loop[] || iteration(false)

"""
Gtk.events_pending()

Check whether events need processing by the Gtk's event loop. This function can
be used in conjuction with `iterate` to refresh the GUI during long operations
or in cases where widgets must be realized before proceeding.
"""
events_pending() = ccall((:gtk_events_pending, libgtk), Cint, ()) != 0

const mainloop_timer = Ref{Timer}()

function glib_main_simple()
mainloop_timer[]=Timer(iterate,0.01;interval=0.005)
wait(mainloop_timer[])
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}()
Expand All @@ -184,6 +219,16 @@ const enable_eventloop_lock = Base.ReentrantLock()
Set whether Gtk's event loop is running.
"""
function enable_eventloop(b::Bool = true; wait_stopped::Bool = false)
if GLib.simple_loop[]
if b
auto_idle[] = false
global glib_main_task = schedule(Task(glib_main_simple))
return
else
close(mainloop_timer[])
return
end
end
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
Expand All @@ -210,16 +255,25 @@ end
Gtk.pause_eventloop(f; force = false)

Pauses the eventloop around a function. Restores the state of the eventloop after
pausing. Respects whether Gtk.jl is configured to allow auto-stopping of the
eventloop, unless `force = true`.
pausing. If GLib.simple_loop[] is disabled, 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)
try
f()
finally
(force || auto_idle[]) && enable_eventloop(was_running)
if GLib.simple_loop[]
pause_loop[] = true
try
f()
finally
pause_loop[] = false
end
else
was_running = is_eventloop_running()
(force || auto_idle[]) && enable_eventloop(false, wait_stopped = true)
try
f()
finally
(force || auto_idle[]) && enable_eventloop(was_running)
end
end
end

Expand All @@ -228,7 +282,7 @@ end

Check whether Gtk's event loop is running.
"""
is_eventloop_running() = gtk_main_running[]
is_eventloop_running() = GLib.simple_loop[] ? !pause_loop[] : gtk_main_running[]

const ser_version = Serialization.ser_version
let cachedir = joinpath(splitdir(@__FILE__)[1], "..", "gen")
Expand Down
2 changes: 1 addition & 1 deletion src/base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ visible(w::GtkWidget, state::Bool) = @sigatom ccall((:gtk_widget_set_visible, li

const shown_widgets = WeakKeyDict()
function handle_auto_idle(w::GtkWidget)
if auto_idle[]
if auto_idle[] && !GLib.simple_loop[]
signal_connect(w, :realize) do w
enable_eventloop(true)
shown_widgets[w] = nothing
Expand Down
3 changes: 3 additions & 0 deletions src/cairo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ function canvas_on_expose_event(::Ptr{GObject}, e::Ptr{Nothing}, widget::GtkCanv
end

function getgc(c::GtkCanvas)
while GLib.simple_loop[] && events_pending() # next step requires a realized canvas
iteration(true)
end
if !isdefined(c,:backcc)
error("GtkCanvas not yet initialized.")
end
Expand Down
16 changes: 16 additions & 0 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ destroy(win)

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

if !Gtk.GLib.simple_loop[]

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

Expand All @@ -64,4 +66,18 @@ destroy(win)
Gtk.auto_idle[] = before
end

else

@testset "Eventloop control" 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

end

end