Skip to content

Commit 25684a9

Browse files
authored
Merge branch 'dev' into dev4
2 parents 6a2389f + 741ddfe commit 25684a9

File tree

36 files changed

+528
-125
lines changed

36 files changed

+528
-125
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/Flow.Launcher.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454

5555
<ItemGroup>
5656
<PackageReference Include="Droplex" Version="1.7.0" />
57-
<PackageReference Include="FSharp.Core" Version="9.0.100" />
57+
<PackageReference Include="FSharp.Core" Version="9.0.101" />
5858
<PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.0" />
5959
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
6060
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />

Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,54 +26,33 @@ internal abstract class JsonRPCPluginV2 : JsonRPCPluginBase, IAsyncDisposable, I
2626

2727
protected override async Task<bool> ExecuteResultAsync(JsonRPCResult result)
2828
{
29-
try
30-
{
31-
var res = await RPC.InvokeAsync<JsonRPCExecuteResponse>(result.JsonRPCAction.Method,
32-
argument: result.JsonRPCAction.Parameters);
29+
var res = await RPC.InvokeAsync<JsonRPCExecuteResponse>(result.JsonRPCAction.Method,
30+
argument: result.JsonRPCAction.Parameters);
3331

34-
return res.Hide;
35-
}
36-
catch
37-
{
38-
return false;
39-
}
32+
return res.Hide;
4033
}
4134

4235
private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());
4336

4437
public override List<Result> LoadContextMenus(Result selectedResult)
4538
{
46-
try
47-
{
48-
var res = JTF.Run(() => RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("context_menu",
49-
new object[] { selectedResult.ContextData }));
39+
var res = JTF.Run(() => RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("context_menu",
40+
new object[] { selectedResult.ContextData }));
5041

51-
var results = ParseResults(res);
42+
var results = ParseResults(res);
5243

53-
return results;
54-
}
55-
catch
56-
{
57-
return new List<Result>();
58-
}
44+
return results;
5945
}
6046

6147
public override async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
6248
{
63-
try
64-
{
65-
var res = await RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("query",
66-
new object[] { query, Settings.Inner },
67-
token);
49+
var res = await RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("query",
50+
new object[] { query, Settings.Inner },
51+
token);
6852

69-
var results = ParseResults(res);
53+
var results = ParseResults(res);
7054

71-
return results;
72-
}
73-
catch
74-
{
75-
return new List<Result>();
76-
}
55+
return results;
7756
}
7857

7958

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

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

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

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

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

107107
<ItemGroup>

Flow.Launcher/Helper/SingletonWindowOpener.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,29 @@ public static T Open<T>(params object[] args) where T : Window
1010
{
1111
var window = Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.GetType() == typeof(T))
1212
?? (T)Activator.CreateInstance(typeof(T), args);
13-
13+
1414
// Fix UI bug
1515
// Add `window.WindowState = WindowState.Normal`
1616
// If only use `window.Show()`, Settings-window doesn't show when minimized in taskbar
1717
// Not sure why this works tho
1818
// Probably because, when `.Show()` fails, `window.WindowState == Minimized` (not `Normal`)
1919
// https://stackoverflow.com/a/59719760/4230390
20-
window.WindowState = WindowState.Normal;
21-
window.Show();
22-
20+
// Ensure the window is not minimized before showing it
21+
if (window.WindowState == WindowState.Minimized)
22+
{
23+
window.WindowState = WindowState.Normal;
24+
}
25+
26+
// Ensure the window is visible
27+
if (!window.IsVisible)
28+
{
29+
window.Show();
30+
}
31+
else
32+
{
33+
window.Activate(); // Bring the window to the foreground if already open
34+
}
35+
2336
window.Focus();
2437

2538
return (T)window;

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"

0 commit comments

Comments
 (0)