Posts: 53
Threads: 21
Joined: Jul 2021
03-03-2024, 01:40 PM
(This post was last modified: 03-03-2024, 01:42 PM by MBaas.)
With gintara's help, I was able to modify an old QM script that can be used to send msgs to my favorite filemanager XYplorer:
void XYSend (string script) {
var w = wnd.findOrRun(of: "xyplorer.exe",run: () => run.it("xyplorer.exe"));
if (w.Is0) return;
WndCopyData.Send<char>(w, 0x00400001, script);
}
NB: check XY's help for "Remote Control"
Posts: 20
Threads: 9
Joined: Jan 2024
Thanks MBaas for sharing your code.
I also love and recommend xyPlorer as filemanager and created a little helper class prXyPlorer to interact with it from LA.
There are two possible ways of interaction between LA and xyPlorer:
- Messages from LA to xyPlorer like your code-snippet does
- Using commandline option /feed= of xyPlorer which my class also implements
Either calls FeedToXyPlorer and SendToXyPlorer are prepared to receive back results from xyPlorer to a registered window as messages coming in via WM_COPYDATA as shown in enclosed sample partial Program class.
Sample calls of helper class could be like following to get back the actual path of first active tab in xyPlorer: - prXyplorer.SendToXyPlorer("tab('get', 'path', 1)");
- prXyplorer.FeedToXyPlorer("tab('get', 'path', 1)");
Any suggestions or extensions of that prXyPlorer-helperclass are welcome.
[code]// class "prXyPlorer.cs"
/*/
role classLibrary;
outputPath %folders.Workspace%\dll;
/*/
using System.Text;
using Au;
using Au.More;
public class prXyplorer {
#region Fields
/// <summary>
/// Startup directory of xyPlorer (working directory with scripts).
/// </summary>
public static string xyDir = @"C:\Users\Siegfried\AppData\Roaming\XYplorer";
/// <summary>
/// Full path to xyPlorer exe.
/// </summary>
public static string xyExe = @"C:\Program Files (x86)\XYplorer\XYplorer.exe";
private static wnd wndReceiver = default;
#endregion Fields
#region Methods
/// <summary>
/// Calls XyPlorer Functions via commandline option '/feed=' and get result back via registered wnd message pump using WM_COPYDATA.
/// </summary>
/// <param name="script"> xyPlorer Script to call </param>
/// <returns> result string should be empty because result comes back via message pump of receiver-window using WM_COPYDATA </returns>
public static string FeedToXyPlorer(string script) {
print.it($"FeedToXyPlorer({script})");
string xyArgs = $"/feed=|{CreateXyPlorerCall(script)}|";
print.it($"run.console([out] string, {xyExe}, {xyArgs}, {xyDir}, Encoding.UTF8)");
int result = run.console(out string xyReturned, xyExe, xyArgs, xyDir, Encoding.UTF8);
print.it($"run.console([out] {xyReturned}) = {result}");
return xyReturned;
}
/// <summary>
/// Return window of running instance of xyPlorer or starting up new instance.
/// </summary>
/// <returns> Return window of xyPlorer </returns>
public static wnd GetXyplorer() {
wnd wndXyplorer = wnd.findOrRun(cn: "ThunderRT6FormDC", run: () => {
print.it($"start xyplorer");
run.it(xyExe, dirEtc: xyDir);
});
return wndXyplorer;
}
/// <summary>
/// Register window which should receive result back via WM_COPYDATA from XyPlorerCalls
/// </summary>
/// <param name="receiver"> </param>
public static void RegisterReceiver(wnd receiver) {
print.it($"RegisterReceiver({receiver})");
wndReceiver = receiver;
}
/// <summary>
/// Call xyPlorer functions and get result back via registered wnd message pump using WM_COPYDATA.
/// </summary>
/// <param name="script"> xyPlorer Script to call </param>
/// <example> prXyplorer.SendToXyPlorer("tab('get', 'path', 1)"); // gets path of first actual tab </example>
/// <returns> result string should be empty because result comes back via message pump of receiver-window using WM_COPYDATA </returns>
public static string SendToXyPlorer(string script) {
wnd w = wnd.findOrRun(of: "xyplorer.exe", run: () => run.it("xyplorer.exe"));
if (w.Is0) return "";
WndCopyData.SendReceive<char>(w, 0x00400001, CreateXyPlorerCall(script), out string receivedFromXyPlorer);
if (!string.IsNullOrEmpty(receivedFromXyPlorer)) {
print.it($"Unexpected data received from xyPlorer {receivedFromXyPlorer}");
}
return receivedFromXyPlorer;
}
private static string CreateXyPlorerCall(string commandlist) {
string result = commandlist;
if (!wndReceiver.Is0) {
result = $"::copydata '{wndReceiver.Handle}', ({commandlist}), 0;";
}
print.it($"CreateXyPlorerCall({commandlist}) = {result}");
return result;
}
#endregion Methods
}
[/code]
[code]// class "xyPlorer-Program.cs"
/*/
pr .\prXyPlorer.cs;
/*/
using System.Runtime.InteropServices;
using Au;
using Au.More;
using Au.Types;
using Microsoft.Win32;
internal partial class Program {
#region Methods
//File/directory triggers. More info in Cookbook.
private static void _FileTriggers() {
using var fw = new FileSystemWatcher(@"C:\Test");
//you can specify various filters etc
//fw.Filters.Add("*.txt");
//fw.Filters.Add("*.xml");
//fw.IncludeSubdirectories = true;
//set one or more events
fw.Created += (o, e) => { print.it($"{e.ChangeType}, {e.Name}, {e.FullPath}"); };
fw.Deleted += (o, e) => { print.it($"{e.ChangeType}, {e.Name}, {e.FullPath}"); };
fw.Renamed += (o, e) => { print.it($"{e.ChangeType}, {e.Name}, {e.OldName}"); };
fw.Changed += (o, e) => { print.it($"{e.ChangeType}, {e.Name}, {e.FullPath}"); };
//fw.Error += (o, e) => { prLog.Log(prLogMode.LogMessage, e.GetException()); };
fw.EnableRaisingEvents = true;
//using var fw2 = new FileSystemWatcher(@"C:\Another directory");
//fw2.Created += ...
// ...
//fw2.EnableRaisingEvents = true;
//then wait or execute any code. Event handlers run in another thread.
wait.ms(-1); //wait forever
//keys.waitForHotkey(0, "Esc"); //another "wait" example
//dialog.show("File change events", buttons: "Stop", x: ^0); //another "wait" example
}
//Process triggers. More info in Cookbook.
private static void _ProcessTriggers() {
foreach (var v in process.triggers()) {
print.it($"{v}");
if (v.Started)
print.it($"{process.getDescription(v.Id)}, {process.getName(v.Id, fullPath: true)}, {process.getCommandLine(v.Id)}");
}
}
//When changed screen (display monitor) count, configuration, DPI (text size, scaling), resolution or other properties.
private void _SystemEvent_DisplaySettingsChanged(object sender, EventArgs e) {
print.it($"{nameof(_SystemEvent_DisplaySettingsChanged)}({sender}, {e})");
}
//When power mode changed (battery/AC etc).
private void _SystemEvent_PowerModeChanged(object sender, PowerModeChangedEventArgs e) {
print.it($"{nameof(_SystemEvent_PowerModeChanged)}({sender}, {e})");
print.it($"PowerMode {e.Mode} Battery={computer.isOnBattery}, {Environment.CurrentManagedThreadId}");
}
//When computer suspending (sleep, hibernate) or resumed.
private void _SystemEvent_ResumeEvent(PowerModes pm) {
print.it($"{nameof(_SystemEvent_ResumeEvent)}({pm}), {Environment.CurrentManagedThreadId}");
}
//When started to log off or shutdown actually.
private void _SystemEvent_SessionEnded(object sender, SessionEndedEventArgs e) {
print.it($"{nameof(_SystemEvent_SessionEnded)}({sender}, {e})");
print.it("SessionEnded", e.Reason);
}
//When trying to log off or shutdown.
private void _SystemEvent_SessionEnding(object sender, SessionEndingEventArgs e) {
print.it($"{nameof(_SystemEvent_SessionEnding)}({sender}, {e})");
print.it("SessionEnding", e.Reason);
//e.Cancel = true;
}
//When switching user sessions, locking/unlocking the computer, etc.
private void _SystemEvent_SessionSwitch(object sender, SessionSwitchEventArgs e) {
print.it($"{nameof(_SystemEvent_SessionSwitch)}({sender}, {e})");
print.it("SessionSwitch", e.Reason);
}
//When changed some system setting. See API SystemParametersInfo.
private void _SystemEvent_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) {
print.it($"{nameof(_SystemEvent_UserPreferenceChanged)}({sender}, {e})");
print.it("UserPreferenceChanged", e.Category);
}
// https://www.libreautomate.com/forum/showthread.php?tid=7574
[Triggers]
private void ClipboardTriggers() {
var w1 = WndUtil.CreateMessageOnlyWindow(static nint (wnd w, int msg, nint wp, nint lp) => {
switch (msg) {
case Api.WM_CLIPBOARDUPDATE:
print.it("WM_CLIPBOARDUPDATE trigger");
//if want to get clipboard text, do it with a delay, else it may interfere with other clipboard programs or scripts
//timer.after(100, _ => {
// var s = clipboard.text;
// prLog.Log(prLogMode.LogMessage, s);
//});
break;
case Api.WM_COPYDATA:
// SR-COPYDATA receive WM_COPYDATA from external program
print.it($"WM_COPYDATA trigger {w} {wp} {lp}");
WndCopyData copyData = new WndCopyData(lp);
string receivedData = copyData.GetString();
print.it($"WM_COPYDATA received '{receivedData}'");
break;
case Api.WM_EXITMENULOOP:
print.it("WM_EXITMENULOOP trigger");
break;
case Api.WM_COMMAND:
print.it($"WM_COMMAND trigger {w} {wp} {lp}");
break;
default:
print.it($"OtherTriggers {msg}");
break;
}
return Api.DefWindowProc(w, msg, wp, lp);
}, "#32770");
// register xyPlorer
prXyplorer.RegisterReceiver(w1);
Api.AddClipboardFormatListener(w1);
}
[Triggers]
private void OtherTriggers() {
//Add other triggers here. More info in Cookbook.
//Click the Run button to apply changes after editing.
computer.suspendResumeEvent += _SystemEvent_ResumeEvent;
SystemEvents.PowerModeChanged += _SystemEvent_PowerModeChanged;
SystemEvents.DisplaySettingsChanged += _SystemEvent_DisplaySettingsChanged;
SystemEvents.UserPreferenceChanged += _SystemEvent_UserPreferenceChanged;
SystemEvents.SessionEnding += _SystemEvent_SessionEnding;
SystemEvents.SessionEnded += _SystemEvent_SessionEnded;
SystemEvents.SessionSwitch += _SystemEvent_SessionSwitch;
//note: all above event handler functions run in other thread.
}
#endregion Methods
#region Classes
private unsafe class Api : NativeApi {
#region Fields
// CLIPBOARD
internal const int WM_CLIPBOARDUPDATE = 0x31D;
internal const int WM_COMMAND = 0x0111;
internal const int WM_CONTEXTMENU = 0x007B;
// COPYDATA
internal const int WM_COPYDATA = 0x004A;
internal const int WM_DDE_EXECUTE = 0x03E8;
// DDE
internal const int WM_DDE_INITIATE = 0x03E0;
internal const int WM_DDE_TERMINATE = 0x03E1;
// MENU
internal const int WM_EXITMENULOOP = 0x0212;
#endregion Fields
#region Methods
[DllImport("user32.dll")]
internal static extern bool AddClipboardFormatListener(wnd hwnd);
[DllImport("user32.dll", EntryPoint = "DefWindowProcW")]
internal static extern nint DefWindowProc(wnd hWnd, int msg, nint wParam, nint lParam);
#endregion Methods
}
[AttributeUsage(AttributeTargets.Method)] private class ToolbarsAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Method)] private class TriggersAttribute : Attribute { }
#endregion Classes
}
[/code]
Posts: 53
Threads: 21
Joined: Jul 2021
wow, that's neat! Getting back data from xy opens new possibilities...
Thanks for sharing that!
Posts: 20
Threads: 9
Joined: Jan 2024
Please find enclosed an updated version. In case no receiving window is registered, the class itself has now an internal functionality to wait and deliver the response from xyPlorer back to the calling function as result.
// class "prXyPlorer.cs"
using System.Runtime.InteropServices;
using System.Text;
using Au;
using Au.More;
using Au.Types;
namespace prGeneral;
public class prXyplorer {
/// <summary>
/// Startup directory of xyPlorer (working directory with scripts).
/// </summary>
public static string xyDir = @"C:\Users\Siegfried\AppData\Roaming\XYplorer";
/// <summary>
/// Full path to xyPlorer exe.
/// </summary>
public static string xyExe = @"C:\Program Files (x86)\XYplorer\XYplorer.exe";
private static wnd wndReceiver = default;
private static wnd defaultReceiver = WndUtil.CreateMessageOnlyWindow(
static nint (wnd w, int msg, nint wp, nint lp) => {
switch (msg) {
case Api.WM_CLIPBOARDUPDATE:
print.it("WM_CLIPBOARDUPDATE trigger");
//if want to get clipboard text, do it with a delay, else it may interfere with other clipboard programs or scripts
//timer.after(100, _ => {
// var s = clipboard.text;
// print.it(s);
//});
break;
case Api.WM_COPYDATA:
// SR-COPYDATA receive WM_COPYDATA from external program
print.it($"WM_COPYDATA trigger {w} {wp} {lp}");
WndCopyData copyData = new WndCopyData(lp);
xyResult = copyData.GetString();
print.it($"WM_COPYDATA received '{xyResult}'");
break;
case Api.WM_EXITMENULOOP:
print.it("WM_EXITMENULOOP trigger");
break;
case Api.WM_COMMAND:
print.it($"WM_COMMAND trigger {w} {wp} {lp}");
break;
default:
print.it($"OtherTriggers {prWindow.MessageName(msg)}={msg}");
break;
}
return Api.DefWindowProc(w, msg, wp, lp);
}, "#32770");
private static string xyResult = "";
private unsafe class Api : NativeApi {
// CLIPBOARD
internal const int WM_CLIPBOARDUPDATE = 0x31D;
internal const int WM_COMMAND = 0x0111;
internal const int WM_CONTEXTMENU = 0x007B;
// COPYDATA
internal const int WM_COPYDATA = 0x004A;
internal const int WM_DDE_EXECUTE = 0x03E8;
// DDE
internal const int WM_DDE_INITIATE = 0x03E0;
internal const int WM_DDE_TERMINATE = 0x03E1;
// MENU
internal const int WM_EXITMENULOOP = 0x0212;
[DllImport("user32.dll")]
internal static extern bool AddClipboardFormatListener(wnd hwnd);
[DllImport("user32.dll", EntryPoint = "DefWindowProcW")]
internal static extern nint DefWindowProc(wnd hWnd, int msg, nint wParam, nint lParam);
}
/// <summary>
/// Calls XyPlorer Functions via commandline option '/feed=' and get result back via registered wnd message pump using WM_COPYDATA.
/// </summary>
/// <param name="script"> xyPlorer Script to call </param>
/// <returns> result string should be empty because result comes back via message pump of receiver-window using WM_COPYDATA </returns>
public static async Task<string> FeedToXyPlorer(
string script,
int waitSecondsForResult = 0
) {
print.it($"FeedToXyPlorer({script})");
PrepareXyPlorerCall();
string xyArgs = $"/feed=|{CreateXyPlorerCall(script)}|";
print.it($"run.console([out] string, {xyExe}, {xyArgs}, {xyDir}, Encoding.UTF8)");
run.thread(() => {
int result = run.console(out string xyReturned, xyExe, xyArgs, xyDir, Encoding.UTF8);
});
bool timeOut = await WaitForXyPlorerResult(waitSecondsForResult);
print.it($"timeOut={timeOut} xyResult={xyResult}");
return xyResult;
}
/// <summary>
/// Return window of running instance of xyPlorer or starting up new instance.
/// </summary>
/// <returns> Return window of xyPlorer </returns>
public static wnd GetXyplorer() {
wnd wndXyplorer = wnd.findOrRun(cn: "ThunderRT6FormDC", run: () => {
print.it($"start xyplorer");
run.it(xyExe, dirEtc: xyDir);
});
return wndXyplorer;
}
/// <summary>
/// Register window which should receive result back via WM_COPYDATA from XyPlorerCalls
/// </summary>
/// <param name="receiver"> </param>
public static void registerReceiverWindow(
wnd receiver
) {
print.it($"RegisterReceiver({receiver.Handle})");
wndReceiver = receiver;
}
/// <summary>
/// Set result from xyPlorer from external receiver
/// </summary>
/// <param name="xyResult"> </param>
public static void setResultFromReceiver(
string resultFromReceiver
) {
xyResult = resultFromReceiver;
}
/// <summary>
/// Call xyPlorer functions and get result back via registered wnd message pump using WM_COPYDATA. Result comes back via message pump of receiver-window using WM_COPYDATA.
/// </summary>
/// <param name="script"> xyPlorer Script to call </param>
/// <param name="waitSecondsForResult"> Time in seconds to wait for result from xyPlorer </param>
/// <example> prXyplorer.QueryXyPlorer("tab('get', 'path', 1)"); // gets path of first actual tab </example>
/// <returns> result string should be empty because </returns>
public static async Task<string> QueryXyPlorer(
string script,
int waitSecondsForResult = 0
) {
wnd w = wnd.findOrRun(of: "xyplorer.exe", run: () => run.it("xyplorer.exe"));
if (w.Is0) return "";
PrepareXyPlorerCall();
WndCopyData.SendReceive<char>(w, 0x00400001, CreateXyPlorerCall(script), out string receivedFromXyPlorer);
print.it($"receivedFromXyPlorer {receivedFromXyPlorer}");
if (!string.IsNullOrEmpty(receivedFromXyPlorer)) {
print.it($"Unexpected data received from xyPlorer {receivedFromXyPlorer}");
}
bool timeOut = await WaitForXyPlorerResult(waitSecondsForResult);
print.it($"timeOut={timeOut} xyResult={xyResult}");
return xyResult;
}
/// <summary>
/// Wait for result from xyPlorer. Result is stored in xyResult.
/// </summary>
/// <param name="waitSecondsForResult"> Maximal waiting time in seconds. </param>
/// <returns> true if timeout was reached </returns>
private static async Task<bool> WaitForXyPlorerResult(
int waitSecondsForResult
) {
return await Task.Run(() => wait.until(-waitSecondsForResult, () => {
print.it($"WaitForXyPlorerResult #{waitSecondsForResult--}s");
if (!string.IsNullOrEmpty(xyResult))
return true;
return false;
}));
}
/// <summary>
/// Reset previous result and register default receiver if extern isnt set already.
/// </summary>
private static void PrepareXyPlorerCall() {
// clear xyResult
setResultFromReceiver("");
// need to register default?
if (wndReceiver.Is0) {
print.it($"Set default receiver {defaultReceiver.Handle}");
registerReceiverWindow(defaultReceiver);
}
}
private static string CreateXyPlorerCall(
string commandlist
) {
string result = commandlist;
if (!wndReceiver.Is0) {
result = $"::copydata '{wndReceiver.Handle}', ({commandlist}), 0;";
} else {
print.it($"wndReceiver {wndReceiver} Is0");
}
print.it($"CreateXyPlorerCall({commandlist}) = {result}");
return result;
}
}
|