fix: small logic fixes and improvements
This commit is contained in:
@@ -1 +0,0 @@
|
||||
MainPackage=C:\Users\jnih\Downloads\gifBrowser\Olive\bin\x64\Release\net10.0-windows10.0.22621.0\win-x64\Olive_0.0.7.0_x64.msix
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Olive.Helpers;
|
||||
using Olive.Klipy;
|
||||
using Olive.Services;
|
||||
|
||||
@@ -7,30 +8,53 @@ namespace Olive.Commands;
|
||||
|
||||
internal sealed partial class CopyGifCommand : InvokableCommand
|
||||
{
|
||||
private readonly KlipyGif _gif;
|
||||
private static readonly SemaphoreSlim CopyPasteGate = new(1, 1);
|
||||
|
||||
public CopyGifCommand(KlipyGif gif)
|
||||
private readonly KlipyGif _gif;
|
||||
private readonly SettingsManager _settingsManager;
|
||||
|
||||
public CopyGifCommand(KlipyGif gif, SettingsManager settingsManager)
|
||||
{
|
||||
_gif = gif;
|
||||
_settingsManager = settingsManager;
|
||||
Name = "Copy GIF";
|
||||
Icon = new IconInfo("\uE8C8");
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
_ = CopyAsync();
|
||||
return CommandResult.Hide();
|
||||
var closeAfterCopy = _settingsManager.CloseAfterCopy;
|
||||
var pasteAfterCopy = _settingsManager.PasteAfterCopy;
|
||||
|
||||
_ = CopyAsync(closeAfterCopy, pasteAfterCopy);
|
||||
return closeAfterCopy ? CommandResult.Dismiss() : CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
private async Task CopyAsync()
|
||||
private async Task CopyAsync(bool closeAfterCopy, bool pasteAfterCopy)
|
||||
{
|
||||
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
|
||||
if (!await CopyPasteGate.WaitAsync(0, cancellation.Token).ConfigureAwait(false))
|
||||
{
|
||||
ShowStatus("A GIF is already being copied.", MessageState.Warning, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var targetWindowTask = CapturePasteTargetAsync(closeAfterCopy, pasteAfterCopy, cancellation.Token);
|
||||
|
||||
ShowStatus("Downloading GIF...", MessageState.Info, 800);
|
||||
var bytes = await KlipyClient.DownloadGifBytesAsync(_gif.GifUrl, cancellation.Token).ConfigureAwait(false);
|
||||
var downloadTask = KlipyClient.DownloadGifBytesAsync(_gif.GifUrl, cancellation.Token);
|
||||
|
||||
var bytes = await downloadTask.ConfigureAwait(false);
|
||||
var targetWindow = await targetWindowTask.ConfigureAwait(false);
|
||||
await ClipboardService.CopyGifBytesAsync(bytes, cancellation.Token).ConfigureAwait(false);
|
||||
ShowStatus("GIF copied - paste with Ctrl + V", MessageState.Success, 3500);
|
||||
|
||||
if (pasteAfterCopy)
|
||||
await PasteShortcutService.SendPasteAfterRestoringFocusAsync(targetWindow, cancellation.Token)
|
||||
.ConfigureAwait(false);
|
||||
else
|
||||
ShowStatus("GIF copied - paste with Ctrl + V", MessageState.Success, 3500);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -41,6 +65,18 @@ internal sealed partial class CopyGifCommand : InvokableCommand
|
||||
{
|
||||
ShowStatus("Could not copy this GIF.", MessageState.Error, 3500);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CopyPasteGate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<nint> CapturePasteTargetAsync(bool closeAfterCopy, bool pasteAfterCopy,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!pasteAfterCopy) return 0;
|
||||
if (closeAfterCopy) await Task.Delay(150, cancellationToken).ConfigureAwait(false);
|
||||
return PasteShortcutService.GetForegroundWindowHandle();
|
||||
}
|
||||
|
||||
private static void ShowStatus(string message, MessageState state, int duration)
|
||||
|
||||
@@ -11,11 +11,13 @@ internal sealed class SettingsManager : JsonSettingsManager
|
||||
FilePath = SettingsJsonPath();
|
||||
Settings.Add(_klipyApiKey);
|
||||
Settings.Add(_resultCount);
|
||||
Settings.Add(_closeAfterCopy);
|
||||
Settings.Add(_pasteAfterCopy);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
Settings.SettingsChanged += (_, _) => SaveSettings();
|
||||
Settings.SettingsChanged += (_, _) => { SaveSettings(); };
|
||||
}
|
||||
|
||||
public string SettingsPath => FilePath;
|
||||
@@ -31,19 +33,34 @@ internal sealed class SettingsManager : JsonSettingsManager
|
||||
ErrorMessage = "Klipy API key is required."
|
||||
};
|
||||
|
||||
private const int MinResultCount = 30;
|
||||
private const int MaxResultCount = 100;
|
||||
|
||||
private readonly IntegerSetting _resultCount = new(
|
||||
Namespaced(nameof(ResultCount)),
|
||||
"Result count",
|
||||
"Number of GIF results to load for each search.",
|
||||
50,
|
||||
20,
|
||||
100)
|
||||
MinResultCount,
|
||||
MaxResultCount)
|
||||
{
|
||||
Placeholder = "Min 20, max 100",
|
||||
Placeholder = $"Min {MinResultCount}, max {MaxResultCount}",
|
||||
IsRequired = true,
|
||||
ErrorMessage = "Enter a number between 20 and 100."
|
||||
ErrorMessage = $"Enter a number between {MinResultCount} and {MaxResultCount}."
|
||||
};
|
||||
|
||||
private readonly ToggleSetting _closeAfterCopy = new(
|
||||
Namespaced(nameof(CloseAfterCopy)),
|
||||
"Close after copy",
|
||||
"Automatically close Olive after copying a GIF.",
|
||||
true);
|
||||
|
||||
private readonly ToggleSetting _pasteAfterCopy = new(
|
||||
Namespaced(nameof(PasteAfterCopy)),
|
||||
"Paste after copy",
|
||||
"After copying a GIF, send Ctrl+V.",
|
||||
false);
|
||||
|
||||
public string KlipyApiKey
|
||||
{
|
||||
get
|
||||
@@ -64,6 +81,24 @@ internal sealed class SettingsManager : JsonSettingsManager
|
||||
}
|
||||
}
|
||||
|
||||
public bool CloseAfterCopy
|
||||
{
|
||||
get
|
||||
{
|
||||
LoadSettings();
|
||||
return _closeAfterCopy.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool PasteAfterCopy
|
||||
{
|
||||
get
|
||||
{
|
||||
LoadSettings();
|
||||
return _pasteAfterCopy.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static string SettingsJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Olive");
|
||||
|
||||
@@ -24,8 +24,22 @@ public sealed partial class OliveCommandsProvider : CommandProvider
|
||||
{
|
||||
Title = "Olive GIF Picker",
|
||||
Subtitle = "Browse through lots of GIFs and find the one that suits you best!",
|
||||
MoreCommands = [new CommandContextItem(_settingsManager.Settings.SettingsPage)],
|
||||
MoreCommands =
|
||||
[
|
||||
new CommandContextItem(_settingsManager.Settings.SettingsPage)
|
||||
{
|
||||
Title = "Olive Settings",
|
||||
Subtitle = "Configure the Klipy API key and result count.",
|
||||
Icon = new IconInfo("\uE713")
|
||||
}
|
||||
],
|
||||
Icon = Icon
|
||||
},
|
||||
new CommandItem(_settingsManager.Settings.SettingsPage)
|
||||
{
|
||||
Title = "Olive Settings",
|
||||
Subtitle = "Configure the Klipy API key and result count.",
|
||||
Icon = new IconInfo("\uE713")
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
<Identity
|
||||
Name="Olive"
|
||||
Publisher="CN=OlivePrivate"
|
||||
Version="0.0.42.0"/>
|
||||
Version="0.0.60.0"/>
|
||||
<Properties>
|
||||
<DisplayName>Olive</DisplayName>
|
||||
<PublisherDisplayName>Private</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
<PublisherDisplayName>EndMove 'contact@endmove.eu'</PublisherDisplayName>
|
||||
<Logo>Assets\AppLogo150.scale-200.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
|
||||
@@ -17,25 +17,27 @@ internal sealed class GifListItemFactory
|
||||
_settingsManager = settingsManager;
|
||||
}
|
||||
|
||||
public static ListItem CreateGifItem(KlipyGif gif)
|
||||
public ListItem CreateGifItem(KlipyGif gif)
|
||||
{
|
||||
return new ListItem(new CopyGifCommand(gif))
|
||||
return new ListItem(new CopyGifCommand(gif, _settingsManager))
|
||||
{
|
||||
Title = string.Empty,
|
||||
Subtitle = string.Empty,
|
||||
Icon = new IconInfo(gif.ThumbnailGifUrl.ToString())
|
||||
Icon = new IconInfo(gif.ThumbnailGifUrl.ToString()),
|
||||
MoreCommands = CommonMoreCommands()
|
||||
};
|
||||
}
|
||||
|
||||
public CommandItem InitialContent()
|
||||
{
|
||||
return new CommandItem(new NoOpCommand())
|
||||
return new CommandItem(_settingsManager.HasKlipyApiKey ? new NoOpCommand() : _settingsManager.Settings.SettingsPage)
|
||||
{
|
||||
Title = _settingsManager.HasKlipyApiKey ? "Search GIFs" : "Klipy API key missing",
|
||||
Title = _settingsManager.HasKlipyApiKey ? "Search GIFs from Klipy.com" : "Klipy API key missing",
|
||||
Subtitle = _settingsManager.HasKlipyApiKey
|
||||
? "Type something like thanks, excited, cat, or confused."
|
||||
: "Open Olive settings and set the Klipy API key.",
|
||||
Icon = _icon
|
||||
Icon = _icon,
|
||||
MoreCommands = CommonMoreCommands()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,17 +47,19 @@ internal sealed class GifListItemFactory
|
||||
{
|
||||
Title = "Loading GIFs...",
|
||||
Subtitle = $"Searching Klipy for \"{search}\"",
|
||||
Icon = _icon
|
||||
Icon = _icon,
|
||||
MoreCommands = CommonMoreCommands()
|
||||
};
|
||||
}
|
||||
|
||||
public CommandItem MissingApiKeyContent()
|
||||
{
|
||||
return new CommandItem(new NoOpCommand())
|
||||
return new CommandItem(_settingsManager.Settings.SettingsPage)
|
||||
{
|
||||
Title = "Klipy API key missing",
|
||||
Subtitle = "Open Olive settings and paste your Klipy API key.",
|
||||
Icon = _icon
|
||||
Icon = _icon,
|
||||
MoreCommands = CommonMoreCommands()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,7 +69,8 @@ internal sealed class GifListItemFactory
|
||||
{
|
||||
Title = "No GIF found",
|
||||
Subtitle = $"Try another search for \"{search}\".",
|
||||
Icon = _icon
|
||||
Icon = _icon,
|
||||
MoreCommands = CommonMoreCommands()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,7 +80,33 @@ internal sealed class GifListItemFactory
|
||||
{
|
||||
Title = "Search failed",
|
||||
Subtitle = ex.Message,
|
||||
Icon = _icon
|
||||
Icon = _icon,
|
||||
MoreCommands = CommonMoreCommands()
|
||||
};
|
||||
}
|
||||
|
||||
private IContextItem[] CommonMoreCommands()
|
||||
{
|
||||
return
|
||||
[
|
||||
new CommandContextItem(_settingsManager.Settings.SettingsPage)
|
||||
{
|
||||
Title = "Olive Settings",
|
||||
Subtitle = "Configure Olive.",
|
||||
Icon = new IconInfo("\uE713")
|
||||
},
|
||||
LinkCommand("Visit Klipy", "Open klipy.com", "https://klipy.com", "\uE774"),
|
||||
LinkCommand("Source code", "Open the Olive repository", "https://git.endmove.eu/EndMove/Olive", "\uE943")
|
||||
];
|
||||
}
|
||||
|
||||
private static CommandContextItem LinkCommand(string title, string subtitle, string url, string icon)
|
||||
{
|
||||
return new CommandContextItem(new OpenUrlCommand(url) { Result = CommandResult.Dismiss() })
|
||||
{
|
||||
Title = title,
|
||||
Subtitle = subtitle,
|
||||
Icon = new IconInfo(icon)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Olive.Pages;
|
||||
internal sealed partial class GifPickerPage : IDynamicListPage
|
||||
{
|
||||
private const int KlipyMaxPageSize = 50;
|
||||
private static readonly TimeSpan SearchDebounceDelay = TimeSpan.FromMilliseconds(500);
|
||||
private static readonly TimeSpan SearchDebounceDelay = TimeSpan.FromMilliseconds(350);
|
||||
|
||||
private readonly Lock _stateLock = new();
|
||||
private readonly SettingsManager _settingsManager;
|
||||
@@ -63,7 +63,7 @@ internal sealed partial class GifPickerPage : IDynamicListPage
|
||||
|
||||
public IFilters? Filters => null;
|
||||
|
||||
public IGridProperties GridProperties { get; } = new GalleryGridLayout { ShowTitle = true, ShowSubtitle = false };
|
||||
public IGridProperties GridProperties { get; } = new GalleryGridLayout { ShowTitle = false, ShowSubtitle = false };
|
||||
|
||||
public ICommandItem EmptyContent
|
||||
{
|
||||
@@ -181,7 +181,7 @@ internal sealed partial class GifPickerPage : IDynamicListPage
|
||||
return;
|
||||
}
|
||||
|
||||
ReplaceState(gifs.Select(GifListItemFactory.CreateGifItem).ToArray());
|
||||
ReplaceState(gifs.Select(_itemFactory.CreateGifItem).ToArray());
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Olive.Services;
|
||||
|
||||
internal static class PasteShortcutService
|
||||
{
|
||||
private const ushort ControlKey = 0x11;
|
||||
private const ushort VKey = 0x56;
|
||||
private const uint KeyUp = 0x0002;
|
||||
private const uint InputKeyboard = 1;
|
||||
private const int RestoreWindow = 9;
|
||||
private static readonly TimeSpan FocusSettleDelay = TimeSpan.FromMilliseconds(450);
|
||||
|
||||
public static nint GetForegroundWindowHandle()
|
||||
{
|
||||
return GetForegroundWindow();
|
||||
}
|
||||
|
||||
public static async Task SendPasteAfterRestoringFocusAsync(nint targetWindow, CancellationToken cancellationToken)
|
||||
{
|
||||
RestoreTargetWindow(targetWindow);
|
||||
await Task.Delay(FocusSettleDelay, cancellationToken).ConfigureAwait(false);
|
||||
SendKeyChord(ControlKey, VKey);
|
||||
}
|
||||
|
||||
private static void RestoreTargetWindow(nint targetWindow)
|
||||
{
|
||||
if (targetWindow == 0 || !IsWindow(targetWindow)) return;
|
||||
|
||||
if (IsIconic(targetWindow)) ShowWindow(targetWindow, RestoreWindow);
|
||||
SetForegroundWindow(targetWindow);
|
||||
}
|
||||
|
||||
private static void SendKeyChord(ushort modifierKey, ushort key)
|
||||
{
|
||||
var inputs = new[]
|
||||
{
|
||||
KeyDown(modifierKey),
|
||||
KeyDown(key),
|
||||
KeyUpInput(key),
|
||||
KeyUpInput(modifierKey)
|
||||
};
|
||||
|
||||
var sent = SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<Input>());
|
||||
if (sent != inputs.Length) throw new InvalidOperationException("Could not send Ctrl+V to the active app.");
|
||||
}
|
||||
|
||||
private static Input KeyDown(ushort key)
|
||||
{
|
||||
return new Input
|
||||
{
|
||||
Type = InputKeyboard,
|
||||
Data = new InputUnion { Keyboard = new KeyboardInput { VirtualKey = key } }
|
||||
};
|
||||
}
|
||||
|
||||
private static Input KeyUpInput(ushort key)
|
||||
{
|
||||
return new Input
|
||||
{
|
||||
Type = InputKeyboard,
|
||||
Data = new InputUnion { Keyboard = new KeyboardInput { VirtualKey = key, Flags = KeyUp } }
|
||||
};
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern uint SendInput(uint inputCount, Input[] inputs, int inputSize);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern nint GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsWindow(nint windowHandle);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsIconic(nint windowHandle);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(nint windowHandle, int commandShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(nint windowHandle);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct Input
|
||||
{
|
||||
public uint Type;
|
||||
public InputUnion Data;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct InputUnion
|
||||
{
|
||||
[FieldOffset(0)] public MouseInput Mouse;
|
||||
|
||||
[FieldOffset(0)] public KeyboardInput Keyboard;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MouseInput
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
public uint MouseData;
|
||||
public uint Flags;
|
||||
public uint Time;
|
||||
public nint ExtraInfo;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct KeyboardInput
|
||||
{
|
||||
public ushort VirtualKey;
|
||||
public ushort ScanCode;
|
||||
public uint Flags;
|
||||
public uint Time;
|
||||
public nint ExtraInfo;
|
||||
}
|
||||
}
|
||||
@@ -1,120 +1,19 @@
|
||||
# Olive
|
||||
|
||||
Olive
|
||||
is
|
||||
a
|
||||
private
|
||||
Microsoft
|
||||
PowerToys
|
||||
Command
|
||||
Palette
|
||||
extension.
|
||||
It
|
||||
searches
|
||||
animated
|
||||
GIFs
|
||||
with
|
||||
Klipy
|
||||
and
|
||||
allow
|
||||
you
|
||||
to
|
||||
quickly
|
||||
the
|
||||
one
|
||||
that
|
||||
suits
|
||||
you
|
||||
best!
|
||||
Olive is a private Microsoft PowerToys Command Palette extension. It searches animated GIFs with Klipy and allows you to quickly use the one that suits you best!
|
||||
|
||||
## Requirements
|
||||
|
||||
-
|
||||
|
||||
Windows
|
||||
10/11
|
||||
with
|
||||
PowerToys
|
||||
and
|
||||
Command
|
||||
Palette
|
||||
enabled.
|
||||
|
||||
-
|
||||
|
||||
.NET
|
||||
SDK
|
||||
|
||||
10.
|
||||
|
||||
-
|
||||
|
||||
Windows
|
||||
SDK
|
||||
`10.0.22621.0`
|
||||
or
|
||||
a
|
||||
compatible
|
||||
newer
|
||||
SDK.
|
||||
|
||||
-
|
||||
|
||||
A
|
||||
Klipy
|
||||
API
|
||||
key.
|
||||
- Windows 10/11 with PowerToys and Command Palette enabled.
|
||||
- .NET SDK 10.
|
||||
- Windows SDK `10.0.22621.0` or a compatible newer SDK.
|
||||
- A Klipy API key.
|
||||
|
||||
## Klipy API Key
|
||||
|
||||
Olive
|
||||
stores
|
||||
the
|
||||
Klipy
|
||||
key
|
||||
locally
|
||||
in
|
||||
its
|
||||
Command
|
||||
Palette
|
||||
settings.
|
||||
Do
|
||||
not
|
||||
put
|
||||
the
|
||||
key
|
||||
in
|
||||
source
|
||||
code,
|
||||
Git,
|
||||
environment
|
||||
variables,
|
||||
or
|
||||
the
|
||||
package.
|
||||
Olive stores the Klipy key locally in its Command Palette settings. Do not put the key in source code, Git, environment variables, or the package.
|
||||
|
||||
To
|
||||
configure
|
||||
it,
|
||||
open
|
||||
Command
|
||||
Palette,
|
||||
open
|
||||
the
|
||||
context
|
||||
commands
|
||||
for
|
||||
`Olive GIF Picker`,
|
||||
open
|
||||
Olive
|
||||
settings,
|
||||
paste
|
||||
the
|
||||
key
|
||||
into
|
||||
`Klipy API key`,
|
||||
then
|
||||
save.
|
||||
To configure it, open Command Palette, open the context commands for `Olive GIF Picker`, open Olive settings, paste the key into `Klipy API key`, then save.
|
||||
|
||||
## Build
|
||||
|
||||
@@ -128,251 +27,71 @@ dotnet build .\Olive.sln -c Debug -p:Platform=x64
|
||||
.\scripts\Build-OlivePackage.ps1
|
||||
```
|
||||
|
||||
Or
|
||||
double-click:
|
||||
Or double-click:
|
||||
|
||||
```text
|
||||
scripts\Build-OlivePackage.cmd
|
||||
```
|
||||
|
||||
This
|
||||
creates
|
||||
the
|
||||
shareable
|
||||
package
|
||||
in
|
||||
`dist\OlivePackage`.
|
||||
This creates the shareable package in `dist\OlivePackage`.
|
||||
|
||||
The
|
||||
private
|
||||
signing
|
||||
file
|
||||
is
|
||||
exported
|
||||
to
|
||||
`dist\private\OlivePrivate.pfx`.
|
||||
Do
|
||||
not
|
||||
share
|
||||
it
|
||||
unless
|
||||
the
|
||||
recipient
|
||||
must
|
||||
be
|
||||
able
|
||||
to
|
||||
sign
|
||||
Olive
|
||||
builds.
|
||||
The private signing file is exported to `dist\private\OlivePrivate.pfx`. Do not share it unless the recipient must be able to sign Olive builds.
|
||||
|
||||
## Install
|
||||
|
||||
From
|
||||
`dist\OlivePackage`:
|
||||
From `dist\OlivePackage`:
|
||||
|
||||
```powershell
|
||||
.\Install-Olive.ps1
|
||||
```
|
||||
|
||||
Or
|
||||
double-click:
|
||||
Or double-click:
|
||||
|
||||
```text
|
||||
Install-Olive.cmd
|
||||
```
|
||||
|
||||
The
|
||||
script
|
||||
asks
|
||||
for
|
||||
administrator
|
||||
rights,
|
||||
imports
|
||||
`OlivePrivate.cer`
|
||||
into
|
||||
`Cert:\LocalMachine\Root`,
|
||||
then
|
||||
opens
|
||||
the
|
||||
`.msix`
|
||||
with
|
||||
Windows
|
||||
App
|
||||
Installer.
|
||||
The script asks for administrator rights, imports `OlivePrivate.cer` into `Cert:\LocalMachine\Root`, then opens the `.msix` with Windows App Installer.
|
||||
|
||||
Restart
|
||||
PowerToys
|
||||
after
|
||||
installation.
|
||||
Restart PowerToys after installation.
|
||||
|
||||
## Uninstall
|
||||
|
||||
From
|
||||
`dist\OlivePackage`
|
||||
or
|
||||
`scripts\`:
|
||||
From `dist\OlivePackage` or `scripts\`:
|
||||
|
||||
```powershell
|
||||
.\Uninstall-Olive.ps1
|
||||
```
|
||||
|
||||
Or
|
||||
double-click:
|
||||
Or double-click:
|
||||
|
||||
```text
|
||||
Uninstall-Olive.cmd
|
||||
```
|
||||
|
||||
By
|
||||
default,
|
||||
uninstall
|
||||
removes
|
||||
the
|
||||
Olive
|
||||
package,
|
||||
the
|
||||
Olive
|
||||
certificate
|
||||
from
|
||||
`Cert:\LocalMachine\Root`,
|
||||
and
|
||||
Olive
|
||||
user
|
||||
data.
|
||||
By default, uninstall removes the Olive package, the Olive certificate from `Cert:\LocalMachine\Root`, and Olive user data.
|
||||
|
||||
Use
|
||||
`-KeepCertificate`
|
||||
to
|
||||
keep
|
||||
the
|
||||
certificate.
|
||||
Use `-KeepCertificate` to keep the certificate.
|
||||
|
||||
Use
|
||||
`-KeepUserData`
|
||||
to
|
||||
keep
|
||||
Olive
|
||||
user
|
||||
data.
|
||||
Use `-KeepUserData` to keep Olive user data.
|
||||
|
||||
## Usage
|
||||
|
||||
1.
|
||||
|
||||
Open
|
||||
PowerToys
|
||||
Command
|
||||
Palette.
|
||||
|
||||
2.
|
||||
|
||||
Launch
|
||||
`Olive GIF Picker`.
|
||||
|
||||
3.
|
||||
|
||||
Search
|
||||
for
|
||||
a
|
||||
GIF.
|
||||
|
||||
4.
|
||||
|
||||
Select
|
||||
a
|
||||
result
|
||||
with
|
||||
Enter.
|
||||
|
||||
5.
|
||||
|
||||
Paste
|
||||
with
|
||||
Ctrl+V
|
||||
in
|
||||
an
|
||||
app
|
||||
that
|
||||
accepts
|
||||
animated
|
||||
GIF
|
||||
files.
|
||||
1. Open PowerToys Command Palette.
|
||||
2. Launch `Olive GIF Picker`.
|
||||
3. Search for a GIF.
|
||||
4. Select a result with Enter.
|
||||
5. Paste with Ctrl+V in an app that accepts animated GIF files.
|
||||
|
||||
## Sharing
|
||||
|
||||
-
|
||||
|
||||
Share
|
||||
only
|
||||
`dist\OlivePackage`.
|
||||
|
||||
-
|
||||
|
||||
Do
|
||||
not
|
||||
share
|
||||
`dist\private\OlivePrivate.pfx`.
|
||||
|
||||
-
|
||||
|
||||
Do
|
||||
not
|
||||
include
|
||||
the
|
||||
Klipy
|
||||
API
|
||||
key
|
||||
in
|
||||
the
|
||||
package.
|
||||
|
||||
-
|
||||
|
||||
Each
|
||||
user
|
||||
must
|
||||
set
|
||||
the
|
||||
Klipy
|
||||
key
|
||||
in
|
||||
Olive
|
||||
settings.
|
||||
- Share only `dist\OlivePackage`.
|
||||
- Do not share `dist\private\OlivePrivate.pfx`.
|
||||
- Do not include the Klipy API key in the package.
|
||||
- Each user must set the Klipy key in Olive settings.
|
||||
|
||||
## Notes
|
||||
|
||||
-
|
||||
|
||||
Olive
|
||||
copies
|
||||
GIFs
|
||||
as
|
||||
files
|
||||
to
|
||||
preserve
|
||||
animation.
|
||||
|
||||
-
|
||||
|
||||
Some
|
||||
apps
|
||||
may
|
||||
reject
|
||||
file
|
||||
pasting
|
||||
or
|
||||
use
|
||||
fallback
|
||||
text
|
||||
only.
|
||||
|
||||
-
|
||||
|
||||
GIFs
|
||||
are
|
||||
downloaded
|
||||
in
|
||||
memory
|
||||
when
|
||||
copied.
|
||||
- Olive copies GIFs as files to preserve animation.
|
||||
- Some apps may reject file pasting or use fallback text only.
|
||||
- GIFs are downloaded in memory when copied.
|
||||
|
||||
Reference in New Issue
Block a user