Skip to content

Commit 2489b88

Browse files
authored
Merge pull request #179 from end2endzone/feature-issue177
Feature issue177 (for #177 and #178)
2 parents 29ba134 + d0d1bfa commit 2489b88

30 files changed

+1345
-200
lines changed

CHANGES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Changes for 0.10.0
1313
* Fixed issue #161: Create tools to help quickly find a system icon when creating a menu.
1414
* Fixed issue #164: Fails to identify icon for HTML files.
1515
* Fixed issue #167: Improve the quality and accuracy of icon's fileextension attribute resolution (Icon::ResolveFileExtensionIcon()).
16+
* Fixed issue #177: Execute a console program without showing a window.
17+
* Fixed issue #178: Exec action should expose the created process id.
1618

1719

1820
Changes for 0.9.0

UserManual.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,9 @@ The application support multiple types of actions. The list of each specific act
706706

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

709+
**Note:**
710+
When a process is created, ShellAnything will set property `process.id` to the process id of the new launched application.
711+
709712
The <exec> elements have the following attributes:
710713

711714

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

756759
**Note:**
757-
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_.
760+
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.
758761

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

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

774777

775778

779+
776780
#### timeout attribute: ####
777781

778782
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.
@@ -784,6 +788,34 @@ For example, the following launch `cmd.exe` and list files and directories recur
784788

785789

786790

791+
#### console attribute: ####
792+
793+
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.
794+
795+
For example, the following will launch ImageMagick `magick.exe` command line application to convert webp images to jpg :
796+
```xml
797+
<exec wait="true" console="off" path="${imagemagick.path}" arguments="&quot;${selection.path}&quot; &quot;${selection.filename.noext}.jpg&quot;" />
798+
```
799+
The conversion to JPEG format will be performed without showing a console and no window flickering will be visible.
800+
801+
**Note:**
802+
* The _console_ attribute may also affects windowed applications and may hide their main graphical user interface.
803+
* 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.
804+
805+
806+
807+
#### pid attribute: ####
808+
809+
The `pid` attribute defines the name of the property to set with the new launch process id.
810+
811+
For example, the following will sets the property `mspaint.process.id` to the process id of `mspaint.exe` :
812+
```xml
813+
<exec path="C:\Windows\System32\mspaint.exe" pid="mspaint.process.id" />
814+
```
815+
816+
The target property is left untouched if the process cannot be launched.
817+
818+
787819
#### verb attribute: ####
788820

789821
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.

src/api/sa_plugin.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ sa_error_t sa_plugin_register_action_event(const char* name, sa_plugin_action_ev
558558
// Check if the regitering action is declared by the plugin xml
559559
if (!plugin->SupportAction(name))
560560
{
561-
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());
561+
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());
562562
return SA_ERROR_NOT_SUPPORTED;
563563
}
564564

src/arguments.debugger.console/main.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ int _tmain(int argc, _TCHAR* argv[])
2828

2929
// The string that appears in the application's title bar.
3030
tcout << _T("ShellAything Arguments Debugging Application\n");
31+
tcout << "\n";
32+
33+
// Get current directory
34+
TCHAR curdir[MAX_PATH] = { 0 };
35+
GetCurrentDirectory(MAX_PATH, curdir);
36+
tcout << _T("Current directory: ") << curdir << "\n";
3137

3238
tstring_t arguments_desc;
3339
ReadCommandLineArguments(arguments_desc);

src/arguments.debugger.window/gui.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,18 @@ int WINAPI WinMain(
4848
)
4949
#endif
5050
{
51-
ReadCommandLineArguments(window_text);
51+
// Get current directory
52+
TCHAR curdir[MAX_PATH] = { 0 };
53+
GetCurrentDirectory(MAX_PATH, curdir);
54+
window_text += _T("Current directory: ");
55+
window_text += curdir;
56+
window_text += _T("\r\n");
57+
58+
tstring_t args_text;
59+
ReadCommandLineArguments(args_text);
60+
61+
window_text += args_text;
62+
window_text += _T("\r\n");
5263

5364
WNDCLASSEX wcex;
5465

src/core/ActionExecute.cpp

Lines changed: 81 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@
3030
#include "PropertyManager.h"
3131
#include "ObjectFactory.h"
3232
#include "LoggerHelper.h"
33-
34-
#include <windows.h>
33+
#include "SaUtils.h"
3534

3635
#include "tinyxml2.h"
3736
using namespace tinyxml2;
@@ -120,6 +119,20 @@ namespace shellanything
120119
action->SetTimeout(tmp_str);
121120
}
122121

122+
//parse console
123+
tmp_str = "";
124+
if (ObjectFactory::ParseAttribute(element, "console", true, true, tmp_str, error))
125+
{
126+
action->SetConsole(tmp_str);
127+
}
128+
129+
//parse pid
130+
tmp_str = "";
131+
if (ObjectFactory::ParseAttribute(element, "pid", true, true, tmp_str, error))
132+
{
133+
action->SetPid(tmp_str);
134+
}
135+
123136
//done parsing
124137
return action;
125138
}
@@ -140,31 +153,6 @@ namespace shellanything
140153
}
141154

142155
bool ActionExecute::Execute(const SelectionContext& context) const
143-
{
144-
PropertyManager& pmgr = PropertyManager::GetInstance();
145-
std::string verb = pmgr.Expand(mVerb);
146-
std::string timeout = pmgr.Expand(mTimeout);
147-
148-
// Validate that timeout is valid
149-
if (!timeout.empty())
150-
{
151-
uint32_t tmp = 0;
152-
bool parsed = ra::strings::Parse(timeout, tmp);
153-
if (!parsed)
154-
{
155-
SA_LOG(ERROR) << "Failed parsing time out value: '" << timeout << "'.";
156-
return false;
157-
}
158-
}
159-
160-
//If a verb was specified, delegate to VerbExecute(). Otherwise, use ProcessExecute().
161-
if (verb.empty())
162-
return ExecuteProcess(context);
163-
else
164-
return ExecuteVerb(context);
165-
}
166-
167-
bool ActionExecute::ExecuteVerb(const SelectionContext& context) const
168156
{
169157
PropertyManager& pmgr = PropertyManager::GetInstance();
170158
std::string path = pmgr.Expand(mPath);
@@ -173,77 +161,28 @@ namespace shellanything
173161
std::string verb = pmgr.Expand(mVerb);
174162
std::string wait = pmgr.Expand(mWait);
175163
std::string timeout_str = pmgr.Expand(mTimeout);
164+
std::string console = pmgr.Expand(mConsole);
165+
std::string pid = pmgr.Expand(mPid);
176166

177-
std::wstring pathW = ra::unicode::Utf8ToUnicode(path);
178-
std::wstring argumentsW = ra::unicode::Utf8ToUnicode(arguments);
179-
std::wstring basedirW = ra::unicode::Utf8ToUnicode(basedir);
180-
std::wstring verbW = ra::unicode::Utf8ToUnicode(verb);
181-
182-
SHELLEXECUTEINFOW info = { 0 };
183-
184-
info.cbSize = sizeof(SHELLEXECUTEINFOW);
185-
186-
info.fMask |= SEE_MASK_NOCLOSEPROCESS;
187-
info.fMask |= SEE_MASK_NOASYNC;
188-
info.fMask |= SEE_MASK_FLAG_DDEWAIT;
189-
190-
info.hwnd = HWND_DESKTOP;
191-
info.nShow = SW_SHOWDEFAULT;
192-
info.lpFile = pathW.c_str();
193-
194-
//Print execute values in the logs
195-
SA_LOG(INFO) << "Path: " << path;
196-
if (!verb.empty())
197-
{
198-
info.lpVerb = verbW.c_str(); // Verb
199-
SA_LOG(INFO) << "Verb: " << verb;
200-
}
201-
if (!arguments.empty())
202-
{
203-
info.lpParameters = argumentsW.c_str(); // Arguments
204-
SA_LOG(INFO) << "Arguments: " << arguments;
205-
}
206-
if (!basedir.empty())
167+
IProcessLauncherService* process_launcher_service = App::GetInstance().GetProcessLauncherService();
168+
if (process_launcher_service == NULL)
207169
{
208-
info.lpDirectory = basedirW.c_str(); // Default directory
209-
SA_LOG(INFO) << "Basedir: " << basedir;
210-
}
211-
212-
//Execute and get the pid
213-
bool success = (ShellExecuteExW(&info) == TRUE);
214-
if (!success)
215-
return false;
216-
DWORD pId = GetProcessId(info.hProcess);
217-
218-
// Check valid process
219-
success = (pId != ra::process::INVALID_PROCESS_ID);
220-
if (!success)
221-
{
222-
SA_LOG(WARNING) << "Failed to create process.";
170+
SA_LOG(ERROR) << "No Process Launcher service configured for creating process.";
223171
return false;
224172
}
225-
SA_LOG(INFO) << "Process created. PID=" << pId;
226173

227-
// Check for wait exit code
228-
bool wait_success = WaitForExit(pId);
229-
if (!wait_success)
174+
// Validate that timeout is valid
175+
if (!timeout_str.empty())
230176
{
231-
SA_LOG(WARNING) << "Timed out! The process with PID=" << pId << " has failed to exit before the specified timeout.";
232-
return false;
177+
uint32_t tmp = 0;
178+
bool parsed = ra::strings::Parse(timeout_str, tmp);
179+
if (!parsed)
180+
{
181+
SA_LOG(ERROR) << "Failed parsing time out value: '" << timeout_str << "'.";
182+
return false;
183+
}
233184
}
234185

235-
return wait_success;
236-
}
237-
238-
bool ActionExecute::ExecuteProcess(const SelectionContext& context) const
239-
{
240-
PropertyManager& pmgr = PropertyManager::GetInstance();
241-
std::string path = pmgr.Expand(mPath);
242-
std::string basedir = pmgr.Expand(mBaseDir);
243-
std::string arguments = pmgr.Expand(mArguments);
244-
std::string wait = pmgr.Expand(mWait);
245-
std::string timeout_str = pmgr.Expand(mTimeout);
246-
247186
bool basedir_missing = basedir.empty();
248187
bool arguments_missing = arguments.empty();
249188

@@ -288,6 +227,14 @@ namespace shellanything
288227

289228
//Print execute values in the logs
290229
SA_LOG(INFO) << "Path: " << path;
230+
if (!verb.empty())
231+
{
232+
SA_LOG(INFO) << "Verb: " << verb;
233+
}
234+
if (!console.empty())
235+
{
236+
SA_LOG(INFO) << "Console: " << console;
237+
}
291238
if (!arguments.empty())
292239
{
293240
SA_LOG(INFO) << "Arguments: " << arguments;
@@ -297,35 +244,42 @@ namespace shellanything
297244
SA_LOG(INFO) << "Basedir: " << basedir;
298245
}
299246

300-
//Execute and get the pid
301-
uint32_t pId = ra::process::INVALID_PROCESS_ID;
302-
if (arguments_missing)
303-
{
304-
pId = ra::process::StartProcessUtf8(path, basedir);
305-
}
306-
else
307-
{
308-
pId = ra::process::StartProcessUtf8(path, basedir, arguments);
309-
}
247+
// Prepare options for process launcher service
248+
PropertyStore options;
249+
if (!verb.empty())
250+
options.SetProperty("verb", verb);
251+
if (!console.empty())
252+
options.SetProperty("console", console);
253+
254+
// Call the process launcher service
255+
IProcessLauncherService::ProcessLaunchResult result = { 0 };
256+
bool success = process_launcher_service->StartProcess(path, basedir, arguments, options, &result);
310257

311258
// Check valid process
312-
bool success = (pId != ra::process::INVALID_PROCESS_ID);
313259
if (!success)
314260
{
315261
SA_LOG(WARNING) << "Failed to create process.";
316262
return false;
317263
}
318-
SA_LOG(INFO) << "Process created. PID=" << pId;
264+
uint32_t pId = result.pId;
265+
SA_LOG(INFO) << "Process created. PID=" << pId << " (" << ToHexString(pId) << ")";
319266

267+
// Save the process id as a property
268+
if (!pid.empty())
269+
pmgr.SetProperty(pid, ra::strings::ToString(pId));
270+
320271
// Check for wait exit code
321-
bool wait_success = WaitForExit(pId);
322-
if (!wait_success)
272+
if (!wait.empty())
323273
{
324-
SA_LOG(WARNING) << "Timed out! The process with PID=" << pId << " has failed to exit before the specified timeout.";
325-
return false;
274+
bool wait_success = WaitForExit(pId);
275+
if (!wait_success)
276+
{
277+
SA_LOG(WARNING) << "Timed out! The process with PID=" << pId << " has failed to exit before the specified timeout.";
278+
return false;
279+
}
326280
}
327281

328-
return wait_success;
282+
return true;
329283
}
330284

331285
bool ActionExecute::WaitForExit(uint32_t pId) const
@@ -457,4 +411,24 @@ namespace shellanything
457411
mTimeout = value;
458412
}
459413

414+
const std::string& ActionExecute::GetConsole() const
415+
{
416+
return mConsole;
417+
}
418+
419+
void ActionExecute::SetConsole(const std::string& value)
420+
{
421+
mConsole = value;
422+
}
423+
424+
const std::string& ActionExecute::GetPid() const
425+
{
426+
return mPid;
427+
}
428+
429+
void ActionExecute::SetPid(const std::string& value)
430+
{
431+
mPid = value;
432+
}
433+
460434
} //namespace shellanything

0 commit comments

Comments
 (0)