Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
366f351
Init new WinUI proj for `Hurl.Selector`
U-C-S Nov 9, 2024
5173d19
Add missing project files
U-C-S Jan 25, 2025
3893c21
Make project self-bootstrap wasdk
U-C-S Jan 25, 2025
d460ebd
Impl Single instancing
U-C-S Jan 25, 2025
c5727a0
Init UI
U-C-S Jan 25, 2025
48fe23e
Update SelectorPage
U-C-S Jan 26, 2025
2ee8c9d
Init TimedDefault Page UI
U-C-S Jan 29, 2025
dea0b57
EOD - implement MVVM
U-C-S Jan 29, 2025
0fdf3e5
Polish UI
U-C-S Jan 30, 2025
09174c3
Show browser icons in the Selector page
U-C-S Jan 30, 2025
86856eb
Update to LibraryImport and etc.
U-C-S Feb 1, 2025
d573777
Code cleanup and formatting
U-C-S Feb 1, 2025
83f5230
EOD
U-C-S Feb 8, 2025
18fa823
Updates
U-C-S Mar 1, 2025
3eec4ae
Address review comments for sizing
U-C-S Apr 19, 2025
7f51a65
Create SelectorWindow instead of page
U-C-S Apr 19, 2025
1a727af
Create TimedWindow instead of page
U-C-S Apr 20, 2025
e3b990d
Update deps
U-C-S Apr 5, 2026
ceaf54e
Misc cleanup
U-C-S Apr 7, 2026
a3b7469
Fix launching URLs in the selector
U-C-S Apr 7, 2026
ab528bc
Support back AltLaunches in selector
U-C-S Apr 7, 2026
14cec97
Add keyboard shortkeys for edit,copy,minimise
U-C-S Apr 7, 2026
d104110
Add tooltips for btns
U-C-S Apr 7, 2026
998bb1d
Misc
U-C-S Apr 11, 2026
f5e7114
Add CLI argument parsing
U-C-S Apr 12, 2026
68b926b
Move the additional options dropdown to top
U-C-S Apr 12, 2026
30cab97
Delete TimedDefaultWindow
U-C-S Apr 12, 2026
28899ca
Move UriLauncher under `Helpers`
U-C-S Apr 12, 2026
21993e4
Implement trayicon and minimise to tray
U-C-S Apr 12, 2026
0eeda22
Reorganize SelectorWindow.cs code into regions
U-C-S Apr 12, 2026
8de3ba3
Misc cleanup
U-C-S Apr 12, 2026
befdbb8
Fix keyboard accerlator tooltip poping everywhere
U-C-S Apr 12, 2026
2c73b0d
Minimize to tray on window close
U-C-S Apr 12, 2026
26ebfba
Output a crash stacktrace in a file
U-C-S Apr 12, 2026
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
26 changes: 26 additions & 0 deletions Hurl.sln
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hurl.Library", "Source\Hurl
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hurl.Settings", "Source\Hurl.Settings\Hurl.Settings.csproj", "{C4DB15A3-1D60-437C-BD29-688E9E280E87}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hurl.Selector", "Source\Hurl.Selector\Hurl.Selector.csproj", "{2406894E-1688-44C0-BBA8-2EB8FF11709B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -72,6 +74,30 @@ Global
{C4DB15A3-1D60-437C-BD29-688E9E280E87}.Release|x64.Build.0 = Release|x64
{C4DB15A3-1D60-437C-BD29-688E9E280E87}.Release|x86.ActiveCfg = Release|x64
{C4DB15A3-1D60-437C-BD29-688E9E280E87}.Release|x86.Build.0 = Release|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Debug|Any CPU.ActiveCfg = Debug|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Debug|Any CPU.Build.0 = Debug|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Debug|Any CPU.Deploy.0 = Debug|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Debug|ARM64.ActiveCfg = Debug|ARM64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Debug|ARM64.Build.0 = Debug|ARM64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Debug|ARM64.Deploy.0 = Debug|ARM64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Debug|x64.ActiveCfg = Debug|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Debug|x64.Build.0 = Debug|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Debug|x64.Deploy.0 = Debug|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Debug|x86.ActiveCfg = Debug|x86
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Debug|x86.Build.0 = Debug|x86
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Debug|x86.Deploy.0 = Debug|x86
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Release|Any CPU.ActiveCfg = Release|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Release|Any CPU.Build.0 = Release|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Release|Any CPU.Deploy.0 = Release|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Release|ARM64.ActiveCfg = Release|ARM64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Release|ARM64.Build.0 = Release|ARM64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Release|ARM64.Deploy.0 = Release|ARM64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Release|x64.ActiveCfg = Release|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Release|x64.Build.0 = Release|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Release|x64.Deploy.0 = Release|x64
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Release|x86.ActiveCfg = Release|x86
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Release|x86.Build.0 = Release|x86
{2406894E-1688-44C0-BBA8-2EB8FF11709B}.Release|x86.Deploy.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
2 changes: 1 addition & 1 deletion Source/Hurl.BrowserSelector/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"profiles": {
"Hurl.BrowserSelector": {
"commandName": "Project",
"commandLineArgs": "\"https://keep.google.com\""
"commandLineArgs": "\"https://github.com/search?q=meow&type=repositories\""
}
}
}
1 change: 0 additions & 1 deletion Source/Hurl.Library/GetBrowsers.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Hurl.Library.Models;
using Microsoft.Win32;
using System.IO;

namespace Hurl.Library;

Expand Down
2 changes: 1 addition & 1 deletion Source/Hurl.Library/Hurl.Library.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0-windows</TargetFramework>
<TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
Expand Down
2 changes: 1 addition & 1 deletion Source/Hurl.Library/IconExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static class IconExtractor
}

// Thanks to https://stackoverflow.com/a/1127795
internal static class IconUtilites
public static class IconUtilites
{
// Avoids memory leak
[DllImport("gdi32.dll", SetLastError = true)]
Expand Down
16 changes: 16 additions & 0 deletions Source/Hurl.Selector/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<Application
x:Class="Hurl.Selector.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Hurl.Selector">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
</ResourceDictionary>
</Application.Resources>
</Application>
96 changes: 96 additions & 0 deletions Source/Hurl.Selector/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using Hurl.Library;
using Hurl.Selector.Helpers;
using Hurl.Selector.Pages;
using Hurl.Selector.Services;
using Hurl.Selector.Services.Interfaces;
using Hurl.Selector.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Windows.AppLifecycle;
using System;
using System.IO;
using System.Text.Json;
using System.Windows;

namespace Hurl.Selector;

public partial class App : Microsoft.UI.Xaml.Application
{
public static IServiceProvider? Services { get; private set; }

private static SelectorWindow? _mainWindow;
private AppActivationArguments? _pendingActivationArgs;

public App()
{
Services = ConfigureServices();
InitializeComponent();
Current.UnhandledException += Dispatcher_UnhandledException;
DispatcherShutdownMode = Microsoft.UI.Xaml.DispatcherShutdownMode.OnLastWindowClose;
AppInstance.GetCurrent().Activated += AppInstance_Activated;
}

private static ServiceProvider ConfigureServices()
{
var services = new ServiceCollection();

services.AddSingleton<ISettingsService, JsonFileService>();
services.AddSingleton<IIconLoader, IconLoaderService>();
services.AddTransient<SettingsViewModel>();
services.AddTransient<SelectorPageViewModel>();

return services.BuildServiceProvider();
}

protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
_mainWindow ??= new SelectorWindow();
HandleActivation(_pendingActivationArgs ?? AppInstance.GetCurrent().GetActivatedEventArgs(), false);
_pendingActivationArgs = null;
}

private void AppInstance_Activated(object? sender, AppActivationArguments args)
{
if (_mainWindow is null)
{
_pendingActivationArgs = args;
return;
}

_ = _mainWindow.DispatcherQueue.TryEnqueue(() => HandleActivation(args, true));
}

private static void HandleActivation(AppActivationArguments activationArgs, bool isSecondInstance)
{
_mainWindow ??= new SelectorWindow();
var cliArgs = CliArgs.GatherInfo(activationArgs, isSecondInstance);
_mainWindow.Init(cliArgs);
}

private void Dispatcher_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
string ErrorMsgBuffer;
string ErrorWndTitle;
switch (e.Exception?.InnerException)
{
case JsonException:
ErrorMsgBuffer = "The UserSettings.json file is in invalid JSON format. \n";
ErrorWndTitle = "Hurl - Invalid JSON";
break;
default:
ErrorMsgBuffer = "An unknown error has occurred. \n";
ErrorWndTitle = "Hurl - Unknown Error";
break;
}

// TODO: create the crashes directory if it doesn't exist
long seconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var argsStoreFile = Path.Combine(Constants.ROAMING, "Hurl", "crashes", $"{seconds}.txt");
var errorFileContents = string.Format("{0}\n\nStackTrace:\n{1}", e.Message,e.Exception.StackTrace);
File.AppendAllText(argsStoreFile, errorFileContents);

string errorMessage = string.Format("{0}\n{1}\n\n{2}", ErrorMsgBuffer, e.Exception?.InnerException?.Message, e.Exception?.Message);
MessageBox.Show(errorMessage, ErrorWndTitle, MessageBoxButton.OK, MessageBoxImage.Error);

Exit();
}
}
Binary file added Source/Hurl.Selector/Assets/internet.ico
Binary file not shown.
147 changes: 147 additions & 0 deletions Source/Hurl.Selector/Helpers/CliArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using Hurl.Library;
using Microsoft.Windows.AppLifecycle;
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.IO;
using System.Threading.Tasks;
using Windows.ApplicationModel.Activation;

namespace Hurl.Selector.Helpers;

public partial class CliArgs
{
// CLI Options and Arguments
private static readonly Option<bool> MinimizedOption = new("--minimized");
private static readonly Option<string?> UriOption = new("--uri");
private static readonly Argument<string[]> ValuesArgument = new("values")
{
Arity = ArgumentArity.ZeroOrMore
};

private static readonly RootCommand CliRoot = CreateRootCommand();

// Properties to store parsed info
public bool IsSecondInstance = false;
public bool IsRunAsMin = false;
public bool IsProtocolActivated = false;
public string Url { get; set; } = string.Empty;

private CliArgs(ParseResult parseResult, bool isSecondInstance)
{
IsRunAsMin = parseResult.GetValue(MinimizedOption);

var values = new List<string>(parseResult.GetValue(ValuesArgument) ?? []);
string? uriValue = parseResult.GetValue(UriOption);

if (IsRunAsMin)
{
return;
}

if (isSecondInstance)
{
IsSecondInstance = true;
if (values.Count >= 2)
{
values.RemoveAt(0);
}
else if (values.Count == 1 && values[0].Contains("Hurl", StringComparison.OrdinalIgnoreCase))
{
return;
}
}

TrimExecutablePath(values);

if (!string.IsNullOrWhiteSpace(uriValue))
{
AssignUrl(uriValue);
return;
}

if (values.Count == 0)
{
return;
}

AssignUrl(values[0]);
}

private void AssignUrl(string value)
{
if (value.StartsWith("hurl://", StringComparison.OrdinalIgnoreCase))
{
IsProtocolActivated = true;
Url = value[7..];
return;
}

Url = value.Contains(' ') ? $"\"{value}\"" : value;
}

private static void TrimExecutablePath(List<string> values)
{
if (values.Count == 0)
{
return;
}

string currentExe = Environment.ProcessPath ?? string.Empty;
if (string.Equals(values[0], currentExe, StringComparison.OrdinalIgnoreCase))
{
values.RemoveAt(0);
}
}

public static CliArgs GatherInfo(AppActivationArguments activationArgs, bool isSecondInstance)
{
var rawCommandLine = GetActivationCommandLine(activationArgs);
var parseResult = CliRoot.Parse(rawCommandLine);

#if DEBUG
Task.Run(() =>
{
Directory.CreateDirectory(Constants.APP_SETTINGS_DIR);
var argsStoreFile = Path.Combine(Constants.ROAMING, "Hurl", "args.txt");
var activationKind = activationArgs.Kind.ToString();
var rawArgs = parseResult.GetValue(ValuesArgument) ?? [];
var strFormat = $"\n\n{isSecondInstance} --- {activationKind} --- {string.Join("__", rawArgs)}";
File.AppendAllText(argsStoreFile, strFormat);
});
#endif

return new CliArgs(parseResult, isSecondInstance);
}

private static RootCommand CreateRootCommand()
{
RootCommand root = new()
{
TreatUnmatchedTokensAsErrors = false
};

root.Add(MinimizedOption);
root.Add(UriOption);
root.Add(ValuesArgument);

return root;
}

private static string GetActivationCommandLine(AppActivationArguments activationArgs)
{
return activationArgs.Kind switch
{
ExtendedActivationKind.Protocol
when activationArgs.Data is IProtocolActivatedEventArgs protocolArgs
=> protocolArgs.Uri.AbsoluteUri,
ExtendedActivationKind.CommandLineLaunch
when activationArgs.Data is ICommandLineActivatedEventArgs commandLineArgs
=> commandLineArgs.Operation.Arguments,
ExtendedActivationKind.Launch
when activationArgs.Data is ILaunchActivatedEventArgs launchArgs
=> launchArgs.Arguments,
_ => string.Empty
};
}
}
Loading