Skip to content

Support Svg File Icon Loading & Local favicon for bookmark plugin #3361

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 58 commits into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
5481a6a
Add local favicon load
onesounds Mar 19, 2025
1f6d771
Remove useless NuGet package & Downgrade System.Drawing.Common package
Jack251970 Mar 20, 2025
f6740bc
Revert style
Jack251970 Mar 20, 2025
cab0cb8
Add SVG favicon support
onesounds Mar 20, 2025
d05389c
Added SVG for firefox
onesounds Mar 20, 2025
e662741
Revert "Add SVG favicon support"
Yusyuriv Mar 20, 2025
02d0916
Adjust code format
Jack251970 Mar 20, 2025
d2c185e
Remove unnecessary dependencies
Yusyuriv Mar 20, 2025
977a8f8
Log exception when deleting failed
Jack251970 Mar 20, 2025
fab9833
Merge branch '250320BookmarkFavicon' of https://github.com/onesounds/…
Jack251970 Mar 20, 2025
a87211b
Fix sqlite file lock issue & Add static
Jack251970 Mar 20, 2025
2b08ad6
Only clear pool for one connection
Jack251970 Mar 20, 2025
0c162b1
Fix call position
Jack251970 Mar 20, 2025
92ef2c7
Reduce nesting
Yusyuriv Mar 20, 2025
a837e8e
Close bookmarks db connection before trying to delete the file
Yusyuriv Mar 20, 2025
443f17d
Code quality
Jack251970 Mar 20, 2025
dfc9d5b
- Add SVG convert for firefox
onesounds Mar 20, 2025
cb8b5f2
Merge remote-tracking branch 'origin/250320BookmarkFavicon' into 2503…
onesounds Mar 20, 2025
4e8272f
- Removed "Remove Cache" button
onesounds Mar 20, 2025
19c3971
Remove Skia nuget
onesounds Mar 24, 2025
3387c2a
Remove svg related nuget and codes
onesounds Mar 25, 2025
2c6fdc0
Merge branch 'dev' into 250320BookmarkFavicon
Jack251970 Apr 2, 2025
0430eea
Use api functions instead of project reference for code quality
Jack251970 Apr 2, 2025
168f898
Cleanup namespaces
Jack251970 Apr 2, 2025
b564a39
Use api functions instead of project reference for code quality
Jack251970 Apr 2, 2025
a845e68
Use plugin cache directory
Jack251970 Apr 2, 2025
53f3e73
Merge branch 'dev' into 250320BookmarkFavicon
Jack251970 Apr 9, 2025
49dc657
Fix build issue
Jack251970 Apr 9, 2025
9c07989
Improve code quality
Jack251970 Apr 9, 2025
4e1d4ab
Remove unused project reference
Jack251970 Apr 9, 2025
1aeaaf2
Add keyevent in Plugin project & Improve Shell plugin code quality
Jack251970 Apr 9, 2025
c035b65
Fix code documents issue
Jack251970 Apr 9, 2025
e07beb2
Add dispose
Jack251970 Apr 9, 2025
46712f2
Improve api documents
Jack251970 Apr 9, 2025
f71e746
Remove unused project reference
Jack251970 Apr 9, 2025
826bc42
Improve code quality
Jack251970 Apr 9, 2025
d4c9626
Improve code quality & Remove unused project reference
Jack251970 Apr 9, 2025
17cf74e
Add obsolete warning
Jack251970 Apr 9, 2025
c41d021
Add obsolete warning
Jack251970 Apr 9, 2025
da8a690
Improve code quality
Jack251970 Apr 9, 2025
f5de5d7
Fix log message issue
Jack251970 Apr 9, 2025
71b9e4a
Fix log info class name issue
Jack251970 Apr 9, 2025
d3d0ccf
Merge branch '250320BookmarkFavicon' of https://github.com/onesounds/…
Jack251970 Apr 9, 2025
048a40d
Code quality
Jack251970 Apr 9, 2025
7061ac5
Fix register bookmark file comment
Jack251970 Apr 9, 2025
f854cd3
Code quality
Jack251970 Apr 9, 2025
82f6788
Support svg image file loading
Jack251970 Apr 9, 2025
f01ccbf
Change function name
Jack251970 Apr 9, 2025
5e7573b
Add log messages
Jack251970 Apr 9, 2025
cce4e89
Add safeguards to SVG loading implementation
Jack251970 Apr 9, 2025
ab34e83
Merge branch 'dev' into 250320BookmarkFavicon
Jack251970 Apr 9, 2025
4f24646
Fix build issue
Jack251970 Apr 9, 2025
ba0205f
Force save favicon icons & Fix svg save issue
Jack251970 Apr 9, 2025
4c712f6
Fix svg render issue
Jack251970 Apr 9, 2025
62a5dd7
Delete temporary files
Jack251970 Apr 9, 2025
150ea84
Make sure temporary files deleted
Jack251970 Apr 9, 2025
3380079
Remove useless try catch
Jack251970 Apr 9, 2025
2d29a42
Improve code quality
Jack251970 Apr 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Flow.Launcher.Core/Resource/LocalizationConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace Flow.Launcher.Core.Resource
{
[Obsolete("LocalizationConverter is obsolete. Use with Flow.Launcher.Localization NuGet package instead.")]
public class LocalizationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
Expand Down
10 changes: 7 additions & 3 deletions Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
using System.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;

namespace Flow.Launcher.Core.Resource
{
public class LocalizedDescriptionAttribute : DescriptionAttribute
{
private readonly Internationalization _translator;
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();

private readonly string _resourceKey;

public LocalizedDescriptionAttribute(string resourceKey)
{
_translator = InternationalizationManager.Instance;
_resourceKey = resourceKey;
}

public override string Description
{
get
{
string description = _translator.GetTranslation(_resourceKey);
string description = API.GetTranslation(_resourceKey);
return string.IsNullOrWhiteSpace(description) ?
string.Format("[[{0}]]", _resourceKey) : description;
}
Expand Down
14 changes: 10 additions & 4 deletions Flow.Launcher.Core/Resource/TranslationConverter.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
using System;
using System.Globalization;
using System.Windows.Data;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;

namespace Flow.Launcher.Core.Resource
{
public class TranslationConverter : IValueConverter
{
// We should not initialize API in static constructor because it will create another API instance
private static IPublicAPI api = null;
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var key = value.ToString();
if (String.IsNullOrEmpty(key))
return key;
return InternationalizationManager.Instance.GetTranslation(key);
if (string.IsNullOrEmpty(key)) return key;
return API.GetTranslation(key);
}

public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException();
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
throw new InvalidOperationException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
</PackageReference>
<PackageReference Include="NLog" Version="4.7.10" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
<PackageReference Include="SharpVectors.Wpf" Version="1.8.4.2" />
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
<!--ToolGood.Words.Pinyin v3.0.2.6 results in high memory usage when search with pinyin is enabled-->
<!--Bumping to it or higher needs to test and ensure this is no longer a problem-->
Expand Down
3 changes: 0 additions & 3 deletions Flow.Launcher.Infrastructure/Image/ImageCache.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
using BitFaster.Caching.Lfu;
Expand Down Expand Up @@ -55,7 +53,6 @@ public bool TryGetValue(string key, bool isFullImage, out ImageSource image)
return image != null;
}


image = null;
return false;
}
Expand Down
76 changes: 70 additions & 6 deletions Flow.Launcher.Infrastructure/Image/ImageLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin;
using SharpVectors.Converters;
using SharpVectors.Renderers.Wpf;

namespace Flow.Launcher.Infrastructure.Image
{
Expand All @@ -32,8 +34,10 @@ public static class ImageLoader
public static ImageSource LoadingImage { get; } = new BitmapImage(new Uri(Constant.LoadingImgIcon));
public const int SmallIconSize = 64;
public const int FullIconSize = 256;
public const int FullImageSize = 320;

private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" };
private static readonly string SvgExtension = ".svg";

public static async Task InitializeAsync()
{
Expand Down Expand Up @@ -235,10 +239,11 @@ private static ImageResult GetThumbnailResult(ref string path, bool loadFullImag
image = LoadFullImage(path);
type = ImageType.FullImageFile;
}
catch (NotSupportedException)
catch (NotSupportedException ex)
{
image = Image;
type = ImageType.Error;
API.LogException(ClassName, $"Failed to load image file from path {path}: {ex.Message}", ex);
}
}
else
Expand All @@ -251,6 +256,20 @@ private static ImageResult GetThumbnailResult(ref string path, bool loadFullImag
image = GetThumbnail(path, ThumbnailOptions.ThumbnailOnly);
}
}
else if (extension == SvgExtension)
{
try
{
image = LoadSvgImage(path, loadFullImage);
type = ImageType.FullImageFile;
}
catch (System.Exception ex)
{
image = Image;
type = ImageType.Error;
API.LogException(ClassName, $"Failed to load SVG image from path {path}: {ex.Message}", ex);
}
}
else
{
type = ImageType.File;
Expand Down Expand Up @@ -324,7 +343,7 @@ public static async ValueTask<ImageSource> LoadAsync(string path, bool loadFullI
return img;
}

private static BitmapImage LoadFullImage(string path)
private static ImageSource LoadFullImage(string path)
{
BitmapImage image = new BitmapImage();
image.BeginInit();
Expand All @@ -333,24 +352,24 @@ private static BitmapImage LoadFullImage(string path)
image.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
image.EndInit();

if (image.PixelWidth > 320)
if (image.PixelWidth > FullImageSize)
{
BitmapImage resizedWidth = new BitmapImage();
resizedWidth.BeginInit();
resizedWidth.CacheOption = BitmapCacheOption.OnLoad;
resizedWidth.UriSource = new Uri(path);
resizedWidth.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
resizedWidth.DecodePixelWidth = 320;
resizedWidth.DecodePixelWidth = FullImageSize;
resizedWidth.EndInit();

if (resizedWidth.PixelHeight > 320)
if (resizedWidth.PixelHeight > FullImageSize)
{
BitmapImage resizedHeight = new BitmapImage();
resizedHeight.BeginInit();
resizedHeight.CacheOption = BitmapCacheOption.OnLoad;
resizedHeight.UriSource = new Uri(path);
resizedHeight.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
resizedHeight.DecodePixelHeight = 320;
resizedHeight.DecodePixelHeight = FullImageSize;
resizedHeight.EndInit();
return resizedHeight;
}
Expand All @@ -360,5 +379,50 @@ private static BitmapImage LoadFullImage(string path)

return image;
}

private static ImageSource LoadSvgImage(string path, bool loadFullImage = false)
{
// Set up drawing settings
var desiredHeight = loadFullImage ? FullImageSize : SmallIconSize;
var drawingSettings = new WpfDrawingSettings
{
IncludeRuntime = true,
// Set IgnoreRootViewbox to false to respect the SVG's viewBox
IgnoreRootViewbox = false
};

// Load and render the SVG
var converter = new FileSvgReader(drawingSettings);
var drawing = converter.Read(new Uri(path));

// Calculate scale to achieve desired height
var drawingBounds = drawing.Bounds;
if (drawingBounds.Height <= 0)
{
throw new InvalidOperationException($"Invalid SVG dimensions: Height must be greater than zero in {path}");
}
var scale = desiredHeight / drawingBounds.Height;
var scaledWidth = drawingBounds.Width * scale;
var scaledHeight = drawingBounds.Height * scale;

// Convert the Drawing to a Bitmap
var drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.PushTransform(new ScaleTransform(scale, scale));
drawingContext.DrawDrawing(drawing);
}

// Create a RenderTargetBitmap to hold the rendered image
var bitmap = new RenderTargetBitmap(
(int)Math.Ceiling(scaledWidth),
(int)Math.Ceiling(scaledHeight),
96, // DpiX
96, // DpiY
PixelFormats.Pbgra32);
bitmap.Render(drawingVisual);

return bitmap;
}
}
}
5 changes: 0 additions & 5 deletions Flow.Launcher.Infrastructure/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ GetModuleHandle
GetKeyState
VIRTUAL_KEY

WM_KEYDOWN
WM_KEYUP
WM_SYSKEYDOWN
WM_SYSKEYUP

EnumWindows

DwmSetWindowAttribute
Expand Down
1 change: 1 addition & 0 deletions Flow.Launcher.Infrastructure/UI/EnumBindingSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace Flow.Launcher.Infrastructure.UI
{
[Obsolete("EnumBindingSourceExtension is obsolete. Use with Flow.Launcher.Localization NuGet package instead.")]
public class EnumBindingSourceExtension : MarkupExtension
{
private Type _enumType;
Expand Down
49 changes: 43 additions & 6 deletions Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,47 @@ public interface IPublicAPI
List<PluginPair> GetAllPlugins();

/// <summary>
/// Register a callback for Global Keyboard Event
/// </summary>
/// <param name="callback"></param>
/// Registers a callback function for global keyboard events.
/// </summary>
/// <param name="callback">
/// The callback function to invoke when a global keyboard event occurs.
/// <para>
/// Parameters:
/// <list type="number">
/// <item><description>int: The type of <see cref="KeyEvent"/> (key down, key up, etc.)</description></item>
/// <item><description>int: The virtual key code of the pressed/released key</description></item>
/// <item><description><see cref="SpecialKeyState"/>: The state of modifier keys (Ctrl, Alt, Shift, etc.)</description></item>
/// </list>
/// </para>
/// <para>
/// Returns: <c>true</c> to allow normal system processing of the key event,
/// or <c>false</c> to intercept and prevent default handling.
/// </para>
/// </param>
/// <remarks>
/// This callback will be invoked for all keyboard events system-wide.
/// Use with caution as intercepting system keys may affect normal system operation.
/// </remarks>
public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback);

/// <summary>
/// Remove a callback for Global Keyboard Event
/// </summary>
/// <param name="callback"></param>
/// <param name="callback">
/// The callback function to invoke when a global keyboard event occurs.
/// <para>
/// Parameters:
/// <list type="number">
/// <item><description>int: The type of <see cref="KeyEvent"/> (key down, key up, etc.)</description></item>
/// <item><description>int: The virtual key code of the pressed/released key</description></item>
/// <item><description><see cref="SpecialKeyState"/>: The state of modifier keys (Ctrl, Alt, Shift, etc.)</description></item>
/// </list>
/// </para>
/// <para>
/// Returns: <c>true</c> to allow normal system processing of the key event,
/// or <c>false</c> to intercept and prevent default handling.
/// </para>
/// </param>
public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback);

/// <summary>
Expand Down Expand Up @@ -372,6 +404,7 @@ public interface IPublicAPI
/// </returns>
public bool SetCurrentTheme(ThemeData theme);

/// <summary>
/// Save all Flow's plugins caches
/// </summary>
void SavePluginCaches();
Expand Down Expand Up @@ -404,7 +437,10 @@ public interface IPublicAPI
/// </remarks>
Task SaveCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory) where T : new();

/// Load image from path. Support local, remote and data:image url.
/// <summary>
/// Load image from path.
/// Support local, remote and data:image url.
/// Support png, jpg, jpeg, gif, bmp, tiff, ico, svg image files.
/// If image path is missing, it will return a missing icon.
/// </summary>
/// <param name="path">The path of the image.</param>
Expand All @@ -418,6 +454,7 @@ public interface IPublicAPI
/// <returns></returns>
ValueTask<ImageSource> LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true);

/// <summary>
/// Update the plugin manifest
/// </summary>
/// <param name="usePrimaryUrlOnly">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
using Windows.Win32;

namespace Flow.Launcher.Infrastructure.Hotkey
namespace Flow.Launcher.Plugin
{
/// <summary>
/// Enumeration of key events for
/// <see cref="IPublicAPI.RegisterGlobalKeyboardCallback(System.Func{int, int, SpecialKeyState, bool})"/>
/// and <see cref="IPublicAPI.RemoveGlobalKeyboardCallback(System.Func{int, int, SpecialKeyState, bool})"/>
/// </summary>
public enum KeyEvent
{
/// <summary>
Expand Down
7 changes: 6 additions & 1 deletion Flow.Launcher.Plugin/NativeMethods.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
EnumThreadWindows
GetWindowText
GetWindowTextLength
GetWindowTextLength

WM_KEYDOWN
WM_KEYUP
WM_SYSKEYDOWN
WM_SYSKEYUP
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ private async Task RefreshExternalPluginsAsync()
public bool SatisfiesFilter(PluginStoreItemViewModel plugin)
{
return string.IsNullOrEmpty(FilterText) ||
StringMatcher.FuzzySearch(FilterText, plugin.Name).IsSearchPrecisionScoreMet() ||
StringMatcher.FuzzySearch(FilterText, plugin.Description).IsSearchPrecisionScoreMet();
App.API.FuzzySearch(FilterText, plugin.Name).IsSearchPrecisionScoreMet() ||
App.API.FuzzySearch(FilterText, plugin.Description).IsSearchPrecisionScoreMet();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ public SettingsPanePluginsViewModel(Settings settings)
public List<PluginViewModel> FilteredPluginViewModels => PluginViewModels
.Where(v =>
string.IsNullOrEmpty(FilterText) ||
StringMatcher.FuzzySearch(FilterText, v.PluginPair.Metadata.Name).IsSearchPrecisionScoreMet() ||
StringMatcher.FuzzySearch(FilterText, v.PluginPair.Metadata.Description).IsSearchPrecisionScoreMet()
App.API.FuzzySearch(FilterText, v.PluginPair.Metadata.Name).IsSearchPrecisionScoreMet() ||
App.API.FuzzySearch(FilterText, v.PluginPair.Metadata.Description).IsSearchPrecisionScoreMet()
)
.ToList();

Expand Down
Loading
Loading