├── .firebaserc
├── .gitignore
├── .metadata
├── LICENSE
├── README.md
├── analysis_options.yaml
├── assets
├── 2.0x
│ └── logo.png
├── background.jpg
├── logo.png
└── roms
│ ├── 82s123.7f
│ ├── 82s126.1m
│ ├── 82s126.3m
│ ├── 82s126.4a
│ ├── pacman.5e
│ ├── pacman.5f
│ ├── pacman.6e
│ ├── pacman.6f
│ ├── pacman.6h
│ └── pacman.6j
├── audio_proc.def
├── audio_proc.dll
├── audio_proc
├── .gitignore
├── audio_proc.c
├── compile.bat
├── dart_api_dl.c
└── include
│ ├── dart_api.h
│ ├── dart_api_dl.h
│ ├── dart_api_dl_impl.h
│ ├── dart_native_api.h
│ └── dart_version.h
├── example
├── StarWars60.mp3
└── sound_out.dart
├── firebase.json
├── lib
├── assets.dart
├── config
│ ├── configure.dart
│ ├── configure_non_web.dart
│ └── configure_web.dart
├── display
│ ├── display.dart
│ ├── display_app.dart
│ └── display_web.dart
├── emulator.dart
├── main.dart
├── utils
│ └── platform_view_web.dart
└── win32_sound
│ ├── constants.dart
│ ├── functions.dart
│ ├── structs.dart
│ └── win32_sound.dart
├── pubspec.lock
├── pubspec.yaml
├── web
├── favicon.ico
├── favicon.png
├── icons
│ ├── pac-man-128x128.png
│ ├── pac-man-256x256.png
│ └── pac-man-512x512.png
├── index.html
└── manifest.json
└── windows
├── .gitignore
├── CMakeLists.txt
├── flutter
├── .template_version
├── CMakeLists.txt
├── generated_plugin_registrant.cc
├── generated_plugin_registrant.h
└── generated_plugins.cmake
└── runner
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── resources
└── app_icon.ico
├── run_loop.cpp
├── run_loop.h
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
├── win32_window.h
├── window_configuration.cpp
└── window_configuration.h
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "flutter-community-website"
4 | },
5 | "targets": {
6 | "flutter-community-website": {
7 | "hosting": {
8 | "pacman": [
9 | "pacman-flutter-community"
10 | ]
11 | }
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Web related
35 | lib/generated_plugin_registrant.dart
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
43 | # Exceptions to above rules.
44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
45 |
46 | .firebase/
47 | release/
48 | pacman_release.zip
49 |
50 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "8495dee1fd4aacbe9de707e7581203232f591b2f"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f
17 | base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f
18 | - platform: web
19 | create_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f
20 | base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2021 Simon Lightfoot (simon@devangels.london)
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Pacman Emulator in Flutter
4 |
5 | Icons from: https://www.classicgaming.cc/classics/pac-man/icons
6 |
7 | Try now: https://pacman.fluttercommunity.dev/
8 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 | constant_identifier_names: false
28 |
29 | # Additional information about this file can be found at
30 | # https://dart.dev/guides/language/analysis-options
31 |
--------------------------------------------------------------------------------
/assets/2.0x/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/assets/2.0x/logo.png
--------------------------------------------------------------------------------
/assets/background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/assets/background.jpg
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/assets/logo.png
--------------------------------------------------------------------------------
/assets/roms/82s123.7f:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/assets/roms/82s123.7f
--------------------------------------------------------------------------------
/assets/roms/82s126.1m:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/assets/roms/82s126.3m:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/roms/82s126.4a:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/roms/pacman.5e:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/assets/roms/pacman.5e
--------------------------------------------------------------------------------
/assets/roms/pacman.5f:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/assets/roms/pacman.5f
--------------------------------------------------------------------------------
/assets/roms/pacman.6e:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/assets/roms/pacman.6e
--------------------------------------------------------------------------------
/assets/roms/pacman.6f:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/assets/roms/pacman.6f
--------------------------------------------------------------------------------
/assets/roms/pacman.6h:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/assets/roms/pacman.6h
--------------------------------------------------------------------------------
/assets/roms/pacman.6j:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/assets/roms/pacman.6j
--------------------------------------------------------------------------------
/audio_proc.def:
--------------------------------------------------------------------------------
1 | LIBRARY audio_proc.dll
2 |
3 | EXPORTS
4 | AudioProc
5 | InitDartApiDL
6 |
--------------------------------------------------------------------------------
/audio_proc.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/audio_proc.dll
--------------------------------------------------------------------------------
/audio_proc/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE Files
2 | .idea/
3 | *.iml
4 |
--------------------------------------------------------------------------------
/audio_proc/audio_proc.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | struct AudioProcResult {
6 | HANDLE hWaveOut;
7 | UINT uMsg;
8 | DWORD_PTR dwParam1;
9 | DWORD_PTR dwParam2;
10 | };
11 |
12 | extern intptr_t InitDartApiDL(void* data) __attribute__((dllexport)) {
13 | return Dart_InitializeApiDL(data);
14 | }
15 |
16 | extern void CALLBACK AudioProc(HANDLE hWaveOut, UINT uMsg, DWORD_PTR dwInstance,
17 | DWORD_PTR dwParam1, DWORD_PTR dwParam2) __attribute__((dllexport))
18 | {
19 | Dart_Port_DL nativePortId = (Dart_Port_DL)dwInstance;
20 | struct AudioProcResult *result = HeapAlloc(GetProcessHeap(), 0, sizeof(struct AudioProcResult));
21 | result->hWaveOut = hWaveOut;
22 | result->uMsg = uMsg;
23 | result->dwParam1 = dwParam1;
24 | result->dwParam2 = dwParam2;
25 | const bool postResult = Dart_PostInteger_DL(nativePortId, (int64_t)result);
26 | if (!postResult) {
27 | printf("FATAL: Posting message to port failed.");
28 | abort();
29 | }
30 | return;
31 | }
32 |
--------------------------------------------------------------------------------
/audio_proc/compile.bat:
--------------------------------------------------------------------------------
1 | c:\tcc\tcc -m64 -I. -shared dart_api_dl.c audio_proc.c -o ..\audio_proc.dll
2 |
--------------------------------------------------------------------------------
/audio_proc/dart_api_dl.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
3 | * for details. All rights reserved. Use of this source code is governed by a
4 | * BSD-style license that can be found in the LICENSE file.
5 | */
6 |
7 | #include "include/dart_api_dl.h" /* NOLINT */
8 | #include "include/dart_version.h" /* NOLINT */
9 | #include "include/dart_api_dl_impl.h" /* NOLINT */
10 |
11 | #include
12 |
13 | #define DART_API_DL_DEFINITIONS(name, R, A) name##_Type name##_DL = NULL;
14 |
15 | DART_API_ALL_DL_SYMBOLS(DART_API_DL_DEFINITIONS)
16 |
17 | #undef DART_API_DL_DEFINITIONS
18 |
19 | typedef void* DartApiEntry_function;
20 |
21 | DartApiEntry_function FindFunctionPointer(const DartApiEntry* entries,
22 | const char* name) {
23 | while (entries->name != NULL) {
24 | if (strcmp(entries->name, name) == 0) return entries->function;
25 | entries++;
26 | }
27 | return NULL;
28 | }
29 |
30 | intptr_t Dart_InitializeApiDL(void* data) {
31 | DartApi* dart_api_data = (DartApi*)data;
32 |
33 | if (dart_api_data->major != DART_API_DL_MAJOR_VERSION) {
34 | // If the DartVM we're running on does not have the same version as this
35 | // file was compiled against, refuse to initialize. The symbols are not
36 | // compatible.
37 | return -1;
38 | }
39 | // Minor versions are allowed to be different.
40 | // If the DartVM has a higher minor version, it will provide more symbols
41 | // than we initialize here.
42 | // If the DartVM has a lower minor version, it will not provide all symbols.
43 | // In that case, we leave the missing symbols un-initialized. Those symbols
44 | // should not be used by the Dart and native code. The client is responsible
45 | // for checking the minor version number himself based on which symbols it
46 | // is using.
47 | // (If we would error out on this case, recompiling native code against a
48 | // newer SDK would break all uses on older SDKs, which is too strict.)
49 |
50 | const DartApiEntry* dart_api_function_pointers = dart_api_data->functions;
51 |
52 | #define DART_API_DL_INIT(name, R, A) \
53 | name##_DL = \
54 | (name##_Type)(FindFunctionPointer(dart_api_function_pointers, #name));
55 | DART_API_ALL_DL_SYMBOLS(DART_API_DL_INIT)
56 | #undef DART_API_DL_INIT
57 |
58 | return 0;
59 | }
60 |
--------------------------------------------------------------------------------
/audio_proc/include/dart_api_dl.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
3 | * for details. All rights reserved. Use of this source code is governed by a
4 | * BSD-style license that can be found in the LICENSE file.
5 | */
6 |
7 | #ifndef RUNTIME_INCLUDE_DART_API_DL_H_
8 | #define RUNTIME_INCLUDE_DART_API_DL_H_
9 |
10 | #include "dart_api.h" /* NOLINT */
11 | #include "dart_native_api.h" /* NOLINT */
12 |
13 | /** \mainpage Dynamically Linked Dart API
14 | *
15 | * This exposes a subset of symbols from dart_api.h and dart_native_api.h
16 | * available in every Dart embedder through dynamic linking.
17 | *
18 | * All symbols are postfixed with _DL to indicate that they are dynamically
19 | * linked and to prevent conflicts with the original symbol.
20 | *
21 | * Link `dart_api_dl.c` file into your library and invoke
22 | * `Dart_InitializeApiDL` with `NativeApi.initializeApiDLData`.
23 | */
24 |
25 | DART_EXPORT intptr_t Dart_InitializeApiDL(void* data);
26 |
27 | // ============================================================================
28 | // IMPORTANT! Never update these signatures without properly updating
29 | // DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION.
30 | //
31 | // Verbatim copy of `dart_native_api.h` and `dart_api.h` symbol names and types
32 | // to trigger compile-time errors if the sybols in those files are updated
33 | // without updating these.
34 | //
35 | // Function return and argument types, and typedefs are carbon copied. Structs
36 | // are typechecked nominally in C/C++, so they are not copied, instead a
37 | // comment is added to their definition.
38 | typedef int64_t Dart_Port_DL;
39 |
40 | typedef void (*Dart_NativeMessageHandler_DL)(Dart_Port_DL dest_port_id,
41 | Dart_CObject* message);
42 |
43 | // dart_native_api.h symbols can be called on any thread.
44 | #define DART_NATIVE_API_DL_SYMBOLS(F) \
45 | /***** dart_native_api.h *****/ \
46 | /* Dart_Port */ \
47 | F(Dart_PostCObject, bool, (Dart_Port_DL port_id, Dart_CObject * message)) \
48 | F(Dart_PostInteger, bool, (Dart_Port_DL port_id, int64_t message)) \
49 | F(Dart_NewNativePort, Dart_Port_DL, \
50 | (const char* name, Dart_NativeMessageHandler_DL handler, \
51 | bool handle_concurrently)) \
52 | F(Dart_CloseNativePort, bool, (Dart_Port_DL native_port_id))
53 |
54 | // dart_api.h symbols can only be called on Dart threads.
55 | #define DART_API_DL_SYMBOLS(F) \
56 | /***** dart_api.h *****/ \
57 | /* Errors */ \
58 | F(Dart_IsError, bool, (Dart_Handle handle)) \
59 | F(Dart_IsApiError, bool, (Dart_Handle handle)) \
60 | F(Dart_IsUnhandledExceptionError, bool, (Dart_Handle handle)) \
61 | F(Dart_IsCompilationError, bool, (Dart_Handle handle)) \
62 | F(Dart_IsFatalError, bool, (Dart_Handle handle)) \
63 | F(Dart_GetError, const char*, (Dart_Handle handle)) \
64 | F(Dart_ErrorHasException, bool, (Dart_Handle handle)) \
65 | F(Dart_ErrorGetException, Dart_Handle, (Dart_Handle handle)) \
66 | F(Dart_ErrorGetStackTrace, Dart_Handle, (Dart_Handle handle)) \
67 | F(Dart_NewApiError, Dart_Handle, (const char* error)) \
68 | F(Dart_NewCompilationError, Dart_Handle, (const char* error)) \
69 | F(Dart_NewUnhandledExceptionError, Dart_Handle, (Dart_Handle exception)) \
70 | F(Dart_PropagateError, void, (Dart_Handle handle)) \
71 | /* Dart_Handle, Dart_PersistentHandle, Dart_WeakPersistentHandle */ \
72 | F(Dart_HandleFromPersistent, Dart_Handle, (Dart_PersistentHandle object)) \
73 | F(Dart_HandleFromWeakPersistent, Dart_Handle, \
74 | (Dart_WeakPersistentHandle object)) \
75 | F(Dart_NewPersistentHandle, Dart_PersistentHandle, (Dart_Handle object)) \
76 | F(Dart_SetPersistentHandle, void, \
77 | (Dart_PersistentHandle obj1, Dart_Handle obj2)) \
78 | F(Dart_DeletePersistentHandle, void, (Dart_PersistentHandle object)) \
79 | F(Dart_NewWeakPersistentHandle, Dart_WeakPersistentHandle, \
80 | (Dart_Handle object, void* peer, intptr_t external_allocation_size, \
81 | Dart_HandleFinalizer callback)) \
82 | F(Dart_DeleteWeakPersistentHandle, void, (Dart_WeakPersistentHandle object)) \
83 | F(Dart_UpdateExternalSize, void, \
84 | (Dart_WeakPersistentHandle object, intptr_t external_allocation_size)) \
85 | F(Dart_NewFinalizableHandle, Dart_FinalizableHandle, \
86 | (Dart_Handle object, void* peer, intptr_t external_allocation_size, \
87 | Dart_HandleFinalizer callback)) \
88 | F(Dart_DeleteFinalizableHandle, void, \
89 | (Dart_FinalizableHandle object, Dart_Handle strong_ref_to_object)) \
90 | F(Dart_UpdateFinalizableExternalSize, void, \
91 | (Dart_FinalizableHandle object, Dart_Handle strong_ref_to_object, \
92 | intptr_t external_allocation_size)) \
93 | /* Dart_Port */ \
94 | F(Dart_Post, bool, (Dart_Port_DL port_id, Dart_Handle object)) \
95 | F(Dart_NewSendPort, Dart_Handle, (Dart_Port_DL port_id)) \
96 | F(Dart_SendPortGetId, Dart_Handle, \
97 | (Dart_Handle port, Dart_Port_DL * port_id)) \
98 | /* Scopes */ \
99 | F(Dart_EnterScope, void, (void)) \
100 | F(Dart_ExitScope, void, (void))
101 |
102 | #define DART_API_ALL_DL_SYMBOLS(F) \
103 | DART_NATIVE_API_DL_SYMBOLS(F) \
104 | DART_API_DL_SYMBOLS(F)
105 | // IMPORTANT! Never update these signatures without properly updating
106 | // DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION.
107 | //
108 | // End of verbatim copy.
109 | // ============================================================================
110 |
111 | // Copy of definition of DART_EXPORT without 'used' attribute.
112 | //
113 | // The 'used' attribute cannot be used with DART_API_ALL_DL_SYMBOLS because
114 | // they are not function declarations, but variable declarations with a
115 | // function pointer type.
116 | //
117 | // The function pointer variables are initialized with the addresses of the
118 | // functions in the VM. If we were to use function declarations instead, we
119 | // would need to forward the call to the VM adding indirection.
120 | #if defined(__CYGWIN__)
121 | #error Tool chain and platform not supported.
122 | #elif defined(_WIN32)
123 | #if defined(DART_SHARED_LIB)
124 | #define DART_EXPORT_DL DART_EXTERN_C __declspec(dllexport)
125 | #else
126 | #define DART_EXPORT_DL DART_EXTERN_C
127 | #endif
128 | #else
129 | #if __GNUC__ >= 4
130 | #if defined(DART_SHARED_LIB)
131 | #define DART_EXPORT_DL DART_EXTERN_C __attribute__((visibility("default")))
132 | #else
133 | #define DART_EXPORT_DL DART_EXTERN_C
134 | #endif
135 | #else
136 | #error Tool chain not supported.
137 | #endif
138 | #endif
139 |
140 | #define DART_API_DL_DECLARATIONS(name, R, A) \
141 | typedef R(*name##_Type) A; \
142 | DART_EXPORT_DL name##_Type name##_DL;
143 |
144 | DART_API_ALL_DL_SYMBOLS(DART_API_DL_DECLARATIONS)
145 |
146 | #undef DART_API_DL_DECLARATIONS
147 |
148 | #undef DART_EXPORT_DL
149 |
150 | #endif /* RUNTIME_INCLUDE_DART_API_DL_H_ */ /* NOLINT */
151 |
--------------------------------------------------------------------------------
/audio_proc/include/dart_api_dl_impl.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
3 | * for details. All rights reserved. Use of this source code is governed by a
4 | * BSD-style license that can be found in the LICENSE file.
5 | */
6 |
7 | #ifndef RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_
8 | #define RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_
9 |
10 | typedef struct {
11 | const char* name;
12 | void (*function)(void);
13 | } DartApiEntry;
14 |
15 | typedef struct {
16 | const int major;
17 | const int minor;
18 | const DartApiEntry* const functions;
19 | } DartApi;
20 |
21 | #endif /* RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_ */ /* NOLINT */
22 |
--------------------------------------------------------------------------------
/audio_proc/include/dart_native_api.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
3 | * for details. All rights reserved. Use of this source code is governed by a
4 | * BSD-style license that can be found in the LICENSE file.
5 | */
6 |
7 | #ifndef RUNTIME_INCLUDE_DART_NATIVE_API_H_
8 | #define RUNTIME_INCLUDE_DART_NATIVE_API_H_
9 |
10 | #include "dart_api.h" /* NOLINT */
11 |
12 | /*
13 | * ==========================================
14 | * Message sending/receiving from native code
15 | * ==========================================
16 | */
17 |
18 | /**
19 | * A Dart_CObject is used for representing Dart objects as native C
20 | * data outside the Dart heap. These objects are totally detached from
21 | * the Dart heap. Only a subset of the Dart objects have a
22 | * representation as a Dart_CObject.
23 | *
24 | * The string encoding in the 'value.as_string' is UTF-8.
25 | *
26 | * All the different types from dart:typed_data are exposed as type
27 | * kTypedData. The specific type from dart:typed_data is in the type
28 | * field of the as_typed_data structure. The length in the
29 | * as_typed_data structure is always in bytes.
30 | *
31 | * The data for kTypedData is copied on message send and ownership remains with
32 | * the caller. The ownership of data for kExternalTyped is passed to the VM on
33 | * message send and returned when the VM invokes the
34 | * Dart_HandleFinalizer callback; a non-NULL callback must be provided.
35 | *
36 | * Note that Dart_CObject_kNativePointer is intended for internal use by
37 | * dart:io implementation and has no connection to dart:ffi Pointer class.
38 | * It represents a pointer to a native resource of a known type.
39 | * The receiving side will only see this pointer as an integer and will not
40 | * see the specified finalizer.
41 | * The specified finalizer will only be invoked if the message is not delivered.
42 | */
43 | typedef enum {
44 | Dart_CObject_kNull = 0,
45 | Dart_CObject_kBool,
46 | Dart_CObject_kInt32,
47 | Dart_CObject_kInt64,
48 | Dart_CObject_kDouble,
49 | Dart_CObject_kString,
50 | Dart_CObject_kArray,
51 | Dart_CObject_kTypedData,
52 | Dart_CObject_kExternalTypedData,
53 | Dart_CObject_kSendPort,
54 | Dart_CObject_kCapability,
55 | Dart_CObject_kNativePointer,
56 | Dart_CObject_kUnsupported,
57 | Dart_CObject_kNumberOfTypes
58 | } Dart_CObject_Type;
59 |
60 | typedef struct _Dart_CObject {
61 | Dart_CObject_Type type;
62 | union {
63 | bool as_bool;
64 | int32_t as_int32;
65 | int64_t as_int64;
66 | double as_double;
67 | char* as_string;
68 | struct {
69 | Dart_Port id;
70 | Dart_Port origin_id;
71 | } as_send_port;
72 | struct {
73 | int64_t id;
74 | } as_capability;
75 | struct {
76 | intptr_t length;
77 | struct _Dart_CObject** values;
78 | } as_array;
79 | struct {
80 | Dart_TypedData_Type type;
81 | intptr_t length; /* in elements, not bytes */
82 | uint8_t* values;
83 | } as_typed_data;
84 | struct {
85 | Dart_TypedData_Type type;
86 | intptr_t length; /* in elements, not bytes */
87 | uint8_t* data;
88 | void* peer;
89 | Dart_HandleFinalizer callback;
90 | } as_external_typed_data;
91 | struct {
92 | intptr_t ptr;
93 | intptr_t size;
94 | Dart_HandleFinalizer callback;
95 | } as_native_pointer;
96 | } value;
97 | } Dart_CObject;
98 | // This struct is versioned by DART_API_DL_MAJOR_VERSION, bump the version when
99 | // changing this struct.
100 |
101 | /**
102 | * Posts a message on some port. The message will contain the Dart_CObject
103 | * object graph rooted in 'message'.
104 | *
105 | * While the message is being sent the state of the graph of Dart_CObject
106 | * structures rooted in 'message' should not be accessed, as the message
107 | * generation will make temporary modifications to the data. When the message
108 | * has been sent the graph will be fully restored.
109 | *
110 | * If true is returned, the message was enqueued, and finalizers for external
111 | * typed data will eventually run, even if the receiving isolate shuts down
112 | * before processing the message. If false is returned, the message was not
113 | * enqueued and ownership of external typed data in the message remains with the
114 | * caller.
115 | *
116 | * This function may be called on any thread when the VM is running (that is,
117 | * after Dart_Initialize has returned and before Dart_Cleanup has been called).
118 | *
119 | * \param port_id The destination port.
120 | * \param message The message to send.
121 | *
122 | * \return True if the message was posted.
123 | */
124 | DART_EXPORT bool Dart_PostCObject(Dart_Port port_id, Dart_CObject* message);
125 |
126 | /**
127 | * Posts a message on some port. The message will contain the integer 'message'.
128 | *
129 | * \param port_id The destination port.
130 | * \param message The message to send.
131 | *
132 | * \return True if the message was posted.
133 | */
134 | DART_EXPORT bool Dart_PostInteger(Dart_Port port_id, int64_t message);
135 |
136 | /**
137 | * A native message handler.
138 | *
139 | * This handler is associated with a native port by calling
140 | * Dart_NewNativePort.
141 | *
142 | * The message received is decoded into the message structure. The
143 | * lifetime of the message data is controlled by the caller. All the
144 | * data references from the message are allocated by the caller and
145 | * will be reclaimed when returning to it.
146 | */
147 | typedef void (*Dart_NativeMessageHandler)(Dart_Port dest_port_id,
148 | Dart_CObject* message);
149 |
150 | /**
151 | * Creates a new native port. When messages are received on this
152 | * native port, then they will be dispatched to the provided native
153 | * message handler.
154 | *
155 | * \param name The name of this port in debugging messages.
156 | * \param handler The C handler to run when messages arrive on the port.
157 | * \param handle_concurrently Is it okay to process requests on this
158 | * native port concurrently?
159 | *
160 | * \return If successful, returns the port id for the native port. In
161 | * case of error, returns ILLEGAL_PORT.
162 | */
163 | DART_EXPORT Dart_Port Dart_NewNativePort(const char* name,
164 | Dart_NativeMessageHandler handler,
165 | bool handle_concurrently);
166 | /* TODO(turnidge): Currently handle_concurrently is ignored. */
167 |
168 | /**
169 | * Closes the native port with the given id.
170 | *
171 | * The port must have been allocated by a call to Dart_NewNativePort.
172 | *
173 | * \param native_port_id The id of the native port to close.
174 | *
175 | * \return Returns true if the port was closed successfully.
176 | */
177 | DART_EXPORT bool Dart_CloseNativePort(Dart_Port native_port_id);
178 |
179 | /*
180 | * ==================
181 | * Verification Tools
182 | * ==================
183 | */
184 |
185 | /**
186 | * Forces all loaded classes and functions to be compiled eagerly in
187 | * the current isolate..
188 | *
189 | * TODO(turnidge): Document.
190 | */
191 | DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_CompileAll(void);
192 |
193 | /**
194 | * Finalizes all classes.
195 | */
196 | DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_FinalizeAllClasses(void);
197 |
198 | /* This function is intentionally undocumented.
199 | *
200 | * It should not be used outside internal tests.
201 | */
202 | DART_EXPORT void* Dart_ExecuteInternalCommand(const char* command, void* arg);
203 |
204 | #endif /* INCLUDE_DART_NATIVE_API_H_ */ /* NOLINT */
205 |
--------------------------------------------------------------------------------
/audio_proc/include/dart_version.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
3 | * for details. All rights reserved. Use of this source code is governed by a
4 | * BSD-style license that can be found in the LICENSE file.
5 | */
6 |
7 | #ifndef RUNTIME_INCLUDE_DART_VERSION_H_
8 | #define RUNTIME_INCLUDE_DART_VERSION_H_
9 |
10 | // On breaking changes the major version is increased.
11 | // On backwards compatible changes the minor version is increased.
12 | // The versioning covers the symbols exposed in dart_api_dl.h
13 | #define DART_API_DL_MAJOR_VERSION 2
14 | #define DART_API_DL_MINOR_VERSION 0
15 |
16 | #endif /* RUNTIME_INCLUDE_DART_VERSION_H_ */ /* NOLINT */
17 |
--------------------------------------------------------------------------------
/example/StarWars60.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/example/StarWars60.mp3
--------------------------------------------------------------------------------
/example/sound_out.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:io';
3 | import 'dart:math' as math;
4 |
5 | import 'package:pacman/win32_sound/win32_sound.dart';
6 |
7 | // win32 lib issues
8 | //
9 | // WAVEHDR.lpData is Utf8 when it should be Uint8
10 | // WAVEOUTCAPS._szPname should be Utf16 and name is null terminated
11 |
12 | Future main() async {
13 | final count = WaveOut.deviceCount;
14 | for (var i = 0; i < count; i++) {
15 | final device = WaveOut(i);
16 | print('${device.deviceId}: ${device.name} -> ${device.supportsVolumeControl}');
17 | device.dispose();
18 | }
19 |
20 | final data = File('example/StarWars60.mp3').readAsBytesSync().buffer.asUint8List(0x2C);
21 | final device = WaveOut.defaultDevice;
22 | try {
23 | device.openMp3();
24 | device.volume = 0.5;
25 | for (var i = 0; i < data.length; i += 2048) {
26 | await device.write(data.sublist(i, math.min(i + 2048, data.length)));
27 | //print(
28 | // 'pos:${device.position}: '
29 | // '${device.volume.toStringAsFixed(2)} '
30 | // '${device.playbackRate.toStringAsFixed(2)}',
31 | //);
32 | }
33 | await device.wait();
34 | } finally {
35 | await device.dispose();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "target": "pacman",
4 | "public": "build/web",
5 | "ignore": [
6 | "firebase.json",
7 | "**/.*",
8 | "**/node_modules/**"
9 | ],
10 | "rewrites": [
11 | {
12 | "source": "**",
13 | "destination": "/index.html"
14 | }
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/assets.dart:
--------------------------------------------------------------------------------
1 |
2 | class Assets {
3 | const Assets._();
4 |
5 | static const backgroundImage = 'assets/background.jpg';
6 | static const logoImage = 'assets/logo.png';
7 |
8 | /// Pac-Man (Midway) - ROM Chip 1
9 | /// File: pacman.6e, 4096 bytes
10 | static const rom_data_6e = 'assets/roms/pacman.6e';
11 |
12 | /// Pac-Man (Midway) - ROM Chip 2
13 | /// File: pacman.6f, 4096 bytes
14 | static const rom_data_6f = 'assets/roms/pacman.6f';
15 |
16 | /// Pac-Man (Midway) - ROM Chip 3
17 | /// File: pacman.6h, 4096 bytes
18 | static const rom_data_6h = 'assets/roms/pacman.6h';
19 |
20 | /// Pac-Man (Midway) - ROM Chip 4
21 | /// File: pacman.6j, 4096 bytes
22 | static const rom_data_6j = 'assets/roms/pacman.6j';
23 |
24 | /// Game Char Set
25 | /// File: pacman.5e, 4096 bytes
26 | static const rom_data_5e = 'assets/roms/pacman.5e';
27 |
28 | /// Game Sprite Set
29 | /// File: pacman.5f, 4096 bytes
30 | static const rom_data_5f = 'assets/roms/pacman.5f';
31 |
32 | /// Namco Palette Definition
33 | /// File: 82s123.7f, 32 bytes
34 | static const palette_data = 'assets/roms/82s123.7f';
35 |
36 | /// Namco Color Definition
37 | /// File: 82s126.4a, 256 bytes
38 | static const color_data = 'assets/roms/82s126.4a';
39 |
40 | /// Namco Sound Waveform Data
41 | /// File: 82s126.1m, 256 bytes
42 | static const sound_data = 'assets/roms/82s126.1m';
43 | }
44 |
--------------------------------------------------------------------------------
/lib/config/configure.dart:
--------------------------------------------------------------------------------
1 | export 'configure_non_web.dart' if (dart.library.html) 'configure_web.dart';
2 |
--------------------------------------------------------------------------------
/lib/config/configure_non_web.dart:
--------------------------------------------------------------------------------
1 | void configureApp() {
2 | // No-op for non-web builds.
3 | }
4 |
--------------------------------------------------------------------------------
/lib/config/configure_web.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_web_plugins/flutter_web_plugins.dart';
2 |
3 | void configureApp() {
4 | setUrlStrategy(PathUrlStrategy());
5 | }
6 |
--------------------------------------------------------------------------------
/lib/display/display.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:pacman/emulator.dart';
3 |
4 | import 'display_app.dart' if (dart.library.html) 'display_web.dart' as display;
5 |
6 | abstract class Display extends StatefulWidget {
7 | factory Display({
8 | Key? key,
9 | required ArcadeMachineEmu emulator,
10 | }) {
11 | return display.Display(
12 | key: key,
13 | emulator: emulator,
14 | );
15 | }
16 |
17 | @override
18 | DisplayState createState();
19 | }
20 |
21 | abstract class DisplayState extends State {
22 | void outputVideoFrame(Duration elapsed, Duration delta);
23 |
24 | void outputAudioFrame(Duration elapsed, Duration delta);
25 | }
26 |
--------------------------------------------------------------------------------
/lib/display/display_app.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:typed_data';
3 | import 'dart:ui' as ui;
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:pacman/display/display.dart' as display;
7 | import 'package:pacman/emulator.dart';
8 | import 'package:pacman/win32_sound/win32_sound.dart';
9 |
10 | class Display extends StatefulWidget implements display.Display {
11 | const Display({
12 | super.key,
13 | required this.emulator,
14 | });
15 |
16 | final ArcadeMachineEmu emulator;
17 |
18 | @override
19 | display.DisplayState createState() => _DisplayState();
20 | }
21 |
22 | class _DisplayState extends display.DisplayState {
23 | final _video = Uint32List(VIDEO_WIDTH * VIDEO_HEIGHT);
24 | final _frame = ValueNotifier(null);
25 | final _audioFrame = Float32List(44100 ~/ 60);
26 |
27 | double _frameCount = 0.0;
28 | Duration _elapsed = Duration.zero;
29 | Completer? _completer;
30 |
31 | double get fps => (_frameCount / (_elapsed.inMicroseconds / Duration.microsecondsPerSecond));
32 |
33 | final WaveOut device = WaveOut(WAVE_MAPPER, bufferCount: 8, bufferSizeInBytes: 4 * (44100 ~/ 60));
34 |
35 | @override
36 | void initState() {
37 | super.initState();
38 | if (device.isFormatSupported(SupportedWaveFormat.formatFloat44kM32b)) {
39 | device.open(SupportedWaveFormat.formatFloat44kM32b);
40 | device.volume = 0.5;
41 | }
42 | }
43 |
44 | @override
45 | void dispose() {
46 | device.dispose();
47 | super.dispose();
48 | }
49 |
50 | @override
51 | void outputVideoFrame(Duration elapsed, Duration delta) {
52 | _elapsed = elapsed;
53 | if (!widget.emulator.paused) {
54 | if (_completer?.isCompleted ?? true) {
55 | final completer = _completer = Completer();
56 | _render().then(
57 | (_) {
58 | completer.complete();
59 | _frameCount++;
60 | },
61 | onError: completer.completeError,
62 | );
63 | }
64 | } else {
65 | _frameCount++;
66 | // ignore: invalid_use_of_visible_for_testing_member, invalid_use_of_protected_member
67 | _frame.notifyListeners();
68 | }
69 | }
70 |
71 | @override
72 | void outputAudioFrame(Duration elapsed, Duration delta) {
73 | widget.emulator.renderSound(_audioFrame, 44100);
74 | device.write(_audioFrame.buffer.asUint8List());
75 | }
76 |
77 | Future _render() async {
78 | widget.emulator.renderVideo(_video);
79 | final completer = Completer();
80 | ui.decodeImageFromPixels(_video.buffer.asUint8List(), VIDEO_WIDTH, VIDEO_HEIGHT,
81 | ui.PixelFormat.rgba8888, completer.complete);
82 | _frame.value = await completer.future;
83 | }
84 |
85 | @override
86 | Widget build(BuildContext context) {
87 | return ValueListenableBuilder(
88 | valueListenable: _frame,
89 | builder: (BuildContext context, ui.Image? image, Widget? child) {
90 | return Stack(
91 | children: [
92 | if (image != null)
93 | Positioned.fill(
94 | child: RepaintBoundary(
95 | child: FittedBox(
96 | fit: BoxFit.contain,
97 | child: RawImage(
98 | image: image,
99 | filterQuality: FilterQuality.none,
100 | ),
101 | ),
102 | ),
103 | ),
104 | Positioned(
105 | left: 8.0,
106 | top: 8.0,
107 | child: RepaintBoundary(
108 | child: SafeArea(
109 | child: Text(
110 | fps.toStringAsFixed(2),
111 | style: const TextStyle(
112 | fontSize: 16.0,
113 | color: Colors.red,
114 | ),
115 | ),
116 | ),
117 | ),
118 | ),
119 | ],
120 | );
121 | },
122 | );
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/lib/display/display_web.dart:
--------------------------------------------------------------------------------
1 | import 'dart:js_interop';
2 | import 'dart:typed_data';
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/rendering.dart';
6 | import 'package:pacman/display/display.dart' as display;
7 | import 'package:pacman/emulator.dart';
8 | import 'package:pacman/utils/platform_view_web.dart';
9 | import 'package:web/web.dart'
10 | show
11 | AudioContext,
12 | AudioProcessingEvent,
13 | CanvasRenderingContext2D,
14 | HTMLCanvasElement,
15 | ImageData,
16 | ScriptProcessorNode;
17 |
18 | class Display extends StatefulWidget implements display.Display {
19 | const Display({
20 | super.key,
21 | required this.emulator,
22 | });
23 |
24 | final ArcadeMachineEmu emulator;
25 |
26 | @override
27 | display.DisplayState createState() => _DisplayState();
28 | }
29 |
30 | class _DisplayState extends display.DisplayState {
31 | final _key = UniqueKey();
32 | late HTMLCanvasElement _canvas;
33 | late CanvasRenderingContext2D _context2d;
34 |
35 | final _video = Uint8ClampedList(VIDEO_WIDTH * VIDEO_HEIGHT * 4);
36 | late ImageData _imageData;
37 |
38 | late AudioContext _audioCtx;
39 | late ScriptProcessorNode _scriptNode;
40 |
41 | @override
42 | void initState() {
43 | super.initState();
44 | _canvas = HTMLCanvasElement()
45 | ..width = VIDEO_WIDTH
46 | ..height = VIDEO_HEIGHT;
47 | _context2d = _canvas.getContext('2d') as CanvasRenderingContext2D;
48 | _context2d.imageSmoothingEnabled = false;
49 | _imageData = _context2d.createImageData(VIDEO_WIDTH.toJS, VIDEO_HEIGHT);
50 |
51 | registerViewFactory('pacman-display-${_key.hashCode}', (int viewId) {
52 | return _canvas;
53 | });
54 |
55 | _audioCtx = AudioContext();
56 | _scriptNode = _audioCtx.createScriptProcessor(1024, 0, 1);
57 | _scriptNode.connect(_audioCtx.destination);
58 | _scriptNode.onaudioprocess = _onAudioProcess.toJS;
59 | }
60 |
61 | @override
62 | void dispose() {
63 | _scriptNode.onaudioprocess = null;
64 | _audioCtx.close();
65 | super.dispose();
66 | }
67 |
68 | @override
69 | void outputAudioFrame(Duration elapsed, Duration delta) {
70 | // Skipping un-front rendering
71 | }
72 |
73 | void _onAudioProcess(AudioProcessingEvent audioEvent) {
74 | try {
75 | final channelData = audioEvent.outputBuffer.getChannelData(0).toDart;
76 | if (widget.emulator.paused) {
77 | channelData.fillRange(0, channelData.length, 0);
78 | } else {
79 | widget.emulator.renderSound(channelData, _audioCtx.sampleRate.toInt());
80 | }
81 | } catch (e) {
82 | debugPrint('Web Audio API error playing back audio: $e');
83 | }
84 | }
85 |
86 | @override
87 | void outputVideoFrame(Duration elapsed, Duration delta) {
88 | if (!widget.emulator.paused) {
89 | widget.emulator.renderVideo(_video.buffer.asUint32List());
90 | _imageData.data.toDart.setAll(0, _video);
91 | _context2d.putImageData(_imageData, 0, 0);
92 | }
93 | }
94 |
95 | @override
96 | Widget build(BuildContext context) {
97 | return FittedBox(
98 | child: ConstrainedBox(
99 | constraints: BoxConstraints.tightFor(
100 | width: VIDEO_WIDTH.toDouble(),
101 | height: VIDEO_HEIGHT.toDouble(),
102 | ),
103 | child: HtmlElementView(
104 | key: _key,
105 | viewType: 'pacman-display-${_key.hashCode}',
106 | ),
107 | ),
108 | );
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/lib/emulator.dart:
--------------------------------------------------------------------------------
1 | import 'dart:typed_data';
2 |
3 | import 'package:flutter/services.dart' show LogicalKeyboardKey, rootBundle;
4 | import 'package:pacman/assets.dart';
5 | import 'package:z80/z80.dart';
6 |
7 | const int VIDEO_WIDTH = 224; // pixels
8 | const int VIDEO_HEIGHT = 288; // pixels
9 | const int VIDEO_FREQ = 60; // 60Hhz
10 | const int CPU_CLOCK = 3072000; // 3MHz
11 | const int CPU_CYCLES_PER_FRAME = CPU_CLOCK ~/ VIDEO_FREQ;
12 | const int SOUND_FREQ = CPU_CLOCK ~/ 32; // = 96000 (96kHz)
13 |
14 | const int R220 = 0x97;
15 | const int R470 = 0x47;
16 | const int R1000 = 0x21;
17 |
18 | const int CHEAT_EXTRALIFE1 = 0x4E14;
19 | const int CHEAT_EXTRALIFE2 = 0x4E15;
20 | const int CHEAT_LEVELEND = 0x4E0E; // 0xF4
21 |
22 | const int R0_ROM = 0x0000;
23 | const int R0_ROMMAX = 0x4000;
24 | const int R0_ROMCHARSET = 0x4000;
25 | const int R0_ROMSPRITESET = 0x5000;
26 | const int R0_RAMMAX = 0x5000;
27 | const int R0_RAMMASK = 0x7FFF;
28 | const int R0_RANGEMASK = 0xFFC0;
29 |
30 | const int RW_VRAM_CHAR = 0x4000;
31 | const int RW_VRAM_COLOR = 0x4400;
32 | const int RW_RAM = 0x4800;
33 | const int R0_IN0 = 0x5000;
34 | const int R0_IN1 = 0x5040;
35 | const int R0_DSW1 = 0x5080;
36 | const int R0_WATCHDOG = 0x50C0;
37 | const int RW_CHARMAP = 0x8000;
38 | const int RW_SPRITEMAP = 0xC000;
39 | const int RW_INTVAL = 0xFFFF;
40 |
41 | const int W0_INTENABLE = 0x5000;
42 | const int W0_SNDENABLE = 0x5001;
43 | const int W0_AUXENABLE = 0x5002;
44 | const int W0_SCREENFLIP = 0x5003;
45 | const int W0_PLAY1LAMP = 0x5004;
46 | const int W0_PLAY2LAMP = 0x5005;
47 | const int W0_COINLOCK = 0x5006;
48 | const int W0_COINCOUNT = 0x5007;
49 | const int W0_WATCHDOG = 0x50C0;
50 | const int W0_SNDREGS = 0x5040;
51 | const int W0_SPRITEPOS = 0x5060;
52 | const int W0_SPRITESHAPE = 0x4FF0;
53 |
54 | const int DSW1_PLAYFREE = 0x00; // coins per play
55 | const int DSW1_PLAY1COIN1GAME = 0x01;
56 | const int DSW1_PLAY1COIN2GAMES = 0x02;
57 | const int DSW1_PLAY2COINS1GAME = 0x03;
58 | const int DSW1_PLAYMASK = 0x03;
59 |
60 | const int DSW1_LIVES1 = 0x00; // lives per game
61 | const int DSW1_LIVES2 = 0x04;
62 | const int DSW1_LIVES3 = 0x08;
63 | const int DSW1_LIVES5 = 0x0C;
64 | const int DSW1_LIVESMASK = 0x0C;
65 |
66 | const int DSW1_BONUS10000 = 0x00; // bonus life
67 | const int DSW1_BONUS15000 = 0x10;
68 | const int DSW1_BONUS20000 = 0x20;
69 | const int DSW1_BONUSNONE = 0x30;
70 | const int DSW1_BONUSMASK = 0x30;
71 |
72 | const int DSW1_DIFFHARD = 0x00; // difficulty
73 | const int DSW1_DIFFNORMAL = 0x40;
74 | const int DSW1_DIFFMASK = 0x40;
75 |
76 | const int DSW1_GHOSTNAMESALT = 0x00; // ghost names
77 | const int DSW1_GHOSTNAMESNORMAL = 0x80;
78 | const int DSW1_GHOSTNAMESMASK = 0x80;
79 |
80 | const int IN0_UP = 0x01;
81 | const int IN0_LEFT = 0x02;
82 | const int IN0_RIGHT = 0x04;
83 | const int IN0_DOWN = 0x08;
84 | const int IN0_DIP6 = 0x10;
85 | const int IN0_COIN1 = 0x20;
86 | const int IN0_COIN2 = 0x40;
87 | const int IN0_CREDIT = 0x60;
88 |
89 | const int IN1_UP = 0x01;
90 | const int IN1_LEFT = 0x02;
91 | const int IN1_RIGHT = 0x04;
92 | const int IN1_DOWN = 0x08;
93 | const int IN1_TEST = 0x10;
94 | const int IN1_START1 = 0x20;
95 | const int IN1_START2 = 0x40;
96 | const int IN1_TABLE = 0x60;
97 |
98 | const int SND_WAVFM = 0x05;
99 | const int SND_FREQ1 = 0x10;
100 | const int SND_FREQ2 = 0x11;
101 | const int SND_FREQ3 = 0x12;
102 | const int SND_FREQ4 = 0x13;
103 | const int SND_FREQ5 = 0x14;
104 | const int SND_VOL = 0x15;
105 |
106 | class ArcadeMachineEmu implements Z80Core {
107 | ArcadeMachineEmu();
108 |
109 | late final _cpu = Z80CPU(this);
110 | late final _ram = Uint8ClampedList(0x10000); // 64KB
111 | late Uint8ClampedList _soundData;
112 | late List _palette;
113 |
114 | int _cycles = 0;
115 | bool _paused = false;
116 | bool _muted = false;
117 | double _masterVolume = 0.25;
118 |
119 | bool get paused => _paused;
120 |
121 | Future init() async {
122 | var base = 0x0000;
123 | for (final asset in [
124 | Assets.rom_data_6e,
125 | Assets.rom_data_6f,
126 | Assets.rom_data_6h,
127 | Assets.rom_data_6j,
128 | Assets.rom_data_5e,
129 | Assets.rom_data_5f,
130 | ]) {
131 | _ram.setAll(base, await loadAsset(asset));
132 | base += 0x1000;
133 | }
134 |
135 | createPalette(
136 | await loadAsset(Assets.palette_data),
137 | await loadAsset(Assets.color_data),
138 | );
139 |
140 | decodeVideoRoms(
141 | await loadAsset(Assets.rom_data_5e),
142 | await loadAsset(Assets.rom_data_5f),
143 | );
144 |
145 | _soundData = await loadAsset(Assets.sound_data);
146 |
147 | _ram[W0_INTENABLE] = 0x00;
148 | _ram[RW_INTVAL] = 0x00;
149 | _ram[R0_IN0] = 0xFF;
150 | _ram[R0_IN1] = 0xFF;
151 | _ram[R0_DSW1] = DSW1_PLAY1COIN1GAME |
152 | DSW1_BONUS10000 |
153 | DSW1_LIVES3 |
154 | DSW1_DIFFNORMAL |
155 | DSW1_GHOSTNAMESNORMAL;
156 |
157 | muteSound();
158 | }
159 |
160 | Future loadAsset(String asset) async {
161 | final data = await rootBundle.load(asset);
162 | return data.buffer.asUint8ClampedList();
163 | }
164 |
165 | void createPalette(List palRom, List colorRom) {
166 | final palette = List.filled(16, 0, growable: false);
167 | for (int i = 0; i < 16; i++) {
168 | int v = palRom[i];
169 | int r = R1000 * ((v >> 0) & 1) + R470 * ((v >> 1) & 1) + R220 * ((v >> 2) & 1);
170 | int g = R1000 * ((v >> 3) & 1) + R470 * ((v >> 4) & 1) + R220 * ((v >> 5) & 1);
171 | int b = R1000 * 0 + R470 * ((v >> 6) & 1) + R220 * ((v >> 7) & 1);
172 | palette[i] = 0xff000000 | (r & 0xff) | ((g & 0xff) << 8) | ((b & 0xff) << 16);
173 | }
174 | _palette = List.filled(256, 0);
175 | for (int i = 0; i < 256; i++) {
176 | _palette[i] = palette[colorRom[i] & 0x0F];
177 | }
178 | }
179 |
180 | void decodeVideoRoms(List charSet, List spriteSet) {
181 | for (int i = 0; i < 256; i++) {
182 | final src = charSet.sublist(i * 16);
183 | final dst = _ram.buffer.asUint8ClampedList(RW_CHARMAP + i * 64);
184 | decodePixels(src, 0, dst, 0, 4, 8);
185 | decodePixels(src, 8, dst, 0, 0, 8);
186 | }
187 | for (int i = 0; i < 64; i++) {
188 | final src = spriteSet.sublist(i * 64);
189 | final dst = _ram.buffer.asUint8ClampedList(RW_SPRITEMAP + i * 256);
190 | decodePixels(src, 0, dst, 8, 12, 16);
191 | decodePixels(src, 8, dst, 8, 0, 16);
192 | decodePixels(src, 16, dst, 8, 4, 16);
193 | decodePixels(src, 24, dst, 8, 8, 16);
194 | decodePixels(src, 32, dst, 0, 12, 16);
195 | decodePixels(src, 40, dst, 0, 0, 16);
196 | decodePixels(src, 48, dst, 0, 4, 16);
197 | decodePixels(src, 56, dst, 0, 8, 16);
198 | }
199 | }
200 |
201 | void decodePixels(
202 | List srcData, int srcOffset, List charBuffer, int x, int y, int width) {
203 | int offset = srcOffset;
204 | for (int dx = 7; dx >= 0; dx--) {
205 | int b = srcData[offset++];
206 | for (int dy = 3; dy >= 0; dy--) {
207 | charBuffer[(y + dy) * width + (x + dx)] = ((b >> 3) & 2) | (b & 1);
208 | b >>= 1;
209 | }
210 | }
211 | }
212 |
213 | void renderVideo(Uint32List videoBuffer) {
214 | final vrChar = _ram.buffer.asUint8ClampedList(RW_VRAM_CHAR);
215 | final vrColor = _ram.buffer.asUint8ClampedList(RW_VRAM_COLOR);
216 | for (int i = 2, x = (27 * 8); i < 30; i++, x -= 8) {
217 | _renderChar(videoBuffer, vrChar[0x3C0 + i], x, (0 * 8), vrColor[0x3C0 + i], VIDEO_WIDTH);
218 | _renderChar(videoBuffer, vrChar[0x3E0 + i], x, (1 * 8), vrColor[0x3E0 + i], VIDEO_WIDTH);
219 | _renderChar(videoBuffer, vrChar[0x000 + i], x, (34 * 8), vrColor[0x000 + i], VIDEO_WIDTH);
220 | _renderChar(videoBuffer, vrChar[0x020 + i], x, (35 * 8), vrColor[0x020 + i], VIDEO_WIDTH);
221 | }
222 | for (int i = 0x40, x = (28 * 8), y = 0; i < 0x3C0; i++, y += 8) {
223 | if (i % 0x20 == 0) {
224 | y = (2 * 8);
225 | x -= 8;
226 | }
227 | _renderChar(videoBuffer, vrChar[i], x, y, vrColor[i], VIDEO_WIDTH);
228 | }
229 | for (int i = 7; i >= 0; i--) {
230 | _renderSprite(videoBuffer, i);
231 | }
232 | }
233 |
234 | void _renderChar(List buffer, int index, int ox, int oy, int color, int pitch) {
235 | int offset = (oy * pitch) + ox;
236 | index *= 64;
237 | color = (color & 0x3F) * 4;
238 | for (int y = 0; y < 8; y++) {
239 | for (int x = 0; x < 8; x++) {
240 | buffer[offset + x] = _palette[_ram[RW_CHARMAP + index] + color];
241 | index++;
242 | }
243 | offset += pitch;
244 | }
245 | }
246 |
247 | void _renderSprite(List buffer, int index) {
248 | final spriteShape = _ram[W0_SPRITESHAPE + (index * 2) + 0] >> 2; // Shape
249 | final spriteMode = _ram[W0_SPRITESHAPE + (index * 2) + 0] & 3; // Shape
250 | final spriteColor = _ram[W0_SPRITESHAPE + (index * 2) + 1]; // Color
251 | final spriteY = (256 + 16) - _ram[W0_SPRITEPOS + (index * 2) + 1]; // Pos Y
252 | int spriteX = (256 - 17) - _ram[W0_SPRITEPOS + (index * 2) + 0]; // Pos X
253 | if (index <= 2) {
254 | spriteX--;
255 | }
256 | if ((spriteColor == 0) ||
257 | (spriteX >= VIDEO_WIDTH) ||
258 | (spriteY < 16) ||
259 | (spriteY >= (VIDEO_HEIGHT - 32))) {
260 | return;
261 | }
262 |
263 | final startX = (spriteX < 0) ? 0 : spriteX;
264 | final endX = (spriteX < (VIDEO_WIDTH - 16)) ? spriteX + 16 : VIDEO_WIDTH;
265 | final color = (spriteColor & 0x3F) * 4;
266 | final spriteMap = _ram.buffer.asUint8ClampedList(RW_SPRITEMAP + (spriteShape & 0x3F) * 256);
267 |
268 | int offset = VIDEO_WIDTH * spriteY;
269 | for (int y = 0; y < 16; y++) {
270 | for (int x = startX; x < endX; x++) {
271 | int c = 0, o = (x - spriteX);
272 | switch (spriteMode) {
273 | case 0:
274 | c = spriteMap[o + y * 16];
275 | break;
276 | case 1:
277 | c = spriteMap[o + (15 - y) * 16];
278 | break;
279 | case 2:
280 | c = spriteMap[15 - o + y * 16];
281 | break;
282 | case 3:
283 | c = spriteMap[15 - o + (15 - y) * 16];
284 | break;
285 | }
286 | if (c != 0) {
287 | buffer[offset + x] = _palette[(color + c & 0xFF)];
288 | }
289 | }
290 | offset += VIDEO_WIDTH;
291 | }
292 | }
293 |
294 | void onKeyDown(LogicalKeyboardKey key) {
295 | if (key == LogicalKeyboardKey.space) {
296 | _ram[R0_IN0] &= ~IN0_COIN1;
297 | } else if (key == LogicalKeyboardKey.digit1) {
298 | _ram[R0_IN1] &= ~IN1_START1;
299 | } else if (key == LogicalKeyboardKey.digit2) {
300 | _ram[R0_IN1] &= ~IN1_START2;
301 | } else if (key == LogicalKeyboardKey.arrowUp) {
302 | _ram[R0_IN0] &= ~IN0_UP;
303 | } else if (key == LogicalKeyboardKey.arrowDown) {
304 | _ram[R0_IN0] &= ~IN0_DOWN;
305 | } else if (key == LogicalKeyboardKey.arrowRight) {
306 | _ram[R0_IN0] &= ~IN0_RIGHT;
307 | } else if (key == LogicalKeyboardKey.arrowLeft) {
308 | _ram[R0_IN0] &= ~IN0_LEFT;
309 | }
310 | }
311 |
312 | void onKeyUp(LogicalKeyboardKey key) {
313 | if (key == LogicalKeyboardKey.space) {
314 | _ram[R0_IN0] |= IN0_COIN1;
315 | } else if (key == LogicalKeyboardKey.digit1) {
316 | _ram[R0_IN1] |= IN1_START1;
317 | } else if (key == LogicalKeyboardKey.digit2) {
318 | _ram[R0_IN1] |= IN1_START2;
319 | } else if (key == LogicalKeyboardKey.keyP) {
320 | _paused = !_paused;
321 | } else if (key == LogicalKeyboardKey.arrowUp) {
322 | _ram[R0_IN0] |= IN0_UP;
323 | } else if (key == LogicalKeyboardKey.arrowDown) {
324 | _ram[R0_IN0] |= IN0_DOWN;
325 | } else if (key == LogicalKeyboardKey.arrowRight) {
326 | _ram[R0_IN0] |= IN0_RIGHT;
327 | } else if (key == LogicalKeyboardKey.arrowLeft) {
328 | _ram[R0_IN0] |= IN0_LEFT;
329 | } else if (key == LogicalKeyboardKey.f1) {
330 | _ram[CHEAT_EXTRALIFE1]++;
331 | _ram[CHEAT_EXTRALIFE2]++;
332 | } else if (key == LogicalKeyboardKey.f2) {
333 | _ram[CHEAT_LEVELEND] = 0xF4;
334 | } else if (key == LogicalKeyboardKey.audioVolumeDown ||
335 | key == LogicalKeyboardKey.numpadSubtract) {
336 | _masterVolume = (_masterVolume - 0.1).clamp(0.0, 1.0);
337 | } else if (key == LogicalKeyboardKey.audioVolumeUp || key == LogicalKeyboardKey.numpadAdd) {
338 | _masterVolume = (_masterVolume + 0.1).clamp(0.0, 1.0);
339 | } else if (key == LogicalKeyboardKey.audioVolumeMute || //
340 | key == LogicalKeyboardKey.numpad0) {
341 | _muted = !_muted;
342 | }
343 | }
344 |
345 | void tick() {
346 | if (!_paused) {
347 | if (_ram[W0_INTENABLE] != 0) {
348 | _cpu.interrupt(false, _ram[RW_INTVAL]);
349 | }
350 | _cycles += CPU_CYCLES_PER_FRAME;
351 | while (_cycles > 0) {
352 | _cycles -= _cpu.runInstruction();
353 | }
354 | }
355 | }
356 |
357 | final List waveOffset = [0, 0, 0];
358 |
359 | void muteSound() {
360 | int baseReg = 0;
361 | for (int voice = 0; voice < 3; voice++) {
362 | _ram[W0_SNDREGS + (baseReg + SND_VOL)] = 0;
363 | baseReg += 5;
364 | }
365 | }
366 |
367 | /// Generates floating point PCM at sampleRate
368 | void renderSound(Float32List buf, int sampleRate) {
369 | _ram[W0_SNDREGS];
370 |
371 | final divisor = ((SOUND_FREQ << 10) ~/ sampleRate);
372 | buf.fillRange(0, buf.length, 0.0);
373 | int baseReg = 0;
374 | for (int voice = 0; voice < 3; voice++) {
375 | final waveform = (_ram[W0_SNDREGS + (baseReg + SND_WAVFM)] & 0x07);
376 | final volume = _muted ? 0 : (_ram[W0_SNDREGS + (baseReg + SND_VOL)] & 0x0F);
377 | int freq = ((_ram[W0_SNDREGS + (baseReg + SND_FREQ5)] & 0x0F) << 16);
378 | freq |= ((_ram[W0_SNDREGS + (baseReg + SND_FREQ4)] & 0x0F) << 12);
379 | freq |= ((_ram[W0_SNDREGS + (baseReg + SND_FREQ3)] & 0x0F) << 8);
380 | freq |= ((_ram[W0_SNDREGS + (baseReg + SND_FREQ2)] & 0x0F) << 4);
381 | if (voice == 0) {
382 | freq |= (_ram[W0_SNDREGS + (baseReg + SND_FREQ1)] & 0x0F);
383 | }
384 | if (freq > 0 && volume > 0) {
385 | final waveSample = _soundData.sublist(32 * waveform);
386 | final step = (freq * divisor);
387 | int offset = waveOffset[voice];
388 | for (int i = 0; i < buf.length; i++) {
389 | buf[i] += (waveSample[(offset >> 25) & 0x1F] * volume * _masterVolume);
390 | offset += step;
391 | }
392 | waveOffset[voice] = offset;
393 | }
394 | baseReg += 5;
395 | }
396 | for (int i = 0; i < buf.length; i++) {
397 | buf[i] /= 0xFF; // resample 8-bit to float
398 | }
399 | }
400 |
401 | @override
402 | int memRead(int address) {
403 | address &= R0_RAMMASK;
404 | if (address < R0_RAMMAX) return _ram[address];
405 | switch (address & R0_RANGEMASK) {
406 | case R0_IN0:
407 | return _ram[R0_IN0];
408 | case R0_IN1:
409 | return _ram[R0_IN1] & 0xF0 | _ram[R0_IN0] & 0x0F;
410 | case R0_DSW1:
411 | return _ram[R0_DSW1];
412 | case R0_WATCHDOG:
413 | return 0xFF;
414 | default:
415 | return _ram[address];
416 | }
417 | }
418 |
419 | @override
420 | void memWrite(int address, int value) {
421 | address &= R0_RAMMASK;
422 | if (address >= R0_ROM && address < R0_ROMMAX) {
423 | return;
424 | }
425 | if (address == R0_DSW1 || address == R0_IN0 || address == R0_IN1) {
426 | return;
427 | }
428 | _ram[address] = value;
429 | }
430 |
431 | @override
432 | int ioRead(int port) => 0;
433 |
434 | @override
435 | void ioWrite(int port, int value) {
436 | if ((port >> 8) == value) {
437 | _ram[RW_INTVAL] = value;
438 | }
439 | }
440 | }
441 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui' as ui;
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/rendering.dart' show RenderErrorBox;
5 | import 'package:flutter/scheduler.dart' show Ticker;
6 | import 'package:flutter/services.dart';
7 | import 'package:pacman/assets.dart';
8 | import 'package:pacman/emulator.dart';
9 |
10 | import 'config/configure.dart';
11 | import 'display/display.dart';
12 |
13 | void main() {
14 | configureApp();
15 | runApp(const App());
16 | }
17 |
18 | class App extends StatelessWidget {
19 | const App({super.key});
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return MaterialApp(
24 | debugShowCheckedModeBanner: false,
25 | title: 'Pacman Emulator in Flutter',
26 | theme: ThemeData(
27 | brightness: Brightness.dark,
28 | colorScheme: const ColorScheme.dark(
29 | primary: Color(0xFFFFE311),
30 | background: Colors.black,
31 | brightness: Brightness.dark,
32 | ),
33 | visualDensity: VisualDensity.adaptivePlatformDensity,
34 | ),
35 | home: const Game(),
36 | );
37 | }
38 | }
39 |
40 | @immutable
41 | class Game extends StatefulWidget {
42 | const Game({super.key});
43 |
44 | @override
45 | State createState() => _GameState();
46 | }
47 |
48 | class _GameState extends State with SingleTickerProviderStateMixin {
49 | final _displayKey = GlobalKey();
50 | bool _loaded = false;
51 | bool _started = false;
52 | late ArcadeMachineEmu _emulator;
53 | late Future _loading;
54 | late FocusNode _focusNode;
55 | late Ticker _ticker;
56 | Duration? _last;
57 |
58 | @override
59 | void initState() {
60 | super.initState();
61 | _emulator = ArcadeMachineEmu();
62 | _focusNode = FocusNode();
63 | _ticker = createTicker(_onTick);
64 | _loading = _emulator.init().then((_) {
65 | setState(() => _loaded = true);
66 | });
67 | }
68 |
69 | void _onStart() {
70 | if (!_loaded) return;
71 | setState(() => _started = true);
72 | _focusNode.requestFocus();
73 | _ticker.start();
74 | }
75 |
76 | void _onTick(Duration elapsed) {
77 | final delta = elapsed - (_last ?? elapsed);
78 | if (delta > Duration.zero) {
79 | _emulator.tick();
80 | _displayKey.currentState!.outputVideoFrame(elapsed, delta);
81 | if (!_emulator.paused) {
82 | _displayKey.currentState!.outputAudioFrame(elapsed, delta);
83 | }
84 | }
85 | _last = elapsed;
86 | }
87 |
88 | @override
89 | void dispose() {
90 | _ticker.dispose();
91 | _focusNode.dispose();
92 | super.dispose();
93 | }
94 |
95 | @override
96 | Widget build(BuildContext context) {
97 | return SizedBox.expand(
98 | child: Material(
99 | color: Colors.black,
100 | child: Builder(
101 | builder: (BuildContext context) {
102 | if (_started) {
103 | return RawKeyboardListener(
104 | focusNode: _focusNode,
105 | onKey: (RawKeyEvent event) {
106 | if (event is RawKeyDownEvent) {
107 | _emulator.onKeyDown(event.logicalKey);
108 | } else if (event is RawKeyUpEvent) {
109 | _emulator.onKeyUp(event.logicalKey);
110 | }
111 | },
112 | child: Display(
113 | key: _displayKey,
114 | emulator: _emulator,
115 | ),
116 | );
117 | } else {
118 | return Ink.image(
119 | image: const AssetImage(Assets.backgroundImage),
120 | repeat: ImageRepeat.repeat,
121 | child: GestureDetector(
122 | behavior: HitTestBehavior.opaque,
123 | onTap: _onStart,
124 | child: DefaultTextStyle.merge(
125 | style: const TextStyle(
126 | fontWeight: FontWeight.w500,
127 | fontSize: 20.0,
128 | ),
129 | child: Column(
130 | mainAxisAlignment: MainAxisAlignment.center,
131 | children: [
132 | Image.asset(Assets.logoImage),
133 | Padding(
134 | padding: const EdgeInsets.symmetric(vertical: 32.0),
135 | child: Text(
136 | 'Pacman Emulator',
137 | style: TextStyle(
138 | color: Theme.of(context).colorScheme.primary,
139 | fontWeight: FontWeight.w500,
140 | fontSize: 48.0,
141 | ),
142 | textAlign: TextAlign.center,
143 | ),
144 | ),
145 | FutureBuilder(
146 | future: _loading,
147 | builder: (BuildContext context, AsyncSnapshot snapshot) {
148 | if (snapshot.hasError) {
149 | return const Text('Sorry, cannot load game.');
150 | } else if (snapshot.connectionState != ConnectionState.done) {
151 | return const CircularProgressIndicator();
152 | } else {
153 | return const Padding(
154 | padding: EdgeInsets.symmetric(horizontal: 24.0),
155 | child: Text(
156 | 'Press space to insert coin.\n'
157 | 'Press 1 or 2 to start one or two player game.\n'
158 | 'Use the arrow keys to move pacman.\n'
159 | 'Press P to pause the game.\n'
160 | 'Press numpad +/- changes volume.\n'
161 | '\n'
162 | 'Cheaters:\nF1 for extra-life and F2 to skip level.\n\n'
163 | 'Click to start\n\n',
164 | textAlign: TextAlign.center,
165 | ),
166 | );
167 | }
168 | },
169 | ),
170 | ],
171 | ),
172 | ),
173 | ),
174 | );
175 | }
176 | },
177 | ),
178 | ),
179 | );
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/lib/utils/platform_view_web.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui_web';
2 |
3 | typedef PlatformViewFactory = dynamic Function(int viewId);
4 |
5 | bool registerViewFactory(String viewTypeId, PlatformViewFactory factory) {
6 | // ignore: undefined_prefixed_name
7 | return platformViewRegistry.registerViewFactory(viewTypeId, factory);
8 | }
9 |
--------------------------------------------------------------------------------
/lib/win32_sound/constants.dart:
--------------------------------------------------------------------------------
1 | // waveform audio error return values
2 | const _WAVERR_BASE = 32;
3 | const WAVERR_BADFORMAT = _WAVERR_BASE + 0; // unsupported wave format
4 | const WAVERR_STILLPLAYING = _WAVERR_BASE + 1; // still something playing
5 | const WAVERR_UNPREPARED = _WAVERR_BASE + 2; // header not prepared
6 | const WAVERR_SYNC = _WAVERR_BASE + 3; // device is synchronous
7 | const WAVERR_LASTERROR = _WAVERR_BASE + 3; // last error in range
8 |
9 | // device ID for wave device mapper
10 | const WAVE_MAPPER = -1;
11 |
12 | // flags for dwFlags field of WAVEHDR
13 | const WHDR_DONE = 0x00000001; // done bit
14 | const WHDR_PREPARED = 0x00000002; // set if this header has been prepared
15 | const WHDR_BEGINLOOP = 0x00000004; // loop start block
16 | const WHDR_ENDLOOP = 0x00000008; // loop end block
17 | const WHDR_INQUEUE = 0x00000010; // reserved for driver
18 |
19 | // flags for dwSupport field of WAVEOUTCAPS
20 | const WAVECAPS_PITCH = 0x0001; // supports pitch control
21 | const WAVECAPS_PLAYBACKRATE = 0x0002; // supports playback rate control
22 | const WAVECAPS_VOLUME = 0x0004; // supports volume control
23 | const WAVECAPS_LRVOLUME = 0x0008; // separate left-right volume control
24 | const WAVECAPS_SYNC = 0x0010;
25 | const WAVECAPS_SAMPLEACCURATE = 0x0020;
26 |
27 | // defines for dwFormat field of WAVEINCAPS and WAVEOUTCAPS
28 | const WAVE_INVALIDFORMAT = 0x00000000; // invalid format
29 | const WAVE_FORMAT_1M08 = 0x00000001; // 11.025 kHz, Mono, 8-bit
30 | const WAVE_FORMAT_1S08 = 0x00000002; // 11.025 kHz, Stereo, 8-bit
31 | const WAVE_FORMAT_1M16 = 0x00000004; // 11.025 kHz, Mono, 16-bit
32 | const WAVE_FORMAT_1S16 = 0x00000008; // 11.025 kHz, Stereo, 16-bit
33 | const WAVE_FORMAT_2M08 = 0x00000010; // 22.05 kHz, Mono, 8-bit
34 | const WAVE_FORMAT_2S08 = 0x00000020; // 22.05 kHz, Stereo, 8-bit
35 | const WAVE_FORMAT_2M16 = 0x00000040; // 22.05 kHz, Mono, 16-bit
36 | const WAVE_FORMAT_2S16 = 0x00000080; // 22.05 kHz, Stereo, 16-bit
37 | const WAVE_FORMAT_4M08 = 0x00000100; // 44.1 kHz, Mono, 8-bit
38 | const WAVE_FORMAT_4S08 = 0x00000200; // 44.1 kHz, Stereo, 8-bit
39 | const WAVE_FORMAT_4M16 = 0x00000400; // 44.1 kHz, Mono, 16-bit
40 | const WAVE_FORMAT_4S16 = 0x00000800; // 44.1 kHz, Stereo, 16-bit
41 | const WAVE_FORMAT_44M08 = 0x00000100; // 44.1 kHz, Mono, 8-bit
42 | const WAVE_FORMAT_44S08 = 0x00000200; // 44.1 kHz, Stereo, 8-bit
43 | const WAVE_FORMAT_44M16 = 0x00000400; // 44.1 kHz, Mono, 16-bit
44 | const WAVE_FORMAT_44S16 = 0x00000800; // 44.1 kHz, Stereo, 16-bit
45 | const WAVE_FORMAT_48M08 = 0x00001000; // 48 kHz, Mono, 8-bit
46 | const WAVE_FORMAT_48S08 = 0x00002000; // 48 kHz, Stereo, 8-bit
47 | const WAVE_FORMAT_48M16 = 0x00004000; // 48 kHz, Mono, 16-bit
48 | const WAVE_FORMAT_48S16 = 0x00008000; // 48 kHz, Stereo, 16-bit
49 | const WAVE_FORMAT_96M08 = 0x00010000; // 96 kHz, Mono, 8-bit
50 | const WAVE_FORMAT_96S08 = 0x00020000; // 96 kHz, Stereo, 8-bit
51 | const WAVE_FORMAT_96M16 = 0x00040000; // 96 kHz, Mono, 16-bit
52 | const WAVE_FORMAT_96S16 = 0x00080000; // 96 kHz, Stereo, 16-bit
53 |
54 | //flags for wFormatTag field of WAVEFORMAT
55 | const WAVE_FORMAT_UNKNOWN = 0x0000;
56 | //const WAVE_FORMAT_PCM = 0x0001;
57 | const WAVE_FORMAT_ADPCM = 0x0002;
58 | //const WAVE_FORMAT_IEEE_FLOAT = 0x0003;
59 | const WAVE_FORMAT_ALAW = 0x0006;
60 | const WAVE_FORMAT_MULAW = 0x0007;
61 | const WAVE_FORMAT_DTS = 0x0008;
62 | const WAVE_FORMAT_MPEG = 0x0050;
63 | const WAVE_FORMAT_MPEGLAYER3 = 0x0055;
64 | const WAVE_FORMAT_MPEG_ADTS_AAC = 0x1600;
65 | const WAVE_FORMAT_MPEG_RAW_AAC = 0x1601;
66 | const WAVE_FORMAT_DTS2 = 0x200;
67 | const WAVE_FORMAT_OGG_VORBIS_MODE_1 = 0x674F;
68 | const WAVE_FORMAT_OGG_VORBIS_MODE_2 = 0x6750;
69 | const WAVE_FORMAT_OGG_VORBIS_MODE_3 = 0x6751;
70 | const WAVE_FORMAT_OGG_VORBIS_MODE_1_PLUS = 0x676F;
71 | const WAVE_FORMAT_OGG_VORBIS_MODE_2_PLUS = 0x6770;
72 | const WAVE_FORMAT_OGG_VORBIS_MODE_3_PLUS = 0x6771;
73 | const WAVE_FORMAT_MPEG4_AAC = 0xA106;
74 | const WAVE_FORMAT_FLAC = 0xF1AC;
75 |
--------------------------------------------------------------------------------
/lib/win32_sound/functions.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ffi';
2 |
3 | final _winmm = DynamicLibrary.open('winmm.dll');
4 |
5 | /// The waveOutBreakLoop function breaks a loop on the given waveform-audio
6 | /// output device and allows playback to continue with the next block in
7 | /// the driver list.
8 | ///
9 | /// ```c
10 | /// MMRESULT WINAPI waveOutBreakLoop(
11 | /// _In_ HWAVEOUT hwo
12 | /// );
13 | /// ```
14 | /// {@category winmm}
15 | int waveOutBreakLoop(int hwo) => _waveOutBreakLoop(hwo);
16 |
17 | final _waveOutBreakLoop =
18 | _winmm.lookupFunction('waveOutBreakLoop');
19 |
--------------------------------------------------------------------------------
/lib/win32_sound/structs.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ffi';
2 |
3 | @Packed(1)
4 | final class MPEGLAYER3WAVEFORMAT extends Struct {
5 | @Uint16()
6 | external int wFormatTag;
7 |
8 | @Uint16()
9 | external int nChannels;
10 |
11 | @Uint32()
12 | external int nSamplesPerSec;
13 |
14 | @Uint32()
15 | external int nAvgBytesPerSec;
16 |
17 | @Uint16()
18 | external int nBlockAlign;
19 |
20 | @Uint16()
21 | external int wBitsPerSample;
22 |
23 | @Uint16()
24 | external int cbSize;
25 |
26 | @Uint16()
27 | external int wID;
28 |
29 | @Uint32()
30 | external int fdwFlags;
31 |
32 | @Uint16()
33 | external int nBlockSize;
34 |
35 | @Uint16()
36 | external int nFramesPerBlock;
37 |
38 | @Uint16()
39 | external int nCodecDelay;
40 | }
41 |
--------------------------------------------------------------------------------
/lib/win32_sound/win32_sound.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:ffi';
3 | import 'dart:isolate';
4 | import 'dart:math' as math;
5 | import 'dart:typed_data';
6 |
7 | import 'package:ffi/ffi.dart';
8 | import 'package:pacman/win32_sound/functions.dart';
9 | import 'package:pacman/win32_sound/structs.dart';
10 | import 'package:semaphore_plus/semaphore_plus.dart';
11 | import 'package:win32/win32.dart';
12 |
13 | import 'constants.dart';
14 | export 'constants.dart';
15 |
16 | typedef _MmFunc = int Function();
17 |
18 | int _throwOnError(_MmFunc func) {
19 | final mmError = func();
20 | if (mmError != MMSYSERR_NOERROR) {
21 | throw MmException(mmError);
22 | }
23 | return mmError;
24 | }
25 |
26 | final class _AudioProcResult extends Struct {
27 | @IntPtr()
28 | external int hWaveOut;
29 | @Uint32()
30 | external int uMsg;
31 | @IntPtr()
32 | external int dwParam1;
33 | @IntPtr()
34 | external int dwParam2;
35 | }
36 |
37 | class WaveOut {
38 | static final WaveOut defaultDevice = WaveOut(WAVE_MAPPER);
39 |
40 | static int get deviceCount => waveOutGetNumDevs();
41 |
42 | static bool _audioProcSetup = false;
43 | static late Pointer _audioProcPtr;
44 |
45 | factory WaveOut(int deviceId, {int bufferCount = 4, int bufferSizeInBytes = 2048}) {
46 | if (!_audioProcSetup) {
47 | // print('NativeApi: ${NativeApi.majorVersion}.${NativeApi.minorVersion}');
48 | if (NativeApi.majorVersion == 2) (NativeApi.minorVersion >= 0);
49 | final dl = DynamicLibrary.open('audio_proc.dll');
50 | final initializeApi =
51 | dl.lookupFunction), int Function(Pointer)>(
52 | "InitDartApiDL");
53 | final initResult = initializeApi(NativeApi.initializeApiDLData);
54 | if (initResult != 0) {
55 | throw 'Init failed';
56 | }
57 | _audioProcPtr = dl.lookup('AudioProc');
58 | _audioProcSetup = true;
59 | }
60 |
61 | final waveOutCaps = calloc();
62 | final mmError = waveOutGetDevCaps(deviceId, waveOutCaps, sizeOf());
63 | if (mmError != MMSYSERR_NOERROR) {
64 | throw MmException(mmError);
65 | }
66 | return WaveOut._(
67 | deviceId,
68 | waveOutCaps,
69 | List.unmodifiable(SupportedWaveFormat.fromBitField(waveOutCaps.ref.dwFormats)),
70 | bufferCount,
71 | bufferSizeInBytes,
72 | );
73 | }
74 |
75 | WaveOut._(
76 | this.deviceId, this._waveOutCaps, this.waveFormats, this._bufferCount, int _bufferSizeInBytes)
77 | : _bufferSemaphore = LocalSemaphore(_bufferCount) {
78 | _audioProcSub = _audioProcPort.listen(_waveOutProc);
79 | _buffers = List.generate(
80 | _bufferCount,
81 | (int index) => WaveBuffer(this, index, _bufferSizeInBytes),
82 | );
83 | }
84 |
85 | final _audioProcPort = ReceivePort();
86 | final int deviceId;
87 | final Pointer _waveOutCaps;
88 | late final LocalSemaphore _bufferSemaphore;
89 | late StreamSubscription _audioProcSub;
90 | late List _buffers;
91 |
92 | int? _hWaveOut;
93 | Completer? _closeCompleter;
94 |
95 | final int _bufferCount;
96 | int _currentBuffer = 0;
97 | bool _noBuffer = true;
98 |
99 | // Device Capabilities
100 |
101 | final List waveFormats;
102 |
103 | int get manufacturerId => _waveOutCaps.ref.wMid;
104 |
105 | int get productId => _waveOutCaps.ref.wPid;
106 |
107 | String get driverVersion => '${HIBYTE(_waveOutCaps.ref.vDriverVersion)}.'
108 | '${LOBYTE(_waveOutCaps.ref.vDriverVersion)}';
109 |
110 | String get name =>
111 | String.fromCharCodes(_waveOutCaps.ref.szPname.codeUnits.where((el) => el != 0));
112 |
113 | int get channelCount => _waveOutCaps.ref.wChannels;
114 |
115 | bool get supportsPitchControl => (_waveOutCaps.ref.dwSupport & WAVECAPS_PITCH) != 0;
116 |
117 | bool get supportsPlaybackRateControl => (_waveOutCaps.ref.dwSupport & WAVECAPS_PLAYBACKRATE) != 0;
118 |
119 | bool get supportsVolumeControl => (_waveOutCaps.ref.dwSupport & WAVECAPS_VOLUME) != 0;
120 |
121 | bool get supportsLRVolumeControl => (_waveOutCaps.ref.dwSupport & WAVECAPS_LRVOLUME) != 0;
122 |
123 | bool get supportsSync => (_waveOutCaps.ref.dwSupport & WAVECAPS_SYNC) != 0;
124 |
125 | bool get supportsSampleAccurate => (_waveOutCaps.ref.dwSupport & WAVECAPS_SAMPLEACCURATE) != 0;
126 |
127 | /// Determines whether the specified waveform-audio output device
128 | /// supports a specified waveform-audio format.
129 | bool isFormatSupported(SupportedWaveFormat format) {
130 | final waveFormatEx = format._allocatePcmWaveFormat();
131 | try {
132 | final mmError = waveOutOpen(
133 | nullptr, // ptr can be NULL for query
134 | deviceId, // the device identifier
135 | waveFormatEx, // defines requested format
136 | NULL, // no callback
137 | NULL, // no instance data
138 | WAVE_FORMAT_QUERY, // query only, do not open device
139 | );
140 | if (mmError == MMSYSERR_NOERROR) {
141 | return true;
142 | } else if (mmError == WAVERR_BADFORMAT) {
143 | return false;
144 | }
145 | throw MmException(mmError);
146 | } finally {
147 | free(waveFormatEx);
148 | }
149 | }
150 |
151 | Future dispose() async {
152 | if (_hWaveOut != null) {
153 | reset();
154 | }
155 | for (final hdr in _buffers) {
156 | hdr.dispose();
157 | }
158 | await close();
159 | free(_waveOutCaps);
160 | _audioProcSub.cancel();
161 | }
162 |
163 | // Device Control
164 |
165 | void open(SupportedWaveFormat format) {
166 | assert(_hWaveOut == null, 'Device closed');
167 | final phWo = malloc();
168 | final waveFormatEx = format._allocatePcmWaveFormat();
169 | try {
170 | _throwOnError(
171 | () => waveOutOpen(
172 | phWo,
173 | deviceId,
174 | waveFormatEx,
175 | _audioProcPtr.address,
176 | _audioProcPort.sendPort.nativePort,
177 | WAVE_ALLOWSYNC | CALLBACK_FUNCTION,
178 | ),
179 | );
180 | _hWaveOut = phWo.value;
181 | } finally {
182 | free(waveFormatEx);
183 | free(phWo);
184 | }
185 | }
186 |
187 | void openMp3() {
188 | assert(_hWaveOut == null, 'Device closed');
189 | final phWo = calloc();
190 | // http://damb.dk/snip/playmp3.html
191 | // https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-mpeglayer3waveformat
192 | final waveFormatEx = calloc();
193 | waveFormatEx.ref
194 | ..wFormatTag = WAVE_FORMAT_MPEGLAYER3
195 | ..nChannels = 1
196 | ..nSamplesPerSec = 22050 // hz
197 | ..nBlockAlign = 1
198 | ..wBitsPerSample = 0
199 | ..cbSize = 12 // MPEGLAYER3_WFX_EXTRA_BYTES
200 | ..wID = 1 // MPEGLAYER3_ID_MPEG
201 | ..fdwFlags = 2 // MPEGLAYER3_FLAG_PADDING_OFF
202 | ..nAvgBytesPerSec = 64 * (1024 ~/ 8) // 64kbps
203 | ..nBlockSize = 2048 // or 522
204 | ..nFramesPerBlock = 1;
205 | try {
206 | _throwOnError(
207 | () => waveOutOpen(
208 | phWo,
209 | deviceId,
210 | waveFormatEx.cast(),
211 | _audioProcPtr.address,
212 | _audioProcPort.sendPort.nativePort,
213 | WAVE_ALLOWSYNC | CALLBACK_FUNCTION,
214 | ),
215 | );
216 | _hWaveOut = phWo.value;
217 | } finally {
218 | free(waveFormatEx);
219 | free(phWo);
220 | }
221 | }
222 |
223 | void _waveOutProc(dynamic message) {
224 | final _ptr = Pointer<_AudioProcResult>.fromAddress(message as int);
225 | try {
226 | final result = _ptr.ref;
227 | switch (result.uMsg) {
228 | case MM_WOM_OPEN:
229 | //print('device opened');
230 | break;
231 | case MM_WOM_DONE:
232 | //WAVEHDR waveHdr = Pointer.fromAddress(result.dwParam1).ref;
233 | //print('buffer done ${waveHdr.dwUser}');
234 | _bufferSemaphore.release();
235 | break;
236 | case MM_WOM_CLOSE:
237 | //print('device closed');
238 | _closeCompleter?.complete();
239 | _hWaveOut = null;
240 | break;
241 | }
242 | } finally {
243 | free(_ptr);
244 | }
245 | }
246 |
247 | Future write(Uint8List data) async {
248 | var start = 0;
249 | var nBytes = data.length;
250 | final nWritten = List.filled(1, 0);
251 | while (nBytes != 0) {
252 | // Get a buffer if necessary.
253 | if (_noBuffer) {
254 | await _bufferSemaphore.acquire();
255 | _noBuffer = false;
256 | }
257 | // Write into a buffer.
258 | if (_buffers[_currentBuffer].write(Uint8List.sublistView(data, start), nWritten)) {
259 | start += nWritten[0];
260 | nBytes -= nWritten[0];
261 | _noBuffer = true;
262 | _currentBuffer = (_currentBuffer + 1) % _bufferCount;
263 | }
264 | }
265 | }
266 |
267 | void flush() {
268 | if (!_noBuffer) {
269 | _buffers[_currentBuffer].flush();
270 | _noBuffer = true;
271 | _currentBuffer = (_currentBuffer + 1) % _bufferCount;
272 | }
273 | }
274 |
275 | Future wait() async {
276 | // Send any remaining buffers.
277 | flush();
278 | // Wait for the buffers back.
279 | for (var i = 0; i < _bufferCount; i++) {
280 | await _bufferSemaphore.acquire();
281 | }
282 | for (var i = 0; i < _bufferCount; i++) {
283 | _bufferSemaphore.release();
284 | }
285 | }
286 |
287 | Future close() async {
288 | if (_hWaveOut != null) {
289 | _closeCompleter = Completer();
290 | _throwOnError(() => waveOutClose(_hWaveOut!));
291 | await _closeCompleter?.future;
292 | }
293 | }
294 |
295 | // Volume Control
296 |
297 | // Get current volume combined
298 | double get volume => volumeLR.jointStereo;
299 |
300 | set volume(double value) => volumeLR = LRVolume(value, value);
301 |
302 | /// Get current volume for both channels
303 | LRVolume get volumeLR {
304 | assert(_hWaveOut != null, 'Device not open');
305 | assert(supportsVolumeControl, 'Device does not support volume control.');
306 | final result = calloc();
307 | try {
308 | _throwOnError(() => waveOutGetVolume(_hWaveOut!, result));
309 | final volume = LRVolume.from32bit(result.value);
310 | if (!supportsLRVolumeControl) {
311 | // https://docs.microsoft.com/en-us/windows/win32/api/mmeapi/nf-mmeapi-waveoutgetvolume
312 | // If a device does not support both left and right volume control, the
313 | // low-order word of the specified location contains the mono volume level.
314 | return LRVolume(volume.left, volume.left);
315 | } else {
316 | return volume;
317 | }
318 | } finally {
319 | free(result);
320 | }
321 | }
322 |
323 | set volumeLR(LRVolume value) {
324 | assert(_hWaveOut != null, 'Device not open');
325 | assert(supportsVolumeControl, 'Device does not support volume control.');
326 | _throwOnError(() => waveOutSetVolume(_hWaveOut!, value.volume32bit));
327 | }
328 |
329 | // Pitch Control
330 |
331 | double get pitch {
332 | assert(_hWaveOut != null, 'Device not open');
333 | final result = calloc();
334 | try {
335 | _throwOnError(() => waveOutGetPitch(_hWaveOut!, result));
336 | return _fixedToDouble(result.value);
337 | } finally {
338 | free(result);
339 | }
340 | }
341 |
342 | set pitch(double value) {
343 | assert(_hWaveOut != null, 'Device not open');
344 | assert(value >= 0, 'Cannot set pitch to negative value');
345 | _throwOnError(() => waveOutSetPitch(_hWaveOut!, _doubleToFixed(value)));
346 | }
347 |
348 | // Playback Rate Control
349 |
350 | double get playbackRate {
351 | assert(_hWaveOut != null, 'Device not open');
352 | final result = calloc();
353 | try {
354 | _throwOnError(() => waveOutGetPlaybackRate(_hWaveOut!, result));
355 | return _fixedToDouble(result.value);
356 | } finally {
357 | free(result);
358 | }
359 | }
360 |
361 | set playbackRate(double value) {
362 | assert(_hWaveOut != null, 'Device not open');
363 | assert(value >= 0, 'Cannot set playback rate to negative value');
364 | _throwOnError(() => waveOutSetPlaybackRate(_hWaveOut!, _doubleToFixed(value)));
365 | }
366 |
367 | /// Get position in samples-per-channel
368 | int get position {
369 | assert(_hWaveOut != null, 'Device not open');
370 | final result = calloc()..ref.wType = TIME_SAMPLES;
371 | try {
372 | _throwOnError(
373 | () => waveOutGetPosition(_hWaveOut!, result, sizeOf()),
374 | );
375 | return result.ref.u.sample;
376 | } finally {
377 | free(result);
378 | }
379 | }
380 |
381 | // Playback Control
382 |
383 | /// Pause playback
384 | void pause() {
385 | assert(_hWaveOut != null, 'Device not open');
386 | _throwOnError(() => waveOutPause(_hWaveOut!));
387 | }
388 |
389 | /// Restart paused playback
390 | void restart() {
391 | assert(_hWaveOut != null, 'Device not open');
392 | _throwOnError(() => waveOutRestart(_hWaveOut!));
393 | }
394 |
395 | /// Reset device
396 | /// Halt's all activity and generates a WOM_DONE message for all buffers in chain.
397 | void reset() {
398 | assert(_hWaveOut != null, 'Device not open');
399 | _throwOnError(() => waveOutReset(_hWaveOut!));
400 | }
401 |
402 | void breakLoop() {
403 | assert(_hWaveOut != null, 'Device not open');
404 | _throwOnError(() => waveOutBreakLoop(_hWaveOut!));
405 | }
406 |
407 | // Convert double to 32-bit (16.16) fixed-point
408 | int _doubleToFixed(double input) => (input * (1 << 16)).round();
409 |
410 | // Convert 32-bit (16.16) fixed-point to double
411 | double _fixedToDouble(int input) => input.toDouble() / (1 << 16);
412 | }
413 |
414 | class WaveBuffer {
415 | WaveBuffer(this.waveOut, this._index, this._bufferSizeInBytes) {
416 | _waveHdr = calloc();
417 | _waveHdr.ref
418 | ..lpData = (calloc(_bufferSizeInBytes)).cast()
419 | ..dwUser = _index;
420 | }
421 |
422 | final WaveOut waveOut;
423 | final int _index;
424 | final int _bufferSizeInBytes;
425 | late Pointer _waveHdr;
426 | int _bytesUsed = 0;
427 |
428 | int? get _hDeviceOut => waveOut._hWaveOut;
429 |
430 | bool get done => (_waveHdr.ref.dwFlags & WHDR_DONE) != 0;
431 |
432 | Future wait() {
433 | // FIXME
434 | return Future.doWhile(
435 | () => Future.delayed(const Duration(milliseconds: 10), () => !done),
436 | );
437 | }
438 |
439 | void dispose() {
440 | if ((_waveHdr.ref.dwFlags & WHDR_PREPARED) != 0) {
441 | _throwOnError(
442 | () => waveOutUnprepareHeader(_hDeviceOut!, _waveHdr, sizeOf()),
443 | );
444 | }
445 | free(_waveHdr.ref.lpData);
446 | free(_waveHdr);
447 | }
448 |
449 | void flush() {
450 | assert(_hDeviceOut != null, 'Device not opened');
451 | _waveHdr.ref.dwBufferLength = _bytesUsed;
452 | _bytesUsed = 0;
453 | if ((_waveHdr.ref.dwFlags & WHDR_PREPARED) == 0) {
454 | _throwOnError(
455 | () => waveOutPrepareHeader(_hDeviceOut!, _waveHdr, sizeOf()),
456 | );
457 | }
458 | _throwOnError(
459 | () => waveOutWrite(_hDeviceOut!, _waveHdr, sizeOf()),
460 | );
461 | }
462 |
463 | bool write(Uint8List data, List bytesWritten) {
464 | final bytes = math.min(_bufferSizeInBytes - _bytesUsed, data.length);
465 | final buffer = _waveHdr.ref.lpData.cast().asTypedList(_bufferSizeInBytes);
466 | buffer.setRange(_bytesUsed, _bytesUsed + bytes, data);
467 | _bytesUsed += bytes;
468 | bytesWritten[0] = bytes;
469 | if (_bytesUsed == _bufferSizeInBytes) {
470 | flush();
471 | return true;
472 | }
473 | return false;
474 | }
475 | }
476 |
477 | class LRVolume {
478 | const LRVolume(this.left, this.right);
479 |
480 | LRVolume.from32bit(int value)
481 | : left = LOWORD(value) / 0xFFFF,
482 | right = HIWORD(value) / 0xFFFF;
483 |
484 | final double left;
485 | final double right;
486 |
487 | double get jointStereo => (left + right) / 2;
488 |
489 | int get left16bit => (left * 0xFFFF).toInt();
490 |
491 | int get right16bit => (right * 0xFFFF).toInt();
492 |
493 | int get volume32bit => MAKELONG(left16bit, right16bit);
494 |
495 | @override
496 | String toString() => 'LRVolume{$left, $right => 0x${volume32bit.toHexString(32)}}';
497 | }
498 |
499 | class SupportedWaveFormat {
500 | static const formatPcm08kM08b = SupportedWaveFormat(0x00000000, 8000, 1, 8);
501 | static const formatPcm08kS08b = SupportedWaveFormat(0x00000000, 8000, 2, 8);
502 | static const formatPcm08kM16b = SupportedWaveFormat(0x00000000, 8000, 1, 16);
503 | static const formatPcm08kS16b = SupportedWaveFormat(0x00000000, 8000, 2, 16);
504 | static const formatPcm11kM08b = SupportedWaveFormat(0x00000001, 11025, 1, 8);
505 | static const formatPcm11kS08b = SupportedWaveFormat(0x00000002, 11025, 2, 8);
506 | static const formatPcm11kM16b = SupportedWaveFormat(0x00000004, 11025, 1, 16);
507 | static const formatPcm11kS16b = SupportedWaveFormat(0x00000008, 11025, 2, 16);
508 | static const formatPcm22kM08b = SupportedWaveFormat(0x00000010, 22050, 1, 8);
509 | static const formatPcm22kS08b = SupportedWaveFormat(0x00000020, 22050, 2, 8);
510 | static const formatPcm22kM16b = SupportedWaveFormat(0x00000040, 22050, 1, 16);
511 | static const formatPcm22kS16b = SupportedWaveFormat(0x00000080, 22050, 2, 16);
512 | static const formatPcm44kM08b = SupportedWaveFormat(0x00000100, 44100, 1, 8);
513 | static const formatPcm44kS08b = SupportedWaveFormat(0x00000200, 44100, 2, 8);
514 | static const formatPcm44kM16b = SupportedWaveFormat(0x00000400, 44100, 1, 16);
515 | static const formatPcm44kS16b = SupportedWaveFormat(0x00000800, 44100, 2, 16);
516 | static const formatPcm48kM08b = SupportedWaveFormat(0x00001000, 48000, 1, 8);
517 | static const formatPcm48kS08b = SupportedWaveFormat(0x00002000, 48000, 2, 8);
518 | static const formatPcm48kM16b = SupportedWaveFormat(0x00004000, 48000, 1, 16);
519 | static const formatPcm48kS16b = SupportedWaveFormat(0x00008000, 48000, 2, 16);
520 | static const formatPcm96kM08b = SupportedWaveFormat(0x00010000, 96000, 1, 8);
521 | static const formatPcm96kS08b = SupportedWaveFormat(0x00020000, 96000, 2, 8);
522 | static const formatPcm96kM16b = SupportedWaveFormat(0x00040000, 96000, 1, 16);
523 | static const formatPcm96kS16b = SupportedWaveFormat(0x00080000, 96000, 2, 16);
524 | static const formatFloat44kM32b =
525 | SupportedWaveFormat(0x00000000, 44100, 1, 32, WAVE_FORMAT_IEEE_FLOAT);
526 |
527 | const SupportedWaveFormat(this.id, this.sampleRate, this.channelCount, this.bitPerSample,
528 | [this.tag = WAVE_FORMAT_PCM]);
529 |
530 | final int tag;
531 | final int id;
532 | final int sampleRate;
533 | final int channelCount;
534 | final int bitPerSample;
535 |
536 | int get avgBytesPerSecond => (sampleRate * channelCount * bitPerSample) ~/ 8;
537 |
538 | int get blockAlign => (channelCount * bitPerSample) ~/ 8;
539 |
540 | Pointer _allocatePcmWaveFormat() {
541 | final waveFormatEx = calloc();
542 | waveFormatEx.ref
543 | ..wFormatTag = tag
544 | ..nChannels = channelCount
545 | ..nSamplesPerSec = sampleRate
546 | ..nAvgBytesPerSec = avgBytesPerSecond
547 | ..nBlockAlign = blockAlign
548 | ..wBitsPerSample = bitPerSample;
549 | return waveFormatEx;
550 | }
551 |
552 | @override
553 | String toString() =>
554 | 'SupportedWaveFormat{$tag, ${sampleRate / 1000}kHz, $channelCount, $bitPerSample-bit}';
555 |
556 | static List fromBitField(int value) =>
557 | values.where((el) => value & el.id != 0).toList(growable: false);
558 |
559 | static const values = [
560 | formatPcm08kM08b,
561 | formatPcm08kS08b,
562 | formatPcm08kM16b,
563 | formatPcm08kS16b,
564 | formatPcm11kM08b,
565 | formatPcm11kS08b,
566 | formatPcm11kM16b,
567 | formatPcm11kS16b,
568 | formatPcm22kM08b,
569 | formatPcm22kS08b,
570 | formatPcm22kM16b,
571 | formatPcm22kS16b,
572 | formatPcm44kM08b,
573 | formatPcm44kS08b,
574 | formatPcm44kM16b,
575 | formatPcm44kS16b,
576 | formatPcm48kM08b,
577 | formatPcm48kS08b,
578 | formatPcm48kM16b,
579 | formatPcm48kS16b,
580 | formatPcm96kM08b,
581 | formatPcm96kS08b,
582 | formatPcm96kM16b,
583 | formatPcm96kS16b,
584 | ];
585 | }
586 |
587 | class MmException implements Exception {
588 | const MmException(this.code) : assert(code != 0);
589 |
590 | final int code;
591 |
592 | @override
593 | String toString() => 'MmException{$message}';
594 |
595 | String get message {
596 | final buffer = calloc(256).cast();
597 | try {
598 | final result = waveOutGetErrorText(code, buffer, 256);
599 | if (result == MMSYSERR_NOERROR) {
600 | final message = buffer.toDartString(length: result);
601 | if (message.isNotEmpty) {
602 | return message;
603 | }
604 | }
605 | switch (code) {
606 | case MMSYSERR_NOERROR:
607 | return 'no error';
608 | case MMSYSERR_ERROR:
609 | return 'unspecified error';
610 | case MMSYSERR_BADDEVICEID:
611 | return 'device ID out of range';
612 | case MMSYSERR_NOTENABLED:
613 | return 'driver failed enable';
614 | case MMSYSERR_ALLOCATED:
615 | return 'device already allocated';
616 | case MMSYSERR_INVALHANDLE:
617 | return 'device handle is invalid';
618 | case MMSYSERR_NODRIVER:
619 | return 'no device driver present';
620 | case MMSYSERR_NOMEM:
621 | return 'memory allocation error';
622 | case MMSYSERR_NOTSUPPORTED:
623 | return "function isn't supported";
624 | case MMSYSERR_BADERRNUM:
625 | return 'error value out of range';
626 | case MMSYSERR_INVALFLAG:
627 | return 'invalid flag passed';
628 | case MMSYSERR_INVALPARAM:
629 | return 'invalid parameter passed';
630 | case MMSYSERR_HANDLEBUSY:
631 | return 'handle being used simultaneously on another thread (eg callback)';
632 | case MMSYSERR_INVALIDALIAS:
633 | return 'specified alias not found';
634 | case MMSYSERR_BADDB:
635 | return 'bad registry database';
636 | case MMSYSERR_KEYNOTFOUND:
637 | return 'registry key not found';
638 | case MMSYSERR_READERROR:
639 | return 'registry read error';
640 | case MMSYSERR_WRITEERROR:
641 | return 'registry write error';
642 | case MMSYSERR_DELETEERROR:
643 | return 'registry delete error';
644 | case MMSYSERR_VALNOTFOUND:
645 | return 'registry value not found';
646 | case MMSYSERR_NODRIVERCB:
647 | return 'driver does not call DriverCallback';
648 | case MMSYSERR_MOREDATA:
649 | return 'more data to be returned';
650 | case WAVERR_BADFORMAT:
651 | return 'unsupported wave format';
652 | case WAVERR_STILLPLAYING:
653 | return 'still something playing';
654 | case WAVERR_UNPREPARED:
655 | return 'header not prepared';
656 | case WAVERR_SYNC:
657 | return 'device is synchronous';
658 | default:
659 | return 'Error [$code]>';
660 | }
661 | } finally {
662 | free(buffer);
663 | }
664 | }
665 | }
666 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "2.12.0"
12 | boolean_selector:
13 | dependency: transitive
14 | description:
15 | name: boolean_selector
16 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "2.1.2"
20 | characters:
21 | dependency: transitive
22 | description:
23 | name: characters
24 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "1.4.0"
28 | clock:
29 | dependency: transitive
30 | description:
31 | name: clock
32 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "1.1.2"
36 | collection:
37 | dependency: transitive
38 | description:
39 | name: collection
40 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "1.19.1"
44 | fake_async:
45 | dependency: transitive
46 | description:
47 | name: fake_async
48 | sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.3.2"
52 | ffi:
53 | dependency: "direct main"
54 | description:
55 | name: ffi
56 | sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
57 | url: "https://pub.dev"
58 | source: hosted
59 | version: "2.1.3"
60 | flutter:
61 | dependency: "direct main"
62 | description: flutter
63 | source: sdk
64 | version: "0.0.0"
65 | flutter_lints:
66 | dependency: "direct dev"
67 | description:
68 | name: flutter_lints
69 | sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
70 | url: "https://pub.dev"
71 | source: hosted
72 | version: "5.0.0"
73 | flutter_test:
74 | dependency: "direct dev"
75 | description: flutter
76 | source: sdk
77 | version: "0.0.0"
78 | flutter_web_plugins:
79 | dependency: "direct main"
80 | description: flutter
81 | source: sdk
82 | version: "0.0.0"
83 | leak_tracker:
84 | dependency: transitive
85 | description:
86 | name: leak_tracker
87 | sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
88 | url: "https://pub.dev"
89 | source: hosted
90 | version: "10.0.8"
91 | leak_tracker_flutter_testing:
92 | dependency: transitive
93 | description:
94 | name: leak_tracker_flutter_testing
95 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
96 | url: "https://pub.dev"
97 | source: hosted
98 | version: "3.0.9"
99 | leak_tracker_testing:
100 | dependency: transitive
101 | description:
102 | name: leak_tracker_testing
103 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
104 | url: "https://pub.dev"
105 | source: hosted
106 | version: "3.0.1"
107 | lints:
108 | dependency: transitive
109 | description:
110 | name: lints
111 | sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
112 | url: "https://pub.dev"
113 | source: hosted
114 | version: "5.1.1"
115 | matcher:
116 | dependency: transitive
117 | description:
118 | name: matcher
119 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
120 | url: "https://pub.dev"
121 | source: hosted
122 | version: "0.12.17"
123 | material_color_utilities:
124 | dependency: transitive
125 | description:
126 | name: material_color_utilities
127 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
128 | url: "https://pub.dev"
129 | source: hosted
130 | version: "0.11.1"
131 | meta:
132 | dependency: transitive
133 | description:
134 | name: meta
135 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
136 | url: "https://pub.dev"
137 | source: hosted
138 | version: "1.16.0"
139 | path:
140 | dependency: transitive
141 | description:
142 | name: path
143 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
144 | url: "https://pub.dev"
145 | source: hosted
146 | version: "1.9.1"
147 | semaphore_plus:
148 | dependency: "direct main"
149 | description:
150 | name: semaphore_plus
151 | sha256: "2e05324ace8f4db9aa7ac9a9fa77e97082da4b0caff692e90a2c6f780658878f"
152 | url: "https://pub.dev"
153 | source: hosted
154 | version: "0.3.1"
155 | sky_engine:
156 | dependency: transitive
157 | description: flutter
158 | source: sdk
159 | version: "0.0.0"
160 | source_span:
161 | dependency: transitive
162 | description:
163 | name: source_span
164 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
165 | url: "https://pub.dev"
166 | source: hosted
167 | version: "1.10.1"
168 | stack_trace:
169 | dependency: transitive
170 | description:
171 | name: stack_trace
172 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
173 | url: "https://pub.dev"
174 | source: hosted
175 | version: "1.12.1"
176 | stream_channel:
177 | dependency: transitive
178 | description:
179 | name: stream_channel
180 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
181 | url: "https://pub.dev"
182 | source: hosted
183 | version: "2.1.4"
184 | string_scanner:
185 | dependency: transitive
186 | description:
187 | name: string_scanner
188 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
189 | url: "https://pub.dev"
190 | source: hosted
191 | version: "1.4.1"
192 | term_glyph:
193 | dependency: transitive
194 | description:
195 | name: term_glyph
196 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
197 | url: "https://pub.dev"
198 | source: hosted
199 | version: "1.2.2"
200 | test_api:
201 | dependency: transitive
202 | description:
203 | name: test_api
204 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
205 | url: "https://pub.dev"
206 | source: hosted
207 | version: "0.7.4"
208 | vector_math:
209 | dependency: transitive
210 | description:
211 | name: vector_math
212 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
213 | url: "https://pub.dev"
214 | source: hosted
215 | version: "2.1.4"
216 | vm_service:
217 | dependency: transitive
218 | description:
219 | name: vm_service
220 | sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
221 | url: "https://pub.dev"
222 | source: hosted
223 | version: "14.3.1"
224 | web:
225 | dependency: "direct main"
226 | description:
227 | name: web
228 | sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
229 | url: "https://pub.dev"
230 | source: hosted
231 | version: "1.1.0"
232 | win32:
233 | dependency: "direct main"
234 | description:
235 | name: win32
236 | sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
237 | url: "https://pub.dev"
238 | source: hosted
239 | version: "5.10.1"
240 | z80:
241 | dependency: "direct main"
242 | description:
243 | name: z80
244 | sha256: f28cc12edd6755e582eb71925fc050a1a8cac486511fb5abaead0db11bf75d32
245 | url: "https://pub.dev"
246 | source: hosted
247 | version: "1.0.0"
248 | sdks:
249 | dart: ">=3.7.0-0 <4.0.0"
250 | flutter: ">=3.27.0"
251 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: pacman
2 | description: Pacman Emulator in Flutter
3 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
4 | version: 1.0.0+1
5 |
6 | environment:
7 | sdk: ">=3.6.0 <4.0.0"
8 | flutter: ">=3.27.0"
9 |
10 | dependencies:
11 | z80: ^1.0.0
12 | win32: ^5.10.1
13 | semaphore_plus: ^0.3.1
14 | web: ^1.0.0
15 | ffi: ^2.1.3
16 | flutter:
17 | sdk: flutter
18 | flutter_web_plugins:
19 | sdk: flutter
20 |
21 | dev_dependencies:
22 | flutter_lints: ^5.0.0
23 | flutter_test:
24 | sdk: flutter
25 |
26 | flutter:
27 | uses-material-design: true
28 | assets:
29 | - assets/
30 | - assets/roms/
31 |
--------------------------------------------------------------------------------
/web/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/web/favicon.ico
--------------------------------------------------------------------------------
/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/web/favicon.png
--------------------------------------------------------------------------------
/web/icons/pac-man-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/web/icons/pac-man-128x128.png
--------------------------------------------------------------------------------
/web/icons/pac-man-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/web/icons/pac-man-256x256.png
--------------------------------------------------------------------------------
/web/icons/pac-man-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/web/icons/pac-man-512x512.png
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Pacman Emulator in Flutter
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Pacman",
3 | "short_name": "Pacman",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#000000",
7 | "theme_color": "#FFE311",
8 | "description": "Pacman Emulator in Flutter",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/pac-man-128x128.png",
14 | "sizes": "128x128",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/pac-man-256x256.png",
19 | "sizes": "256x256",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/pac-man-512x512.png",
24 | "sizes": "512x512",
25 | "type": "image/png"
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/windows/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral/
2 |
3 | # Visual Studio user-specific files.
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Visual Studio build-related files.
10 | x64/
11 | x86/
12 |
13 | # Visual Studio cache files
14 | # files ending in .cache can be ignored
15 | *.[Cc]ache
16 | # but keep track of directories ending in .cache
17 | !*.[Cc]ache/
18 |
--------------------------------------------------------------------------------
/windows/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 | project(pacman LANGUAGES CXX)
3 |
4 | set(BINARY_NAME "pacman")
5 |
6 | cmake_policy(SET CMP0063 NEW)
7 |
8 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
9 |
10 | # Configure build options.
11 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
12 | if(IS_MULTICONFIG)
13 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
14 | CACHE STRING "" FORCE)
15 | else()
16 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
17 | set(CMAKE_BUILD_TYPE "Debug" CACHE
18 | STRING "Flutter build mode" FORCE)
19 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
20 | "Debug" "Profile" "Release")
21 | endif()
22 | endif()
23 |
24 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
25 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
26 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
27 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
28 |
29 | # Use Unicode for all projects.
30 | add_definitions(-DUNICODE -D_UNICODE)
31 |
32 | # Compilation settings that should be applied to most targets.
33 | function(APPLY_STANDARD_SETTINGS TARGET)
34 | target_compile_features(${TARGET} PUBLIC cxx_std_17)
35 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
36 | target_compile_options(${TARGET} PRIVATE /EHsc)
37 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
38 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>")
39 | endfunction()
40 |
41 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
42 |
43 | # Flutter library and tool build rules.
44 | add_subdirectory(${FLUTTER_MANAGED_DIR})
45 |
46 | # Application build
47 | add_subdirectory("runner")
48 |
49 | # Generated plugin build rules, which manage building the plugins and adding
50 | # them to the application.
51 | include(flutter/generated_plugins.cmake)
52 |
53 |
54 | # === Installation ===
55 | # Support files are copied into place next to the executable, so that it can
56 | # run in place. This is done instead of making a separate bundle (as on Linux)
57 | # so that building and running from within Visual Studio will work.
58 | set(BUILD_BUNDLE_DIR "$")
59 | # Make the "install" step default, as it's required to run.
60 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
61 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
62 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
63 | endif()
64 |
65 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
66 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
67 |
68 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
69 | COMPONENT Runtime)
70 |
71 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
72 | COMPONENT Runtime)
73 |
74 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
75 | COMPONENT Runtime)
76 |
77 | if(PLUGIN_BUNDLED_LIBRARIES)
78 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
79 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
80 | COMPONENT Runtime)
81 | endif()
82 |
83 | # Fully re-copy the assets directory on each build to avoid having stale files
84 | # from a previous install.
85 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
86 | install(CODE "
87 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
88 | " COMPONENT Runtime)
89 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
90 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
91 |
92 | # Install the AOT library on non-Debug builds only.
93 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
94 | CONFIGURATIONS Profile;Release
95 | COMPONENT Runtime)
96 |
--------------------------------------------------------------------------------
/windows/flutter/.template_version:
--------------------------------------------------------------------------------
1 | 7
2 |
--------------------------------------------------------------------------------
/windows/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 |
3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
4 |
5 | # Configuration provided via flutter tool.
6 | include(${EPHEMERAL_DIR}/generated_config.cmake)
7 |
8 | # TODO: Move the rest of this into files in ephemeral. See
9 | # https://github.com/flutter/flutter/issues/57146.
10 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
11 |
12 | # === Flutter Library ===
13 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
14 |
15 | # Published to parent scope for install step.
16 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
17 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
18 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
19 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
20 |
21 | list(APPEND FLUTTER_LIBRARY_HEADERS
22 | "flutter_export.h"
23 | "flutter_windows.h"
24 | "flutter_messenger.h"
25 | "flutter_plugin_registrar.h"
26 | )
27 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
28 | add_library(flutter INTERFACE)
29 | target_include_directories(flutter INTERFACE
30 | "${EPHEMERAL_DIR}"
31 | )
32 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
33 | add_dependencies(flutter flutter_assemble)
34 |
35 | # === Wrapper ===
36 | list(APPEND CPP_WRAPPER_SOURCES_CORE
37 | "core_implementations.cc"
38 | "standard_codec.cc"
39 | )
40 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
41 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
42 | "plugin_registrar.cc"
43 | )
44 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
45 | list(APPEND CPP_WRAPPER_SOURCES_APP
46 | "flutter_engine.cc"
47 | "flutter_view_controller.cc"
48 | )
49 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
50 |
51 | # Wrapper sources needed for a plugin.
52 | add_library(flutter_wrapper_plugin STATIC
53 | ${CPP_WRAPPER_SOURCES_CORE}
54 | ${CPP_WRAPPER_SOURCES_PLUGIN}
55 | )
56 | apply_standard_settings(flutter_wrapper_plugin)
57 | set_target_properties(flutter_wrapper_plugin PROPERTIES
58 | POSITION_INDEPENDENT_CODE ON)
59 | set_target_properties(flutter_wrapper_plugin PROPERTIES
60 | CXX_VISIBILITY_PRESET hidden)
61 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
62 | target_include_directories(flutter_wrapper_plugin PUBLIC
63 | "${WRAPPER_ROOT}/include"
64 | )
65 | add_dependencies(flutter_wrapper_plugin flutter_assemble)
66 |
67 | # Wrapper sources needed for the runner.
68 | add_library(flutter_wrapper_app STATIC
69 | ${CPP_WRAPPER_SOURCES_CORE}
70 | ${CPP_WRAPPER_SOURCES_APP}
71 | )
72 | apply_standard_settings(flutter_wrapper_app)
73 | target_link_libraries(flutter_wrapper_app PUBLIC flutter)
74 | target_include_directories(flutter_wrapper_app PUBLIC
75 | "${WRAPPER_ROOT}/include"
76 | )
77 | add_dependencies(flutter_wrapper_app flutter_assemble)
78 |
79 | # === Flutter tool backend ===
80 | # _phony_ is a non-existent file to force this command to run every time,
81 | # since currently there's no way to get a full input/output list from the
82 | # flutter tool.
83 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
84 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
85 | add_custom_command(
86 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
87 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
88 | ${CPP_WRAPPER_SOURCES_APP}
89 | ${PHONY_OUTPUT}
90 | COMMAND ${CMAKE_COMMAND} -E env
91 | ${FLUTTER_TOOL_ENVIRONMENT}
92 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
93 | windows-x64 $
94 | VERBATIM
95 | )
96 | add_custom_target(flutter_assemble DEPENDS
97 | "${FLUTTER_LIBRARY}"
98 | ${FLUTTER_LIBRARY_HEADERS}
99 | ${CPP_WRAPPER_SOURCES_CORE}
100 | ${CPP_WRAPPER_SOURCES_PLUGIN}
101 | ${CPP_WRAPPER_SOURCES_APP}
102 | )
103 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 |
10 | void RegisterPlugins(flutter::PluginRegistry* registry) {
11 | }
12 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void RegisterPlugins(flutter::PluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | )
7 |
8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
9 | )
10 |
11 | set(PLUGIN_BUNDLED_LIBRARIES)
12 |
13 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
18 | endforeach(plugin)
19 |
20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
23 | endforeach(ffi_plugin)
24 |
--------------------------------------------------------------------------------
/windows/runner/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 | project(runner LANGUAGES CXX)
3 |
4 | add_executable(${BINARY_NAME} WIN32
5 | "flutter_window.cpp"
6 | "main.cpp"
7 | "run_loop.cpp"
8 | "utils.cpp"
9 | "win32_window.cpp"
10 | "window_configuration.cpp"
11 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
12 | "Runner.rc"
13 | "runner.exe.manifest"
14 | )
15 | apply_standard_settings(${BINARY_NAME})
16 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
17 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
18 | add_dependencies(${BINARY_NAME} flutter_assemble)
19 |
--------------------------------------------------------------------------------
/windows/runner/Runner.rc:
--------------------------------------------------------------------------------
1 | // Microsoft Visual C++ generated resource script.
2 | //
3 | #pragma code_page(65001)
4 | #include "resource.h"
5 |
6 | #define APSTUDIO_READONLY_SYMBOLS
7 | /////////////////////////////////////////////////////////////////////////////
8 | //
9 | // Generated from the TEXTINCLUDE 2 resource.
10 | //
11 | #include "winres.h"
12 |
13 | /////////////////////////////////////////////////////////////////////////////
14 | #undef APSTUDIO_READONLY_SYMBOLS
15 |
16 | /////////////////////////////////////////////////////////////////////////////
17 | // English (United States) resources
18 |
19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
21 |
22 | #ifdef APSTUDIO_INVOKED
23 | /////////////////////////////////////////////////////////////////////////////
24 | //
25 | // TEXTINCLUDE
26 | //
27 |
28 | 1 TEXTINCLUDE
29 | BEGIN
30 | "resource.h\0"
31 | END
32 |
33 | 2 TEXTINCLUDE
34 | BEGIN
35 | "#include ""winres.h""\r\n"
36 | "\0"
37 | END
38 |
39 | 3 TEXTINCLUDE
40 | BEGIN
41 | "\r\n"
42 | "\0"
43 | END
44 |
45 | #endif // APSTUDIO_INVOKED
46 |
47 |
48 | /////////////////////////////////////////////////////////////////////////////
49 | //
50 | // Icon
51 | //
52 |
53 | // Icon with lowest ID value placed first to ensure application icon
54 | // remains consistent on all systems.
55 | IDI_APP_ICON ICON "resources\\app_icon.ico"
56 |
57 | #endif // English (United States) resources
58 | /////////////////////////////////////////////////////////////////////////////
59 |
60 |
61 |
62 | #ifndef APSTUDIO_INVOKED
63 | /////////////////////////////////////////////////////////////////////////////
64 | //
65 | // Generated from the TEXTINCLUDE 3 resource.
66 | //
67 |
68 |
69 | /////////////////////////////////////////////////////////////////////////////
70 | #endif // not APSTUDIO_INVOKED
71 |
--------------------------------------------------------------------------------
/windows/runner/flutter_window.cpp:
--------------------------------------------------------------------------------
1 | #include "flutter_window.h"
2 |
3 | #include
4 |
5 | #include "flutter/generated_plugin_registrant.h"
6 |
7 | FlutterWindow::FlutterWindow(RunLoop* run_loop,
8 | const flutter::DartProject& project)
9 | : run_loop_(run_loop), project_(project) {}
10 |
11 | FlutterWindow::~FlutterWindow() {}
12 |
13 | bool FlutterWindow::OnCreate() {
14 | if (!Win32Window::OnCreate()) {
15 | return false;
16 | }
17 |
18 | RECT frame = GetClientArea();
19 |
20 | // The size here must match the window dimensions to avoid unnecessary surface
21 | // creation / destruction in the startup path.
22 | flutter_controller_ = std::make_unique(
23 | frame.right - frame.left, frame.bottom - frame.top, project_);
24 | // Ensure that basic setup of the controller was successful.
25 | if (!flutter_controller_->engine() || !flutter_controller_->view()) {
26 | return false;
27 | }
28 | RegisterPlugins(flutter_controller_->engine());
29 | run_loop_->RegisterFlutterInstance(flutter_controller_->engine());
30 | SetChildContent(flutter_controller_->view()->GetNativeWindow());
31 | return true;
32 | }
33 |
34 | void FlutterWindow::OnDestroy() {
35 | if (flutter_controller_) {
36 | run_loop_->UnregisterFlutterInstance(flutter_controller_->engine());
37 | flutter_controller_ = nullptr;
38 | }
39 |
40 | Win32Window::OnDestroy();
41 | }
42 |
43 | LRESULT
44 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
45 | WPARAM const wparam,
46 | LPARAM const lparam) noexcept {
47 | // Give Flutter, including plugins, an opporutunity to handle window messages.
48 | if (flutter_controller_) {
49 | std::optional result =
50 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
51 | lparam);
52 | if (result) {
53 | return *result;
54 | }
55 | }
56 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
57 | }
58 |
--------------------------------------------------------------------------------
/windows/runner/flutter_window.h:
--------------------------------------------------------------------------------
1 | #ifndef FLUTTER_WINDOW_H_
2 | #define FLUTTER_WINDOW_H_
3 |
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | #include "run_loop.h"
10 | #include "win32_window.h"
11 |
12 | // A window that does nothing but host a Flutter view.
13 | class FlutterWindow : public Win32Window {
14 | public:
15 | // Creates a new FlutterWindow driven by the |run_loop|, hosting a
16 | // Flutter view running |project|.
17 | explicit FlutterWindow(RunLoop* run_loop,
18 | const flutter::DartProject& project);
19 | virtual ~FlutterWindow();
20 |
21 | protected:
22 | // Win32Window:
23 | bool OnCreate() override;
24 | void OnDestroy() override;
25 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
26 | LPARAM const lparam) noexcept override;
27 |
28 | private:
29 | // The run loop driving events for this window.
30 | RunLoop* run_loop_;
31 |
32 | // The project to run.
33 | flutter::DartProject project_;
34 |
35 | // The Flutter instance hosted by this window.
36 | std::unique_ptr flutter_controller_;
37 | };
38 |
39 | #endif // FLUTTER_WINDOW_H_
40 |
--------------------------------------------------------------------------------
/windows/runner/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "flutter_window.h"
6 | #include "run_loop.h"
7 | #include "utils.h"
8 | #include "window_configuration.h"
9 |
10 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
11 | _In_ wchar_t *command_line, _In_ int show_command) {
12 | // Attach to console when present (e.g., 'flutter run') or create a
13 | // new console when running with a debugger.
14 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
15 | CreateAndAttachConsole();
16 | }
17 |
18 | // Initialize COM, so that it is available for use in the library and/or
19 | // plugins.
20 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
21 |
22 | RunLoop run_loop;
23 |
24 | flutter::DartProject project(L"data");
25 | FlutterWindow window(&run_loop, project);
26 | Win32Window::Point origin(kFlutterWindowOriginX, kFlutterWindowOriginY);
27 | Win32Window::Size size(kFlutterWindowWidth, kFlutterWindowHeight);
28 | if (!window.CreateAndShow(kFlutterWindowTitle, origin, size)) {
29 | return EXIT_FAILURE;
30 | }
31 | window.SetQuitOnClose(true);
32 |
33 | run_loop.Run();
34 |
35 | ::CoUninitialize();
36 | return EXIT_SUCCESS;
37 | }
38 |
--------------------------------------------------------------------------------
/windows/runner/resource.h:
--------------------------------------------------------------------------------
1 | //{{NO_DEPENDENCIES}}
2 | // Microsoft Visual C++ generated include file.
3 | // Used by Runner.rc
4 | //
5 | #define IDI_APP_ICON 101
6 |
7 | // Next default values for new objects
8 | //
9 | #ifdef APSTUDIO_INVOKED
10 | #ifndef APSTUDIO_READONLY_SYMBOLS
11 | #define _APS_NEXT_RESOURCE_VALUE 102
12 | #define _APS_NEXT_COMMAND_VALUE 40001
13 | #define _APS_NEXT_CONTROL_VALUE 1001
14 | #define _APS_NEXT_SYMED_VALUE 101
15 | #endif
16 | #endif
17 |
--------------------------------------------------------------------------------
/windows/runner/resources/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slightfoot/flutter-pacman/6150aaa7eaffedee4b9d09b2238ff22040235986/windows/runner/resources/app_icon.ico
--------------------------------------------------------------------------------
/windows/runner/run_loop.cpp:
--------------------------------------------------------------------------------
1 | #include "run_loop.h"
2 |
3 | #include
4 | // Don't stomp std::min/std::max
5 | #undef max
6 | #undef min
7 |
8 | #include
9 |
10 | RunLoop::RunLoop() {}
11 |
12 | RunLoop::~RunLoop() {}
13 |
14 | void RunLoop::Run() {
15 | bool keep_running = true;
16 | TimePoint next_flutter_event_time = TimePoint::clock::now();
17 | while (keep_running) {
18 | std::chrono::nanoseconds wait_duration =
19 | std::max(std::chrono::nanoseconds(0),
20 | next_flutter_event_time - TimePoint::clock::now());
21 | ::MsgWaitForMultipleObjects(
22 | 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000),
23 | QS_ALLINPUT);
24 | bool processed_events = false;
25 | MSG message;
26 | // All pending Windows messages must be processed; MsgWaitForMultipleObjects
27 | // won't return again for items left in the queue after PeekMessage.
28 | while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
29 | processed_events = true;
30 | if (message.message == WM_QUIT) {
31 | keep_running = false;
32 | break;
33 | }
34 | ::TranslateMessage(&message);
35 | ::DispatchMessage(&message);
36 | // Allow Flutter to process messages each time a Windows message is
37 | // processed, to prevent starvation.
38 | next_flutter_event_time =
39 | std::min(next_flutter_event_time, ProcessFlutterMessages());
40 | }
41 | // If the PeekMessage loop didn't run, process Flutter messages.
42 | if (!processed_events) {
43 | next_flutter_event_time =
44 | std::min(next_flutter_event_time, ProcessFlutterMessages());
45 | }
46 | }
47 | }
48 |
49 | void RunLoop::RegisterFlutterInstance(
50 | flutter::FlutterEngine* flutter_instance) {
51 | flutter_instances_.insert(flutter_instance);
52 | }
53 |
54 | void RunLoop::UnregisterFlutterInstance(
55 | flutter::FlutterEngine* flutter_instance) {
56 | flutter_instances_.erase(flutter_instance);
57 | }
58 |
59 | RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
60 | TimePoint next_event_time = TimePoint::max();
61 | for (auto instance : flutter_instances_) {
62 | std::chrono::nanoseconds wait_duration = instance->ProcessMessages();
63 | if (wait_duration != std::chrono::nanoseconds::max()) {
64 | next_event_time =
65 | std::min(next_event_time, TimePoint::clock::now() + wait_duration);
66 | }
67 | }
68 | return next_event_time;
69 | }
70 |
--------------------------------------------------------------------------------
/windows/runner/run_loop.h:
--------------------------------------------------------------------------------
1 | #ifndef RUN_LOOP_H_
2 | #define RUN_LOOP_H_
3 |
4 | #include
5 |
6 | #include
7 | #include
8 |
9 | // A runloop that will service events for Flutter instances as well
10 | // as native messages.
11 | class RunLoop {
12 | public:
13 | RunLoop();
14 | ~RunLoop();
15 |
16 | // Prevent copying
17 | RunLoop(RunLoop const&) = delete;
18 | RunLoop& operator=(RunLoop const&) = delete;
19 |
20 | // Runs the run loop until the application quits.
21 | void Run();
22 |
23 | // Registers the given Flutter instance for event servicing.
24 | void RegisterFlutterInstance(
25 | flutter::FlutterEngine* flutter_instance);
26 |
27 | // Unregisters the given Flutter instance from event servicing.
28 | void UnregisterFlutterInstance(
29 | flutter::FlutterEngine* flutter_instance);
30 |
31 | private:
32 | using TimePoint = std::chrono::steady_clock::time_point;
33 |
34 | // Processes all currently pending messages for registered Flutter instances.
35 | TimePoint ProcessFlutterMessages();
36 |
37 | std::set flutter_instances_;
38 | };
39 |
40 | #endif // RUN_LOOP_H_
41 |
--------------------------------------------------------------------------------
/windows/runner/runner.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PerMonitorV2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/windows/runner/utils.cpp:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | void CreateAndAttachConsole() {
11 | if (::AllocConsole()) {
12 | FILE *unused;
13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
14 | _dup2(_fileno(stdout), 1);
15 | }
16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
17 | _dup2(_fileno(stdout), 2);
18 | }
19 | std::ios::sync_with_stdio();
20 | FlutterDesktopResyncOutputStreams();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/windows/runner/utils.h:
--------------------------------------------------------------------------------
1 | #ifndef CONSOLE_UTILS_H_
2 | #define CONSOLE_UTILS_H_
3 |
4 | // Creates a console for the process, and redirects stdout and stderr to
5 | // it for both the runner and the Flutter library.
6 | void CreateAndAttachConsole();
7 |
8 | #endif // CONSOLE_UTILS_H_
9 |
--------------------------------------------------------------------------------
/windows/runner/win32_window.cpp:
--------------------------------------------------------------------------------
1 | #include "win32_window.h"
2 |
3 | #include
4 |
5 | #include "resource.h"
6 |
7 | namespace {
8 |
9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
10 |
11 | // The number of Win32Window objects that currently exist.
12 | static int g_active_window_count = 0;
13 |
14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
15 |
16 | // Scale helper to convert logical scaler values to physical using passed in
17 | // scale factor
18 | int Scale(int source, double scale_factor) {
19 | return static_cast(source * scale_factor);
20 | }
21 |
22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
23 | // This API is only needed for PerMonitor V1 awareness mode.
24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) {
25 | HMODULE user32_module = LoadLibraryA("User32.dll");
26 | if (!user32_module) {
27 | return;
28 | }
29 | auto enable_non_client_dpi_scaling =
30 | reinterpret_cast(
31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
32 | if (enable_non_client_dpi_scaling != nullptr) {
33 | enable_non_client_dpi_scaling(hwnd);
34 | FreeLibrary(user32_module);
35 | }
36 | }
37 |
38 | } // namespace
39 |
40 | // Manages the Win32Window's window class registration.
41 | class WindowClassRegistrar {
42 | public:
43 | ~WindowClassRegistrar() = default;
44 |
45 | // Returns the singleton registar instance.
46 | static WindowClassRegistrar* GetInstance() {
47 | if (!instance_) {
48 | instance_ = new WindowClassRegistrar();
49 | }
50 | return instance_;
51 | }
52 |
53 | // Returns the name of the window class, registering the class if it hasn't
54 | // previously been registered.
55 | const wchar_t* GetWindowClass();
56 |
57 | // Unregisters the window class. Should only be called if there are no
58 | // instances of the window.
59 | void UnregisterWindowClass();
60 |
61 | private:
62 | WindowClassRegistrar() = default;
63 |
64 | static WindowClassRegistrar* instance_;
65 |
66 | bool class_registered_ = false;
67 | };
68 |
69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
70 |
71 | const wchar_t* WindowClassRegistrar::GetWindowClass() {
72 | if (!class_registered_) {
73 | WNDCLASS window_class{};
74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
75 | window_class.lpszClassName = kWindowClassName;
76 | window_class.style = CS_HREDRAW | CS_VREDRAW;
77 | window_class.cbClsExtra = 0;
78 | window_class.cbWndExtra = 0;
79 | window_class.hInstance = GetModuleHandle(nullptr);
80 | window_class.hIcon =
81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
82 | window_class.hbrBackground = 0;
83 | window_class.lpszMenuName = nullptr;
84 | window_class.lpfnWndProc = Win32Window::WndProc;
85 | RegisterClass(&window_class);
86 | class_registered_ = true;
87 | }
88 | return kWindowClassName;
89 | }
90 |
91 | void WindowClassRegistrar::UnregisterWindowClass() {
92 | UnregisterClass(kWindowClassName, nullptr);
93 | class_registered_ = false;
94 | }
95 |
96 | Win32Window::Win32Window() {
97 | ++g_active_window_count;
98 | }
99 |
100 | Win32Window::~Win32Window() {
101 | --g_active_window_count;
102 | Destroy();
103 | }
104 |
105 | bool Win32Window::CreateAndShow(const std::wstring& title,
106 | const Point& origin,
107 | const Size& size) {
108 | Destroy();
109 |
110 | const wchar_t* window_class =
111 | WindowClassRegistrar::GetInstance()->GetWindowClass();
112 |
113 | const POINT target_point = {static_cast(origin.x),
114 | static_cast(origin.y)};
115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
117 | double scale_factor = dpi / 96.0;
118 |
119 | HWND window = CreateWindow(
120 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
121 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
122 | Scale(size.width, scale_factor), Scale(size.height, scale_factor),
123 | nullptr, nullptr, GetModuleHandle(nullptr), this);
124 |
125 | if (!window) {
126 | return false;
127 | }
128 |
129 | return OnCreate();
130 | }
131 |
132 | // static
133 | LRESULT CALLBACK Win32Window::WndProc(HWND const window,
134 | UINT const message,
135 | WPARAM const wparam,
136 | LPARAM const lparam) noexcept {
137 | if (message == WM_NCCREATE) {
138 | auto window_struct = reinterpret_cast(lparam);
139 | SetWindowLongPtr(window, GWLP_USERDATA,
140 | reinterpret_cast(window_struct->lpCreateParams));
141 |
142 | auto that = static_cast(window_struct->lpCreateParams);
143 | EnableFullDpiSupportIfAvailable(window);
144 | that->window_handle_ = window;
145 | } else if (Win32Window* that = GetThisFromHandle(window)) {
146 | return that->MessageHandler(window, message, wparam, lparam);
147 | }
148 |
149 | return DefWindowProc(window, message, wparam, lparam);
150 | }
151 |
152 | LRESULT
153 | Win32Window::MessageHandler(HWND hwnd,
154 | UINT const message,
155 | WPARAM const wparam,
156 | LPARAM const lparam) noexcept {
157 | switch (message) {
158 | case WM_DESTROY:
159 | window_handle_ = nullptr;
160 | Destroy();
161 | if (quit_on_close_) {
162 | PostQuitMessage(0);
163 | }
164 | return 0;
165 |
166 | case WM_DPICHANGED: {
167 | auto newRectSize = reinterpret_cast(lparam);
168 | LONG newWidth = newRectSize->right - newRectSize->left;
169 | LONG newHeight = newRectSize->bottom - newRectSize->top;
170 |
171 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
172 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
173 |
174 | return 0;
175 | }
176 | case WM_SIZE:
177 | RECT rect = GetClientArea();
178 | if (child_content_ != nullptr) {
179 | // Size and position the child window.
180 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
181 | rect.bottom - rect.top, TRUE);
182 | }
183 | return 0;
184 |
185 | case WM_ACTIVATE:
186 | if (child_content_ != nullptr) {
187 | SetFocus(child_content_);
188 | }
189 | return 0;
190 |
191 | // Messages that are directly forwarded to embedding.
192 | case WM_FONTCHANGE:
193 | SendMessage(child_content_, WM_FONTCHANGE, NULL, NULL);
194 | return 0;
195 | }
196 |
197 | return DefWindowProc(window_handle_, message, wparam, lparam);
198 | }
199 |
200 | void Win32Window::Destroy() {
201 | OnDestroy();
202 |
203 | if (window_handle_) {
204 | DestroyWindow(window_handle_);
205 | window_handle_ = nullptr;
206 | }
207 | if (g_active_window_count == 0) {
208 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
209 | }
210 | }
211 |
212 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
213 | return reinterpret_cast(
214 | GetWindowLongPtr(window, GWLP_USERDATA));
215 | }
216 |
217 | void Win32Window::SetChildContent(HWND content) {
218 | child_content_ = content;
219 | SetParent(content, window_handle_);
220 | RECT frame = GetClientArea();
221 |
222 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
223 | frame.bottom - frame.top, true);
224 |
225 | SetFocus(child_content_);
226 | }
227 |
228 | RECT Win32Window::GetClientArea() {
229 | RECT frame;
230 | GetClientRect(window_handle_, &frame);
231 | return frame;
232 | }
233 |
234 | HWND Win32Window::GetHandle() {
235 | return window_handle_;
236 | }
237 |
238 | void Win32Window::SetQuitOnClose(bool quit_on_close) {
239 | quit_on_close_ = quit_on_close;
240 | }
241 |
242 | bool Win32Window::OnCreate() {
243 | // No-op; provided for subclasses.
244 | return true;
245 | }
246 |
247 | void Win32Window::OnDestroy() {
248 | // No-op; provided for subclasses.
249 | }
250 |
--------------------------------------------------------------------------------
/windows/runner/win32_window.h:
--------------------------------------------------------------------------------
1 | #ifndef WIN32_WINDOW_H_
2 | #define WIN32_WINDOW_H_
3 |
4 | #include
5 | #include
6 |
7 | #include
8 | #include
9 | #include
10 |
11 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be
12 | // inherited from by classes that wish to specialize with custom
13 | // rendering and input handling
14 | class Win32Window {
15 | public:
16 | struct Point {
17 | unsigned int x;
18 | unsigned int y;
19 | Point(unsigned int x, unsigned int y) : x(x), y(y) {}
20 | };
21 |
22 | struct Size {
23 | unsigned int width;
24 | unsigned int height;
25 | Size(unsigned int width, unsigned int height)
26 | : width(width), height(height) {}
27 | };
28 |
29 | Win32Window();
30 | virtual ~Win32Window();
31 |
32 | // Creates and shows a win32 window with |title| and position and size using
33 | // |origin| and |size|. New windows are created on the default monitor. Window
34 | // sizes are specified to the OS in physical pixels, hence to ensure a
35 | // consistent size to will treat the width height passed in to this function
36 | // as logical pixels and scale to appropriate for the default monitor. Returns
37 | // true if the window was created successfully.
38 | bool CreateAndShow(const std::wstring& title,
39 | const Point& origin,
40 | const Size& size);
41 |
42 | // Release OS resources associated with window.
43 | void Destroy();
44 |
45 | // Inserts |content| into the window tree.
46 | void SetChildContent(HWND content);
47 |
48 | // Returns the backing Window handle to enable clients to set icon and other
49 | // window properties. Returns nullptr if the window has been destroyed.
50 | HWND GetHandle();
51 |
52 | // If true, closing this window will quit the application.
53 | void SetQuitOnClose(bool quit_on_close);
54 |
55 | // Return a RECT representing the bounds of the current client area.
56 | RECT GetClientArea();
57 |
58 | protected:
59 | // Processes and route salient window messages for mouse handling,
60 | // size change and DPI. Delegates handling of these to member overloads that
61 | // inheriting classes can handle.
62 | virtual LRESULT MessageHandler(HWND window,
63 | UINT const message,
64 | WPARAM const wparam,
65 | LPARAM const lparam) noexcept;
66 |
67 | // Called when CreateAndShow is called, allowing subclass window-related
68 | // setup. Subclasses should return false if setup fails.
69 | virtual bool OnCreate();
70 |
71 | // Called when Destroy is called.
72 | virtual void OnDestroy();
73 |
74 | private:
75 | friend class WindowClassRegistrar;
76 |
77 | // OS callback called by message pump. Handles the WM_NCCREATE message which
78 | // is passed when the non-client area is being created and enables automatic
79 | // non-client DPI scaling so that the non-client area automatically
80 | // responsponds to changes in DPI. All other messages are handled by
81 | // MessageHandler.
82 | static LRESULT CALLBACK WndProc(HWND const window,
83 | UINT const message,
84 | WPARAM const wparam,
85 | LPARAM const lparam) noexcept;
86 |
87 | // Retrieves a class instance pointer for |window|
88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept;
89 |
90 | bool quit_on_close_ = false;
91 |
92 | // window handle for top level window.
93 | HWND window_handle_ = nullptr;
94 |
95 | // window handle for hosted content.
96 | HWND child_content_ = nullptr;
97 | };
98 |
99 | #endif // WIN32_WINDOW_H_
100 |
--------------------------------------------------------------------------------
/windows/runner/window_configuration.cpp:
--------------------------------------------------------------------------------
1 | #include "window_configuration.h"
2 |
3 | const wchar_t* kFlutterWindowTitle = L"Pacman Emulator";
4 | const unsigned int kFlutterWindowOriginX = 10;
5 | const unsigned int kFlutterWindowOriginY = 10;
6 | const unsigned int kFlutterWindowWidth = 224 * 3;
7 | const unsigned int kFlutterWindowHeight = 288 * 3 + 18;
8 |
--------------------------------------------------------------------------------
/windows/runner/window_configuration.h:
--------------------------------------------------------------------------------
1 | #ifndef WINDOW_CONFIGURATION_
2 | #define WINDOW_CONFIGURATION_
3 |
4 | // This is a temporary approach to isolate changes that people are likely to
5 | // make to main.cpp, where the APIs are still in flux. This will reduce the
6 | // need to resolve conflicts or re-create changes slightly differently every
7 | // time the Windows Flutter API surface changes.
8 | //
9 | // Longer term there should be simpler configuration options for common
10 | // customizations like this, without requiring native code changes.
11 |
12 | extern const wchar_t* kFlutterWindowTitle;
13 | extern const unsigned int kFlutterWindowOriginX;
14 | extern const unsigned int kFlutterWindowOriginY;
15 | extern const unsigned int kFlutterWindowWidth;
16 | extern const unsigned int kFlutterWindowHeight;
17 |
18 | #endif // WINDOW_CONFIGURATION_
19 |
--------------------------------------------------------------------------------