Gtk4.jl
Gtk4.jl copied to clipboard
gtk_print_operation_run Print Dialog is empty under Julia 1.9.3 on OSX
gtk_print_operation_run with PrintOperationAction_PRINT_DIALOG results in print dialog without any printers and without Print to File option. Corresponding event handlers for on_begin_print and on_draw_page are never invoked. The system is OSX Montery. Julia is 1.9.3. I have verified that corresponding operations in the Gtk3 space and the Gtk4 space from C++ behave as expected on the same system. As the API parameters are the same across both the C++ code and the ccall in Julia I am supposing there may be an issue in the libgtk4 build of the Gtk4 Julia package.
Below is a basic program to illustrate the problem:
using Gtk4 win = GtkWindow("Basic Printer Operation", 400, 200) b = GtkButton("Click Me") push!(win,b) function on_begin_print(o,c) # operation, context println("begin print called ") # Use print compositor to set number of pages... end function on_draw_page(o,c,n,d) # operation, context, pagenum,data println("on draw page ") # Use compositor to draw page n using operation o and context c end function on_button_clicked(w) printer_operation = Gtk4.G_.PrintOperation_new() Gtk4.G_.set_allow_async(printer_operation,true) # both async and sync exhibit the error. signal_connect(on_draw_page,printer_operation,"draw-page") signal_connect(on_begin_print,printer_operation,"begin-print") Gtk4.G_.run(printer_operation,Gtk4.PrintOperationAction_PRINT_DIALOG,win) end signal_connect(on_button_clicked, b, "clicked") show(win)
This yields an empty Print dialog. The General Tab is empty. No Printer. No Print to File option.
On the same system, below is a basic C++ program which works.
#include#include static void begin_print (GtkPrintOperation *operation, GtkPrintContext *context) { // g_assert (editor->buffer); // /* Create a print compositor from the buffer */ // editor->print_compositor = gtk_source_print_compositor_new (editor->buffer); // /* Set some formatting options for pages */ // gtk_source_print_compositor_set_print_header (editor->print_compositor, TRUE); // gtk_source_print_compositor_set_print_footer (editor->print_compositor, TRUE); // gtk_source_print_compositor_set_header_format (editor->print_compositor, TRUE, NULL, "%N/%Q", NULL); // gtk_source_print_compositor_set_footer_format (editor->print_compositor, TRUE, NULL, PACKAGE_URL, NULL); // gtk_source_print_compositor_set_left_margin (editor->print_compositor, 15.0, GTK_UNIT_MM); // gtk_source_print_compositor_set_right_margin (editor->print_compositor, 15.0, GTK_UNIT_MM); // gtk_source_print_compositor_set_bottom_margin (editor->print_compositor, 15.0, GTK_UNIT_MM); // /* Pagination */ // while (!gtk_source_print_compositor_paginate (editor->print_compositor, context)); // gtk_print_operation_set_n_pages (operation, gtk_source_print_compositor_get_n_pages (editor->print_compositor)); } static void draw_page (GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr) { // gtk_source_print_compositor_draw_page (editor->print_compositor, context, page_nr); } static void activate(GtkApplication *app, gpointer user_data) { GtkWidget *window; GtkPrintOperationResult res = GTK_PRINT_OPERATION_RESULT_ERROR; GError *error = NULL; window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "GTK Window : Hello World!"); gtk_window_set_default_size(GTK_WINDOW(window), 200, 200); gtk_widget_show(window); GtkPrintOperation *print_operation; print_operation = gtk_print_operation_new (); g_signal_connect (print_operation, "draw_page", G_CALLBACK (draw_page), NULL); g_signal_connect (print_operation, "begin_print", G_CALLBACK (begin_print), NULL); res = gtk_print_operation_run(print_operation, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, GTK_WINDOW(window), &error); } int main(int argc, char **argv) { GtkApplication *app; int status; app = gtk_application_new("org.gtk.example", G_APPLICATION_FLAGS_NONE); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); status = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app); return status; }
Running this with
g++ gtkcpp.cpp -o main `pkg-config --cflags --libs gtk4` && ./main
and linking to libgtk4 from Homebrew works and results in a functional Print dialog.
Julia non working Print Dialog:
C++ Working Dialog
Thanks for posting the example code -- I had not tried the print dialog before. It also doesn't work on Linux. This is probably an issue with the JLL, but maybe there's something we can do here...
Thank you @jwahlstrand. I tried reaching out on Linked-In as well. Anything I can try, please let me know. And yes, I agree it's probably in the JLL. Would it help to post what dynamic libraries are are pulled in by both binaries? I can do a dump in Activity Monitor to show the DDLs/SOs/DYLIBs. Presumably they will be different in that the Julia version will pull in from the artifacts directories.
I am happy to work with you on putting together a sample app for printing. I'm writing an editor using gtk source view, so that is what I'm looking to print...
The print backend modules for GTK get installed in "libdir/gtk-4.0/4.0.0/printbackends". On Fedora, I see 2 backends installed in /usr/lib64/gtk-4.0/4.0.0/printbackends: one for "print to file" and one for CUPS. On Linux the GTK4_jll artifact directory contains just the one for "print to file".
As you also saw, "print to file" isn't offered as an option in the print dialog on Linux. So I think the first issue is, how can we make that appear?
Maybe one of the environmental variables here can point GTK to that directory? https://docs.gtk.org/gtk4/running.html
~~I have not had any success with that yet.~~ It works! I will submit a PR.
As far as CUPS (and whatever it is on OSX, also CUPS?) is concerned, it looks like it isn't in Yggdrasil, which is a prerequisite for enabling it in GTK4_jll. There was an attempt fairly recently, but it stalled due to an issue building one of its dependencies: https://github.com/JuliaPackaging/Yggdrasil/pull/5004
I am happy to work with you on putting together a sample app for printing. I'm writing an editor using gtk source view, so that is what I'm looking to print...
I recently started trying to use GtkTextView
and realized that it was left in a half-broken state. I have been working on a breaking PR to fix it that I will submit very soon, and will then update GtkSourceView.jl once it's merged.
Yes so I started putting together my own CCALL wrappers to steer around that perhaps. I'm using GtkSourceWidget.jl, but in order to get that to fly - in particular your type constraints, I had to wrap a few functions.
# This is the original undo(instance::GtkTextBuffer...) function from gtk4_methods with the GtkTextBuffer type constraint removed in the instance paramater function undo_gtksourcebuffer(instance) ret = ccall(("gtk_text_buffer_undo", Gtk4.libgtk4), Nothing, (Ptr{Gtk4.GObject},), instance) nothing end # This is the original delete(instance::GtkTextBuffer...) function from gtk4_methods with the GtkTextBuffer type constraint removed in the instance paramater function delete_gtksourcebuffer(instance, _start::Union{GtkTextIter, Ref{_GtkTextIter}}, _end::Union{GtkTextIter, Ref{_GtkTextIter}}) @async ret = ccall(("gtk_text_buffer_delete", Gtk4.libgtk4), Nothing, (Ptr{Gtk4.GObject}, Ptr{_GtkTextIter}, Ptr{_GtkTextIter}), instance, _start, _end) nothing end # This is the original run(GtkPrintOperation...) function from gtk4_methods with the GtkWindow type constraint removed in the GtkWindow parameter function run_gtk_print_operation(instance::GtkPrintOperation, _action, _parent) @async begin _parent_maybe = Gtk4.nothing_to_null(_parent) err = Gtk4.err_buf() ret = ccall(("gtk_print_operation_run", Gtk4.libgtk4), UInt32, (Ptr{Gtk4.GObject}, UInt32, Ptr{Gtk4.GObject}, Ptr{Ptr{Gtk4.GError}}), instance, _action, _parent_maybe, err) Gtk4.check_err(err) ret2 = PrintOperationResult(ret) ret2 end end
I don't know if the above helps, but with these few, I managed to the the GtkSourceView to work, albeit only by pulling it into my project directly - which is clearly a hack.
It also meant bypassing quite a bit of the GtkSourceView functionality and reaching through to the underlying text buffers directly. If you have something I could test, I'd be happy to be the Guinea Pig.
I think the reason the type removals on the CCALLs was needed was because of namespace crossovers. Otherwise the type hierarchy should have sorted out the subtypes as valid ISA's. Just a guess, but I could not get things to work without "Python'izing" the code which as a traditional C++ programmer really galls me :)
The fix of GtkTextView is in #62 . The issue I was talking about is having to pass in _GtkTextIter
to a lot of methods..
What error are you seeing with Gtk4's undo
and delete
and GtkSourceBuffer
? I think that should work. And the parent
argument for the other method really should be a GtkWindow
or nothing
, so it makes no sense to me why removing the type helps. Is there a name collision involving Maybe
?
It is very possible that we will want to make a lot of changes to GtkSourceWidget. It is brand new and hasn't been tested much at all -- you are definitely the guinea pig. But I have been happy with how painlessly the GI-generated methods work in most cases and I expected GtkSourceWidget to work without too many ccall's.
No the undo wasn't working. It wasn't error'ing but it did nothing. Here is how I ended up getting it to work on the GtkSourceView
# This is the original undo(instance::GtkTextBuffer...) function from gtk4_methods with the GtkTextBuffer type constraint removed in the instance paramater function undo_gtksourcebuffer(instance) ret = ccall(("gtk_text_buffer_undo", Gtk4.libgtk4), Nothing, (Ptr{Gtk4.GObject},), instance) nothing end function on_edit_undo(b) @async begin println("print undo ") undo_gtksourcebuffer(input_textbuffer) end end signal_connect(on_edit_undo,edit_undo_button,"clicked")
input_textbuffer is instantiated like so
input_textbuffer = GtkSourceWidget.GtkSourceBuffer(language)
Delete wasn't my major trouble. edit-cut was.
I only managed to puzzle that together from the copy handler, then doing the delete via the iterators...
function on_input_cut_clipboard(b) println("cut button ") selbounds = Gtk4.G_.get_selection_bounds(analyticumtechnica.input_textbuffer) iter_start = Ref{Gtk4._GtkTextIter}(selbounds[2]) iter_end = Ref{Gtk4._GtkTextIter}(selbounds[3]) if iter_end > iter_start on_input_copy_clipboard(b) delete_gtksourcebuffer(analyticumtechnica.input_textbuffer,iter_start,iter_end) end end signal_connect(on_input_cut_clipboard,edit_cut_button,"clicked")
OK, it sounds like text editing is just broken. Once #62 is done and merged I will look at GtkSourceWidget again and try to produce a working example.
Regarding your original issue, "print to file" is fixable (#61 works on Linux and I'll check what it does to the other platforms today before merging it), but real printing will require a lot of effort and I will not personally have time for it in the near future.
I have a quick question there... looking at your code: why is this eval needed?
eval(include("gen/gdk4_methods"))
Now, I'm a Lisp programmer so the different evaluation times make sense to me, but I thought an include would effect this already. Seems to get compiled anyway. I guess what I'm saying is I never put the eval in front of my includes...
Also, when doing a "using Gtk4," that does not seem to pull in Gtk4_jll automatically. How is that? What confuses me further is that while src/Gtk4.jl performs a "using GTK4_jll," when I try that in my project from the REPL, it wants to install GTK4_jll.
julia> using Gtk4 julia> using GTK4_jll │ Package GTK4_jll not found, but a package named GTK4_jll is available from a registry. │ Install package? │ pkg> add GTK4_jll └ (y/n/o) [y]:
How will this have ever worked if GTK4_jll is needed by the Gtk4 module, but it isn't installed?
Thank you for your updates. As for printing not working without major re-work, might it be better then to call out to C++ land via jluna? Can the signal handlers cross from C++ to Julia? I'm struggling with the notion of A) Making the application skeleton C++ and the data science bits Julia via jluna or B) Making the application Julia only. When I found the Gtk4 wrappers I was inclined to go with B so I'm scoping out whether all the peripherals will work. I don't mind resorting to C++ for an isolated area - like printing.
I can confirm that this code will make print-to-file available.
using GTK4_jll ENV["GTK_PATH"] = joinpath(dirname(GTK4_jll.libgtk4_path::String),"gtk-4.0") using Gtk4 win = GtkWindow("Basic Printer Operation", 400, 200) b = GtkButton("Click Me") push!(win,b) function on_begin_print(o,c) # operation, context println("begin print called ") # Use print compositor to set number of pages... end function on_draw_page(o,c,n,d) # operation, context, pagenum,data println("on draw page ") # Use compositor to draw page n using operation o and context c end function on_button_clicked(w) printer_operation = Gtk4.G_.PrintOperation_new() Gtk4.G_.set_allow_async(printer_operation,true) # both async and sync exhibit the error. signal_connect(on_draw_page,printer_operation,"draw-page") signal_connect(on_begin_print,printer_operation,"begin-print") Gtk4.G_.run(printer_operation,Gtk4.PrintOperationAction_PRINT_DIALOG,win) end signal_connect(on_button_clicked, b, "clicked") show(win)
This is the original example with the following prepended:
using GTK4_jll ENV["GTK_PATH"] = joinpath(dirname(GTK4_jll.libgtk4_path::String),"gtk-4.0")
@jwahlstrand What is the complication with making regular printers available? I presume this leaves the Julia space in that both C++ and Julia simply call the C code, no? There should not be any additional development, yes?
I am noticing one difference between the gtk3 artifacts ( which by the way gedit on GTK3 installed via Homebrew works fine ) and the gtk4 artifacts:
./e6ff6a9e66b9724d435a000cd359cdef38d3876b/lib/gtk-4.0/4.0.0/printbackends ./e6ff6a9e66b9724d435a000cd359cdef38d3876b/lib/gtk-4.0/4.0.0/printbackends/libprintbackend-file.so ./5c891c10775421b72c44801565308c0140560ec0/lib/gtk-3.0/3.0.0/printbackends ./5c891c10775421b72c44801565308c0140560ec0/lib/gtk-3.0/3.0.0/printbackends/libprintbackend-file.dylib ./5c891c10775421b72c44801565308c0140560ec0/lib/gtk-3.0/3.0.0/printbackends/libprintbackend-lpr.dylib
Gtk4 features an .so file which is ordinarily Linux format. Importantly, file printing is the only compiled backend.
If we compare that to what Homebrew has installed and which is referenced by my C++ app:
./Cellar/gtk4/4.12.5/lib/gtk-4.0/4.0.0/printbackends/libprintbackend-file.so ./Cellar/gtk4/4.12.5/lib/gtk-4.0/4.0.0/printbackends/giomodule.cache ./Cellar/gtk4/4.12.5/lib/gtk-4.0/4.0.0/printbackends/libprintbackend-cups.so
That gives us the libprintbackend-cups.so file. Of course merely copying the Homebrew compiled .so over to the Julia artifact folder causes all manner of symbol conflicts...
objc[7999]: Class GNotificationCenterDelegate is implemented in both /Users/chris/.julia_x86_64/artifacts/65a74410c75b9704dc242d01ae2839af1e84c7ff/lib/libgio-2.0.0.dylib (0x157195e00) and /usr/local/Cellar/glib/2.78.4/lib/libgio-2.0.0.dylib (0x15e1436b0). One of the two will be used. Which one is undefined. objc[7999]: Class ResultReceiver is implemented in both /Users/chris/.julia_x86_64/artifacts/e6ff6a9e66b9724d435a000cd359cdef38d3876b/lib/libgtk-4.1.dylib (0x15c946c28) and /usr/local/Cellar/gtk4/4.12.5/lib/libgtk-4.1.dylib (0x1611e22b0). One of the two will be used. Which one is undefined. objc[7999]: Class GtkApplicationQuartzDelegate is implemented in both /Users/chris/.julia_x86_64/artifacts/e6ff6a9e66b9724d435a000cd359cdef38d3876b/lib/libgtk-4.1.dylib (0x15c946c50) and /usr/local/Cellar/gtk4/4.12.5/lib/libgtk-4.1.dylib (0x1611e22d8). One of the two will be used. Which one is undefined. objc[7999]: Class GNSMenuItem is implemented in both /Users/chris/.julia_x86_64/artifacts/e6ff6a9e66b9724d435a000cd359cdef38d3876b/lib/libgtk-4.1.dylib (0x15c946ca0) and /usr/local/Cellar/gtk4/4.12.5/lib/libgtk-4.1.dylib (0x1611e2328). One of the two will be used. Which one is undefined.
I have a quick question there... looking at your code: why is this eval needed?
eval(include("gen/gdk4_methods"))
Now, I'm a Lisp programmer so the different evaluation times make sense to me, but I thought an include would effect this already. Seems to get compiled anyway.
What's in the file is a bunch of Julia expressions and that is a way to have Julia evaluate them. I think I copied that pattern from Gtk.jl but maybe it's from somewhere else. I think eval
is necessary in this case because the file contains quote
. I'm sure it's possible to strip those out but I just went with eval
. I don't think it makes much difference.
Also, when doing a "using Gtk4," that does not seem to pull in Gtk4_jll automatically. How is that? What confuses me further is that while src/Gtk4.jl performs a "using GTK4_jll," when I try that in my project from the REPL, it wants to install GTK4_jll.
julia> using Gtk4
julia> using GTK4_jll │ Package GTK4_jll not found, but a package named GTK4_jll is available from a registry. │ Install package? │ pkg> add GTK4_jll └ (y/n/o) [y]:
How will this have ever worked if GTK4_jll is needed by the Gtk4 module, but it isn't installed?
The Gtk4.jl package has GTK4_jll installed in its environment but your global environment (which the REPL uses by default) doesn't unless you add it:
https://pkgdocs.julialang.org/v1/environments/
@jwahlstrand Ok that makes sense. There is quoted content. Eval forces the evaluation as if at runtime. It's Lisp still :) What did you think of the missing cups library in the Julia artifacts?
As for the REPL - I invoke it with --project in the project folder so I presume that will pull the correct artifacts.
So I repointed
ENV["GTK_PATH"] = "/usr/local/Cellar/gtk4/4.12.5/lib/gtk-4.0"
to the Homebrew installation. Of course that will wreck havoc with GTK4_jll. But it does confirm that it will try at least to do CUPS printing...
(process:8800): GLib-GObject-CRITICAL **: 23:25:53.349: g_type_module_use: assertion 'G_IS_TYPE_MODULE (module)' failed (process:8800): GLib-GObject-CRITICAL **: 23:25:53.349: plugin pointer (0x6000013dea60) for type 'GtkPrintBackendCups' is invalid (process:8800): GLib-GObject-CRITICAL **: 23:25:53.349: plugin pointer (0x6000013dea60) for type 'GtkPrinterCups' is invalid
Which is not surprising because we get a raft of errors from our repointing:
objc[8800]: Class GNotificationCenterDelegate is implemented in both /Users/chris/.julia_x86_64/artifacts/65a74410c75b9704dc242d01ae2839af1e84c7ff/lib/libgio-2.0.0.dylib (0x156d96e00) and /usr/local/Cellar/glib/2.78.4/lib/libgio-2.0.0.dylib (0x1698886b0). One of the two will be used. Which one is undefined. objc[8800]: Class ResultReceiver is implemented in both /Users/chris/.julia_x86_64/artifacts/e6ff6a9e66b9724d435a000cd359cdef38d3876b/lib/libgtk-4.1.dylib (0x15c547c28) and /usr/local/Cellar/gtk4/4.12.5/lib/libgtk-4.1.dylib (0x168db82b0). One of the two will be used. Which one is undefined. objc[8800]: Class GtkApplicationQuartzDelegate is implemented in both /Users/chris/.julia_x86_64/artifacts/e6ff6a9e66b9724d435a000cd359cdef38d3876b/lib/libgtk-4.1.dylib (0x15c547c50) and /usr/local/Cellar/gtk4/4.12.5/lib/libgtk-4.1.dylib (0x168db82d8). One of the two will be used. Which one is undefined. objc[8800]: Class GNSMenuItem is implemented in both /Users/chris/.julia_x86_64/artifacts/e6ff6a9e66b9724d435a000cd359cdef38d3876b/lib/libgtk-4.1.dylib (0x15c547ca0) and /usr/local/Cellar/gtk4/4.12.5/lib/libgtk-4.1.dylib (0x168db8328). One of the two will be used. Which one is undefined.
So how would we get Gtk4.jl to generate the missing cups library in addition to the file printing library?