2022-07-17 11:46:05 +00:00
# main.py
#
2022-08-11 08:16:44 +00:00
# Change the look of Adwaita, with ease
# Copyright (C) 2022 Adwaita Manager Team
2022-07-17 11:46:05 +00:00
#
2022-08-11 08:16:44 +00:00
# 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 3 of the License, or
# (at your option) any later version.
2022-08-11 16:39:24 +00:00
#
2022-08-11 08:16:44 +00:00
# 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.
2022-08-11 16:39:24 +00:00
#
2022-08-11 08:16:44 +00:00
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
2022-07-17 11:46:05 +00:00
import sys
import json
2022-07-17 17:07:40 +00:00
import os
2022-07-18 10:51:25 +00:00
import re
2022-07-21 19:03:18 +00:00
import traceback
2022-07-17 11:46:05 +00:00
2022-07-25 11:05:17 +00:00
from anyascii import anyascii
2022-07-17 11:46:05 +00:00
2022-07-29 11:56:36 +00:00
import gi
2022-07-24 11:00:05 +00:00
from gi . repository import Gtk , Gdk , Gio , Adw , GLib , Xdp , XdpGtk4
2022-08-11 12:22:08 +00:00
from material_color_utilities_python import *
2022-07-29 11:56:36 +00:00
2022-07-26 13:31:16 +00:00
from . settings_schema import settings_schema
2022-07-17 11:46:05 +00:00
from . window import AdwcustomizerMainWindow
2022-07-21 19:03:18 +00:00
from . palette_shades import AdwcustomizerPaletteShades
2022-07-17 11:46:05 +00:00
from . option import AdwcustomizerOption
2022-07-22 12:29:32 +00:00
from . app_type_dialog import AdwcustomizerAppTypeDialog
2022-07-24 17:46:37 +00:00
from . custom_css_group import AdwcustomizerCustomCSSGroup
2022-08-10 15:51:55 +00:00
from . plugins_list import AdwcustomizerPluginsList
2022-08-11 16:44:51 +00:00
from . import info
2022-08-11 16:39:24 +00:00
2022-08-11 16:47:30 +00:00
2022-07-18 10:51:25 +00:00
def to_slug_case ( non_slug ) :
2022-07-25 11:30:16 +00:00
return re . sub ( r " [^0-9a-z]+ " , " - " , anyascii ( non_slug ) . lower ( ) ) . strip ( " - " )
2022-07-17 11:46:05 +00:00
2022-08-10 08:46:52 +00:00
2022-07-17 11:46:05 +00:00
class AdwcustomizerApplication ( Adw . Application ) :
""" The main application singleton class. """
2022-07-20 10:18:55 +00:00
def __init__ ( self , version ) :
2022-08-10 08:46:52 +00:00
super ( ) . __init__ (
application_id = " com.github.AdwCustomizerTeam.AdwCustomizer " ,
flags = Gio . ApplicationFlags . FLAGS_NONE ,
)
2022-07-20 10:18:55 +00:00
self . version = version
2022-07-17 11:46:05 +00:00
2022-07-24 11:00:05 +00:00
self . portal = Xdp . Portal ( )
2022-07-29 11:56:36 +00:00
2022-08-01 17:09:45 +00:00
self . preset_name = " "
2022-08-01 19:42:42 +00:00
self . is_dirty = False
2022-08-01 17:09:45 +00:00
2022-07-17 15:02:29 +00:00
self . variables = { }
2022-07-29 11:56:36 +00:00
self . pref_variables = { }
2022-07-21 19:03:18 +00:00
self . palette = { }
2022-07-29 11:56:36 +00:00
self . pref_palette_shades = { }
2022-07-24 17:46:37 +00:00
self . custom_css = { }
2022-07-29 11:56:36 +00:00
self . custom_css_group = None
self . custom_presets = { }
2022-07-21 19:03:18 +00:00
self . global_errors = [ ]
2022-07-24 17:46:37 +00:00
self . current_css_provider = None
2022-07-29 11:56:36 +00:00
2022-07-17 15:02:29 +00:00
self . is_ready = False
2022-07-29 11:56:36 +00:00
def do_activate ( self ) :
""" Called when the application is activated.
We raise the application ' s main window, creating it if
necessary .
"""
2022-08-10 15:11:39 +00:00
self . win = self . props . active_window
if not self . win :
self . win = AdwcustomizerMainWindow ( application = self )
2022-07-17 11:46:05 +00:00
2022-07-24 11:00:05 +00:00
self . create_action ( " open_preset_directory " , self . open_preset_directory )
2022-08-10 08:46:52 +00:00
self . create_stateful_action (
" load_preset " ,
GLib . VariantType . new ( " s " ) ,
GLib . Variant ( " s " , " adwaita " ) ,
self . load_preset_action ,
)
2022-08-11 16:39:52 +00:00
self . create_action ( " apply_color_scheme " , self . show_apply_color_scheme_dialog )
self . create_action ( " reset_color_scheme " , self . show_reset_color_scheme_dialog )
2022-07-18 10:51:25 +00:00
self . create_action ( " save_preset " , self . show_save_preset_dialog )
2022-07-17 11:46:05 +00:00
self . create_action ( " about " , self . show_about_window )
2022-08-01 09:31:36 +00:00
self . reload_user_defined_presets ( )
2022-08-11 14:22:34 +00:00
self . style_manager = Adw . StyleManager . get_default ( )
2022-08-11 14:34:28 +00:00
if self . style_manager . get_dark ( ) :
2022-08-11 14:22:34 +00:00
self . load_preset_from_resource (
" /com/github/AdwCustomizerTeam/AdwCustomizer/presets/adwaita-dark.json "
)
else :
self . load_preset_from_resource (
" /com/github/AdwCustomizerTeam/AdwCustomizer/presets/adwaita.json "
)
2022-08-01 09:31:36 +00:00
2022-08-10 15:11:39 +00:00
self . win . present ( )
2022-08-01 09:31:36 +00:00
def reload_user_defined_presets ( self ) :
if self . props . active_window . presets_menu . get_n_items ( ) > 1 :
self . props . active_window . presets_menu . remove ( 1 )
2022-08-10 08:46:52 +00:00
preset_directory = os . path . join (
2022-08-11 16:39:52 +00:00
os . environ . get ( " XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config " ) ,
" presets " ,
)
2022-08-01 08:51:39 +00:00
if not os . path . exists ( preset_directory ) :
os . makedirs ( preset_directory )
2022-08-01 09:31:36 +00:00
self . custom_presets . clear ( )
2022-08-01 08:51:39 +00:00
for file_name in os . listdir ( preset_directory ) :
2022-07-25 06:11:02 +00:00
if file_name . endswith ( " .json " ) :
2022-07-18 11:27:31 +00:00
try :
2022-08-10 08:46:52 +00:00
with open (
os . path . join ( preset_directory , file_name ) , " r " , encoding = " utf-8 "
) as file :
2022-07-24 18:43:32 +00:00
preset_text = file . read ( )
2022-07-18 11:27:31 +00:00
preset = json . loads ( preset_text )
2022-08-10 08:46:52 +00:00
if preset . get ( " variables " ) is None :
raise KeyError ( " variables " )
if preset . get ( " palette " ) is None :
raise KeyError ( " palette " )
2022-08-11 16:39:52 +00:00
self . custom_presets [ file_name . replace ( " .json " , " " ) ] = preset [ " name " ]
2022-08-01 09:43:42 +00:00
except Exception :
2022-08-10 08:46:52 +00:00
self . global_errors . append (
{
" error " : _ ( " Failed to load preset " ) ,
" element " : file_name ,
" line " : traceback . format_exc ( ) . strip ( ) ,
}
)
2022-08-11 16:39:24 +00:00
self . win . toast_overlay . add_toast (
2022-08-11 16:39:52 +00:00
Adw . Toast ( title = _ ( " Failed to load preset " ) )
)
2022-08-10 15:05:47 +00:00
2022-07-21 19:03:18 +00:00
self . props . active_window . update_errors ( self . global_errors )
2022-07-18 11:27:31 +00:00
custom_menu_section = Gio . Menu ( )
2022-07-24 18:43:32 +00:00
for preset , preset_name in self . custom_presets . items ( ) :
2022-07-18 11:27:31 +00:00
menu_item = Gio . MenuItem ( )
2022-07-24 18:43:32 +00:00
menu_item . set_label ( preset_name )
2022-07-18 14:11:46 +00:00
if not preset . startswith ( " error " ) :
2022-08-10 08:46:52 +00:00
menu_item . set_action_and_target_value (
" app.load_preset " , GLib . Variant ( " s " , " custom- " + preset )
)
2022-07-18 14:11:46 +00:00
else :
menu_item . set_action_and_target_value ( " " )
2022-07-18 11:27:31 +00:00
custom_menu_section . append_item ( menu_item )
2022-07-24 11:00:05 +00:00
open_in_file_manager_item = Gio . MenuItem ( )
2022-07-25 14:35:40 +00:00
open_in_file_manager_item . set_label ( _ ( " Open in File Manager " ) )
2022-08-10 08:46:52 +00:00
open_in_file_manager_item . set_action_and_target_value (
" app.open_preset_directory "
)
2022-07-24 11:00:05 +00:00
custom_menu_section . append_item ( open_in_file_manager_item )
2022-08-10 08:46:52 +00:00
self . props . active_window . presets_menu . append_section (
_ ( " User Defined Presets " ) , custom_menu_section
)
2022-07-17 15:02:29 +00:00
2022-07-25 14:35:40 +00:00
def open_preset_directory ( self , * _args ) :
2022-07-24 11:00:05 +00:00
parent = XdpGtk4 . parent_new_gtk ( self . props . active_window )
2022-08-10 08:46:52 +00:00
2022-07-24 18:43:32 +00:00
def open_dir_callback ( _ , result ) :
self . portal . open_uri_finish ( result )
2022-08-10 08:46:52 +00:00
2022-07-24 11:00:05 +00:00
self . portal . open_uri (
parent ,
2022-08-11 16:39:52 +00:00
" file:// "
+ os . path . join (
os . environ . get ( " XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config " ) ,
" presets " ,
) ,
2022-07-24 11:00:05 +00:00
Xdp . OpenUriFlags . NONE ,
None ,
2022-08-10 08:46:52 +00:00
open_dir_callback ,
2022-07-24 11:00:05 +00:00
)
2022-07-17 19:27:57 +00:00
def load_preset_from_file ( self , preset_path ) :
preset_text = " "
2022-08-10 08:46:52 +00:00
with open ( preset_path , " r " , encoding = " utf-8 " ) as file :
2022-07-24 18:43:32 +00:00
preset_text = file . read ( )
2022-07-21 19:03:18 +00:00
self . load_preset_variables ( json . loads ( preset_text ) )
2022-07-17 19:27:57 +00:00
def load_preset_from_resource ( self , preset_path ) :
2022-08-11 16:39:52 +00:00
preset_text = Gio . resources_lookup_data ( preset_path , 0 ) . get_data ( ) . decode ( )
2022-07-21 19:03:18 +00:00
self . load_preset_variables ( json . loads ( preset_text ) )
2022-07-17 19:27:57 +00:00
2022-07-21 19:03:18 +00:00
def load_preset_variables ( self , preset ) :
2022-08-01 19:42:42 +00:00
self . is_ready = False
2022-08-01 17:09:45 +00:00
self . preset_name = preset [ " name " ]
2022-07-17 15:02:29 +00:00
self . variables = preset [ " variables " ]
2022-07-21 19:03:18 +00:00
self . palette = preset [ " palette " ]
2022-07-24 17:46:37 +00:00
if " custom_css " in preset :
self . custom_css = preset [ " custom_css " ]
2022-07-25 08:18:32 +00:00
else :
2022-07-26 13:31:16 +00:00
for app_type in settings_schema [ " custom_css_app_types " ] :
2022-07-25 08:18:32 +00:00
self . custom_css [ app_type ] = " "
2022-07-17 15:02:29 +00:00
for key in self . variables . keys ( ) :
2022-07-17 11:46:05 +00:00
if key in self . pref_variables :
2022-07-17 15:02:29 +00:00
self . pref_variables [ key ] . update_value ( self . variables [ key ] )
2022-07-21 19:03:18 +00:00
for key in self . palette . keys ( ) :
if key in self . pref_palette_shades :
self . pref_palette_shades [ key ] . update_shades ( self . palette [ key ] )
2022-07-24 17:46:37 +00:00
self . custom_css_group . load_custom_css ( self . custom_css )
2022-07-17 11:46:05 +00:00
2022-08-01 19:42:42 +00:00
self . clear_dirty ( )
2022-07-17 15:02:29 +00:00
self . reload_variables ( )
2022-07-17 11:46:05 +00:00
2022-08-11 16:39:24 +00:00
def rgba_from_argb ( self , argb , alpha = None ) - > str :
2022-08-11 13:39:31 +00:00
base = " rgba( {} , {} , {} , {} ) "
red = redFromArgb ( argb )
green = greenFromArgb ( argb )
blue = blueFromArgb ( argb )
if not alpha :
alpha = alphaFromArgb ( argb )
return base . format ( red , green , blue , alpha )
2022-08-11 13:00:07 +00:00
def update_theme_from_monet ( self , theme , tone , monet_theme ) :
2022-08-11 12:22:08 +00:00
palettes = theme [ " palettes " ]
2022-08-11 16:39:24 +00:00
monet_theme = monet_theme . get_string ( ) . lower ( ) # dark / light
2022-08-11 13:00:07 +00:00
2022-08-11 12:22:08 +00:00
palette = { }
i = 0
for color in palettes . values ( ) :
i + = 1
2022-08-11 12:23:31 +00:00
palette [ str ( i ) ] = hexFromArgb ( color . tone ( int ( tone . get_string ( ) ) ) )
2022-08-11 12:24:16 +00:00
self . pref_palette_shades [ " monet " ] . update_shades ( palette )
2022-08-11 14:37:03 +00:00
if monet_theme == " automatic " :
if self . style_manager . get_dark ( ) :
monet_theme = " dark "
else :
monet_theme = " light "
2022-08-11 16:39:24 +00:00
2022-08-11 13:39:31 +00:00
if monet_theme == " dark " :
2022-08-11 16:39:24 +00:00
dark_theme = theme [ " schemes " ] [ " dark " ]
2022-08-11 13:41:46 +00:00
variable = {
2022-08-11 14:06:55 +00:00
" accent_color " : self . rgba_from_argb ( dark_theme . primary ) ,
" accent_bg_color " : self . rgba_from_argb ( dark_theme . primaryContainer ) ,
" accent_fg_color " : self . rgba_from_argb ( dark_theme . onPrimaryContainer ) ,
" destructive_color " : self . rgba_from_argb ( dark_theme . error ) ,
" destructive_bg_color " : self . rgba_from_argb ( dark_theme . errorContainer ) ,
" destructive_fg_color " : self . rgba_from_argb ( dark_theme . onError ) ,
" success_color " : self . rgba_from_argb ( dark_theme . tertiary ) ,
" success_bg_color " : self . rgba_from_argb ( dark_theme . onTertiary ) ,
" success_fg_color " : self . rgba_from_argb ( dark_theme . tertiaryContainer ) ,
" warning_color " : self . rgba_from_argb ( dark_theme . secondaryContainer ) ,
" warning_bg_color " : self . rgba_from_argb ( dark_theme . inversePrimary ) ,
2022-08-11 14:03:34 +00:00
" warning_fg_color " : self . rgba_from_argb ( dark_theme . primary , " 0.8 " ) ,
2022-08-11 14:06:55 +00:00
" error_color " : self . rgba_from_argb ( dark_theme . error ) ,
" error_bg_color " : self . rgba_from_argb ( dark_theme . errorContainer ) ,
" error_fg_color " : self . rgba_from_argb ( dark_theme . onError ) ,
" window_bg_color " : self . rgba_from_argb ( dark_theme . surface ) ,
" window_fg_color " : self . rgba_from_argb ( dark_theme . onSurface ) ,
" view_bg_color " : self . rgba_from_argb ( dark_theme . surface ) ,
" view_fg_color " : self . rgba_from_argb ( dark_theme . onSurface ) ,
" headerbar_bg_color " : self . rgba_from_argb ( dark_theme . surface ) ,
" headerbar_fg_color " : self . rgba_from_argb ( dark_theme . onSurface ) ,
2022-08-11 16:39:52 +00:00
" headerbar_border_color " : self . rgba_from_argb (
dark_theme . primary , " 0.8 "
) ,
2022-08-11 13:39:31 +00:00
" headerbar_backdrop_color " : " @window_bg_color " ,
2022-08-11 14:06:55 +00:00
" headerbar_shade_color " : self . rgba_from_argb ( dark_theme . shadow ) ,
2022-08-11 14:03:34 +00:00
" card_bg_color " : self . rgba_from_argb ( dark_theme . primary , " 0.05 " ) ,
2022-08-12 09:38:35 +00:00
" card_fg_color " : self . rgba_from_argb ( dark_theme . onSurface ) ,
2022-08-11 14:06:55 +00:00
" card_shade_color " : self . rgba_from_argb ( dark_theme . shadow ) ,
" dialog_bg_color " : self . rgba_from_argb ( dark_theme . secondaryContainer ) ,
" dialog_fg_color " : self . rgba_from_argb ( dark_theme . onSecondaryContainer ) ,
" popover_bg_color " : self . rgba_from_argb ( dark_theme . secondaryContainer ) ,
2022-08-11 16:39:52 +00:00
" popover_fg_color " : self . rgba_from_argb (
dark_theme . onSecondaryContainer
) ,
2022-08-11 14:06:55 +00:00
" shade_color " : self . rgba_from_argb ( dark_theme . shadow ) ,
2022-08-11 16:39:52 +00:00
" scrollbar_outline_color " : self . rgba_from_argb ( dark_theme . outline ) ,
2022-08-11 13:39:31 +00:00
}
2022-08-11 16:39:24 +00:00
else : # light
light_theme = theme [ " schemes " ] [ " light " ]
2022-08-11 13:41:46 +00:00
variable = {
2022-08-11 14:39:34 +00:00
" accent_color " : self . rgba_from_argb ( light_theme . primary ) ,
" accent_bg_color " : self . rgba_from_argb ( light_theme . primaryContainer ) ,
" accent_fg_color " : self . rgba_from_argb ( light_theme . onPrimaryContainer ) ,
" destructive_color " : self . rgba_from_argb ( light_theme . error ) ,
" destructive_bg_color " : self . rgba_from_argb ( light_theme . errorContainer ) ,
" destructive_fg_color " : self . rgba_from_argb ( light_theme . onError ) ,
" success_color " : self . rgba_from_argb ( light_theme . tertiary ) ,
" success_bg_color " : self . rgba_from_argb ( light_theme . onTertiary ) ,
" success_fg_color " : self . rgba_from_argb ( light_theme . tertiaryContainer ) ,
" warning_color " : self . rgba_from_argb ( light_theme . secondaryContainer ) ,
" warning_bg_color " : self . rgba_from_argb ( light_theme . inversePrimary ) ,
" warning_fg_color " : self . rgba_from_argb ( light_theme . primary , " 0.8 " ) ,
" error_color " : self . rgba_from_argb ( light_theme . error ) ,
" error_bg_color " : self . rgba_from_argb ( light_theme . errorContainer ) ,
" error_fg_color " : self . rgba_from_argb ( light_theme . onError ) ,
" window_bg_color " : self . rgba_from_argb ( light_theme . surface ) ,
" window_fg_color " : self . rgba_from_argb ( light_theme . onSurface ) ,
" view_bg_color " : self . rgba_from_argb ( light_theme . surface ) ,
" view_fg_color " : self . rgba_from_argb ( light_theme . onSurface ) ,
" headerbar_bg_color " : self . rgba_from_argb ( light_theme . surface ) ,
" headerbar_fg_color " : self . rgba_from_argb ( light_theme . onSurface ) ,
2022-08-11 16:39:52 +00:00
" headerbar_border_color " : self . rgba_from_argb (
light_theme . primary , " 0.8 "
) ,
2022-08-11 13:39:31 +00:00
" headerbar_backdrop_color " : " @window_bg_color " ,
2022-08-11 14:39:34 +00:00
" headerbar_shade_color " : self . rgba_from_argb ( light_theme . shadow ) ,
" card_bg_color " : self . rgba_from_argb ( light_theme . primary , " 0.05 " ) ,
" card_fg_color " : self . rgba_from_argb ( light_theme . surfaceVariant ) ,
" card_shade_color " : self . rgba_from_argb ( light_theme . shadow ) ,
" dialog_bg_color " : self . rgba_from_argb ( light_theme . secondaryContainer ) ,
2022-08-11 16:39:52 +00:00
" dialog_fg_color " : self . rgba_from_argb (
light_theme . onSecondaryContainer
) ,
2022-08-11 14:39:34 +00:00
" popover_bg_color " : self . rgba_from_argb ( light_theme . secondaryContainer ) ,
2022-08-11 16:39:52 +00:00
" popover_fg_color " : self . rgba_from_argb (
light_theme . onSecondaryContainer
) ,
2022-08-11 14:39:34 +00:00
" shade_color " : self . rgba_from_argb ( light_theme . shadow ) ,
2022-08-11 16:39:52 +00:00
" scrollbar_outline_color " : self . rgba_from_argb ( light_theme . outline ) ,
2022-08-11 13:39:31 +00:00
}
2022-08-11 13:57:41 +00:00
for key in variable . keys ( ) :
2022-08-11 13:39:31 +00:00
if key in self . pref_variables :
2022-08-11 13:57:41 +00:00
self . pref_variables [ key ] . update_value ( variable [ key ] )
2022-08-11 16:39:24 +00:00
2022-08-11 13:44:12 +00:00
self . reload_variables ( )
2022-08-11 13:00:07 +00:00
2022-07-24 17:46:37 +00:00
def generate_gtk_css ( self , app_type ) :
2022-07-17 11:46:05 +00:00
final_css = " "
2022-07-24 17:46:37 +00:00
for key in self . variables . keys ( ) :
2022-07-29 12:04:18 +00:00
final_css + = f " @define-color { key } { self . variables [ key ] } ; \n "
2022-07-24 17:46:37 +00:00
for prefix_key in self . palette . keys ( ) :
for key in self . palette [ prefix_key ] . keys ( ) :
2022-07-29 12:04:18 +00:00
final_css + = f " @define-color { prefix_key + key } { self . palette [ prefix_key ] [ key ] } ; \n "
2022-07-24 17:46:37 +00:00
final_css + = self . custom_css . get ( app_type , " " )
2022-07-17 11:46:05 +00:00
return final_css
2022-08-01 19:42:42 +00:00
def mark_as_dirty ( self ) :
self . is_dirty = True
2022-08-10 13:05:54 +00:00
self . props . active_window . save_preset_button . get_child ( ) . set_icon_name (
2022-08-10 15:15:46 +00:00
" disk-unsaved-symbolic "
2022-08-10 13:05:54 +00:00
)
self . props . active_window . save_preset_button . get_child ( ) . set_tooltip_text (
2022-08-10 08:46:52 +00:00
_ ( " Unsaved changes " )
)
2022-08-01 19:42:42 +00:00
def clear_dirty ( self ) :
self . is_dirty = False
2022-08-10 15:15:46 +00:00
self . props . active_window . save_preset_button . get_child ( ) . set_icon_name (
" disk-saved-symbolic "
)
2022-08-01 19:42:42 +00:00
self . props . active_window . save_preset_button . get_child ( ) . set_label ( " " )
2022-07-17 15:02:29 +00:00
def reload_variables ( self ) :
2022-07-18 19:20:02 +00:00
parsing_errors = [ ]
2022-07-24 17:46:37 +00:00
gtk_css = self . generate_gtk_css ( " gtk4 " )
2022-07-17 11:46:05 +00:00
css_provider = Gtk . CssProvider ( )
2022-08-10 08:46:52 +00:00
2022-07-24 18:43:32 +00:00
def on_error ( _ , section , error ) :
2022-07-18 19:20:02 +00:00
start_location = section . get_start_location ( ) . chars
end_location = section . get_end_location ( ) . chars
line_number = section . get_end_location ( ) . lines
2022-08-10 08:46:52 +00:00
parsing_errors . append (
{
" error " : error . message ,
" element " : gtk_css [ start_location : end_location ] . strip ( ) ,
" line " : gtk_css . splitlines ( ) [ line_number ]
if line_number < len ( gtk_css . splitlines ( ) )
else " <last line> " ,
}
)
2022-07-21 19:03:18 +00:00
css_provider . connect ( " parsing-error " , on_error )
2022-07-22 15:10:52 +00:00
css_provider . load_from_data ( gtk_css . encode ( ) )
2022-08-11 16:39:52 +00:00
self . props . active_window . update_errors ( self . global_errors + parsing_errors )
2022-07-17 11:46:05 +00:00
# loading with the priority above user to override the applied config
2022-07-24 17:46:37 +00:00
if self . current_css_provider is not None :
2022-08-10 08:46:52 +00:00
Gtk . StyleContext . remove_provider_for_display (
Gdk . Display . get_default ( ) , self . current_css_provider
)
Gtk . StyleContext . add_provider_for_display (
Gdk . Display . get_default ( ) ,
css_provider ,
Gtk . STYLE_PROVIDER_PRIORITY_USER + 1 ,
)
2022-07-24 17:46:37 +00:00
self . current_css_provider = css_provider
2022-07-17 11:46:05 +00:00
2022-08-01 19:42:42 +00:00
self . is_ready = True
2022-07-25 14:35:40 +00:00
def load_preset_action ( self , _unused , * args ) :
2022-07-18 08:10:00 +00:00
if args [ 0 ] . get_string ( ) . startswith ( " custom- " ) :
2022-08-10 08:46:52 +00:00
self . load_preset_from_file (
os . path . join (
2022-08-11 16:39:52 +00:00
os . environ . get ( " XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config " ) ,
2022-08-10 08:46:52 +00:00
" presets " ,
args [ 0 ] . get_string ( ) . replace ( " custom- " , " " , 1 ) + " .json " ,
)
)
2022-07-17 19:27:57 +00:00
else :
2022-08-10 08:46:52 +00:00
self . load_preset_from_resource (
" /com/github/AdwCustomizerTeam/AdwCustomizer/presets/ "
+ args [ 0 ] . get_string ( )
+ " .json "
)
2022-07-17 19:27:57 +00:00
Gio . SimpleAction . set_state ( self . lookup_action ( " load_preset " ) , args [ 0 ] )
2022-07-17 11:46:05 +00:00
2022-07-25 14:35:40 +00:00
def show_apply_color_scheme_dialog ( self , * _args ) :
2022-08-10 08:46:52 +00:00
dialog = AdwcustomizerAppTypeDialog (
_ ( " Apply this color scheme? " ) ,
_ (
" Warning: any custom CSS files for those app types will be irreversibly overwritten! "
) ,
" apply " ,
_ ( " Apply " ) ,
Adw . ResponseAppearance . SUGGESTED ,
transient_for = self . props . active_window ,
)
2022-08-11 14:53:41 +00:00
2022-08-11 16:38:00 +00:00
box = Gtk . Box ( orientation = Gtk . Orientation . HORIZONTAL , spacing = 6 )
2022-08-11 14:53:41 +00:00
2022-08-11 16:38:00 +00:00
label = Gtk . Label ( label = " Apply as Dark theme " )
2022-08-11 16:31:44 +00:00
switch = Gtk . Switch ( )
2022-08-11 14:53:41 +00:00
2022-08-11 16:31:44 +00:00
box . append ( label )
box . append ( switch )
2022-08-11 16:24:37 +00:00
2022-08-11 16:31:44 +00:00
dialog . set_extra_child ( box )
dialog . connect ( " response " , self . apply_color_scheme , switch )
2022-07-17 16:24:07 +00:00
dialog . present ( )
2022-07-25 14:35:40 +00:00
def show_reset_color_scheme_dialog ( self , * _args ) :
2022-08-10 08:46:52 +00:00
dialog = AdwcustomizerAppTypeDialog (
_ ( " Reset applied color scheme? " ) ,
_ ( " Make sure you have the current settings saved as a preset. " ) ,
" reset " ,
_ ( " Reset " ) ,
Adw . ResponseAppearance . DESTRUCTIVE ,
transient_for = self . props . active_window ,
)
2022-07-22 15:10:52 +00:00
dialog . connect ( " response " , self . reset_color_scheme )
2022-07-17 16:24:07 +00:00
dialog . present ( )
2022-07-25 14:35:40 +00:00
def show_save_preset_dialog ( self , * _args ) :
2022-08-10 08:46:52 +00:00
dialog = Adw . MessageDialog (
transient_for = self . props . active_window ,
heading = _ ( " Save preset as... " ) ,
body = _ (
" Saving preset to <tt> {0} </tt>. If that preset already exists, it will be overwritten! "
) . format (
os . path . join (
2022-08-11 16:39:52 +00:00
os . environ . get ( " XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config " ) ,
2022-08-10 08:46:52 +00:00
" presets " ,
to_slug_case ( self . preset_name ) + " .json " ,
)
) ,
body_use_markup = True ,
)
2022-07-18 10:51:25 +00:00
2022-07-25 14:35:40 +00:00
dialog . add_response ( " cancel " , _ ( " Cancel " ) )
dialog . add_response ( " save " , _ ( " Save " ) )
2022-08-11 16:39:52 +00:00
dialog . set_response_appearance ( " save " , Adw . ResponseAppearance . SUGGESTED )
2022-07-18 10:51:25 +00:00
dialog . set_default_response ( " cancel " )
dialog . set_close_response ( " cancel " )
preset_entry = Gtk . Entry ( placeholder_text = " Preset Name " )
2022-08-01 17:09:45 +00:00
preset_entry . set_text ( self . preset_name )
2022-08-10 08:46:52 +00:00
2022-07-25 14:35:40 +00:00
def on_preset_entry_change ( * _args ) :
2022-07-18 10:51:25 +00:00
if len ( preset_entry . get_text ( ) ) == 0 :
2022-08-10 08:46:52 +00:00
dialog . set_body (
_ (
" Saving preset to <tt> {0} </tt>. If that preset already exists, it will be overwritten! "
2022-08-11 16:39:52 +00:00
) . format (
os . path . join (
os . environ . get (
" XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config "
) ,
" presets " ,
)
)
2022-08-10 08:46:52 +00:00
)
2022-07-18 10:51:25 +00:00
dialog . set_response_enabled ( " save " , False )
else :
2022-08-10 08:46:52 +00:00
dialog . set_body (
_ (
" Saving preset to <tt> {0} </tt>. If that preset already exists, it will be overwritten! "
) . format (
os . path . join (
2022-08-11 16:39:52 +00:00
os . environ . get (
" XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config "
) ,
2022-08-10 08:46:52 +00:00
" presets " ,
to_slug_case ( preset_entry . get_text ( ) ) + " .json " ,
)
)
)
2022-07-18 10:51:25 +00:00
dialog . set_response_enabled ( " save " , True )
2022-08-10 08:46:52 +00:00
2022-07-18 10:51:25 +00:00
preset_entry . connect ( " changed " , on_preset_entry_change )
dialog . set_extra_child ( preset_entry )
dialog . connect ( " response " , self . save_preset , preset_entry )
dialog . present ( )
2022-07-25 14:35:40 +00:00
def save_preset ( self , _unused , response , entry ) :
2022-07-18 10:51:25 +00:00
if response == " save " :
2022-08-10 08:46:52 +00:00
with open (
os . path . join (
2022-08-11 16:39:52 +00:00
os . environ . get ( " XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config " ) ,
2022-08-10 08:46:52 +00:00
" presets " ,
to_slug_case ( entry . get_text ( ) ) + " .json " ,
) ,
" w " ,
encoding = " utf-8 " ,
) as file :
2022-07-18 10:51:25 +00:00
object_to_write = {
" name " : entry . get_text ( ) ,
2022-07-21 19:03:18 +00:00
" variables " : self . variables ,
2022-07-24 17:46:37 +00:00
" palette " : self . palette ,
2022-08-10 08:46:52 +00:00
" custom_css " : self . custom_css ,
2022-07-18 10:51:25 +00:00
}
2022-07-24 18:43:32 +00:00
file . write ( json . dumps ( object_to_write , indent = 4 ) )
2022-08-01 19:42:42 +00:00
self . clear_dirty ( )
2022-08-11 16:39:24 +00:00
self . win . toast_overlay . add_toast (
2022-08-11 16:39:52 +00:00
Adw . Toast ( title = _ ( " Scheme successfully saved! " ) )
)
2022-07-18 10:51:25 +00:00
2022-08-11 16:31:44 +00:00
def apply_color_scheme ( self , widget , response , switch ) :
2022-07-17 16:24:07 +00:00
if response == " apply " :
2022-08-11 16:31:44 +00:00
if switch . get_active ( ) :
2022-08-11 14:53:41 +00:00
if widget . get_app_types ( ) [ " gtk4 " ] :
gtk4_dir = os . path . join (
2022-08-11 16:39:52 +00:00
os . environ . get (
" XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config "
) ,
" gtk-4.0 " ,
)
2022-08-11 14:53:41 +00:00
if not os . path . exists ( gtk4_dir ) :
os . makedirs ( gtk4_dir )
gtk4_css = self . generate_gtk_css ( " gtk4 " )
with open (
os . path . join ( gtk4_dir , " gtk-dark.css " ) , " w " , encoding = " utf-8 "
) as file :
file . write ( gtk4_css )
if widget . get_app_types ( ) [ " gtk3 " ] :
gtk3_dir = os . path . join (
2022-08-11 16:39:52 +00:00
os . environ . get (
" XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config "
) ,
" gtk-3.0 " ,
)
2022-08-11 14:53:41 +00:00
if not os . path . exists ( gtk3_dir ) :
os . makedirs ( gtk3_dir )
gtk3_css = self . generate_gtk_css ( " gtk3 " )
with open (
os . path . join ( gtk3_dir , " gtk-dark.css " ) , " w " , encoding = " utf-8 "
) as file :
file . write ( gtk3_css )
else :
if widget . get_app_types ( ) [ " gtk4 " ] :
gtk4_dir = os . path . join (
2022-08-11 16:39:52 +00:00
os . environ . get (
" XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config "
) ,
" gtk-4.0 " ,
)
2022-08-11 14:53:41 +00:00
if not os . path . exists ( gtk4_dir ) :
os . makedirs ( gtk4_dir )
gtk4_css = self . generate_gtk_css ( " gtk4 " )
with open (
os . path . join ( gtk4_dir , " gtk.css " ) , " w " , encoding = " utf-8 "
) as file :
file . write ( gtk4_css )
if widget . get_app_types ( ) [ " gtk3 " ] :
gtk3_dir = os . path . join (
2022-08-11 16:39:52 +00:00
os . environ . get (
" XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config "
) ,
" gtk-3.0 " ,
)
2022-08-11 14:53:41 +00:00
if not os . path . exists ( gtk3_dir ) :
os . makedirs ( gtk3_dir )
gtk3_css = self . generate_gtk_css ( " gtk3 " )
with open (
os . path . join ( gtk3_dir , " gtk.css " ) , " w " , encoding = " utf-8 "
) as file :
file . write ( gtk3_css )
2022-08-11 16:39:24 +00:00
self . win . toast_overlay . add_toast (
2022-08-11 16:39:52 +00:00
Adw . Toast ( title = _ ( " Scheme set successfully! " ) )
)
2022-07-17 16:24:07 +00:00
2022-07-22 15:10:52 +00:00
def reset_color_scheme ( self , widget , response ) :
2022-07-17 16:24:07 +00:00
if response == " reset " :
2022-07-22 12:29:32 +00:00
if widget . get_app_types ( ) [ " gtk4 " ] :
2022-08-10 08:46:52 +00:00
file = Gio . File . new_for_path (
os . path . join (
2022-08-11 16:39:52 +00:00
os . environ . get (
" XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config "
) ,
" gtk-4.0/gtk.css " ,
)
2022-08-10 08:46:52 +00:00
)
2022-07-22 12:29:32 +00:00
try :
file . delete ( )
2022-08-01 09:43:42 +00:00
except Exception :
2022-07-22 12:29:32 +00:00
pass
2022-08-11 16:39:24 +00:00
2022-07-22 12:29:32 +00:00
if widget . get_app_types ( ) [ " gtk3 " ] :
2022-08-10 08:46:52 +00:00
file = Gio . File . new_for_path (
os . path . join (
2022-08-11 16:39:52 +00:00
os . environ . get (
" XDG_CONFIG_HOME " , os . environ [ " HOME " ] + " /.config "
) ,
" gtk-3.0/gtk.css " ,
)
2022-08-10 08:46:52 +00:00
)
2022-07-22 12:29:32 +00:00
try :
file . delete ( )
2022-08-01 09:43:42 +00:00
except Exception :
2022-07-22 12:29:32 +00:00
pass
2022-08-11 16:39:52 +00:00
self . win . toast_overlay . add_toast ( Adw . Toast ( title = _ ( " Reset successfully! " ) ) )
2022-07-17 16:24:07 +00:00
2022-07-25 14:35:40 +00:00
def show_about_window ( self , * _args ) :
2022-08-10 08:46:52 +00:00
about = Adw . AboutWindow (
transient_for = self . props . active_window ,
application_name = _ ( " Adwaita Manager " ) ,
application_icon = " com.github.AdwCustomizerTeam.AdwCustomizer " ,
developer_name = _ ( " Adwaita Manager Team " ) ,
2022-08-10 12:18:38 +00:00
website = " https://github.com/AdwCustomizerTeam/AdwCustomizer " ,
support_url = " https://github.com/orgs/AdwCustomizerTeam/discussions " ,
issue_url = " https://github.com/AdwCustomizerTeam/AdwCustomizer/issues " ,
2022-08-10 08:46:52 +00:00
developers = [
' Artyom " ArtyIF " Fomin https://github.com/ArtyIF ' ,
2022-08-10 12:18:38 +00:00
" 0xMRTT https://github.com/0xMRTT " ,
2022-08-11 10:57:29 +00:00
" Verantor https://github.com/Verantor " ,
2022-08-10 12:18:38 +00:00
] ,
2022-08-11 16:39:52 +00:00
artists = [ ' David " Daudix UFO " Lapshin https://github.com/daudix-UFO ' ] ,
designers = [ ' David " Daudix UFO " Lapshin https://github.com/daudix-UFO ' ] ,
2022-08-10 08:46:52 +00:00
# Translators: This is a place to put your credits (formats: "Name https://example.com" or "Name <email@example.com>", no quotes) and is not meant to be translated literally.
2022-08-10 12:18:38 +00:00
translator_credits = """ Maxime V https://www.transifex.com/user/profile/Adaoh/
FineFindus https : / / github . com / FineFindus
Karol Lademan https : / / www . transifex . com / user / profile / karlod /
Monty Monteusz https : / / www . transifex . com / user / profile / MontyQIQI /
Renato Corrêa https : / / www . transifex . com / user / profile / renatocrrs /
Aggelos Tselios https : / / www . transifex . com / user / profile / AndroGR /
David " Daudix UFO " Lapshin https : / / github . com / daudix - UFO '
2022-08-11 10:57:29 +00:00
0 xMRTT https : / / github . com / 0 xMRTT
2022-08-10 12:18:38 +00:00
Juanjo Cillero https : / / www . transifex . com / user / profile / renux918 /
Taylan Tatlı https : / / www . transifex . com / user / profile / TaylanTatli34 / """ ,
2022-08-10 08:46:52 +00:00
copyright = " © 2022 Adwaita Manager Team " ,
2022-08-11 15:57:02 +00:00
license_type = Gtk . License . GPL_3_0 ,
2022-08-11 16:47:30 +00:00
version = f " { info . version } " ,
2022-08-12 09:49:29 +00:00
release_notes = """
< ul >
< li > Add AdwViewSwitcher in the header bar . < / li >
< li > Move CSS to the " Advanced " tab < / li >
< li > Move the rest to the " Colours " tab < / li >
< li > Add Monet tab which generates a theme from a background < / li >
< li > Add disk saved and disk unsaved icon in the header bar < / li >
< li > Update about dialog < / li >
< li > Change license to GNU GPLv3 < / li >
< li > Begin plugin support < / li >
< li > Move preset selector to a drop - down called palette ( icon palette ) < / li >
< li > Add ability to apply the theme onlyfor dark theme or oy for light theme < / li >
< li > Automaticly use Adwaita - dark preset if the user prefered scheme is dark . < / li >
< li > Added Flatpak CI build < / li >
< li > Added issue template for bug and feature request < / li >
< li > ` Main ` branch is now protected by GitHub branch protection . The development is done on ` next ` branch < / li >
< / ul >
""" ,
comments = """
Adwaita Manager ( AdwCustomizer ) is a tool for customizing Libadwaita applications and the adw - gtk3 theme .
2022-08-12 09:54:45 +00:00
With Adwaita Manager you can :
2022-08-12 09:55:46 +00:00
Change any color of Adwaita theme
Apply Material 3 colors from wallaper
Use other users presets
Change advanced options with CSS
Extend functionality using plugins
2022-08-12 09:54:45 +00:00
This app is written in Python and uses GTK 4 and libadwaita .
2022-08-12 09:49:29 +00:00
"""
2022-08-10 08:46:52 +00:00
)
2022-07-17 11:46:05 +00:00
about . present ( )
2022-07-24 17:46:37 +00:00
def update_custom_css_text ( self , app_type , new_value ) :
self . custom_css [ app_type ] = new_value
self . reload_variables ( )
2022-07-17 11:46:05 +00:00
def create_action ( self , name , callback , shortcuts = None ) :
""" Add an application action.
Args :
name : the name of the action
callback : the function to be called when the action is
activated
shortcuts : an optional list of accelerators
"""
action = Gio . SimpleAction . new ( name , None )
action . connect ( " activate " , callback )
self . add_action ( action )
if shortcuts :
self . set_accels_for_action ( f " app. { name } " , shortcuts )
2022-08-10 08:46:52 +00:00
def create_stateful_action (
self , name , parameter_type , initial_state , callback , shortcuts = None
) :
""" Add a stateful application action. """
2022-08-11 16:39:52 +00:00
action = Gio . SimpleAction . new_stateful ( name , parameter_type , initial_state )
2022-07-17 19:27:57 +00:00
action . connect ( " activate " , callback )
self . add_action ( action )
if shortcuts :
self . set_accels_for_action ( f " app. { name } " , shortcuts )
2022-07-17 11:46:05 +00:00
def main ( version ) :
""" The application ' s entry point. """
2022-07-20 10:18:55 +00:00
app = AdwcustomizerApplication ( version )
2022-07-17 11:46:05 +00:00
return app . run ( sys . argv )