Skip to content

Commit

Permalink
implement gtk4 theming over libadwaita apps
Browse files Browse the repository at this point in the history
by overwriting the GTK4 CSS
  • Loading branch information
ALEX11BR committed Jul 16, 2024
1 parent d4d28fa commit ca13ba8
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 31 deletions.
5 changes: 4 additions & 1 deletion src/applythemes.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class BaseApplyThemes:
"""
The base theme applying mechanism: write themes and settings to config files
"""
def applyThemes(self, props, gtk2Theme, gtk4Theme, kvantumTheme, kvantumThemeFilePath, cssText):
def applyThemes(self, props, gtk2Theme, gtk4Theme, kvantumTheme, kvantumThemeFilePath, cssText, gtk4ThemePath):
gtkKeyFile = GLib.KeyFile()

gtkKeyFile.set_string("Settings", "gtk-theme-name", gtk4Theme)
Expand Down Expand Up @@ -99,6 +99,9 @@ def applyThemes(self, props, gtk2Theme, gtk4Theme, kvantumTheme, kvantumThemeFil
with open(os.path.join(GLib.get_user_config_dir(), "gtk-3.0", "gtk.css"), "w") as cssFile:
cssFile.write(cssText)
with open(os.path.join(GLib.get_user_config_dir(), "gtk-4.0", "gtk.css"), "w") as cssFile:
if gtk4ThemePath is not None:
cssFile.write("* {all: unset;}\n")
cssFile.write(f'@import url("{gtk4ThemePath}")\n')
cssFile.write(cssText)

class GSettingsApplyThemes(BaseApplyThemes):
Expand Down
41 changes: 20 additions & 21 deletions src/getavailablethemes.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Copyright (C) 2021 Popa Ioan Alexandru
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Expand All @@ -31,17 +31,16 @@ def uniquifySortedListStore(listStore):
"""
The functions below create Gtk.ListStore's that store themes
for use in SearchableThemeList's and have the following columns:
1. A str column which holds the display name of the theme
2. A str column which holds the underlying theme name
3. (Optional, depends on which kind of themes are used)
a) A GdkPixbuf.Pixbuf column which holds the theme's preview image
b) A str column which holds the theme file path (for Kvantum)
0. A str column which holds the display name of the theme
1. A str column which holds the underlying theme name
2. A str column which holds the theme file/folder path (those starting in "//" are fake ones that show a lack of a theme file)
3. (Optional) A GdkPixbuf.Pixbuf column which holds the theme's preview image
"""

def getAvailableGtk3Themes():
availableThemes = Gtk.ListStore(str, str, GdkPixbuf.Pixbuf)
availableThemes = Gtk.ListStore(str, str, str, GdkPixbuf.Pixbuf)
availableThemes.set_sort_column_id(0, Gtk.SortType.ASCENDING)
availableThemes.append([" Adwaita", "Adwaita",
availableThemes.append([" Adwaita", "Adwaita", "//none",
GdkPixbuf.Pixbuf.new_from_resource(
"/com/github/alex11br/themechanger/adwaita.png"
)
Expand All @@ -58,7 +57,7 @@ def getAvailableGtk3Themes():
availableThemes.append([
# we add a space to the beginning of the display name
# to create spacing between the theme preview image and the theme name text
" "+f, f,
" "+f, f, os.path.join(lookupPath, f),
GdkPixbuf.Pixbuf.new_from_file_at_size(
thumbnailFile, 120, 35
) if os.path.isfile(thumbnailFile) else GdkPixbuf.Pixbuf.new_from_resource(
Expand All @@ -70,7 +69,7 @@ def getAvailableGtk3Themes():
return uniquifySortedListStore(availableThemes)

def getAvailableIconThemes():
availableThemes = Gtk.ListStore(str, str, GdkPixbuf.Pixbuf)
availableThemes = Gtk.ListStore(str, str, str, GdkPixbuf.Pixbuf)
availableThemes.set_sort_column_id(0, Gtk.SortType.ASCENDING)
lookupPaths = [
os.path.join(GLib.get_user_data_dir(), "icons"),
Expand All @@ -89,7 +88,7 @@ def getAvailableIconThemes():
)
if (themeFileParser.get_string("Icon Theme", "Directories")):
availableThemes.append([
themeFileParser.get_locale_string("Icon Theme", "Name"), f,
themeFileParser.get_locale_string("Icon Theme", "Name"), f, os.path.join(lookupPath, f),
iconTheme.load_icon(
iconTheme.get_example_icon_name(),
32, Gtk.IconLookupFlags.FORCE_SIZE
Expand All @@ -102,10 +101,10 @@ def getAvailableIconThemes():
return uniquifySortedListStore(availableThemes)

def getAvailableCursorThemes():
availableThemes = Gtk.ListStore(str, str, GdkPixbuf.Pixbuf)
availableThemes = Gtk.ListStore(str, str, str, GdkPixbuf.Pixbuf)
availableThemes.set_sort_column_id(0, Gtk.SortType.ASCENDING)
availableThemes.append([
"Default cursor", "default",
"Default cursor", "default", "//none",
GdkPixbuf.Pixbuf.new_from_resource(
"/com/github/alex11br/themechanger/defaultcursor.png"
)
Expand All @@ -125,7 +124,7 @@ def getAvailableCursorThemes():
os.path.join(lookupPath, f, "index.theme"), GLib.KeyFileFlags.NONE
)
availableThemes.append([
themeFileParser.get_locale_string("Icon Theme", "Name"), f,
themeFileParser.get_locale_string("Icon Theme", "Name"), f, os.path.join(lookupPath, f),
pixbufFromXCursor(
cursorPath
) if os.path.isfile(cursorPath) else GdkPixbuf.Pixbuf.new_from_resource(
Expand All @@ -139,9 +138,9 @@ def getAvailableCursorThemes():
return uniquifySortedListStore(availableThemes)

def getAvailableGtk4Themes():
availableThemes = Gtk.ListStore(str, str)
availableThemes = Gtk.ListStore(str, str, str)
availableThemes.set_sort_column_id(0, Gtk.SortType.ASCENDING)
availableThemes.append(["Adwaita", "Adwaita"])
availableThemes.append(["Adwaita", "Adwaita", "//none"])
lookupPaths = [
os.path.join(GLib.get_user_data_dir(), "themes"),
os.path.join(GLib.get_home_dir(), ".themes"),
Expand All @@ -150,13 +149,13 @@ def getAvailableGtk4Themes():
try:
for f in os.listdir(lookupPath):
if os.path.isfile(os.path.join(lookupPath, f, "gtk-4.0", "gtk.css")):
availableThemes.append([f, f])
availableThemes.append([f, f, os.path.join(lookupPath, f)])
except:
pass
return uniquifySortedListStore(availableThemes)

def getAvailableGtk2Themes():
availableThemes = Gtk.ListStore(str, str)
availableThemes = Gtk.ListStore(str, str, str)
availableThemes.set_sort_column_id(0, Gtk.SortType.ASCENDING)
lookupPaths = [
os.path.join(GLib.get_user_data_dir(), "themes"),
Expand All @@ -166,7 +165,7 @@ def getAvailableGtk2Themes():
try:
for f in os.listdir(lookupPath):
if os.path.isfile(os.path.join(lookupPath, f, "gtk-2.0", "gtkrc")):
availableThemes.append([f, f])
availableThemes.append([f, f, os.path.join(lookupPath, f)])
except:
pass
return uniquifySortedListStore(availableThemes)
Expand Down
16 changes: 9 additions & 7 deletions src/searchablethemelist.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Copyright (C) 2021 Popa Ioan Alexandru
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Expand Down Expand Up @@ -38,8 +38,8 @@ def __init__(self, themesTreeViewModel, selectedTheme, onThemeSelectedCallback,
if hasPixbuf:
pixbufCell = Gtk.CellRendererPixbuf()
themeColumn.pack_start(pixbufCell, False)
themeColumn.add_attribute(pixbufCell, "pixbuf", 2)
themeColumn.add_attribute(pixbufCell, "pixbuf", 3)

nameCell = Gtk.CellRendererText()
themeColumn.pack_start(nameCell, True)
themeColumn.add_attribute(nameCell, "text", 0)
Expand All @@ -54,10 +54,12 @@ def selectTheme(self):
self.themesTreeViewModelFiltered.get_path(row.iter),
None, False
)
self.selectedThemeRow = row
return
# Maybe the selectedTheme isn't in the model; we'll properly handle this case here
firstRow = self.themesTreeViewModelFiltered[0]
self.selectedTheme = firstRow[1]
self.selectedThemeRow = firstRow
self.themesTreeViewSelection.select_iter(firstRow.iter)
self.themesTreeView.scroll_to_point(0, 0)

Expand All @@ -70,7 +72,7 @@ def setThemesTreeViewModel(self, themesTreeViewModel):

def setScrolledWindowOverlayScrolling(self, state):
self.themesScrolledWindow.set_overlay_scrolling(state)

def filterFunc(self, model, treeiter, data):
return self.themesSearchEntry.get_text().lower() in model[treeiter][0].lower()

Expand All @@ -83,6 +85,7 @@ def onThemeSelected(self, selection):
if not treeiter:
return
self.selectedTheme = model[treeiter][1]
self.selectedThemeRow = model[treeiter]
if self.hasThemeFilePath:
self.onThemeSelectedCallback(self.selectedTheme, model[treeiter][2])
else:
Expand All @@ -93,4 +96,3 @@ def onChangeFilter(self, searchentry):
self.themesTreeViewModelFiltered.refilter()
# This handles the case in which the newly filtered model doen't have the selectedTheme anymore
self.selectTheme()

19 changes: 18 additions & 1 deletion src/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,22 @@ def setDefaultCursor(self):
defaultCursor = Gdk.Cursor.new_for_display(self.defaultDisplay, Gdk.CursorType.LEFT_PTR)
self.defaultScreen.get_root_window().set_cursor(defaultCursor)

def getGtk4ThemeCssPath(self):
if self.anotherGtk4ThemeSwitch.get_active():
gtk4ThemeRoot = self.gtk4SearchableThemeList.selectedThemeRow[2]
else:
gtk4ThemeRoot = self.gtkSearchableThemeList.selectedThemeRow[2]

if gtk4ThemeRoot[:2] == "//":
return None

gtk4ThemeCssPath = os.path.join(gtk4ThemeRoot, "gtk-4.0", "gtk-dark.css" if self.gtkProps.gtk_application_prefer_dark_theme else "gtk.css")
if not os.path.isfile(gtk4ThemeCssPath):
gtk4ThemeCssPath = os.path.join(gtk4ThemeRoot, "gtk-4.0", "gtk.css")
if not os.path.isfile(gtk4ThemeCssPath):
return None
return gtk4ThemeCssPath

def onGtkThemeChanged(self, themename):
self.onSettingChanged()

Expand Down Expand Up @@ -380,7 +396,8 @@ def applySettings(self, *args):
gtk4Theme=self.gtk4ThemeName,
kvantumTheme=self.kvantumThemeName,
kvantumThemeFilePath=self.kvantumThemeFilePath,
cssText=self.cssTextBuffer.props.text
cssText=self.cssTextBuffer.props.text,
gtk4ThemePath=self.getGtk4ThemeCssPath()
)
except Exception as err:
showErrorDialog("Error: Could not apply settings", str(err))
Expand Down
2 changes: 1 addition & 1 deletion src/window.ui
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Instantaneous theme changing is supported. This means that setting another theme for GTK2 or GTK4 won't have any effect.</property>
<property name="label" translatable="yes">Instantaneous theme changing is supported. This means that setting another theme for GTK2 won't have any effect.</property>
<property name="wrap">True</property>
</object>
<packing>
Expand Down

0 comments on commit ca13ba8

Please sign in to comment.