@@ -196,6 +196,87 @@ def build_nsis(srcname, dstname, data):
196196 print ("Execution failed:" , e , file = sys .stderr )
197197 os .remove (dstname )
198198
199+ def checkPath (path , mode ):
200+ """ from https://gist.github.com/flyx/2965682 """
201+ import os , os .path
202+ if not os .path .exists (path ) or not os .path .isfile (path ):
203+ raise ValueError ("{0} does not exist or isn't a file." .format (path ))
204+ if not os .access (path , mode ):
205+ raise ValueError ("Insufficient permissions: {0}" .format (path ))
206+
207+ def updateExecutableIcon (executablePath , iconPath ):
208+ """ from https://gist.github.com/flyx/2965682 """
209+ import win32api , win32con
210+ import struct
211+ import math
212+ """
213+ Updates the icon of a Windows executable file.
214+ """
215+
216+ checkPath (executablePath , os .W_OK )
217+ checkPath (iconPath , os .R_OK )
218+
219+ handle = win32api .BeginUpdateResource (executablePath , False )
220+
221+ icon = open (iconPath , "rb" )
222+
223+ fileheader = icon .read (6 )
224+
225+ # Read icon data
226+ image_type , image_count = struct .unpack ("xxHH" , fileheader )
227+ print ("Icon file has type {0} and contains {1} images." .format (image_type , image_count ))
228+
229+ icon_group_desc = struct .pack ("<HHH" , 0 , image_type , image_count )
230+ icon_sizes = []
231+ icon_offsets = []
232+
233+ # Read data of all included icons
234+ for i in range (1 , image_count + 1 ):
235+ imageheader = icon .read (16 )
236+ width , height , colors , panes , bits_per_pixel , image_size , offset = \
237+ struct .unpack ("BBBxHHLL" , imageheader )
238+ print ("Image is {0}x{1}, has {2} colors in the palette, {3} planes, {4} bits per pixel." .format (
239+ width , height , colors , panes , bits_per_pixel ));
240+ print ("Image size is {0}, the image content has an offset of {1}" .format (image_size , offset ));
241+
242+ icon_group_desc = icon_group_desc + struct .pack ("<BBBBHHIH" ,
243+ width , # Icon width
244+ height , # Icon height
245+ colors , # Colors (0 for 256 colors)
246+ 0 , # Reserved2 (must be 0)
247+ panes , # Color planes
248+ bits_per_pixel , # Bits per pixel
249+ image_size , # ImageSize
250+ i # Resource ID
251+ )
252+ icon_sizes .append (image_size )
253+ icon_offsets .append (offset )
254+
255+ # Read icon content and write it to executable file
256+ for i in range (1 , image_count + 1 ):
257+ icon_content = icon .read (icon_sizes [i - 1 ])
258+ print ("Read {0} bytes for image #{1}" .format (len (icon_content ), i ))
259+ win32api .UpdateResource (handle , win32con .RT_ICON , i , icon_content )
260+
261+ win32api .UpdateResource (handle , win32con .RT_GROUP_ICON , "MAINICON" , icon_group_desc )
262+
263+ win32api .EndUpdateResource (handle , False )
264+
265+
266+ def build_shimmy_launcher (launcher_name , command , icon_path ):
267+ """Build shimmy script"""
268+ # access to mkshim.py
269+ # define where is mkshim
270+ mkshim_program = str (Path (__file__ ).resolve ().parent / "mkshim.py" )
271+ python_program = utils .get_python_executable ()
272+
273+ # Create the executable using mkshim
274+ mkshim_command = f'{ python_program } "{ mkshim_program } " -f "{ launcher_name } " -c "{ command } "'
275+ print ("zzzz Building shimmy:" , mkshim_command )
276+ subprocess .run (mkshim_command , shell = True )
277+
278+ # Embed the icon pywin32
279+ updateExecutableIcon (launcher_name , icon_path )
199280
200281def build_iss (srcname , dstname , data ):
201282 """Build Inno Setup Script"""
@@ -511,6 +592,48 @@ def create_batch_script(self, name, contents, do_changes=None):
511592 fd .write (contents_final )
512593 fd .close ()
513594
595+ def create_launcher_shimmy (
596+ self ,
597+ name ,
598+ icon ,
599+ command = None ,
600+ args = None ,
601+ workdir = r"$EXEDIR\scripts" ,
602+ launcher = "launcher_basic.nsi" ,
603+ ):
604+ """Create exe launcher with NSIS"""
605+ assert name .endswith (".exe" )
606+ portable_dir = str (Path (__file__ ).resolve ().parent / "portable" )
607+ icon_fname = str (Path (portable_dir ) / "icons" / icon )
608+ assert Path (icon_fname ).is_file ()
609+
610+ # Customizing NSIS script
611+ if command is None :
612+ if args is not None and ".pyw" in args :
613+ command = "${WINPYDIR}\pythonw.exe"
614+ else :
615+ command = "${WINPYDIR}\python.exe"
616+ if args is None :
617+ args = ""
618+ if workdir is None :
619+ workdir = ""
620+ fname = str (Path (self .winpydir ) / (Path (name ).stem + ".nsi" ))
621+
622+ data = [
623+ ("WINPYDIR" , f"$EXEDIR\{ self .python_name } " ),
624+ ("WINPYVER" , self .winpyver ),
625+ ("COMMAND" , command ),
626+ ("PARAMETERS" , args ),
627+ ("WORKDIR" , workdir ),
628+ ("Icon" , icon_fname ),
629+ ("OutFile" , name ),
630+ ]
631+ iconlauncherfullname = str (Path (self .winpydir ) / name )
632+
633+ print ("yyyy Buildin shimmy:" , iconlauncherfullname , command , icon_fname )
634+ true_command = command .replace (r"$SYSDIR\cmd.exe" ,"cmd.exe" )+ " " + args
635+ build_shimmy_launcher (iconlauncherfullname , true_command , icon_fname )
636+
514637 def create_launcher (
515638 self ,
516639 name ,
@@ -718,31 +841,55 @@ def _create_launchers(self):
718841 """Create launchers"""
719842
720843 self ._print ("Creating launchers" )
721- self .create_launcher (
844+
845+ self .create_launcher_shimmy (
722846 "WinPython Command Prompt.exe" ,
723847 "cmd.ico" ,
724- command = "$SYSDIR\cmd.exe" ,
725- args = r"/k cmd.bat" ,
848+ #command="$SYSDIR\cmd.exe",
849+ #args=r"/k cmd.bat",
850+ command = "scripts\\ cmd.bat" ,
851+ args = r"" ,
726852 )
853+
727854 self .create_launcher (
728855 "WinPython Powershell Prompt.exe" ,
729856 "powershell.ico" ,
730857 command = "$SYSDIR\cmd.exe" ,
731858 args = r"/k cmd_ps.bat" ,
732859 )
733860
861+ #not yet: workdirectory = icon directory
862+ # self.create_launcher_shimmy(
863+ # "WinPython Powershell PromptShimmy.exe",
864+ # "powershell.ico",
865+ # #command="$SYSDIR\cmd.exe",
866+ # #args=r"/k scripts\\cmd_ps.bat",
867+ # command="scripts\\cmd_ps.bat",
868+ # args=r"",
869+ #)
870+
734871 self .create_launcher (
735872 "WinPython Terminal.exe" ,
736873 "terminal.ico" ,
737874 command = "wscript.exe" ,
738875 args = r"Noshell.vbs WinPython_Terminal.bat" ,
739876 )
740877
741- self .create_launcher (
878+ #not yet: workdirectory = icon directory
879+ #self.create_launcher_shimmy(
880+ # "WinPython TerminalShimmy.exe",
881+ # "terminal.ico",
882+ # command="wscript.exe",
883+ # args=r"scripts\\Noshell.vbs scripts\\WinPython_Terminal.bat",
884+ #)
885+
886+ self .create_launcher_shimmy (
742887 "WinPython Interpreter.exe" ,
743888 "python.ico" ,
744- command = "$SYSDIR\cmd.exe" ,
745- args = r"/k winpython.bat" ,
889+ #command="$SYSDIR\cmd.exe",
890+ #args=r"/k scripts\\winpython.bat",
891+ command = "scripts\\ winpython.bat" ,
892+ args = r"" ,
746893 )
747894
748895 self .create_launcher (
@@ -752,32 +899,60 @@ def _create_launchers(self):
752899 args = r"Noshell.vbs winidle.bat" ,
753900 )
754901
902+ #not yet: dos window behind
903+ #self.create_launcher_shimmy(
904+ # "IDLE (Python GUI)Shimmy.exe",
905+ # "python.ico",
906+ # command="wscript.exe",
907+ # args=r"scripts\\Noshell.vbs scripts\\winidle.bat",
908+ #command="scripts\\Noshell.vbs scripts\\winidle.bat",
909+ #args=r"",
910+ #)
911+
755912 self .create_launcher (
756913 "Spyder.exe" ,
757914 "spyder.ico" ,
758915 command = "wscript.exe" ,
759916 args = r"Noshell.vbs winspyder.bat" ,
760917 )
761918
919+ #not yet
920+ #self.create_launcher_shimmy(
921+ # "SpyderShimmy.exe",
922+ # "spyder.ico",
923+ # command="wscript.exe",
924+ # args=r"scripts\\Noshell.vbs scripts\\winspyder.bat",
925+ #)
926+
762927 self .create_launcher (
763928 "Spyder reset.exe" ,
764929 "spyder_reset.ico" ,
765930 command = "wscript.exe" ,
766- args = r"Noshell.vbs spyder_reset.bat" ,
931+ args = r"scripts\\ Noshell.vbs scripts\\ spyder_reset.bat" ,
767932 )
768933
769- self .create_launcher (
934+ #not yet
935+ #self.create_launcher_shimmy(
936+ # "Spyder reset.exe",
937+ # "spyder_reset.ico",
938+ # command="wscript.exe",
939+ # args=r"scripts\\Noshell.vbs scripts\\spyder_reset.bat",
940+ #)
941+
942+
943+ self .create_launcher_shimmy (
770944 "WinPython Control Panel.exe" ,
771945 "winpython.ico" ,
772946 # command="wscript.exe",
773947 # args=r"Noshell.vbs wpcp.bat",
774- command = "$SYSDIR\cmd.exe" ,
775- args = r"/k wpcp.bat" ,
948+ #command="$SYSDIR\cmd.exe",
949+ #args=r"/k scripts\\wpcp.bat",
950+ command = "scripts\\ wpcp.bat" ,
951+ args = r"" ,
776952 )
777953
778954 # Jupyter launchers
779955
780- # removing another Qt string
781956 # self.create_launcher(
782957 # "IPython Qt Console.exe",
783958 # "ipython.ico",
@@ -802,11 +977,11 @@ def _create_launchers(self):
802977 )
803978
804979 # VSCode launcher
805- self .create_launcher (
806- "VS Code .exe" ,
980+ self .create_launcher_shimmy (
981+ "VS CodeShimmy .exe" ,
807982 "code.ico" ,
808983 command = "wscript.exe" ,
809- args = r"Noshell.vbs winvscode.bat" ,
984+ args = r"scripts\\ Noshell.vbs scripts\\ winvscode.bat" ,
810985 )
811986
812987 self ._print_done ()
@@ -1203,8 +1378,8 @@ def _create_batch_scripts_initial(self):
12031378 )
12041379 )
12051380) else (
1206- rem if it is launched from another directory , we keep it that one echo %__CD__%
1207- if not "%__CD__%"=="%~dp0" set WINPYWORKDIR1="%__CD__%"
1381+ rem if it is launched from another directory than icon origin , we keep it that one echo %__CD__%
1382+ if not "%__CD__%"=="%~dp0" if not "%__CD__%scripts\"=="%~dp0" set WINPYWORKDIR1="%__CD__%"
12081383)
12091384rem remove potential doublequote
12101385set WINPYWORKDIR1=%WINPYWORKDIR1:"=%
@@ -1214,6 +1389,13 @@ def _create_batch_scripts_initial(self):
12141389FOR /F "delims=" %%i IN ('cscript /nologo "%~dp0WinpythonIni.vbs"') DO set winpythontoexec=%%i
12151390%winpythontoexec%set winpythontoexec=
12161391
1392+ rem 2024-08-18: we go initial directory WINPYWORKDIR if no direction and we are on icon directory
1393+ rem old NSIS launcher is by default at icon\scripts level
1394+ if "%__CD__%scripts\"=="%~dp0" if "%WINPYWORKDIR1%"=="%WINPYDIRBASE%\Notebooks" cd/D %WINPYWORKDIR1%
1395+ rem new shimmy launcher is by default at icon level
1396+ if "%__CD__%"=="%~dp0" if "%WINPYWORKDIR1%"=="%WINPYDIRBASE%\Notebooks" cd/D %WINPYWORKDIR1%
1397+
1398+
12171399rem ******************
12181400rem missing student directory part
12191401rem ******************
0 commit comments