From 8899dbef3c8bcb597df1711b43d4193ea3733d04 Mon Sep 17 00:00:00 2001 From: Siana Gearz Date: Fri, 27 Jan 2012 05:05:42 +0100 Subject: [PATCH 1/3] Fix plug-ins not working when path >32 characters --- indra/llvfs/lldir_linux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llvfs/lldir_linux.cpp b/indra/llvfs/lldir_linux.cpp index a4df354c5..3c74e50a4 100644 --- a/indra/llvfs/lldir_linux.cpp +++ b/indra/llvfs/lldir_linux.cpp @@ -117,7 +117,7 @@ LLDir_Linux::LLDir_Linux() mOSUserAppDir = ""; mLindenUserDir = ""; - char path [32]; /* Flawfinder: ignore */ + char path [MAX_PATH]; /* Flawfinder: ignore */ // *NOTE: /proc/%d/exe doesn't work on FreeBSD. But that's ok, // because this is the linux implementation. From 856507a5806a5d37b7d29e09aed8ad9be2dc4741 Mon Sep 17 00:00:00 2001 From: Latif Khalifa Date: Fri, 27 Jan 2012 05:18:50 +0100 Subject: [PATCH 2/3] Implemented support for external script editor based on v3 code --- indra/newview/llexternaleditor.cpp | 208 +++++++++++++++++++++++++++++ indra/newview/llexternaleditor.h | 105 +++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 indra/newview/llexternaleditor.cpp create mode 100644 indra/newview/llexternaleditor.h diff --git a/indra/newview/llexternaleditor.cpp b/indra/newview/llexternaleditor.cpp new file mode 100644 index 000000000..c016b8da6 --- /dev/null +++ b/indra/newview/llexternaleditor.cpp @@ -0,0 +1,208 @@ +/** + * @file llexternaleditor.cpp + * @brief A convenient class to run external editor. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llexternaleditor.h" + +#include "lltrans.h" +#include "llui.h" + +// static +const std::string LLExternalEditor::sFilenameMarker = "%s"; + +// static +const std::string LLExternalEditor::sSetting = "ExternalEditor"; + +LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env_var, const std::string& override) +{ + std::string cmd = findCommand(env_var, override); + if (cmd.empty()) + { + llwarns << "Editor command is empty or not set" << llendl; + return EC_NOT_SPECIFIED; + } + + // Add the filename marker if missing. + if (cmd.find(sFilenameMarker) == std::string::npos) + { + cmd += " \"" + sFilenameMarker + "\""; + llinfos << "Adding the filename marker (" << sFilenameMarker << ")" << llendl; + } + + string_vec_t tokens; + if (tokenize(tokens, cmd) < 2) // 2 = bin + at least one arg (%s) + { + llwarns << "Error parsing editor command" << llendl; + return EC_PARSE_ERROR; + } + + // Check executable for existence. + std::string bin_path = tokens[0]; + if (!LLFile::isfile(bin_path)) + { + llwarns << "Editor binary [" << bin_path << "] not found" << llendl; + return EC_BINARY_NOT_FOUND; + } + + // Save command. + mProcess.setExecutable(bin_path); + mArgs.clear(); + for (size_t i = 1; i < tokens.size(); ++i) + { + if (i > 1) mArgs += " "; + mArgs += "\"" + tokens[i] + "\""; + } + llinfos << "Setting command [" << bin_path << " " << mArgs << "]" << llendl; + + return EC_SUCCESS; +} + +LLExternalEditor::EErrorCode LLExternalEditor::run(const std::string& file_path) +{ + std::string args = mArgs; + if (mProcess.getExecutable().empty() || args.empty()) + { + llwarns << "Editor command not set" << llendl; + return EC_NOT_SPECIFIED; + } + + // Substitute the filename marker in the command with the actual passed file name. + LLStringUtil::replaceString(args, sFilenameMarker, file_path); + + // Split command into separate tokens. + string_vec_t tokens; + tokenize(tokens, args); + + // Set process arguments taken from the command. + mProcess.clearArguments(); + for (string_vec_t::const_iterator arg_it = tokens.begin(); arg_it != tokens.end(); ++arg_it) + { + mProcess.addArgument(*arg_it); + } + + // Run the editor. + llinfos << "Running editor command [" << mProcess.getExecutable() + " " + args << "]" << llendl; + int result = mProcess.launch(); + if (result == 0) + { + // Prevent killing the process in destructor (will add it to the zombies list). + mProcess.orphan(); + } + + return result == 0 ? EC_SUCCESS : EC_FAILED_TO_RUN; +} + +// static +std::string LLExternalEditor::getErrorMessage(EErrorCode code) +{ + switch (code) + { + case EC_SUCCESS: return LLTrans::getString("ok"); + case EC_NOT_SPECIFIED: return LLTrans::getString("ExternalEditorNotSet"); + case EC_PARSE_ERROR: return LLTrans::getString("ExternalEditorCommandParseError"); + case EC_BINARY_NOT_FOUND: return LLTrans::getString("ExternalEditorNotFound"); + case EC_FAILED_TO_RUN: return LLTrans::getString("ExternalEditorFailedToRun"); + } + + return LLTrans::getString("Unknown"); +} + +// static +size_t LLExternalEditor::tokenize(string_vec_t& tokens, const std::string& str) +{ + tokens.clear(); + + // Split the argument string into separate strings for each argument + typedef boost::tokenizer< boost::char_separator > tokenizer; + boost::char_separator sep("", "\" ", boost::drop_empty_tokens); + + tokenizer tokens_list(str, sep); + tokenizer::iterator token_iter; + BOOL inside_quotes = FALSE; + BOOL last_was_space = FALSE; + for (token_iter = tokens_list.begin(); token_iter != tokens_list.end(); ++token_iter) + { + if (!strncmp("\"",(*token_iter).c_str(),2)) + { + inside_quotes = !inside_quotes; + } + else if (!strncmp(" ",(*token_iter).c_str(),2)) + { + if(inside_quotes) + { + tokens.back().append(std::string(" ")); + last_was_space = TRUE; + } + } + else + { + std::string to_push = *token_iter; + if (last_was_space) + { + tokens.back().append(to_push); + last_was_space = FALSE; + } + else + { + tokens.push_back(to_push); + } + } + } + + return tokens.size(); +} + +// static +std::string LLExternalEditor::findCommand( + const std::string& env_var, + const std::string& override) +{ + std::string cmd; + + // Get executable path. + if (!override.empty()) // try the supplied override first + { + cmd = override; + llinfos << "Using override" << llendl; + } + else if (!gSavedSettings.getString(sSetting).empty()) + { + cmd = gSavedSettings.getString(sSetting); + llinfos << "Using setting" << llendl; + } + else // otherwise use the path specified by the environment variable + { + char* env_var_val = getenv(env_var.c_str()); + if (env_var_val) + { + cmd = env_var_val; + llinfos << "Using env var " << env_var << llendl; + } + } + + llinfos << "Found command [" << cmd << "]" << llendl; + return cmd; +} diff --git a/indra/newview/llexternaleditor.h b/indra/newview/llexternaleditor.h new file mode 100644 index 000000000..ef5db56c6 --- /dev/null +++ b/indra/newview/llexternaleditor.h @@ -0,0 +1,105 @@ +/** + * @file llexternaleditor.h + * @brief A convenient class to run external editor. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLEXTERNALEDITOR_H +#define LL_LLEXTERNALEDITOR_H + +#include + +/** + * Usage: + * LLExternalEditor ed; + * ed.setCommand("MY_EXTERNAL_EDITOR_VAR"); + * ed.run("/path/to/file1"); + * ed.run("/other/path/to/file2"); + */ +class LLExternalEditor +{ + typedef std::vector string_vec_t; + +public: + + typedef enum e_error_code { + EC_SUCCESS, /// No error. + EC_NOT_SPECIFIED, /// Editor path not specified. + EC_PARSE_ERROR, /// Editor command parsing error. + EC_BINARY_NOT_FOUND, /// Could find the editor binary (missing or not quoted). + EC_FAILED_TO_RUN, /// Could not execute the editor binary. + } EErrorCode; + + /** + * Set editor command. + * + * @param env_var Environment variable of the same purpose. + * @param override Optional override. + * + * First tries the override, then a predefined setting (sSetting), + * then the environment variable. + * + * @return EC_SUCCESS if command is valid and refers to an existing executable, + * EC_NOT_SPECIFIED or EC_FAILED_TO_RUNan on error. + * + * @see sSetting + */ + EErrorCode setCommand(const std::string& env_var, const std::string& override = LLStringUtil::null); + + /** + * Run the editor with the given file. + * + * @param file_path File to edit. + * @return EC_SUCCESS on success, error code on error. + */ + EErrorCode run(const std::string& file_path); + + /** + * Get a meaningful error message for the given status code. + */ + static std::string getErrorMessage(EErrorCode code); + +private: + + static std::string findCommand( + const std::string& env_var, + const std::string& override); + + static size_t tokenize(string_vec_t& tokens, const std::string& str); + + /** + * Filename placeholder that gets replaced with an actual file name. + */ + static const std::string sFilenameMarker; + + /** + * Setting that can specify the editor command. + */ + static const std::string sSetting; + + + std::string mArgs; + LLProcessLauncher mProcess; +}; + +#endif // LL_LLEXTERNALEDITOR_H From 2e59ae13409fef30b83e6fc86022ae533dd255a6 Mon Sep 17 00:00:00 2001 From: Latif Khalifa Date: Fri, 27 Jan 2012 05:24:47 +0100 Subject: [PATCH 3/3] Implemented support for external script editor based on v3 code (part 2) --- indra/newview/CMakeLists.txt | 2 + indra/newview/app_settings/settings.xml | 11 + indra/newview/llpreviewscript.cpp | 211 +++++++++++++++++- indra/newview/llpreviewscript.h | 15 +- .../xui/en-us/floater_script_ed_panel.xml | 5 +- 5 files changed, 239 insertions(+), 5 deletions(-) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 30f097407..f50c47d41 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -165,6 +165,7 @@ set(viewer_SOURCE_FILES lleventinfo.cpp lleventnotifier.cpp lleventpoll.cpp + llexternaleditor.cpp llface.cpp llfasttimerview.cpp llfeaturemanager.cpp @@ -643,6 +644,7 @@ set(viewer_HEADER_FILES lleventinfo.h lleventnotifier.h lleventpoll.h + llexternaleditor.h llface.h llfasttimerview.h llfeaturemanager.h diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 71c2f0779..871f7efdb 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -14261,6 +14261,17 @@ Value 150000.0 + ExternalEditor + + Comment + Path to program used to edit LSL scripts and XUI files, e.g.: /usr/bin/gedit --new-window "%s" + Persist + 1 + Type + String + Value + + YawFromMousePosition Comment diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 446a96979..6682a6e8c 100644 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -45,6 +45,8 @@ #include "llkeyboard.h" #include "lllineeditor.h" +#include "lllivefile.h" +#include "llexternaleditor.h" #include "llnotificationsutil.h" #include "llresmgr.h" #include "llscrollbar.h" @@ -147,6 +149,50 @@ static bool have_script_upload_cap(LLUUID& object_id) return object && (! object->getRegion()->getCapability("UpdateScriptTask").empty()); } +/// --------------------------------------------------------------------------- +/// LLLiveLSLFile +/// --------------------------------------------------------------------------- +class LLLiveLSLFile : public LLLiveFile +{ +public: + typedef boost::function change_callback_t; + + LLLiveLSLFile(std::string file_path, change_callback_t change_cb); + ~LLLiveLSLFile(); + + void ignoreNextUpdate() { mIgnoreNextUpdate = true; } + +protected: + /*virtual*/ bool loadFile(); + + change_callback_t mOnChangeCallback; + bool mIgnoreNextUpdate; +}; + +LLLiveLSLFile::LLLiveLSLFile(std::string file_path, change_callback_t change_cb) + : mOnChangeCallback(change_cb) + , mIgnoreNextUpdate(false) + , LLLiveFile(file_path, 1.0) +{ + llassert(mOnChangeCallback); +} + +LLLiveLSLFile::~LLLiveLSLFile() +{ + LLFile::remove(filename()); +} + +bool LLLiveLSLFile::loadFile() +{ + if (mIgnoreNextUpdate) + { + mIgnoreNextUpdate = false; + return true; + } + + return mOnChangeCallback(filename()); +} + /// --------------------------------------------------------------------------- /// LLScriptEdCore /// --------------------------------------------------------------------------- @@ -169,6 +215,8 @@ LLScriptEdCore::LLScriptEdCore( void (*save_callback)(void*, BOOL), void (*search_replace_callback) (void* userdata), void* userdata, + LLUUID objectUUID, + LLUUID itemUUID, S32 bottom_pad) : LLPanel( std::string("name"), rect ), @@ -183,8 +231,11 @@ LLScriptEdCore::LLScriptEdCore( mLastHelpToken(NULL), mLiveHelpHistorySize(0), mEnableSave(FALSE), + mLiveFile(NULL), mHasScriptData(FALSE), - LLEventTimer(60) + LLEventTimer(60), + mObjectUUID(objectUUID), + mItemUUID(itemUUID) { setFollowsAll(); setBorderVisible(FALSE); @@ -275,6 +326,7 @@ LLScriptEdCore::LLScriptEdCore( childSetCommitCallback("lsl errors", &LLScriptEdCore::onErrorList, this); childSetAction("Save_btn", onBtnSave,this); + childSetAction("Edit_btn", openInExternalEditor, this); initMenu(); @@ -290,6 +342,7 @@ LLScriptEdCore::LLScriptEdCore( LLScriptEdCore::~LLScriptEdCore() { deleteBridges(); + delete mLiveFile; } BOOL LLScriptEdCore::tick() @@ -359,6 +412,106 @@ void LLScriptEdCore::setScriptText(const std::string& text, BOOL is_valid) } } +bool LLScriptEdCore::loadScriptText(const std::string& filename) +{ + if (filename.empty()) + { + llwarns << "Empty file name" << llendl; + return false; + } + + LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ + if (!file) + { + llwarns << "Error opening " << filename << llendl; + return false; + } + + // read in the whole file + fseek(file, 0L, SEEK_END); + size_t file_length = (size_t) ftell(file); + fseek(file, 0L, SEEK_SET); + char* buffer = new char[file_length+1]; + size_t nread = fread(buffer, 1, file_length, file); + if (nread < file_length) + { + llwarns << "Short read" << llendl; + } + buffer[nread] = '\0'; + fclose(file); + + mEditor->setText(LLStringExplicit(buffer)); + delete[] buffer; + + return true; +} + +bool LLScriptEdCore::writeToFile(const std::string& filename) +{ + LLFILE* fp = LLFile::fopen(filename, "wb"); + if (!fp) + { + llwarns << "Unable to write to " << filename << llendl; + + LLSD row; + row["columns"][0]["value"] = "Error writing to local file. Is your hard drive full?"; + row["columns"][0]["font"] = "SANSSERIF_SMALL"; + mErrorList->addElement(row); + return false; + } + + std::string utf8text = mEditor->getText(); + + // Special case for a completely empty script - stuff in one space so it can store properly. See SL-46889 + if (utf8text.size() == 0) + { + utf8text = " "; + } + + fputs(utf8text.c_str(), fp); + fclose(fp); + return true; +} + +void LLScriptEdCore::sync() +{ + // Sync with external editor. + std::string tmp_file = getTmpFileName(); + llstat s; + if (LLFile::stat(tmp_file, &s) == 0) // file exists + { + if (mLiveFile) mLiveFile->ignoreNextUpdate(); + writeToFile(tmp_file); + } +} + +std::string LLScriptEdCore::getTmpFileName() +{ + // Take script inventory item id (within the object inventory) + // to consideration so that it's possible to edit multiple scripts + // in the same object inventory simultaneously (STORM-781). + std::string script_id = mObjectUUID.asString() + "_" + mItemUUID.asString(); + + // Use MD5 sum to make the file name shorter and not exceed maximum path length. + char script_id_hash_str[33]; /* Flawfinder: ignore */ + LLMD5 script_id_hash((const U8 *)script_id.c_str()); + script_id_hash.hex_digest(script_id_hash_str); + + return std::string(LLFile::tmpdir()) + "sl_script_" + script_id_hash_str + ".lsl"; +} + +bool LLScriptEdCore::onExternalChange(const std::string& filename) +{ + if (!loadScriptText(filename)) + { + return false; + } + + // Avoid recursive save/compile loop + doSave(this, false, false); + return true; +} + BOOL LLScriptEdCore::hasChanged(void* userdata) { LLScriptEdCore* self = (LLScriptEdCore*)userdata; @@ -752,7 +905,7 @@ void LLScriptEdCore::onBtnInsertFunction(LLUICtrl *ui, void* userdata) } // static -void LLScriptEdCore::doSave( void* userdata, BOOL close_after_save ) +void LLScriptEdCore::doSave( void* userdata, BOOL close_after_save, BOOL sync_external_editor) { LLViewerStats::getInstance()->incStat( LLViewerStats::ST_LSL_SAVE_COUNT ); @@ -762,6 +915,56 @@ void LLScriptEdCore::doSave( void* userdata, BOOL close_after_save ) { self->mSaveCallback( self->mUserdata, close_after_save ); } + if ( sync_external_editor ) + { + self->sync(); + } +} + +void LLScriptEdCore::openInExternalEditor(void *userdata) +{ + LLScriptEdCore* self = (LLScriptEdCore*) userdata; + + delete self->mLiveFile; // deletes file + + // Save the script to a temporary file. + std::string filename = self->getTmpFileName(); + self->writeToFile(filename); + + // Start watching file changes. + self->mLiveFile = new LLLiveLSLFile(filename, boost::bind(&LLScriptEdCore::onExternalChange, self, _1)); + self->mLiveFile->ignoreNextUpdate(); + self->mLiveFile->addToEventTimer(); + + // Open it in external editor. + { + LLExternalEditor ed; + LLExternalEditor::EErrorCode status; + std::string msg; + + status = ed.setCommand("LL_SCRIPT_EDITOR"); + if (status != LLExternalEditor::EC_SUCCESS) + { + if (status == LLExternalEditor::EC_NOT_SPECIFIED) // Use custom message for this error. + { + msg = "External editor not set"; + } + else + { + msg = LLExternalEditor::getErrorMessage(status); + } + + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg)); + return; + } + + status = ed.run(filename); + if (status != LLExternalEditor::EC_SUCCESS) + { + msg = LLExternalEditor::getErrorMessage(status); + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg)); + } + } } // static @@ -1038,6 +1241,8 @@ void* LLPreviewLSL::createScriptEdPanel(void* userdata) LLPreviewLSL::onSave, LLPreviewLSL::onSearchReplace, self, + self->mObjectID, + self->mItemUUID, 0); return self->mScriptEd; @@ -1602,6 +1807,8 @@ void* LLLiveLSLEditor::createScriptEdPanel(void* userdata) &LLLiveLSLEditor::onSave, &LLLiveLSLEditor::onSearchReplace, self, + self->mObjectID, + self->mItemUUID, 0); return self->mScriptEd; diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h index 436d68d4f..4a42332db 100644 --- a/indra/newview/llpreviewscript.h +++ b/indra/newview/llpreviewscript.h @@ -42,7 +42,7 @@ #include "llframetimer.h" #include "lleventtimer.h" - +class LLLiveLSLFile; class LLMessageSystem; class LLTextEditor; class LLButton; @@ -72,6 +72,8 @@ public: void (*save_callback)(void* userdata, BOOL close_after_save), void (*search_replace_callback)(void* userdata), void* userdata, + LLUUID objectUUID, + LLUUID itemUUID, S32 bottom_pad = 0); // pad below bottom row of buttons ~LLScriptEdCore(); @@ -82,6 +84,12 @@ public: BOOL canClose(); void setScriptText(const std::string& text, BOOL is_valid); + bool loadScriptText(const std::string& filename); + bool writeToFile(const std::string& filename); + void sync(); + std::string getTmpFileName(); + static void openInExternalEditor(void* userdata); + bool onExternalChange(const std::string& filename); bool handleSaveChangesDialog(const LLSD& notification, const LLSD& response); bool handleReloadFromServerDialog(const LLSD& notification, const LLSD& response); @@ -95,7 +103,7 @@ public: static void onClickForward(void* userdata); static void onBtnInsertSample(void*); static void onBtnInsertFunction(LLUICtrl*, void*); - static void doSave( void* userdata, BOOL close_after_save ); + static void doSave( void* userdata, BOOL close_after_save, BOOL sync_external_editor = TRUE ); static void onBtnSave(void*); static void onBtnUndoChanges(void*); static void onSearchMenu(void* userdata); @@ -158,6 +166,9 @@ private: S32 mLiveHelpHistorySize; BOOL mEnableSave; BOOL mHasScriptData; + LLLiveLSLFile* mLiveFile; + LLUUID mObjectUUID; + LLUUID mItemUUID; }; diff --git a/indra/newview/skins/default/xui/en-us/floater_script_ed_panel.xml b/indra/newview/skins/default/xui/en-us/floater_script_ed_panel.xml index b83a6df97..9e5147595 100644 --- a/indra/newview/skins/default/xui/en-us/floater_script_ed_panel.xml +++ b/indra/newview/skins/default/xui/en-us/floater_script_ed_panel.xml @@ -8,7 +8,10 @@ word_wrap="true" show_line_numbers="true"> Loading... -