This commit is contained in:
ekibun
2020-08-08 18:00:21 +08:00
commit 17d2876e36
64 changed files with 5377 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
.DS_Store
.dart_tool/
.packages
.pub/
build/

19
.idea/libraries/Dart_SDK.xml generated Normal file
View File

@@ -0,0 +1,19 @@
<component name="libraryTable">
<library name="Dart SDK">
<CLASSES>
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/async" />
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/collection" />
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/convert" />
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/core" />
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/developer" />
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/html" />
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/io" />
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/isolate" />
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/math" />
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/mirrors" />
<root url="file://D:\flutter/bin/cache/dart-sdk/lib/typed_data" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

10
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/flutter_qjs.iml" filepath="$PROJECT_DIR$/flutter_qjs.iml" />
<module fileurl="file://$PROJECT_DIR$/android/flutter_qjs_android.iml" filepath="$PROJECT_DIR$/android/flutter_qjs_android.iml" />
<module fileurl="file://$PROJECT_DIR$/example/android/flutter_qjs_example_android.iml" filepath="$PROJECT_DIR$/example/android/flutter_qjs_example_android.iml" />
</modules>
</component>
</project>

View File

@@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="example/lib/main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="filePath" value="$PROJECT_DIR$/example/lib/main.dart" />
<method />
</configuration>
</component>

45
.idea/workspace.xml generated Normal file
View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FileEditorManager">
<leaf>
<file leaf-file-name="flutter_qjs.dart" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/lib/flutter_qjs.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="main.dart" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/example/lib/main.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="ToolWindowManager">
<editor active="true" />
<layout>
<window_info id="Project" active="true" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
</layout>
</component>
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1">
</navigator>
<panes>
<pane id="ProjectPane">
<option name="show-excluded-files" value="false" />
</pane>
</panes>
</component>
<component name="PropertiesComponent">
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="dart.analysis.tool.window.force.activate" value="true" />
<property name="show.migrate.to.gradle.popup" value="false" />
</component>
</project>

10
.metadata Normal file
View File

@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: f3b7788f7754a51092ae1d677001767960c21910
channel: master
project_type: plugin

14
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Flutter",
"program": "lib/main.dart",
"request": "launch",
"type": "dart"
}
]
}

62
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,62 @@
{
"files.associations": {
"algorithm": "cpp",
"atomic": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"cmath": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"cwchar": "cpp",
"deque": "cpp",
"exception": "cpp",
"resumable": "cpp",
"functional": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"ios": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"iterator": "cpp",
"limits": "cpp",
"list": "cpp",
"map": "cpp",
"memory": "cpp",
"mutex": "cpp",
"new": "cpp",
"ostream": "cpp",
"queue": "cpp",
"ratio": "cpp",
"set": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"string": "cpp",
"system_error": "cpp",
"thread": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"typeinfo": "cpp",
"unordered_map": "cpp",
"utility": "cpp",
"vector": "cpp",
"xfacet": "cpp",
"xhash": "cpp",
"xiosbase": "cpp",
"xlocale": "cpp",
"xlocinfo": "cpp",
"xlocnum": "cpp",
"xmemory": "cpp",
"xstddef": "cpp",
"xstring": "cpp",
"xtr1common": "cpp",
"xtree": "cpp",
"xutility": "cpp"
}
}

3
CHANGELOG.md Normal file
View File

@@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

1
LICENSE Normal file
View File

@@ -0,0 +1 @@
TODO: Add your license here.

18
README.md Normal file
View File

@@ -0,0 +1,18 @@
# flutter_qjs
A new flutter plugin project.
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/developing-packages/),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
The plugin project was generated without specifying the `--platforms` flag, no platforms are currently supported.
To add platforms, run `flutter create -t plugin --platforms <platforms> .` under the same
directory. You can also find a detailed instruction on how to add platforms in the `pubspec.yaml` at https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms.

41
example/.gitignore vendored Normal file
View File

@@ -0,0 +1,41 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json

10
example/.metadata Normal file
View File

@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: f3b7788f7754a51092ae1d677001767960c21910
channel: master
project_type: app

16
example/README.md Normal file
View File

@@ -0,0 +1,16 @@
# flutter_qjs_example
Demonstrates how to use the flutter_qjs plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@@ -0,0 +1,34 @@
/*
* @Description:
* @Author: ekibun
* @Date: 2020-08-01 13:20:06
* @LastEditors: ekibun
* @LastEditTime: 2020-08-08 17:52:22
*/
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'highlight.dart';
class CodeEditor extends StatefulWidget {
final void Function(String) onChanged;
const CodeEditor({Key key, this.onChanged}) : super(key: key);
@override
_CodeEditorState createState() => _CodeEditorState();
}
class _CodeEditorState extends State<CodeEditor> {
CodeInputController _controller = CodeInputController();
@override
Widget build(BuildContext context) {
return TextField(
autofocus: true,
controller: _controller,
textCapitalization: TextCapitalization.none,
decoration: null,
maxLines: null,
onChanged: this.widget.onChanged,
);
}
}

View File

@@ -0,0 +1,106 @@
/*
* @Description: Code highlight controller
* @Author: ekibun
* @Date: 2020-08-01 17:42:06
* @LastEditors: ekibun
* @LastEditTime: 2020-08-02 12:39:26
*/
import 'dart:math';
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_highlight/themes/a11y-light.dart';
import 'package:highlight/highlight.dart';
Map<String, TextStyle> _theme = a11yLightTheme;
List<TextSpan> _convert(String code) {
var nodes = highlight.parse(code, language: 'javascript').nodes;
List<TextSpan> spans = [];
var currentSpans = spans;
List<List<TextSpan>> stack = [];
_traverse(Node node) {
if (node.value != null) {
currentSpans.add(node.className == null
? TextSpan(text: node.value)
: TextSpan(text: node.value, style: _theme[node.className]));
} else if (node.children != null) {
List<TextSpan> tmp = [];
currentSpans.add(TextSpan(children: tmp, style: _theme[node.className]));
stack.add(currentSpans);
currentSpans = tmp;
node.children.forEach((n) {
_traverse(n);
if (n == node.children.last) {
currentSpans = stack.isEmpty ? spans : stack.removeLast();
}
});
}
}
for (var node in nodes) {
_traverse(node);
}
return spans;
}
class CodeInputController extends TextEditingController {
CodeInputController({String text}) : super(text: text);
TextSpan oldSpan = TextSpan();
Future<void> spanCall;
@override
TextSpan buildTextSpan({TextStyle style, bool withComposing}) {
String oldText = oldSpan.toPlainText();
String newText = value.text;
if (oldText == newText) return oldSpan;
(spanCall?.timeout(Duration.zero) ?? Future.delayed(Duration.zero))
.then((_) => spanCall = compute(_convert, value.text).then((lsSpan) {
TextSpan newSpan = TextSpan(style: style, children: lsSpan);
if (newSpan.toPlainText() == value.text) oldSpan = newSpan;
notifyListeners();
}))
.catchError((_) => {});
List<TextSpan> beforeSpans = [];
int splitAt = value.selection.start;
if (splitAt < 0) splitAt = newText.length ~/ 2;
int start = 0;
InlineSpan leftSpan;
oldSpan.children?.indexWhere((element) {
String elementText = element.toPlainText();
if (start + elementText.length > splitAt || !newText.startsWith(elementText, start)) {
leftSpan = element;
return true;
}
beforeSpans.add(element);
start += elementText.length;
return false;
});
List<TextSpan> endSpans = [];
int end = 0;
InlineSpan rightSpan;
oldSpan.children?.sublist(beforeSpans.length)?.lastIndexWhere((element) {
String elementText = element.toPlainText();
if (splitAt + end + elementText.length >= newText.length ||
!newText.substring(start, newText.length - end).endsWith(elementText)) {
rightSpan = element;
return true;
}
endSpans.add(element);
end += elementText.length;
return false;
});
return TextSpan(style: style, children: [
...beforeSpans,
TextSpan(
style: leftSpan != null && leftSpan == rightSpan ? leftSpan.style : style,
text: newText.substring(start, max(start, newText.length - end))),
...endSpans.reversed
]);
}
}

39
example/lib/main.dart Normal file
View File

@@ -0,0 +1,39 @@
/*
* @Description:
* @Author: ekibun
* @Date: 2020-08-08 08:16:51
* @LastEditors: ekibun
* @LastEditTime: 2020-08-08 17:50:30
*/
import 'package:flutter/material.dart';
import 'package:flutter_qjs_example/test.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({ Key key }) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter_qjs',
debugShowCheckedModeBanner: false,
theme: ThemeData(
appBarTheme: AppBarTheme(brightness: Brightness.dark, elevation: 0),
primaryColor: Color(0xfff09199),
accentColor: Color(0xffec818a),
backgroundColor: Colors.grey[300],
primaryColorBrightness: Brightness.dark,
),
routes: {
'home': (BuildContext context) => TestPage(),
},
initialRoute: 'home',
);
}
}

102
example/lib/test.dart Normal file
View File

@@ -0,0 +1,102 @@
/*
* @Description:
* @Author: ekibun
* @Date: 2020-07-18 23:28:55
* @LastEditors: ekibun
* @LastEditTime: 2020-08-08 17:38:48
*/
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_qjs/flutter_qjs.dart';
import 'code/editor.dart';
class TestPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
String code, resp;
int engine;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("JS 引擎功能测试"),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
FlatButton(child: Text("初始化引擎"), onPressed: () async {
if ((engine?? 0) != 0) return;
engine = await FlutterJs.initEngine();
// dart 函数回调
FlutterJs.methodHandler = (String method, List arg) async {
switch (method) {
case "delay":
await Future.delayed(Duration(milliseconds: arg[0]));
return;
case "http":
Response response = await Dio().get(arg[0]);
return response.data;
default:
}
};
}),
FlatButton(child: Text("运行"), onPressed: () async {
if ((engine?? 0) == 0) {
print("请先初始化引擎");
return;
}
try {
resp = await FlutterJs.evaluate(code ?? '', "<eval>");
} catch(e) {
resp = e.toString();
}
setState(() {
code = code;
});
}),
FlatButton(child: Text("释放引擎"), onPressed: () async {
if ((engine?? 0) == 0) return;
await FlutterJs.close();
engine = 0;
}),
],
),
),
Container(
padding: const EdgeInsets.all(12),
color: Colors.grey.withOpacity(0.1),
constraints: BoxConstraints(minHeight: 200),
child: CodeEditor(
onChanged: (v) {
code = v;
},
),
),
SizedBox(height: 16),
Text("运行结果:"),
SizedBox(height: 16),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
color: Colors.green.withOpacity(0.05),
constraints: BoxConstraints(minHeight: 100),
child: Text(resp ?? ''),
),
],
),
),
);
}
}

182
example/pubspec.lock Normal file
View File

@@ -0,0 +1,182 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.0-nullsafety"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0-nullsafety"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0-nullsafety.2"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0-nullsafety"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0-nullsafety"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.15.0-nullsafety.2"
dio:
dependency: "direct main"
description:
name: dio
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.10"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0-nullsafety"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_highlight:
dependency: "direct main"
description:
name: flutter_highlight
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.6.0"
flutter_qjs:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "0.0.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
highlight:
dependency: "direct main"
description:
name: highlight
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.6.0"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.4"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.12.10-nullsafety"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0-nullsafety.2"
path:
dependency: transitive
description:
name: path
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.8.0-nullsafety"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.8.0-nullsafety"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.10.0-nullsafety"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0-nullsafety"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0-nullsafety"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0-nullsafety"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.19-nullsafety"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0-nullsafety.2"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0-nullsafety.2"
sdks:
dart: ">=2.10.0-0.0.dev <2.10.0"
flutter: ">=1.20.0 <2.0.0"

71
example/pubspec.yaml Normal file
View File

@@ -0,0 +1,71 @@
name: flutter_qjs_example
description: Demonstrates how to use the flutter_qjs plugin.
# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_qjs:
# When depending on this package from a real application you should use:
# flutter_qjs: ^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: ../
highlight: 0.6.0
flutter_highlight: 0.6.0
dio: 3.0.10
dev_dependencies:
flutter_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages

View File

@@ -0,0 +1,27 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_qjs_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) => widget is Text &&
widget.data.startsWith('Running on:'),
),
findsOneWidget,
);
});
}

17
example/windows/.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
flutter/ephemeral/
# Visual Studio user-specific files.
*.suo
*.user
*.userosscache
*.sln.docstates
# Visual Studio build-related files.
x64/
x86/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

View File

@@ -0,0 +1,95 @@
cmake_minimum_required(VERSION 3.15)
project(flutter_qjs_example LANGUAGES CXX)
set(BINARY_NAME "flutter_qjs_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)

View File

@@ -0,0 +1 @@
4

View File

@@ -0,0 +1,98 @@
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")
# === 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
"engine_method_result.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_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.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG>
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)

View File

@@ -0,0 +1,12 @@
//
// Generated file. Do not edit.
//
#include "generated_plugin_registrant.h"
#include <flutter_qjs/flutter_qjs_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterQjsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterQjsPlugin"));
}

View File

@@ -0,0 +1,13 @@
//
// Generated file. Do not edit.
//
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View File

@@ -0,0 +1,16 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_qjs
)
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)

View File

@@ -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"
"window_configuration.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
apply_standard_settings(${BINARY_NAME})
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)

View File

@@ -0,0 +1,70 @@
// 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"
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@@ -0,0 +1,29 @@
#include "flutter_window.h"
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(RunLoop* run_loop,
const flutter::DartProject& project)
: run_loop_(run_loop), project_(project) {}
FlutterWindow::~FlutterWindow() {}
void FlutterWindow::OnCreate() {
Win32Window::OnCreate();
// The size here is arbitrary since SetChildContent will resize it.
flutter_controller_ =
std::make_unique<flutter::FlutterViewController>(100, 100, project_);
RegisterPlugins(flutter_controller_.get());
run_loop_->RegisterFlutterInstance(flutter_controller_.get());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
}
void FlutterWindow::OnDestroy() {
if (flutter_controller_) {
run_loop_->UnregisterFlutterInstance(flutter_controller_.get());
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}

View File

@@ -0,0 +1,37 @@
#ifndef FLUTTER_WINDOW_H_
#define FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include "run_loop.h"
#include "win32_window.h"
#include <memory>
// 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:
void OnCreate() override;
void OnDestroy() 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 // FLUTTER_WINDOW_H_

View File

@@ -0,0 +1,37 @@
#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"
#include "window_configuration.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(kFlutterWindowOriginX, kFlutterWindowOriginY);
Win32Window::Size size(kFlutterWindowWidth, kFlutterWindowHeight);
if (!window.CreateAndShow(kFlutterWindowTitle, origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
run_loop.Run();
::CoUninitialize();
return EXIT_SUCCESS;
}

View File

@@ -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

View File

@@ -0,0 +1,70 @@
#include "run_loop.h"
#include <Windows.h>
// Don't stomp std::min/std::max
#undef max
#undef min
#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::FlutterViewController* flutter_instance) {
flutter_instances_.insert(flutter_instance);
}
void RunLoop::UnregisterFlutterInstance(
flutter::FlutterViewController* flutter_instance) {
flutter_instances_.erase(flutter_instance);
}
RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
TimePoint next_event_time = TimePoint::max();
for (auto flutter_controller : flutter_instances_) {
std::chrono::nanoseconds wait_duration =
flutter_controller->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;
}

View File

@@ -0,0 +1,40 @@
#ifndef RUN_LOOP_H_
#define RUN_LOOP_H_
#include <flutter/flutter_view_controller.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::FlutterViewController* flutter_instance);
// Unregisters the given Flutter instance from event servicing.
void UnregisterFlutterInstance(
flutter::FlutterViewController* 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::FlutterViewController*> flutter_instances_;
};
#endif // RUN_LOOP_H_

View File

@@ -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>

View File

@@ -0,0 +1,22 @@
#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();
}
}

View File

@@ -0,0 +1,8 @@
#ifndef CONSOLE_UTILS_H_
#define CONSOLE_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 // CONSOLE_UTILS_H_

View File

@@ -0,0 +1,249 @@
#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);
OnCreate();
return window != nullptr;
}
// 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 {
auto window =
reinterpret_cast<Win32Window*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (window == nullptr) {
return 0;
}
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;
GetClientRect(hwnd, &rect);
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;
// Messages that are directly forwarded to embedding.
case WM_FONTCHANGE:
SendMessage(child_content_, WM_FONTCHANGE, NULL, NULL);
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;
GetClientRect(window_handle_, &frame);
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
SetFocus(child_content_);
}
HWND Win32Window::GetHandle() {
return window_handle_;
}
void Win32Window::SetQuitOnClose(bool quit_on_close) {
quit_on_close_ = quit_on_close;
}
void Win32Window::OnCreate() {
// No-op; provided for subclasses.
}
void Win32Window::OnDestroy() {
// No-op; provided for subclasses.
}

View File

@@ -0,0 +1,96 @@
#ifndef WIN32_WINDOW_H_
#define WIN32_WINDOW_H_
#include <Windows.h>
#include <Windowsx.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);
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.
virtual void 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 // WIN32_WINDOW_H_

View File

@@ -0,0 +1,7 @@
#include "window_configuration.h"
const wchar_t* kFlutterWindowTitle = L"flutter_qjs_example";
const unsigned int kFlutterWindowOriginX = 10;
const unsigned int kFlutterWindowOriginY = 10;
const unsigned int kFlutterWindowWidth = 1280;
const unsigned int kFlutterWindowHeight = 720;

View File

@@ -0,0 +1,18 @@
#ifndef WINDOW_CONFIGURATION_
#define WINDOW_CONFIGURATION_
// This is a temporary approach to isolate changes that people are likely to
// make to main.cpp, where the APIs are still in flux. This will reduce the
// need to resolve conflicts or re-create changes slightly differently every
// time the Windows Flutter API surface changes.
//
// Longer term there should be simpler configuration options for common
// customizations like this, without requiring native code changes.
extern const wchar_t* kFlutterWindowTitle;
extern const unsigned int kFlutterWindowOriginX;
extern const unsigned int kFlutterWindowOriginY;
extern const unsigned int kFlutterWindowWidth;
extern const unsigned int kFlutterWindowHeight;
#endif // WINDOW_CONFIGURATION_

19
flutter_qjs.iml Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/build" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart Packages" level="project" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
</component>
</module>

37
lib/flutter_qjs.dart Normal file
View File

@@ -0,0 +1,37 @@
/*
* @Description:
* @Author: ekibun
* @Date: 2020-08-08 08:29:09
* @LastEditors: ekibun
* @LastEditTime: 2020-08-08 17:40:35
*/
import 'dart:async';
import 'dart:convert';
import 'package:flutter/services.dart';
class FlutterJs {
static const MethodChannel _channel = const MethodChannel('soko.ekibun.flutter_qjs');
static Future<dynamic> Function(String method, List args) methodHandler;
static Future<int> initEngine() async {
final int engineId = await _channel.invokeMethod("initEngine");
_channel.setMethodCallHandler((call) async {
if (methodHandler == null) return call.noSuchMethod(null);
List args = jsonDecode(call.arguments);
return jsonEncode(await methodHandler(call.method, args));
});
return engineId;
}
static Future<String> evaluate(String command, String name) async {
var arguments = {"script": command, "name": command};
final String jsResult = await _channel.invokeMethod("evaluate", arguments);
return jsResult ?? "null";
}
static Future<void> close() async {
return await _channel.invokeMethod("close");
}
}

147
pubspec.lock Normal file
View File

@@ -0,0 +1,147 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.0-nullsafety"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0-nullsafety"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0-nullsafety.2"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0-nullsafety"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0-nullsafety"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.15.0-nullsafety.2"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0-nullsafety"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.12.10-nullsafety"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0-nullsafety.2"
path:
dependency: transitive
description:
name: path
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.8.0-nullsafety"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.8.0-nullsafety"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.10.0-nullsafety"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0-nullsafety"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0-nullsafety"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0-nullsafety"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.19-nullsafety"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0-nullsafety.2"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0-nullsafety.2"
sdks:
dart: ">=2.10.0-0.0.dev <2.10.0"
flutter: ">=1.20.0 <2.0.0"

68
pubspec.yaml Normal file
View File

@@ -0,0 +1,68 @@
name: flutter_qjs
description: A new flutter plugin project.
version: 0.0.1
author:
homepage:
environment:
sdk: ">=2.7.0 <3.0.0"
flutter: ">=1.20.0 <2.0.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' and Android 'package' identifiers should not ordinarily
# be modified. They are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
platforms:
# This plugin project was generated without specifying any
# platforms with the `--platform` argument. If you see the `fake_platform` map below, remove it and
# then add platforms following the instruction here:
# https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms
# -------------------
windows:
pluginClass: FlutterQjsPlugin
# -------------------
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages

View File

@@ -0,0 +1,19 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
const MethodChannel channel = MethodChannel('flutter_qjs');
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
return '42';
});
});
tearDown(() {
channel.setMockMethodCallHandler(null);
});
}

17
windows/.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
flutter/
# Visual Studio user-specific files.
*.suo
*.user
*.userosscache
*.sln.docstates
# Visual Studio build-related files.
x64/
x86/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

25
windows/CMakeLists.txt Normal file
View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.15)
set(PROJECT_NAME "flutter_qjs")
project(${PROJECT_NAME} LANGUAGES CXX)
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
set(PLUGIN_NAME "${PROJECT_NAME}_plugin")
add_library(${PLUGIN_NAME} SHARED
"${PLUGIN_NAME}.cpp"
)
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin
"${CMAKE_CURRENT_SOURCE_DIR}/quickjs/libquickjs64.a"
)
# List of absolute paths to libraries that should be bundled with the plugin
set(flutter_qjs_bundled_libraries
"${CMAKE_CURRENT_SOURCE_DIR}/quickjs/libquickjs64.dll"
PARENT_SCOPE
)

View File

@@ -0,0 +1,123 @@
#include "include/flutter_qjs/flutter_qjs_plugin.h"
// This must be included before many other Windows headers.
#include <windows.h>
// For getPlatformVersion; remove unless needed for your plugin implementation.
#include <VersionHelpers.h>
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>
#include "js_engine.hpp"
namespace
{
class FlutterQjsPlugin : public flutter::Plugin
{
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
FlutterQjsPlugin();
virtual ~FlutterQjsPlugin();
private:
// Called when a method is called on this plugin's channel from Dart.
void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
};
std::shared_ptr<flutter::MethodChannel<flutter::EncodableValue>> channel;
// static
void FlutterQjsPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarWindows *registrar)
{
channel =
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), "soko.ekibun.flutter_qjs",
&flutter::StandardMethodCodec::GetInstance());
auto plugin = std::make_unique<FlutterQjsPlugin>();
channel->SetMethodCallHandler(
[plugin_pointer = plugin.get()](const auto &call, auto result) {
plugin_pointer->HandleMethodCall(call, std::move(result));
});
registrar->AddPlugin(std::move(plugin));
}
FlutterQjsPlugin::FlutterQjsPlugin() {}
FlutterQjsPlugin::~FlutterQjsPlugin() {}
const flutter::EncodableValue &ValueOrNull(const flutter::EncodableMap &map, const char *key)
{
static flutter::EncodableValue null_value;
auto it = map.find(flutter::EncodableValue(key));
if (it == map.end())
{
return null_value;
}
return it->second;
}
qjs::Engine *engine = nullptr;
void FlutterQjsPlugin::HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
{
// Replace "getPlatformVersion" check with your plugin's method.
// See:
// https://github.com/flutter/engine/tree/master/shell/platform/common/cpp/client_wrapper/include/flutter
// and
// https://github.com/flutter/engine/tree/master/shell/platform/glfw/client_wrapper/include/flutter
// for the relevant Flutter APIs.
if (method_call.method_name().compare("initEngine") == 0)
{
engine = new qjs::Engine(channel);
flutter::EncodableValue response((long)engine);
result->Success(&response);
}
else if (method_call.method_name().compare("evaluate") == 0)
{
flutter::EncodableMap args = *((flutter::EncodableMap *)method_call.arguments());
std::string script = std::get<std::string>(ValueOrNull(args, "script"));
std::string name = std::get<std::string>(ValueOrNull(args, "name"));
auto presult = result.release();
engine->commit(qjs::EngineTask{
script, name,
[presult](std::string resolve) {
flutter::EncodableValue response(resolve);
presult->Success(&response);
},
[presult](std::string reject) {
presult->Error("FlutterJSException", reject);
}});
}
else if (method_call.method_name().compare("close") == 0)
{
delete engine;
result->Success();
}
else
{
result->NotImplemented();
}
}
} // namespace
void FlutterQjsPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar)
{
FlutterQjsPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarManager::GetInstance()
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
}

View File

@@ -0,0 +1,23 @@
#ifndef FLUTTER_PLUGIN_FLUTTER_JS_PLUGIN_H_
#define FLUTTER_PLUGIN_FLUTTER_JS_PLUGIN_H_
#include <flutter_plugin_registrar.h>
#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport)
#else
#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport)
#endif
#if defined(__cplusplus)
extern "C" {
#endif
FLUTTER_PLUGIN_EXPORT void FlutterQjsPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar);
#if defined(__cplusplus)
} // extern "C"
#endif
#endif // FLUTTER_PLUGIN_FLUTTER_JS_PLUGIN_H_

207
windows/js_dart_promise.hpp Normal file
View File

@@ -0,0 +1,207 @@
/*
* @Description:
* @Author: ekibun
* @Date: 2020-08-07 13:55:52
* @LastEditors: ekibun
* @LastEditTime: 2020-08-08 16:54:23
*/
#pragma once
#include "quickjs/quickjspp.hpp"
#include "quickjs/quickjs/list.h"
#include <flutter/method_result_functions.h>
#include <future>
namespace qjs
{
static JSClassID js_dart_promise_class_id;
typedef struct
{
int count;
JSValue *argv;
} JSOSFutureArgv;
using JSFutureReturn = std::function<JSOSFutureArgv(JSContext *)>;
typedef struct
{
struct list_head link;
std::shared_future<JSFutureReturn> future;
JSValue resolve;
JSValue reject;
} JSOSFuture;
typedef struct JSThreadState
{
struct list_head os_future; /* list of JSOSFuture.link */
std::shared_ptr<flutter::MethodChannel<flutter::EncodableValue>> channel;
} JSThreadState;
static JSValue js_add_future(Value resolve, Value reject, std::shared_future<JSFutureReturn> future)
{
JSRuntime *rt = JS_GetRuntime(resolve.ctx);
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
JSValueConst jsResolve, jsReject;
JSOSFuture *th;
JSValue obj;
jsResolve = resolve.v;
if (!JS_IsFunction(resolve.ctx, jsResolve))
return JS_ThrowTypeError(resolve.ctx, "resolve not a function");
jsReject = reject.v;
if (!JS_IsFunction(reject.ctx, jsReject))
return JS_ThrowTypeError(reject.ctx, "reject not a function");
obj = JS_NewObjectClass(resolve.ctx, js_dart_promise_class_id);
if (JS_IsException(obj))
return obj;
th = (JSOSFuture *)js_mallocz(resolve.ctx, sizeof(*th));
if (!th)
{
JS_FreeValue(resolve.ctx, obj);
return JS_EXCEPTION;
}
th->future = future;
th->resolve = JS_DupValue(resolve.ctx, jsResolve);
th->reject = JS_DupValue(reject.ctx, jsReject);
list_add_tail(&th->link, &ts->os_future);
JS_SetOpaque(obj, th);
return obj;
}
JSValue js_dart_future(Value resolve, Value reject, std::string name, std::string args)
{
auto promise = new std::promise<JSFutureReturn>();
JSRuntime *rt = JS_GetRuntime(resolve.ctx);
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
ts->channel->InvokeMethod(
name,
std::make_unique<flutter::EncodableValue>(args),
std::make_unique<flutter::MethodResultFunctions<flutter::EncodableValue>>(
(flutter::ResultHandlerSuccess<flutter::EncodableValue>)[promise](
const flutter::EncodableValue *result) {
promise->set_value((JSFutureReturn)[rep = std::get<std::string>(*result)](JSContext * ctx) {
JSValue *ret = new JSValue{JS_NewString(ctx, rep.c_str())};
return JSOSFutureArgv{1, ret};
});
},
(flutter::ResultHandlerError<flutter::EncodableValue>)[promise](
const std::string &error_code,
const std::string &error_message,
const flutter::EncodableValue *error_details) {
promise->set_value((JSFutureReturn)[error_message](JSContext * ctx) {
JSValue *ret = new JSValue{JS_NewString(ctx, error_message.c_str())};
return JSOSFutureArgv{-1, ret};
});
},
(flutter::ResultHandlerNotImplemented<flutter::EncodableValue>)[promise]() {
promise->set_value((JSFutureReturn)[](JSContext * ctx) {
JSValue *ret = new JSValue{JS_NewString(ctx, "NotImplemented")};
return JSOSFutureArgv{-1, ret};
});
}));
return js_add_future(resolve, reject, promise->get_future());
}
static void unlink_future(JSRuntime *rt, JSOSFuture *th)
{
if (th->link.prev)
{
list_del(&th->link);
th->link.prev = th->link.next = NULL;
}
}
static void free_future(JSRuntime *rt, JSOSFuture *th)
{
JS_FreeValueRT(rt, th->resolve);
JS_FreeValueRT(rt, th->reject);
js_free_rt(rt, th);
}
void js_init_handlers(JSRuntime *rt, std::shared_ptr<flutter::MethodChannel<flutter::EncodableValue>> channel)
{
JSThreadState *ts = (JSThreadState *)malloc(sizeof(*ts));
if (!ts)
{
fprintf(stderr, "Could not allocate memory for the worker");
exit(1);
}
memset(ts, 0, sizeof(*ts));
init_list_head(&ts->os_future);
ts->channel = channel;
JS_SetRuntimeOpaque(rt, ts);
}
void js_free_handlers(JSRuntime *rt)
{
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
struct list_head *el, *el1;
list_for_each_safe(el, el1, &ts->os_future)
{
JSOSFuture *th = list_entry(el, JSOSFuture, link);
th->future.get();
unlink_future(rt, th);
free_future(rt, th);
}
ts->channel = nullptr;
free(ts);
JS_SetRuntimeOpaque(rt, NULL); /* fail safe */
}
static void call_handler(JSContext *ctx, JSValueConst func, int count, JSValue *argv)
{
JSValue ret, func1;
/* 'func' might be destroyed when calling itself (if it frees the
handler), so must take extra care */
func1 = JS_DupValue(ctx, func);
ret = JS_Call(ctx, func1, JS_UNDEFINED, count, argv);
JS_FreeValue(ctx, func1);
if (JS_IsException(ret))
throw exception{};
JS_FreeValue(ctx, ret);
}
static int js_dart_poll(JSContext *ctx)
{
JSRuntime *rt = JS_GetRuntime(ctx);
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
struct list_head *el;
/* XXX: handle signals if useful */
if (list_empty(&ts->os_future))
return -1; /* no more events */
/* XXX: only timers and basic console input are supported */
if (!list_empty(&ts->os_future))
{
list_for_each(el, &ts->os_future)
{
JSOSFuture *th = list_entry(el, JSOSFuture, link);
if (th->future._Is_ready())
{
JSOSFutureArgv argv = th->future.get()(ctx);
JSValue resolve, reject;
int64_t delay;
/* the timer expired */
resolve = th->resolve;
th->resolve = JS_UNDEFINED;
reject = th->reject;
th->reject = JS_UNDEFINED;
unlink_future(rt, th);
free_future(rt, th);
call_handler(ctx, argv.count < 0 ? reject : resolve, abs(argv.count), argv.argv);
for (int i = 0; i < abs(argv.count); ++i)
JS_FreeValue(ctx, argv.argv[i]);
JS_FreeValue(ctx, resolve);
JS_FreeValue(ctx, reject);
delete argv.argv;
return 0;
}
}
}
return 0;
}
} // namespace qjs

192
windows/js_engine.hpp Normal file
View File

@@ -0,0 +1,192 @@
/*
* @Description:
* @Author: ekibun
* @Date: 2020-08-08 10:30:59
* @LastEditors: ekibun
* @LastEditTime: 2020-08-08 17:05:22
*/
#pragma once
#include <vector>
#include <queue>
#include <thread>
#include <atomic>
#include <future>
#include <iostream>
#include "js_dart_promise.hpp"
#include "quickjs/quickjspp.hpp"
namespace qjs
{
struct EngineTask
{
std::string command;
std::string name;
std::function<void(std::string)> resolve;
std::function<void(std::string)> reject;
};
struct EngineTaskResolver
{
Value result;
std::function<void(std::string)> resolve;
std::function<void(std::string)> reject;
};
std::string getStackTrack(Value exc)
{
std::string err = (std::string)exc;
if ((bool)exc["stack"])
err += "\n" + (std::string)exc["stack"];
return err;
}
class Engine
{
// 引擎线程
std::thread thread;
// 任务队列
std::queue<EngineTask> tasks;
// 同步
std::mutex m_lock;
// 是否关闭提交
std::atomic<bool> stoped;
void handleException(qjs::Value exc)
{
std::cout << getStackTrack(exc) << std::endl;
}
public:
inline Engine(std::shared_ptr<flutter::MethodChannel<flutter::EncodableValue>> channel) : stoped{false}
{
thread = std::thread([this, channel] { // 工作线程函数
// 创建运行环境
Runtime rt;
js_init_handlers(rt.rt, channel);
Context ctx(rt);
auto &module = ctx.addModule("__DartImpl");
module.function<&js_dart_future>("__invoke");
ctx.eval(
R"xxx(
import * as __DartImpl from "__DartImpl";
globalThis.dart = (method, ...args) => new Promise((res, rej) =>
__DartImpl.__invoke((v) => res(JSON.parse(v)), rej, method, JSON.stringify(args)));
)xxx",
"<dart>", JS_EVAL_TYPE_MODULE);
std::vector<EngineTaskResolver> unresolvedTask;
// 循环
while (!this->stoped)
{
// 获取待执行的task
EngineTask task;
{ // 获取一个待执行的 task
std::unique_lock<std::mutex> lock{this->m_lock}; // unique_lock 相比 lock_guard 的好处是:可以随时 unlock() 和 lock()
if (!this->tasks.empty())
{
task = this->tasks.front(); // 取一个 task
this->tasks.pop();
}
}
// 执行task
if (task.resolve)
try
{
ctx.global()["__evalstr"] = JS_NewString(ctx.ctx, task.command.c_str());
Value ret = ctx.eval(
R"xxx(
(() => {
const __ret = Promise.resolve(eval(__evalstr))
.then(v => {
__ret.__value = v;
__ret.__resolved = true;
}).catch(e => {
__ret.__error = e;
__ret.__rejected = true;
});
return __ret;
})()
)xxx",
task.name.c_str());
unresolvedTask.emplace_back(EngineTaskResolver{ret, std::move(task.resolve), std::move(task.reject)});
}
catch (exception e)
{
task.reject(getStackTrack(ctx.getException()));
}
// 执行microtask
JSContext *pctx;
for (;;)
{
int err = JS_ExecutePendingJob(rt.rt, &pctx);
if (err <= 0)
{
if (err < 0)
std::cout << getStackTrack(ctx.getException()) << std::endl;
break;
}
}
// TODO 检查promise状态
for (auto it = unresolvedTask.begin(); it != unresolvedTask.end();)
{
bool finished = false;
if (it->result["__resolved"])
{
it->resolve((std::string)it->result["__value"]);
finished = true;
};
if (it->result["__rejected"])
{
it->reject(getStackTrack(it->result["__error"]));
finished = true;
};
if (finished)
it = unresolvedTask.erase(it);
else
++it;
}
// 检查dart交互
bool idle = true;
try
{
idle = js_dart_poll(ctx.ctx);
}
catch (exception e)
{
handleException(ctx.getException());
}
// 空闲时reject所有task
if (idle && !JS_IsJobPending(rt.rt) && !unresolvedTask.empty())
{
for (EngineTaskResolver &task : unresolvedTask)
{
task.reject("Promise cannot resolve");
}
unresolvedTask.clear();
}
}
js_free_handlers(rt.rt);
});
}
inline ~Engine()
{
stoped.store(true);
if (thread.joinable())
thread.join(); // 等待任务结束, 前提:线程一定会执行完
}
public:
// 提交一个任务
void commit(EngineTask task)
{
if (stoped.load()) // stop == true ??
throw std::runtime_error("commit on stopped engine.");
{ // 添加任务到队列
std::lock_guard<std::mutex> lock{m_lock}; //对当前块的语句加锁 lock_guard 是 mutex 的 stack 封装类,构造的时候 lock(),析构的时候 unlock()
tasks.emplace(task);
}
}
};
} // namespace qjs

View File

@@ -0,0 +1,195 @@
EXPORTS
JS_AddIntrinsicAtomics
JS_AddIntrinsicBaseObjects
JS_AddIntrinsicBigDecimal
JS_AddIntrinsicBigFloat
JS_AddIntrinsicBigInt
JS_AddIntrinsicDate
JS_AddIntrinsicEval
JS_AddIntrinsicJSON
JS_AddIntrinsicMapSet
JS_AddIntrinsicOperators
JS_AddIntrinsicPromise
JS_AddIntrinsicProxy
JS_AddIntrinsicRegExp
JS_AddIntrinsicRegExpCompiler
JS_AddIntrinsicStringNormalize
JS_AddIntrinsicTypedArrays
JS_AddModuleExport
JS_AddModuleExportList
JS_AtomToCString
JS_AtomToString
JS_AtomToValue
JS_Call
JS_CallConstructor
JS_CallConstructor2
JS_ComputeMemoryUsage
JS_DefineProperty
JS_DefinePropertyGetSet
JS_DefinePropertyValue
JS_DefinePropertyValueInt64
JS_DefinePropertyValueStr
JS_DefinePropertyValueUint32
JS_DefinePropertyValueValue
JS_DeleteProperty
JS_DeletePropertyInt64
JS_DetachArrayBuffer
JS_DetectModule
JS_DumpMemoryUsage
JS_DupAtom
JS_DupContext
JS_EnableBignumExt
JS_EnqueueJob
JS_Eval
JS_EvalFunction
JS_ExecutePendingJob
JS_FreeAtom
JS_FreeAtomRT
JS_FreeCString
JS_FreeContext
JS_FreeRuntime
JS_GetArrayBuffer
JS_GetClassProto
JS_GetContextOpaque
JS_GetException
JS_GetGlobalObject
JS_GetImportMeta
JS_GetModuleName
JS_GetOpaque
JS_GetOpaque2
JS_GetOwnProperty
JS_GetOwnPropertyNames
JS_GetPropertyInternal
JS_GetPropertyStr
JS_GetPropertyUint32
JS_GetPrototype
JS_GetRuntime
JS_GetRuntimeOpaque
JS_GetTypedArrayBuffer
JS_HasProperty
JS_Invoke
JS_IsArray
JS_IsCFunction
JS_IsConstructor
JS_IsError
JS_IsExtensible
JS_IsFunction
JS_IsInstanceOf
JS_IsJobPending
JS_IsLiveObject
JS_IsRegisteredClass
JS_IsUncatchableError
JS_JSONStringify
JS_MarkValue
JS_NewArray
JS_NewArrayBuffer
JS_NewArrayBufferCopy
JS_NewAtom
JS_NewAtomLen
JS_NewAtomString
JS_NewAtomUInt32
JS_NewBigInt64
JS_NewBigInt64_1
JS_NewBigUint64
JS_NewCFunction2
JS_NewCFunctionData
JS_NewCModule
JS_NewClass
JS_NewClassID
JS_NewContext
JS_NewContextRaw
JS_NewError
JS_NewObject
JS_NewObjectClass
JS_NewObjectProto
JS_NewObjectProtoClass
JS_NewPromiseCapability
JS_NewRuntime
JS_NewRuntime2
JS_NewString
JS_NewStringLen
JS_ParseJSON
JS_ParseJSON2
JS_PreventExtensions
JS_ReadObject
JS_ResetUncatchableError
JS_ResolveModule
JS_RunGC
JS_SetCanBlock
JS_SetClassProto
JS_SetConstructor
JS_SetConstructorBit
JS_SetContextOpaque
JS_SetGCThreshold
JS_SetHostPromiseRejectionTracker
JS_SetInterruptHandler
JS_SetMaxStackSize
JS_SetMemoryLimit
JS_SetModuleExport
JS_SetModuleExportList
JS_SetModuleLoaderFunc
JS_SetOpaque
JS_SetPropertyFunctionList
JS_SetPropertyInt64
JS_SetPropertyInternal
JS_SetPropertyStr
JS_SetPropertyUint32
JS_SetPrototype
JS_SetRuntimeInfo
JS_SetRuntimeOpaque
JS_SetSharedArrayBufferFunctions
JS_SetUncatchableError
JS_Throw
JS_ThrowInternalError
JS_ThrowOutOfMemory
JS_ThrowRangeError
JS_ThrowReferenceError
JS_ThrowSyntaxError
JS_ThrowTypeError
JS_ToBigInt64
JS_ToBool
JS_ToCStringLen2
JS_ToFloat64
JS_ToIndex
JS_ToInt32
JS_ToInt32Clamp
JS_ToInt32Sat
JS_ToInt64
JS_ToInt64Clamp
JS_ToInt64Ext
JS_ToInt64Sat
JS_ToPropertyKey
JS_ToString
JS_ToStringInternal
JS_ValueToAtom
JS_WriteObject
JS_WriteObject2
__JS_FreeValue
__JS_FreeValueRT
js_free
js_free_rt
js_init_module_os
js_init_module_std
js_load_file
js_malloc
js_malloc_rt
js_malloc_usable_size
js_malloc_usable_size_rt
js_mallocz
js_mallocz_rt
js_module_loader
js_module_set_import_meta
js_parse_error
js_realloc
js_realloc2
js_realloc_rt
js_std_add_helpers
js_std_dump_error
js_std_eval_binary
js_std_free_handlers
js_std_init_handlers
js_std_loop
js_std_promise_rejection_tracker
js_strdup
js_string_codePointRange
js_strndup

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,100 @@
/*
* Linux klist like system
*
* Copyright (c) 2016-2017 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef LIST_H
#define LIST_H
#ifndef NULL
#include <stddef.h>
#endif
struct list_head {
struct list_head *prev;
struct list_head *next;
};
#define LIST_HEAD_INIT(el) { &(el), &(el) }
/* return the pointer of type 'type *' containing 'el' as field 'member' */
#define list_entry(el, type, member) \
((type *)((uint8_t *)(el) - offsetof(type, member)))
static inline void init_list_head(struct list_head *head)
{
head->prev = head;
head->next = head;
}
/* insert 'el' between 'prev' and 'next' */
static inline void __list_add(struct list_head *el,
struct list_head *prev, struct list_head *next)
{
prev->next = el;
el->prev = prev;
el->next = next;
next->prev = el;
}
/* add 'el' at the head of the list 'head' (= after element head) */
static inline void list_add(struct list_head *el, struct list_head *head)
{
__list_add(el, head, head->next);
}
/* add 'el' at the end of the list 'head' (= before element head) */
static inline void list_add_tail(struct list_head *el, struct list_head *head)
{
__list_add(el, head->prev, head);
}
static inline void list_del(struct list_head *el)
{
struct list_head *prev, *next;
prev = el->prev;
next = el->next;
prev->next = next;
next->prev = prev;
el->prev = NULL; /* fail safe */
el->next = NULL; /* fail safe */
}
static inline int list_empty(struct list_head *el)
{
return el->next == el;
}
#define list_for_each(el, head) \
for(el = (head)->next; el != (head); el = el->next)
#define list_for_each_safe(el, el1, head) \
for(el = (head)->next, el1 = el->next; el != (head); \
el = el1, el1 = el->next)
#define list_for_each_prev(el, head) \
for(el = (head)->prev; el != (head); el = el->prev)
#define list_for_each_prev_safe(el, el1, head) \
for(el = (head)->prev, el1 = el->prev; el != (head); \
el = el1, el1 = el->prev)
#endif /* LIST_H */

View File

@@ -0,0 +1,58 @@
/*
* QuickJS C library
*
* Copyright (c) 2017-2018 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef QUICKJS_LIBC_H
#define QUICKJS_LIBC_H
#include <stdio.h>
#include <stdlib.h>
#include "quickjs.h"
#ifdef __cplusplus
extern "C" {
#endif
JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name);
JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name);
void js_std_add_helpers(JSContext *ctx, int argc, char **argv);
void js_std_loop(JSContext *ctx);
void js_std_init_handlers(JSRuntime *rt);
void js_std_free_handlers(JSRuntime *rt);
void js_std_dump_error(JSContext *ctx);
uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename);
int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val,
JS_BOOL use_realpath, JS_BOOL is_main);
JSModuleDef *js_module_loader(JSContext *ctx,
const char *module_name, void *opaque);
void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len,
int flags);
void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
JSValueConst reason,
JS_BOOL is_handled, void *opaque);
#ifdef __cplusplus
} /* extern "C" { */
#endif
#endif /* QUICKJS_LIBC_H */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff