From 73a8f2671b60ce5503257d35bc7523a61fe5b4ed Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 11 Jan 2022 23:39:05 -0500 Subject: [PATCH 1/7] add idle and isidle for controlling event loop idle state --- src/GLib/GLib.jl | 1 + src/GLib/signals.jl | 6 ++++-- src/Gtk.jl | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/GLib/GLib.jl b/src/GLib/GLib.jl index c9b2ad9f..c74eeda3 100644 --- a/src/GLib/GLib.jl +++ b/src/GLib/GLib.jl @@ -32,6 +32,7 @@ export set_gtk_property!, get_gtk_property export GConnectFlags export @sigatom, cfunction_ +const IDLE = Ref{Bool}(true) cfunction_(@nospecialize(f), r, a::Tuple) = cfunction_(f, r, Tuple{a...}) diff --git a/src/GLib/signals.jl b/src/GLib/signals.jl index 075a7679..3d7f820c 100644 --- a/src/GLib/signals.jl +++ b/src/GLib/signals.jl @@ -288,7 +288,9 @@ function uv_prepare(src::Ptr{Nothing}, timeout::Ptr{Cint}) global expiration, uv_pollfd local tmout_ms::Cint evt = Base.eventloop() - if !_isempty_workqueue() + if IDLE[] + return Int32(1) + elseif !_isempty_workqueue() tmout_ms = 0 elseif !uv_loop_alive(evt) tmout_ms = -1 @@ -316,7 +318,7 @@ end function uv_check(src::Ptr{Nothing}) global expiration ex = expiration::UInt64 - if !_isempty_workqueue() + if !_isempty_workqueue() || IDLE[] return Int32(1) elseif !uv_loop_alive(Base.eventloop()) return Int32(0) diff --git a/src/Gtk.jl b/src/Gtk.jl index 09bf6a2c..1172e3a6 100644 --- a/src/Gtk.jl +++ b/src/Gtk.jl @@ -135,6 +135,24 @@ function __init__() end end +""" + Gtk.idle(b::Bool = true) + +Set whether Gtk's event loop should be idle. +""" +function idle(b::Bool) + GLib.IDLE[] = b +end + +""" + Gtk.isidle()::Bool + +Check whether Gtk's event loop is idle. +""" +function isidle() + GLib.IDLE[] +end + const ser_version = Serialization.ser_version let cachedir = joinpath(splitdir(@__FILE__)[1], "..", "gen") fastgtkcache = joinpath(cachedir, "gtk$(libgtk_version.major)_julia_ser$(ser_version)") From 6a7eab11c1245b1c424f3e8ecc6fc873e28b78cd Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 11 Jan 2022 23:39:42 -0500 Subject: [PATCH 2/7] start idle by default but expose ENV var to change default --- src/Gtk.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Gtk.jl b/src/Gtk.jl index 1172e3a6..691e5bf2 100644 --- a/src/Gtk.jl +++ b/src/Gtk.jl @@ -133,6 +133,8 @@ function __init__() if ccall((:g_main_depth, GLib.libglib), Cint, ()) == 0 global gtk_main_task = schedule(Task(gtk_main)) end + + idle(get(ENV, "GTK_START_IDLE", "true") == "true") end """ From e6b7956983eeca2ac4ce6fc3c6511a950a39c7e2 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 11 Jan 2022 23:40:05 -0500 Subject: [PATCH 3/7] unset idle with any call to `show` or `showall` --- src/base.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base.jl b/src/base.jl index d742f062..217165dd 100644 --- a/src/base.jl +++ b/src/base.jl @@ -27,8 +27,8 @@ screen_size(w::GtkWindowLeaf) = screen_size(Gtk.GAccessor.screen(w)) ### Functions and methods common to all GtkWidget objects visible(w::GtkWidget) = Bool(ccall((:gtk_widget_get_visible, libgtk), Cint, (Ptr{GObject},), w)) visible(w::GtkWidget, state::Bool) = @sigatom ccall((:gtk_widget_set_visible, libgtk), Nothing, (Ptr{GObject}, Cint), w, state) -show(w::GtkWidget) = (@sigatom ccall((:gtk_widget_show, libgtk), Nothing, (Ptr{GObject},), w); w) -showall(w::GtkWidget) = (@sigatom ccall((:gtk_widget_show_all, libgtk), Nothing, (Ptr{GObject},), w); w) +show(w::GtkWidget) = (idle(false); @sigatom ccall((:gtk_widget_show, libgtk), Nothing, (Ptr{GObject},), w); w) +showall(w::GtkWidget) = (idle(false); @sigatom ccall((:gtk_widget_show_all, libgtk), Nothing, (Ptr{GObject},), w); w) hide(w::GtkWidget) = (@sigatom ccall((:gtk_widget_hide , libgtk),Cvoid,(Ptr{GObject},),w); w) grab_focus(w::GtkWidget) = (@sigatom ccall((:gtk_widget_grab_focus , libgtk), Cvoid, (Ptr{GObject},), w); w) From 08e4e620a7f3d3ca91770a5701b7929e3b7184f0 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 12 Jan 2022 01:09:24 -0500 Subject: [PATCH 4/7] automatically return to idle when shown widgets are destroyed --- src/base.jl | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/base.jl b/src/base.jl index 217165dd..a7897255 100644 --- a/src/base.jl +++ b/src/base.jl @@ -27,8 +27,29 @@ screen_size(w::GtkWindowLeaf) = screen_size(Gtk.GAccessor.screen(w)) ### Functions and methods common to all GtkWidget objects visible(w::GtkWidget) = Bool(ccall((:gtk_widget_get_visible, libgtk), Cint, (Ptr{GObject},), w)) visible(w::GtkWidget, state::Bool) = @sigatom ccall((:gtk_widget_set_visible, libgtk), Nothing, (Ptr{GObject}, Cint), w, state) -show(w::GtkWidget) = (idle(false); @sigatom ccall((:gtk_widget_show, libgtk), Nothing, (Ptr{GObject},), w); w) -showall(w::GtkWidget) = (idle(false); @sigatom ccall((:gtk_widget_show_all, libgtk), Nothing, (Ptr{GObject},), w); w) + +const SHOWN_WIDGETS = WeakKeyDict() +function show(w::GtkWidget) + idle(false) + @sigatom ccall((:gtk_widget_show, libgtk), Nothing, (Ptr{GObject},), w) + SHOWN_WIDGETS[w] = nothing + signal_connect(w, :destroy) do w + delete!(SHOWN_WIDGETS, w) + isempty(SHOWN_WIDGETS) && idle(true) + end + w +end +function showall(w::GtkWidget) + idle(false) + @sigatom ccall((:gtk_widget_show_all, libgtk), Nothing, (Ptr{GObject},), w) + SHOWN_WIDGETS[w] = nothing + signal_connect(w, :destroy) do w + delete!(SHOWN_WIDGETS, w) + isempty(SHOWN_WIDGETS) && idle(true) + end + w +end + hide(w::GtkWidget) = (@sigatom ccall((:gtk_widget_hide , libgtk),Cvoid,(Ptr{GObject},),w); w) grab_focus(w::GtkWidget) = (@sigatom ccall((:gtk_widget_grab_focus , libgtk), Cvoid, (Ptr{GObject},), w); w) From 9a6d3bd7be88960c6aae2777f6ea50cfcd1ffa12 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 12 Jan 2022 01:19:47 -0500 Subject: [PATCH 5/7] tidy up --- src/Gtk.jl | 5 ++++- src/base.jl | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Gtk.jl b/src/Gtk.jl index 691e5bf2..eb16da42 100644 --- a/src/Gtk.jl +++ b/src/Gtk.jl @@ -134,9 +134,12 @@ function __init__() global gtk_main_task = schedule(Task(gtk_main)) end - idle(get(ENV, "GTK_START_IDLE", "true") == "true") + AUTO_IDLE[] = get(ENV, "GTK_AUTO_IDLE", "true") == "true" + idle(AUTO_IDLE[]) end +const AUTO_IDLE = Ref{Bool}(true) + """ Gtk.idle(b::Bool = true) diff --git a/src/base.jl b/src/base.jl index a7897255..270097bc 100644 --- a/src/base.jl +++ b/src/base.jl @@ -29,24 +29,24 @@ visible(w::GtkWidget) = Bool(ccall((:gtk_widget_get_visible, libgtk), Cint, (Ptr visible(w::GtkWidget, state::Bool) = @sigatom ccall((:gtk_widget_set_visible, libgtk), Nothing, (Ptr{GObject}, Cint), w, state) const SHOWN_WIDGETS = WeakKeyDict() +function handle_auto_idle(w::GtkWidget) + if AUTO_IDLE[] + idle(false) + SHOWN_WIDGETS[w] = nothing + signal_connect(w, :destroy) do w + delete!(SHOWN_WIDGETS, w) + isempty(SHOWN_WIDGETS) && idle(true) + end + end +end function show(w::GtkWidget) - idle(false) + handle_auto_idle(w) @sigatom ccall((:gtk_widget_show, libgtk), Nothing, (Ptr{GObject},), w) - SHOWN_WIDGETS[w] = nothing - signal_connect(w, :destroy) do w - delete!(SHOWN_WIDGETS, w) - isempty(SHOWN_WIDGETS) && idle(true) - end w end function showall(w::GtkWidget) - idle(false) + handle_auto_idle(w) @sigatom ccall((:gtk_widget_show_all, libgtk), Nothing, (Ptr{GObject},), w) - SHOWN_WIDGETS[w] = nothing - signal_connect(w, :destroy) do w - delete!(SHOWN_WIDGETS, w) - isempty(SHOWN_WIDGETS) && idle(true) - end w end From cb15a816db00880e09e7530dbeef37c18bc04462 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 12 Jan 2022 20:43:17 -0500 Subject: [PATCH 6/7] start/stop the gtk eventloop instead --- src/GLib/GLib.jl | 3 ++- src/GLib/signals.jl | 7 +++---- src/Gtk.jl | 48 +++++++++++++++++++++++++++++---------------- src/base.jl | 12 ++++++------ src/windows.jl | 4 ++-- 5 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/GLib/GLib.jl b/src/GLib/GLib.jl index c74eeda3..b70a9cb3 100644 --- a/src/GLib/GLib.jl +++ b/src/GLib/GLib.jl @@ -32,7 +32,6 @@ export set_gtk_property!, get_gtk_property export GConnectFlags export @sigatom, cfunction_ -const IDLE = Ref{Bool}(true) cfunction_(@nospecialize(f), r, a::Tuple) = cfunction_(f, r, Tuple{a...}) @@ -42,6 +41,8 @@ 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) diff --git a/src/GLib/signals.jl b/src/GLib/signals.jl index 3d7f820c..a917048a 100644 --- a/src/GLib/signals.jl +++ b/src/GLib/signals.jl @@ -288,9 +288,7 @@ function uv_prepare(src::Ptr{Nothing}, timeout::Ptr{Cint}) global expiration, uv_pollfd local tmout_ms::Cint evt = Base.eventloop() - if IDLE[] - return Int32(1) - elseif !_isempty_workqueue() + if !_isempty_workqueue() tmout_ms = 0 elseif !uv_loop_alive(evt) tmout_ms = -1 @@ -318,7 +316,7 @@ end function uv_check(src::Ptr{Nothing}) global expiration ex = expiration::UInt64 - if !_isempty_workqueue() || IDLE[] + if !_isempty_workqueue() return Int32(1) elseif !uv_loop_alive(Base.eventloop()) return Int32(0) @@ -383,6 +381,7 @@ 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, diff --git a/src/Gtk.jl b/src/Gtk.jl index eb16da42..c8ba8958 100644 --- a/src/Gtk.jl +++ b/src/Gtk.jl @@ -128,35 +128,49 @@ 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, - # so we don't need to start a new one - if ccall((:g_main_depth, GLib.libglib), Cint, ()) == 0 - global gtk_main_task = schedule(Task(gtk_main)) - 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" - idle(AUTO_IDLE[]) + 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[]) end -const AUTO_IDLE = Ref{Bool}(true) +const auto_idle = Ref{Bool}(true) # control default via ENV["GTK_AUTO_IDLE"] +const gtk_main_running = Ref{Bool}(false) """ - Gtk.idle(b::Bool = true) + Gtk.enable_eventloop(b::Bool = true) -Set whether Gtk's event loop should be idle. +Set whether Gtk's event loop is running. """ -function idle(b::Bool) - GLib.IDLE[] = b +function enable_eventloop(b::Bool = true) + if b + if !is_eventloop_running() + global gtk_main_task = schedule(Task(gtk_main)) + gtk_main_running[] = true + end + else + if is_eventloop_running() + gtk_quit() + gtk_main_running[] = false + end + end end """ - Gtk.isidle()::Bool + Gtk.is_eventloop_running()::Bool -Check whether Gtk's event loop is idle. +Check whether Gtk's event loop is running. """ -function isidle() - GLib.IDLE[] -end +is_eventloop_running() = gtk_main_running[] const ser_version = Serialization.ser_version let cachedir = joinpath(splitdir(@__FILE__)[1], "..", "gen") diff --git a/src/base.jl b/src/base.jl index 270097bc..da456a19 100644 --- a/src/base.jl +++ b/src/base.jl @@ -28,14 +28,14 @@ screen_size(w::GtkWindowLeaf) = screen_size(Gtk.GAccessor.screen(w)) visible(w::GtkWidget) = Bool(ccall((:gtk_widget_get_visible, libgtk), Cint, (Ptr{GObject},), w)) visible(w::GtkWidget, state::Bool) = @sigatom ccall((:gtk_widget_set_visible, libgtk), Nothing, (Ptr{GObject}, Cint), w, state) -const SHOWN_WIDGETS = WeakKeyDict() +const shown_widgets = WeakKeyDict() function handle_auto_idle(w::GtkWidget) - if AUTO_IDLE[] - idle(false) - SHOWN_WIDGETS[w] = nothing + if auto_idle[] + enable_eventloop(true) + shown_widgets[w] = nothing signal_connect(w, :destroy) do w - delete!(SHOWN_WIDGETS, w) - isempty(SHOWN_WIDGETS) && idle(true) + delete!(shown_widgets, w) + isempty(shown_widgets) && enable_eventloop(false) end end end diff --git a/src/windows.jl b/src/windows.jl index 0defc677..1e8198ae 100644 --- a/src/windows.jl +++ b/src/windows.jl @@ -17,9 +17,9 @@ end resize!(win::GtkWindow, w::Integer, h::Integer) = ccall((:gtk_window_resize, libgtk), Nothing, (Ptr{GObject}, Int32, Int32), win, w, h) -present(win::GtkWindow) = ccall((:gtk_window_present, libgtk), Nothing, (Ptr{GObject},), win) +present(win::GtkWindow) = (handle_auto_idle(win); ccall((:gtk_window_present, libgtk), Nothing, (Ptr{GObject},), win)) -fullscreen(win::GtkWindow) = ccall((:gtk_window_fullscreen, libgtk), Nothing, (Ptr{GObject},), win) +fullscreen(win::GtkWindow) = (handle_auto_idle(win); ccall((:gtk_window_fullscreen, libgtk), Nothing, (Ptr{GObject},), win)) unfullscreen(win::GtkWindow) = ccall((:gtk_window_unfullscreen, libgtk), Nothing, (Ptr{GObject},), win) maximize(win::GtkWindow) = ccall((:gtk_window_maximize, libgtk), Nothing, (Ptr{GObject},), win) From 2e6d48d89f6b67d43a0f9f585867f0f2ae8806b7 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Fri, 14 Jan 2022 10:44:06 -0500 Subject: [PATCH 7/7] only start eventloop when widget is actually going to be drawn --- src/base.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/base.jl b/src/base.jl index da456a19..e5f464ed 100644 --- a/src/base.jl +++ b/src/base.jl @@ -31,11 +31,13 @@ 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[] - enable_eventloop(true) - shown_widgets[w] = nothing - signal_connect(w, :destroy) do w - delete!(shown_widgets, w) - isempty(shown_widgets) && enable_eventloop(false) + signal_connect(w, :realize) do w + enable_eventloop(true) + shown_widgets[w] = nothing + signal_connect(w, :destroy) do w + delete!(shown_widgets, w) + isempty(shown_widgets) && enable_eventloop(false) + end end end end