feat: support windows share sheet (#2555)
@@ -1,4 +1,4 @@
|
||||
import 'dart:convert' show utf8;
|
||||
import 'dart:convert' show jsonDecode, utf8;
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:localsend_app/util/send_ignore.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
import 'package:share_handler/share_handler.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
final _logger = Logger('SelectedSendingFiles');
|
||||
@@ -287,7 +288,27 @@ class LoadSelectionFromArgsAction extends AsyncReduxActionWithResult<SelectedSen
|
||||
@override
|
||||
Future<(List<CrossFile>, bool)> reduce() async {
|
||||
bool filesAdded = false;
|
||||
bool nextShare = false;
|
||||
for (final arg in args) {
|
||||
if (arg == '--share') {
|
||||
nextShare = true;
|
||||
continue;
|
||||
}
|
||||
if (nextShare) {
|
||||
nextShare = false;
|
||||
final json = jsonDecode(arg);
|
||||
final SharedMedia payload = SharedMedia.decode(json);
|
||||
final message = payload.content;
|
||||
if (message != null && message.trim().isNotEmpty) {
|
||||
dispatch(AddMessageAction(message: message));
|
||||
}
|
||||
await dispatchAsync(AddFilesAction(
|
||||
files: payload.attachments?.where((a) => a != null).cast<SharedAttachment>() ?? <SharedAttachment>[],
|
||||
converter: CrossFileConverters.convertSharedAttachment,
|
||||
));
|
||||
filesAdded = true;
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith('-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -15,3 +15,5 @@ x86/
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
localsend_msix_helper.msix
|
||||
|
||||
@@ -74,6 +74,13 @@ set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES
|
||||
"${BINARY_NAME}.exe.manifest"
|
||||
"localsend_msix_helper.msix"
|
||||
"install_msix_helper.ps1"
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Add-AppxPackage .\localsend_msix_helper.msix -ExternalLocation $(Get-Location)
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="0.0.0.0" name="localsendapp" />
|
||||
<msix xmlns="urn:schemas-microsoft-com:msix.v1"
|
||||
publisher="CN=Tien Do Nam, O=Tien Do Nam, S=Sachsen, C=DE"
|
||||
packageName="com.flutter.localsendapp"
|
||||
applicationId="localsendapp"
|
||||
/>
|
||||
</assembly>
|
||||
@@ -11,6 +11,7 @@ add_executable(${BINARY_NAME} WIN32
|
||||
"main.cpp"
|
||||
"utils.cpp"
|
||||
"win32_window.cpp"
|
||||
"winrt_ext.cpp"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
"Runner.rc"
|
||||
"runner.exe.manifest"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "flutter_window.h"
|
||||
#include "utils.h"
|
||||
#include "winrt_ext.h"
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||
@@ -22,6 +23,15 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
std::vector<std::string> command_line_arguments =
|
||||
GetCommandLineArguments();
|
||||
|
||||
if (IsRunningWithIdentity()) {
|
||||
winrt::hstring share_arg = GetSharedMedia();
|
||||
if (!share_arg.empty()) {
|
||||
printf("share: %ls\n", share_arg.c_str());
|
||||
command_line_arguments.push_back("--share");
|
||||
command_line_arguments.push_back(Utf8FromUtf16(share_arg.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
||||
|
||||
FlutterWindow window(project);
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "winrt_ext.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <appmodel.h>
|
||||
#include <winrt/windows.foundation.h>
|
||||
#include <winrt/windows.foundation.collections.h>
|
||||
#include <winrt/windows.storage.h>
|
||||
#include <winrt/windows.storage.streams.h>
|
||||
#include <winrt/windows.applicationmodel.activation.h>
|
||||
#include <winrt/windows.applicationmodel.datatransfer.h>
|
||||
#include <winrt/windows.applicationmodel.datatransfer.sharetarget.h>
|
||||
#include <winrt/windows.data.json.h>
|
||||
|
||||
using winrt::Windows::ApplicationModel::AppInstance;
|
||||
using winrt::Windows::ApplicationModel::Activation::ActivationKind;
|
||||
using winrt::Windows::ApplicationModel::Activation::ShareTargetActivatedEventArgs;
|
||||
using winrt::Windows::ApplicationModel::DataTransfer::DataPackageView;
|
||||
using winrt::Windows::ApplicationModel::DataTransfer::StandardDataFormats;
|
||||
using winrt::Windows::Data::Json::JsonArray;
|
||||
using winrt::Windows::Data::Json::JsonObject;
|
||||
using winrt::Windows::Data::Json::JsonValue;
|
||||
|
||||
enum class SharedAttachmentType {
|
||||
IMAGE,
|
||||
VIDEO,
|
||||
AUDIO,
|
||||
FILE,
|
||||
};
|
||||
|
||||
bool IsRunningWithIdentity() {
|
||||
constexpr SIZE_T kPackageNameMaxLength = 1024;
|
||||
UINT32 length = kPackageNameMaxLength;
|
||||
wchar_t packageName[kPackageNameMaxLength];
|
||||
LONG result = GetCurrentPackageFullName(&length, packageName);
|
||||
|
||||
return (result == ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
winrt::hstring GetSharedMedia() {
|
||||
auto args = AppInstance::GetActivatedEventArgs();
|
||||
if (args == nullptr)
|
||||
return winrt::hstring();
|
||||
if (args.Kind() != ActivationKind::ShareTarget)
|
||||
return winrt::hstring();
|
||||
auto share_target_args = args.as<ShareTargetActivatedEventArgs>();
|
||||
auto op = share_target_args.ShareOperation();
|
||||
auto data = op.Data();
|
||||
JsonObject json;
|
||||
if (data.Contains(StandardDataFormats::Text())) {
|
||||
auto text = data.GetTextAsync().get();
|
||||
json.SetNamedValue(L"content", JsonValue::CreateStringValue(text));
|
||||
}
|
||||
if (data.Contains(StandardDataFormats::Uri())) {
|
||||
auto uri = data.GetUriAsync().get();
|
||||
json.SetNamedValue(L"content", JsonValue::CreateStringValue(uri.ToString()));
|
||||
}
|
||||
if (data.Contains(StandardDataFormats::StorageItems())) {
|
||||
JsonArray attachments;
|
||||
auto storage_items = data.GetStorageItemsAsync().get();
|
||||
for (const auto& item : storage_items) {
|
||||
JsonObject attachment;
|
||||
attachment.SetNamedValue(L"type", JsonValue::CreateNumberValue(double(SharedAttachmentType::FILE)));
|
||||
attachment.SetNamedValue(L"path", JsonValue::CreateStringValue(item.Path()));
|
||||
attachments.Append(attachment);
|
||||
}
|
||||
json.SetNamedValue(L"attachments", attachments);
|
||||
}
|
||||
return json.Stringify();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef RUNNER_WINRT_EXT_H_
|
||||
#define RUNNER_WINRT_EXT_H_
|
||||
|
||||
#include <winrt/base.h>
|
||||
|
||||
bool IsRunningWithIdentity();
|
||||
winrt::hstring GetSharedMedia();
|
||||
|
||||
#endif // RUNNER_WINRT_EXT_H_
|
||||
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4"
|
||||
xmlns:uap6="http://schemas.microsoft.com/appx/manifest/uap/windows10/6"
|
||||
xmlns:uap7="http://schemas.microsoft.com/appx/manifest/uap/windows10/7"
|
||||
xmlns:uap8="http://schemas.microsoft.com/appx/manifest/uap/windows10/8"
|
||||
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
|
||||
xmlns:iot="http://schemas.microsoft.com/appx/manifest/iot/windows10"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:desktop2="http://schemas.microsoft.com/appx/manifest/desktop/windows10/2"
|
||||
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
|
||||
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
|
||||
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:rescap3="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/3"
|
||||
xmlns:rescap6="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/6"
|
||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||
xmlns:com2="http://schemas.microsoft.com/appx/manifest/com/windows10/2"
|
||||
xmlns:com3="http://schemas.microsoft.com/appx/manifest/com/windows10/3"
|
||||
IgnorableNamespaces="uap3 desktop">
|
||||
<Identity Name="11157TienDoNam.LocalSend" Version="1.14.0.0"
|
||||
Publisher="CN=Tien Do Nam, O=Tien Do Nam, S=Sachsen, C=DE" ProcessorArchitecture="x64" />
|
||||
<Properties>
|
||||
<DisplayName>LocalSend MSIX Helper</DisplayName>
|
||||
<PublisherDisplayName>Tien Do Nam</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
<Description>An open source cross-platform alternative to AirDrop</Description>
|
||||
<uap10:AllowExternalContent>true</uap10:AllowExternalContent>
|
||||
</Properties>
|
||||
<Resources>
|
||||
<Resource Language="en" /><Resource Language="ar" /><Resource Language="bn" /><Resource Language="cs" /><Resource Language="da" /><Resource Language="de" /><Resource Language="el" /><Resource Language="es-ES" /><Resource Language="eu" /><Resource Language="fa" /><Resource Language="fr" /><Resource Language="he" /><Resource Language="hu" /><Resource Language="in" /><Resource Language="it" /><Resource Language="ja" /><Resource Language="ko" /><Resource Language="ne" /><Resource Language="nl" /><Resource Language="pl" /><Resource Language="pt-BR" /><Resource Language="ru" /><Resource Language="sv" /><Resource Language="th" /><Resource Language="tr" /><Resource Language="uk" /><Resource Language="vi" /><Resource Language="zh-CN" /><Resource Language="zh-HK" /><Resource Language="zh-TW" />
|
||||
</Resources>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.22621.2506" />
|
||||
</Dependencies>
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
<Applications>
|
||||
<Application Id="localsendapp" Executable="localsend_app.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements AppListEntry="none" BackgroundColor="transparent"
|
||||
DisplayName="LocalSend" Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png" Description="An open source cross-platform alternative to AirDrop">
|
||||
<uap:DefaultTile ShortName="LocalSend" Square310x310Logo="Images\LargeTile.png"
|
||||
Square71x71Logo="Images\SmallTile.png" Wide310x150Logo="Images\Wide310x150Logo.png">
|
||||
<uap:ShowNameOnTiles>
|
||||
<uap:ShowOn Tile="square150x150Logo"/>
|
||||
<uap:ShowOn Tile="square310x310Logo"/>
|
||||
<uap:ShowOn Tile="wide310x150Logo"/>
|
||||
</uap:ShowNameOnTiles>
|
||||
</uap:DefaultTile>
|
||||
<uap:SplashScreen Image="Images\SplashScreen.png"/>
|
||||
<uap:LockScreen BadgeLogo="Images\BadgeLogo.png" Notification="badge"/>
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<desktop:Extension Category="windows.startupTask" Executable="localsend_app.exe" EntryPoint="Windows.FullTrustApplication" uap10:Parameters="autostart">
|
||||
<desktop:StartupTask TaskId="localsend" Enabled="false" DisplayName="LocalSend"/>
|
||||
</desktop:Extension>
|
||||
<uap:Extension Category="windows.shareTarget">
|
||||
<uap:ShareTarget Description="Send to Localsend">
|
||||
<uap:SupportedFileTypes>
|
||||
<uap:SupportsAnyFileType />
|
||||
</uap:SupportedFileTypes>
|
||||
<uap:DataFormat>Text</uap:DataFormat>
|
||||
<uap:DataFormat>Uri</uap:DataFormat>
|
||||
<uap:DataFormat>StorageItems</uap:DataFormat>
|
||||
</uap:ShareTarget>
|
||||
</uap:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
</Package>
|
||||
|
After Width: | Height: | Size: 973 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 622 B |
|
After Width: | Height: | Size: 779 B |
|
After Width: | Height: | Size: 973 B |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 622 B |
|
After Width: | Height: | Size: 779 B |
|
After Width: | Height: | Size: 973 B |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 622 B |
|
After Width: | Height: | Size: 779 B |
|
After Width: | Height: | Size: 973 B |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 58 KiB |
@@ -8,6 +8,7 @@
|
||||
#define MyAppPublisher "Tien Do Nam"
|
||||
#define MyAppURL "https://localsend.org"
|
||||
#define MyAppExeName "localsend_app.exe"
|
||||
#define MyAppMsixHelper "localsend_msix_helper.msix"
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||
@@ -70,8 +71,10 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{
|
||||
|
||||
[Files]
|
||||
Source: "D:\inno\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "D:\inno\{#MyAppExeName}.manifest"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "D:\inno\*.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "D:\inno\data\*"; DestDir: "{app}\data"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "D:\inno\{#MyAppMsixHelper}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
@@ -80,4 +83,7 @@ Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: de
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||
Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -Command Add-AppxPackage .\localsend_msix_helper.msix -ExternalLocation $(Get-Location)"; WorkingDir: {app}; Flags: nowait postinstall
|
||||
|
||||
[UninstallRun]
|
||||
Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -Command Remove-AppxPackage $(Get-AppxPackage com.flutter.localsendapp)"; WorkingDir: {app}; Flags: nowait
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Using Visual Studio 2022 Developer PowerShell
|
||||
# or using e.g. "C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\makeappx.exe"
|
||||
|
||||
MakeAppx.exe pack /o /d .\msix /nv /p .\app\windows\localsend_msix_helper.msix
|
||||