Skip to content

Commit 2a88d67

Browse files
committed
Initial commit
0 parents  commit 2a88d67

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+7099
-0
lines changed

.github/README.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<div align="center">
2+
<img src="../TabPort-webext/src/assets/icon.svg" alt="Logo" style=" width: 128px; height: 128px;" />
3+
</div>
4+
5+
<div align="center">
6+
7+
![GitHub License](https://img.shields.io/github/license/cppakko/TabPort)
8+
![Mozilla Add-on Version](https://img.shields.io/amo/v/TabPort)
9+
![GitHub repo file or directory count](https://img.shields.io/github/directory-file-count/cppakko/TabPort)
10+
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/cppakko/TabPort/build.yml)
11+
12+
</div>
13+
14+
<h1 style="margin: 0;">TabPort</h1>
15+
TabPort is a browser extension and PowerToys Run plugin that helps you:
16+
17+
- Switch tabs across different browsers
18+
- Close tabs
19+
- Copy tab's URL/Title
20+
21+
<p align="center"><img src="screenshot.png" /></p>
22+
23+
## Installation
24+
25+
You have two options for downloading the latest version:
26+
27+
**A. Recommended: Download from
28+
the [Releases page](https://github.com/cppakko/Community.PowerToys.Run.Plugin.TabPort-Private/releases/latest).** This
29+
is the easiest and most stable option, providing pre-built releases.
30+
31+
**B. For the latest development build: Download from GitHub Actions.** This option provides the most up-to-date features
32+
and bug fixes, but may be less stable.
33+
34+
> [!NOTE]
35+
> There are double level compression in the GitHub Actions artifacts. You need to extract the zip file twice to get the
36+
> actual files.
37+
38+
### Browser Extension
39+
40+
**For Firefox Users:**
41+
42+
Due to Firefox's security policies, unsigned extensions cannot be installed directly. You have two options:
43+
44+
1. **Download the signed xpi file from the Releases page or GitHub Actions artifacts (if available) and install it.**
45+
This is the easiest method.
46+
2. **Resolve the signing issue yourself.** You can temporarily install the unsigned extension or sign it with your own
47+
developer certificate. For more details, please refer to
48+
the [official Mozilla documentation](https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/).
49+
50+
**For Chrome/Edge/Opera (Chromium-based browsers) Users:**
51+
52+
1. Enable developer mode in your browser.
53+
2. Install the extension from the downloaded zip file.
54+
55+
### PowerToys Run Plugin
56+
57+
1. Exit PowerToys.
58+
2. Extract the folder from the zip file to `%LOCALAPPDATA%\Microsoft\PowerToys\PowerToys Run\Plugins`.
59+
3. Restart PowerToys and enjoy!
60+
61+
> [!NOTE]
62+
> Currently, after changing the Port number in the PowerToys settings for TabPort, you need to either disable and
63+
> re-enable the PowerToys Run or restart PowerToys entirely for the changes to take effect.
64+
65+
## Favicon Setting
66+
67+
### Favicon DB Priority
68+
69+
By default, the plugin will access the favicon database from these paths depending on the browser you choose:
70+
71+
| Browser | Path |
72+
|-----------|-----------------------------------------------------------------------------|
73+
| Chrome | `%LOCALAPPDATA%\Google\Chrome\User Data\Default\Favicons` |
74+
| Opera | `%AppData%\Opera Software\Opera Stable\Default\Favicons` |
75+
| Edge | `%LOCALAPPDATA%\Microsoft\Edge\User Data\Default\Favicons` |
76+
| Firefox\* | `%APPDATA%\Mozilla\Firefox\Profiles\[Last modifyed folder]\favicons.sqlite` |
77+
78+
### Custom Favicon DB Path
79+
80+
If the plugin can't find the favicon database from the default path, you can set a custom path in the settings.
81+
82+
The database layout is Chromium style (Chrome, Opera, Edge) by default, but you can change it to Firefox style by
83+
changing the `Favicon DB Priority` to Firefox.
84+
85+
### Thanks
86+
87+
Awesome plugin template by [PowerToys Run Plugin Template](https://github.com/hlaueriksson/Community.PowerToys.Run.Plugin.Templates)

.github/screenshot.png

164 KB
Loading

.github/workflows/build.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Build and Package
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
release:
7+
types: [ published ]
8+
9+
jobs:
10+
build_ptr_plugin:
11+
runs-on: ubuntu-latest
12+
defaults:
13+
run:
14+
shell: pwsh
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
- name: Setup .NET 9
20+
uses: actions/setup-dotnet@v4
21+
with:
22+
dotnet-version: 9.0.x
23+
24+
- name: Run .NET build script
25+
run: ./pack.ps1
26+
27+
- name: Upload .NET Artifacts (ARM64)
28+
uses: actions/upload-artifact@v4
29+
with:
30+
name: TabPort-PTR-Plugin-arm64
31+
path: bin/ARM64/*.zip
32+
retention-days: 90
33+
34+
- name: Upload .NET Artifacts (x64)
35+
uses: actions/upload-artifact@v4
36+
with:
37+
name: TabPort-PTR-Plugin-x64
38+
path: bin/x64/*.zip
39+
retention-days: 90
40+
41+
build_webext:
42+
runs-on: ubuntu-latest
43+
needs:
44+
- build_ptr_plugin
45+
defaults:
46+
run:
47+
shell: pwsh
48+
steps:
49+
- name: Checkout code
50+
uses: actions/checkout@v4
51+
52+
- name: Setup Node.js
53+
uses: actions/setup-node@v4
54+
with:
55+
node-version: '22'
56+
57+
- name: Install yarn dependencies
58+
run: yarn install
59+
working-directory: TabPort-webext
60+
61+
- name: Build Node.js packages
62+
run: |
63+
yarn zip
64+
yarn zip -b firefox
65+
working-directory: TabPort-webext
66+
67+
- name: Upload Node.js Artifacts
68+
uses: actions/upload-artifact@v4
69+
with:
70+
name: TabPort-webext
71+
path: TabPort-webext/.output/*.zip
72+
retention-days: 90
73+
include-hidden-files: true

.gitignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
bin/
2+
obj/
3+
/packages/
4+
riderModule.iml
5+
/_ReSharper.Caches/
6+
7+
# Logs
8+
logs
9+
*.log
10+
npm-debug.log*
11+
yarn-debug.log*
12+
yarn-error.log*
13+
pnpm-debug.log*
14+
lerna-debug.log*
15+
16+
TabPort-webext/node_modules
17+
TabPort-webext/.output
18+
TabPort-webext/stats.html
19+
TabPort-webext/stats-*.json
20+
TabPort-webext/.wxt
21+
TabPort-webext/web-ext.config.ts
22+
23+
# Editor directories and files
24+
.vscode/*
25+
!.vscode/extensions.json
26+
.idea
27+
.DS_Store
28+
*.suo
29+
*.ntvs*
30+
*.njsproj
31+
*.sln
32+
*.sw?
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
4+
<UseWPF>true</UseWPF>
5+
<Platforms>x64;ARM64</Platforms>
6+
<PlatformTarget>$(Platform)</PlatformTarget>
7+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
8+
<EnableWindowsTargeting>true</EnableWindowsTargeting>
9+
</PropertyGroup>
10+
<ItemGroup>
11+
<PackageReference Include="Community.PowerToys.Run.Plugin.Dependencies" Version="0.87.1"/>
12+
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0"/>
13+
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2"/>
14+
</ItemGroup>
15+
<ItemGroup>
16+
<None Include=".github\README.md"/>
17+
<None Include="plugin.json">
18+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
19+
</None>
20+
<None Include="Images/*.png">
21+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
22+
</None>
23+
</ItemGroup>
24+
<ItemGroup>
25+
<EmbeddedResource Update="Properties\Resources.resx">
26+
<Generator>ResXFileCodeGenerator</Generator>
27+
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
28+
</EmbeddedResource>
29+
</ItemGroup>
30+
<ItemGroup>
31+
<Compile Update="Properties\Resources.Designer.cs">
32+
<DesignTime>True</DesignTime>
33+
<AutoGen>True</AutoGen>
34+
<DependentUpon>Resources.resx</DependentUpon>
35+
</Compile>
36+
</ItemGroup>
37+
<ItemGroup>
38+
<Content Include=".github\screenshot.png" />
39+
</ItemGroup>
40+
</Project>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAssert_002Ecs_002Fl_003AC_0021_003FUsers_003Fa1378_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fea501b1a950043b99f3df638f1824d6143a18_003Fb8_003Fb16d6a68_003FAssert_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
3+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnvironment_002Ecs_002Fl_003AC_0021_003FUsers_003Fa1378_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F24372012c6c94138bf3f496e104366ffc8f918_003F8e_003F8c12313f_003FEnvironment_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
4+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APowerLauncherPluginSettings_002Ecs_002Fl_003AC_0021_003FUsers_003Fa1378_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe5201fdb4c79492592642afb9032c5a534600_003Fce_003Fd52788bd_003FPowerLauncherPluginSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
5+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AQuery_002Ecs_002Fl_003AC_0021_003FUsers_003Fa1378_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdebb500739d54c82aae3e99580f1842710c00_003Fba_003F3f172956_003FQuery_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
6+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResult_002Ecs_002Fl_003AC_0021_003FUsers_003Fa1378_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdebb500739d54c82aae3e99580f1842710c00_003F08_003F57098445_003FResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
7+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStringMatcher_002Ecs_002Fl_003AC_0021_003FUsers_003Fa1378_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F121667c9ac6445b7be0a50c1084eead210c00_003Ff3_003Ffc37a0d4_003FStringMatcher_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
8+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATask_002Ecs_002Fl_003AC_0021_003FUsers_003Fa1378_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F24372012c6c94138bf3f496e104366ffc8f918_003F17_003F0b7d478c_003FTask_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
9+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003AC_0021_003FUsers_003Fa1378_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F24372012c6c94138bf3f496e104366ffc8f918_003F8a_003Ff6bc1cba_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
10+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATuple_00602_002Ecs_002Fl_003AC_0021_003FUsers_003Fa1378_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F24372012c6c94138bf3f496e104366ffc8f918_003Faf_003F4f478c55_003FTuple_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
11+
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=58c56b00_002Df81e_002D48fd_002Da74f_002Dc8e84271fcf4/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="Continuous Testing" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
12+
&lt;Solution /&gt;&#xD;
13+
&lt;/SessionState&gt;</s:String>
14+
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=c876b98b_002D3305_002D4176_002Da0b5_002Dce1a7737913d/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Query_should_calculate_the_number_of_words" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
15+
&lt;TestAncestor&gt;&#xD;
16+
&lt;TestId&gt;MSTest::0863454B-861B-4E61-BC81-7763A97C1E4A::net8.0-windows10.0.22621.0::Community.PowerToys.Run.Plugin.TabPort.UnitTest.CheckDuplicateTest.DuplicateActiveTitleWithPreviousWindowId_ReturnsManyGroup_MultiWsClient_Duplicates&lt;/TestId&gt;&#xD;
17+
&lt;/TestAncestor&gt;&#xD;
18+
&lt;/SessionState&gt;</s:String>
19+
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=Community_002EPowerToys_002ERun_002EPlugin_002ETabPort_002FProperties_002FResources/@EntryIndexedValue">True</s:Boolean>
20+
21+
<s:Boolean x:Key="/Default/ResxEditorPersonal/Initialized/@EntryValue">True</s:Boolean>
22+
<s:Boolean x:Key="/Default/ResxEditorPersonal/ShowOnlyStringResources/@EntryValue">False</s:Boolean></wpf:ResourceDictionary>

FaviconFetcher.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using Community.PowerToys.Run.Plugin.TabPort.util;
3+
using Microsoft.Data.Sqlite;
4+
using Wox.Plugin.Logger;
5+
6+
namespace Community.PowerToys.Run.Plugin.TabPort;
7+
8+
public class FaviconFetcher
9+
{
10+
private const int MaxRetryCount = 1;
11+
12+
private const string FirefoxQuery =
13+
"""
14+
SELECT data FROM moz_pages_w_icons
15+
INNER JOIN moz_icons_to_pages ON moz_pages_w_icons.id = moz_icons_to_pages.page_id
16+
INNER JOIN moz_icons ON moz_icons_to_pages.icon_id = moz_icons.id
17+
WHERE moz_pages_w_icons.page_url LIKE @domain
18+
ORDER BY width DESC LIMIT 1;
19+
""";
20+
21+
private const string ChromiumQuery =
22+
"""
23+
SELECT image_data FROM icon_mapping
24+
INNER JOIN favicon_bitmaps ON icon_mapping.icon_id = favicon_bitmaps.icon_id
25+
WHERE icon_mapping.page_url LIKE @domain
26+
ORDER BY width DESC LIMIT 1;
27+
""";
28+
29+
public static bool TemporarilyDisabled { get; set; }
30+
31+
public static byte[] FetchFaviconLocalDatabase(string domain, int retryCount = 0)
32+
{
33+
if (TemporarilyDisabled) return null;
34+
if (retryCount > MaxRetryCount) return null;
35+
36+
try
37+
{
38+
var connection = SqliteConnectionUtils.Instance.GetConnection();
39+
var query = Main.Setting.FaviconDbPathPriority == Settings.FaviconDbPathPriorityItem.Firefox
40+
? FirefoxQuery
41+
: ChromiumQuery;
42+
43+
using var command = new SqliteCommand(query, connection);
44+
command.Parameters.AddWithValue("@domain", $"%{GetHostName(domain)}%");
45+
46+
using var reader = command.ExecuteReader();
47+
if (reader.Read())
48+
{
49+
var bufferSize = reader.GetBytes(0, 0, null, 0, 0);
50+
var icoBlob = new byte[bufferSize];
51+
long bytesRead = 0;
52+
var offset = 0;
53+
while (bytesRead < bufferSize)
54+
{
55+
bytesRead += reader.GetBytes(0, offset, icoBlob, offset, (int)(bufferSize - bytesRead));
56+
offset += (int)bytesRead;
57+
}
58+
59+
return icoBlob;
60+
}
61+
}
62+
catch (SqliteException ex)
63+
{
64+
Log.Info(
65+
$"[FaviconFetcher] SQLite error: {ex.Message}, Database Path: {SqliteConnectionUtils.Instance.GetConnection().DataSource}",
66+
typeof(FaviconFetcher));
67+
68+
SqliteConnectionUtils.Instance.ReopenConnection();
69+
return retryCount < MaxRetryCount ? FetchFaviconLocalDatabase(domain, retryCount + 1) : null;
70+
}
71+
catch (Exception ex)
72+
{
73+
Log.Info($"[FaviconFetcher] General error: {ex.Message}", typeof(FaviconFetcher));
74+
}
75+
return null;
76+
}
77+
78+
private static string GetHostName(string url)
79+
{
80+
return new Uri(url).Host;
81+
}
82+
}

Images/tabport.dark.png

689 Bytes
Loading

Images/tabport.light.png

655 Bytes
Loading

Images/website.dark.png

437 Bytes
Loading

0 commit comments

Comments
 (0)