hbexternaleditor.cpp 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /**
  2. * @file hbexternaleditor.cpp
  3. * @brief Utility class to launch an external program for editing a file and
  4. * tracking changes on the latter.
  5. *
  6. * $LicenseInfo:firstyear=2019&license=viewergpl$
  7. *
  8. * Copyright (c) 2019, Henri Beauchamp.
  9. *
  10. * Second Life Viewer Source Code
  11. * The source code in this file ("Source Code") is provided by Linden Lab
  12. * to you under the terms of the GNU General Public License, version 2.0
  13. * ("GPL"), unless you have obtained a separate licensing agreement
  14. * ("Other License"), formally executed by you and Linden Lab. Terms of
  15. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  16. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  17. *
  18. * There are special exceptions to the terms and conditions of the GPL as
  19. * it is applied to this Source Code. View the full text of the exception
  20. * in the file doc/FLOSS-exception.txt in this software distribution, or
  21. * online at
  22. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  23. *
  24. * By copying, modifying or distributing this software, you acknowledge
  25. * that you have read and understood your obligations described above,
  26. * and agree to abide by those obligations.
  27. *
  28. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  29. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  30. * COMPLETENESS OR PERFORMANCE.
  31. * $/LicenseInfo$
  32. */
  33. #include "linden_common.h"
  34. #include "hbexternaleditor.h"
  35. #include "lllivefile.h"
  36. #include "llprocesslauncher.h"
  37. #include "lltrans.h"
  38. #include "llui.h" // For LLUI::sConfigGroup
  39. ///////////////////////////////////////////////////////////////////////////////
  40. // HBEditorLiveFile class, for edited file live tracking
  41. ///////////////////////////////////////////////////////////////////////////////
  42. class HBEditorLiveFile final : public LLLiveFile
  43. {
  44. public:
  45. HBEditorLiveFile(HBExternalEditor* editor, const std::string& filename)
  46. : LLLiveFile(filename, 1.f),
  47. mEditor(editor)
  48. {
  49. }
  50. ~HBEditorLiveFile() override
  51. {
  52. }
  53. protected:
  54. bool loadFile() override
  55. {
  56. if (mEditor)
  57. {
  58. mEditor->callChangedCallback(filename());
  59. }
  60. return true;
  61. }
  62. private:
  63. HBExternalEditor* mEditor;
  64. };
  65. ///////////////////////////////////////////////////////////////////////////////
  66. // HBExternalEditor class proper
  67. ///////////////////////////////////////////////////////////////////////////////
  68. HBExternalEditor::HBExternalEditor(HBExternalEditorFileChangedCB callback,
  69. void* userdata, bool orphanize_on_destroy)
  70. : mFiledChangedCallback(callback),
  71. mUserData(userdata),
  72. mIgnoreNextUpdate(false),
  73. mEditorIsDetached(false),
  74. mOrphanizeOnDestroy(orphanize_on_destroy),
  75. mProcess(NULL),
  76. mEditedFile(NULL)
  77. {
  78. }
  79. HBExternalEditor::~HBExternalEditor()
  80. {
  81. if (mEditedFile)
  82. {
  83. delete mEditedFile;
  84. }
  85. if (mProcess)
  86. {
  87. if (mOrphanizeOnDestroy)
  88. {
  89. mProcess->orphan();
  90. }
  91. delete mProcess;
  92. }
  93. }
  94. void HBExternalEditor::callChangedCallback(const std::string& filename)
  95. {
  96. if (!mIgnoreNextUpdate && mFiledChangedCallback)
  97. {
  98. mFiledChangedCallback(filename, mUserData);
  99. }
  100. mIgnoreNextUpdate = false;
  101. }
  102. bool HBExternalEditor::open(const std::string& filename, std::string cmd)
  103. {
  104. if (!LLFile::isfile(filename))
  105. {
  106. mErrorMessage = LLTrans::getString("file_not_found") + " " + filename;
  107. llwarns << mErrorMessage << llendl;
  108. return false;
  109. }
  110. mEditorIsDetached = false;
  111. if (cmd.empty())
  112. {
  113. cmd = LLUI::sConfigGroup->getString("ExternalEditor");
  114. }
  115. if (cmd.empty())
  116. {
  117. #if LL_LINUX
  118. llwarns << "Could not find a configured editor; trying 'xdg-open'. This is suboptimal because the state of the editor it will launch cannot be tracked. Please, consider configuring the \"ExternalEditor\" setting."
  119. << llendl;
  120. // *TODO: try every PATH element, in case xdg-open is not in /usr/bin ?
  121. cmd = "/usr/bin/xdg-open %s";
  122. mEditorIsDetached = true;
  123. #elif LL_DARWIN
  124. llwarns << "Could not find a configured editor; trying 'open'. This is suboptimal because the state of the editor it will launch cannot be tracked. Please, consider configuring the \"ExternalEditor\" setting."
  125. << llendl;
  126. // *TODO: try every PATH element, in case open is not in /usr/bin ?
  127. cmd = "/usr/bin/open -e %s";
  128. mEditorIsDetached = true;
  129. #elif LL_WINDOWS
  130. llwarns << "Could not find a configured editor; trying 'notepad.exe'."
  131. << llendl;
  132. cmd = "\"C:\\Windows\\System32\\notepad.exe\" \"%s\"";
  133. #endif
  134. }
  135. LLStringUtil::trim(cmd);
  136. if (cmd.empty())
  137. {
  138. mErrorMessage = LLTrans::getString("no_valid_command");
  139. llwarns << mErrorMessage << llendl;
  140. return false;
  141. }
  142. // Split the command line between program file name and arguments
  143. std::string prg;
  144. size_t i;
  145. if (cmd[0] == '"')
  146. {
  147. // Starting with a quoted program name, as often seen under Windows,
  148. // because of spaces in the path.
  149. i = cmd.find('"', 1); // Find the matching closing quote
  150. if (i == std::string::npos)
  151. {
  152. mErrorMessage = LLTrans::getString("bad_quoting");
  153. llwarns << mErrorMessage << llendl;
  154. return false;
  155. }
  156. prg = cmd.substr(1, i - 1);
  157. cmd = cmd.substr(i + 1);
  158. }
  159. else
  160. {
  161. i = cmd.find(' ', 1); // Find the first space
  162. if (i == std::string::npos)
  163. {
  164. // No argument, just a program...
  165. prg = cmd;
  166. cmd.clear();
  167. }
  168. else
  169. {
  170. prg = cmd.substr(0, i);
  171. cmd = cmd.substr(i + 1);
  172. }
  173. }
  174. if (cmd.find("%s") == std::string::npos)
  175. {
  176. // Add the filename if absent from the arguments
  177. #if LL_WINDOWS
  178. cmd += " \"%s\"";
  179. #else
  180. cmd += " %s";
  181. #endif
  182. }
  183. LLStringUtil::trimHead(cmd);
  184. if (!LLFile::isfile(prg))
  185. {
  186. mErrorMessage = LLTrans::getString("program_not_found") + " " + prg;
  187. llwarns << mErrorMessage << llendl;
  188. return false;
  189. }
  190. llinfos << "Using external editor command line: " << prg << " " << cmd
  191. << llendl;
  192. if (mEditedFile)
  193. {
  194. delete mEditedFile;
  195. mEditedFile = NULL;
  196. }
  197. // Watch as live file only if we got a "file changed" event callback
  198. if (mFiledChangedCallback)
  199. {
  200. mEditedFile = new HBEditorLiveFile(this, filename);
  201. mEditedFile->addToEventTimer();
  202. }
  203. std::vector<std::string> tokens;
  204. LLStringUtil::getTokens(cmd, tokens, " ");
  205. if (mProcess)
  206. {
  207. if (mOrphanizeOnDestroy)
  208. {
  209. mProcess->orphan();
  210. }
  211. else
  212. {
  213. mProcess->kill();
  214. }
  215. mProcess->clearArguments();
  216. mProcess->setWorkingDirectory("");
  217. }
  218. else
  219. {
  220. mProcess = new LLProcessLauncher();
  221. }
  222. mProcess->setExecutable(prg);
  223. for (U32 i = 0, count = tokens.size(); i < count; ++i)
  224. {
  225. std::string& parameter = tokens[i];
  226. if (!parameter.empty())
  227. {
  228. #if LL_LINUX || LL_DARWIN
  229. // Under POSIX operating systems, arguments for execv() are passed
  230. // in the argv array and none need quoting; much to the contrary
  231. // since quotes would cause the path to be considered relative and
  232. // be prefixed with the working directory path, which is not what
  233. // we want here !
  234. LLStringUtil::replaceString(parameter, "\"%s\"", filename);
  235. #endif
  236. LLStringUtil::replaceString(parameter, "%s", filename);
  237. mProcess->addArgument(parameter);
  238. }
  239. }
  240. if (mProcess->launch() != 0)
  241. {
  242. mErrorMessage = LLTrans::getString("command_failed") + " " + prg +
  243. " " + cmd;
  244. llwarns << mErrorMessage << llendl;
  245. kill();
  246. return false;
  247. }
  248. // Opening the file in the external editor caused it to be touched and we
  249. // do not want to trigger a "file changed" event for this...
  250. mIgnoreNextUpdate = true;
  251. return true;
  252. }
  253. void HBExternalEditor::kill()
  254. {
  255. if (mEditedFile)
  256. {
  257. delete mEditedFile;
  258. mEditedFile = NULL;
  259. }
  260. if (mProcess)
  261. {
  262. if (mEditorIsDetached)
  263. {
  264. llwarns << "Cannot kill a detached editor process..." << llendl;
  265. }
  266. delete mProcess;
  267. mProcess = NULL;
  268. }
  269. }
  270. bool HBExternalEditor::running()
  271. {
  272. return mProcess && (mEditorIsDetached || mProcess->isRunning());
  273. }
  274. std::string HBExternalEditor::getFilename()
  275. {
  276. return mEditedFile ? mEditedFile->filename() : LLStringUtil::null;
  277. }