Skip to content

Commit

Permalink
Merge pull request #179 from end2endzone/feature-issue177
Browse files Browse the repository at this point in the history
Feature issue177 (for #177 and #178)
  • Loading branch information
end2endzone authored Oct 11, 2024
2 parents 29ba134 + d0d1bfa commit 2489b88
Show file tree
Hide file tree
Showing 30 changed files with 1,345 additions and 200 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Changes for 0.10.0
* Fixed issue #161: Create tools to help quickly find a system icon when creating a menu.
* Fixed issue #164: Fails to identify icon for HTML files.
* Fixed issue #167: Improve the quality and accuracy of icon's fileextension attribute resolution (Icon::ResolveFileExtensionIcon()).
* Fixed issue #177: Execute a console program without showing a window.
* Fixed issue #178: Exec action should expose the created process id.


Changes for 0.9.0
Expand Down
34 changes: 33 additions & 1 deletion UserManual.md
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,9 @@ The application support multiple types of actions. The list of each specific act

The <exec> element is used to launch an application. The <exec> element must be added under the <actions> element.

**Note:**
When a process is created, ShellAnything will set property `process.id` to the process id of the new launched application.

The <exec> elements have the following attributes:


Expand Down Expand Up @@ -754,7 +757,7 @@ For example, the following launch `cmd.exe` and list files and directories recur
```

**Note:**
It is recommanded to use the `wait` attribute with the `timeout` attribute. Without a _timeout_ value, ShellAnything will wait indefinitely until the launched process exits. This can result in system instability. If the launced process freezes, pauses or never exists, it will lock _ShellAnything_ and _File Explorer_.
It is recommanded to use the `wait` attribute with the `timeout` attribute. Without a _timeout_ value, ShellAnything will wait indefinitely until the launched process exits. This can result in system instability. If the launced process freezes, pauses or never exists, it will lock _ShellAnything_ and _File Explorer_ forever.

When combined with other elements, the `wait` attribute allows advanced use case.

Expand All @@ -773,6 +776,7 @@ Tell ShellAnything to wait until the search is complete before proceeding to the




#### timeout attribute: ####

The `timeout` attribute defines the maximum time to wait in seconds with the `wait` attribute. If the running process fails to exit before the _timeout_ value, a warning is logged and the next actions of the menu are not executed. The value must be numerical. The attribute is optional.
Expand All @@ -784,6 +788,34 @@ For example, the following launch `cmd.exe` and list files and directories recur



#### console attribute: ####

The `console` attribute defines how we should display the main window of the launched application. The attribute allow console applications to be launched without a console. The feature is particularly useful for running background tasks. The attribute must be set to a value that evaluates to `false` to enable the feature. See [istrue attribute](https://github.com/end2endzone/ShellAnything/blob/master/UserManual.md#istrue-attribute) or [isfalse attribute](https://github.com/end2endzone/ShellAnything/blob/master/UserManual.md#isfalse-attribute) logic for details. The attribute is optional.

For example, the following will launch ImageMagick `magick.exe` command line application to convert webp images to jpg :
```xml
<exec wait="true" console="off" path="${imagemagick.path}" arguments="&quot;${selection.path}&quot; &quot;${selection.filename.noext}.jpg&quot;" />
```
The conversion to JPEG format will be performed without showing a console and no window flickering will be visible.

**Note:**
* The _console_ attribute may also affects windowed applications and may hide their main graphical user interface.
* Users must be careful when launching background applications (hidden applications). A background application should not wait for user input or it may never complete/terminate gracefully. Background tasks can also cause system instability if the `wait` attribute is also set and the background process freezes, pauses or never exists because it will lock _ShellAnything_ and _File Explorer_ forever.



#### pid attribute: ####

The `pid` attribute defines the name of the property to set with the new launch process id.

For example, the following will sets the property `mspaint.process.id` to the process id of `mspaint.exe` :
```xml
<exec path="C:\Windows\System32\mspaint.exe" pid="mspaint.process.id" />
```

The target property is left untouched if the process cannot be launched.


#### verb attribute: ####

The `verb` attribute defines special directives on how to execute a file or launching the application. For example, the verb `open` or `edit` allows the user to open a document using the associated application. The attribute is optional.
Expand Down
2 changes: 1 addition & 1 deletion src/api/sa_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ sa_error_t sa_plugin_register_action_event(const char* name, sa_plugin_action_ev
// Check if the regitering action is declared by the plugin xml
if (!plugin->SupportAction(name))
{
sa_logging_print_format(SA_LOG_LEVEL_ERROR, SA_API_LOG_IDDENTIFIER, "Failed to register action '%s' event function. The plugin '%s' does not report this condition.", name, plugin->GetPath().c_str());
sa_logging_print_format(SA_LOG_LEVEL_ERROR, SA_API_LOG_IDDENTIFIER, "Failed to register action '%s' event function. The plugin '%s' does not report this action.", name, plugin->GetPath().c_str());
return SA_ERROR_NOT_SUPPORTED;
}

Expand Down
6 changes: 6 additions & 0 deletions src/arguments.debugger.console/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ int _tmain(int argc, _TCHAR* argv[])

// The string that appears in the application's title bar.
tcout << _T("ShellAything Arguments Debugging Application\n");
tcout << "\n";

// Get current directory
TCHAR curdir[MAX_PATH] = { 0 };
GetCurrentDirectory(MAX_PATH, curdir);
tcout << _T("Current directory: ") << curdir << "\n";

tstring_t arguments_desc;
ReadCommandLineArguments(arguments_desc);
Expand Down
13 changes: 12 additions & 1 deletion src/arguments.debugger.window/gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,18 @@ int WINAPI WinMain(
)
#endif
{
ReadCommandLineArguments(window_text);
// Get current directory
TCHAR curdir[MAX_PATH] = { 0 };
GetCurrentDirectory(MAX_PATH, curdir);
window_text += _T("Current directory: ");
window_text += curdir;
window_text += _T("\r\n");

tstring_t args_text;
ReadCommandLineArguments(args_text);

window_text += args_text;
window_text += _T("\r\n");

WNDCLASSEX wcex;

Expand Down
188 changes: 81 additions & 107 deletions src/core/ActionExecute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@
#include "PropertyManager.h"
#include "ObjectFactory.h"
#include "LoggerHelper.h"

#include <windows.h>
#include "SaUtils.h"

#include "tinyxml2.h"
using namespace tinyxml2;
Expand Down Expand Up @@ -120,6 +119,20 @@ namespace shellanything
action->SetTimeout(tmp_str);
}

//parse console
tmp_str = "";
if (ObjectFactory::ParseAttribute(element, "console", true, true, tmp_str, error))
{
action->SetConsole(tmp_str);
}

//parse pid
tmp_str = "";
if (ObjectFactory::ParseAttribute(element, "pid", true, true, tmp_str, error))
{
action->SetPid(tmp_str);
}

//done parsing
return action;
}
Expand All @@ -140,31 +153,6 @@ namespace shellanything
}

bool ActionExecute::Execute(const SelectionContext& context) const
{
PropertyManager& pmgr = PropertyManager::GetInstance();
std::string verb = pmgr.Expand(mVerb);
std::string timeout = pmgr.Expand(mTimeout);

// Validate that timeout is valid
if (!timeout.empty())
{
uint32_t tmp = 0;
bool parsed = ra::strings::Parse(timeout, tmp);
if (!parsed)
{
SA_LOG(ERROR) << "Failed parsing time out value: '" << timeout << "'.";
return false;
}
}

//If a verb was specified, delegate to VerbExecute(). Otherwise, use ProcessExecute().
if (verb.empty())
return ExecuteProcess(context);
else
return ExecuteVerb(context);
}

bool ActionExecute::ExecuteVerb(const SelectionContext& context) const
{
PropertyManager& pmgr = PropertyManager::GetInstance();
std::string path = pmgr.Expand(mPath);
Expand All @@ -173,77 +161,28 @@ namespace shellanything
std::string verb = pmgr.Expand(mVerb);
std::string wait = pmgr.Expand(mWait);
std::string timeout_str = pmgr.Expand(mTimeout);
std::string console = pmgr.Expand(mConsole);
std::string pid = pmgr.Expand(mPid);

std::wstring pathW = ra::unicode::Utf8ToUnicode(path);
std::wstring argumentsW = ra::unicode::Utf8ToUnicode(arguments);
std::wstring basedirW = ra::unicode::Utf8ToUnicode(basedir);
std::wstring verbW = ra::unicode::Utf8ToUnicode(verb);

SHELLEXECUTEINFOW info = { 0 };

info.cbSize = sizeof(SHELLEXECUTEINFOW);

info.fMask |= SEE_MASK_NOCLOSEPROCESS;
info.fMask |= SEE_MASK_NOASYNC;
info.fMask |= SEE_MASK_FLAG_DDEWAIT;

info.hwnd = HWND_DESKTOP;
info.nShow = SW_SHOWDEFAULT;
info.lpFile = pathW.c_str();

//Print execute values in the logs
SA_LOG(INFO) << "Path: " << path;
if (!verb.empty())
{
info.lpVerb = verbW.c_str(); // Verb
SA_LOG(INFO) << "Verb: " << verb;
}
if (!arguments.empty())
{
info.lpParameters = argumentsW.c_str(); // Arguments
SA_LOG(INFO) << "Arguments: " << arguments;
}
if (!basedir.empty())
IProcessLauncherService* process_launcher_service = App::GetInstance().GetProcessLauncherService();
if (process_launcher_service == NULL)
{
info.lpDirectory = basedirW.c_str(); // Default directory
SA_LOG(INFO) << "Basedir: " << basedir;
}

//Execute and get the pid
bool success = (ShellExecuteExW(&info) == TRUE);
if (!success)
return false;
DWORD pId = GetProcessId(info.hProcess);

// Check valid process
success = (pId != ra::process::INVALID_PROCESS_ID);
if (!success)
{
SA_LOG(WARNING) << "Failed to create process.";
SA_LOG(ERROR) << "No Process Launcher service configured for creating process.";
return false;
}
SA_LOG(INFO) << "Process created. PID=" << pId;

// Check for wait exit code
bool wait_success = WaitForExit(pId);
if (!wait_success)
// Validate that timeout is valid
if (!timeout_str.empty())
{
SA_LOG(WARNING) << "Timed out! The process with PID=" << pId << " has failed to exit before the specified timeout.";
return false;
uint32_t tmp = 0;
bool parsed = ra::strings::Parse(timeout_str, tmp);
if (!parsed)
{
SA_LOG(ERROR) << "Failed parsing time out value: '" << timeout_str << "'.";
return false;
}
}

return wait_success;
}

bool ActionExecute::ExecuteProcess(const SelectionContext& context) const
{
PropertyManager& pmgr = PropertyManager::GetInstance();
std::string path = pmgr.Expand(mPath);
std::string basedir = pmgr.Expand(mBaseDir);
std::string arguments = pmgr.Expand(mArguments);
std::string wait = pmgr.Expand(mWait);
std::string timeout_str = pmgr.Expand(mTimeout);

bool basedir_missing = basedir.empty();
bool arguments_missing = arguments.empty();

Expand Down Expand Up @@ -288,6 +227,14 @@ namespace shellanything

//Print execute values in the logs
SA_LOG(INFO) << "Path: " << path;
if (!verb.empty())
{
SA_LOG(INFO) << "Verb: " << verb;
}
if (!console.empty())
{
SA_LOG(INFO) << "Console: " << console;
}
if (!arguments.empty())
{
SA_LOG(INFO) << "Arguments: " << arguments;
Expand All @@ -297,35 +244,42 @@ namespace shellanything
SA_LOG(INFO) << "Basedir: " << basedir;
}

//Execute and get the pid
uint32_t pId = ra::process::INVALID_PROCESS_ID;
if (arguments_missing)
{
pId = ra::process::StartProcessUtf8(path, basedir);
}
else
{
pId = ra::process::StartProcessUtf8(path, basedir, arguments);
}
// Prepare options for process launcher service
PropertyStore options;
if (!verb.empty())
options.SetProperty("verb", verb);
if (!console.empty())
options.SetProperty("console", console);

// Call the process launcher service
IProcessLauncherService::ProcessLaunchResult result = { 0 };
bool success = process_launcher_service->StartProcess(path, basedir, arguments, options, &result);

// Check valid process
bool success = (pId != ra::process::INVALID_PROCESS_ID);
if (!success)
{
SA_LOG(WARNING) << "Failed to create process.";
return false;
}
SA_LOG(INFO) << "Process created. PID=" << pId;
uint32_t pId = result.pId;
SA_LOG(INFO) << "Process created. PID=" << pId << " (" << ToHexString(pId) << ")";

// Save the process id as a property
if (!pid.empty())
pmgr.SetProperty(pid, ra::strings::ToString(pId));

// Check for wait exit code
bool wait_success = WaitForExit(pId);
if (!wait_success)
if (!wait.empty())
{
SA_LOG(WARNING) << "Timed out! The process with PID=" << pId << " has failed to exit before the specified timeout.";
return false;
bool wait_success = WaitForExit(pId);
if (!wait_success)
{
SA_LOG(WARNING) << "Timed out! The process with PID=" << pId << " has failed to exit before the specified timeout.";
return false;
}
}

return wait_success;
return true;
}

bool ActionExecute::WaitForExit(uint32_t pId) const
Expand Down Expand Up @@ -457,4 +411,24 @@ namespace shellanything
mTimeout = value;
}

const std::string& ActionExecute::GetConsole() const
{
return mConsole;
}

void ActionExecute::SetConsole(const std::string& value)
{
mConsole = value;
}

const std::string& ActionExecute::GetPid() const
{
return mPid;
}

void ActionExecute::SetPid(const std::string& value)
{
mPid = value;
}

} //namespace shellanything
Loading

0 comments on commit 2489b88

Please sign in to comment.