splash_templates.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. # -----------------------------------------------------------------------------
  2. # Copyright (c) 2005-2023, PyInstaller Development Team.
  3. #
  4. # Distributed under the terms of the GNU General Public License (version 2
  5. # or later) with exception for distributing the bootloader.
  6. #
  7. # The full license is in the file COPYING.txt, distributed with this software.
  8. #
  9. # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
  10. # -----------------------------------------------------------------------------
  11. """
  12. Templates for the splash screen tcl script.
  13. """
  14. from PyInstaller.compat import is_cygwin, is_darwin, is_win
  15. ipc_script = r"""
  16. proc _ipc_server {channel clientaddr clientport} {
  17. # This function is called if a new client connects to
  18. # the server. This creates a channel, which calls
  19. # _ipc_caller if data was send through the connection
  20. set client_name [format <%s:%d> $clientaddr $clientport]
  21. chan configure $channel \
  22. -buffering none \
  23. -encoding utf-8 \
  24. -eofchar \x04 \
  25. -translation cr
  26. chan event $channel readable [list _ipc_caller $channel $client_name]
  27. }
  28. proc _ipc_caller {channel client_name} {
  29. # This function is called if a command was sent through
  30. # the tcp connection. The current implementation supports
  31. # two commands: update_text and exit, although exit
  32. # is implemented to be called if the connection gets
  33. # closed (from python) or the character 0x04 was received
  34. chan gets $channel cmd
  35. if {[chan eof $channel]} {
  36. # This is entered if either the connection was closed
  37. # or the char 0x04 was send
  38. chan close $channel
  39. exit
  40. } elseif {![chan blocked $channel]} {
  41. # RPC methods
  42. # update_text command
  43. if {[string match "update_text*" $cmd]} {
  44. global status_text
  45. set first [expr {[string first "(" $cmd] + 1}]
  46. set last [expr {[string last ")" $cmd] - 1}]
  47. set status_text [string range $cmd $first $last]
  48. }
  49. # Implement other procedures here
  50. }
  51. }
  52. # By setting the port to 0 the os will assign a free port
  53. set server_socket [socket -server _ipc_server -myaddr localhost 0]
  54. set server_port [fconfigure $server_socket -sockname]
  55. # This environment variable is shared between the python and the tcl
  56. # interpreter and publishes the port the tcp server socket is available
  57. set env(_PYI_SPLASH_IPC) [lindex $server_port 2]
  58. """
  59. image_script = r"""
  60. # The variable $_image_data, which holds the data for the splash
  61. # image is created by the bootloader.
  62. image create photo splash_image
  63. splash_image put $_image_data
  64. # delete the variable, because the image now holds the data
  65. unset _image_data
  66. proc canvas_text_update {canvas tag _var - -} {
  67. # This function is rigged to be called if the a variable
  68. # status_text gets changed. This updates the text on
  69. # the canvas
  70. upvar $_var var
  71. $canvas itemconfigure $tag -text $var
  72. }
  73. """
  74. splash_canvas_setup = r"""
  75. package require Tk
  76. set image_width [image width splash_image]
  77. set image_height [image height splash_image]
  78. # If bootloader set $_pyi_screen_geometry array (advanced splash-screen
  79. # centering modes that are available on some platforms), use values from it.
  80. # Otherwise, try to center splash screen using information obtained via
  81. # winfo screenwidth and screenheight command.
  82. if {[info exists _pyi_screen_geometry]} {
  83. set display_x $_pyi_screen_geometry(x)
  84. set display_y $_pyi_screen_geometry(y)
  85. set display_width $_pyi_screen_geometry(width)
  86. set display_height $_pyi_screen_geometry(height)
  87. } else {
  88. set display_x 0
  89. set display_y 0
  90. set display_width [winfo screenwidth .]
  91. set display_height [winfo screenheight .]
  92. }
  93. set x_position [expr {int($display_x + 0.5*($display_width - $image_width))}]
  94. set y_position [expr {int($display_y + 0.5*($display_height - $image_height))}]
  95. # Toplevel frame in which all widgets should be positioned
  96. frame .root
  97. # Configure the canvas on which the splash
  98. # screen will be drawn
  99. canvas .root.canvas \
  100. -width $image_width \
  101. -height $image_height \
  102. -borderwidth 0 \
  103. -highlightthickness 0
  104. # Draw the image into the canvas, filling it.
  105. .root.canvas create image \
  106. [expr {$image_width / 2}] \
  107. [expr {$image_height / 2}] \
  108. -image splash_image
  109. """
  110. splash_canvas_text = r"""
  111. # Create a text on the canvas, which tracks the local
  112. # variable status_text. status_text is changed via C to
  113. # update the progress on the splash screen.
  114. # We cannot use the default label, because it has a
  115. # default background, which cannot be turned transparent
  116. .root.canvas create text \
  117. %(pad_x)d \
  118. %(pad_y)d \
  119. -fill %(color)s \
  120. -justify center \
  121. -font myFont \
  122. -tag vartext \
  123. -anchor sw
  124. trace add variable status_text write \
  125. [list canvas_text_update .root.canvas vartext]
  126. set status_text "%(default_text)s"
  127. """
  128. splash_canvas_default_font = r"""
  129. font create myFont {*}[font actual TkDefaultFont]
  130. font configure myFont -size %(font_size)d
  131. """
  132. splash_canvas_custom_font = r"""
  133. font create myFont -family %(font)s -size %(font_size)d
  134. """
  135. if is_win or is_cygwin:
  136. transparent_setup = r"""
  137. # If the image is transparent, the background will be filled
  138. # with magenta. The magenta background is later replaced with transparency.
  139. # Here is the limitation of this implementation, that only
  140. # sharp transparent image corners are possible
  141. wm attributes . -transparentcolor magenta
  142. .root.canvas configure -background magenta
  143. """
  144. elif is_darwin:
  145. # This is untested, but should work following: https://stackoverflow.com/a/44296157/5869139
  146. transparent_setup = r"""
  147. wm attributes . -transparent 1
  148. . configure -background systemTransparent
  149. .root.canvas configure -background systemTransparent
  150. """
  151. else:
  152. # For Linux there is no common way to create a transparent window
  153. transparent_setup = r""
  154. pack_widgets = r"""
  155. # Position all widgets in the window
  156. pack .root
  157. grid .root.canvas -column 0 -row 0 -columnspan 1 -rowspan 2
  158. """
  159. # Enable always-on-top behavior, by setting overrideredirect and the topmost attribute.
  160. position_window_on_top = r"""
  161. # Set position and mode of the window - always-on-top behavior
  162. wm overrideredirect . 1
  163. wm geometry . +${x_position}+${y_position}
  164. wm attributes . -topmost 1
  165. """
  166. # Disable always-on-top behavior
  167. if is_win or is_cygwin or is_darwin:
  168. # On Windows, we disable the always-on-top behavior while still setting overrideredirect
  169. # (to disable window decorations), but set topmost attribute to 0.
  170. position_window = r"""
  171. # Set position and mode of the window
  172. wm overrideredirect . 1
  173. wm geometry . +${x_position}+${y_position}
  174. wm attributes . -topmost 0
  175. """
  176. else:
  177. # On Linux, we must not use overrideredirect; instead, we set X11-specific type attribute to splash,
  178. # which lets the window manager to properly handle the splash screen (without window decorations
  179. # but allowing other windows to be brought to front).
  180. position_window = r"""
  181. # Set position and mode of the window
  182. wm geometry . +${x_position}+${y_position}
  183. wm attributes . -type splash
  184. """
  185. raise_window = r"""
  186. raise .
  187. """
  188. def build_script(text_options=None, always_on_top=False):
  189. """
  190. This function builds the tcl script for the splash screen.
  191. """
  192. # Order is important!
  193. script = [
  194. ipc_script,
  195. image_script,
  196. splash_canvas_setup,
  197. ]
  198. if text_options:
  199. # If the default font is used we need a different syntax
  200. if text_options['font'] == "TkDefaultFont":
  201. script.append(splash_canvas_default_font % text_options)
  202. else:
  203. script.append(splash_canvas_custom_font % text_options)
  204. script.append(splash_canvas_text % text_options)
  205. script.append(transparent_setup)
  206. script.append(pack_widgets)
  207. script.append(position_window_on_top if always_on_top else position_window)
  208. script.append(raise_window)
  209. return '\n'.join(script)