3636import babel
3737from datetime import datetime , timezone
3838from library .smartmonitor_compile import compile_theme_file
39+ from library .smartmonitor_classic_theme_convert import convert_classic_theme_to_smartmonitor_project
3940from library .smartmonitor_ui import encode_ui_file
4041
4142try :
5253 from tktooltip import ToolTip
5354except Exception as e :
5455 print ("""Import error: %s
55- Please see README.md / README_RU.md in the repository root for installation steps.
56- If the GUI still does not start, check repository issues or discussions. """ % str (
56+ Please follow start guide to install required packages: https://github.com/mathoudebine/turing-smart-screen-python/wiki/System-monitor-:-how-to-start
57+ Or the troubleshooting page: https://github.com/mathoudebine/turing-smart-screen-python/wiki/Troubleshooting#all-os-tkinter-dependency-not-installed """ % str (
5758 e ))
5859 try :
5960 sys .exit (0 )
137138DEFAULT_SMARTMONITOR_VENDOR_THEME_ROOT = (
138139 os .path .join (MAIN_DIRECTORY , "vendor" , "themefor3.5" )
139140 if os .path .isdir (os .path .join (MAIN_DIRECTORY , "vendor" , "themefor3.5" ))
140- else os . path . join ( MAIN_DIRECTORY , "vendor" , " themefor3.5")
141+ else "/tmp/smartmonitor_unpacked/app/ themefor3.5"
141142)
142143DEFAULT_SMARTMONITOR_PROJECTS_DIR = os .path .join (MAIN_DIRECTORY , "res" , "smartmonitor" , "projects" )
143144
@@ -350,6 +351,10 @@ def autostart_service_path() -> Path:
350351 return autostart_service_dir () / AUTOSTART_SERVICE_NAME
351352
352353
354+ def autostart_service_template_path () -> Path :
355+ return Path (MAIN_DIRECTORY ) / "tools" / AUTOSTART_SERVICE_NAME
356+
357+
353358def render_autostart_service () -> str :
354359 python_exec = Path (sys .executable ).resolve ()
355360 main_script = Path (MAIN_DIRECTORY ) / "main.py"
@@ -488,6 +493,43 @@ def compile_and_import_smartmonitor_ui(theme_name: str, ui_path: str):
488493 return target_img
489494
490495
496+ def compile_and_import_classic_theme (theme_name : str , classic_theme_path : str ):
497+ project_root = Path (DEFAULT_SMARTMONITOR_PROJECTS_DIR ) / "classic-converted"
498+ result = convert_classic_theme_to_smartmonitor_project (
499+ classic_theme_path ,
500+ project_root ,
501+ project_name = theme_name ,
502+ )
503+ compiled = compile_theme_file (result .ui_path )
504+ theme_dir = Path (smartmonitor_theme_dir (theme_name ))
505+ os .makedirs (theme_dir , exist_ok = True )
506+ target_img = smartmonitor_theme_img (theme_name )
507+ with open (target_img , "wb" ) as stream :
508+ stream .write (compiled )
509+ shutil .copy2 (target_img , smartmonitor_bundled_dat_path (theme_name ))
510+
511+ editable_project_dir = theme_dir / "source"
512+ if editable_project_dir .exists ():
513+ shutil .rmtree (editable_project_dir )
514+ shutil .copytree (result .output_dir , editable_project_dir )
515+ editable_ui_path = editable_project_dir / result .ui_path .name
516+
517+ write_smartmonitor_theme_metadata (
518+ theme_name ,
519+ {
520+ "name" : theme_name ,
521+ "source_theme" : str (Path (classic_theme_path ).resolve ()),
522+ "source_ui" : str (editable_ui_path .resolve ()),
523+ "source_project" : str (editable_project_dir .resolve ()),
524+ "compiled_at" : datetime .now (timezone .utc ).isoformat (),
525+ "compiler" : "classic_theme_to_imgdat" ,
526+ "size" : os .path .getsize (target_img ),
527+ "sha256" : compute_file_sha256 (target_img ),
528+ },
529+ )
530+ return target_img , result
531+
532+
491533def create_smartmonitor_ui_project (project_dir : str , project_name : str ):
492534 project_path = Path (project_dir )
493535 images_dir = project_path / "images"
@@ -697,7 +739,7 @@ class TuringConfigWindow:
697739 def __init__ (self ):
698740 self .window = Tk ()
699741 self .window .title ('Turing System Monitor configuration' )
700- self .window .geometry ("940x580 " )
742+ self .window .geometry ("940x640 " )
701743 self .window .iconphoto (True , PhotoImage (file = MAIN_DIRECTORY + "res/icons/monitor-icon-17865/64.png" ))
702744 # When window gets focus again, reload theme preview in case it has been updated by theme editor
703745 self .window .bind ("<FocusIn>" , self .on_theme_change )
@@ -795,57 +837,57 @@ def __init__(self):
795837 "Fans missing from the list? Install lm-sensors package\n "
796838 "and run 'sudo sensors-detect' command, then reboot." )
797839
798- self .weather_ping_btn = ttk .Button (self .window , text = "Weather & ping " ,
840+ self .weather_ping_btn = ttk .Button (self .window , text = "Weather" ,
799841 command = lambda : self .on_weatherping_click ())
800- self .weather_ping_btn .place (x = 20 , y = 520 , height = 50 , width = 100 )
842+ self .weather_ping_btn .place (x = 20 , y = 505 , height = 42 , width = 105 )
801843
802- self .open_theme_folder_btn = ttk .Button (self .window , text = "Open themes \n folder " ,
844+ self .open_theme_folder_btn = ttk .Button (self .window , text = "Themes " ,
803845 command = lambda : self .on_open_theme_folder_click ())
804- self .open_theme_folder_btn .place (x = 130 , y = 520 , height = 50 , width = 100 )
846+ self .open_theme_folder_btn .place (x = 135 , y = 505 , height = 42 , width = 105 )
805847
806848 self .edit_theme_btn = ttk .Button (self .window , text = "Edit theme" , command = lambda : self .on_theme_editor_click ())
807- self .edit_theme_btn .place (x = 240 , y = 520 , height = 50 , width = 100 )
849+ self .edit_theme_btn .place (x = 250 , y = 505 , height = 42 , width = 105 )
808850
809851 self .edit_ui_btn = ttk .Button (
810852 self .window ,
811853 text = "Edit UI" ,
812854 command = lambda : self .on_edit_ui_click (),
813855 )
814- self .edit_ui_btn .place (x = 350 , y = 520 , height = 50 , width = 100 )
856+ self .edit_ui_btn .place (x = 365 , y = 505 , height = 42 , width = 105 )
815857
816858 self .convert_theme_btn = ttk .Button (
817859 self .window ,
818860 text = "Convert" ,
819861 command = lambda : self .on_theme_convert_click (),
820862 )
821- self .convert_theme_btn .place (x = 460 , y = 520 , height = 50 , width = 100 )
863+ self .convert_theme_btn .place (x = 480 , y = 505 , height = 42 , width = 105 )
822864
823865 self .new_ui_btn = ttk .Button (
824866 self .window ,
825867 text = "New UI" ,
826868 command = lambda : self .on_new_ui_project_click (),
827869 )
828- self .new_ui_btn .place (x = 570 , y = 520 , height = 50 , width = 100 )
870+ self .new_ui_btn .place (x = 595 , y = 505 , height = 42 , width = 105 )
829871
830872 self .autostart_enable_btn = ttk .Button (
831873 self .window ,
832- text = "Enable \n autostart " ,
874+ text = "Autostart ON " ,
833875 command = lambda : self .on_enable_autostart_click (),
834876 )
835- self .autostart_enable_btn .place (x = 680 , y = 520 , height = 50 , width = 100 )
877+ self .autostart_enable_btn .place (x = 20 , y = 555 , height = 42 , width = 120 )
836878
837879 self .autostart_disable_btn = ttk .Button (
838880 self .window ,
839- text = "Disable \n autostart " ,
881+ text = "Autostart OFF " ,
840882 command = lambda : self .on_disable_autostart_click (),
841883 )
842- self .autostart_disable_btn .place (x = 790 , y = 520 , height = 50 , width = 100 )
884+ self .autostart_disable_btn .place (x = 150 , y = 555 , height = 42 , width = 120 )
843885
844- self .save_btn = ttk .Button (self .window , text = "Save settings " , command = lambda : self .on_save_click ())
845- self .save_btn .place (x = 900 , y = 520 , height = 50 , width = 110 )
886+ self .save_btn = ttk .Button (self .window , text = "Save" , command = lambda : self .on_save_click ())
887+ self .save_btn .place (x = 280 , y = 555 , height = 42 , width = 120 )
846888
847- self .save_run_btn = ttk .Button (self .window , text = "Save and run" , command = lambda : self .on_saverun_click ())
848- self .save_run_btn .place (x = 1020 , y = 520 , height = 50 , width = 110 )
889+ self .save_run_btn = ttk .Button (self .window , text = "Save + run" , command = lambda : self .on_saverun_click ())
890+ self .save_run_btn .place (x = 410 , y = 555 , height = 42 , width = 120 )
849891
850892 self .config = None
851893 self .load_config_values ()
@@ -860,18 +902,18 @@ def refresh_theme_selector(self):
860902 if self .is_smartmonitor_model ():
861903 themes = get_smartmonitor_themes ()
862904 self .theme_label .config (text = 'SmartMonitor theme' )
863- self .open_theme_folder_btn .config (text = "Import vendor \n set " )
864- self .edit_theme_btn .config (text = "Import\n .dat" )
865- self .edit_ui_btn .config (text = "Open/ Edit\n UI " , state = "normal" )
866- self .convert_theme_btn .config (text = "Convert\n UI->DAT" , state = "normal " )
867- self .new_ui_btn .config (text = "New UI\n project " , state = "normal" )
905+ self .open_theme_folder_btn .config (text = "Vendor set " )
906+ self .edit_theme_btn .config (text = "Import .dat" )
907+ self .edit_ui_btn .config (text = "Edit UI " , state = "normal" )
908+ self .convert_theme_btn .config (text = "Convert" )
909+ self .new_ui_btn .config (text = "New UI" , state = "normal" )
868910 else :
869911 size = self .size_cb .get ().replace (SIZE_2_x_INCH , SIZE_2_1_INCH )
870912 themes = get_themes (size )
871913 self .theme_label .config (text = 'Theme' )
872- self .open_theme_folder_btn .config (text = "Open themes \n folder " )
914+ self .open_theme_folder_btn .config (text = "Themes " )
873915 self .edit_theme_btn .config (text = "Edit theme" )
874- self .edit_ui_btn .config (text = "Open/ Edit UI" , state = "disabled" )
916+ self .edit_ui_btn .config (text = "Edit UI" , state = "disabled" )
875917 self .convert_theme_btn .config (text = "Convert" , state = "disabled" )
876918 self .new_ui_btn .config (text = "New UI" , state = "disabled" )
877919
@@ -1114,7 +1156,7 @@ def on_theme_editor_click(self):
11141156
11151157 def on_theme_convert_click (self ):
11161158 if self .is_smartmonitor_model ():
1117- self .on_smartmonitor_ui_convert_click ()
1159+ self .on_smartmonitor_convert_click ()
11181160 return
11191161
11201162 def on_new_ui_project_click (self ):
@@ -1405,6 +1447,88 @@ def on_smartmonitor_ui_convert_click(self):
14051447 parent = self .window ,
14061448 )
14071449
1450+ def on_smartmonitor_classic_theme_convert_click (self ):
1451+ theme_dir = filedialog .askdirectory (
1452+ parent = self .window ,
1453+ title = "Choose classic theme directory to convert" ,
1454+ initialdir = THEMES_DIR ,
1455+ mustexist = True ,
1456+ )
1457+ if not theme_dir :
1458+ return
1459+
1460+ theme_yaml = Path (theme_dir ) / "theme.yaml"
1461+ if not theme_yaml .is_file ():
1462+ messagebox .showerror (
1463+ "Invalid classic theme" ,
1464+ "The selected directory does not contain theme.yaml" ,
1465+ parent = self .window ,
1466+ )
1467+ return
1468+
1469+ suggested_name = f"{ Path (theme_dir ).name } -smartmonitor"
1470+ theme_name = simpledialog .askstring (
1471+ "Convert classic theme" ,
1472+ "Theme name for the compiled .dat:" ,
1473+ parent = self .window ,
1474+ initialvalue = suggested_name ,
1475+ )
1476+ if theme_name is None :
1477+ return
1478+
1479+ theme_name = sanitize_smartmonitor_theme_name (theme_name )
1480+ if not theme_name :
1481+ messagebox .showerror ("Invalid name" , "Theme name cannot be empty." , parent = self .window )
1482+ return
1483+
1484+ target_img = smartmonitor_theme_img (theme_name )
1485+ if os .path .isfile (target_img ):
1486+ overwrite = messagebox .askyesno (
1487+ "Overwrite theme?" ,
1488+ f"A SmartMonitor theme named '{ theme_name } ' already exists.\n Overwrite it?" ,
1489+ parent = self .window ,
1490+ )
1491+ if not overwrite :
1492+ return
1493+
1494+ try :
1495+ _ , result = compile_and_import_classic_theme (theme_name , str (theme_yaml ))
1496+ except Exception as exc :
1497+ messagebox .showerror ("Classic theme conversion failed" , str (exc ), parent = self .window )
1498+ return
1499+
1500+ self .refresh_theme_selector ()
1501+ self .theme_cb .set (theme_name )
1502+ self .on_theme_change ()
1503+ skipped = ""
1504+ if result .skipped_items :
1505+ preview = ", " .join (result .skipped_items [:6 ])
1506+ skipped = f"\n \n Skipped unsupported classic items:\n { preview } "
1507+ if len (result .skipped_items ) > 6 :
1508+ skipped += ", ..."
1509+ messagebox .showinfo (
1510+ "Classic theme converted" ,
1511+ f"Theme '{ theme_name } ' was converted from a classic YAML theme to SmartMonitor .dat.\n "
1512+ f"Use Save or Save and run to activate it.{ skipped } " ,
1513+ parent = self .window ,
1514+ )
1515+
1516+ def on_smartmonitor_convert_click (self ):
1517+ source_kind = messagebox .askyesnocancel (
1518+ "Choose conversion source" ,
1519+ "What do you want to convert?\n \n "
1520+ "Yes: vendor SmartMonitor .ui -> .dat\n "
1521+ "No: classic YAML theme -> SmartMonitor .dat\n "
1522+ "Cancel: do nothing" ,
1523+ parent = self .window ,
1524+ )
1525+ if source_kind is None :
1526+ return
1527+ if source_kind :
1528+ self .on_smartmonitor_ui_convert_click ()
1529+ else :
1530+ self .on_smartmonitor_classic_theme_convert_click ()
1531+
14081532 def on_smartmonitor_new_ui_project_click (self ):
14091533 os .makedirs (DEFAULT_SMARTMONITOR_PROJECTS_DIR , exist_ok = True )
14101534 parent_dir = filedialog .askdirectory (
0 commit comments