Coverage for src/robotide/postinstall/__init__.py: 25%
244 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
1# Copyright 2008-2015 Nokia Networks
2# Copyright 2016- Robot Framework Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
16from .desktopshortcut import ShortcutPlugin 1ef
18import sys 1ef
19try: 1ef
20 import wx 1ef
21 from wx import Colour 1ef
22except ImportError:
23 sys.stderr.write("No wxPython installation detected!"
24 "\n"
25 "Please ensure that you have wxPython installed "
26 "before running RIDE. "
27 "You can obtain wxPython from "
28 "https://wxpython.org/pages/downloads/\n"
29 "or pip install wxPython")
30 exit(-1)
32from os import environ, getlogin 1ef
33from os.path import exists, join 1ef
34from robotide.widgets import RIDEDialog 1ef
36__doc__ = """ 1ef
37Usage: python ride_postinstall.py [options] <-install|-remove>
38 or python -m robotide.postinstall [options] <-install|-remove>
39 -install - Installs a Desktop Shortcut to RIDE.
40 -remove - [TODO] Removes a Desktop Shortcut to RIDE.
41 options: -q - Quiet, don't ask user for confirmation.
42 -f - Force action.
43 -help - This help.
44""".strip()
45# DEBUG: Add -remove, to remove desktop shortcut
47ROBOT_ICO = "robot.ico" 1ef
48DEFAULT_LANGUAGE = environ.get('LANG', '').split(':') 1ef
51def verify_install(): 1ef
52 try:
53 from wx import version
54 except ImportError:
55 sys.stderr.write("No wxPython installation detected!"
56 "\n"
57 "Please ensure that you have wxPython installed "
58 "before running RIDE. "
59 "You can obtain wxPython from "
60 "https://wxpython.org/pages/downloads/\n"
61 "or pip install wxPython")
62 return False
63 else:
64 sys.stderr.write("wxPython is installed.\n%s\n" % version())
65 return True
68class MessageDialog(RIDEDialog): 1ef
69 def __init__(self, parent, message, title, ttl=10, no_default=False): 1ef
70 RIDEDialog.__init__(self, title=title, parent=parent, size=(300, 200)) 1abcd
72 self.CenterOnScreen(wx.BOTH) 1abcd
73 self.timeToLive = ttl 1abcd
74 st_msg = [] 1abcd
75 for msg in message.split('\n'): 1abcd
76 st_msg.append(wx.StaticText(self, -1, msg)) 1abcd
77 self.settimetolivemsg = wx.StaticText(self, -1, 'Closing this dialog box in %ds...' % self.timeToLive) 1abcd
78 vbox = wx.BoxSizer(wx.VERTICAL) 1abcd
79 for sp_msg in st_msg: 1abcd
80 vbox.Add(sp_msg, 0, wx.ALIGN_LEFT | wx.TOP, 10) 1abcd
81 vbox.Add(self.settimetolivemsg, 0, wx.ALIGN_CENTER | wx.TOP, 10) 1abcd
82 self.SetSizer(vbox) 1abcd
83 self.SetAffirmativeId(wx.ID_OK) 1abcd
84 self._create_buttons(None, no_default) 1abcd
85 self.SetBackgroundColour(Colour(self.color_background)) 1abcd
86 self.SetForegroundColour(Colour(self.color_foreground)) 1abcd
87 self.timer = wx.Timer(self) 1abcd
88 self.timer.Start(1000) # Generate a timer event every second 1abcd
89 self.Bind(wx.EVT_CLOSE, self.on_close) 1abcd
90 self.Bind(wx.EVT_TIMER, self.on_timer, self.timer) 1abcd
91 self.Bind(wx.EVT_BUTTON, self.on_cancel, id=wx.ID_CANCEL) 1abcd
92 self.Bind(wx.EVT_BUTTON, self.on_no, id=wx.ID_NO) 1abcd
93 self.Bind(wx.EVT_CHAR_HOOK, self.on_key_pressed) 1abcd
95 def on_key_pressed(self, event): 1ef
96 key_code = event.GetKeyCode()
97 if key_code == wx.WXK_ESCAPE:
98 self.EndModal(wx.ID_NO)
99 event.Skip()
101 def on_cancel(self, evt): 1ef
102 _ = evt
103 self.EndModal(wx.ID_NO)
105 def on_close(self, evt): 1ef
106 self.on_cancel(evt)
108 def on_no(self, evt): 1ef
109 self.on_cancel(evt)
111 def on_timer(self, evt): 1ef
112 _ = evt 1abcd
113 self.timeToLive -= 1 1abcd
114 self.settimetolivemsg.SetLabel('Closing this dialog box in %ds...' % self.timeToLive) 1abcd
116 if self.timeToLive == 0: 1abcd
117 self.timer.Stop() 1abcd
118 self.EndModal(wx.ID_NO) 1abcd
120 def _create_buttons(self, sizer, no_default=False): 1ef
121 flags = wx.OK | wx.CANCEL 1abcd
122 if no_default: 122 ↛ 124line 122 didn't jump to line 124 because the condition on line 122 was always true1abcd
123 flags |= wx.NO_DEFAULT 1abcd
124 buttons = self.CreateStdDialogButtonSizer(flags) 1abcd
125 self.SetBackgroundColour(Colour(self.color_background)) 1abcd
126 self.SetForegroundColour(Colour(self.color_foreground)) 1abcd
127 for item in self.GetChildren(): 1abcd
128 if isinstance(item, (wx.Button, wx.BitmapButton)): 1abcd
129 item.SetBackgroundColour(Colour(self.color_secondary_background)) 1abcd
130 # item.SetOwnBackgroundColour(Colour(self.color_secondary_background))
131 item.SetForegroundColour(Colour(self.color_secondary_foreground)) 1abcd
132 # item.SetOwnForegroundColour(Colour(self.color_secondary_foreground))
133 self.Sizer.Add(buttons, flag=wx.ALIGN_CENTER | wx.ALL, border=5) 1abcd
136def _askyesno(title, message, frame=None, no_default=False): 1ef
137 if frame is None:
138 _ = wx.App()
139 parent = wx.Frame(None, size=(0, 0))
140 else:
141 parent = wx.Frame(frame, size=(0, 0))
142 parent.CenterOnScreen()
143 dlg = MessageDialog(parent, message, title, ttl=8, no_default=no_default)
144 dlg.Fit()
145 result = dlg.ShowModal() in [wx.ID_YES, wx.ID_OK]
146 print("Result %s" % result)
147 if dlg:
148 dlg.Destroy()
149 parent.Destroy()
150 return result
153def _askdirectory(title, initialdir, frame=None): 1ef
154 import wx
155 if frame is None:
156 _ = wx.App()
157 parent = wx.Frame(None, size=(0, 0))
158 else:
159 parent = wx.Frame(frame, size=(0, 0))
160 parent.CenterOnScreen()
161 dlg = wx.DirDialog(parent, title, initialdir, style=wx.DD_DIR_MUST_EXIST)
162 if dlg.ShowModal() == wx.ID_OK:
163 result = dlg.GetPath()
164 else:
165 result = None
166 dlg.Destroy()
167 parent.Destroy()
168 return result
171def _create_desktop_shortcut_linux(frame=None): 1ef
172 from os.path import expanduser
173 from os import chmod, chown
174 from stat import S_IRWXU
175 import subprocess
176 import pwd
177 import sysconfig
178 # DEBUG: Add more languages
179 desktop = {"de": "Desktop", "en": "Desktop", "es": "Escritorio",
180 "fi": r"Työpöytä", "fr": "Bureau", "it": "Scrivania",
181 "pt": r"Área de Trabalho", "zh": "Desktop"}
182 user = getlogin()
183 try:
184 ndesktop = desktop[DEFAULT_LANGUAGE[0][:2]]
185 directory = join("/home", user, ndesktop)
186 defaultdir = join("/home", user, "Desktop")
187 if not exists(directory):
188 if exists(defaultdir):
189 directory = defaultdir
190 else:
191 if not option_q:
192 directory = _askdirectory(title="Locate Desktop Directory",
193 initialdir=join(expanduser('~')),
194 frame=frame)
195 else:
196 directory = None
197 except KeyError:
198 if not option_q:
199 directory = _askdirectory(title="Locate Desktop Directory",
200 initialdir=join(expanduser('~')),
201 frame=frame)
202 else:
203 directory = None
204 if directory is None:
205 sys.stderr.write("Desktop shortcut creation aborted!\n")
206 return False
207 try:
208 link = join(directory, "RIDE.desktop")
209 except UnicodeError:
210 link = join(directory.encode('utf-8'), "RIDE.desktop")
211 if not exists(link) or option_f:
212 if not option_q and not option_f:
213 if not _askyesno("Setup", "Create desktop shortcut?", frame):
214 return False
215 roboticon = join(sysconfig.get_paths()["purelib"], "robotide", "widgets", ROBOT_ICO)
216 if not exists(roboticon):
217 try:
218 import robotide as _
219 roboticon = join(_.__path__[0], "widgets", ROBOT_ICO)
220 except ImportError:
221 _ = None
222 if not exists(roboticon):
223 roboticon = join("FIXME: find correct path to: .../site-packages/", "robotide", "widgets", ROBOT_ICO)
224 with open(link, "w+") as shortcut:
225 shortcut.write(f"#!/usr/bin/env xdg-open\n[Desktop Entry]\n"
226 f"Exec={sys.executable} -m robotide.__init__\n"
227 f"Comment=A Robot Framework IDE\nGenericName=RIDE\n"
228 f"Icon={roboticon}\n"
229 f"Name=RIDE\nStartupNotify=true\nTerminal=false\n"
230 "Type=Application\nX-KDE-SubstituteUID=false\n")
231 uid = pwd.getpwnam(user).pw_uid
232 chown(link, uid, -1) # groupid == -1 means keep unchanged
233 chmod(link, S_IRWXU)
236def _create_desktop_shortcut_mac(frame=None): 1ef
237 import os
238 import shutil
239 import subprocess
240 ride_app_name = 'RIDE.app'
241 application_path = '/Applications'
242 ride_app_pc_path = os.path.join(application_path, ride_app_name)
243 ride_app_module_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ride_app_name)
245 if not exists(ride_app_pc_path) or option_f:
246 if not option_q and not option_f and not _askyesno("Setup", "Create application shortcut?", frame):
247 return False
248 app_script = os.path.join(ride_app_module_path, 'Contents', 'MacOS', 'RIDE')
249 with open(app_script, 'w+') as shortcut:
250 shortcut.write("#!/bin/sh\nPAL=$PATH\nfor i in `cat /etc/paths`\n do\n PAL=\"$PAL:$i\"\n"
251 " done\nPATH=$PAL\nexport $PATH\n{} -m robotide.__init__ $* 2>"
252 " /dev/null &\n".format(sys.executable))
253 if exists(ride_app_pc_path):
254 shutil.rmtree(ride_app_pc_path, True)
255 shutil.copytree(ride_app_module_path, ride_app_pc_path)
256 user = getlogin()
257 user_desktop_link = '/Users/' + user + '/Desktop/' + ride_app_name
258 if exists(user_desktop_link):
259 os.remove(user_desktop_link)
260 try:
261 os.symlink(ride_app_pc_path, user_desktop_link)
262 except Exception as e:
263 print(e)
266def _create_desktop_shortcut_windows(frame=None): 1ef
267 # Dependency of http://sourceforge.net/projects/pywin32/
268 import os
269 import sys
270 try:
271 from win32com.shell import shell, shellcon
272 except ImportError:
273 sys.stderr.write("Cannot create desktop shortcut.\nPlease install"
274 " pywin32 from https://github.com/mhammond/pywin32\n"
275 "or pip install pywin32")
276 return False
277 desktop = shell.SHGetFolderPath(0, shellcon.CSIDL_DESKTOP, None, 0)
278 link = os.path.join(desktop, 'RIDE.lnk')
279 public_link = os.path.join(os.getenv('PUBLIC'), 'Desktop', 'RIDE.lnk')
280 icon = os.path.join(sys.prefix, 'Lib', 'site-packages', 'robotide',
281 'widgets', 'robot.ico')
282 if not (exists(public_link) or exists(link)) or option_f:
283 if not option_q and not option_f:
284 if not _askyesno("Setup", "Create desktop shortcut?", frame):
285 sys.stderr.write("Users can create a Desktop shortcut to RIDE "
286 "with:\n%s -m robotide.postinstall -install\n"
287 % sys.executable)
288 return False
289 import pythoncom
290 shortcut = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None,
291 pythoncom.CLSCTX_INPROC_SERVER,
292 shell.IID_IShellLink)
293 command_args = " -c \"from robotide import main; main()\""
294 shortcut.SetPath(sys.executable.replace('python.exe', 'pythonw.exe'))
295 shortcut.SetArguments(command_args)
296 shortcut.SetDescription("Robot Framework testdata editor")
297 shortcut.SetIconLocation(icon, 0)
298 persist_file = shortcut.QueryInterface(pythoncom.IID_IPersistFile)
299 from pywintypes import com_error
300 try:
301 persist_file.Save(public_link, 0)
302 sys.stderr.write("Desktop shortcut created for all users.")
303 except com_error:
304 persist_file.Save(link, 0)
307def create_desktop_shortcut(platform, frame=None): 1ef
308 if platform.startswith("linux"):
309 return _create_desktop_shortcut_linux(frame)
310 elif platform.startswith("darwin"):
311 return _create_desktop_shortcut_mac(frame)
312 elif platform.startswith("win"):
313 return _create_desktop_shortcut_windows(frame)
314 else:
315 sys.stderr.write("Unknown platform {0}: Failed to create desktop short"
316 "cut.".format(platform))
317 return False
320def caller(frame, platform): 1ef
321 # Options
322 global option_q
323 global option_f
324 option_q = None
325 option_f = frame is not None
326 # We don't verify install because called from RIDE
327 return create_desktop_shortcut(platform, frame)
330def main(*args): 1ef
331 myargs = list(args[:])
332 # Options
333 global option_q
334 global option_f
335 option_q = option_f = None
336 option_q = next((x for x in myargs if x == "-q"), None)
337 if option_q is not None:
338 del myargs[myargs.index(option_q)]
339 option_q = True
340 option_f = next((x for x in myargs if x == "-f"), None)
341 if option_f is not None:
342 del myargs[myargs.index(option_f)]
343 option_f = True
344 arg = myargs[-1] if len(myargs) == 1 and myargs[-1] in ['-install', '-remove',
345 '-help'] else None
346 if arg == '-install':
347 platform = sys.platform.lower()
348 doit = verify_install()
349 if doit:
350 create_desktop_shortcut(platform)
351 elif arg == '-remove':
352 sys.stderr.write("Sorry, -remove is not implemented yet.\n")
353 else:
354 sys.stderr.write(__doc__)
355 sys.stderr.write("\n")
358if __name__ == '__main__': 1ef
359 main(*sys.argv[1:])