GTK3 DarkMode

Linux specific forum
User avatar
mk-soft
Always Here
Always Here
Posts: 6238
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

GTK3 DarkMode

Post by mk-soft »

DarkMode for GTK3 Application ... :wink:

Update v1.02.1

Update v1.02.2
- Change get/set property

Update v1.02.3
- Added GetThemeIsDark

GTK-Darkmode.pb

Code: Select all

;-TOP

EnableExplicit

; GTK DarkMode by mk-soft, v1.02.3, 18.12.2022 - 28.08.2025
; Link: https://www.purebasic.fr/english/viewtopic.php?t=80298

; GTK GetWindowColor by mk-soft, v1.01.1, 27.08.2023

;- Global glib gvalue

CompilerIf Not Defined(__G_TYPE_H__, #PB_Constant)
  #__G_TYPE_H__ = #True
  
  Macro G_TYPE_MAKE_FUNDAMENTAL(x)
    x << 2
  EndMacro
  
  #G_TYPE_INVALID = G_TYPE_MAKE_FUNDAMENTAL(0)
  #G_TYPE_NONE = G_TYPE_MAKE_FUNDAMENTAL(1)
  #G_TYPE_INTERFACE = G_TYPE_MAKE_FUNDAMENTAL(2)
  #G_TYPE_CHAR = G_TYPE_MAKE_FUNDAMENTAL(3)
  #G_TYPE_UCHAR = G_TYPE_MAKE_FUNDAMENTAL(4)
  #G_TYPE_BOOLEAN = G_TYPE_MAKE_FUNDAMENTAL(5)
  #G_TYPE_INT = G_TYPE_MAKE_FUNDAMENTAL(6)
  #G_TYPE_UINT = G_TYPE_MAKE_FUNDAMENTAL(7)
  #G_TYPE_LONG = G_TYPE_MAKE_FUNDAMENTAL(8)
  #G_TYPE_ULONG = G_TYPE_MAKE_FUNDAMENTAL(9)
  #G_TYPE_INT64 = G_TYPE_MAKE_FUNDAMENTAL(10)
  #G_TYPE_UINT64 = G_TYPE_MAKE_FUNDAMENTAL(11)
  #G_TYPE_ENUM = G_TYPE_MAKE_FUNDAMENTAL(12)
  #G_TYPE_FLAGS = G_TYPE_MAKE_FUNDAMENTAL(13)
  #G_TYPE_FLOAT = G_TYPE_MAKE_FUNDAMENTAL(14)
  #G_TYPE_DOUBLE = G_TYPE_MAKE_FUNDAMENTAL(15)
  #G_TYPE_STRING = G_TYPE_MAKE_FUNDAMENTAL(16)
  #G_TYPE_POINTER = G_TYPE_MAKE_FUNDAMENTAL(17)
  #G_TYPE_BOXED = G_TYPE_MAKE_FUNDAMENTAL(18)
  #G_TYPE_PARAM = G_TYPE_MAKE_FUNDAMENTAL(19)
  #G_TYPE_OBJECT = G_TYPE_MAKE_FUNDAMENTAL(20)
  
  
CompilerEndIf

ImportC ""
  gtk_widget_get_style_context(Widget)
  gtk_style_context_new()
  gtk_style_context_get_property(context, property.p-utf8, state, value)
  gtk_style_context_get_color(context, state, pcolor)
  gtk_style_context_get_background_color(context, state, pcolor)
EndImport

#GTK_STATE_FLAG_NORMAL       = 0
#GTK_STATE_FLAG_ACTIVE       = 1 << 0
#GTK_STATE_FLAG_PRELIGHT     = 1 << 1
#GTK_STATE_FLAG_SELECTED     = 1 << 2
#GTK_STATE_FLAG_INSENSITIVE  = 1 << 3
#GTK_STATE_FLAG_INCONSISTENT = 1 << 4
#GTK_STATE_FLAG_FOCUSED      = 1 << 5
#GTK_STATE_FLAG_BACKDROP     = 1 << 6
#GTK_STATE_FLAG_DIR_LTR      = 1 << 7
#GTK_STATE_FLAG_DIR_RTL      = 1 << 8

Structure GdkRGBA
  red.d;
  green.d;
  blue.d;
  alpha.d;
EndStructure

; ----

Procedure.s GetThemeName()
  Protected ThemeName.s, *Settings, Theme.gvalue
  
  *Settings = gtk_settings_get_default_()
  If *Settings
    g_object_get_property_(*Settings, "gtk-theme-name", @Theme);
    ThemeName.s = PeekS(g_value_get_string_(Theme), -1, #PB_UTF8)
    g_value_unset_(Theme)
    ProcedureReturn ThemeName
  EndIf
EndProcedure

Procedure SetThemeName(ThemeName.s)
  Protected *Settings, Theme.gvalue
  
  *Settings = gtk_settings_get_default_()
  If *Settings
    g_value_init_(Theme, #G_TYPE_STRING)
    g_value_set_string_(Theme, ThemeName)
    g_object_set_property_(*Settings, "gtk-theme-name", Theme);
    g_value_unset_(Theme)
    ProcedureReturn #True
  EndIf
EndProcedure

; ----

Procedure GetThemeIsDark(Window)
  Protected context, c1.l, c2.l, color.GdkRGBA, color_background.GdkRGBA
  
  context = gtk_widget_get_style_context(WindowID(Window))
  gtk_style_context_get_color(context, #GTK_STATE_FLAG_NORMAL, @color)
  gtk_style_context_get_background_color(context, #GTK_STATE_FLAG_NORMAL, @color_background)
  c1 = RGB(color\red * 255, color\green * 255, color\blue * 255)
  c2 = RGB(color_background\red * 255, color_background\green * 255, color_background\blue * 255)
  If c1 > c2
    ProcedureReturn #True
  Else
    ProcedureReturn #False
  EndIf
EndProcedure

; ----

Procedure IsDarkMode()
  Protected boolVal, *Settings, Value.gvalue
  
  *Settings = gtk_settings_get_default_()
  If *Settings
    g_object_get_property_(*Settings, "gtk-application-prefer-dark-theme", @Value);
    boolVal = g_value_get_boolean_(Value)
    ProcedureReturn boolVal
  EndIf
EndProcedure

Procedure SetDarkMode(State)
  Protected boolVal, *Settings, Value.gvalue
  
  *Settings = gtk_settings_get_default_()
  If *Settings
    g_value_init_(Value, #G_TYPE_BOOLEAN)
    g_object_get_property_(*Settings, "gtk-application-prefer-dark-theme", Value);
    boolVal = g_value_get_boolean_(Value)
    g_value_set_boolean_(Value, State)
    g_object_set_property_(*Settings, "gtk-application-prefer-dark-theme", Value);
    g_value_unset_(Value)
    ProcedureReturn boolVal
  EndIf
EndProcedure

; ****

;- Colors

Procedure.l GetWindowColorEx(Window)
  Protected r1, context, color.GdkRGBA
  
  context = gtk_widget_get_style_context(WindowID(Window))
  gtk_style_context_get_background_color(context, #GTK_STATE_FLAG_NORMAL, @color)
  r1 = RGBA(color\red * 255, color\green * 255, color\blue * 255, color\alpha * 255)
  ProcedureReturn r1
EndProcedure

Procedure.l GetStyleColor(Widget, Property.s)
  Protected r1, context, value.gvalue, *color.GdkRGBA
  
  context = gtk_widget_get_style_context(widget)
  gtk_style_context_get_property(context, Property, #GTK_STATE_FLAG_NORMAL, @Value)
  *color = g_value_get_boxed_(value)
  If *color
    r1 = RGBA(*color\red * 255, *color\green * 255, *color\blue * 255, *color\alpha * 255)
  EndIf
  g_value_unset_(value)
  ProcedureReturn r1
EndProcedure

; ****

CompilerIf #PB_Compiler_IsMainFile
  
  Enumeration Windows
    #Main
  EndEnumeration
  
  Enumeration MenuBar
    #MainMenu
  EndEnumeration
  
  Enumeration MenuItems
    
  EndEnumeration
  
  Enumeration Gadgets
    #MainEdit
    #MainButton1
    #MainButton2
  EndEnumeration
  
  Enumeration StatusBar
    #MainStatusBar
  EndEnumeration
  
  ; ----
  
  Procedure UpdateWindow()
    Protected dx, dy
    dx = WindowWidth(#Main)
    dy = WindowHeight(#Main) - StatusBarHeight(#MainStatusBar)
    ; Resize gadgets
    ResizeGadget(#MainEdit, 5, 5, dx -10, dy - 45)
    ResizeGadget(#MainButton1, 10, dy - 35, 120, 30)
    ResizeGadget(#MainButton2, dx - 130, dy - 35, 120, 30)
  EndProcedure
  
  Procedure Main()
    Protected dx, dy, IsSysDarkMode, Theme.s
    
    #MainStyle = #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget
    
    If OpenWindow(#Main, #PB_Ignore, #PB_Ignore, 800, 600, "Window" , #MainStyle | #PB_Window_Invisible)
      ; Menu
      CreateMenu(#MainMenu, WindowID(#Main))
      
      ; StatusBar
      CreateStatusBar(#MainStatusBar, WindowID(#Main))
      AddStatusBarField(#PB_Ignore)
      
      ; Gadgets
      dx = WindowWidth(#Main)
      dy = WindowHeight(#Main) - StatusBarHeight(#MainStatusBar)
      EditorGadget(#MainEdit, 5, 5, dx -10, dy - 45)
      ButtonGadget(#MainButton1, 10, dy - 35, 120, 30, "Set Dark")
      ButtonGadget(#MainButton2, dx - 130, dy - 35, 120, 30, "Clear Dark")
      
      Theme = GetThemeName()
      AddGadgetItem(#MainEdit, -1, "Theme Name: " +  GetThemeName())
      
      AddGadgetItem(#MainEdit, -1, "Window Color: " + Hex(GetStyleColor(WindowID(#Main), "color"), #PB_Long))
      AddGadgetItem(#MainEdit, -1, "Window Background-Color: " + Hex(GetStyleColor(WindowID(#Main), "background-color"), #PB_Long))
      
      IsSysDarkMode = GetThemeIsDark(#Main)
      If IsSysDarkMode
        AddGadgetItem(#MainEdit, -1, "System is DarkMode")
      Else
        AddGadgetItem(#MainEdit, -1, "System is not DarkMode")
      EndIf
      
      SetThemeName("Adwaita")
      SetDarkMode(IsSysDarkMode)
      Theme = GetThemeName()
      AddGadgetItem(#MainEdit, -1, "New Theme Name: " +  GetThemeName())
      
      ; Bind Events
      BindEvent(#PB_Event_SizeWindow, @UpdateWindow(), #Main)
      
      HideWindow(#Main, 0)
      
      Repeat
        Select WaitWindowEvent()
          Case #PB_Event_CloseWindow
            Break
            
          Case #PB_Event_Menu
            Select EventMenu()
              CompilerIf #PB_Compiler_OS = #PB_OS_MacOS   
                Case #PB_Menu_About
                  
                Case #PB_Menu_Preferences
                  
                Case #PB_Menu_Quit
                  PostEvent(#PB_Event_CloseWindow, #Main, #Null)
                  
              CompilerEndIf
                
            EndSelect
            
          Case #PB_Event_Gadget
            Select EventGadget()
              Case #MainEdit
                Select EventType()
                  Case #PB_EventType_Change
                    ;
                EndSelect
                
              Case #MainButton1
                Select EventType()
                  Case #PB_EventType_LeftClick
                    If SetDarkMode(#True)
                      StatusBarText(#MainStatusBar, 0, " Old DarkMode: True")
                    Else
                      StatusBarText(#MainStatusBar, 0, " Old DarkMode: False")
                    EndIf
                    
                EndSelect
                Debug "$" + RSet(Hex(GetWindowColorEx(#Main), #PB_Long), 8, "0")
                
              Case #MainButton2
                Select EventType()
                  Case #PB_EventType_LeftClick
                    If SetDarkMode(#False)
                      StatusBarText(#MainStatusBar, 0, " Old DarkMode: True")
                    Else
                      StatusBarText(#MainStatusBar, 0, " Old DarkMode: False")
                    EndIf
                    
                EndSelect
                Debug "$" + RSet(Hex(GetWindowColorEx(#Main), #PB_Long), 8, "0")
                
            EndSelect
            
        EndSelect
      ForEver
      
    EndIf
    
  EndProcedure : Main()
  
CompilerEndIf
Last edited by mk-soft on Thu Aug 28, 2025 3:54 pm, edited 5 times in total.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
kenmo
Addict
Addict
Posts: 2042
Joined: Tue Dec 23, 2003 3:54 am

Re: GTK3 DarkMode

Post by kenmo »

Nice!!!!! Thanks!
User avatar
mk-soft
Always Here
Always Here
Posts: 6238
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: GTK3 DarkMode

Post by mk-soft »

Update v1.02.2
- Warning on new kernel: Change set/get property

I was bothered by the compiler warnings from g_object_get/set on the new kernel 6.12

Unfortunately, this only works with themes that support both modes. Like Adwaita or with many themes of Mint Linux.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
kenmo
Addict
Addict
Posts: 2042
Joined: Tue Dec 23, 2003 3:54 am

Re: GTK3 DarkMode

Post by kenmo »

On my system, IsDarkMode() returns 0 or 1 depending on whether the PB app is currently set light or dark... But how can we poll the user's OS settings to know whether the app SHOULD be light or dark? Does that vary by Linux distro....? Thanks for any advice!
User avatar
mk-soft
Always Here
Always Here
Posts: 6238
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: GTK3 DarkMode

Post by mk-soft »

What I have found so far is only about a trick ...

Code: Select all

;-TOP

; GTK GetWindowColor by mk-soft, v1.01.1, 27.08.2023

ImportC ""
  gtk_window_get_titlebar(window)
  gtk_widget_get_style_context(Widget)
  gtk_style_context_get_background_color(context, state, pcolor)
EndImport

#GTK_STATE_FLAG_NORMAL       = 0
#GTK_STATE_FLAG_ACTIVE       = 1 << 0
#GTK_STATE_FLAG_PRELIGHT     = 1 << 1
#GTK_STATE_FLAG_SELECTED     = 1 << 2
#GTK_STATE_FLAG_INSENSITIVE  = 1 << 3
#GTK_STATE_FLAG_INCONSISTENT = 1 << 4
#GTK_STATE_FLAG_FOCUSED      = 1 << 5
#GTK_STATE_FLAG_BACKDROP     = 1 << 6
#GTK_STATE_FLAG_DIR_LTR      = 1 << 7
#GTK_STATE_FLAG_DIR_RTL      = 1 << 8

Structure GdkRGBA
  red.d;
  green.d;
  blue.d;
  alpha.d;
EndStructure

Procedure.l GetWindowColorEx(Window)
  Protected r1, context, color.GdkRGBA
  
  context = gtk_widget_get_style_context(WindowID(Window))
  gtk_style_context_get_background_color(context, #GTK_STATE_FLAG_NORMAL, @color)
  r1 = RGBA(color\red * 255, color\green * 255, color\blue * 255, color\alpha * 255)
  ProcedureReturn r1
EndProcedure

Procedure GetDarkMode(Window)
  Protected context, color.GdkRGBA
  
  context = gtk_widget_get_style_context(WindowID(Window))
  gtk_style_context_get_background_color(context, #GTK_STATE_FLAG_NORMAL, @color)
  If color\red <= 0.5 And color\green <= 0.5 And color\blue <= 0.5
    ProcedureReturn #True
  Else
    ProcedureReturn #False
  EndIf
EndProcedure

; ****
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
kenmo
Addict
Addict
Posts: 2042
Joined: Tue Dec 23, 2003 3:54 am

Re: GTK3 DarkMode

Post by kenmo »

Doesn't work for me :( assuming I'm supposed to create a PB window and pass that to the procedure. Always returns 0 whether OS is set to Light or Dark mode.

Firefox tracks the OS light/dark mode very well, and it looks like it's using basically the same method!
https://askubuntu.com/questions/1469869 ... de-systems
User avatar
mk-soft
Always Here
Always Here
Posts: 6238
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: GTK3 DarkMode

Post by mk-soft »

Update v1.02.3
- Added GetThemeIsDark

I've customised it a bit. Should work now ;)
See above

Here an example ...

Update 1.02
- use signal now

Code: Select all

;-TOP example

IncludeFile "GTK-Darkmode.pb"

Enumeration customEvent #PB_Event_FirstCustomValue
  #MyEvent_ChangeDarkMode
EndEnumeration

Global IsDarkMode, signal_themename

; ----

ProcedureC DoEventThemeChanged(settings, gparam)
  IsDarkMode = GetThemeIsDark(0)
  PostEvent(#MyEvent_ChangeDarkMode, 0, 0, 0, IsDarkMode)
EndProcedure

; ----

Procedure DrawCanvas()
  Protected dx, dy
  dx = GadgetWidth(0)
  dy = GadgetHeight(0)
  If StartDrawing(CanvasOutput(0))
    If IsDarkMode
      Box(0, 0, dx, dy, $242424)
      DrawText(20, 20, "Hello ;) - Is Dark", $F5F5F5, $242424)
    Else
      Box(0, 0, dx, dy, $F5F5F5)
      DrawText(20, 20, "Hello ;) - Is Light", $242424, $F5F5F5)
    EndIf
    StopDrawing()
  EndIf
EndProcedure

Procedure UpdateWindow()
  Protected dx, dy
  dx = WindowWidth(0)
  dy = WindowHeight(0) - StatusBarHeight(0) - MenuHeight()
  ; Resize Gadgets
  ResizeGadget( 0, 5, 5, dx-10,dy-10)
  DrawCanvas()
EndProcedure

Procedure Main()
  Protected dx, dy
  
  #WinStyle = #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget
  
  If OpenWindow(0, #PB_Ignore, #PB_Ignore, 600, 400, "Test Window", #WinStyle)
    IsDarkMode = GetThemeIsDark(0)
    
    ; MenuBar
    CreateMenu(0, WindowID(0))
    MenuTitle("&File")
    MenuItem(99, "E&xit")
    
    ; StatusBar
    CreateStatusBar(0, WindowID(0))
    AddStatusBarField(#PB_Ignore)
    
    ; Gadgets
    dx = WindowWidth(0)
    dy = WindowHeight(0) - StatusBarHeight(0) - MenuHeight()
    CanvasGadget( 0, 5, 5, dx-10,dy-10, #PB_Canvas_Border)
    DrawCanvas()
    
    ; Bind Events
    BindEvent(#PB_Event_SizeWindow, @UpdateWindow(), 0)
    
    ; Bind Signals
    signal_themename = g_signal_connect_(gtk_settings_get_default_(), "notify::gtk-theme-name", @DoEventThemeChanged(), 0)
    
    ; Main Loop
    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_CloseWindow
          Select EventWindow()
            Case 0
              Break
          EndSelect
          
        Case #PB_Event_Menu
          Select EventMenu()
            Case 99
              PostEvent(#PB_Event_CloseWindow, 0, 0)
              
          EndSelect
          
        Case #PB_Event_Gadget
          Select EventGadget()
              
          EndSelect
          
        Case #MyEvent_ChangeDarkMode
          DrawCanvas()
          
      EndSelect
    ForEver
    
    ; Unbind Signals
    If g_signal_handler_is_connected_(gtk_settings_get_default_(), signal_themename)
      g_signal_handler_disconnect_(gtk_settings_get_default_(), signal_themename)
    EndIf
  EndIf
  
EndProcedure : Main()
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
Post Reply