StefanSalewski / gintro

High level GObject-Introspection based GTK3/GTK4 bindings for Nim language
MIT License
293 stars 20 forks source link

`Settings.set` expects `self==PrintSettings` #100

Open nonchip opened 3 years ago

nonchip commented 3 years ago
proc appActivate(app: Application) =
  gtk4.getDefaultSettings().set("gtk-application-prefer-dark-theme", true)

apparently getDefaultSettings() returns a value of type Settings (as expected), but the set method inside that expects self to be of type PrintSettings:

Error: type mismatch: got <Settings, string, bool>
but expected one of: 
proc set(self: PrintSettings; key: cstring; value: cstring = "")
  first type mismatch at position: 1
  required type for self: PrintSettings
  but expression 'getDefaultSettings()' is of type: Settings

expression: set(getDefaultSettings(), "gtk-application-prefer-dark-theme", true)

since your bindings keep changing perfectly fine names around, i'm not sure if i'm doing this right (apparently there's also a gtk4.Settings but that doesn't have either of get_default nor getDefault, even though without changing the names around one'd expect it to be exactly that: gtk4.Settings.get_default(), since the c name is gtk_settings_get_default).

but to me that looks like PrintSettings inherits from Settings and somehow manages to overwrite instead of overload the set method.

btw, the C equivalent of what i'm trying to do here is:

g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL);
StefanSalewski commented 3 years ago

Thanks for reporting, I will try to investigate that issue in the next few days.

StefanSalewski commented 3 years ago

Seems to be not a bug in the bindings :-)

g_object_set() is a low level C function for multiple untyped arguments. That function is not provided by gobject-introspection, so it is not available in most high level GTK bindings including Nim.

Instead we use g_object_set_property() in Nim as setProperty(). Unfortunately that procedure needs a GValue as third parameter which makes it use a bit complicated.

https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#g-object-set

https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#g-object-set-property

Unfortunately this is not yet explained in the GTK4 book, but we have examples in the gintro README, just do a text search for the term setProperty:

https://github.com/StefanSalewski/gintro

http://ssalewski.de/gtkprogramming.html

A compiling code would look like

# issue
# gtk4.getDefaultSettings().set("gtk-application-prefer-dark-theme", true)

import gintro/[gtk4, gobject]

proc toBoolVal(b: bool): Value =
  let gtype = typeFromName("gboolean")
  discard init(result, gtype)
  setBoolean(result, b)

let d = gtk4.getDefaultSettings()

setProperty(d, "gtk-application-prefer-dark-theme", toBoolVal(true))

setProperty(getDefaultSettings(), "gtk-application-prefer-dark-theme", toBoolVal(true))

getDefaultSettings().setProperty("gtk-application-prefer-dark-theme", toBoolVal(true))

The toBoolVal() is copied from the gintro README. For the last line -- I am not sure if this notation with method call syntax works, I have never used that notation myself. I have no idea what this settings does and of course I can not run the code as it is not complete, but at least it compiles.

It is interesting that you are using already GTK4. Not all Linux distros ship it already, and there are still some bugs in GTK4. But I hope that it will be soon official available.

Let us know if above code will work for you or if you have more issues.

nonchip commented 3 years ago

thanks for clearing that up, your code seems to compile, but sadly doesn't work as expected.

I have no idea what this settings does and of course I can not run the code as it is not complete, but at least it compiles.

it should tell gtk to prefer a dark version of the stylesheet (e.g. adwaita-dark instead of adwaita). it doesn't seem to do anything though, while setting the environment variable GTK_THEME=Adwaita:dark works just fine (so it doesn't seem to be a gtk4 issue).

It is interesting that you are using already GTK4. Not all Linux distros ship it already, and there are still some bugs in GTK4. But I hope that it will be soon official available.

yeah it's a hobby project where i don't need to be very compatible right from the start so i figured better futureproof it.

minimal reproducing example:

# see comments below ("HERE") as to where i tried to insert the following line:
#getDefaultSettings().setProperty("gtk-application-prefer-dark-theme", toBoolVal(true))

import gintro/[gtk4, gobject, gio]

proc toBoolVal(b: bool): Value =
  let gtype = typeFromName("gboolean")
  discard init(result, gtype)
  setBoolean(result, b)

proc appActivate(app: Application) =
  #HERE does nothing
  let window = newApplicationWindow(app)
  #HERE does nothing
  window.title = "GTK4 & Nim"
  window.defaultSize = (200, 200)
  show(window)

proc main =
  #HERE would crash
  let app = newApplication("org.gtk.example")
  #HERE would crash
  connect(app, "activate", appActivate)
  discard run(app)

main()

where "crash" means (with of course varying line numbers):

Traceback (most recent call last)
/home/kyra/dev/nz80mvm/nz80mvm.nim(20) nz80mvm
/home/kyra/dev/nz80mvm/nz80mvm.nim(16) main
/home/kyra/.nimble/pkgs/gintro-0.8.3/gintro/gobject.nim(1908) setProperty
SIGSEGV: Illegal storage access. (Attempt to read from nil?)

so apparently i can't getDefaultSettings before appActivate (makes sense kinda), but no matter whether i do it before or after newApplicationWindow in that function, i still get a white 200x200 square window, instead of a darkish-grey one like i would when running it with the GTK_THEME=Adwaita:dark environment variable.

the gtk docs show an example of:

gtk_init (&argc, &argv);
g_object_set (gtk_settings_get_default (), "setting", value, NULL);

which seems to be way earlier than my appActivate stage. is there maybe a way to gtk_init manually before creating my app, thus allowing to access the settings (because they shouldn't be nil anymore then) before the app tries to load them?

when logging the value of the setting i see it change, but it has no effect. which is rather weird since https://gitlab.gnome.org/GNOME/gtk/-/blob/master/gtk/gtksettings.c#L1251 should be even updating all the css stuff.

according to https://stackoverflow.com/a/16003697/668125 i need to set it before "creating any widgets (which includes the main window)" but apparently doing it as the first thing in appActivate isn't enough, if i had to guess it's likely because some app wide StyleProvider is already initialized by then.

StefanSalewski commented 3 years ago

I have to admit that I don't know much about setting dark themes. A working C code example would be very helpful, an example in another language would be fine too.

Do you think that we can really set dark themes for single apps in GTK? I though of it more about a general decision for whole desktop, but maybe it works?

The stack overflow answer is from 2013, since then a lot has changed. And GTK4 may be again different, or maybe it is a GTK bug? Well I will see if I get something working, maybe I will have to ask Mr Bassi or some of the other core GTK devs.

nonchip commented 3 years ago

I have to admit that I don't know much about setting dark themes.

it's about setting themes at all, the gtk-application-prefer-dark-theme property is just a helper to make it so i don't have to hardcode or find a dark theme, see https://gitlab.gnome.org/GNOME/gtk/-/blob/master/gtk/gtksettings.c#L1637. the way i'm using it here (without an environment variable, with the flag set, and without a custom theme) the lines influencing the result of that function should be:

1670:  *theme_variant = g_strdup ("dark");
1676:  *theme_name = g_strdup (DEFAULT_THEME_NAME);

resulting (on my platform) in Adwaita-dark.

verifying with:

proc appActivate(app: Application) =
  # dumpSetting just gets a property, transforms it to a string GValue and echoes it
  getDefaultSettings().setProperty("gtk-application-prefer-dark-theme", toBoolVal(true))
  dumpSetting("gtk-application-prefer-dark-theme") # prints TRUE
  dumpSetting("gtk-theme-name") # prints Adwaita-dark
  let window = newApplicationWindow(app)
  # ...

double checked it's the right value by commenting out the setProperty line and running with GTK_THEME=Adwaita:dark environment, it prints FALSE (because now the application isn't the one preferring it but the user env), followed by Adwaita-dark (= the same theme name). so the value is set correctly, i just don't see the result (the window background is still white), while with the env it works, so it can't just be missing or similar, it must be that somehow the update isn't propagated to the window i'm about to create.

Do you think that we can really set dark themes for single apps in GTK?

According to the gtk3 docs and the gtk4 sources i'm rather positive that should still be possible. According to the docs it's even preferable to do it per-app, since e.g. a browser or text editor with a large bright document might not want the contrast of dark controls around it (as a user i personally disagree, but i see why as a dev it should be a per-app thing).

remember, GTK doesn't actually have anything resembling a standardized theme api anyone is supposed to use, it just has platform stylesheets in (usually) a bright and a dark variant. "Adwaita" literally means "the only one" because that's what it's supposed to be: the only css it loads (on a normal desktop at least, phones get their own platform css afaik). any "desktop themeing" done on top of that is a horrible hack many devs don't like

StefanSalewski commented 3 years ago

Well this works:

# nim c t0.nim
import gintro/[gtk, gobject]

#import gintro/[gtk4, gobject]

proc toBoolVal(b: bool): Value =
  let gtype = typeFromName("gboolean")
  discard init(result, gtype)
  setBoolean(result, b)

proc bye(w: gtk.Window) =
  mainQuit()
  echo "Bye..."

proc main =
  gtk.init()

  let d = gtk.getDefaultSettings()
  setProperty(d, "gtk-application-prefer-dark-theme", toBoolVal(true))

  let window = newWindow()
  window.title = "First Test"
  window.connect("destroy", bye)
  window.showAll
  gtk.main()

main()

I get a dark window with that example. Based on the GTK3 t0.nim example. So maybe GTK4 is the problem, or maybe the new GTK3/GTK4 app style.

StefanSalewski commented 3 years ago

And this GTK4 example works also for me:

##  https://gitlab.gnome.org/GNOME/gtk/-/blob/master/tests/simple.c
##  nim c simple.nim

import gintro/[gtk4, glib, gobject]

proc toBoolVal(b: bool): Value =
  let gtype = typeFromName("gboolean")
  discard init(result, gtype)
  setBoolean(result, b)

proc hello(b: Button) =
  echo "hello world"

proc quit_cb(window: Window; done: ref bool) =
  done[] = true
  wakeup(defaultMainContext())

proc main =
  var done = new bool
  gtk4.init()
  let d = gtk4.getDefaultSettings()
  setProperty(d, "gtk-application-prefer-dark-theme", toBoolVal(true))
  let window = newWindow()
  window.title = "hello world"
  window.resizable = false
  window.connect("destroy", quit_cb, done)
  let button = newButton()
  button.label = "hello world"
  button.marginTop = 10
  button.marginBottom = 10
  button.marginStart = 10
  button.marginEnd = 10
  button.connect("clicked", hello)
  window.setChild(button)
  window.show
  while not done[]:
    discard iteration(defaultMainContext(), mayBlock = true)

main()
nonchip commented 3 years ago

so that's interesting, i just ran your gtk4 example (just renamed the main function and inserted the debug output calls), and got this: image

while what i expect (and get with the env var) is this: image

sooo that's weird 0.o

StefanSalewski commented 3 years ago

And finally this GTK4 app style example works also:

##  https://gitlab.gnome.org/GNOME/gtk/-/blob/master/examples/hello-world.c
##  nim c helloWorld.nim

import gintro/[gtk4, gobject, gio]

proc toBoolVal(b: bool): Value =
  let gtype = typeFromName("gboolean")
  discard init(result, gtype)
  setBoolean(result, b)

proc destroyWindow(b: Button; w: gtk4.ApplicationWindow) =
  gtk4.destroy(w)

proc printHello(widget: Button) =
  echo("Hello World")

proc activate(app: gtk4.Application) =
  let d = gtk4.getDefaultSettings()
  setProperty(d, "gtk-application-prefer-dark-theme", toBoolVal(true))
  let window = newApplicationWindow(app)
  window.title = "Window"
  window.defaultSize = (20, 20)
  let box = newBox(Orientation.horizontal, 0)
  window.setChild( box)
  let button = newButton("Hello World")
  button.connect("clicked", printHello)
  button.connect("clicked", destroyWindow, window)
  box.append(button)
  window.show

proc main =
  let app = newApplication("org.gtk.example", {})
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

Based on the GTK4 book code from https://github.com/StefanSalewski/GtkProgrammingBook/tree/master/nim

If it should not work for you then maybe your GTK4 is broken. My one is very recent, compiled from gitlab sources some days ago.

nonchip commented 3 years ago

ok crazy enough your newest (gtk4 app style) example did work for me. just... WHY? we didn't do anything really different in the order of things between that nonworking (previous "gtk4 old style" example) and this? and isn't it pretty much exactly the same as what i did way above? beats me tbh...

StefanSalewski commented 3 years ago

And for me all is dark, the window header and the window content. But well there may exists GTK4 bugs still...

nonchip commented 3 years ago

And for me all is dark, the window header and the window content.

yeah that's how all those methods we tried so far should have behaved, but for me it only worked on your very last code.