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

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. 

15 

16from .desktopshortcut import ShortcutPlugin 1ef

17 

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) 

31 

32from os import environ, getlogin 1ef

33from os.path import exists, join 1ef

34from robotide.widgets import RIDEDialog 1ef

35 

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 

46 

47ROBOT_ICO = "robot.ico" 1ef

48DEFAULT_LANGUAGE = environ.get('LANG', '').split(':') 1ef

49 

50 

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 

66 

67 

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

71 

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

94 

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() 

100 

101 def on_cancel(self, evt): 1ef

102 _ = evt 

103 self.EndModal(wx.ID_NO) 

104 

105 def on_close(self, evt): 1ef

106 self.on_cancel(evt) 

107 

108 def on_no(self, evt): 1ef

109 self.on_cancel(evt) 

110 

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

115 

116 if self.timeToLive == 0: 1abcd

117 self.timer.Stop() 1abcd

118 self.EndModal(wx.ID_NO) 1abcd

119 

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

134 

135 

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 

151 

152 

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 

169 

170 

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) 

234 

235 

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) 

244 

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) 

264 

265 

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) 

305 

306 

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 

318 

319 

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) 

328 

329 

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") 

356 

357 

358if __name__ == '__main__': 1ef

359 main(*sys.argv[1:])