Open nathan-b opened 1 year ago
Expected behavior: throw an exception, not segfault.
Extra credit: add an example in the documentation explaining how to do what I'm trying to do, but the correct way (or tell me personally and I'll update the docs).
So, the issue is the application is returning from the show_win
method in the middle of a GUI application runtime, which interrupts it and breaks it. It is not expected GUI behavior (as per the MVC pattern), and should never really be needed. Any application that does that could be re-written in a way to work correctly without it (for example, by triggering a model method from the listener to do some work as per the proper GUI MVC pattern). If the code was to avoid returning from the method, the application would work just fine, like in this code, which simply puts
in the on_clicked
listener instead of outside the show_win
method (though it could call a method on a model if there is a need to do something more sophisticated):
#!/usr/bin/ruby
require 'glimmer-dsl-libui'
include Glimmer
def show_win
window('foo') {
button('Boom') {
on_clicked {
puts "boom"
}
}
}.show
end
show_win
show_win
Returning from a method is not a proper way for exiting a GUI application. The proper way is for the user to click the x button in the window, or for the application to destroy the window and then quit LibUI
's event loop like in the following code:
#!/usr/bin/ruby
require 'glimmer-dsl-libui'
include Glimmer
def show_win
window('foo') { |w|
button('Boom') {
on_clicked {
puts "boom"
w.destroy
::LibUI.quit
}
}
}.show
end
show_win
show_win
That should accomplish what you want as every time you click the Boom
button, it destroys the window and quits LibUI's event loop, thus allowing the next invocation of show_win
to relaunch a new window from scratch.
This behavior is partially documented under: https://github.com/AndyObtiva/glimmer-dsl-libui#smart-defaults-and-conventions
I just added extra documentation about it under Common Control Operations: https://github.com/AndyObtiva/glimmer-dsl-libui/tree/master#common-control-operations
### Common Control Operations
- `destroy` (note that for closing a `window`, in addition to calling `somewindow.destroy`, you also have to call `::LibUI.quit`)
...
If you feel there is a need to provide further clarification (like for a use-case I am not aware of), or even an example as you suggested, please feel free to ask or just share a Pull Request!
Please confirm to me that the examples above resolve your issue in order for me to close this issue (or feel free to ask other questions if needed).
Interesting!
Apparently, if you really insist on the method return approach, this code makes it work (on the Mac at least, where I ran it):
#!/usr/bin/ruby
require 'glimmer-dsl-libui'
include Glimmer
def show_win
window('foo') { |w|
button('Boom') {
on_clicked {
w.destroy
::LibUI.quit
return "boom"
}
}
}.show
return "done"
end
puts show_win
puts show_win
I don't recommend it, but if you really want to return from the method (instead of calling a model method as per MVC), the code above works.
I am of the belief that there are always exceptions to the rule, and it is good to bend rules to be pragmatic every once in a while (staying mindful of the pros/cons vs other approaches). So, perhaps in a quick and dirty Ruby scripting situation, this method return approach might be helpful. But, I'd still think twice before applying it.
One more thing. When running the original application, I don't get a seg fault on the Mac. I get an unexpected return (LocalJumpError)
error:
tmp/test_open_window_multiple_times0.rb:13:in `block (3 levels) in show_win': unexpected return (LocalJumpError)
from /Users/andymaleh/code/glimmer-dsl-libui/lib/glimmer/libui/control_proxy.rb:171:in `block in handle_listener'
from /Users/andymaleh/code/glimmer-dsl-libui/lib/glimmer/libui/control_proxy.rb:175:in `block (2 levels) in handle_listener'
from /Users/andymaleh/code/glimmer-dsl-libui/lib/glimmer/libui/control_proxy.rb:175:in `map'
from /Users/andymaleh/code/glimmer-dsl-libui/lib/glimmer/libui/control_proxy.rb:175:in `block in handle_listener'
from /Users/andymaleh/.rvm/rubies/ruby-3.1.0/lib/ruby/3.1.0/fiddle/closure.rb:45:in `call'
from /Users/andymaleh/.rvm/gems/ruby-3.1.0@glimmer-dsl-libui/gems/libui-0.1.2.pre-arm64-darwin/lib/libui/ffi.rb:20:in `call'
from /Users/andymaleh/.rvm/gems/ruby-3.1.0@glimmer-dsl-libui/gems/libui-0.1.2.pre-arm64-darwin/lib/libui/ffi.rb:20:in `uiMain'
from /Users/andymaleh/.rvm/gems/ruby-3.1.0@glimmer-dsl-libui/gems/libui-0.1.2.pre-arm64-darwin/lib/libui/libui_base.rb:46:in `public_send'
from /Users/andymaleh/.rvm/gems/ruby-3.1.0@glimmer-dsl-libui/gems/libui-0.1.2.pre-arm64-darwin/lib/libui/libui_base.rb:46:in `block (2 levels) in <module:LibUIBase>'
from /Users/andymaleh/code/glimmer-dsl-libui/lib/glimmer/libui/control_proxy/window_proxy.rb:69:in `show'
from tmp/test_open_window_multiple_times0.rb:16:in `show_win'
from tmp/test_open_window_multiple_times0.rb:21:in `<main>'
That is totally correct as it is mentioning that returning is unexpected behavior.
If you are still encountering this somehow, it might be an issue on a specific environment. I know that Linux does not support launching a window multiple times within the same Ruby session unfortunately. It is a known problem observed when using girb
(Glimmer IRB) on Linux, which requires exiting and restarting on every window launch (reported here: https://github.com/AndyObtiva/glimmer-dsl-libui/issues/45).
Please confirm what environment you are on (Linux, Windows, or Mac) if the code snippets above do not resolve your issue.
OK, I just tested your application in Linux and confirmed that in Linux, unlike on the Mac, I don't get an unexpected return (LocalJumpError)
exception, yet a segfault. Additionally, as mentioned in my last comment, the Linux implementation of the underlying C libui library does not currently support launching a window multiple times in the same Ruby session.
I am reporting this issue to the upstream projects (libui C library and bindings). I will report back here once it is fixed.
Thanks so much for the information and for reporting the issue upstream!
The problem I'm (clumsily) trying to solve is that I need a dialog box asking for input from the user. I need this at several points in the application, so I wrote a function that creates said dialog box. Once the user presses "OK" or "Cancel" I want the function to return the user input (or nil
if the user pressed Cancel). Hopefully this explains why I'm trying to do such a seemingly silly thing :)
I actually tried something similar to your approach (calling destroy
on the window and quit
on LibUI), but I guess at that point I ran afoul of the upstream bug you mentioned. I'll watch this issue and try again once it gets fixed and merged!
You're welcome.
I reported your issue at the underlying C libui project: https://github.com/libui-ng/libui-ng/issues/205
And, the libui binding project: https://github.com/kojix2/LibUI/issues/45
In the meantime, here is a workaround.
You can write multiple Ruby applications that call each other conditionally based on your specific needs' logic. That way, instead of attempting to relaunch a window in the same Ruby application, you simply call another Ruby application to launch a window in a separate Ruby session (using system
, ticks, or IO.popen
).
I actually used this approach (using IO.popen
) in building the Glimmer Meta-Example, which enables launching many Glimmer DSL for LibUI windows at the same time side by side from the same Ruby application session:
You can go ahead and verify that for yourself by launching it via these instructions (ruby -r './lib/glimmer-dsl-libui' examples/meta_example.rb
if you have the glimmer-dsl-libui project cloned locally):
https://github.com/AndyObtiva/glimmer-dsl-libui#examples
I hope this will address your needs until the reported issue is fixed.
Cheers!
Probably I'm just doing something stupid and wrong, but the documentation didn't tell me how to do what I was trying to do (write a function that displays a window whenever its called). I figured I'd give it a go anyway, and it works brilliantly the first time it's called, but the second time the world falls apart.
Minimal reproduction:
I assume
return "boom"
is the problem here; probably the UI loop needs to clean something up. I tried callingwindow.destroy
beforereturn
, but that did not help.