Merge branch 'dev' into dev4

This commit is contained in:
Jack Ye 2025-02-19 17:13:51 +08:00 committed by GitHub
commit 8b6dcd0afe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 149 additions and 62 deletions

View file

@ -8,6 +8,7 @@ using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Shell;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
@ -309,12 +310,15 @@ namespace Flow.Launcher.Core.Resource
var marginSetter = windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) as Setter;
if (marginSetter == null)
{
var margin = new Thickness(ShadowExtraMargin, 12, ShadowExtraMargin, ShadowExtraMargin);
marginSetter = new Setter()
{
Property = Border.MarginProperty,
Value = new Thickness(ShadowExtraMargin, 12, ShadowExtraMargin, ShadowExtraMargin),
Value = margin,
};
windowBorderStyle.Setters.Add(marginSetter);
SetResizeBoarderThickness(margin);
}
else
{
@ -325,6 +329,8 @@ namespace Flow.Launcher.Core.Resource
baseMargin.Right + ShadowExtraMargin,
baseMargin.Bottom + ShadowExtraMargin);
marginSetter.Value = newMargin;
SetResizeBoarderThickness(newMargin);
}
windowBorderStyle.Setters.Add(effectSetter);
@ -355,9 +361,36 @@ namespace Flow.Launcher.Core.Resource
marginSetter.Value = newMargin;
}
SetResizeBoarderThickness(null);
UpdateResourceDictionary(dict);
}
// because adding drop shadow effect will change the margin of the window,
// we need to update the window chrome thickness to correct set the resize border
private static void SetResizeBoarderThickness(Thickness? effectMargin)
{
var window = Application.Current.MainWindow;
if (WindowChrome.GetWindowChrome(window) is WindowChrome windowChrome)
{
Thickness thickness;
if (effectMargin == null)
{
thickness = SystemParameters.WindowResizeBorderThickness;
}
else
{
thickness = new Thickness(
effectMargin.Value.Left + SystemParameters.WindowResizeBorderThickness.Left,
effectMargin.Value.Top + SystemParameters.WindowResizeBorderThickness.Top,
effectMargin.Value.Right + SystemParameters.WindowResizeBorderThickness.Right,
effectMargin.Value.Bottom + SystemParameters.WindowResizeBorderThickness.Bottom);
}
windowChrome.ResizeBorderThickness = thickness;
}
}
public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null);
}
}

View file

@ -84,6 +84,11 @@ namespace Flow.Launcher.Infrastructure.Image
// Fallback to IconOnly if ThumbnailOnly fails
imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
}
catch (FileNotFoundException) when (options == ThumbnailOptions.ThumbnailOnly)
{
// Fallback to IconOnly if files cannot be found
imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
}
}
finally
{

View file

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace Flow.Launcher.Plugin
{
@ -9,15 +6,6 @@ namespace Flow.Launcher.Plugin
{
public Query() { }
[Obsolete("Use the default Query constructor.")]
public Query(string rawQuery, string search, string[] terms, string[] searchTerms, string actionKeyword = "")
{
Search = search;
RawQuery = rawQuery;
SearchTerms = searchTerms;
ActionKeyword = actionKeyword;
}
/// <summary>
/// Raw query, this includes action keyword if it has
/// We didn't recommend use this property directly. You should always use Search property.

View file

@ -38,7 +38,7 @@
mc:Ignorable="d">
<!-- WindowChrome -->
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="9" ResizeBorderThickness="32 4 32 32" />
<WindowChrome CaptionHeight="9" />
</WindowChrome.WindowChrome>
<Window.Resources>
<converters:QuerySuggestionBoxConverter x:Key="QuerySuggestionBoxConverter" />

View file

@ -12,6 +12,8 @@ namespace Flow.Launcher.Storage
internal bool IsTopMost(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
// in this case, we do not need to check if the result is top most
if (records.IsEmpty || result.OriginQuery == null ||
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
{
@ -24,11 +26,25 @@ namespace Flow.Launcher.Storage
internal void Remove(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
// in this case, we do not need to remove the record
if (result.OriginQuery == null)
{
return;
}
records.Remove(result.OriginQuery.RawQuery, out _);
}
internal void AddOrUpdate(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
// in this case, we do not need to add or update the record
if (result.OriginQuery == null)
{
return;
}
var record = new Record
{
PluginID = result.PluginID,
@ -38,11 +54,6 @@ namespace Flow.Launcher.Storage
};
records.AddOrUpdate(result.OriginQuery.RawQuery, record, (key, oldValue) => record);
}
public void Load(Dictionary<string, Record> dictionary)
{
records = new ConcurrentDictionary<string, Record>(dictionary);
}
}
public class Record

View file

@ -57,6 +57,8 @@ namespace Flow.Launcher.Storage
private static int GenerateQueryAndResultHashCode(Query query, Result result)
{
// query is null when user select the context menu item directly of one item from query list
// so we only need to consider the result
if (query == null)
{
return GenerateResultHashCode(result);

View file

@ -35,8 +35,6 @@ namespace Flow.Launcher.ViewModel
private bool _isQueryRunning;
private Query _lastQuery;
private Result lastContextMenuResult = new Result();
private List<Result> lastContextMenuResults = new List<Result>();
private string _queryTextBeforeLeaveResults;
private readonly FlowLauncherJsonStorage<History> _historyItemsStorage;
@ -399,11 +397,15 @@ namespace Flow.Launcher.ViewModel
})
.ConfigureAwait(false);
if (SelectedIsFromQueryResults())
{
_userSelectedRecord.Add(result);
_history.Add(result.OriginQuery.RawQuery);
// origin query is null when user select the context menu item directly of one item from query list
// so we don't want to add it to history
if (result.OriginQuery != null)
{
_history.Add(result.OriginQuery.RawQuery);
}
lastHistoryIndex = 1;
}
@ -987,19 +989,10 @@ namespace Flow.Launcher.ViewModel
if (selected != null) // SelectedItem returns null if selection is empty.
{
List<Result> results;
if (selected == lastContextMenuResult)
{
results = lastContextMenuResults;
}
else
{
results = PluginManager.GetContextMenusForPlugin(selected);
lastContextMenuResults = results;
lastContextMenuResult = selected;
results.Add(ContextMenuTopMost(selected));
results.Add(ContextMenuPluginInfo(selected.PluginID));
}
results = PluginManager.GetContextMenusForPlugin(selected);
results.Add(ContextMenuTopMost(selected));
results.Add(ContextMenuPluginInfo(selected.PluginID));
if (!string.IsNullOrEmpty(query))
{
@ -1274,6 +1267,8 @@ namespace Flow.Launcher.ViewModel
{
_topMostRecord.Remove(result);
App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success"));
App.API.BackToQueryResults();
App.API.ReQuery();
return false;
}
};
@ -1290,6 +1285,8 @@ namespace Flow.Launcher.ViewModel
{
_topMostRecord.AddOrUpdate(result);
App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success"));
App.API.BackToQueryResults();
App.API.ReQuery();
return false;
}
};
@ -1378,8 +1375,6 @@ namespace Flow.Launcher.ViewModel
lastHistoryIndex = 1;
// Trick for no delay
MainWindowOpacity = 0;
lastContextMenuResult = new Result();
lastContextMenuResults = new List<Result>();
if (ExternalPreviewVisible)
CloseExternalPreview();

View file

@ -242,6 +242,7 @@ namespace Flow.Launcher.Plugin.Explorer
var name = "Plugin: Folder";
var message = $"File not found: {e.Message}";
Context.API.ShowMsgError(name, message);
return false;
}
return true;

View file

@ -264,6 +264,8 @@ namespace Flow.Launcher.Plugin.Program
Context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success"),
Context.API.GetTranslation(
"flowlauncher_plugin_program_disable_dlgtitle_success_message"));
Context.API.BackToQueryResults();
Context.API.ReQuery();
return false;
},
IcoPath = "Images/disable.png",

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
@ -9,6 +10,7 @@ using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Security;
using Windows.Win32.System.Shutdown;
using Application = System.Windows.Application;
using Control = System.Windows.Controls.Control;
@ -20,6 +22,10 @@ namespace Flow.Launcher.Plugin.Sys
private PluginInitContext context;
private Dictionary<string, string> KeywordTitleMappings = new Dictionary<string, string>();
// SHTDN_REASON_MAJOR_OTHER indicates a generic shutdown reason that isn't categorized under hardware failure, software updates, or other predefined reasons.
// SHTDN_REASON_FLAG_PLANNED marks the shutdown as planned rather than an unexpected shutdown or failure
private const SHUTDOWN_REASON REASON = SHUTDOWN_REASON.SHTDN_REASON_MAJOR_OTHER | SHUTDOWN_REASON.SHTDN_REASON_FLAG_PLANNED;
public Control CreateSettingPanel()
{
var results = Commands();
@ -100,6 +106,44 @@ namespace Flow.Launcher.Plugin.Sys
};
}
private static unsafe bool EnableShutdownPrivilege()
{
try
{
if (!PInvoke.OpenProcessToken(Process.GetCurrentProcess().SafeHandle, TOKEN_ACCESS_MASK.TOKEN_ADJUST_PRIVILEGES | TOKEN_ACCESS_MASK.TOKEN_QUERY, out var tokenHandle))
{
return false;
}
if (!PInvoke.LookupPrivilegeValue(null, PInvoke.SE_SHUTDOWN_NAME, out var luid))
{
return false;
}
var privileges = new TOKEN_PRIVILEGES
{
PrivilegeCount = 1,
Privileges = new() { e0 = new LUID_AND_ATTRIBUTES { Luid = luid, Attributes = TOKEN_PRIVILEGES_ATTRIBUTES.SE_PRIVILEGE_ENABLED } }
};
if (!PInvoke.AdjustTokenPrivileges(tokenHandle, false, &privileges, 0, null, null))
{
return false;
}
if (Marshal.GetLastWin32Error() != (int)WIN32_ERROR.NO_ERROR)
{
return false;
}
return true;
}
catch (Exception)
{
return false;
}
}
private List<Result> Commands()
{
var results = new List<Result>();
@ -120,10 +164,12 @@ namespace Flow.Launcher.Plugin.Sys
context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_shutdown_computer"),
context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"),
MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
{
Process.Start("shutdown", "/s /t 0");
}
if (EnableShutdownPrivilege())
PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_SHUTDOWN | EXIT_WINDOWS_FLAGS.EWX_POWEROFF, REASON);
else
Process.Start("shutdown", "/s /t 0");
return true;
}
@ -140,10 +186,12 @@ namespace Flow.Launcher.Plugin.Sys
context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer"),
context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"),
MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
{
Process.Start("shutdown", "/r /t 0");
}
if (EnableShutdownPrivilege())
PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_REBOOT, REASON);
else
Process.Start("shutdown", "/r /t 0");
return true;
}
@ -162,7 +210,10 @@ namespace Flow.Launcher.Plugin.Sys
MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
Process.Start("shutdown", "/r /o /t 0");
if (EnableShutdownPrivilege())
PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_REBOOT | EXIT_WINDOWS_FLAGS.EWX_BOOTOPTIONS, REASON);
else
Process.Start("shutdown", "/r /o /t 0");
return true;
}
@ -181,7 +232,7 @@ namespace Flow.Launcher.Plugin.Sys
MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_LOGOFF, 0);
PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_LOGOFF, REASON);
return true;
}
@ -204,7 +255,11 @@ namespace Flow.Launcher.Plugin.Sys
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_sleep"),
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xec46"),
IcoPath = "Images\\sleep.png",
Action = c => PInvoke.SetSuspendState(false, false, false)
Action = c =>
{
PInvoke.SetSuspendState(false, false, false);
return true;
}
},
new Result
{
@ -214,12 +269,7 @@ namespace Flow.Launcher.Plugin.Sys
IcoPath = "Images\\hibernate.png",
Action= c =>
{
var info = ShellCommand.SetProcessStartInfo("shutdown", arguments:"/h");
info.WindowStyle = ProcessWindowStyle.Hidden;
info.UseShellExecute = true;
ShellCommand.Execute(info);
PInvoke.SetSuspendState(true, false, false);
return true;
}
},
@ -231,10 +281,7 @@ namespace Flow.Launcher.Plugin.Sys
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe773"),
Action = c =>
{
{
System.Diagnostics.Process.Start("control.exe", "srchadmin.dll");
}
Process.Start("control.exe", "srchadmin.dll");
return true;
}
},
@ -272,10 +319,7 @@ namespace Flow.Launcher.Plugin.Sys
CopyText = recycleBinFolder,
Action = c =>
{
{
System.Diagnostics.Process.Start("explorer", recycleBinFolder);
}
Process.Start("explorer", recycleBinFolder);
return true;
}
},

View file

@ -3,4 +3,10 @@ LockWorkStation
SHEmptyRecycleBin
S_OK
E_UNEXPECTED
SetSuspendState
SetSuspendState
OpenProcessToken
WIN32_ERROR
LookupPrivilegeValue
AdjustTokenPrivileges
TOKEN_PRIVILEGES
SE_SHUTDOWN_NAME