Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -331,9 +331,9 @@ class _CopyPasteAppState extends State<CopyPasteApp>

final isUpdate = _config.lastRunVersion != AppConfig.appVersion;
final windowsNeedsOnboarding =
Platform.isWindows && (!_config.hasSeenOnboarding || isUpdate);
Platform.isWindows && !_config.hasSeenOnboarding;
final linuxNeedsOnboarding =
Platform.isLinux && (!_config.hasCompletedOnboarding || isUpdate);
Platform.isLinux && !_config.hasCompletedOnboarding;
final desktopNeedsOnboarding =
windowsNeedsOnboarding || linuxNeedsOnboarding;
final showOnStart =
Expand Down Expand Up @@ -534,6 +534,7 @@ class _CopyPasteAppState extends State<CopyPasteApp>

void _startListening() {
if (!Platform.isWindows && !Platform.isMacOS && !Platform.isLinux) return;
AppLogger.info('_startListening: subscribing to clipboard event stream');
_listenerSubscription = widget.listener.onEvent.listen(
_onClipboardEvent,
onError: (Object e, StackTrace s) {
Expand Down
10 changes: 7 additions & 3 deletions app/lib/shell/msix_startup_task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class MsixStartupTask {
return _parseState(raw);
} on PlatformException catch (e) {
AppLogger.error(
'MsixStartupTask.getState failed: ${e.code} ${e.message}',
'MsixStartupTask.getState failed: ${e.code} ${e.message} — ${e.details}',
);
return null;
}
Expand All @@ -54,7 +54,9 @@ class MsixStartupTask {
});
return _parseState(raw);
} on PlatformException catch (e) {
AppLogger.error('MsixStartupTask.enable failed: ${e.code} ${e.message}');
AppLogger.error(
'MsixStartupTask.enable failed: ${e.code} ${e.message} — ${e.details}',
);
return null;
}
}
Expand All @@ -66,7 +68,9 @@ class MsixStartupTask {
});
return _parseState(raw);
} on PlatformException catch (e) {
AppLogger.error('MsixStartupTask.disable failed: ${e.code} ${e.message}');
AppLogger.error(
'MsixStartupTask.disable failed: ${e.code} ${e.message} — ${e.details}',
);
return null;
}
}
Expand Down
5 changes: 5 additions & 0 deletions app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ dev_dependencies:
flutter_lints: ^6.0.0
msix: ^3.16.8

msix_config:
startup_task:
task_id: CopyPasteStartup
enabled: true

flutter:
uses-material-design: true

Expand Down
13 changes: 13 additions & 0 deletions app/windows/runner/startup_task_channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ void RegisterStartupTaskChannel(flutter::FlutterViewController* controller) {
winrt::init_apartment();
using namespace winrt::Windows::ApplicationModel;
auto task = StartupTask::GetAsync(wtask_id).get();
if (!task) {
shared_result->Error(
"task_not_found",
"StartupTask not found in manifest",
flutter::EncodableValue(
"No startup task with id '" +
winrt::to_string(wtask_id) +
"' is declared in the AppxManifest."));
return;
}
if (method == "getState") {
shared_result->Success(
flutter::EncodableValue(StateToString(task.State())));
Expand All @@ -101,6 +111,9 @@ void RegisterStartupTaskChannel(flutter::FlutterViewController* controller) {
char code_buf[32];
snprintf(code_buf, sizeof(code_buf), "0x%08X",
static_cast<unsigned int>(e.code()));
// E_INVALIDARG (0x80070057) from GetAsync means the TaskId is not
// declared in the AppxManifest. Ensure windows.startupTask is
// present in the manifest with a matching TaskId attribute.
shared_result->Error("winrt_error", code_buf,
flutter::EncodableValue(
winrt::to_string(e.message())));
Expand Down
18 changes: 18 additions & 0 deletions listener/windows/listener_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ void ListenerPlugin::StartListening(
std::lock_guard<std::mutex> lock(sink_mutex_);
sink_ = std::move(sink);

clipboard_format_registered_ = false;

HWND hwnd = registrar_->GetView()
? registrar_->GetView()->GetNativeWindow()
: nullptr;
Expand All @@ -236,6 +238,7 @@ void ListenerPlugin::StartListening(
HWND topHwnd = hwnd ? GetAncestor(hwnd, GA_ROOT) : nullptr;
if (topHwnd) {
AddClipboardFormatListener(topHwnd);
clipboard_format_registered_ = true;
}

window_proc_id_ = registrar_->RegisterTopLevelWindowProcDelegate(
Expand All @@ -258,13 +261,28 @@ void ListenerPlugin::StopListening() {
KillTimer(topHwnd, kClipboardTimerId);
RemoveClipboardFormatListener(topHwnd);
}
clipboard_format_registered_ = false;

std::lock_guard<std::mutex> lock(sink_mutex_);
sink_ = nullptr;
}

std::optional<LRESULT> ListenerPlugin::HandleWindowMessage(
HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
// Deferred clipboard registration: if AddClipboardFormatListener was not
// called during StartListening (e.g. window handle not ready at plugin-start
// time after an MSIX/standalone update), register it here on the first
// window message. The hwnd passed to RegisterTopLevelWindowProcDelegate is
// always the top-level window, so no GetAncestor() call is needed.
if (!clipboard_format_registered_) {
if (AddClipboardFormatListener(hwnd)) {
clipboard_format_registered_ = true;
OutputDebugStringA(
"[CopyPaste Listener] AddClipboardFormatListener registered "
"deferred (was not ready at StartListening)\n");
}
}

if (message == WM_CLIPBOARDUPDATE) {
KillTimer(hwnd, kClipboardTimerId);
SetTimer(hwnd, kClipboardTimerId, kClipboardTimerDelayMs, nullptr);
Expand Down
2 changes: 2 additions & 0 deletions listener/windows/listener_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class ListenerPlugin : public flutter::Plugin {

ULONGLONG last_write_tick_ = 0;

bool clipboard_format_registered_ = false;

UINT cf_rtf_ = 0;
UINT cf_html_ = 0;
UINT cf_exclude_history_ = 0;
Expand Down
Loading