Skip to content

Commit 2978226

Browse files
authored
Merge branch 'dev' into dev
2 parents 6c081e1 + 81c068e commit 2978226

File tree

22 files changed

+546
-81
lines changed

22 files changed

+546
-81
lines changed

Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Flow.Launcher.Infrastructure.Logger;
1+
using Flow.Launcher.Infrastructure.Logger;
22
using System;
33
using System.Collections.Generic;
44
using System.Threading;
@@ -21,7 +21,7 @@ public static class PluginsManifest
2121

2222
public static List<UserPlugin> UserPlugins { get; private set; }
2323

24-
public static async Task UpdateManifestAsync(CancellationToken token = default, bool usePrimaryUrlOnly = false)
24+
public static async Task<bool> UpdateManifestAsync(CancellationToken token = default, bool usePrimaryUrlOnly = false)
2525
{
2626
try
2727
{
@@ -31,8 +31,14 @@ public static async Task UpdateManifestAsync(CancellationToken token = default,
3131
{
3232
var results = await mainPluginStore.FetchAsync(token, usePrimaryUrlOnly).ConfigureAwait(false);
3333

34-
UserPlugins = results;
35-
lastFetchedAt = DateTime.Now;
34+
// If the results are empty, we shouldn't update the manifest because the results are invalid.
35+
if (results.Count != 0)
36+
{
37+
UserPlugins = results;
38+
lastFetchedAt = DateTime.Now;
39+
40+
return true;
41+
}
3642
}
3743
}
3844
catch (Exception e)
@@ -43,6 +49,8 @@ public static async Task UpdateManifestAsync(CancellationToken token = default,
4349
{
4450
manifestUpdateLock.Release();
4551
}
52+
53+
return false;
4654
}
4755
}
4856
}

Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.ComponentModel;
43
using System.Diagnostics.CodeAnalysis;
54
using System.IO;
65
using System.Runtime.CompilerServices;
@@ -121,10 +120,10 @@ public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = def
121120
return _api.HttpGetStreamAsync(url, token);
122121
}
123122

124-
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath,
123+
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null,
125124
CancellationToken token = default)
126125
{
127-
return _api.HttpDownloadAsync(url, filePath, token);
126+
return _api.HttpDownloadAsync(url, filePath, reportProgress, token);
128127
}
129128

130129
public void AddActionKeyword(string pluginId, string newActionKeyword)
@@ -162,16 +161,19 @@ public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null
162161
_api.OpenDirectory(DirectoryPath, FileNameOrFilePath);
163162
}
164163

165-
166164
public void OpenUrl(string url, bool? inPrivate = null)
167165
{
168166
_api.OpenUrl(url, inPrivate);
169167
}
170168

171-
172169
public void OpenAppUri(string appUri)
173170
{
174171
_api.OpenAppUri(appUri);
175172
}
173+
174+
public void BackToQueryResults()
175+
{
176+
_api.BackToQueryResults();
177+
}
176178
}
177179
}

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.Infrastructure/Http/Http.cs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,50 @@ var userName when string.IsNullOrEmpty(userName) =>
8383
}
8484
}
8585

86-
public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default)
86+
public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null, CancellationToken token = default)
8787
{
8888
try
8989
{
9090
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
91+
9192
if (response.StatusCode == HttpStatusCode.OK)
9293
{
93-
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
94-
await response.Content.CopyToAsync(fileStream, token);
94+
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
95+
var canReportProgress = totalBytes != -1;
96+
97+
if (canReportProgress && reportProgress != null)
98+
{
99+
await using var contentStream = await response.Content.ReadAsStreamAsync(token);
100+
await using var fileStream = new FileStream(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, 8192, true);
101+
102+
var buffer = new byte[8192];
103+
long totalRead = 0;
104+
int read;
105+
double progressValue = 0;
106+
107+
reportProgress(0);
108+
109+
while ((read = await contentStream.ReadAsync(buffer, token)) > 0)
110+
{
111+
await fileStream.WriteAsync(buffer.AsMemory(0, read), token);
112+
totalRead += read;
113+
114+
progressValue = totalRead * 100.0 / totalBytes;
115+
116+
if (token.IsCancellationRequested)
117+
return;
118+
else
119+
reportProgress(progressValue);
120+
}
121+
122+
if (progressValue < 100)
123+
reportProgress(100);
124+
}
125+
else
126+
{
127+
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
128+
await response.Content.CopyToAsync(fileStream, token);
129+
}
95130
}
96131
else
97132
{

Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Flow.Launcher.Plugin.SharedModels;
1+
using Flow.Launcher.Plugin.SharedModels;
22
using JetBrains.Annotations;
33
using System;
44
using System.Collections.Generic;
@@ -181,9 +181,13 @@ public interface IPublicAPI
181181
/// </summary>
182182
/// <param name="url">URL to download file</param>
183183
/// <param name="filePath">path to save downloaded file</param>
184+
/// <param name="reportProgress">
185+
/// Action to report progress. The input of the action is the progress value which is a double value between 0 and 100.
186+
/// It will be called if url support range request and the reportProgress is not null.
187+
/// </param>
184188
/// <param name="token">place to store file</param>
185189
/// <returns>Task showing the progress</returns>
186-
Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default);
190+
Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null, CancellationToken token = default);
187191

188192
/// <summary>
189193
/// Add ActionKeyword for specific plugin
@@ -316,5 +320,19 @@ public interface IPublicAPI
316320
/// <param name="defaultResult">Specifies the default result of the message box.</param>
317321
/// <returns>Specifies which message box button is clicked by the user.</returns>
318322
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK);
323+
324+
/// <summary>
325+
/// Displays a standardised Flow message box.
326+
/// If there is issue when showing the message box, it will return null.
327+
/// </summary>
328+
/// <param name="caption">The caption of the message box.</param>
329+
/// <param name="reportProgressAsync">
330+
/// Time-consuming task function, whose input is the action to report progress.
331+
/// The input of the action is the progress value which is a double value between 0 and 100.
332+
/// If there are any exceptions, this action will be null.
333+
/// </param>
334+
/// <param name="forceClosed">When user closes the progress box manually by button or esc key, this action will be called.</param>
335+
/// <returns>A progress box interface.</returns>
336+
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action forceClosed = null);
319337
}
320338
}

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/Flow.Launcher.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
101101
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
102102
<PackageReference Include="SemanticVersioning" Version="3.0.0" />
103-
<PackageReference Include="VirtualizingWrapPanel" Version="2.1.0" />
103+
<PackageReference Include="VirtualizingWrapPanel" Version="2.1.1" />
104104
</ItemGroup>
105105

106106
<ItemGroup>

Flow.Launcher/Helper/WindowsInteropHelper.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,64 @@ public static Point TransformPixelsToDIP(Visual visual, double unitX, double uni
148148

149149
return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY));
150150
}
151+
152+
#region Alt Tab
153+
154+
private static int SetWindowLong(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, int dwNewLong)
155+
{
156+
PInvoke.SetLastError(WIN32_ERROR.NO_ERROR); // Clear any existing error
157+
158+
var result = PInvoke.SetWindowLong(hWnd, nIndex, dwNewLong);
159+
if (result == 0 && Marshal.GetLastPInvokeError() != 0)
160+
{
161+
throw new Win32Exception(Marshal.GetLastPInvokeError());
162+
}
163+
164+
return result;
165+
}
166+
167+
/// <summary>
168+
/// Hide windows in the Alt+Tab window list
169+
/// </summary>
170+
/// <param name="window">To hide a window</param>
171+
public static void HideFromAltTab(Window window)
172+
{
173+
var exStyle = GetCurrentWindowStyle(window);
174+
175+
// Add TOOLWINDOW style, remove APPWINDOW style
176+
var newExStyle = ((uint)exStyle | (uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) & ~(uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW;
177+
178+
SetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle);
179+
}
180+
181+
/// <summary>
182+
/// Restore window display in the Alt+Tab window list.
183+
/// </summary>
184+
/// <param name="window">To restore the displayed window</param>
185+
public static void ShowInAltTab(Window window)
186+
{
187+
var exStyle = GetCurrentWindowStyle(window);
188+
189+
// Remove the TOOLWINDOW style and add the APPWINDOW style.
190+
var newExStyle = ((uint)exStyle & ~(uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) | (uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW;
191+
192+
SetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle);
193+
}
194+
195+
/// <summary>
196+
/// To obtain the current overridden style of a window.
197+
/// </summary>
198+
/// <param name="window">To obtain the style dialog window</param>
199+
/// <returns>current extension style value</returns>
200+
private static int GetCurrentWindowStyle(Window window)
201+
{
202+
var style = PInvoke.GetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
203+
if (style == 0 && Marshal.GetLastPInvokeError() != 0)
204+
{
205+
throw new Win32Exception(Marshal.GetLastPInvokeError());
206+
}
207+
return style;
208+
}
209+
210+
#endregion
151211
}

Flow.Launcher/MainWindow.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
Closing="OnClosing"
2121
Deactivated="OnDeactivated"
2222
Icon="Images/app.png"
23+
SourceInitialized="OnSourceInitialized"
2324
Initialized="OnInitialized"
2425
Left="{Binding Settings.WindowLeft, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
2526
Loaded="OnLoaded"

Flow.Launcher/MainWindow.xaml.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ private async void OnClosing(object sender, CancelEventArgs e)
171171
Environment.Exit(0);
172172
}
173173

174+
private void OnSourceInitialized(object sender, EventArgs e)
175+
{
176+
WindowsInteropHelper.HideFromAltTab(this);
177+
}
178+
174179
private void OnInitialized(object sender, EventArgs e)
175180
{
176181
}

0 commit comments

Comments
 (0)