Merge pull request #3944 from Flow-Launcher/url_open_enhancement

Enhancement: Support Custom Browser Path & Open in window / tab & In private for URL Plugin
This commit is contained in:
Jack Ye 2025-10-12 12:22:36 +08:00 committed by GitHub
commit eb261f503b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 287 additions and 27 deletions

View file

@ -0,0 +1,23 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace Flow.Launcher.Plugin.Url.Converters;
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not bool)
throw new ArgumentException("value should be boolean", nameof(value));
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace Flow.Launcher.Plugin.Url.Converters;
[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not bool)
throw new ArgumentException("value should be boolean", nameof(value));
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not bool)
throw new ArgumentException("value should be boolean", nameof(value));
return !(bool)value;
}
}

View file

@ -1,18 +1,22 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_url_open_search_in">Open search in:</system:String>
<system:String x:Key="flowlauncher_plugin_new_window">New Window</system:String>
<system:String x:Key="flowlauncher_plugin_new_tab">New Tab</system:String>
<system:String x:Key="flowlauncher_plugin_url_open_url">Open url:{0}</system:String>
<system:String x:Key="flowlauncher_plugin_url_cannot_open_url">Can't open url:{0}</system:String>
<system:String x:Key="flowlauncher_plugin_url_plugin_name">URL</system:String>
<system:String x:Key="flowlauncher_plugin_url_plugin_description">Open the typed URL from Flow Launcher</system:String>
<system:String x:Key="flowlauncher_plugin_url_plugin_set_tip">Please set your browser path:</system:String>
<system:String x:Key="flowlauncher_plugin_url_plugin_choose">Choose</system:String>
<system:String x:Key="flowlauncher_plugin_url_plugin_filter">Application(*.exe)|*.exe|All files|*.*</system:String>
<system:String x:Key="flowlauncher_plugin_url_use_custom_browser">Use custom instead of Flow's default web browser</system:String>
<system:String x:Key="flowlauncher_plugin_url_browser_path">Browser path</system:String>
<system:String x:Key="flowlauncher_plugin_url_new_tab">New tab</system:String>
<system:String x:Key="flowlauncher_plugin_url_new_window">New window</system:String>
<system:String x:Key="flowlauncher_plugin_url_private_mode">Private mode</system:String>
</ResourceDictionary>

View file

@ -1,13 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Plugin.Url
{
public class Main : IPlugin, IPluginI18n
public class Main : IPlugin, IPluginI18n, ISettingProvider
{
//based on https://gist.github.com/dperini/729294
private const string urlPattern = "^" +
private const string UrlPattern = "^" +
// protocol identifier
"(?:(?:https?|ftp)://|)" +
// user:pass authentication
@ -39,18 +41,18 @@ namespace Flow.Launcher.Plugin.Url
// resource path
"(?:/\\S*)?" +
"$";
Regex reg = new Regex(urlPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly Regex UrlRegex = new(UrlPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
internal static PluginInitContext Context { get; private set; }
private Settings _settings;
internal static Settings Settings { get; private set; }
public List<Result> Query(Query query)
{
var raw = query.Search;
if (IsURL(raw))
{
return new List<Result>
{
new Result
return
[
new()
{
Title = raw,
SubTitle = Localize.flowlauncher_plugin_url_open_url(raw),
@ -58,14 +60,28 @@ namespace Flow.Launcher.Plugin.Url
Score = 8,
Action = _ =>
{
if (!raw.ToLower().StartsWith("http"))
if (!raw.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
raw = "http://" + raw;
}
try
{
Context.API.OpenUrl(raw);
if (Settings.UseCustomBrowser)
{
if (Settings.OpenInNewBrowserWindow)
{
SearchWeb.OpenInBrowserWindow(raw, Settings.BrowserPath, Settings.OpenInPrivateMode, Settings.PrivateModeArgument);
}
else
{
SearchWeb.OpenInBrowserTab(raw, Settings.BrowserPath, Settings.OpenInPrivateMode, Settings.PrivateModeArgument);
}
}
else
{
Context.API.OpenWebUrl(raw);
}
return true;
}
catch(Exception)
@ -75,16 +91,17 @@ namespace Flow.Launcher.Plugin.Url
}
}
}
};
];
}
return new List<Result>(0);
return [];
}
public bool IsURL(string raw)
{
raw = raw.ToLower();
if (reg.Match(raw).Value == raw) return true;
if (UrlRegex.Match(raw).Value == raw) return true;
if (raw == "localhost" || raw.StartsWith("localhost:") ||
raw == "http://localhost" || raw.StartsWith("http://localhost:") ||
@ -100,8 +117,8 @@ namespace Flow.Launcher.Plugin.Url
public void Init(PluginInitContext context)
{
Context = context;
_settings = context.API.LoadSettingJsonStorage<Settings>();
Settings = context.API.LoadSettingJsonStorage<Settings>();
}
public string GetTranslatedPluginTitle()
@ -113,5 +130,10 @@ namespace Flow.Launcher.Plugin.Url
{
return Localize.flowlauncher_plugin_url_plugin_description();
}
public Control CreateSettingPanel()
{
return new SettingsControl();
}
}
}

View file

@ -1,9 +1,51 @@
namespace Flow.Launcher.Plugin.Url
{
public class Settings
public class Settings : BaseModel
{
public string BrowserPath { get; set; }
private bool _useCustomBrowser = false;
public bool UseCustomBrowser
{
get => _useCustomBrowser;
set
{
if (_useCustomBrowser != value)
{
_useCustomBrowser = value;
OnPropertyChanged();
}
}
}
public bool OpenInNewBrowserWindow { get; set; } = true;
private string _browserPath = string.Empty;
public string BrowserPath
{
get => _browserPath;
set
{
if (_browserPath != value)
{
_browserPath = value;
OnPropertyChanged();
}
}
}
private bool _openInNewBrowserWindow = true;
public bool OpenInNewBrowserWindow
{
get => _openInNewBrowserWindow;
set
{
if (_openInNewBrowserWindow != value)
{
_openInNewBrowserWindow = value;
OnPropertyChanged();
}
}
}
public bool OpenInPrivateMode { get; set; } = false;
public string PrivateModeArgument { get; set; } = string.Empty;
}
}

View file

@ -0,0 +1,117 @@
<UserControl
x:Class="Flow.Launcher.Plugin.Url.SettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:Flow.Launcher.Plugin.Url.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Flow.Launcher.Plugin.Url"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<UserControl.Resources>
<converters:InverseBoolConverter x:Key="InverseBoolConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</UserControl.Resources>
<Grid Margin="{StaticResource SettingPanelMargin}">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<CheckBox
Grid.Row="0"
Margin="{StaticResource SettingPanelItemTopBottomMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Content="{DynamicResource flowlauncher_plugin_url_use_custom_browser}"
IsChecked="{Binding Settings.UseCustomBrowser, Mode=TwoWay}" />
<Grid
Grid.Row="1"
HorizontalAlignment="Left"
Visibility="{Binding Settings.UseCustomBrowser, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource SettingPanelItemRightTopBottomMargin}"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource flowlauncher_plugin_url_browser_path}" />
<Grid
Grid.Row="0"
Grid.Column="1"
Margin="{StaticResource SettingPanelItemLeftTopBottomMargin}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsReadOnly="True"
Text="{Binding Settings.BrowserPath, Mode=OneWay}" />
<Button
Grid.Column="1"
Margin="{StaticResource SettingPanelItemLeftMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Click="SelectBrowserPath"
Content="{DynamicResource flowlauncher_plugin_url_plugin_choose}" />
</Grid>
<StackPanel
Grid.Row="1"
Grid.Column="1"
Margin="{StaticResource SettingPanelItemLeftTopBottomMargin}"
Orientation="Horizontal">
<RadioButton Content="{DynamicResource flowlauncher_plugin_url_new_tab}" IsChecked="{Binding Settings.OpenInNewBrowserWindow, Converter={StaticResource InverseBoolConverter}, Mode=TwoWay}" />
<RadioButton Content="{DynamicResource flowlauncher_plugin_url_new_window}" IsChecked="{Binding Settings.OpenInNewBrowserWindow, Mode=TwoWay}" />
</StackPanel>
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource SettingPanelItemTopBottomMargin}"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource flowlauncher_plugin_url_private_mode}" />
<Grid
Grid.Row="2"
Grid.Column="1"
Margin="{StaticResource SettingPanelItemLeftTopBottomMargin}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding Settings.PrivateModeArgument, Mode=TwoWay}" />
<CheckBox
Grid.Column="1"
Margin="{StaticResource SettingPanelItemLeftMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Content=""
IsChecked="{Binding Settings.OpenInPrivateMode, Mode=TwoWay}" />
</Grid>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,27 @@
using System.Windows;
using System.Windows.Controls;
namespace Flow.Launcher.Plugin.Url;
public partial class SettingsControl : UserControl
{
public Settings Settings { get; } = Main.Settings;
public SettingsControl()
{
InitializeComponent();
}
private void SelectBrowserPath(object sender, RoutedEventArgs e)
{
var dlg = new Microsoft.Win32.OpenFileDialog
{
Filter = Localize.flowlauncher_plugin_url_plugin_filter()
};
if (dlg.ShowDialog() == true && !string.IsNullOrEmpty(dlg.FileName))
{
Settings.BrowserPath = dlg.FileName;
}
}
}