Skip to content

Commit 23f6342

Browse files
authored
Merge branch 'dev' into dev4
2 parents 25684a9 + 6c22aea commit 23f6342

File tree

13 files changed

+210
-50
lines changed

13 files changed

+210
-50
lines changed

Flow.Launcher.Core/Plugin/PythonPlugin.cs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Diagnostics;
1+
using System;
2+
using System.Diagnostics;
23
using System.IO;
34
using System.Text.Json;
45
using System.Threading;
@@ -25,14 +26,13 @@ public PythonPlugin(string filename)
2526

2627
var path = Path.Combine(Constant.ProgramDirectory, JsonRPC);
2728
_startInfo.EnvironmentVariables["PYTHONPATH"] = path;
29+
// Prevent Python from writing .py[co] files.
30+
// Because .pyc contains location infos which will prevent python portable.
31+
_startInfo.EnvironmentVariables["PYTHONDONTWRITEBYTECODE"] = "1";
2832

2933
_startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
3034
_startInfo.EnvironmentVariables["FLOW_PROGRAM_DIRECTORY"] = Constant.ProgramDirectory;
3135
_startInfo.EnvironmentVariables["FLOW_APPLICATION_DIRECTORY"] = Constant.ApplicationDirectory;
32-
33-
34-
//Add -B flag to tell python don't write .py[co] files. Because .pyc contains location infos which will prevent python portable
35-
_startInfo.ArgumentList.Add("-B");
3636
}
3737

3838
protected override Task<Stream> RequestAsync(JsonRPCRequestModel request, CancellationToken token = default)
@@ -50,10 +50,53 @@ protected override string Request(JsonRPCRequestModel rpcRequest, CancellationTo
5050
// TODO: Async Action
5151
return Execute(_startInfo);
5252
}
53+
5354
public override async Task InitAsync(PluginInitContext context)
5455
{
55-
_startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
56-
_startInfo.ArgumentList.Add("");
56+
// Run .py files via `-c <code>`
57+
if (context.CurrentPluginMetadata.ExecuteFilePath.EndsWith(".py", StringComparison.OrdinalIgnoreCase))
58+
{
59+
var rootDirectory = context.CurrentPluginMetadata.PluginDirectory;
60+
var libDirectory = Path.Combine(rootDirectory, "lib");
61+
var libPyWin32Directory = Path.Combine(libDirectory, "win32");
62+
var libPyWin32LibDirectory = Path.Combine(libPyWin32Directory, "lib");
63+
var pluginDirectory = Path.Combine(rootDirectory, "plugin");
64+
65+
// This makes it easier for plugin authors to import their own modules.
66+
// They won't have to add `.`, `./lib`, or `./plugin` to their sys.path manually.
67+
// Instead of running the .py file directly, we pass the code we want to run as a CLI argument.
68+
// This code sets sys.path for the plugin author and then runs the .py file via runpy.
69+
_startInfo.ArgumentList.Add("-c");
70+
_startInfo.ArgumentList.Add(
71+
$"""
72+
import sys
73+
sys.path.append(r'{rootDirectory}')
74+
sys.path.append(r'{libDirectory}')
75+
sys.path.append(r'{libPyWin32LibDirectory}')
76+
sys.path.append(r'{libPyWin32Directory}')
77+
sys.path.append(r'{pluginDirectory}')
78+
79+
import runpy
80+
runpy.run_path(r'{context.CurrentPluginMetadata.ExecuteFilePath}', None, '__main__')
81+
"""
82+
);
83+
// Plugins always expect the JSON data to be in the third argument
84+
// (we're always setting it as _startInfo.ArgumentList[2] = ...).
85+
_startInfo.ArgumentList.Add("");
86+
}
87+
// Run .pyz files as is
88+
else
89+
{
90+
// No need for -B flag because we're using PYTHONDONTWRITEBYTECODE env variable now,
91+
// but the plugins still expect data to be sent as the third argument, so we're keeping
92+
// the flag here, even though it's not necessary anymore.
93+
_startInfo.ArgumentList.Add("-B");
94+
_startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
95+
// Plugins always expect the JSON data to be in the third argument
96+
// (we're always setting it as _startInfo.ArgumentList[2] = ...).
97+
_startInfo.ArgumentList.Add("");
98+
}
99+
57100
await base.InitAsync(context);
58101
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
59102
}

Flow.Launcher.Core/Plugin/PythonPluginV2.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,45 @@ public PythonPluginV2(string filename)
2626

2727
var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
2828
StartInfo.EnvironmentVariables["PYTHONPATH"] = path;
29-
30-
//Add -B flag to tell python don't write .py[co] files. Because .pyc contains location infos which will prevent python portable
31-
StartInfo.ArgumentList.Add("-B");
29+
StartInfo.EnvironmentVariables["PYTHONDONTWRITEBYTECODE"] = "1";
3230
}
3331

3432
public override async Task InitAsync(PluginInitContext context)
3533
{
36-
StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
34+
// Run .py files via `-c <code>`
35+
if (context.CurrentPluginMetadata.ExecuteFilePath.EndsWith(".py", StringComparison.OrdinalIgnoreCase))
36+
{
37+
var rootDirectory = context.CurrentPluginMetadata.PluginDirectory;
38+
var libDirectory = Path.Combine(rootDirectory, "lib");
39+
var libPyWin32Directory = Path.Combine(libDirectory, "win32");
40+
var libPyWin32LibDirectory = Path.Combine(libPyWin32Directory, "lib");
41+
var pluginDirectory = Path.Combine(rootDirectory, "plugin");
42+
var filePath = context.CurrentPluginMetadata.ExecuteFilePath;
43+
44+
// This makes it easier for plugin authors to import their own modules.
45+
// They won't have to add `.`, `./lib`, or `./plugin` to their sys.path manually.
46+
// Instead of running the .py file directly, we pass the code we want to run as a CLI argument.
47+
// This code sets sys.path for the plugin author and then runs the .py file via runpy.
48+
StartInfo.ArgumentList.Add("-c");
49+
StartInfo.ArgumentList.Add(
50+
$"""
51+
import sys
52+
sys.path.append(r'{rootDirectory}')
53+
sys.path.append(r'{libDirectory}')
54+
sys.path.append(r'{libPyWin32LibDirectory}')
55+
sys.path.append(r'{libPyWin32Directory}')
56+
sys.path.append(r'{pluginDirectory}')
57+
58+
import runpy
59+
runpy.run_path(r'{filePath}', None, '__main__')
60+
"""
61+
);
62+
}
63+
// Run .pyz files as is
64+
else
65+
{
66+
StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
67+
}
3768
await base.InitAsync(context);
3869
}
3970

Flow.Launcher.Infrastructure/FileExplorerHelper.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,20 @@ public static string GetActiveExplorerPath()
1515
{
1616
var explorerWindow = GetActiveExplorer();
1717
string locationUrl = explorerWindow?.LocationURL;
18-
return !string.IsNullOrEmpty(locationUrl) ? new Uri(locationUrl).LocalPath + "\\" : null;
18+
return !string.IsNullOrEmpty(locationUrl) ? GetDirectoryPath(new Uri(locationUrl).LocalPath) : null;
19+
}
20+
21+
/// <summary>
22+
/// Get directory path from a file path
23+
/// </summary>
24+
private static string GetDirectoryPath(string path)
25+
{
26+
if (!path.EndsWith("\\"))
27+
{
28+
return path + "\\";
29+
}
30+
31+
return path;
1932
}
2033

2134
/// <summary>

Flow.Launcher.Plugin/Result.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,16 @@ public Result Clone()
185185
TitleHighlightData = TitleHighlightData,
186186
OriginQuery = OriginQuery,
187187
PluginDirectory = PluginDirectory,
188+
ContextData = ContextData,
189+
PluginID = PluginID,
190+
TitleToolTip = TitleToolTip,
191+
SubTitleToolTip = SubTitleToolTip,
192+
PreviewPanel = PreviewPanel,
193+
ProgressBar = ProgressBar,
194+
ProgressBarColor = ProgressBarColor,
195+
Preview = Preview,
196+
AddSelectedCount = AddSelectedCount,
197+
RecordKey = RecordKey
188198
};
189199
}
190200

@@ -252,6 +262,13 @@ public ValueTask<bool> ExecuteAsync(ActionContext context)
252262
/// </summary>
253263
public const int MaxScore = int.MaxValue;
254264

265+
/// <summary>
266+
/// The key to identify the record. This is used when FL checks whether the result is the topmost record. Or FL calculates the hashcode of the result for user selected records.
267+
/// This can be useful when your plugin will change the Title or SubTitle of the result dynamically.
268+
/// If the plugin does not specific this, FL just uses Title and SubTitle to identify this result.
269+
/// </summary>
270+
public string RecordKey { get; set; } = string.Empty;
271+
255272
/// <summary>
256273
/// Info of the preview section of a <see cref="Result"/>
257274
/// </summary>

Flow.Launcher/Storage/TopMostRecord.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ internal void AddOrUpdate(Result result)
3333
{
3434
PluginID = result.PluginID,
3535
Title = result.Title,
36-
SubTitle = result.SubTitle
36+
SubTitle = result.SubTitle,
37+
RecordKey = result.RecordKey
3738
};
3839
records.AddOrUpdate(result.OriginQuery.RawQuery, record, (key, oldValue) => record);
3940
}
@@ -49,12 +50,21 @@ public class Record
4950
public string Title { get; set; }
5051
public string SubTitle { get; set; }
5152
public string PluginID { get; set; }
53+
public string RecordKey { get; set; }
5254

5355
public bool Equals(Result r)
5456
{
55-
return Title == r.Title
56-
&& SubTitle == r.SubTitle
57-
&& PluginID == r.PluginID;
57+
if (string.IsNullOrEmpty(RecordKey) || string.IsNullOrEmpty(r.RecordKey))
58+
{
59+
return Title == r.Title
60+
&& SubTitle == r.SubTitle
61+
&& PluginID == r.PluginID;
62+
}
63+
else
64+
{
65+
return RecordKey == r.RecordKey
66+
&& PluginID == r.PluginID;
67+
}
5868
}
5969
}
6070
}

Flow.Launcher/Storage/UserSelectedRecord.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ public class UserSelectedRecord
1515
[JsonInclude, JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
1616
public Dictionary<string, int> records { get; private set; }
1717

18-
1918
public UserSelectedRecord()
2019
{
2120
recordsWithQuery = new Dictionary<int, int>();
@@ -45,8 +44,15 @@ private static int GenerateStaticHashCode(string s, int start = HASH_INITIAL)
4544

4645
private static int GenerateResultHashCode(Result result)
4746
{
48-
int hashcode = GenerateStaticHashCode(result.Title);
49-
return GenerateStaticHashCode(result.SubTitle, hashcode);
47+
if (string.IsNullOrEmpty(result.RecordKey))
48+
{
49+
int hashcode = GenerateStaticHashCode(result.Title);
50+
return GenerateStaticHashCode(result.SubTitle, hashcode);
51+
}
52+
else
53+
{
54+
return GenerateStaticHashCode(result.RecordKey);
55+
}
5056
}
5157

5258
private static int GenerateQueryAndResultHashCode(Query query, Result result)
@@ -58,8 +64,16 @@ private static int GenerateQueryAndResultHashCode(Query query, Result result)
5864

5965
int hashcode = GenerateStaticHashCode(query.ActionKeyword);
6066
hashcode = GenerateStaticHashCode(query.Search, hashcode);
61-
hashcode = GenerateStaticHashCode(result.Title, hashcode);
62-
hashcode = GenerateStaticHashCode(result.SubTitle, hashcode);
67+
68+
if (string.IsNullOrEmpty(result.RecordKey))
69+
{
70+
hashcode = GenerateStaticHashCode(result.Title, hashcode);
71+
hashcode = GenerateStaticHashCode(result.SubTitle, hashcode);
72+
}
73+
else
74+
{
75+
hashcode = GenerateStaticHashCode(result.RecordKey, hashcode);
76+
}
6377

6478
return hashcode;
6579
}

Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ internal List<Result> InstallFromWeb(string url)
534534
return false;
535535
}
536536

537-
Application.Current.MainWindow.Hide();
537+
Context.API.HideMainWindow();
538538
_ = InstallOrUpdateAsync(plugin);
539539

540540
return ShouldHideWindow;
@@ -572,7 +572,7 @@ internal List<Result> InstallFromLocalPath(string localPath)
572572
return false;
573573
}
574574

575-
Application.Current.MainWindow.Hide();
575+
Context.API.HideMainWindow();
576576
_ = InstallOrUpdateAsync(plugin);
577577

578578
return ShouldHideWindow;
@@ -626,7 +626,7 @@ internal async ValueTask<List<Result>> RequestInstallOrUpdateAsync(string search
626626
return ShouldHideWindow;
627627
}
628628

629-
Application.Current.MainWindow.Hide();
629+
Context.API.HideMainWindow();
630630
_ = InstallOrUpdateAsync(x); // No need to wait
631631
return ShouldHideWindow;
632632
},
@@ -703,7 +703,7 @@ internal List<Result> RequestUninstall(string search)
703703
Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
704704
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
705705
{
706-
Application.Current.MainWindow.Hide();
706+
Context.API.HideMainWindow();
707707
Uninstall(x.Metadata);
708708
if (Settings.AutoRestartAfterChanging)
709709
{

Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<system:String x:Key="flowlauncher_plugin_cmd_press_any_key_to_close">Press any key to close this window...</system:String>
88
<system:String x:Key="flowlauncher_plugin_cmd_leave_cmd_open">Do not close Command Prompt after command execution</system:String>
99
<system:String x:Key="flowlauncher_plugin_cmd_always_run_as_administrator">Always run as administrator</system:String>
10+
<system:String x:Key="flowlauncher_plugin_cmd_use_windows_terminal">Use Windows Terminal</system:String>
1011
<system:String x:Key="flowlauncher_plugin_cmd_run_as_different_user">Run as different user</system:String>
1112
<system:String x:Key="flowlauncher_plugin_cmd_plugin_name">Shell</system:String>
1213
<system:String x:Key="flowlauncher_plugin_cmd_plugin_description">Allows to execute system commands from Flow Launcher</system:String>
@@ -15,4 +16,4 @@
1516
<system:String x:Key="flowlauncher_plugin_cmd_run_as_administrator">Run As Administrator</system:String>
1617
<system:String x:Key="flowlauncher_plugin_cmd_copy">Copy the command</system:String>
1718
<system:String x:Key="flowlauncher_plugin_cmd_history">Only show number of most used commands:</system:String>
18-
</ResourceDictionary>
19+
</ResourceDictionary>

Plugins/Flow.Launcher.Plugin.Shell/Main.cs

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -202,28 +202,31 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin
202202
{
203203
case Shell.Cmd:
204204
{
205-
info.FileName = "cmd.exe";
206-
info.Arguments = $"{(_settings.LeaveShellOpen ? "/k" : "/c")} {command} {(_settings.CloseShellAfterPress ? $"&& echo {context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")} && pause > nul /c" : "")}";
207-
208-
//// Use info.Arguments instead of info.ArgumentList to enable users better control over the arguments they are writing.
209-
//// Previous code using ArgumentList, commands needed to be separated correctly:
210-
//// Incorrect:
211-
// info.ArgumentList.Add(_settings.LeaveShellOpen ? "/k" : "/c");
212-
// info.ArgumentList.Add(command); //<== info.ArgumentList.Add("mkdir \"c:\\test new\"");
213-
214-
//// Correct version should be:
215-
//info.ArgumentList.Add(_settings.LeaveShellOpen ? "/k" : "/c");
216-
//info.ArgumentList.Add("mkdir");
217-
//info.ArgumentList.Add(@"c:\test new");
218-
219-
//https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.argumentlist?view=net-6.0#remarks
205+
if (_settings.UseWindowsTerminal)
206+
{
207+
info.FileName = "wt.exe";
208+
info.ArgumentList.Add("cmd");
209+
}
210+
else
211+
{
212+
info.FileName = "cmd.exe";
213+
}
220214

215+
info.ArgumentList.Add($"{(_settings.LeaveShellOpen ? "/k" : "/c")} {command} {(_settings.CloseShellAfterPress ? $"&& echo {context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")} && pause > nul /c" : "")}");
221216
break;
222217
}
223218

224219
case Shell.Powershell:
225220
{
226-
info.FileName = "powershell.exe";
221+
if (_settings.UseWindowsTerminal)
222+
{
223+
info.FileName = "wt.exe";
224+
info.ArgumentList.Add("powershell");
225+
}
226+
else
227+
{
228+
info.FileName = "powershell.exe";
229+
}
227230
if (_settings.LeaveShellOpen)
228231
{
229232
info.ArgumentList.Add("-NoExit");
@@ -232,21 +235,28 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin
232235
else
233236
{
234237
info.ArgumentList.Add("-Command");
235-
info.ArgumentList.Add($"{command}; {(_settings.CloseShellAfterPress ? $"Write-Host '{context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'; [System.Console]::ReadKey(); exit" : "")}");
238+
info.ArgumentList.Add($"{command}\\; {(_settings.CloseShellAfterPress ? $"Write-Host '{context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'\\; [System.Console]::ReadKey()\\; exit" : "")}");
236239
}
237240
break;
238241
}
239242

240243
case Shell.Pwsh:
241244
{
242-
info.FileName = "pwsh.exe";
245+
if (_settings.UseWindowsTerminal)
246+
{
247+
info.FileName = "wt.exe";
248+
info.ArgumentList.Add("pwsh");
249+
}
250+
else
251+
{
252+
info.FileName = "pwsh.exe";
253+
}
243254
if (_settings.LeaveShellOpen)
244255
{
245256
info.ArgumentList.Add("-NoExit");
246257
}
247258
info.ArgumentList.Add("-Command");
248-
info.ArgumentList.Add($"{command}; {(_settings.CloseShellAfterPress ? $"Write-Host '{context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'; [System.Console]::ReadKey(); exit" : "")}");
249-
259+
info.ArgumentList.Add($"{command}\\; {(_settings.CloseShellAfterPress ? $"Write-Host '{context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'\\; [System.Console]::ReadKey()\\; exit" : "")}");
250260
break;
251261
}
252262

0 commit comments

Comments
 (0)