Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/client/gtk3/splash_screen.py : 93%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2020 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
7import sys
8import signal
9from gi.repository import Gtk, Gdk, GLib, Pango
11from xpra import __version__
12from xpra.util import envint
13from xpra.os_util import SIGNAMES, OSX
14from xpra.exit_codes import EXIT_TIMEOUT
15from xpra.common import SPLASH_EXIT_DELAY
16from xpra.gtk_common.gtk_util import add_close_accel, get_icon_pixbuf
17from xpra.gtk_common.gobject_compat import install_signal_handlers
18from xpra.client.gtk_base.css_overrides import inject_css_overrides
19from xpra.platform.gui import force_focus
20from xpra.log import Logger
22log = Logger("client", "util")
24inject_css_overrides()
26TIMEOUT = envint("XPRA_SPLASH_TIMEOUT", 60)
27LINES = envint("XPRA_SPLASH_LINES", 4)
29#PULSE_CHARS = "▁▂▃▄▅▆▇█▇▆▅▄▃▁"
30PULSE_CHARS = "◐◓◑◒"
31if OSX:
32 DONE_CHAR = "-"
33else:
34 DONE_CHAR = "⬤"
36W = 400
39class SplashScreen(Gtk.Window):
41 def __init__(self):
42 self.exit_code = None
43 super().__init__(type=Gtk.WindowType.TOPLEVEL)
44 self.connect("delete_event", self.exit)
45 title = "Xpra %s" % __version__
46 self.set_title(title)
47 self.set_size_request(W, 40+40*LINES)
48 self.set_position(Gtk.WindowPosition.CENTER)
49 self.set_decorated(False)
50 self.set_opacity(0.9)
51 self.set_accept_focus(False)
52 self.set_focus_on_map(False)
53 self.set_skip_pager_hint(True)
54 self.set_skip_taskbar_hint(True)
55 self.set_type_hint(Gdk.WindowTypeHint.SPLASHSCREEN)
56 vbox = Gtk.VBox()
57 hbox = Gtk.HBox(homogeneous=False)
58 icon = get_icon_pixbuf("xpra.png")
59 if icon:
60 self.set_icon(icon)
61 hbox.pack_start(Gtk.Image.new_from_pixbuf(icon), False, False, 20)
62 label = Gtk.Label(label=title)
63 label.modify_font(Pango.FontDescription("sans 18"))
64 hbox.pack_start(label, True, True, 20)
65 vbox.add(hbox)
66 self.labels = []
67 for i in range(LINES):
68 l = Gtk.Label(label=" ")
69 l.set_opacity((i+1)/LINES)
70 #l.set_line_wrap(True)
71 self.labels.append(l)
72 al = Gtk.Alignment(xalign=0, yalign=0.5, xscale=0, yscale=0)
73 al.add(l)
74 vbox.pack_start(al, True, True, 4)
75 self.progress_bar = Gtk.ProgressBar()
76 self.progress_bar.set_size_request(320, 30)
77 self.progress_bar.set_show_text(False)
78 self.progress_bar.set_fraction(0)
79 self.progress_timer = None
80 self.fade_out_timer = None
81 self.exit_timer = None
82 vbox.add(self.progress_bar)
83 self.timeout_timer = 0
84 self.pulse_timer = 0
85 self.pulse_counter = 0
86 self.current_label_text = None
87 self.add(vbox)
88 install_signal_handlers(None, self.handle_signal)
89 SIGPIPE = getattr(signal, "SIGPIPE", None)
90 if SIGPIPE: #ie: POSIX
91 signal.signal(SIGPIPE, self.handle_signal)
92 self.opacity = 100
93 self.pct = 0
96 def run(self):
97 from xpra.make_thread import start_thread
98 start_thread(self.read_stdin, "read-stdin", True)
99 self.show_all()
100 force_focus()
101 self.present()
102 self.timeout_timer = GLib.timeout_add(TIMEOUT*1000, self.timeout)
103 self.pulse_timer = GLib.timeout_add(100, self.pulse)
104 Gtk.main()
105 return self.exit_code or 0
107 def timeout(self):
108 log("timeout()")
109 self.timeout_timer = None
110 self.exit_code = EXIT_TIMEOUT
111 self.show_progress_value(100)
112 self.progress_bar.set_text("timeout")
113 self.progress_bar.set_show_text(True)
115 def cancel_timeout_timer(self):
116 tt = self.timeout_timer
117 if tt:
118 self.timeout_timer = 0
119 GLib.source_remove(tt)
121 def pulse(self):
122 if self.pct<100:
123 pulse_char = PULSE_CHARS[self.pulse_counter % len(PULSE_CHARS)]
124 else:
125 pulse_char = DONE_CHAR
126 label = " %s %s" % (pulse_char, self.current_label_text)
127 self.labels[-1].set_label(label)
128 self.pulse_counter += 1
129 return True
131 def read_stdin(self):
132 log("read_stdin()")
133 while self.exit_code is None:
134 line = sys.stdin.readline()
135 self.handle_stdin_line(line)
137 def handle_stdin_line(self, line):
138 parts = line.rstrip("\n\r").split(":", 1)
139 log("handle_stdin_line(%r)", line)
140 pct = self.pct
141 if parts[0]:
142 try:
143 pct = int(parts[0])
144 except ValueError:
145 pass
146 else:
147 self.show_progress_value(pct)
148 if len(parts)>=2:
149 self.current_label_text = parts[1]
150 if self.pct!=pct:
151 for i in range(len(self.labels)-1):
152 next_line = self.labels[i+1].get_label()
153 for c in PULSE_CHARS:
154 next_line = next_line.replace(c, DONE_CHAR)
155 self.labels[i].set_label(next_line)
156 self.pulse_counter = 0
157 self.pct = pct
158 self.pulse()
160 def show_progress_value(self, pct):
161 self.cancel_progress_timer()
162 GLib.idle_add(self.progress_bar.set_fraction, pct/100.0)
163 if pct>=100:
164 self.cancel_pulse_timer()
165 self.cancel_fade_out_timer()
166 self.opacity = 100
167 self.fade_out_timer = GLib.timeout_add(SPLASH_EXIT_DELAY*1000//100, self.fade_out)
168 self.cancel_exit_timer()
169 def exit_splash():
170 self.exit_timer = None
171 self.exit()
172 self.exit_timer = GLib.timeout_add(SPLASH_EXIT_DELAY*1000, exit_splash)
173 else:
174 self.progress_timer = GLib.timeout_add(40, self.increase_fraction, pct)
176 def cancel_exit_timer(self):
177 et = self.exit_timer
178 if et:
179 self.exit_timer = None
180 GLib.source_remove(et)
182 def cancel_fade_out_timer(self):
183 fot = self.fade_out_timer
184 if fot:
185 self.fade_out_timer = None
186 GLib.source_remove(fot)
188 def cancel_progress_timer(self):
189 pt = self.progress_timer
190 if pt:
191 self.progress_timer = None
192 GLib.source_remove(pt)
194 def cancel_pulse_timer(self):
195 pt = self.pulse_timer
196 if pt:
197 self.pulse_timer = None
198 GLib.source_remove(pt)
200 def increase_fraction(self, pct, inc=1, max_increase=10):
201 log("increase_fraction%s", (pct, inc, max_increase))
202 self.cancel_progress_timer()
203 GLib.idle_add(self.progress_bar.set_fraction, (pct+inc)/100.0)
204 if inc<max_increase:
205 self.progress_timer = GLib.timeout_add(40+20*(2+inc)**2, self.increase_fraction, pct, inc+1, max_increase)
206 return False
209 def fade_out(self):
210 self.opacity = max(0, self.opacity-1)
211 actual = int(self.get_opacity()*100)
212 if actual>self.opacity:
213 self.set_opacity(self.opacity/100.0)
214 if actual<=0:
215 self.fade_out_timer = None
216 return actual>0
218 def exit(self, *args):
219 log("exit%s calling %s", args, Gtk.main_quit)
220 if self.exit_code is None:
221 self.exit_code = 0
222 self.cancel_progress_timer()
223 self.cancel_timeout_timer()
224 self.cancel_fade_out_timer()
225 self.cancel_pulse_timer()
226 self.cancel_exit_timer()
227 Gtk.main_quit()
230 def handle_signal(self, signum, frame=None):
231 log("handle_signal(%s, %s)", SIGNAMES.get(signum, signum), frame)
232 self.exit_code = 128-(signum or 0)
233 GLib.idle_add(self.exit)
236def main(_args):
237 import os
238 if os.environ.get("XPRA_HIDE_DOCK") is None:
239 os.environ["XPRA_HIDE_DOCK"] = "1"
240 from xpra.platform import program_context
241 with program_context("splash", "Splash"):
242 Gtk.Window.set_auto_startup_notification(False)
243 w = SplashScreen()
244 add_close_accel(w, Gtk.main_quit)
245 return w.run()
248if __name__ == "__main__": # pragma: no cover
249 sys.exit(main(sys.argv))