Skip to content

Commit 2260c2f

Browse files
committed
Various fixes
- Actions update handlers were wonkey. Now moved most logic to the action activate handler - Updated docs
1 parent 737cfe7 commit 2260c2f

4 files changed

Lines changed: 48 additions & 42 deletions

File tree

README.md

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,51 +14,53 @@ To execute a script, just press `ENTER` or double-click it. After running a scri
1414

1515
An active script will then be monitored for changes. If you modify the script in your favorite text editor and save it, then QScripts will execute the script for you automatically in IDA.
1616

17-
To deactivate a script, just press `Ctrl-D` or right-click and choose `Deactivate script monitor` from the QScripts window. When an active script becomes inactive, it will be shown in *italics*.
17+
To deactivate the script monitor, just press `Ctrl-D` or right-click and choose `Deactivate script monitor` from the QScripts window. When an active script becomes inactive, it will be shown in *italics*.
1818

1919
There are few options that can be configured in QScripts. Just press `Ctrl+E` or right-click and select `Options`:
2020

2121
* Clear message window before execution: clear the message log before re-running the script. Very handy if you to have a fresh output log each time.
2222
* Show file name when execution: display the name of the file that is automatically executed
23-
* Execute the unload script function: A special function, if defined, called `__quick_unload_script` will be invoked before reloading the script. This gives your script a chance to do some cleanup (for example to unregister some hotkeys)
23+
* Execute the unload script function: A special function, if defined in the global scope (usually by your active script), called `__quick_unload_script` will be invoked before reloading the script. This gives your script a chance to do some cleanup (for example to unregister some hotkeys)
2424
* Script monitor interval: controls the refresh rate of the script change monitor. Ideally 500ms is a good amount of time to pick up script changes.
25-
* Allow QScripts execution to be undo-able: The executed script's side effects can be reverted with IDA's Undo
25+
* Allow QScripts execution to be undo-able: The executed script's side effects can be reverted with IDA's Undo.
2626

2727
## Executing a script without activating it
2828

29-
It is possible to execute a script from QScripts without having to activate it. Just press `Shift-ENTER` on a script and it will be executed.
29+
It is possible to execute a script from QScripts without having to activate it. Just press `Shift-Enter` on a script and it will be executed (disregarding if there's an active script or not).
3030

31-
## Working with dependencies
31+
## Managing Dependencies in QScripts
3232

33-
It is possible to instruct QScripts to re-execute the active script if any of its dependent scripts are also modified. To use the automatic dependency system, please create a file named exactly like your active script but with the additional `.deps.qscripts` extension. In that file you put your dependent scripts path.
33+
QScripts offers a feature that allows automatic re-execution of the active script when any of its dependent scripts, undergo modifications.
3434

35-
When using Python, it would be helpful if we can also [reload](https://docs.python.org/3/library/importlib.html#importlib.reload) the changed dependent script from the active script automatically. To do that, simply add the directive line `/reload` along with the desired reload syntax. For example, here's a complete `.deps.qscripts` file with a `reload` directive (for Python 2.x):
35+
### Setting Up Automatic Dependencies
3636

37-
```
38-
/reload reload($basename$)
39-
t2.py
40-
// This is a comment
41-
t3.py
42-
```
37+
To leverage this automatic dependency tracking feature, create a file named identically to your active script, appending `.deps.qscripts` to its name. This file should contain paths to dependent scripts, along with any necessary reload directives.
4338

44-
And for Python 3.x:
39+
Optionally, you can place the `.deps.qscripts` file within a `.qscripts` subfolder, located alongside your active script.
4540

46-
```
47-
/reload import importlib;importlib.reload($basename$);
41+
### Integrating Python Scripts
42+
43+
For projects involving Python, QScripts can automatically [reload](https://docs.python.org/3/library/importlib.html#importlib.reload) any changed dependent Python scripts. Include a `/reload` directive in your `.deps.qscripts` file, followed by the appropriate Python reload syntax.
44+
45+
#### Example `.deps.qscripts` file for `t1.py`:
46+
47+
```plaintext
48+
/reload import importlib; import $basename$; importlib.reload($basename$);
4849
t2.py
4950
# This is a comment
5051
t3.py
5152
```
5253

53-
So what happens now if we have an active file `t1.py` with the dependency file above?
54+
The `t1.py.deps.qscripts` configuration enables the following behavior:
5455

55-
1. Any time `t1.py` changes, it will be automatically re-executed in IDA.
56-
2. If the dependency index file `t1.py.deps.qscripts` is changed, then your new dependencies will be reloaded and the active script will be executed again.
57-
3. If any dependency script file has changed, then the active script will re-execute. If you had a `reload` directive set up, then the modified dependency files will also be reloaded.
56+
1. **Script Auto-Execution**: Changes to `t1.py` trigger its automatic re-execution within the IDA environment.
57+
2. **Dependency Reload**: Modifications to the dependency index file (`t1.py.deps.qscripts`) lead to the reloading of specified dependencies, followed by the re-execution of the active script.
58+
3. **Dependency Script Changes**: Any alteration in a dependency script file causes the active script to re-execute. If a reload directive is present, the modified dependency files are also reloaded. In our cases, if either or both of `t2.py` and `t3.py` are modified, `t1.py` is re-executed and the modified dependencies are reloaded as well.
5859

59-
Please note that if each dependent script file has its own dependency index file, then QScripts will recursively make all the linked dependencies as part of the active script dependencies. In this case, the directives (such as `reload`) are ignored.
60+
**Note**: If a dependent script possesses its own `.deps.qscripts` file, QScripts recursively integrates all linked dependencies into the active script's dependencies. However, specific directives (e.g., `reload`) within these recursive dependencies are disregarded.
6061

6162
See also:
63+
6264
* [Simple dependency example](test_scripts/dependency-test/README.md)
6365
* [Package dependency example](test_scripts/pkg-dependency/README.md)
6466

qscripts.cpp

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
4141
int opt_with_undo = 0;
4242

4343
active_script_info_t selected_script;
44+
script_info_t* action_active_script = nullptr;
4445

4546
struct expand_ctx_t
4647
{
@@ -329,26 +330,30 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
329330

330331
if (!elang->eval_snippet(reload_cmd.c_str(), &err))
331332
{
332-
err.sprnt("failed to execute the reload directive for '%s' with command: %s!\n",
333-
script_file,
334-
reload_cmd.c_str());
333+
err.sprnt(
334+
"QScripts failed to reload script file: '%s'\n"
335+
"Reload command used: %s", script_file, reload_cmd.c_str());
335336
break;
336337
}
337338
return true;
338339
} while (false);
339340

340341
if (!silent)
341-
msg("QScripts failed to reload script file: '%s'\nReload command used: %s", script_file, err.c_str());
342+
msg("%s", err.c_str());
342343

343344
return false;
344345
}
345346

346347
bool execute_script(script_info_t *script_info, bool with_undo)
347348
{
348349
if (with_undo)
349-
return process_ui_action(ACTION_EXECUTE_SCRIPT_WITH_UNDO_ID);
350-
else
351-
return execute_script_sync(script_info);
350+
{
351+
action_active_script = script_info;
352+
auto r = process_ui_action(ACTION_EXECUTE_SCRIPT_WITH_UNDO_ID);
353+
action_active_script = nullptr;
354+
return r;
355+
}
356+
return execute_script_sync(script_info);
352357
}
353358

354359
// Executes a script file
@@ -824,14 +829,14 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
824829
"Deactivate script monitor",
825830
"Ctrl+D",
826831
FO_ACTION_UPDATE([this],
827-
if (!this->is_correct_widget(ctx))
828-
return AST_DISABLE_FOR_WIDGET;
829-
else
830-
return this->is_monitor_active() ? AST_ENABLE : AST_DISABLE;
832+
return this->is_correct_widget(ctx) ? AST_ENABLE_FOR_WIDGET : AST_DISABLE_FOR_WIDGET;
831833
),
832834
FO_ACTION_ACTIVATE([this]) {
833-
this->clear_selected_script();
834-
refresh_chooser(QSCRIPTS_TITLE);
835+
if (this->is_monitor_active())
836+
{
837+
this->clear_selected_script();
838+
refresh_chooser(QSCRIPTS_TITLE);
839+
}
835840
return 1;
836841
},
837842
nullptr,
@@ -843,10 +848,7 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
843848
"Execute selected script",
844849
"Shift+Enter",
845850
FO_ACTION_UPDATE([this],
846-
if (!this->is_correct_widget(ctx))
847-
return AST_DISABLE_FOR_WIDGET;
848-
else
849-
return ctx->chooser_selection.empty() ? AST_DISABLE : AST_ENABLE;
851+
return this->is_correct_widget(ctx) ? AST_ENABLE_FOR_WIDGET : AST_DISABLE_FOR_WIDGET;
850852
),
851853
FO_ACTION_ACTIVATE([this]) {
852854
if (!ctx->chooser_selection.empty())
@@ -862,10 +864,12 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
862864
"QScripts monitor: execute last active script",
863865
"Alt-Shift-X",
864866
FO_ACTION_UPDATE([this],
865-
return this->has_selected_script() ? AST_ENABLE : AST_DISABLE;
867+
return AST_ENABLE_ALWAYS;
866868
),
867869
FO_ACTION_ACTIVATE([this]) {
868-
if (this->has_selected_script())
870+
if (action_active_script != nullptr)
871+
this->execute_script_sync(action_active_script);
872+
else if (this->has_selected_script())
869873
this->execute_script_sync(&selected_script);
870874
return 1;
871875
},

test_scripts/dependency-test/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ This is a dependency test folder.
22

33
The script `t1.py` has its dependency index file which describes the reload directive and one dependency on `t2.py`:
44
```
5-
/reload import importlib;importlib.reload($basename$)
5+
/reload import importlib;import $basename$;importlib.reload($basename$)
66
t2.py
77
```
88

test_scripts/pkg-dependency/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ For this purpose, we can either explicitly specify the package's full path or re
66
# Define package base folder
77
/pkgbase $env:idapyx$
88
# Automatically reload the package's modules when they change
9-
/reload import importlib; from $pkgparentmodname$ import $basename$ as __qscripts_autoreload__; importlib.reload(__qscripts_autoreload__)
9+
/reload import importlib;from $pkgparentmodname$ import $basename$ as __qscripts_autoreload__; importlib.reload(__qscripts_autoreload__)
1010
# Specify the paths to the package modules that need to be reloaded if they change
1111
$pkgbase$/idapyx/bin/pe/rtfuncs.py
1212
$pkgbase$/idapyx/bin/pe/types.py

0 commit comments

Comments
 (0)