fix: Récupérer l'opération active depuis la table operations
- Corrige l'erreur SQL 'Unknown column fk_operation in users' - L'opération active est récupérée depuis operations.chk_active = 1 - Jointure avec users pour filtrer par entité de l'admin créateur - Query: SELECT o.id FROM operations o INNER JOIN users u ON u.fk_entite = o.fk_entite WHERE u.id = ? AND o.chk_active = 1
This commit is contained in:
@@ -1 +0,0 @@
|
||||
/home/pierre/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/
|
||||
66
app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/AUTHORS
Executable file
66
app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/AUTHORS
Executable file
@@ -0,0 +1,66 @@
|
||||
# Below is a list of people and organizations that have contributed
|
||||
# to the Flutter project. Names should be added to the list like so:
|
||||
#
|
||||
# Name/Organization <email address>
|
||||
|
||||
Google Inc.
|
||||
The Chromium Authors
|
||||
German Saprykin <saprykin.h@gmail.com>
|
||||
Benjamin Sauer <sauer.benjamin@gmail.com>
|
||||
larsenthomasj@gmail.com
|
||||
Ali Bitek <alibitek@protonmail.ch>
|
||||
Pol Batlló <pol.batllo@gmail.com>
|
||||
Anatoly Pulyaevskiy
|
||||
Hayden Flinner <haydenflinner@gmail.com>
|
||||
Stefano Rodriguez <hlsroddy@gmail.com>
|
||||
Salvatore Giordano <salvatoregiordanoo@gmail.com>
|
||||
Brian Armstrong <brian@flutter.institute>
|
||||
Paul DeMarco <paulmdemarco@gmail.com>
|
||||
Fabricio Nogueira <feufeu@gmail.com>
|
||||
Simon Lightfoot <simon@devangels.london>
|
||||
Ashton Thomas <ashton@acrinta.com>
|
||||
Thomas Danner <thmsdnnr@gmail.com>
|
||||
Diego Velásquez <diego.velasquez.lopez@gmail.com>
|
||||
Hajime Nakamura <nkmrhj@gmail.com>
|
||||
Tuyển Vũ Xuân <netsoft1985@gmail.com>
|
||||
Miguel Ruivo <miguel@miguelruivo.com>
|
||||
Sarthak Verma <sarthak@artiosys.com>
|
||||
Mike Diarmid <mike@invertase.io>
|
||||
Invertase <oss@invertase.io>
|
||||
Elliot Hesp <elliot@invertase.io>
|
||||
Vince Varga <vince.varga@smaho.com>
|
||||
Aawaz Gyawali <awazgyawali@gmail.com>
|
||||
EUI Limited <ian.evans3@admiralgroup.co.uk>
|
||||
Katarina Sheremet <katarina@sheremet.ch>
|
||||
Thomas Stockx <thomas@stockxit.com>
|
||||
Sarbagya Dhaubanjar <sarbagyastha@gmail.com>
|
||||
Ozkan Eksi <ozeksi@gmail.com>
|
||||
Rishab Nayak <rishab@bu.edu>
|
||||
ko2ic <ko2ic.dev@gmail.com>
|
||||
Jonathan Younger <jonathan@daikini.com>
|
||||
Jose Sanchez <josesm82@gmail.com>
|
||||
Debkanchan Samadder <debu.samadder@gmail.com>
|
||||
Audrius Karosevicius <audrius.karosevicius@gmail.com>
|
||||
Lukasz Piliszczuk <lukasz@intheloup.io>
|
||||
SoundReply Solutions GmbH <ch@soundreply.com>
|
||||
Rafal Wachol <rwachol@gmail.com>
|
||||
Pau Picas <pau.picas@gmail.com>
|
||||
Christian Weder <chrstian.weder@yapeal.ch>
|
||||
Alexandru Tuca <salexandru.tuca@outlook.com>
|
||||
Christian Weder <chrstian.weder@yapeal.ch>
|
||||
Rhodes Davis Jr. <rody.davis.jr@gmail.com>
|
||||
Luigi Agosti <luigi@tengio.com>
|
||||
Quentin Le Guennec <quentin@tengio.com>
|
||||
Koushik Ravikumar <koushik@tengio.com>
|
||||
Nissim Dsilva <nissim@tengio.com>
|
||||
Giancarlo Rocha <giancarloiff@gmail.com>
|
||||
Ryo Miyake <ryo@miyake.id>
|
||||
Théo Champion <contact.theochampion@gmail.com>
|
||||
Kazuki Yamaguchi <y.kazuki0614n@gmail.com>
|
||||
Eitan Schwartz <eshvartz@gmail.com>
|
||||
Chris Rutkowski <chrisrutkowski89@gmail.com>
|
||||
Juan Alvarez <juan.alvarez@resideo.com>
|
||||
Aleksandr Yurkovskiy <sanekyy@gmail.com>
|
||||
Anton Borries <mail@antonborri.es>
|
||||
Alex Li <google@alexv525.com>
|
||||
Rahul Raj <64.rahulraj@gmail.com>
|
||||
@@ -0,0 +1,123 @@
|
||||
## 2.3.0
|
||||
|
||||
* Replaces `win32` dependency with direct FFI usage.
|
||||
* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2.
|
||||
|
||||
## 2.2.1
|
||||
|
||||
* Adds pub topics to package metadata.
|
||||
* Updates minimum supported SDK version to Flutter 3.7/Dart 2.19.
|
||||
|
||||
## 2.2.0
|
||||
|
||||
* Adds getApplicationCachePath() for storing app-specific cache files.
|
||||
|
||||
## 2.1.7
|
||||
|
||||
* Adds compatibility with `win32` 5.x.
|
||||
* Updates minimum supported SDK version to Flutter 3.3/Dart 2.18.
|
||||
|
||||
## 2.1.6
|
||||
|
||||
* Adds compatibility with `win32` 4.x.
|
||||
|
||||
## 2.1.5
|
||||
|
||||
* Clarifies explanation of endorsement in README.
|
||||
* Aligns Dart and Flutter SDK constraints.
|
||||
|
||||
## 2.1.4
|
||||
|
||||
* Updates links for the merge of flutter/plugins into flutter/packages.
|
||||
* Updates minimum Flutter version to 3.0.
|
||||
|
||||
## 2.1.3
|
||||
|
||||
* Updates minimum Flutter version to 2.10.
|
||||
* Adds compatibility with `package:win32` 3.x.
|
||||
|
||||
## 2.1.2
|
||||
|
||||
* Fixes avoid_redundant_argument_values lint warnings and minor typos.
|
||||
|
||||
## 2.1.1
|
||||
|
||||
* Updates dependency version of `package:win32` to 2.1.0.
|
||||
|
||||
## 2.1.0
|
||||
|
||||
* Upgrades `package:ffi` dependency to 2.0.0.
|
||||
* Added support for unicode encoded VERSIONINFO.
|
||||
* Minor fixes for new analysis options.
|
||||
|
||||
## 2.0.6
|
||||
|
||||
* Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors
|
||||
lint warnings.
|
||||
|
||||
## 2.0.5
|
||||
|
||||
* Removes dependency on `meta`.
|
||||
|
||||
## 2.0.4
|
||||
|
||||
* Removed obsolete `pluginClass: none` from pubpsec.
|
||||
|
||||
## 2.0.3
|
||||
|
||||
* Updated installation instructions in README.
|
||||
|
||||
## 2.0.2
|
||||
|
||||
* Add `implements` to pubspec.yaml.
|
||||
* Add `registerWith()` to the Dart main class.
|
||||
|
||||
## 2.0.1
|
||||
|
||||
* Fix a crash when a known folder can't be located.
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* Migrate to null safety
|
||||
|
||||
## 0.0.4+4
|
||||
|
||||
* Update Flutter SDK constraint.
|
||||
|
||||
## 0.0.4+3
|
||||
|
||||
* Remove unused `test` dependency.
|
||||
* Update Dart SDK constraint in example.
|
||||
|
||||
## 0.0.4+2
|
||||
|
||||
* Check in windows/ directory for example/
|
||||
|
||||
## 0.0.4+1
|
||||
|
||||
* Add getPath to the stub, so that the analyzer won't complain about
|
||||
fakes that override it.
|
||||
* export 'folders.dart' rather than importing it, since it's intended to be
|
||||
public.
|
||||
|
||||
## 0.0.4
|
||||
|
||||
* Move the actual implementation behind a conditional import, exporting
|
||||
a stub for platforms that don't support FFI. Fixes web builds in
|
||||
projects with transitive dependencies on path_provider.
|
||||
|
||||
## 0.0.3
|
||||
|
||||
* Add missing `pluginClass: none` for compatibilty with stable channel.
|
||||
|
||||
## 0.0.2
|
||||
|
||||
* README update for endorsement.
|
||||
* Changed getApplicationSupportPath location.
|
||||
* Removed getLibraryPath.
|
||||
|
||||
## 0.0.1+2
|
||||
|
||||
* The initial implementation of path_provider for Windows
|
||||
* Implements getTemporaryPath, getApplicationSupportPath, getLibraryPath,
|
||||
getApplicationDocumentsPath and getDownloadsPath.
|
||||
25
app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/LICENSE
Executable file
25
app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/LICENSE
Executable file
@@ -0,0 +1,25 @@
|
||||
Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,15 @@
|
||||
# path\_provider\_windows
|
||||
|
||||
The Windows implementation of [`path_provider`][1].
|
||||
|
||||
## Usage
|
||||
|
||||
This package is [endorsed][2], which means you can simply use `path_provider`
|
||||
normally. This package will be automatically included in your app when you do,
|
||||
so you do not need to add it to your `pubspec.yaml`.
|
||||
|
||||
However, if you `import` this package to use any of its APIs directly, you
|
||||
should add it to your `pubspec.yaml` as usual.
|
||||
|
||||
[1]: https://pub.dev/packages/path_provider
|
||||
[2]: https://flutter.dev/to/endorsed-federated-plugin
|
||||
@@ -0,0 +1,9 @@
|
||||
# Platform Implementation Test App
|
||||
|
||||
This is a test app for manual testing and automated integration testing
|
||||
of this platform implementation. It is not intended to demonstrate actual use of
|
||||
this package, since the intent is that plugin clients use the app-facing
|
||||
package.
|
||||
|
||||
Unless you are making changes to this implementation package, this example is
|
||||
very unlikely to be relevant.
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path_provider_windows/path_provider_windows.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('getTemporaryDirectory', (WidgetTester tester) async {
|
||||
final PathProviderWindows provider = PathProviderWindows();
|
||||
final String? result = await provider.getTemporaryPath();
|
||||
_verifySampleFile(result, 'temporaryDirectory');
|
||||
});
|
||||
|
||||
testWidgets('getApplicationDocumentsDirectory', (WidgetTester tester) async {
|
||||
final PathProviderWindows provider = PathProviderWindows();
|
||||
final String? result = await provider.getApplicationDocumentsPath();
|
||||
_verifySampleFile(result, 'applicationDocuments');
|
||||
});
|
||||
|
||||
testWidgets('getApplicationSupportDirectory', (WidgetTester tester) async {
|
||||
final PathProviderWindows provider = PathProviderWindows();
|
||||
final String? result = await provider.getApplicationSupportPath();
|
||||
_verifySampleFile(result, 'applicationSupport');
|
||||
});
|
||||
|
||||
testWidgets('getApplicationCacheDirectory', (WidgetTester tester) async {
|
||||
final PathProviderWindows provider = PathProviderWindows();
|
||||
final String? result = await provider.getApplicationCachePath();
|
||||
_verifySampleFile(result, 'applicationCache');
|
||||
});
|
||||
|
||||
testWidgets('getDownloadsDirectory', (WidgetTester tester) async {
|
||||
final PathProviderWindows provider = PathProviderWindows();
|
||||
final String? result = await provider.getDownloadsPath();
|
||||
_verifySampleFile(result, 'downloads');
|
||||
});
|
||||
}
|
||||
|
||||
/// Verify a file called [name] in [directoryPath] by recreating it with test
|
||||
/// contents when necessary.
|
||||
void _verifySampleFile(String? directoryPath, String name) {
|
||||
expect(directoryPath, isNotNull);
|
||||
if (directoryPath == null) {
|
||||
return;
|
||||
}
|
||||
final Directory directory = Directory(directoryPath);
|
||||
final File file = File('${directory.path}${Platform.pathSeparator}$name');
|
||||
|
||||
if (file.existsSync()) {
|
||||
file.deleteSync();
|
||||
expect(file.existsSync(), isFalse);
|
||||
}
|
||||
|
||||
file.writeAsStringSync('Hello world!');
|
||||
expect(file.readAsStringSync(), 'Hello world!');
|
||||
expect(directory.listSync(), isNotEmpty);
|
||||
file.deleteSync();
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path_provider_windows/path_provider_windows.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
/// Sample app
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
String? _tempDirectory = 'Unknown';
|
||||
String? _downloadsDirectory = 'Unknown';
|
||||
String? _appSupportDirectory = 'Unknown';
|
||||
String? _documentsDirectory = 'Unknown';
|
||||
String? _cacheDirectory = 'Unknown';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initDirectories();
|
||||
}
|
||||
|
||||
// Platform messages are asynchronous, so we initialize in an async method.
|
||||
Future<void> initDirectories() async {
|
||||
String? tempDirectory;
|
||||
String? downloadsDirectory;
|
||||
String? appSupportDirectory;
|
||||
String? documentsDirectory;
|
||||
String? cacheDirectory;
|
||||
final PathProviderWindows provider = PathProviderWindows();
|
||||
|
||||
try {
|
||||
tempDirectory = await provider.getTemporaryPath();
|
||||
} catch (exception) {
|
||||
tempDirectory = 'Failed to get temp directory: $exception';
|
||||
}
|
||||
try {
|
||||
downloadsDirectory = await provider.getDownloadsPath();
|
||||
} catch (exception) {
|
||||
downloadsDirectory = 'Failed to get downloads directory: $exception';
|
||||
}
|
||||
|
||||
try {
|
||||
documentsDirectory = await provider.getApplicationDocumentsPath();
|
||||
} catch (exception) {
|
||||
documentsDirectory = 'Failed to get documents directory: $exception';
|
||||
}
|
||||
|
||||
try {
|
||||
appSupportDirectory = await provider.getApplicationSupportPath();
|
||||
} catch (exception) {
|
||||
appSupportDirectory = 'Failed to get app support directory: $exception';
|
||||
}
|
||||
|
||||
try {
|
||||
cacheDirectory = await provider.getApplicationCachePath();
|
||||
} catch (exception) {
|
||||
cacheDirectory = 'Failed to get cache directory: $exception';
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_tempDirectory = tempDirectory;
|
||||
_downloadsDirectory = downloadsDirectory;
|
||||
_appSupportDirectory = appSupportDirectory;
|
||||
_documentsDirectory = documentsDirectory;
|
||||
_cacheDirectory = cacheDirectory;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Path Provider example app'),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Text('Temp Directory: $_tempDirectory\n'),
|
||||
Text('Documents Directory: $_documentsDirectory\n'),
|
||||
Text('Downloads Directory: $_downloadsDirectory\n'),
|
||||
Text('Application Support Directory: $_appSupportDirectory\n'),
|
||||
Text('Cache Directory: $_cacheDirectory\n'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
name: path_provider_example
|
||||
description: Demonstrates how to use the path_provider plugin.
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ^3.2.0
|
||||
flutter: ">=3.16.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
path_provider_windows:
|
||||
# When depending on this package from a real application you should use:
|
||||
# path_provider_windows: ^x.y.z
|
||||
# See https://dart.dev/tools/pub/dependencies#version-constraints
|
||||
# The example app is bundled with the plugin so we use a path dependency on
|
||||
# the parent directory to use the current plugin's version.
|
||||
path: ../
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:integration_test/integration_test_driver.dart';
|
||||
|
||||
Future<void> main() => integrationDriver();
|
||||
@@ -0,0 +1,95 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(example LANGUAGES CXX)
|
||||
|
||||
set(BINARY_NAME "example")
|
||||
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||
|
||||
# Configure build options.
|
||||
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
if(IS_MULTICONFIG)
|
||||
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
|
||||
CACHE STRING "" FORCE)
|
||||
else()
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||
STRING "Flutter build mode" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Profile" "Release")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
|
||||
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
|
||||
# Use Unicode for all projects.
|
||||
add_definitions(-DUNICODE -D_UNICODE)
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_17)
|
||||
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
|
||||
target_compile_options(${TARGET} PRIVATE /EHsc)
|
||||
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
|
||||
endfunction()
|
||||
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# Application build
|
||||
add_subdirectory("runner")
|
||||
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
|
||||
|
||||
# === Installation ===
|
||||
# Support files are copied into place next to the executable, so that it can
|
||||
# run in place. This is done instead of making a separate bundle (as on Linux)
|
||||
# so that building and running from within Visual Studio will work.
|
||||
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
|
||||
# Make the "install" step default, as it's required to run.
|
||||
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
if(PLUGIN_BUNDLED_LIBRARIES)
|
||||
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||
# from a previous install.
|
||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||
" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||
|
||||
# Install the AOT library on non-Debug builds only.
|
||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
CONFIGURATIONS Profile;Release
|
||||
COMPONENT Runtime)
|
||||
@@ -0,0 +1,107 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
|
||||
|
||||
# Set fallback configurations for older versions of the flutter tool.
|
||||
if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
|
||||
set(FLUTTER_TARGET_PLATFORM "windows-x64")
|
||||
endif()
|
||||
|
||||
# === Flutter Library ===
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"flutter_export.h"
|
||||
"flutter_windows.h"
|
||||
"flutter_messenger.h"
|
||||
"flutter_plugin_registrar.h"
|
||||
)
|
||||
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Wrapper ===
|
||||
list(APPEND CPP_WRAPPER_SOURCES_CORE
|
||||
"core_implementations.cc"
|
||||
"standard_codec.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
|
||||
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
|
||||
"plugin_registrar.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
|
||||
list(APPEND CPP_WRAPPER_SOURCES_APP
|
||||
"flutter_engine.cc"
|
||||
"flutter_view_controller.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
|
||||
|
||||
# Wrapper sources needed for a plugin.
|
||||
add_library(flutter_wrapper_plugin STATIC
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
)
|
||||
apply_standard_settings(flutter_wrapper_plugin)
|
||||
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON)
|
||||
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden)
|
||||
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
|
||||
target_include_directories(flutter_wrapper_plugin PUBLIC
|
||||
"${WRAPPER_ROOT}/include"
|
||||
)
|
||||
add_dependencies(flutter_wrapper_plugin flutter_assemble)
|
||||
|
||||
# Wrapper sources needed for the runner.
|
||||
add_library(flutter_wrapper_app STATIC
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
)
|
||||
apply_standard_settings(flutter_wrapper_app)
|
||||
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
|
||||
target_include_directories(flutter_wrapper_app PUBLIC
|
||||
"${WRAPPER_ROOT}/include"
|
||||
)
|
||||
add_dependencies(flutter_wrapper_app flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
|
||||
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
${PHONY_OUTPUT}
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
|
||||
${FLUTTER_TARGET_PLATFORM} $<CONFIG>
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
||||
@@ -0,0 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
add_executable(${BINARY_NAME} WIN32
|
||||
"flutter_window.cpp"
|
||||
"main.cpp"
|
||||
"run_loop.cpp"
|
||||
"utils.cpp"
|
||||
"win32_window.cpp"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
"Runner.rc"
|
||||
"runner.exe.manifest"
|
||||
)
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
|
||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||
@@ -0,0 +1,121 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#pragma code_page(65001)
|
||||
#include "resource.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "winres.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (United States) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""winres.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Icon
|
||||
//
|
||||
|
||||
// Icon with lowest ID value placed first to ensure application icon
|
||||
// remains consistent on all systems.
|
||||
IDI_APP_ICON ICON "resources\\app_icon.ico"
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
|
||||
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
|
||||
#else
|
||||
#define VERSION_AS_NUMBER 1,0,0,0
|
||||
#endif
|
||||
|
||||
#if defined(FLUTTER_VERSION)
|
||||
#define VERSION_AS_STRING FLUTTER_VERSION
|
||||
#else
|
||||
#define VERSION_AS_STRING "1.0.0"
|
||||
#endif
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VERSION_AS_NUMBER
|
||||
PRODUCTVERSION VERSION_AS_NUMBER
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904e4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "com.example" "\0"
|
||||
VALUE "FileDescription", "A new Flutter project." "\0"
|
||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||
VALUE "InternalName", "example" "\0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2020 com.example. All rights reserved." "\0"
|
||||
VALUE "OriginalFilename", "example.exe" "\0"
|
||||
VALUE "ProductName", "example" "\0"
|
||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "flutter_window.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
FlutterWindow::FlutterWindow(RunLoop* run_loop,
|
||||
const flutter::DartProject& project)
|
||||
: run_loop_(run_loop), project_(project) {}
|
||||
|
||||
FlutterWindow::~FlutterWindow() {}
|
||||
|
||||
bool FlutterWindow::OnCreate() {
|
||||
if (!Win32Window::OnCreate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT frame = GetClientArea();
|
||||
|
||||
// The size here must match the window dimensions to avoid unnecessary surface
|
||||
// creation / destruction in the startup path.
|
||||
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
|
||||
frame.right - frame.left, frame.bottom - frame.top, project_);
|
||||
// Ensure that basic setup of the controller was successful.
|
||||
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
|
||||
return false;
|
||||
}
|
||||
RegisterPlugins(flutter_controller_->engine());
|
||||
run_loop_->RegisterFlutterInstance(flutter_controller_->engine());
|
||||
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
||||
return true;
|
||||
}
|
||||
|
||||
void FlutterWindow::OnDestroy() {
|
||||
if (flutter_controller_) {
|
||||
run_loop_->UnregisterFlutterInstance(flutter_controller_->engine());
|
||||
flutter_controller_ = nullptr;
|
||||
}
|
||||
|
||||
Win32Window::OnDestroy();
|
||||
}
|
||||
|
||||
LRESULT
|
||||
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
// Give Flutter, including plugins, an opporutunity to handle window messages.
|
||||
if (flutter_controller_) {
|
||||
std::optional<LRESULT> result =
|
||||
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
|
||||
lparam);
|
||||
if (result) {
|
||||
return *result;
|
||||
}
|
||||
}
|
||||
|
||||
switch (message) {
|
||||
case WM_FONTCHANGE:
|
||||
flutter_controller_->engine()->ReloadSystemFonts();
|
||||
break;
|
||||
}
|
||||
|
||||
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef RUNNER_FLUTTER_WINDOW_H_
|
||||
#define RUNNER_FLUTTER_WINDOW_H_
|
||||
|
||||
#include <flutter/dart_project.h>
|
||||
#include <flutter/flutter_view_controller.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "run_loop.h"
|
||||
#include "win32_window.h"
|
||||
|
||||
// A window that does nothing but host a Flutter view.
|
||||
class FlutterWindow : public Win32Window {
|
||||
public:
|
||||
// Creates a new FlutterWindow driven by the |run_loop|, hosting a
|
||||
// Flutter view running |project|.
|
||||
explicit FlutterWindow(RunLoop* run_loop,
|
||||
const flutter::DartProject& project);
|
||||
virtual ~FlutterWindow();
|
||||
|
||||
protected:
|
||||
// Win32Window:
|
||||
bool OnCreate() override;
|
||||
void OnDestroy() override;
|
||||
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept override;
|
||||
|
||||
private:
|
||||
// The run loop driving events for this window.
|
||||
RunLoop* run_loop_;
|
||||
|
||||
// The project to run.
|
||||
flutter::DartProject project_;
|
||||
|
||||
// The Flutter instance hosted by this window.
|
||||
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
|
||||
};
|
||||
|
||||
#endif // RUNNER_FLUTTER_WINDOW_H_
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <flutter/dart_project.h>
|
||||
#include <flutter/flutter_view_controller.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "flutter_window.h"
|
||||
#include "run_loop.h"
|
||||
#include "utils.h"
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
_In_ wchar_t* command_line, _In_ int show_command) {
|
||||
// Attach to console when present (e.g., 'flutter run') or create a
|
||||
// new console when running with a debugger.
|
||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
||||
CreateAndAttachConsole();
|
||||
}
|
||||
|
||||
// Initialize COM, so that it is available for use in the library and/or
|
||||
// plugins.
|
||||
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
RunLoop run_loop;
|
||||
|
||||
flutter::DartProject project(L"data");
|
||||
FlutterWindow window(&run_loop, project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Size size(1280, 720);
|
||||
if (!window.CreateAndShow(L"example", origin, size)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
window.SetQuitOnClose(true);
|
||||
|
||||
run_loop.Run();
|
||||
|
||||
::CoUninitialize();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by Runner.rc
|
||||
//
|
||||
#define IDI_APP_ICON 101
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1,70 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "run_loop.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
RunLoop::RunLoop() {}
|
||||
|
||||
RunLoop::~RunLoop() {}
|
||||
|
||||
void RunLoop::Run() {
|
||||
bool keep_running = true;
|
||||
TimePoint next_flutter_event_time = TimePoint::clock::now();
|
||||
while (keep_running) {
|
||||
std::chrono::nanoseconds wait_duration =
|
||||
std::max(std::chrono::nanoseconds(0),
|
||||
next_flutter_event_time - TimePoint::clock::now());
|
||||
::MsgWaitForMultipleObjects(
|
||||
0, nullptr, FALSE, static_cast<DWORD>(wait_duration.count() / 1000),
|
||||
QS_ALLINPUT);
|
||||
bool processed_events = false;
|
||||
MSG message;
|
||||
// All pending Windows messages must be processed; MsgWaitForMultipleObjects
|
||||
// won't return again for items left in the queue after PeekMessage.
|
||||
while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
|
||||
processed_events = true;
|
||||
if (message.message == WM_QUIT) {
|
||||
keep_running = false;
|
||||
break;
|
||||
}
|
||||
::TranslateMessage(&message);
|
||||
::DispatchMessage(&message);
|
||||
// Allow Flutter to process messages each time a Windows message is
|
||||
// processed, to prevent starvation.
|
||||
next_flutter_event_time =
|
||||
std::min(next_flutter_event_time, ProcessFlutterMessages());
|
||||
}
|
||||
// If the PeekMessage loop didn't run, process Flutter messages.
|
||||
if (!processed_events) {
|
||||
next_flutter_event_time =
|
||||
std::min(next_flutter_event_time, ProcessFlutterMessages());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RunLoop::RegisterFlutterInstance(
|
||||
flutter::FlutterEngine* flutter_instance) {
|
||||
flutter_instances_.insert(flutter_instance);
|
||||
}
|
||||
|
||||
void RunLoop::UnregisterFlutterInstance(
|
||||
flutter::FlutterEngine* flutter_instance) {
|
||||
flutter_instances_.erase(flutter_instance);
|
||||
}
|
||||
|
||||
RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
|
||||
TimePoint next_event_time = TimePoint::max();
|
||||
for (auto instance : flutter_instances_) {
|
||||
std::chrono::nanoseconds wait_duration = instance->ProcessMessages();
|
||||
if (wait_duration != std::chrono::nanoseconds::max()) {
|
||||
next_event_time =
|
||||
std::min(next_event_time, TimePoint::clock::now() + wait_duration);
|
||||
}
|
||||
}
|
||||
return next_event_time;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef RUNNER_RUN_LOOP_H_
|
||||
#define RUNNER_RUN_LOOP_H_
|
||||
|
||||
#include <flutter/flutter_engine.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
|
||||
// A runloop that will service events for Flutter instances as well
|
||||
// as native messages.
|
||||
class RunLoop {
|
||||
public:
|
||||
RunLoop();
|
||||
~RunLoop();
|
||||
|
||||
// Prevent copying
|
||||
RunLoop(RunLoop const&) = delete;
|
||||
RunLoop& operator=(RunLoop const&) = delete;
|
||||
|
||||
// Runs the run loop until the application quits.
|
||||
void Run();
|
||||
|
||||
// Registers the given Flutter instance for event servicing.
|
||||
void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
|
||||
|
||||
// Unregisters the given Flutter instance from event servicing.
|
||||
void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance);
|
||||
|
||||
private:
|
||||
using TimePoint = std::chrono::steady_clock::time_point;
|
||||
|
||||
// Processes all currently pending messages for registered Flutter instances.
|
||||
TimePoint ProcessFlutterMessages();
|
||||
|
||||
std::set<flutter::FlutterEngine*> flutter_instances_;
|
||||
};
|
||||
|
||||
#endif // RUNNER_RUN_LOOP_H_
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include <flutter_windows.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void CreateAndAttachConsole() {
|
||||
if (::AllocConsole()) {
|
||||
FILE* unused;
|
||||
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
|
||||
_dup2(_fileno(stdout), 1);
|
||||
}
|
||||
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
|
||||
_dup2(_fileno(stdout), 2);
|
||||
}
|
||||
std::ios::sync_with_stdio();
|
||||
FlutterDesktopResyncOutputStreams();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef RUNNER_UTILS_H_
|
||||
#define RUNNER_UTILS_H_
|
||||
|
||||
// Creates a console for the process, and redirects stdout and stderr to
|
||||
// it for both the runner and the Flutter library.
|
||||
void CreateAndAttachConsole();
|
||||
|
||||
#endif // RUNNER_UTILS_H_
|
||||
@@ -0,0 +1,240 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "win32_window.h"
|
||||
|
||||
#include <flutter_windows.h>
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
||||
|
||||
// The number of Win32Window objects that currently exist.
|
||||
static int g_active_window_count = 0;
|
||||
|
||||
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
|
||||
|
||||
// Scale helper to convert logical scaler values to physical using passed in
|
||||
// scale factor
|
||||
int Scale(int source, double scale_factor) {
|
||||
return static_cast<int>(source * scale_factor);
|
||||
}
|
||||
|
||||
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
|
||||
// This API is only needed for PerMonitor V1 awareness mode.
|
||||
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
|
||||
HMODULE user32_module = LoadLibraryA("User32.dll");
|
||||
if (!user32_module) {
|
||||
return;
|
||||
}
|
||||
auto enable_non_client_dpi_scaling =
|
||||
reinterpret_cast<EnableNonClientDpiScaling*>(
|
||||
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
|
||||
if (enable_non_client_dpi_scaling != nullptr) {
|
||||
enable_non_client_dpi_scaling(hwnd);
|
||||
FreeLibrary(user32_module);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Manages the Win32Window's window class registration.
|
||||
class WindowClassRegistrar {
|
||||
public:
|
||||
~WindowClassRegistrar() = default;
|
||||
|
||||
// Returns the singleton registar instance.
|
||||
static WindowClassRegistrar* GetInstance() {
|
||||
if (!instance_) {
|
||||
instance_ = new WindowClassRegistrar();
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
// Returns the name of the window class, registering the class if it hasn't
|
||||
// previously been registered.
|
||||
const wchar_t* GetWindowClass();
|
||||
|
||||
// Unregisters the window class. Should only be called if there are no
|
||||
// instances of the window.
|
||||
void UnregisterWindowClass();
|
||||
|
||||
private:
|
||||
WindowClassRegistrar() = default;
|
||||
|
||||
static WindowClassRegistrar* instance_;
|
||||
|
||||
bool class_registered_ = false;
|
||||
};
|
||||
|
||||
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
|
||||
|
||||
const wchar_t* WindowClassRegistrar::GetWindowClass() {
|
||||
if (!class_registered_) {
|
||||
WNDCLASS window_class{};
|
||||
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
window_class.lpszClassName = kWindowClassName;
|
||||
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
||||
window_class.cbClsExtra = 0;
|
||||
window_class.cbWndExtra = 0;
|
||||
window_class.hInstance = GetModuleHandle(nullptr);
|
||||
window_class.hIcon =
|
||||
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
|
||||
window_class.hbrBackground = 0;
|
||||
window_class.lpszMenuName = nullptr;
|
||||
window_class.lpfnWndProc = Win32Window::WndProc;
|
||||
RegisterClass(&window_class);
|
||||
class_registered_ = true;
|
||||
}
|
||||
return kWindowClassName;
|
||||
}
|
||||
|
||||
void WindowClassRegistrar::UnregisterWindowClass() {
|
||||
UnregisterClass(kWindowClassName, nullptr);
|
||||
class_registered_ = false;
|
||||
}
|
||||
|
||||
Win32Window::Win32Window() { ++g_active_window_count; }
|
||||
|
||||
Win32Window::~Win32Window() {
|
||||
--g_active_window_count;
|
||||
Destroy();
|
||||
}
|
||||
|
||||
bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin,
|
||||
const Size& size) {
|
||||
Destroy();
|
||||
|
||||
const wchar_t* window_class =
|
||||
WindowClassRegistrar::GetInstance()->GetWindowClass();
|
||||
|
||||
const POINT target_point = {static_cast<LONG>(origin.x),
|
||||
static_cast<LONG>(origin.y)};
|
||||
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
||||
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
||||
double scale_factor = dpi / 96.0;
|
||||
|
||||
HWND window = CreateWindow(
|
||||
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
||||
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
|
||||
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
|
||||
nullptr, nullptr, GetModuleHandle(nullptr), this);
|
||||
|
||||
if (!window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return OnCreate();
|
||||
}
|
||||
|
||||
// static
|
||||
LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
if (message == WM_NCCREATE) {
|
||||
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
|
||||
SetWindowLongPtr(window, GWLP_USERDATA,
|
||||
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
|
||||
|
||||
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
|
||||
EnableFullDpiSupportIfAvailable(window);
|
||||
that->window_handle_ = window;
|
||||
} else if (Win32Window* that = GetThisFromHandle(window)) {
|
||||
return that->MessageHandler(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
return DefWindowProc(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
LRESULT
|
||||
Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
switch (message) {
|
||||
case WM_DESTROY:
|
||||
window_handle_ = nullptr;
|
||||
Destroy();
|
||||
if (quit_on_close_) {
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_DPICHANGED: {
|
||||
auto newRectSize = reinterpret_cast<RECT*>(lparam);
|
||||
LONG newWidth = newRectSize->right - newRectSize->left;
|
||||
LONG newHeight = newRectSize->bottom - newRectSize->top;
|
||||
|
||||
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
|
||||
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
case WM_SIZE:
|
||||
RECT rect = GetClientArea();
|
||||
if (child_content_ != nullptr) {
|
||||
// Size and position the child window.
|
||||
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
|
||||
rect.bottom - rect.top, TRUE);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_ACTIVATE:
|
||||
if (child_content_ != nullptr) {
|
||||
SetFocus(child_content_);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return DefWindowProc(window_handle_, message, wparam, lparam);
|
||||
}
|
||||
|
||||
void Win32Window::Destroy() {
|
||||
OnDestroy();
|
||||
|
||||
if (window_handle_) {
|
||||
DestroyWindow(window_handle_);
|
||||
window_handle_ = nullptr;
|
||||
}
|
||||
if (g_active_window_count == 0) {
|
||||
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
|
||||
}
|
||||
}
|
||||
|
||||
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
|
||||
return reinterpret_cast<Win32Window*>(
|
||||
GetWindowLongPtr(window, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
void Win32Window::SetChildContent(HWND content) {
|
||||
child_content_ = content;
|
||||
SetParent(content, window_handle_);
|
||||
RECT frame = GetClientArea();
|
||||
|
||||
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
|
||||
frame.bottom - frame.top, true);
|
||||
|
||||
SetFocus(child_content_);
|
||||
}
|
||||
|
||||
RECT Win32Window::GetClientArea() {
|
||||
RECT frame;
|
||||
GetClientRect(window_handle_, &frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
HWND Win32Window::GetHandle() { return window_handle_; }
|
||||
|
||||
void Win32Window::SetQuitOnClose(bool quit_on_close) {
|
||||
quit_on_close_ = quit_on_close;
|
||||
}
|
||||
|
||||
bool Win32Window::OnCreate() {
|
||||
// No-op; provided for subclasses.
|
||||
return true;
|
||||
}
|
||||
|
||||
void Win32Window::OnDestroy() {
|
||||
// No-op; provided for subclasses.
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef RUNNER_WIN32_WINDOW_H_
|
||||
#define RUNNER_WIN32_WINDOW_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
|
||||
// inherited from by classes that wish to specialize with custom
|
||||
// rendering and input handling
|
||||
class Win32Window {
|
||||
public:
|
||||
struct Point {
|
||||
unsigned int x;
|
||||
unsigned int y;
|
||||
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
|
||||
};
|
||||
|
||||
struct Size {
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
Size(unsigned int width, unsigned int height)
|
||||
: width(width), height(height) {}
|
||||
};
|
||||
|
||||
Win32Window();
|
||||
virtual ~Win32Window();
|
||||
|
||||
// Creates and shows a win32 window with |title| and position and size using
|
||||
// |origin| and |size|. New windows are created on the default monitor. Window
|
||||
// sizes are specified to the OS in physical pixels, hence to ensure a
|
||||
// consistent size to will treat the width height passed in to this function
|
||||
// as logical pixels and scale to appropriate for the default monitor. Returns
|
||||
// true if the window was created successfully.
|
||||
bool CreateAndShow(const std::wstring& title, const Point& origin,
|
||||
const Size& size);
|
||||
|
||||
// Release OS resources associated with window.
|
||||
void Destroy();
|
||||
|
||||
// Inserts |content| into the window tree.
|
||||
void SetChildContent(HWND content);
|
||||
|
||||
// Returns the backing Window handle to enable clients to set icon and other
|
||||
// window properties. Returns nullptr if the window has been destroyed.
|
||||
HWND GetHandle();
|
||||
|
||||
// If true, closing this window will quit the application.
|
||||
void SetQuitOnClose(bool quit_on_close);
|
||||
|
||||
// Return a RECT representing the bounds of the current client area.
|
||||
RECT GetClientArea();
|
||||
|
||||
protected:
|
||||
// Processes and route salient window messages for mouse handling,
|
||||
// size change and DPI. Delegates handling of these to member overloads that
|
||||
// inheriting classes can handle.
|
||||
virtual LRESULT MessageHandler(HWND window, UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept;
|
||||
|
||||
// Called when CreateAndShow is called, allowing subclass window-related
|
||||
// setup. Subclasses should return false if setup fails.
|
||||
virtual bool OnCreate();
|
||||
|
||||
// Called when Destroy is called.
|
||||
virtual void OnDestroy();
|
||||
|
||||
private:
|
||||
friend class WindowClassRegistrar;
|
||||
|
||||
// OS callback called by message pump. Handles the WM_NCCREATE message which
|
||||
// is passed when the non-client area is being created and enables automatic
|
||||
// non-client DPI scaling so that the non-client area automatically
|
||||
// responsponds to changes in DPI. All other messages are handled by
|
||||
// MessageHandler.
|
||||
static LRESULT CALLBACK WndProc(HWND const window, UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept;
|
||||
|
||||
// Retrieves a class instance pointer for |window|
|
||||
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
|
||||
|
||||
bool quit_on_close_ = false;
|
||||
|
||||
// window handle for top level window.
|
||||
HWND window_handle_ = nullptr;
|
||||
|
||||
// window handle for hosted content.
|
||||
HWND child_content_ = nullptr;
|
||||
};
|
||||
|
||||
#endif // RUNNER_WIN32_WINDOW_H_
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// path_provider_windows is implemented using FFI; export a stub for platforms
|
||||
// that don't support FFI (e.g., web) to avoid having transitive dependencies
|
||||
// break web compilation.
|
||||
export 'src/folders_stub.dart' if (dart.library.ffi) 'src/folders.dart';
|
||||
export 'src/path_provider_windows_stub.dart'
|
||||
if (dart.library.ffi) 'src/path_provider_windows_real.dart';
|
||||
@@ -0,0 +1,250 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
|
||||
// ignore: avoid_classes_with_only_static_members
|
||||
/// A class containing the GUID references for each of the documented Windows
|
||||
/// known folders. A property of this class may be passed to the `getPath`
|
||||
/// method in the [PathProvidersWindows] class to retrieve a known folder from
|
||||
/// Windows.
|
||||
// These constants come from
|
||||
// https://learn.microsoft.com/windows/win32/shell/knownfolderid
|
||||
class WindowsKnownFolder {
|
||||
/// The file system directory that is used to store administrative tools for
|
||||
/// an individual user. The MMC will save customized consoles to this
|
||||
/// directory, and it will roam with the user.
|
||||
static String get AdminTools => '{724EF170-A42D-4FEF-9F26-B60E846FBA4F}';
|
||||
|
||||
/// The file system directory that acts as a staging area for files waiting to
|
||||
/// be written to a CD. A typical path is C:\Documents and
|
||||
/// Settings\username\Local Settings\Application Data\Microsoft\CD Burning.
|
||||
static String get CDBurning => '{9E52AB10-F80D-49DF-ACB8-4330F5687855}';
|
||||
|
||||
/// The file system directory that contains administrative tools for all users
|
||||
/// of the computer.
|
||||
static String get CommonAdminTools =>
|
||||
'{D0384E7D-BAC3-4797-8F14-CBA229B392B5}';
|
||||
|
||||
/// The file system directory that contains the directories for the common
|
||||
/// program groups that appear on the Start menu for all users. A typical path
|
||||
/// is C:\Documents and Settings\All Users\Start Menu\Programs.
|
||||
static String get CommonPrograms => '{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}';
|
||||
|
||||
/// The file system directory that contains the programs and folders that
|
||||
/// appear on the Start menu for all users. A typical path is C:\Documents and
|
||||
/// Settings\All Users\Start Menu.
|
||||
static String get CommonStartMenu => '{A4115719-D62E-491D-AA7C-E74B8BE3B067}';
|
||||
|
||||
/// The file system directory that contains the programs that appear in the
|
||||
/// Startup folder for all users. A typical path is C:\Documents and
|
||||
/// Settings\All Users\Start Menu\Programs\Startup.
|
||||
static String get CommonStartup => '{82A5EA35-D9CD-47C5-9629-E15D2F714E6E}';
|
||||
|
||||
/// The file system directory that contains the templates that are available
|
||||
/// to all users. A typical path is C:\Documents and Settings\All
|
||||
/// Users\Templates.
|
||||
static String get CommonTemplates => '{B94237E7-57AC-4347-9151-B08C6C32D1F7}';
|
||||
|
||||
/// The virtual folder that represents My Computer, containing everything on
|
||||
/// the local computer: storage devices, printers, and Control Panel. The
|
||||
/// folder can also contain mapped network drives.
|
||||
static String get ComputerFolder => '{0AC0837C-BBF8-452A-850D-79D08E667CA7}';
|
||||
|
||||
/// The virtual folder that represents Network Connections, that contains
|
||||
/// network and dial-up connections.
|
||||
static String get ConnectionsFolder =>
|
||||
'{6F0CD92B-2E97-45D1-88FF-B0D186B8DEDD}';
|
||||
|
||||
/// The virtual folder that contains icons for the Control Panel applications.
|
||||
static String get ControlPanelFolder =>
|
||||
'{82A74AEB-AEB4-465C-A014-D097EE346D63}';
|
||||
|
||||
/// The file system directory that serves as a common repository for Internet
|
||||
/// cookies. A typical path is C:\Documents and Settings\username\Cookies.
|
||||
static String get Cookies => '{2B0F765D-C0E9-4171-908E-08A611B84FF6}';
|
||||
|
||||
/// The virtual folder that represents the Windows desktop, the root of the
|
||||
/// namespace.
|
||||
static String get Desktop => '{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}';
|
||||
|
||||
/// The virtual folder that represents the My Documents desktop item.
|
||||
static String get Documents => '{FDD39AD0-238F-46AF-ADB4-6C85480369C7}';
|
||||
|
||||
/// The file system directory that serves as a repository for Internet
|
||||
/// downloads.
|
||||
static String get Downloads => '{374DE290-123F-4565-9164-39C4925E467B}';
|
||||
|
||||
/// The file system directory that serves as a common repository for the
|
||||
/// user's favorite items. A typical path is C:\Documents and
|
||||
/// Settings\username\Favorites.
|
||||
static String get Favorites => '{1777F761-68AD-4D8A-87BD-30B759FA33DD}';
|
||||
|
||||
/// A virtual folder that contains fonts. A typical path is C:\Windows\Fonts.
|
||||
static String get Fonts => '{FD228CB7-AE11-4AE3-864C-16F3910AB8FE}';
|
||||
|
||||
/// The file system directory that serves as a common repository for Internet
|
||||
/// history items.
|
||||
static String get History => '{D9DC8A3B-B784-432E-A781-5A1130A75963}';
|
||||
|
||||
/// The file system directory that serves as a common repository for temporary
|
||||
/// Internet files. A typical path is C:\Documents and Settings\username\Local
|
||||
/// Settings\Temporary Internet Files.
|
||||
static String get InternetCache => '{352481E8-33BE-4251-BA85-6007CAEDCF9D}';
|
||||
|
||||
/// A virtual folder for Internet Explorer.
|
||||
static String get InternetFolder => '{4D9F7874-4E0C-4904-967B-40B0D20C3E4B}';
|
||||
|
||||
/// The file system directory that serves as a data repository for local
|
||||
/// (nonroaming) applications. A typical path is C:\Documents and
|
||||
/// Settings\username\Local Settings\Application Data.
|
||||
static String get LocalAppData => '{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}';
|
||||
|
||||
/// The file system directory that serves as a common repository for music
|
||||
/// files. A typical path is C:\Documents and Settings\User\My Documents\My
|
||||
/// Music.
|
||||
static String get Music => '{4BD8D571-6D19-48D3-BE97-422220080E43}';
|
||||
|
||||
/// A file system directory that contains the link objects that may exist in
|
||||
/// the My Network Places virtual folder. A typical path is C:\Documents and
|
||||
/// Settings\username\NetHood.
|
||||
static String get NetHood => '{C5ABBF53-E17F-4121-8900-86626FC2C973}';
|
||||
|
||||
/// The folder that represents other computers in your workgroup.
|
||||
static String get NetworkFolder => '{D20BEEC4-5CA8-4905-AE3B-BF251EA09B53}';
|
||||
|
||||
/// The file system directory that serves as a common repository for image
|
||||
/// files. A typical path is C:\Documents and Settings\username\My
|
||||
/// Documents\My Pictures.
|
||||
static String get Pictures => '{33E28130-4E1E-4676-835A-98395C3BC3BB}';
|
||||
|
||||
/// The file system directory that contains the link objects that can exist in
|
||||
/// the Printers virtual folder. A typical path is C:\Documents and
|
||||
/// Settings\username\PrintHood.
|
||||
static String get PrintHood => '{9274BD8D-CFD1-41C3-B35E-B13F55A758F4}';
|
||||
|
||||
/// The virtual folder that contains installed printers.
|
||||
static String get PrintersFolder => '{76FC4E2D-D6AD-4519-A663-37BD56068185}';
|
||||
|
||||
/// The user's profile folder. A typical path is C:\Users\username.
|
||||
/// Applications should not create files or folders at this level.
|
||||
static String get Profile => '{5E6C858F-0E22-4760-9AFE-EA3317B67173}';
|
||||
|
||||
/// The file system directory that contains application data for all users. A
|
||||
/// typical path is C:\Documents and Settings\All Users\Application Data. This
|
||||
/// folder is used for application data that is not user specific. For
|
||||
/// example, an application can store a spell-check dictionary, a database of
|
||||
/// clip art, or a log file in the CSIDL_COMMON_APPDATA folder. This
|
||||
/// information will not roam and is available to anyone using the computer.
|
||||
static String get ProgramData => '{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}';
|
||||
|
||||
/// The Program Files folder. A typical path is C:\Program Files.
|
||||
static String get ProgramFiles => '{905e63b6-c1bf-494e-b29c-65b732d3d21a}';
|
||||
|
||||
/// The common Program Files folder. A typical path is C:\Program
|
||||
/// Files\Common.
|
||||
static String get ProgramFilesCommon =>
|
||||
'{F7F1ED05-9F6D-47A2-AAAE-29D317C6F066}';
|
||||
|
||||
/// On 64-bit systems, a link to the common Program Files folder. A typical path is
|
||||
/// C:\Program Files\Common Files.
|
||||
static String get ProgramFilesCommonX64 =>
|
||||
'{6365D5A7-0F0D-45e5-87F6-0DA56B6A4F7D}';
|
||||
|
||||
/// On 64-bit systems, a link to the 32-bit common Program Files folder. A
|
||||
/// typical path is C:\Program Files (x86)\Common Files. On 32-bit systems, a
|
||||
/// link to the Common Program Files folder.
|
||||
static String get ProgramFilesCommonX86 =>
|
||||
'{DE974D24-D9C6-4D3E-BF91-F4455120B917}';
|
||||
|
||||
/// On 64-bit systems, a link to the Program Files folder. A typical path is
|
||||
/// C:\Program Files.
|
||||
static String get ProgramFilesX64 => '{6D809377-6AF0-444b-8957-A3773F02200E}';
|
||||
|
||||
/// On 64-bit systems, a link to the 32-bit Program Files folder. A typical
|
||||
/// path is C:\Program Files (x86). On 32-bit systems, a link to the Common
|
||||
/// Program Files folder.
|
||||
static String get ProgramFilesX86 => '{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}';
|
||||
|
||||
/// The file system directory that contains the user's program groups (which
|
||||
/// are themselves file system directories).
|
||||
static String get Programs => '{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}';
|
||||
|
||||
/// The file system directory that contains files and folders that appear on
|
||||
/// the desktop for all users. A typical path is C:\Documents and Settings\All
|
||||
/// Users\Desktop.
|
||||
static String get PublicDesktop => '{C4AA340D-F20F-4863-AFEF-F87EF2E6BA25}';
|
||||
|
||||
/// The file system directory that contains documents that are common to all
|
||||
/// users. A typical path is C:\Documents and Settings\All Users\Documents.
|
||||
static String get PublicDocuments => '{ED4824AF-DCE4-45A8-81E2-FC7965083634}';
|
||||
|
||||
/// The file system directory that serves as a repository for music files
|
||||
/// common to all users. A typical path is C:\Documents and Settings\All
|
||||
/// Users\Documents\My Music.
|
||||
static String get PublicMusic => '{3214FAB5-9757-4298-BB61-92A9DEAA44FF}';
|
||||
|
||||
/// The file system directory that serves as a repository for image files
|
||||
/// common to all users. A typical path is C:\Documents and Settings\All
|
||||
/// Users\Documents\My Pictures.
|
||||
static String get PublicPictures => '{B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5}';
|
||||
|
||||
/// The file system directory that serves as a repository for video files
|
||||
/// common to all users. A typical path is C:\Documents and Settings\All
|
||||
/// Users\Documents\My Videos.
|
||||
static String get PublicVideos => '{2400183A-6185-49FB-A2D8-4A392A602BA3}';
|
||||
|
||||
/// The file system directory that contains shortcuts to the user's most
|
||||
/// recently used documents. A typical path is C:\Documents and
|
||||
/// Settings\username\My Recent Documents.
|
||||
static String get Recent => '{AE50C081-EBD2-438A-8655-8A092E34987A}';
|
||||
|
||||
/// The virtual folder that contains the objects in the user's Recycle Bin.
|
||||
static String get RecycleBinFolder =>
|
||||
'{B7534046-3ECB-4C18-BE4E-64CD4CB7D6AC}';
|
||||
|
||||
/// The file system directory that contains resource data. A typical path is
|
||||
/// C:\Windows\Resources.
|
||||
static String get ResourceDir => '{8AD10C31-2ADB-4296-A8F7-E4701232C972}';
|
||||
|
||||
/// The file system directory that serves as a common repository for
|
||||
/// application-specific data. A typical path is C:\Documents and
|
||||
/// Settings\username\Application Data.
|
||||
static String get RoamingAppData => '{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}';
|
||||
|
||||
/// The file system directory that contains Send To menu items. A typical path
|
||||
/// is C:\Documents and Settings\username\SendTo.
|
||||
static String get SendTo => '{8983036C-27C0-404B-8F08-102D10DCFD74}';
|
||||
|
||||
/// The file system directory that contains Start menu items. A typical path
|
||||
/// is C:\Documents and Settings\username\Start Menu.
|
||||
static String get StartMenu => '{625B53C3-AB48-4EC1-BA1F-A1EF4146FC19}';
|
||||
|
||||
/// The file system directory that corresponds to the user's Startup program
|
||||
/// group. The system starts these programs whenever the associated user logs
|
||||
/// on. A typical path is C:\Documents and Settings\username\Start
|
||||
/// Menu\Programs\Startup.
|
||||
static String get Startup => '{B97D20BB-F46A-4C97-BA10-5E3608430854}';
|
||||
|
||||
/// The Windows System folder. A typical path is C:\Windows\System32.
|
||||
static String get System => '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}';
|
||||
|
||||
/// The 32-bit Windows System folder. On 32-bit systems, this is typically
|
||||
/// C:\Windows\system32. On 64-bit systems, this is typically
|
||||
/// C:\Windows\syswow64.
|
||||
static String get SystemX86 => '{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}';
|
||||
|
||||
/// The file system directory that serves as a common repository for document
|
||||
/// templates. A typical path is C:\Documents and Settings\username\Templates.
|
||||
static String get Templates => '{A63293E8-664E-48DB-A079-DF759E0509F7}';
|
||||
|
||||
/// The file system directory that serves as a common repository for video
|
||||
/// files. A typical path is C:\Documents and Settings\username\My
|
||||
/// Documents\My Videos.
|
||||
static String get Videos => '{18989B1D-99B5-455B-841C-AB7C74E4DDFC}';
|
||||
|
||||
/// The Windows directory or SYSROOT. This corresponds to the %windir% or
|
||||
/// %SYSTEMROOT% environment variables. A typical path is C:\Windows.
|
||||
static String get Windows => '{F38BF404-1D43-42F2-9305-67DE0B28FC23}';
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
/// Stub version of the actual class.
|
||||
class WindowsKnownFolder {}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ffi';
|
||||
import 'dart:typed_data';
|
||||
|
||||
/// Representation of the Win32 GUID struct.
|
||||
// For the layout of this struct, see
|
||||
// https://learn.microsoft.com/windows/win32/api/guiddef/ns-guiddef-guid
|
||||
@Packed(4)
|
||||
base class GUID extends Struct {
|
||||
/// Native Data1 field.
|
||||
@Uint32()
|
||||
external int data1;
|
||||
|
||||
/// Native Data2 field.
|
||||
@Uint16()
|
||||
external int data2;
|
||||
|
||||
/// Native Data3 field.
|
||||
@Uint16()
|
||||
external int data3;
|
||||
|
||||
/// Native Data4 field.
|
||||
// This should be an eight-element byte array, but there's no such annotation.
|
||||
@Uint64()
|
||||
external int data4;
|
||||
|
||||
/// Parses a GUID string, with optional enclosing "{}"s and optional "-"s,
|
||||
/// into data.
|
||||
void parse(String guid) {
|
||||
final String hexOnly = guid.replaceAll(RegExp(r'[{}-]'), '');
|
||||
if (hexOnly.length != 32) {
|
||||
throw ArgumentError.value(guid, 'guid', 'Invalid GUID string');
|
||||
}
|
||||
final ByteData bytes = ByteData(16);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
bytes.setUint8(
|
||||
i, int.parse(hexOnly.substring(i * 2, i * 2 + 2), radix: 16));
|
||||
}
|
||||
data1 = bytes.getInt32(0);
|
||||
data2 = bytes.getInt16(4);
|
||||
data3 = bytes.getInt16(6);
|
||||
// [bytes] is big endian, but the host is little endian, so a default
|
||||
// big-endian read would reverse the bytes. Since data4 is supposed to be
|
||||
// a byte array, the order should be preserved, so do a little-endian read.
|
||||
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
|
||||
data4 = bytes.getInt64(8, Endian.little);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter/foundation.dart' show visibleForTesting;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
|
||||
|
||||
import 'folders.dart';
|
||||
import 'guid.dart';
|
||||
import 'win32_wrappers.dart';
|
||||
|
||||
/// Constant for en-US language used in VersionInfo keys.
|
||||
@visibleForTesting
|
||||
const String languageEn = '0409';
|
||||
|
||||
/// Constant for CP1252 encoding used in VersionInfo keys
|
||||
@visibleForTesting
|
||||
const String encodingCP1252 = '04e4';
|
||||
|
||||
/// Constant for Unicode encoding used in VersionInfo keys
|
||||
@visibleForTesting
|
||||
const String encodingUnicode = '04b0';
|
||||
|
||||
/// Wraps the Win32 VerQueryValue API call.
|
||||
///
|
||||
/// This class exists to allow injecting alternate metadata in tests without
|
||||
/// building multiple custom test binaries.
|
||||
@visibleForTesting
|
||||
class VersionInfoQuerier {
|
||||
/// Returns the value for [key] in [versionInfo]s in section with given
|
||||
/// language and encoding, or null if there is no such entry,
|
||||
/// or if versionInfo is null.
|
||||
///
|
||||
/// See https://docs.microsoft.com/windows/win32/menurc/versioninfo-resource
|
||||
/// for list of possible language and encoding values.
|
||||
String? getStringValue(
|
||||
Pointer<Uint8>? versionInfo,
|
||||
String key, {
|
||||
required String language,
|
||||
required String encoding,
|
||||
}) {
|
||||
assert(language.isNotEmpty);
|
||||
assert(encoding.isNotEmpty);
|
||||
if (versionInfo == null) {
|
||||
return null;
|
||||
}
|
||||
final Pointer<Utf16> keyPath =
|
||||
'\\StringFileInfo\\$language$encoding\\$key'.toNativeUtf16();
|
||||
final Pointer<UINT> length = calloc<UINT>();
|
||||
final Pointer<Pointer<Utf16>> valueAddress = calloc<Pointer<Utf16>>();
|
||||
try {
|
||||
if (VerQueryValue(versionInfo, keyPath, valueAddress, length) == 0) {
|
||||
return null;
|
||||
}
|
||||
return valueAddress.value.toDartString();
|
||||
} finally {
|
||||
calloc.free(keyPath);
|
||||
calloc.free(length);
|
||||
calloc.free(valueAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Windows implementation of [PathProviderPlatform]
|
||||
///
|
||||
/// This class implements the `package:path_provider` functionality for Windows.
|
||||
class PathProviderWindows extends PathProviderPlatform {
|
||||
/// Registers the Windows implementation.
|
||||
static void registerWith() {
|
||||
PathProviderPlatform.instance = PathProviderWindows();
|
||||
}
|
||||
|
||||
/// The object to use for performing VerQueryValue calls.
|
||||
@visibleForTesting
|
||||
VersionInfoQuerier versionInfoQuerier = VersionInfoQuerier();
|
||||
|
||||
/// This is typically the same as the TMP environment variable.
|
||||
@override
|
||||
Future<String?> getTemporaryPath() async {
|
||||
final Pointer<Utf16> buffer = calloc<Uint16>(MAX_PATH + 1).cast<Utf16>();
|
||||
String path;
|
||||
|
||||
try {
|
||||
final int length = GetTempPath(MAX_PATH, buffer);
|
||||
|
||||
if (length == 0) {
|
||||
final int error = GetLastError();
|
||||
throw _createWin32Exception(error);
|
||||
} else {
|
||||
path = buffer.toDartString();
|
||||
|
||||
// GetTempPath adds a trailing backslash, but SHGetKnownFolderPath does
|
||||
// not. Strip off trailing backslash for consistency with other methods
|
||||
// here.
|
||||
if (path.endsWith(r'\')) {
|
||||
path = path.substring(0, path.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the directory exists, since GetTempPath doesn't.
|
||||
final Directory directory = Directory(path);
|
||||
if (!directory.existsSync()) {
|
||||
await directory.create(recursive: true);
|
||||
}
|
||||
|
||||
return path;
|
||||
} finally {
|
||||
calloc.free(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getApplicationSupportPath() =>
|
||||
_createApplicationSubdirectory(WindowsKnownFolder.RoamingAppData);
|
||||
|
||||
@override
|
||||
Future<String?> getApplicationDocumentsPath() =>
|
||||
getPath(WindowsKnownFolder.Documents);
|
||||
|
||||
@override
|
||||
Future<String?> getApplicationCachePath() =>
|
||||
_createApplicationSubdirectory(WindowsKnownFolder.LocalAppData);
|
||||
|
||||
@override
|
||||
Future<String?> getDownloadsPath() => getPath(WindowsKnownFolder.Downloads);
|
||||
|
||||
/// Retrieve any known folder from Windows.
|
||||
///
|
||||
/// folderID is a GUID that represents a specific known folder ID, drawn from
|
||||
/// [WindowsKnownFolder].
|
||||
Future<String?> getPath(String folderID) {
|
||||
final Pointer<Pointer<Utf16>> pathPtrPtr = calloc<Pointer<Utf16>>();
|
||||
final Pointer<GUID> knownFolderID = calloc<GUID>()..ref.parse(folderID);
|
||||
|
||||
try {
|
||||
final int hr = SHGetKnownFolderPath(
|
||||
knownFolderID,
|
||||
KF_FLAG_DEFAULT,
|
||||
NULL,
|
||||
pathPtrPtr,
|
||||
);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
if (hr == E_INVALIDARG || hr == E_FAIL) {
|
||||
throw _createWin32Exception(hr);
|
||||
}
|
||||
return Future<String?>.value();
|
||||
}
|
||||
|
||||
final String path = pathPtrPtr.value.toDartString();
|
||||
return Future<String>.value(path);
|
||||
} finally {
|
||||
calloc.free(pathPtrPtr);
|
||||
calloc.free(knownFolderID);
|
||||
}
|
||||
}
|
||||
|
||||
String? _getStringValue(Pointer<Uint8>? infoBuffer, String key) =>
|
||||
versionInfoQuerier.getStringValue(infoBuffer, key,
|
||||
language: languageEn, encoding: encodingCP1252) ??
|
||||
versionInfoQuerier.getStringValue(infoBuffer, key,
|
||||
language: languageEn, encoding: encodingUnicode);
|
||||
|
||||
/// Returns the relative path string to append to the root directory returned
|
||||
/// by Win32 APIs for application storage (such as RoamingAppDir) to get a
|
||||
/// directory that is unique to the application.
|
||||
///
|
||||
/// The convention is to use company-name\product-name\. This will use that if
|
||||
/// possible, using the data in the VERSIONINFO resource, with the following
|
||||
/// fallbacks:
|
||||
/// - If the company name isn't there, that component will be dropped.
|
||||
/// - If the product name isn't there, it will use the exe's filename (without
|
||||
/// extension).
|
||||
String _getApplicationSpecificSubdirectory() {
|
||||
String? companyName;
|
||||
String? productName;
|
||||
|
||||
final Pointer<Utf16> moduleNameBuffer =
|
||||
calloc<WCHAR>(MAX_PATH + 1).cast<Utf16>();
|
||||
final Pointer<DWORD> unused = calloc<DWORD>();
|
||||
Pointer<BYTE>? infoBuffer;
|
||||
try {
|
||||
// Get the module name.
|
||||
final int moduleNameLength =
|
||||
GetModuleFileName(0, moduleNameBuffer, MAX_PATH);
|
||||
if (moduleNameLength == 0) {
|
||||
final int error = GetLastError();
|
||||
throw _createWin32Exception(error);
|
||||
}
|
||||
|
||||
// From that, load the VERSIONINFO resource
|
||||
final int infoSize = GetFileVersionInfoSize(moduleNameBuffer, unused);
|
||||
if (infoSize != 0) {
|
||||
infoBuffer = calloc<BYTE>(infoSize);
|
||||
if (GetFileVersionInfo(moduleNameBuffer, 0, infoSize, infoBuffer) ==
|
||||
0) {
|
||||
calloc.free(infoBuffer);
|
||||
infoBuffer = null;
|
||||
}
|
||||
}
|
||||
companyName =
|
||||
_sanitizedDirectoryName(_getStringValue(infoBuffer, 'CompanyName'));
|
||||
productName =
|
||||
_sanitizedDirectoryName(_getStringValue(infoBuffer, 'ProductName'));
|
||||
|
||||
// If there was no product name, use the executable name.
|
||||
productName ??=
|
||||
path.basenameWithoutExtension(moduleNameBuffer.toDartString());
|
||||
|
||||
return companyName != null
|
||||
? path.join(companyName, productName)
|
||||
: productName;
|
||||
} finally {
|
||||
calloc.free(moduleNameBuffer);
|
||||
calloc.free(unused);
|
||||
if (infoBuffer != null) {
|
||||
calloc.free(infoBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes [rawString] safe as a directory component. See
|
||||
/// https://docs.microsoft.com/windows/win32/fileio/naming-a-file#naming-conventions
|
||||
///
|
||||
/// If after sanitizing the string is empty, returns null.
|
||||
String? _sanitizedDirectoryName(String? rawString) {
|
||||
if (rawString == null) {
|
||||
return null;
|
||||
}
|
||||
String sanitized = rawString
|
||||
// Replace banned characters.
|
||||
.replaceAll(RegExp(r'[<>:"/\\|?*]'), '_')
|
||||
// Remove trailing whitespace.
|
||||
.trimRight()
|
||||
// Ensure that it does not end with a '.'.
|
||||
.replaceAll(RegExp(r'[.]+$'), '');
|
||||
const int kMaxComponentLength = 255;
|
||||
if (sanitized.length > kMaxComponentLength) {
|
||||
sanitized = sanitized.substring(0, kMaxComponentLength);
|
||||
}
|
||||
return sanitized.isEmpty ? null : sanitized;
|
||||
}
|
||||
|
||||
Future<String?> _createApplicationSubdirectory(String folderId) async {
|
||||
final String? baseDir = await getPath(folderId);
|
||||
if (baseDir == null) {
|
||||
return null;
|
||||
}
|
||||
final Directory directory =
|
||||
Directory(path.join(baseDir, _getApplicationSpecificSubdirectory()));
|
||||
// Ensure that the directory exists if possible, since it will on other
|
||||
// platforms. If the name is longer than MAXPATH, creating will fail, so
|
||||
// skip that step; it's up to the client to decide what to do with the path
|
||||
// in that case (e.g., using a short path).
|
||||
if (directory.path.length <= MAX_PATH) {
|
||||
if (!directory.existsSync()) {
|
||||
await directory.create(recursive: true);
|
||||
}
|
||||
}
|
||||
return directory.path;
|
||||
}
|
||||
}
|
||||
|
||||
Exception _createWin32Exception(int errorCode) {
|
||||
return PlatformException(
|
||||
code: 'Win32 Error',
|
||||
// TODO(stuartmorgan): Consider getting the system error message via
|
||||
// FormatMessage if it turns out to be necessary for debugging issues.
|
||||
// Plugin-client-level usability isn't a major consideration since per
|
||||
// https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#platform-exception-handling
|
||||
// any case that comes up in practice should be handled and returned
|
||||
// via a plugin-specific exception, not this fallback.
|
||||
message: 'Error code 0x${errorCode.toRadixString(16)}');
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
|
||||
|
||||
/// A stub implementation to satisfy compilation of multi-platform packages that
|
||||
/// depend on path_provider_windows. This should never actually be created.
|
||||
///
|
||||
/// Notably, because path_provider needs to manually register
|
||||
/// path_provider_windows, anything with a transitive dependency on
|
||||
/// path_provider will also depend on path_provider_windows, not just at the
|
||||
/// pubspec level but the code level.
|
||||
class PathProviderWindows extends PathProviderPlatform {
|
||||
/// Errors on attempted instantiation of the stub. It exists only to satisfy
|
||||
/// compile-time dependencies, and should never actually be created.
|
||||
PathProviderWindows() : assert(false);
|
||||
|
||||
/// Registers the Windows implementation.
|
||||
static void registerWith() {
|
||||
PathProviderPlatform.instance = PathProviderWindows();
|
||||
}
|
||||
|
||||
/// Stub; see comment on VersionInfoQuerier.
|
||||
VersionInfoQuerier versionInfoQuerier = VersionInfoQuerier();
|
||||
|
||||
/// Match PathProviderWindows so that the analyzer won't report invalid
|
||||
/// overrides if tests provide fake PathProviderWindows implementations.
|
||||
Future<String> getPath(String folderID) async => '';
|
||||
}
|
||||
|
||||
/// Stub to satisfy the analyzer, which doesn't seem to handle conditional
|
||||
/// exports correctly.
|
||||
class VersionInfoQuerier {}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// The types and functions here correspond directly to corresponding Windows
|
||||
// types and functions, so the Windows docs are the definitive source of
|
||||
// documentation.
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import 'guid.dart';
|
||||
|
||||
typedef BOOL = Int32;
|
||||
typedef BYTE = Uint8;
|
||||
typedef DWORD = Uint32;
|
||||
typedef UINT = Uint32;
|
||||
typedef HANDLE = IntPtr;
|
||||
typedef HMODULE = HANDLE;
|
||||
typedef HRESULT = Int32;
|
||||
typedef LPCVOID = Pointer<NativeType>;
|
||||
typedef LPCWSTR = Pointer<Utf16>;
|
||||
typedef LPDWORD = Pointer<DWORD>;
|
||||
typedef LPWSTR = Pointer<Utf16>;
|
||||
typedef LPVOID = Pointer<NativeType>;
|
||||
typedef PUINT = Pointer<UINT>;
|
||||
typedef PWSTR = Pointer<Pointer<Utf16>>;
|
||||
typedef WCHAR = Uint16;
|
||||
|
||||
const int NULL = 0;
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
|
||||
const int MAX_PATH = 260;
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/seccrypto/common-hresult-values
|
||||
// ignore: non_constant_identifier_names
|
||||
final int E_FAIL = 0x80004005.toSigned(32);
|
||||
// ignore: non_constant_identifier_names
|
||||
final int E_INVALIDARG = 0x80070057.toSigned(32);
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/api/winerror/nf-winerror-failed#remarks
|
||||
// ignore: non_constant_identifier_names
|
||||
bool FAILED(int hr) => hr < 0;
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/api/shlobj_core/ne-shlobj_core-known_folder_flag
|
||||
const int KF_FLAG_DEFAULT = 0x00000000;
|
||||
|
||||
final DynamicLibrary _dllKernel32 = DynamicLibrary.open('kernel32.dll');
|
||||
final DynamicLibrary _dllVersion = DynamicLibrary.open('version.dll');
|
||||
final DynamicLibrary _dllShell32 = DynamicLibrary.open('shell32.dll');
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
|
||||
typedef _FFITypeSHGetKnownFolderPath = HRESULT Function(
|
||||
Pointer<GUID>, DWORD, HANDLE, PWSTR);
|
||||
typedef FFITypeSHGetKnownFolderPathDart = int Function(
|
||||
Pointer<GUID>, int, int, Pointer<Pointer<Utf16>>);
|
||||
// ignore: non_constant_identifier_names
|
||||
final FFITypeSHGetKnownFolderPathDart SHGetKnownFolderPath =
|
||||
_dllShell32.lookupFunction<_FFITypeSHGetKnownFolderPath,
|
||||
FFITypeSHGetKnownFolderPathDart>('SHGetKnownFolderPath');
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/api/winver/nf-winver-getfileversioninfow
|
||||
typedef _FFITypeGetFileVersionInfoW = BOOL Function(
|
||||
LPCWSTR, DWORD, DWORD, LPVOID);
|
||||
typedef FFITypeGetFileVersionInfoW = int Function(
|
||||
Pointer<Utf16>, int, int, Pointer<NativeType>);
|
||||
// ignore: non_constant_identifier_names
|
||||
final FFITypeGetFileVersionInfoW GetFileVersionInfo = _dllVersion
|
||||
.lookupFunction<_FFITypeGetFileVersionInfoW, FFITypeGetFileVersionInfoW>(
|
||||
'GetFileVersionInfoW');
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/api/winver/nf-winver-getfileversioninfosizew
|
||||
typedef _FFITypeGetFileVersionInfoSizeW = DWORD Function(LPCWSTR, LPDWORD);
|
||||
typedef FFITypeGetFileVersionInfoSizeW = int Function(
|
||||
Pointer<Utf16>, Pointer<Uint32>);
|
||||
// ignore: non_constant_identifier_names
|
||||
final FFITypeGetFileVersionInfoSizeW GetFileVersionInfoSize =
|
||||
_dllVersion.lookupFunction<_FFITypeGetFileVersionInfoSizeW,
|
||||
FFITypeGetFileVersionInfoSizeW>('GetFileVersionInfoSizeW');
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
|
||||
typedef _FFITypeGetLastError = DWORD Function();
|
||||
typedef FFITypeGetLastError = int Function();
|
||||
// ignore: non_constant_identifier_names
|
||||
final FFITypeGetLastError GetLastError = _dllKernel32
|
||||
.lookupFunction<_FFITypeGetLastError, FFITypeGetLastError>('GetLastError');
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew
|
||||
typedef _FFITypeGetModuleFileNameW = DWORD Function(HMODULE, LPWSTR, DWORD);
|
||||
typedef FFITypeGetModuleFileNameW = int Function(int, Pointer<Utf16>, int);
|
||||
// ignore: non_constant_identifier_names
|
||||
final FFITypeGetModuleFileNameW GetModuleFileName = _dllKernel32.lookupFunction<
|
||||
_FFITypeGetModuleFileNameW,
|
||||
FFITypeGetModuleFileNameW>('GetModuleFileNameW');
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/api/winver/nf-winver-verqueryvaluew
|
||||
typedef _FFITypeVerQueryValueW = BOOL Function(LPCVOID, LPCWSTR, LPVOID, PUINT);
|
||||
typedef FFITypeVerQueryValueW = int Function(
|
||||
Pointer<NativeType>, Pointer<Utf16>, Pointer<NativeType>, Pointer<Uint32>);
|
||||
// ignore: non_constant_identifier_names
|
||||
final FFITypeVerQueryValueW VerQueryValue =
|
||||
_dllVersion.lookupFunction<_FFITypeVerQueryValueW, FFITypeVerQueryValueW>(
|
||||
'VerQueryValueW');
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/api/fileapi/nf-fileapi-gettemppathw
|
||||
typedef _FFITypeGetTempPathW = DWORD Function(DWORD, LPWSTR);
|
||||
typedef FFITypeGetTempPathW = int Function(int, Pointer<Utf16>);
|
||||
// ignore: non_constant_identifier_names
|
||||
final FFITypeGetTempPathW GetTempPath = _dllKernel32
|
||||
.lookupFunction<_FFITypeGetTempPathW, FFITypeGetTempPathW>('GetTempPathW');
|
||||
@@ -0,0 +1,32 @@
|
||||
name: path_provider_windows
|
||||
description: Windows implementation of the path_provider plugin
|
||||
repository: https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_windows
|
||||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22
|
||||
version: 2.3.0
|
||||
|
||||
environment:
|
||||
sdk: ^3.2.0
|
||||
flutter: ">=3.16.0"
|
||||
|
||||
flutter:
|
||||
plugin:
|
||||
implements: path_provider
|
||||
platforms:
|
||||
windows:
|
||||
dartPluginClass: PathProviderWindows
|
||||
|
||||
dependencies:
|
||||
ffi: ^2.0.0
|
||||
flutter:
|
||||
sdk: flutter
|
||||
path: ^1.8.0
|
||||
path_provider_platform_interface: ^2.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
topics:
|
||||
- files
|
||||
- path-provider
|
||||
- paths
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ffi';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path_provider_windows/src/guid.dart';
|
||||
|
||||
void main() {
|
||||
test('has correct byte representation', () async {
|
||||
final Pointer<GUID> guid = calloc<GUID>()
|
||||
..ref.parse('{00112233-4455-6677-8899-aabbccddeeff}');
|
||||
final ByteData data = ByteData(16)
|
||||
..setInt32(0, guid.ref.data1, Endian.little)
|
||||
..setInt16(4, guid.ref.data2, Endian.little)
|
||||
..setInt16(6, guid.ref.data3, Endian.little)
|
||||
..setInt64(8, guid.ref.data4, Endian.little);
|
||||
expect(data.getUint8(0), 0x33);
|
||||
expect(data.getUint8(1), 0x22);
|
||||
expect(data.getUint8(2), 0x11);
|
||||
expect(data.getUint8(3), 0x00);
|
||||
expect(data.getUint8(4), 0x55);
|
||||
expect(data.getUint8(5), 0x44);
|
||||
expect(data.getUint8(6), 0x77);
|
||||
expect(data.getUint8(7), 0x66);
|
||||
expect(data.getUint8(8), 0x88);
|
||||
expect(data.getUint8(9), 0x99);
|
||||
expect(data.getUint8(10), 0xAA);
|
||||
expect(data.getUint8(11), 0xBB);
|
||||
expect(data.getUint8(12), 0xCC);
|
||||
expect(data.getUint8(13), 0xDD);
|
||||
expect(data.getUint8(14), 0xEE);
|
||||
expect(data.getUint8(15), 0xFF);
|
||||
|
||||
calloc.free(guid);
|
||||
});
|
||||
|
||||
test('handles alternate forms', () async {
|
||||
final Pointer<GUID> guid1 = calloc<GUID>()
|
||||
..ref.parse('{00112233-4455-6677-8899-aabbccddeeff}');
|
||||
final Pointer<GUID> guid2 = calloc<GUID>()
|
||||
..ref.parse('00112233445566778899AABBCCDDEEFF');
|
||||
|
||||
expect(guid1.ref.data1, guid2.ref.data1);
|
||||
expect(guid1.ref.data2, guid2.ref.data2);
|
||||
expect(guid1.ref.data3, guid2.ref.data3);
|
||||
expect(guid1.ref.data4, guid2.ref.data4);
|
||||
|
||||
calloc.free(guid1);
|
||||
calloc.free(guid2);
|
||||
});
|
||||
|
||||
test('throws for bad data', () async {
|
||||
final Pointer<GUID> guid = calloc<GUID>();
|
||||
|
||||
expect(() => guid.ref.parse('{00112233-4455-6677-88'), throwsArgumentError);
|
||||
|
||||
calloc.free(guid);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
|
||||
import 'package:path_provider_windows/path_provider_windows.dart';
|
||||
import 'package:path_provider_windows/src/path_provider_windows_real.dart'
|
||||
show encodingCP1252, encodingUnicode, languageEn;
|
||||
|
||||
// A fake VersionInfoQuerier that just returns preset responses.
|
||||
class FakeVersionInfoQuerier implements VersionInfoQuerier {
|
||||
FakeVersionInfoQuerier(
|
||||
this.responses, {
|
||||
this.language = languageEn,
|
||||
this.encoding = encodingUnicode,
|
||||
});
|
||||
|
||||
final String language;
|
||||
final String encoding;
|
||||
final Map<String, String> responses;
|
||||
|
||||
// ignore: unreachable_from_main
|
||||
String? getStringValue(
|
||||
Pointer<Uint8>? versionInfo,
|
||||
String key, {
|
||||
required String language,
|
||||
required String encoding,
|
||||
}) {
|
||||
if (language == this.language && encoding == this.encoding) {
|
||||
return responses[key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('registered instance', () {
|
||||
PathProviderWindows.registerWith();
|
||||
expect(PathProviderPlatform.instance, isA<PathProviderWindows>());
|
||||
});
|
||||
|
||||
test('getTemporaryPath', () async {
|
||||
final PathProviderWindows pathProvider = PathProviderWindows();
|
||||
expect(await pathProvider.getTemporaryPath(), contains(r'C:\'));
|
||||
}, skip: !Platform.isWindows);
|
||||
|
||||
test('getApplicationSupportPath with no version info', () async {
|
||||
final PathProviderWindows pathProvider = PathProviderWindows();
|
||||
pathProvider.versionInfoQuerier =
|
||||
FakeVersionInfoQuerier(<String, String>{});
|
||||
final String? path = await pathProvider.getApplicationSupportPath();
|
||||
expect(path, contains(r'C:\'));
|
||||
expect(path, contains(r'AppData'));
|
||||
// The last path component should be the executable name.
|
||||
expect(path, endsWith(r'flutter_tester'));
|
||||
}, skip: !Platform.isWindows);
|
||||
|
||||
test('getApplicationSupportPath with full version info in CP1252', () async {
|
||||
final PathProviderWindows pathProvider = PathProviderWindows();
|
||||
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
|
||||
'CompanyName': 'A Company',
|
||||
'ProductName': 'Amazing App',
|
||||
}, encoding: encodingCP1252);
|
||||
final String? path = await pathProvider.getApplicationSupportPath();
|
||||
expect(path, isNotNull);
|
||||
if (path != null) {
|
||||
expect(path, endsWith(r'AppData\Roaming\A Company\Amazing App'));
|
||||
expect(Directory(path).existsSync(), isTrue);
|
||||
}
|
||||
}, skip: !Platform.isWindows);
|
||||
|
||||
test('getApplicationSupportPath with full version info in Unicode', () async {
|
||||
final PathProviderWindows pathProvider = PathProviderWindows();
|
||||
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
|
||||
'CompanyName': 'A Company',
|
||||
'ProductName': 'Amazing App',
|
||||
});
|
||||
final String? path = await pathProvider.getApplicationSupportPath();
|
||||
expect(path, isNotNull);
|
||||
if (path != null) {
|
||||
expect(path, endsWith(r'AppData\Roaming\A Company\Amazing App'));
|
||||
expect(Directory(path).existsSync(), isTrue);
|
||||
}
|
||||
}, skip: !Platform.isWindows);
|
||||
|
||||
test(
|
||||
'getApplicationSupportPath with full version info in Unsupported Encoding',
|
||||
() async {
|
||||
final PathProviderWindows pathProvider = PathProviderWindows();
|
||||
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
|
||||
'CompanyName': 'A Company',
|
||||
'ProductName': 'Amazing App',
|
||||
}, language: '0000', encoding: '0000');
|
||||
final String? path = await pathProvider.getApplicationSupportPath();
|
||||
expect(path, contains(r'C:\'));
|
||||
expect(path, contains(r'AppData'));
|
||||
// The last path component should be the executable name.
|
||||
expect(path, endsWith(r'flutter_tester'));
|
||||
}, skip: !Platform.isWindows);
|
||||
|
||||
test('getApplicationSupportPath with missing company', () async {
|
||||
final PathProviderWindows pathProvider = PathProviderWindows();
|
||||
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
|
||||
'ProductName': 'Amazing App',
|
||||
});
|
||||
final String? path = await pathProvider.getApplicationSupportPath();
|
||||
expect(path, isNotNull);
|
||||
if (path != null) {
|
||||
expect(path, endsWith(r'AppData\Roaming\Amazing App'));
|
||||
expect(Directory(path).existsSync(), isTrue);
|
||||
}
|
||||
}, skip: !Platform.isWindows);
|
||||
|
||||
test('getApplicationSupportPath with problematic values', () async {
|
||||
final PathProviderWindows pathProvider = PathProviderWindows();
|
||||
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
|
||||
'CompanyName': r'A <Bad> Company: Name.',
|
||||
'ProductName': r'A"/Terrible\|App?*Name',
|
||||
});
|
||||
final String? path = await pathProvider.getApplicationSupportPath();
|
||||
expect(path, isNotNull);
|
||||
if (path != null) {
|
||||
expect(
|
||||
path,
|
||||
endsWith(
|
||||
r'AppData\Roaming\A _Bad_ Company_ Name\A__Terrible__App__Name'));
|
||||
expect(Directory(path).existsSync(), isTrue);
|
||||
}
|
||||
}, skip: !Platform.isWindows);
|
||||
|
||||
test('getApplicationSupportPath with a completely invalid company', () async {
|
||||
final PathProviderWindows pathProvider = PathProviderWindows();
|
||||
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
|
||||
'CompanyName': r'..',
|
||||
'ProductName': r'Amazing App',
|
||||
});
|
||||
final String? path = await pathProvider.getApplicationSupportPath();
|
||||
expect(path, isNotNull);
|
||||
if (path != null) {
|
||||
expect(path, endsWith(r'AppData\Roaming\Amazing App'));
|
||||
expect(Directory(path).existsSync(), isTrue);
|
||||
}
|
||||
}, skip: !Platform.isWindows);
|
||||
|
||||
test('getApplicationSupportPath with very long app name', () async {
|
||||
final PathProviderWindows pathProvider = PathProviderWindows();
|
||||
final String truncatedName = 'A' * 255;
|
||||
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
|
||||
'CompanyName': 'A Company',
|
||||
'ProductName': truncatedName * 2,
|
||||
});
|
||||
final String? path = await pathProvider.getApplicationSupportPath();
|
||||
expect(path, endsWith('\\$truncatedName'));
|
||||
// The directory won't exist, since it's longer than MAXPATH, so don't check
|
||||
// that here.
|
||||
}, skip: !Platform.isWindows);
|
||||
|
||||
test('getApplicationDocumentsPath', () async {
|
||||
final PathProviderWindows pathProvider = PathProviderWindows();
|
||||
final String? path = await pathProvider.getApplicationDocumentsPath();
|
||||
expect(path, contains(r'C:\'));
|
||||
expect(path, contains(r'Documents'));
|
||||
}, skip: !Platform.isWindows);
|
||||
|
||||
test('getApplicationCachePath', () async {
|
||||
final PathProviderWindows pathProvider = PathProviderWindows();
|
||||
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
|
||||
'CompanyName': 'A Company',
|
||||
'ProductName': 'Amazing App',
|
||||
}, encoding: encodingCP1252);
|
||||
final String? path = await pathProvider.getApplicationCachePath();
|
||||
expect(path, isNotNull);
|
||||
if (path != null) {
|
||||
expect(path, endsWith(r'AppData\Local\A Company\Amazing App'));
|
||||
expect(Directory(path).existsSync(), isTrue);
|
||||
}
|
||||
}, skip: !Platform.isWindows);
|
||||
|
||||
test('getDownloadsPath', () async {
|
||||
final PathProviderWindows pathProvider = PathProviderWindows();
|
||||
final String? path = await pathProvider.getDownloadsPath();
|
||||
expect(path, contains(r'C:\'));
|
||||
expect(path, contains(r'Downloads'));
|
||||
}, skip: !Platform.isWindows);
|
||||
}
|
||||
Reference in New Issue
Block a user