diff --git a/app/3rd_party/gsettings/CHANGELOG.md b/app/3rd_party/gsettings/CHANGELOG.md new file mode 100644 index 0000000..e6e987f --- /dev/null +++ b/app/3rd_party/gsettings/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +## 0.2.0 + +* Initial release, replacing the existing ffi bindings. + diff --git a/app/3rd_party/gsettings/CONTRIBUTING.md b/app/3rd_party/gsettings/CONTRIBUTING.md new file mode 100644 index 0000000..03630ca --- /dev/null +++ b/app/3rd_party/gsettings/CONTRIBUTING.md @@ -0,0 +1,8 @@ +# gsettings.dart Contribution Guide + +If you have a problem, please [file an issue](https://github.com/canonical/gsettings.dart/issues/new). + +If you have a solution, then we accept contributions via [pull requests](https://github.com/canonical/gsettings.dart/pulls). +All contributions require the author(s) to sign the [contributor license agreement](http://www.ubuntu.com/legal/contributors/). + +Thanks for your help! diff --git a/app/3rd_party/gsettings/LICENSE b/app/3rd_party/gsettings/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/app/3rd_party/gsettings/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/app/3rd_party/gsettings/README.md b/app/3rd_party/gsettings/README.md new file mode 100644 index 0000000..dcd37e2 --- /dev/null +++ b/app/3rd_party/gsettings/README.md @@ -0,0 +1,19 @@ +[![Pub Package](https://img.shields.io/pub/v/gsettings.svg)](https://pub.dev/packages/gsettings) + +Provides a client to use [GSettings](https://developer.gnome.org/gio/stable/GSettings.html) - a settings database used for storing user preferences on Linux. + +```dart +import 'package:dbus/dbus.dart'; +import 'package:gsettings/gsettings.dart'; + +void main() async { + var schema = GSettings('org.gnome.desktop.interface'); + var value = await schema.get('font-name'); + var font = (value as DBusString).value; + print('Current font set to: $font'); +} +``` + +## Contributing to gsettings.dart + +We welcome contributions! See the [contribution guide](CONTRIBUTING.md) for more details. diff --git a/app/3rd_party/gsettings/analysis_options.yaml b/app/3rd_party/gsettings/analysis_options.yaml new file mode 100644 index 0000000..108d105 --- /dev/null +++ b/app/3rd_party/gsettings/analysis_options.yaml @@ -0,0 +1 @@ +include: package:pedantic/analysis_options.yaml diff --git a/app/3rd_party/gsettings/lib/gsettings.dart b/app/3rd_party/gsettings/lib/gsettings.dart new file mode 100644 index 0000000..aff9027 --- /dev/null +++ b/app/3rd_party/gsettings/lib/gsettings.dart @@ -0,0 +1 @@ +export 'src/gsettings.dart'; diff --git a/app/3rd_party/gsettings/lib/src/dconf_client.dart b/app/3rd_party/gsettings/lib/src/dconf_client.dart new file mode 100644 index 0000000..2ffd7dd --- /dev/null +++ b/app/3rd_party/gsettings/lib/src/dconf_client.dart @@ -0,0 +1,287 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:dbus/dbus.dart'; +import 'package:xdg_directories/xdg_directories.dart'; + +import 'getuid.dart'; +import 'gvariant_binary_codec.dart'; +import 'gvariant_database.dart'; + +XDGDirectories xdg = XDGDirectories(); + +/// Message received when DConf notifies changes. +class DConfNotifyEvent { + /// A prefixed applied to each value in [keys]. + final String prefix; + + /// The paths to each key that has changed, to be prefixed with [prefix]. + /// If empty, a single key has changed with the value [prefix]. + final List paths; + + /// Unique tag for this change, used to detect if this client generated the change. + final String tag; + + const DConfNotifyEvent(this.prefix, this.paths, this.tag); + + @override + String toString() => "DConfNotifyEvent('$prefix', $paths, '$tag')"; +} + +/// A client that connects to DConf. +class DConfClient { + final String? profile; + + /// Stream of key names that indicate when a value has changed. + Stream get notify => _notifyController.stream; + final _notifyController = StreamController(); + + /// The D-Bus buses this client is connected to. + final DBusClient _systemBus; + final DBusClient _sessionBus; + final bool _closeSystemBus; + final bool _closeSessionBus; + + /// Creates a new DConf client. + DConfClient({this.profile, DBusClient? systemBus, DBusClient? sessionBus}) + : _systemBus = systemBus ?? DBusClient.system(), + _sessionBus = sessionBus ?? DBusClient.session(), + _closeSessionBus = sessionBus == null, + _closeSystemBus = systemBus == null { + _notifyController.onListen = () { + _loadSources().then((sources) { + if (sources.isEmpty) { + throw 'No DConf source to write to'; + } + _notifyController.addStream(DBusRemoteObjectSignalStream( + object: sources[0].writer, + interface: 'ca.desrt.dconf.Writer', + name: 'Notify', + signature: DBusSignature('sass')) + .map((signal) => DConfNotifyEvent( + (signal.values[0] as DBusString).value, + (signal.values[1] as DBusArray) + .children + .map((child) => (child as DBusString).value) + .toList(), + (signal.values[2] as DBusString).value))); + }); + }; + } + + /// Gets all the keys available underneath the given directory. + Future> list(String dir) async { + var sources = await _loadSources(); + var keys = {}; + for (var source in sources) { + keys.addAll(await source.database.list(dir: dir)); + } + return keys.toList(); + } + + /// Gets the value of a given [key]. + Future read(String key) async { + var sources = await _loadSources(); + for (var source in sources) { + var value = await source.database.lookup(key); + if (value != null) { + return value; + } + } + return null; + } + + /// Sets key values in the dconf database. + Future write(Map values) async { + var sources = await _loadSources(); + if (sources.isEmpty) { + throw 'No DConf source to write to'; + } + + var changeset = DBusDict( + DBusSignature('s'), + DBusSignature('mv'), + values.map((key, value) => MapEntry( + DBusString(key), + DBusMaybe(DBusSignature('v'), + value != null ? DBusVariant(value) : null)))); + var codec = GVariantBinaryCodec(); + var result = await sources[0].writer.callMethod( + 'ca.desrt.dconf.Writer', + 'Change', + [DBusArray.byte(codec.encode(changeset, endian: Endian.host))], + replySignature: DBusSignature('s')); + return (result.values[0] as DBusString).value; + } + + /// Terminates the connection to the DConf daemon. If a client remains unclosed, the Dart process may not terminate. + Future close() async { + if (_closeSystemBus) { + await _systemBus.close(); + } + if (_closeSessionBus) { + await _sessionBus.close(); + } + } + + // Load the DConf sources in use. + Future> _loadSources() async { + // Generate list of files to look for the profile in. + var paths = []; + var profileName = profile; + if (profileName == null) { + var uid = getuid(); + paths.add('/run/dconf/user/$uid'); + profileName = Platform.environment['DCONF_PROFILE']; + } + if (profileName != null) { + if (profileName.startsWith('/')) { + paths.add(profileName); + } else { + paths.addAll(_getProfilePaths(profileName)); + } + } else { + var rd = xdg.runtimeDir; + if (rd != null) { + paths.add(_buildFilename([rd.path, 'dconf', 'profile'])); + } + paths.addAll(_getProfilePaths('user')); + } + + // Find the first file that exists. + for (var path in paths) { + var sources = await _loadProfileFile(path); + if (sources != null) { + return sources; + } + } + + // Return the default profile. + if (profileName == null) { + return [DConfEngineSourceUser('user', _sessionBus)]; + } else { + return []; + } + } + + // Get the paths to find a DConf profile with [profileName]. + List _getProfilePaths(String profileName) { + var paths = [ + _buildFilename(['/etc', 'dconf', 'profile', profileName]) + ]; + for (var dir in xdg.dataDirs) { + paths.add(_buildFilename([dir.path, 'dconf', 'profile', profileName])); + } + return paths; + } + + // Load a DConf profile file. + Future?> _loadProfileFile(String path) async { + var file = File(path); + List lines; + try { + lines = await file.readAsLines(); + } on FileSystemException { + return null; + } + + var sources = []; + for (var line in lines) { + // Strip off comments. + var commentIndex = line.lastIndexOf('#'); + if (commentIndex >= 0) { + line = line.substring(0, commentIndex); + } + line = line.trim(); + if (line.isEmpty) { + continue; + } + + var index = line.indexOf(':'); + if (index < 0) { + throw "Invalid DConf profile line: '$line'"; + } + var type = line.substring(0, index); + var value = line.substring(index + 1); + DConfEngineSource source; + switch (type) { + case 'user-db': + source = DConfEngineSourceUser(value, _sessionBus); + break; + case 'system-db': + source = DConfEngineSourceSystem(value, _systemBus); + break; + case 'service-db': // Not implemented + case 'file-db': // Not implemented + default: + throw "Unknown DConf source: 'line'"; + } + sources.add(source); + } + + return sources; + } +} + +class DConfEngineSource { + /// The database containing configuration. + GVariantDatabase get database { + throw ('Not implemented'); + } + + /// D-Bus object to write to configuration. + DBusRemoteObject get writer { + throw ('Not implemented'); + } +} + +class DConfEngineSourceUser extends DConfEngineSource { + final String name; + final DBusClient sessionBus; + + DConfEngineSourceUser(this.name, this.sessionBus); + + @override + GVariantDatabase get database => + GVariantDatabase(_buildFilename([xdg.configHome.path, 'dconf', name])); + + @override + DBusRemoteObject get writer => DBusRemoteObject(sessionBus, + name: 'ca.desrt.dconf', + path: DBusObjectPath('/ca/desrt/dconf/Writer/$name')); + + @override + String toString() => "DConfEngineSourceUser('$name')"; +} + +class DConfEngineSourceSystem extends DConfEngineSource { + final String name; + final DBusClient systemBus; + + DConfEngineSourceSystem(this.name, this.systemBus); + + @override + GVariantDatabase get database => + GVariantDatabase(_buildFilename(['/etc', 'dconf', 'db', name])); + + @override + DBusRemoteObject get writer => DBusRemoteObject(systemBus, + name: 'ca.desrt.dconf', + path: DBusObjectPath('/ca/desrt/dconf/Writer/$name')); + + @override + String toString() => "DConfEngineSourceSystem('$name')"; +} + +// Build a filename from parts. +String _buildFilename(List parts) { + var path = parts.join('/'); + while (true) { + var updatedPath = path.replaceAll('//', '/'); + if (updatedPath == path) { + return path; + } + path = updatedPath; + } +} diff --git a/app/3rd_party/gsettings/lib/src/getuid.dart b/app/3rd_party/gsettings/lib/src/getuid.dart new file mode 100644 index 0000000..6911ed4 --- /dev/null +++ b/app/3rd_party/gsettings/lib/src/getuid.dart @@ -0,0 +1,2 @@ +// Conditionally import the FFI version, so this doesn't break web builds. +export 'getuid_stub.dart' if (dart.library.ffi) 'getuid_linux.dart'; diff --git a/app/3rd_party/gsettings/lib/src/getuid_linux.dart b/app/3rd_party/gsettings/lib/src/getuid_linux.dart new file mode 100644 index 0000000..652702d --- /dev/null +++ b/app/3rd_party/gsettings/lib/src/getuid_linux.dart @@ -0,0 +1,11 @@ +import 'dart:ffi'; + +typedef _getuidC = Int32 Function(); +typedef _getuidDart = int Function(); + +/// Gets the user ID of the current user. +int getuid() { + final dylib = DynamicLibrary.open('libc.so.6'); + final getuidP = dylib.lookupFunction<_getuidC, _getuidDart>('getuid'); + return getuidP(); +} diff --git a/app/3rd_party/gsettings/lib/src/getuid_stub.dart b/app/3rd_party/gsettings/lib/src/getuid_stub.dart new file mode 100644 index 0000000..ebfe08b --- /dev/null +++ b/app/3rd_party/gsettings/lib/src/getuid_stub.dart @@ -0,0 +1,4 @@ +/// Gets the user ID of the current user. +int getuid() { + throw 'Unable to determine UID on this system'; +} diff --git a/app/3rd_party/gsettings/lib/src/gsettings.dart b/app/3rd_party/gsettings/lib/src/gsettings.dart new file mode 100644 index 0000000..7fa0c97 --- /dev/null +++ b/app/3rd_party/gsettings/lib/src/gsettings.dart @@ -0,0 +1,277 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:dbus/dbus.dart'; +import 'package:xdg_directories/xdg_directories.dart'; + +import 'dconf_client.dart'; +import 'gvariant_database.dart'; + +XDGDirectories xdg = XDGDirectories(); + +/// Get the names of the installed GSettings schemas. +/// These schemas can be accessed using a [GSettings] object. +Future> listGSettingsSchemas() async { + var schemaNames = {}; + for (var dir in _getSchemaDirs()) { + try { + var database = GVariantDatabase(dir.path + '/gschemas.compiled'); + schemaNames.addAll(await database.list(dir: '')); + } on FileSystemException { + continue; + } + } + return schemaNames.toList(); +} + +/// An object to access settings stored in a GSettings database. +class GSettings { + /// The name of the schema for these settings, e.g. 'org.gnome.desktop.interface'. + final String schemaName; + + /// A stream of settings key names as they change. + Stream> get keysChanged => _keysChangedController.stream; + final _keysChangedController = StreamController>(); + + // Client for communicating with DConf. + final DConfClient _dconfClient; + + /// Creates an object to access settings from the shema with name [schemaName]. + GSettings(this.schemaName, {DBusClient? systemBus, DBusClient? sessionBus}) + : _dconfClient = + DConfClient(systemBus: systemBus, sessionBus: sessionBus) { + _keysChangedController.onListen = () { + _load().then((table) { + var path = _getPath(table); + _keysChangedController.addStream(_dconfClient.notify + .map((event) => event.paths.isEmpty + ? [event.prefix] + : event.paths.map((path) => event.prefix + path)) + .where((keys) => keys.any((key) => key.startsWith(path))) + .map((keys) => + keys.map((key) => key.substring(path.length)).toList())); + }); + }; + } + + /// Gets the names of the settings keys available. + /// If the schema is not installed will throw a [GSettingsSchemaNotInstalledException]. + Future> list() async { + var table = await _load(); + return table.list(dir: '', type: 'v'); + } + + /// Reads the value of the settings key with [name]. + /// Attempting to read an unknown key will throw a [GSettingsUnknownKeyException]. + /// If the schema is not installed will throw a [GSettingsSchemaNotInstalledException]. + Future get(String name) async { + var table = await _load(); + var schemaEntry = _getSchemaEntry(table, name); + var path = _getPath(table); + + // Lookup user value in DConf. + var value = await _dconfClient.read(path + name); + if (value != null) { + return value; + } + + return _getDefaultValue(schemaEntry); + } + + /// Reads the default value of the settings key with [name]. + /// If this key is not set, then this value will be returned by [get]. + /// Attempting to read an unknown key will throw a [GSettingsUnknownKeyException]. + /// If the schema is not installed will throw a [GSettingsSchemaNotInstalledException]. + Future getDefault(String name) async { + var table = await _load(); + var schemaEntry = _getSchemaEntry(table, name); + return _getDefaultValue(schemaEntry); + } + + /// Check if the settings key with [name] is set. + Future isSet(String name) async { + var table = await _load(); + var path = _getPath(table); + return await _dconfClient.read(path + name) != null; + } + + /// Writes a single settings keys. + /// If you need to set multiple values, use [setAll]. + Future set(String name, DBusValue value) async { + var table = await _load(); + var path = _getPath(table); + await _dconfClient.write({path + name: value}); + } + + /// Removes a setting value. + /// The key will now return the default value specified in the GSetting schema. + Future unset(String name) async { + var table = await _load(); + var path = _getPath(table); + await _dconfClient.write({path + name: null}); + } + + /// Writes multiple settings keys in a single transaction. + /// Writing a null value will reset it to its default value. + Future setAll(Map values) async { + var table = await _load(); + var path = _getPath(table); + await _dconfClient + .write(values.map((name, value) => MapEntry(path + name, value))); + } + + /// Terminates any open connections. If a settings object remains unclosed, the Dart process may not terminate. + Future close() async { + await _dconfClient.close(); + } + + // Get the database entry for this schema. + Future _load() async { + for (var dir in _getSchemaDirs()) { + var database = GVariantDatabase(dir.path + '/gschemas.compiled'); + try { + var table = await database.lookupTable(schemaName); + if (table != null) { + return table; + } + } on FileSystemException { + continue; + } + } + + throw GSettingsSchemaNotInstalledException(schemaName); + } + + _GSettingsSchemaEntry _getSchemaEntry( + GVariantDatabaseTable table, String name) { + var entry = table.lookup(name); + if (entry == null) { + throw GSettingsUnknownKeyException(schemaName, name); + } + entry as DBusStruct; + var defaultValue = entry.children[0]; + List? words; + DBusValue? minimumValue; + DBusValue? maximumValue; + Map? desktopOverrides; + for (var item in entry.children.skip(1)) { + item as DBusStruct; + switch ((item.children[0] as DBusByte).value) { + case 108: // 'l' - localization + //var l10n = (item.children[1] as DBusByte).value; // 'm': messages, 't': time. + //var unparsedDefaultValue = (item.children[2] as DBusString).value; + break; + case 102: // 'f' - flags + case 101: // 'e' - enum + case 99: // 'c' - choice + words = (item.children[0] as DBusArray) + .children + .map((value) => (value as DBusUint32).value) + .toList(); + break; + case 114: // 'r' - range + minimumValue = item.children[1]; + maximumValue = item.children[2]; + break; + case 100: // 'd' - desktop overrides + desktopOverrides = (item.children[1] as DBusDict).children.map( + (key, value) => MapEntry( + (key as DBusString).value, (value as DBusVariant).value)); + break; + } + } + return _GSettingsSchemaEntry( + defaultValue: defaultValue, + words: words, + minimumValue: minimumValue, + maximumValue: maximumValue, + desktopOverrides: desktopOverrides); + } + + DBusValue _getDefaultValue(_GSettingsSchemaEntry entry) { + if (entry.desktopOverrides != null) { + var xdgCurrentDesktop = Platform.environment['XDG_CURRENT_DESKTOP'] ?? ''; + for (var desktop in xdgCurrentDesktop.split(':')) { + var defaultValue = entry.desktopOverrides![desktop]; + if (defaultValue != null) { + return defaultValue; + } + } + } + + return entry.defaultValue; + } + + // Get the key path from the database table. + String _getPath(GVariantDatabaseTable table) { + var pathValue = table.lookup('.path'); + if (pathValue == null) { + throw ('Unable to determine path for schema $schemaName'); + } + return (pathValue as DBusString).value; + } +} + +// Get the directories that contain schemas. +List _getSchemaDirs() { + var schemaDirs = []; + + var schemaDir = Platform.environment['GSETTINGS_SCHEMA_DIR']; + if (schemaDir != null) { + schemaDirs.add(Directory(schemaDir)); + } + + for (var dataDir in xdg.dataDirs) { + var path = dataDir.path; + if (!path.endsWith('/')) { + path += '/'; + } + path += 'glib-2.0/schemas'; + schemaDirs.add(Directory(path)); + } + return schemaDirs; +} + +/// Exception thrown when trying to access a GSettings schema that is not installed. +class GSettingsSchemaNotInstalledException implements Exception { + /// The name of the GSettings schema that was being accessed. + final String schemaName; + + const GSettingsSchemaNotInstalledException(this.schemaName); + + @override + String toString() => 'GSettings schema $schemaName not installed'; +} + +/// Exception thrown when trying to access a key not in a GSettings schema. +class GSettingsUnknownKeyException implements Exception { + /// The name of the GSettings schema that was being accessed. + final String schemaName; + + /// The name of the key being accessed. + final String keyName; + + const GSettingsUnknownKeyException(this.schemaName, this.keyName); + + @override + String toString() => 'Key $keyName not in GSettings schema $schemaName'; +} + +class _GSettingsSchemaEntry { + final DBusValue defaultValue; + final List? words; + final DBusValue? minimumValue; + final DBusValue? maximumValue; + final Map? desktopOverrides; + + const _GSettingsSchemaEntry( + {required this.defaultValue, + this.words, + this.minimumValue, + this.maximumValue, + this.desktopOverrides}); + + @override + String toString() => + '_GSettingsSchemaEntry(defaultValue: $defaultValue, words: $words, minimumValue: $minimumValue, maximumValue: $maximumValue, desktopOverrides: $desktopOverrides)'; +} diff --git a/app/3rd_party/gsettings/lib/src/gvariant_binary_codec.dart b/app/3rd_party/gsettings/lib/src/gvariant_binary_codec.dart new file mode 100644 index 0000000..34b52b2 --- /dev/null +++ b/app/3rd_party/gsettings/lib/src/gvariant_binary_codec.dart @@ -0,0 +1,642 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:dbus/dbus.dart'; + +class GVariantBinaryCodec { + GVariantBinaryCodec(); + + /// Encode a value using GVariant binary format. + Uint8List encode(DBusValue value, {required Endian endian}) { + var builder = BytesBuilder(); + _encode(builder, value, endian); + return builder.takeBytes(); + } + + /// Parse a single GVariant value. [type] is expected to be a valid single type. + DBusValue decode(String type, ByteData data, {required Endian endian}) { + // struct + if (type.startsWith('(')) { + return _parseGVariantStruct(type, data, endian: endian); + } + + // array / dict + if (type.startsWith('a')) { + if (type.startsWith('a{')) { + return _parseGVariantDict(type, data, endian: endian); + } else { + var childType = type.substring(1); + return _parseGVariantArray(childType, data, endian: endian); + } + } + + // maybe + if (type.startsWith('m')) { + var childType = type.substring(1); + return _parseGVariantMaybe(childType, data, endian: endian); + } + + switch (type) { + case 'b': // boolean + if (data.lengthInBytes != 1) { + throw ('Invalid length of ${data.lengthInBytes} for boolean GVariant'); + } + return DBusBoolean(data.getUint8(0) != 0); + case 'y': // byte + if (data.lengthInBytes != 1) { + throw ('Invalid length of ${data.lengthInBytes} for byte GVariant'); + } + return DBusByte(data.getUint8(0)); + case 'n': // int16 + if (data.lengthInBytes != 2) { + throw ('Invalid length of ${data.lengthInBytes} for int16 GVariant'); + } + return DBusInt16(data.getInt16(0, endian)); + case 'q': // uint16 + if (data.lengthInBytes != 2) { + throw ('Invalid length of ${data.lengthInBytes} for uint16 GVariant'); + } + return DBusUint16(data.getUint16(0, endian)); + case 'i': // int32 + if (data.lengthInBytes != 4) { + throw ('Invalid length of ${data.lengthInBytes} for int32 GVariant'); + } + return DBusInt32(data.getInt32(0, endian)); + case 'u': // uint32 + if (data.lengthInBytes != 4) { + throw ('Invalid length of ${data.lengthInBytes} for uint32 GVariant'); + } + return DBusUint32(data.getUint32(0, endian)); + case 'x': // int64 + if (data.lengthInBytes != 8) { + throw ('Invalid length of ${data.lengthInBytes} for int64 GVariant'); + } + return DBusInt64(data.getInt64(0, endian)); + case 't': // uint64 + if (data.lengthInBytes != 8) { + throw ('Invalid length of ${data.lengthInBytes} for uint64 GVariant'); + } + return DBusUint64(data.getUint64(0, endian)); + case 'd': // double + return DBusDouble(data.getFloat64(0, endian)); + case 's': // string + if (data.lengthInBytes < 1) { + throw ('Invalid length of ${data.lengthInBytes} for string GVariant'); + } + if (data.getUint8(data.lengthInBytes - 1) != 0) { + throw ('Missing trailing nul character for string GVariant'); + } + return DBusString(utf8.decode(data.buffer + .asUint8List(data.offsetInBytes, data.lengthInBytes - 1))); + case 'o': // object path + if (data.lengthInBytes < 1) { + throw ('Invalid length of ${data.lengthInBytes} for object path GVariant'); + } + if (data.getUint8(data.lengthInBytes - 1) != 0) { + throw ('Missing trailing nul character for object path GVariant'); + } + return DBusObjectPath(utf8.decode(data.buffer + .asUint8List(data.offsetInBytes, data.lengthInBytes - 1))); + case 'g': // signature + if (data.lengthInBytes < 1) { + throw ('Invalid length of ${data.lengthInBytes} for signature GVariant'); + } + if (data.getUint8(data.lengthInBytes - 1) != 0) { + throw ('Missing trailing nul character for object path GVariant'); + } + return DBusSignature(utf8.decode(data.buffer + .asUint8List(data.offsetInBytes, data.lengthInBytes - 1))); + case 'v': // variant + // Type is a suffix on the data + var childType = ''; + var offset = data.lengthInBytes - 1; + while (offset >= 0 && data.getUint8(offset) != 0) { + childType = ascii.decode([data.getUint8(offset)]) + childType; + offset--; + } + if (offset < 0) { + throw ('GVariant variant missing child type'); + } + var childData = ByteData.sublistView(data, 0, offset); + return DBusVariant(decode(childType, childData, endian: endian)); + default: + throw ("Unsupported GVariant type: '$type'"); + } + } + + void _encode(BytesBuilder builder, DBusValue value, Endian endian) { + /// Align this value. + var offset = _align(builder.length, _getAlignment(value.signature.value)); + for (var i = builder.length; i < offset; i++) { + builder.addByte(0); + } + + if (value is DBusBoolean) { + builder.addByte(value.value ? 0x01 : 0x00); + } else if (value is DBusByte) { + _writeUint8(builder, value.value); + } else if (value is DBusInt16) { + var buffer = Uint8List(2).buffer; + ByteData.view(buffer).setInt16(0, value.value, endian); + builder.add(buffer.asUint8List()); + } else if (value is DBusUint16) { + _writeUint16(builder, value.value, endian); + } else if (value is DBusInt32) { + var buffer = Uint8List(4).buffer; + ByteData.view(buffer).setInt32(0, value.value, endian); + builder.add(buffer.asUint8List()); + } else if (value is DBusUint32) { + _writeUint32(builder, value.value, endian); + } else if (value is DBusInt64) { + var buffer = Uint8List(8).buffer; + ByteData.view(buffer).setInt64(0, value.value, endian); + builder.add(buffer.asUint8List()); + } else if (value is DBusUint64) { + _writeUint64(builder, value.value, endian); + } else if (value is DBusDouble) { + var buffer = Uint8List(8).buffer; + ByteData.view(buffer).setFloat64(0, value.value, endian); + builder.add(buffer.asUint8List()); + } else if (value is DBusString) { + builder.add(utf8.encode(value.value)); + builder.addByte(0); + } else if (value is DBusObjectPath) { + builder.add(utf8.encode(value.value)); + builder.addByte(0); + } else if (value is DBusSignature) { + builder.add(utf8.encode(value.value)); + builder.addByte(0); + } else if (value is DBusVariant) { + _encode(builder, value.value, endian); + builder.addByte(0); + builder.add(utf8.encode(value.value.signature.value)); + } else if (value is DBusMaybe) { + if (value.value != null) { + var childSize = _getElementSize(value.valueSignature.value); + _encode(builder, value.value!, endian); + if (childSize == -1) { + builder.addByte(0); + } + } + } else if (value is DBusStruct) { + _encodeStruct(builder, value.children, endian); + } else if (value is DBusArray) { + _encodeArray(builder, value.childSignature.value, value.children, endian); + } else if (value is DBusDict) { + _encodeDict(builder, value.keySignature.value, value.valueSignature.value, + value.children, endian); + } else { + throw ("Unsupported DBus type: '$value'"); + } + } + + void _encodeStruct( + BytesBuilder builder, List values, Endian endian) { + var isVariable = false; + var endOffsets = []; + var startOffset = builder.length; + var alignment = 0; + for (var value in values) { + _encode(builder, value, endian); + + // Variable sized elements will have end offsets appended. + var valueSize = _getElementSize(value.signature.value); + if (value != values.last && valueSize == -1) { + isVariable = true; + endOffsets.add(builder.length - startOffset); + } + + // Struct is alignent to largest element size. + if (valueSize == -1) { + alignment = -1; + } else if (alignment != -1 && valueSize > alignment) { + alignment = valueSize; + } + } + + if (isVariable) { + // Calculate smallest size that can be used for offset values. + var dataLength = builder.length - startOffset; + var offsetSize = 1; + while (_getOffsetSize(dataLength + endOffsets.length * offsetSize) != + offsetSize) { + offsetSize *= 2; + } + + // Append en offsets of variable sized elements. + for (var i = endOffsets.length - 1; i >= 0; i--) { + _writeOffset(builder, endOffsets[i], offsetSize, endian); + } + } else { + // Fixed structures must be padded to their alignment. + if (alignment > 0) { + var offset = _align(builder.length, alignment); + for (var i = builder.length; i < offset; i++) { + builder.addByte(0); + } + } + } + } + + void _encodeArray(BytesBuilder builder, String childType, + List values, Endian endian) { + var endOffsets = []; + var startOffset = builder.length; + for (var value in values) { + _encode(builder, value, endian); + endOffsets.add(builder.length - startOffset); + } + + // If the elements are of variable length, then each end position needs to recorded. + var isVariable = _getElementSize(childType) == -1; + if (isVariable) { + // Calculate smallest size that can be used for offset values. + var dataLength = builder.length - startOffset; + var offsetSize = 1; + while (_getOffsetSize(dataLength + values.length * offsetSize) != + offsetSize) { + offsetSize *= 2; + } + + // Write the end offsets of each element. + for (var i = 0; i < values.length; i++) { + _writeOffset(builder, endOffsets[i], offsetSize, endian); + } + } + } + + void _encodeDict(BytesBuilder builder, String keyType, String valueType, + Map values, Endian endian) { + var endOffsets = []; + var startOffset = builder.length; + for (var entry in values.entries) { + _encode(builder, DBusStruct([entry.key, entry.value]), endian); + endOffsets.add(builder.length - startOffset); + } + + // If the elements are of variable length, then each end position needs to recorded. + var isVariable = + _getElementSize(keyType) == -1 || _getElementSize(valueType) == -1; + if (isVariable) { + // Calculate smallest size that can be used for offset values. + var dataLength = builder.length - startOffset; + var offsetSize = 1; + while (_getOffsetSize(dataLength + values.length * offsetSize) != + offsetSize) { + offsetSize *= 2; + } + + // Write the end offsets of each element. + for (var i = 0; i < values.length; i++) { + _writeOffset(builder, endOffsets[i], offsetSize, endian); + } + } + } + + void _writeUint8(BytesBuilder builder, int value) { + builder.addByte(value); + } + + void _writeUint16(BytesBuilder builder, int value, Endian endian) { + var buffer = Uint8List(2).buffer; + ByteData.view(buffer).setUint16(0, value, endian); + builder.add(buffer.asUint8List()); + } + + void _writeUint32(BytesBuilder builder, int value, Endian endian) { + var buffer = Uint8List(4).buffer; + ByteData.view(buffer).setUint32(0, value, endian); + builder.add(buffer.asUint8List()); + } + + void _writeUint64(BytesBuilder builder, int value, Endian endian) { + var buffer = Uint8List(8).buffer; + ByteData.view(buffer).setUint64(0, value, endian); + builder.add(buffer.asUint8List()); + } + + void _writeOffset( + BytesBuilder builder, int offset, int offsetSize, Endian endian) { + switch (offsetSize) { + case 1: + _writeUint8(builder, offset); + break; + case 2: + _writeUint16(builder, offset, endian); + break; + case 4: + _writeUint32(builder, offset, endian); + break; + case 8: + _writeUint64(builder, offset, endian); + break; + default: + throw ('Unsupported offset size $offsetSize'); + } + } + + DBusStruct _parseGVariantStruct(String type, ByteData data, + {required Endian endian}) { + if (!type.startsWith('(') || !type.endsWith(')')) { + throw ('Invalid struct type: $type'); + } + var childTypes = DBusSignature(type.substring(1, type.length - 1)) + .split() + .map((s) => s.value) + .toList(); + var childSizes = childTypes.map((type) => _getElementSize(type)).toList(); + + // Check if the sizes of the elements can be determined before parsing. + // The last element can be variable, as it takes up the remaining space. + var variableSize = false; + for (var i = 0; i < childSizes.length - 1; i++) { + if (childSizes[i] == -1) { + variableSize = true; + break; + } + } + + if (variableSize) { + return _parseGVariantVariableStruct(childTypes, data, endian: endian); + } else { + return _parseGVariantFixedStruct(childTypes, data, endian: endian); + } + } + + DBusStruct _parseGVariantFixedStruct(List childTypes, ByteData data, + {required Endian endian}) { + var children = []; + var offset = 0; + for (var i = 0; i < childTypes.length; i++) { + var start = _align(offset, _getAlignment(childTypes[i])); + var size = _getElementSize(childTypes[i]); + if (size < 0) { + size = data.lengthInBytes - start; + } + children.add(decode( + childTypes[i], ByteData.sublistView(data, start, start + size), + endian: endian)); + offset += size; + } + + return DBusStruct(children); + } + + DBusStruct _parseGVariantVariableStruct( + List childTypes, ByteData data, + {required Endian endian}) { + var offsetSize = _getOffsetSize(data.lengthInBytes); + var dataEnd = data.lengthInBytes; + var children = []; + var offset = 0; + for (var i = 0; i < childTypes.length; i++) { + var size = _getElementSize(childTypes[i]); + var start = _align(offset, _getAlignment(childTypes[i])); + int end; + if (size > 0) { + // Fixed elements + end = start + size; + } else if (i == childTypes.length - 1) { + // Last variable element ends where the data ends. + end = dataEnd; + } else { + // Read the offset from the end of the data. + end = + _getOffset(data, dataEnd - offsetSize, offsetSize, endian: endian); + if (end > dataEnd) { + throw ('Invalid element end offset in struct'); + } + dataEnd -= offsetSize; + } + children.add(decode(childTypes[i], ByteData.sublistView(data, start, end), + endian: endian)); + offset = end; + } + + return DBusStruct(children); + } + + DBusDict _parseGVariantDict(String type, ByteData data, + {required Endian endian}) { + if (!type.startsWith('a{') || !type.endsWith('}')) { + throw ('Invalid dict type: $type'); + } + var childTypes = DBusSignature(type.substring(2, type.length - 1)) + .split() + .map((s) => s.value) + .toList(); + var keyType = childTypes[0]; + var valueType = childTypes[1]; + // Data is stored as an array, this could be optimised to avoid being unpacked and repacked as a dict. + var array = + _parseGVariantArray('($keyType$valueType)', data, endian: endian); + var values = {}; + for (var child in array.children) { + var keyValue = child as DBusStruct; + values[keyValue.children[0]] = keyValue.children[1]; + } + return DBusDict(DBusSignature(keyType), DBusSignature(valueType), values); + } + + DBusArray _parseGVariantArray(String childType, ByteData data, + {required Endian endian}) { + var elementSize = _getElementSize(childType); + if (elementSize > 0) { + return _parseGVariantFixedArray(childType, elementSize, data, + endian: endian); + } else { + return _parseGVariantVariableArray(childType, data, endian: endian); + } + } + + DBusArray _parseGVariantFixedArray( + String childType, int elementSize, ByteData data, + {required Endian endian}) { + var arrayLength = data.lengthInBytes ~/ elementSize; + + var children = []; + for (var i = 0; i < arrayLength; i++) { + var start = i * elementSize; + var childData = ByteData.sublistView(data, start, start + elementSize); + children.add(decode(childType, childData, endian: endian)); + } + + return DBusArray(DBusSignature(childType), children); + } + + DBusArray _parseGVariantVariableArray(String childType, ByteData data, + {required Endian endian}) { + // Get end of last element. + var offsetSize = _getOffsetSize(data.lengthInBytes); + int arrayLength; + if (data.lengthInBytes > 0) { + var lastOffset = _getOffset( + data, data.lengthInBytes - offsetSize, offsetSize, + endian: endian); + var dataLength = data.lengthInBytes - lastOffset; + if (dataLength < 0 || dataLength % offsetSize != 0) { + throw ('Invalid element end offset in array'); + } + + // Array size is the number of offsets after the last element. + arrayLength = dataLength ~/ offsetSize; + } else { + arrayLength = 0; + } + + var children = []; + var start = 0; + var offsetStart = data.lengthInBytes - offsetSize * arrayLength; + var childAlignment = _getAlignment(childType); + for (var i = 0; i < arrayLength; i++) { + var end = _getOffset(data, offsetStart + offsetSize * i, offsetSize, + endian: endian); + if (end > offsetStart) { + throw ('Invalid element end offset in array'); + } + var childData = ByteData.sublistView(data, start, end); + children.add(decode(childType, childData, endian: endian)); + start = _align(end, childAlignment); + } + + return DBusArray(DBusSignature(childType), children); + } + + DBusMaybe _parseGVariantMaybe(String childType, ByteData data, + {required Endian endian}) { + DBusValue? value; + if (data.lengthInBytes > 0) { + ByteData childData; + var childSize = _getElementSize(childType); + if (childSize == -1) { + if (data.getUint8(data.lengthInBytes - 1) != 0) { + throw ('Invalid padding byte on maybe GVariant'); + } + childData = ByteData.sublistView(data, 0, data.lengthInBytes - 1); + } else { + childData = data; + } + value = decode(childType, childData, endian: endian); + } + return DBusMaybe(DBusSignature(childType), value); + } + + int _align(int offset, int alignment) { + var x = offset % alignment; + return x == 0 ? offset : offset + (alignment - x); + } + + int _getElementSize(String type) { + /// Containers are variable length. + if (type.startsWith('a') || type.startsWith('m')) { + return -1; + } + + if (type.startsWith('(') || type.startsWith('{')) { + var childTypes = DBusSignature(type.substring(1, type.length - 1)) + .split() + .map((s) => s.value); + var size = 0; + for (var type in childTypes) { + var s = _getElementSize(type); + if (s < 0) { + return -1; + } + size += s; + } + return size; + } + + switch (type) { + case 'y': // byte + case 'b': // boolean + return 1; + case 'n': // int16 + case 'q': // uint16 + return 2; + case 'i': // int32 + case 'u': // uint32 + return 4; + case 'x': // int64 + case 't': // uint64 + case 'd': // double + return 8; + case 's': // string + case 'o': // object path + case 'g': // signature + case 'v': // variant + return -1; // variable size + default: + throw ArgumentError.value(type, 'type', 'Unknown type'); + } + } + + int _getAlignment(String type) { + if (type.startsWith('a') || type.startsWith('m')) { + return _getAlignment(type.substring(1)); + } + if (type.startsWith('(') || type.startsWith('{')) { + var childTypes = DBusSignature(type.substring(1, type.length - 1)) + .split() + .map((s) => s.value); + var alignment = 1; + for (var type in childTypes) { + var a = _getAlignment(type); + if (a > alignment) { + alignment = a; + } + } + return alignment; + } + + switch (type) { + case 'y': // byte + case 'b': // boolean + case 's': // string + case 'o': // object path + case 'g': // signature + return 1; + case 'n': // int16 + case 'q': // uint16 + return 2; + case 'i': // int32 + case 'u': // uint32 + return 4; + case 'x': // int64 + case 't': // uint64 + case 'd': // double + case 'v': // variant + return 8; + default: + throw ArgumentError.value(type, 'type', 'Unknown type'); + } + } + + int _getOffsetSize(int size) { + if (size > 0xffffffff) { + return 8; + } else if (size > 0xffff) { + return 4; + } else if (size > 0xff) { + return 2; + } else { + return 1; + } + } + + int _getOffset(ByteData data, int offset, int offsetSize, + {required Endian endian}) { + switch (offsetSize) { + case 1: + return data.getUint8(offset); + case 2: + return data.getUint16(offset, endian); + case 4: + return data.getUint32(offset, endian); + case 8: + return data.getUint64(offset, endian); + default: + throw ('Unknown offset size $offsetSize'); + } + } +} diff --git a/app/3rd_party/gsettings/lib/src/gvariant_database.dart b/app/3rd_party/gsettings/lib/src/gvariant_database.dart new file mode 100644 index 0000000..1d3f4b4 --- /dev/null +++ b/app/3rd_party/gsettings/lib/src/gvariant_database.dart @@ -0,0 +1,188 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:dbus/dbus.dart'; + +import 'gvariant_binary_codec.dart'; + +class GVariantDatabase { + final String path; + + GVariantDatabase(this.path); + + Future> list({String? dir, String? type}) async { + var root = await _loadRootTable(); + return root.list(dir: dir, type: type); + } + + Future lookup(String key) async { + var root = await _loadRootTable(); + return root.lookup(key); + } + + Future lookupTable(String key) async { + var root = await _loadRootTable(); + return root.lookupTable(key); + } + + Future _loadRootTable() async { + var rawData = await File(path).readAsBytes(); + var data = ByteData.view(rawData.buffer); + + // Check for correct signature and detect endianess. + var signature0 = data.getUint32(0, Endian.little); + var signature1 = data.getUint32(4, Endian.little); + var version = data.getUint32(8, Endian.little); + Endian endian; + if (signature0 == 1918981703 && signature1 == 1953390953 && version == 0) { + endian = Endian.little; + /*} else if (signature0 == && signature1 == && version == 0) { + endian = Endian.big;*/ + } else { + throw ('Invalid signature'); + } + + var rootStart = data.getUint32(16, endian); + var rootEnd = data.getUint32(20, endian); + return GVariantDatabaseTable( + ByteData.sublistView(data, rootStart, rootEnd), data, endian); + } +} + +class GVariantDatabaseTable { + final ByteData data; + final ByteData fullData; + final Endian endian; + late final int _nBloomWords; + //late final int _bloomOffset; // FIXME + late final int _nBuckets; + late final int _bucketOffset; + late final int _nHashItems; + late final int _hashOffset; + + GVariantDatabaseTable(this.data, this.fullData, this.endian) { + var offset = 0; + _nBloomWords = data.getUint32(offset + 0, endian) & 0x3ffffff; + _nBuckets = data.getUint32(offset + 4, endian); + offset += 8; + //_bloomOffset = offset; // FIXME + offset += _nBloomWords * 4; + _bucketOffset = offset; + offset += _nBuckets * 4; + _hashOffset = offset; + + _nHashItems = (data.lengthInBytes - _hashOffset) ~/ 24; + } + + List list({String? dir, String? type}) { + var dirHash = dir != null ? _hashKey(dir) : 0; + var children = []; + + for (var i = 0; i < _nHashItems; i++) { + var parent = _getParent(i); + if (type != null && _getType(i) != type) { + continue; + } + if (dir != null) { + if (parent == 0xffffffff || _getHash(parent) != dirHash) { + continue; + } + } else { + if (parent != 0xffffffff) { + continue; + } + } + children.add(_getKey(i)); + } + + return children; + } + + DBusValue? lookup(String key) { + var value = _lookup(key, type: 'v'); + if (value == null) { + return null; + } + var codec = GVariantBinaryCodec(); + return (codec.decode('v', value, endian: endian) as DBusVariant).value; + } + + GVariantDatabaseTable? lookupTable(String key) { + var value = _lookup(key, type: 'H'); + if (value == null) { + return null; + } + return GVariantDatabaseTable(value, fullData, endian); + } + + ByteData? _lookup(String key, {required String type}) { + var hash = _hashKey(key); + var bucket = hash % _nBuckets; + var start = data.getUint32(_bucketOffset + bucket * 4, endian); + var end = bucket + 1 < _nBuckets + ? data.getUint32(_bucketOffset + (bucket + 1) * 4, endian) + : _nHashItems; + start = 0; + end = _nHashItems; + + for (var i = start; i < end; i++) { + if (_getHash(i) == hash && + _getKey(i, recurse: true) == key && + _getType(i) == type) { + return _getValue(i); + } + } + + return null; + } + + /// Gets the hash for a table key. + int _hashKey(String key) { + var hashValue = 5381; + for (var o in utf8.encode(key)) { + // Use bytes as signed 8 bit numbers. + if (o >= 128) { + o -= 256; + } + hashValue = (hashValue * 33 + o) & 0xffffffff; + } + return hashValue; + } + + int _getHash(int index) { + return data.getUint32(_getHashItemOffset(index) + 0, endian); + } + + int _getParent(int index) { + return data.getUint32(_getHashItemOffset(index) + 4, endian); + } + + /// Gets the key name for a hash item. + String _getKey(int index, {bool recurse = false}) { + var parent = recurse ? _getParent(index) : 0xffffffff; + var parentKey = + parent != 0xffffffff ? _getKey(parent, recurse: recurse) : ''; + + var offset = _getHashItemOffset(index); + var keyStart = data.getUint32(offset + 8, endian); + var keySize = data.getUint16(offset + 12, endian); + return parentKey + utf8.decode(data.buffer.asUint8List(keyStart, keySize)); + } + + String _getType(int index) { + return ascii.decode([data.getUint8(_getHashItemOffset(index) + 14)]); + } + + ByteData _getValue(int index) { + var offset = _getHashItemOffset(index); + + var valueStart = data.getUint32(offset + 16, endian); + var valueEnd = data.getUint32(offset + 20, endian); + return ByteData.sublistView(fullData, valueStart, valueEnd); + } + + int _getHashItemOffset(int index) { + return _hashOffset + index * 24; + } +} diff --git a/app/3rd_party/gsettings/lib/src/gvariant_text_codec.dart b/app/3rd_party/gsettings/lib/src/gvariant_text_codec.dart new file mode 100644 index 0000000..83de690 --- /dev/null +++ b/app/3rd_party/gsettings/lib/src/gvariant_text_codec.dart @@ -0,0 +1,484 @@ +import 'package:dbus/dbus.dart'; + +class GVariantTextCodec { + GVariantTextCodec(); + + /// Encode a value using GVariant text format. + String encode(DBusValue value) { + var buffer = StringBuffer(); + _encode(buffer, value); + return buffer.toString(); + } + + /// Parse a single GVariant value. [type] is expected to be a valid single type. + DBusValue decode(String type, String data) { + var buffer = _DecodeBuffer(data); + var value = _decode(type, buffer); + buffer.consumeWhitespace(); + if (!buffer.isEmpty) { + throw "Unexpected data after encoded GVariant: '${buffer.data.substring(buffer.offset)}'"; + } + return value; + } + + void _encode(StringBuffer buffer, DBusValue value) { + if (value is DBusBoolean) { + buffer.write(value.value ? 'true' : 'false'); + } else if (value is DBusByte) { + buffer.write('0x' + value.value.toRadixString(16).padLeft(2, '0')); + } else if (value is DBusInt16) { + buffer.write(value.value.toString()); + } else if (value is DBusUint16) { + buffer.write(value.value.toString()); + } else if (value is DBusInt32) { + buffer.write(value.value.toString()); + } else if (value is DBusUint32) { + buffer.write(value.value.toString()); + } else if (value is DBusInt64) { + buffer.write(value.value.toString()); + } else if (value is DBusUint64) { + buffer.write(value.value.toString()); + } else if (value is DBusDouble) { + buffer.write(value.value.toString()); + } else if (value is DBusObjectPath) { + buffer.write('objectpath '); + _writeString(buffer, value.value); + } else if (value is DBusSignature) { + buffer.write('signature '); + _writeString(buffer, value.value); + } else if (value is DBusString) { + _writeString(buffer, value.value); + } else if (value is DBusVariant) { + buffer.write('<'); + _encode(buffer, value.value); + buffer.write('>'); + } else if (value is DBusMaybe) { + if (value.value != null) { + var childBuffer = StringBuffer(); + _encode(childBuffer, value.value!); + var childText = childBuffer.toString(); + if (childText.endsWith('nothing')) { + buffer.write('just '); + } + buffer.write(childText); + } else { + buffer.write('nothing'); + } + } else if (value is DBusStruct) { + buffer.write('('); + for (var child in value.children) { + if (child != value.children.first) { + buffer.write(', '); + } + _encode(buffer, child); + } + buffer.write(')'); + } else if (value is DBusArray) { + buffer.write('['); + for (var child in value.children) { + if (child != value.children.first) { + buffer.write(', '); + } + _encode(buffer, child); + } + buffer.write(']'); + } else if (value is DBusDict) { + buffer.write('{'); + var first = true; + for (var entry in value.children.entries) { + if (!first) { + buffer.write(', '); + } + first = false; + _encode(buffer, entry.key); + buffer.write(': '); + _encode(buffer, entry.value); + } + buffer.write('}'); + } else { + throw ("Unsupported DBus type: '$value'"); + } + } + + void _writeString(StringBuffer buffer, String value) { + var quote = value.contains("'") ? '"' : "'"; + buffer.write(quote); + for (var rune in value.runes) { + switch (rune) { + case 7: // bell + buffer.write(r'\a'); + break; + case 8: // backspace + buffer.write(r'\b'); + break; + case 9: // tab + buffer.write(r'\t'); + break; + case 10: // newline + buffer.write(r'\n'); + break; + case 11: // vertical tab + buffer.write(r'\v'); + break; + case 12: // form feed + buffer.write(r'\f'); + break; + case 13: // carriage return + buffer.write(r'\r'); + break; + case 34: // double quote + buffer.write(quote == '"' ? r'\"' : '"'); + break; + case 39: // single quote + buffer.write(quote == "'" ? r"\'" : "'"); + break; + case 92: // backslash + buffer.write(r'\\'); + break; + default: + // There's not a dart method to check if a character is "printable", so we use: + // 00 - 1f - C0 control + // 7f - delete + // 80 - 9f - C1 control + // e000 - f8ff - private use + // fff0 - ffff - specials + // 1ff80 - 1ffff - unassigned + // 2ff80 - 2ffff - unassigned + // 3ff80 - 3ffff - unassigned + // 4ff80 - 4ffff - unassigned + // 5ff80 - 5ffff - unassigned + // 6ff80 - 6ffff - unassigned + // 7ff80 - 7ffff - unassigned + // 8ff80 - 8ffff - unassigned + // 9ff80 - 9ffff - unassigned + // aff80 - affff - unassigned + // bff80 - bffff - unassigned + // cff80 - cffff - unassigned + // dff80 - dffff - unassigned + // eff80 - effff - unassigned + // f0000 - fffff - supplementary private use area A + // 100000 - 10fffd - supplementary private use area B + if (rune <= 0x1f || + (rune >= 0x7f && rune <= 0x9f) || + (rune >= 0xe000 && rune <= 0xf8ff) || + (rune >= 0xfff0 && rune <= 0xffff) || + (rune >= 0x1ff80 && rune <= 0x1ffff) || + (rune >= 0x2ff80 && rune <= 0x2ffff) || + (rune >= 0x3ff80 && rune <= 0x3ffff) || + (rune >= 0x4ff80 && rune <= 0x4ffff) || + (rune >= 0x5ff80 && rune <= 0x5ffff) || + (rune >= 0x6ff80 && rune <= 0x6ffff) || + (rune >= 0x7ff80 && rune <= 0x7ffff) || + (rune >= 0x8ff80 && rune <= 0x8ffff) || + (rune >= 0x9ff80 && rune <= 0x9ffff) || + (rune >= 0xaff80 && rune <= 0xaffff) || + (rune >= 0xbff80 && rune <= 0xbffff) || + (rune >= 0xcff80 && rune <= 0xcffff) || + (rune >= 0xdff80 && rune <= 0xdffff) || + (rune >= 0xeff80 && rune <= 0xeffff) || + (rune >= 0x100000 && rune <= 0x10fffd)) { + int padding; + String prefix; + if (rune <= 0xffff) { + padding = 4; + prefix = 'u'; + } else { + padding = 8; + prefix = 'U'; + } + var hex = rune.toRadixString(16).padLeft(padding, '0'); + buffer.write('\\$prefix$hex'); + } else { + buffer.writeCharCode(rune); + } + } + } + buffer.write(quote); + } + + DBusValue _decode(String type, _DecodeBuffer buffer) { + buffer.consumeWhitespace(); + + // struct + if (type.startsWith('(')) { + return _decodeStruct(type, buffer); + } + + // array / dict + if (type.startsWith('a')) { + if (type.startsWith('a{')) { + return _decodeDict(type, buffer); + } else { + return _decodeArray(type, buffer); + } + } + + // maybe + if (type.startsWith('m')) { + var childType = type.substring(1); + DBusValue? value; + if (!buffer.consume('nothing')) { + value = _decode(childType, buffer); + } + return DBusMaybe(DBusSignature(childType), value); + } + + switch (type) { + case 'b': // boolean + bool value; + if (buffer.consume('true')) { + value = true; + } else if (buffer.consume('false')) { + value = false; + } else { + throw 'Invalid boolean encoding'; + } + return DBusBoolean(value); + case 'y': // byte + return DBusByte(_decodeInteger(buffer)); + case 'n': // int16 + return DBusInt16(_decodeInteger(buffer)); + case 'q': // uint16 + return DBusUint16(_decodeInteger(buffer)); + case 'i': // int32 + return DBusInt32(_decodeInteger(buffer)); + case 'u': // uint32 + return DBusUint32(_decodeInteger(buffer)); + case 'x': // int64 + return DBusInt64(_decodeInteger(buffer)); + case 't': // uint64 + return DBusUint64(_decodeInteger(buffer)); + case 'd': // double + return DBusDouble(_decodeDouble(buffer)); + case 's': // string + return DBusString(_decodeString(buffer)); + case 'o': // object path + if (!buffer.consume('objectpath ')) { + throw 'Invalid object path encoding'; + } + return DBusObjectPath(_decodeString(buffer)); + case 'g': // signature + if (!buffer.consume('signature ')) { + throw 'Invalid signature encoding'; + } + return DBusSignature(_decodeString(buffer)); + default: + throw ("Unsupported GVariant type: '$type'"); + } + } + + int _decodeInteger(_DecodeBuffer buffer) { + var end = buffer.offset; + if (buffer.data.startsWith('-')) { + end++; + } else if (buffer.data.startsWith('0x')) { + end += 2; + } + while (end < buffer.data.length && + '0123456789abcdefABCDEF'.contains(buffer.data[end])) { + end++; + } + + var value = int.parse(buffer.data.substring(buffer.offset, end)); + buffer.offset = end; + return value; + } + + double _decodeDouble(_DecodeBuffer buffer) { + var end = buffer.offset; + if (buffer.data.startsWith('-', end)) { + end++; + } + while ( + end < buffer.data.length && '0123456789'.contains(buffer.data[end])) { + end++; + } + if (buffer.data.startsWith('.', end)) { + end++; + } + while ( + end < buffer.data.length && '0123456789'.contains(buffer.data[end])) { + end++; + } + + var value = double.parse(buffer.data.substring(buffer.offset, end)); + buffer.offset = end; + return value; + } + + String _decodeString(_DecodeBuffer buffer) { + var output = StringBuffer(); + if (buffer.isEmpty) { + throw 'No data for string'; + } + var quote = buffer.data[buffer.offset]; + if (quote != "'" && quote != '"') { + throw 'Missing start quote on string'; + } + var end = buffer.offset + 1; + while (end < buffer.data.length) { + var c = buffer.data[end]; + end++; + if (c == quote) { + buffer.offset = end; + return output.toString(); + } else if (c == r'\') { + if (end == buffer.data.length - 1) { + throw 'Escape character at end of string'; + } + var escapeChar = buffer.data[end]; + end++; + switch (escapeChar) { + case 'a': // bell + output.writeCharCode(7); + break; + case 'b': // backspace + output.writeCharCode(8); + break; + case 't': // tab + output.writeCharCode(9); + break; + case 'n': // newline + output.writeCharCode(10); + break; + case 'v': // vertical tab + output.writeCharCode(11); + break; + case 'f': // form feed + output.writeCharCode(12); + break; + case 'r': // carriage return + output.writeCharCode(13); + break; + case 'u': + if (end + 4 > buffer.data.length) { + throw ('Not enough space for unicode character'); + } + output.writeCharCode( + int.parse(buffer.data.substring(end, end + 4), radix: 16)); + end += 4; + break; + case 'U': + if (end + 8 > buffer.data.length) { + throw ('Not enough space for unicode character'); + } + output.writeCharCode( + int.parse(buffer.data.substring(end, end + 8), radix: 16)); + end += 8; + break; + case '"': + case "'": + case r'\': + default: + output.write(escapeChar); + break; + } + } else { + output.write(c); + } + } + + throw 'Missing end quote on string'; + } + + DBusStruct _decodeStruct(String type, _DecodeBuffer buffer) { + if (!buffer.consume('(')) { + throw 'Missing start of struct'; + } + var signature = DBusSignature(type.substring(1, type.length - 1)); + var children = []; + var first = true; + for (var childSignature in signature.split()) { + if (!first && !buffer.consume(',')) { + throw ('Missing comma between struct elements'); + } + first = false; + buffer.consumeWhitespace(); + children.add(_decode(childSignature.value, buffer)); + } + if (!buffer.consume(')')) { + throw 'Missing end of struct'; + } + return DBusStruct(children); + } + + DBusArray _decodeArray(String type, _DecodeBuffer buffer) { + if (!buffer.consume('[')) { + throw 'Missing start of array'; + } + var childType = type.substring(1); + var children = []; + var first = true; + while (!buffer.isEmpty) { + if (buffer.consume(']')) { + return DBusArray(DBusSignature(childType), children); + } + + if (!first && !buffer.consume(',')) { + throw ('Missing comma between array elements'); + } + first = false; + buffer.consumeWhitespace(); + children.add(_decode(childType, buffer)); + } + throw 'Missing end of array'; + } + + DBusDict _decodeDict(String type, _DecodeBuffer buffer) { + var signatures = DBusSignature(type.substring(2, type.length - 1)).split(); + var keyType = signatures[0].value; + var valueType = signatures[1].value; + if (!buffer.consume('{')) { + throw 'Missing start of dict'; + } + var children = {}; + var first = true; + while (!buffer.isEmpty) { + if (buffer.consume('}')) { + return DBusDict( + DBusSignature(keyType), DBusSignature(valueType), children); + } + + if (!first && !buffer.consume(',')) { + throw ('Missing comma between dict elements'); + } + first = false; + buffer.consumeWhitespace(); + + var key = _decode(keyType, buffer); + buffer.consumeWhitespace(); + if (!first && !buffer.consume(':')) { + throw ('Missing colon between dict key and value'); + } + buffer.consumeWhitespace(); + var value = _decode(valueType, buffer); + + children[key] = value; + } + throw 'Missing end of dict'; + } +} + +class _DecodeBuffer { + String data; + int offset = 0; + + _DecodeBuffer(this.data); + + bool get isEmpty => offset >= data.length; + + void consumeWhitespace() { + while (offset < data.length && data.startsWith(' ', offset)) { + offset++; + } + } + + bool consume(String value) { + if (!data.startsWith(value, offset)) { + return false; + } + + offset += value.length; + return true; + } +} diff --git a/app/3rd_party/gsettings/pubspec.yaml b/app/3rd_party/gsettings/pubspec.yaml new file mode 100644 index 0000000..73498c0 --- /dev/null +++ b/app/3rd_party/gsettings/pubspec.yaml @@ -0,0 +1,21 @@ +name: gsettings +version: 0.2.0 + +publish_to: none + +description: + GSettings client + +homepage: https://github.com/canonical/gsettings.dart + +environment: + sdk: '>=2.12.0 <3.0.0' + +dependencies: + dbus: ^0.6.0 + xdg_directories: + path: ../xdg_directories + +dev_dependencies: + pedantic: ^1.9.0 + test: ^1.16.8 diff --git a/app/3rd_party/path_provider_linux/.gitignore b/app/3rd_party/path_provider_linux/.gitignore new file mode 100644 index 0000000..e9dc58d --- /dev/null +++ b/app/3rd_party/path_provider_linux/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ diff --git a/app/3rd_party/path_provider_linux/.metadata b/app/3rd_party/path_provider_linux/.metadata new file mode 100644 index 0000000..9615744 --- /dev/null +++ b/app/3rd_party/path_provider_linux/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: e491544588e8d34fdf31d5f840b4649850ef167a + channel: master + +project_type: plugin diff --git a/app/3rd_party/path_provider_linux/AUTHORS b/app/3rd_party/path_provider_linux/AUTHORS new file mode 100644 index 0000000..493a0b4 --- /dev/null +++ b/app/3rd_party/path_provider_linux/AUTHORS @@ -0,0 +1,66 @@ +# Below is a list of people and organizations that have contributed +# to the Flutter project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. +The Chromium Authors +German Saprykin +Benjamin Sauer +larsenthomasj@gmail.com +Ali Bitek +Pol Batlló +Anatoly Pulyaevskiy +Hayden Flinner +Stefano Rodriguez +Salvatore Giordano +Brian Armstrong +Paul DeMarco +Fabricio Nogueira +Simon Lightfoot +Ashton Thomas +Thomas Danner +Diego Velásquez +Hajime Nakamura +Tuyển Vũ Xuân +Miguel Ruivo +Sarthak Verma +Mike Diarmid +Invertase +Elliot Hesp +Vince Varga +Aawaz Gyawali +EUI Limited +Katarina Sheremet +Thomas Stockx +Sarbagya Dhaubanjar +Ozkan Eksi +Rishab Nayak +ko2ic +Jonathan Younger +Jose Sanchez +Debkanchan Samadder +Audrius Karosevicius +Lukasz Piliszczuk +SoundReply Solutions GmbH +Rafal Wachol +Pau Picas +Christian Weder +Alexandru Tuca +Christian Weder +Rhodes Davis Jr. +Luigi Agosti +Quentin Le Guennec +Koushik Ravikumar +Nissim Dsilva +Giancarlo Rocha +Ryo Miyake +Théo Champion +Kazuki Yamaguchi +Eitan Schwartz +Chris Rutkowski +Juan Alvarez +Aleksandr Yurkovskiy +Anton Borries +Alex Li +Rahul Raj <64.rahulraj@gmail.com> diff --git a/app/3rd_party/path_provider_linux/CHANGELOG.md b/app/3rd_party/path_provider_linux/CHANGELOG.md new file mode 100644 index 0000000..66c11a4 --- /dev/null +++ b/app/3rd_party/path_provider_linux/CHANGELOG.md @@ -0,0 +1,42 @@ +## 2.0.2 + +* Updated installation instructions in README. + +## 2.0.1 + +* Add `implements` to pubspec.yaml. +* Add `registerWith` method to the main Dart class. + +## 2.0.0 + +* Migrate to null safety. + +## 0.1.1+3 + +* Update Flutter SDK constraint. + +## 0.1.1+2 + +* Log errors in the example when calls to the `path_provider` fail. + +## 0.1.1+1 + +* Check in linux/ directory for example/ + +## 0.1.1 - NOT PUBLISHED +* Reverts changes on 0.1.0, which broke the tree. + + +## 0.1.0 - NOT PUBLISHED +* This release updates getApplicationSupportPath to use the application ID instead of the executable name. + * No migration is provided, so any older apps that were using this path will now have a different directory. + +## 0.0.1+2 +* This release updates the example to depend on the endorsed plugin rather than relative path + +## 0.0.1+1 +* This updates the readme and pubspec and example to reflect the endorsement of this implementation of `path_provider` + +## 0.0.1 +* The initial implementation of path_provider for Linux + * Implements getApplicationSupportPath, getApplicationDocumentsPath, getDownloadsPath, and getTemporaryPath diff --git a/app/3rd_party/path_provider_linux/LICENSE b/app/3rd_party/path_provider_linux/LICENSE new file mode 100644 index 0000000..c6823b8 --- /dev/null +++ b/app/3rd_party/path_provider_linux/LICENSE @@ -0,0 +1,25 @@ +Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/app/3rd_party/path_provider_linux/README.md b/app/3rd_party/path_provider_linux/README.md new file mode 100644 index 0000000..b0b73dc --- /dev/null +++ b/app/3rd_party/path_provider_linux/README.md @@ -0,0 +1,11 @@ +# path\_provider\_linux + +The linux implementation of [`path_provider`]. + +## Usage + +This package is [endorsed][2], which means you can simply use `path_provider` +normally. This package will be automatically included in your app when you do. + +[1]: https://pub.dev/packages/path_provider +[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin diff --git a/app/3rd_party/path_provider_linux/lib/path_provider_linux.dart b/app/3rd_party/path_provider_linux/lib/path_provider_linux.dart new file mode 100644 index 0000000..0adbe44 --- /dev/null +++ b/app/3rd_party/path_provider_linux/lib/path_provider_linux.dart @@ -0,0 +1,49 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:xdg_directories/xdg_directories.dart'; + +XDGDirectories xdg = XDGDirectories(); + +/// The linux implementation of [PathProviderPlatform] +/// +/// This class implements the `package:path_provider` functionality for linux +class PathProviderLinux extends PathProviderPlatform { + /// Registers this class as the default instance of [PathProviderPlatform] + static void registerWith() { + PathProviderPlatform.instance = PathProviderLinux(); + } + + @override + Future getTemporaryPath() { + return Future.value('/tmp'); + } + + @override + Future getApplicationSupportPath() async { + final String processName = path.basenameWithoutExtension( + await File('/proc/self/exe').resolveSymbolicLinks()); + final Directory directory = + Directory(path.join(xdg.dataHome.path, processName)); + // Creating the directory if it doesn't exist, because mobile implementations assume the directory exists + if (!directory.existsSync()) { + await directory.create(recursive: true); + } + return directory.path; + } + + @override + Future getApplicationDocumentsPath() { + return Future.value(xdg.getUserDirectory('DOCUMENTS')?.path); + } + + @override + Future getDownloadsPath() { + return Future.value(xdg.getUserDirectory('DOWNLOAD')?.path); + } +} diff --git a/app/3rd_party/path_provider_linux/pubspec.yaml b/app/3rd_party/path_provider_linux/pubspec.yaml new file mode 100644 index 0000000..a39a9e3 --- /dev/null +++ b/app/3rd_party/path_provider_linux/pubspec.yaml @@ -0,0 +1,30 @@ +name: path_provider_linux +description: Linux implementation of the path_provider plugin +repository: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider_linux +issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 +version: 2.0.2 + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=2.0.0" + +flutter: + plugin: + implements: path_provider + platforms: + linux: + dartPluginClass: PathProviderLinux + pluginClass: none + +dependencies: + flutter: + sdk: flutter + path: ^1.8.0 + path_provider_platform_interface: ^2.0.0 + xdg_directories: + path: ../xdg_directories + +dev_dependencies: + flutter_test: + sdk: flutter + pedantic: ^1.10.0 diff --git a/app/3rd_party/window_size/.gitignore b/app/3rd_party/window_size/.gitignore new file mode 100644 index 0000000..0393a47 --- /dev/null +++ b/app/3rd_party/window_size/.gitignore @@ -0,0 +1,5 @@ +.dart_tool +.packages +.flutter-plugins +.flutter-plugins-dependencies +pubspec.lock diff --git a/app/3rd_party/window_size/LICENSE b/app/3rd_party/window_size/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/app/3rd_party/window_size/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/app/3rd_party/window_size/README.md b/app/3rd_party/window_size/README.md new file mode 100644 index 0000000..f811945 --- /dev/null +++ b/app/3rd_party/window_size/README.md @@ -0,0 +1,40 @@ +# window_size + +This plugin allows resizing and repositioning the window containing the Flutter +content, as well as querying screen information. + +This is a prototype, and in the long term is expected to be replaced by +[functionality within the Flutter +framework](https://flutter.dev/go/desktop-multi-window-support). + +## Scope + +There are currently no plans to add new functionality, such as window +minimization and maximization, to this plugin. The goals of this plugin were to: +- unblock certain core use cases among very early adopters, and +- validate plugin APIs in Flutter itself during early development of the desktop + plugin APIs. + +Now that those goals have been met, and the plugin APIs have been stabilized +such that anyone can create and publish desktop Flutter plugins, new functionality +will likely only be added here if unblocks a [Flutter top-tier +customer](https://github.com/flutter/flutter/wiki/Issue-hygiene#customers). +The community is encouraged to create their own plugins for other window +manipulation features. + +## Supported Platforms + +- [x] macOS +- [x] Windows +- [x] Linux + +Not all operations have been implemented on all platforms, but the core functionality +of resizing and repositioning is available for all three. + +## Use + +See [the plugin README](../README.md) for general instructions on using FDE plugins. + +### Linux + +Requires GTK 3.22 or later. diff --git a/app/3rd_party/window_size/analysis_options.yaml b/app/3rd_party/window_size/analysis_options.yaml new file mode 100644 index 0000000..1a8ac5f --- /dev/null +++ b/app/3rd_party/window_size/analysis_options.yaml @@ -0,0 +1,126 @@ +# Copyright 2017 The Fuchsia Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Root analysis options shared among all Dart code in the respository. Based +# on the Fuchsia standard analysis options, with some changes. +linter: + # Full list available at http://dart-lang.github.io/linter/lints/options/options.html. + rules: + - always_declare_return_types + - always_put_control_body_on_new_line + - always_put_required_named_parameters_first + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catches_without_on_clauses + - avoid_catching_errors + - avoid_classes_with_only_static_members + - avoid_empty_else + # Not compatible with VS Code yet. + # - avoid_field_initializers_in_const_classes + - avoid_function_literals_in_foreach_calls + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_private_typedef_functions + # TODO: Change relative imports for package imports + # - avoid_relative_lib_imports + # This puts an unnecessary burden on API clients. + # - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null + - avoid_returning_this + - avoid_single_cascade_in_expression_statements + - avoid_setters_without_getters + - avoid_slow_async_io + - avoid_types_as_parameter_names + - avoid_types_on_closure_parameters + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_types + - cancel_subscriptions + - cascade_invocations + - close_sinks + - comment_references + - constant_identifier_names + - control_flow_in_finally + - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + - hash_and_equals + - implementation_imports + - invariant_booleans + - iterable_contains_unrelated_type + - join_return_with_assignment + - library_names + - library_prefixes + - list_remove_unrelated_type + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - no_duplicate_case_values + - non_constant_identifier_names + - omit_local_variable_types + - one_member_abstracts + - only_throw_errors + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_collection_literals + - prefer_conditional_assignment + # Disabled until bug is fixed + # https://github.com/dart-lang/linter/issues/995 + # - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_equal_for_default_values + # Add this when 'short' is better defined. + # - prefer_expression_function_bodies + - prefer_final_fields + - prefer_final_locals + # Seems to have false positive with await for. + # - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_initializing_formals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_iterable_whereType + - prefer_single_quotes + - prefer_typing_uninitialized_variables + - public_member_api_docs + - recursive_getters + - slash_for_doc_comments + - sort_constructors_first + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - type_annotate_public_apis + - type_init_formals + - unawaited_futures + - unnecessary_brace_in_string_interps + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_statements + - unnecessary_this + - unrelated_type_equality_checks + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_string_buffers + - use_to_and_as_if_applicable + - valid_regexps + # Not compatible with VS Code yet. + # - void_checks diff --git a/app/3rd_party/window_size/lib/src/platform_window.dart b/app/3rd_party/window_size/lib/src/platform_window.dart new file mode 100644 index 0000000..82e14cb --- /dev/null +++ b/app/3rd_party/window_size/lib/src/platform_window.dart @@ -0,0 +1,32 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import 'dart:ui'; + +import 'screen.dart'; + +/// Represents a window, containing information about its size, position, and +/// properties. +class PlatformWindow { + /// Create a new window. + PlatformWindow(this.frame, this.scaleFactor, this.screen); + + /// The frame of the screen, in screen coordinates. + final Rect frame; + + /// The number of pixels per screen coordinate for this screen. + final double scaleFactor; + + /// The (or a) screen containing this window, if any. + final Screen? screen; +} diff --git a/app/3rd_party/window_size/lib/src/screen.dart b/app/3rd_party/window_size/lib/src/screen.dart new file mode 100644 index 0000000..ca7c27e --- /dev/null +++ b/app/3rd_party/window_size/lib/src/screen.dart @@ -0,0 +1,31 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import 'dart:ui'; + +/// Represents a screen, containing information about its size, position, and +/// properties. +class Screen { + /// Create a new screen. + Screen(this.frame, this.visibleFrame, this.scaleFactor); + + /// The frame of the screen, in screen coordinates. + final Rect frame; + + /// The portion of the screen's frame that is available for use by application + /// windows. E.g., on macOS, this excludes the menu bar. + final Rect visibleFrame; + + /// The number of pixels per screen coordinate for this screen. + final double scaleFactor; +} diff --git a/app/3rd_party/window_size/lib/src/window_size_channel.dart b/app/3rd_party/window_size/lib/src/window_size_channel.dart new file mode 100644 index 0000000..bc82291 --- /dev/null +++ b/app/3rd_party/window_size/lib/src/window_size_channel.dart @@ -0,0 +1,249 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/services.dart'; + +import 'platform_window.dart'; +import 'screen.dart'; + +/// The name of the plugin's platform channel. +const String _windowSizeChannelName = 'flutter/windowsize'; + +/// The method name to request information about the available screens. +/// +/// Returns a list of screen info maps; see keys below. +const String _getScreenListMethod = 'getScreenList'; + +/// The method name to request information about the window containing the +/// Flutter instance. +/// +/// Returns a list of window info maps; see keys below. +const String _getWindowInfoMethod = 'getWindowInfo'; + +/// The method name to set the frame of a window. +/// +/// Takes a frame array, as documented for the value of _frameKey. +const String _setWindowFrameMethod = 'setWindowFrame'; + +/// The method name to set the minimum size of a window. +/// +/// Takes a window size array, with the value is a list of two doubles: +/// [width, height]. +/// +/// A value of zero for width or height is to be interpreted as +/// unconstrained in that dimension. +const String _setWindowMinimumSizeMethod = 'setWindowMinimumSize'; + +/// The method name to set the maximum size of a window. +/// +/// Takes a window size array, with the value is a list of two doubles: +/// [width, height]. +/// +/// A value of `-1` for width or height is to be interpreted as +/// unconstrained in that dimension. +const String _setWindowMaximumSizeMethod = 'setWindowMaximumSize'; + +/// The method name to set the window title of a window. +const String _setWindowTitleMethod = 'setWindowTitle'; + +/// The method name to set the window title's represented URL. +/// +/// Only implemented for macOS. If the URL is a file URL, the +/// window shows an icon in its title bar. +const String _setWindowTitleRepresentedUrlMethod = + 'setWindowTitleRepresentedUrl'; + +/// The method name to get the minimum size of a window. +/// +/// Returns a window size array, with the value is a list of two doubles: +/// [width, height]. +/// +/// A value of zero for width or height is to be interpreted as +/// unconstrained in that dimension. +const String _getWindowMinimumSizeMethod = 'getWindowMinimumSize'; + +/// The method name to get the maximum size of a window. +/// +/// Returns a window size array, with the value is a list of two doubles: +/// [width, height]. +/// +/// A value of `-1` for width or height is to be interpreted as +/// unconstrained in that dimension. +const String _getWindowMaximumSizeMethod = 'getWindowMaximumSize'; + +/// The method name to set the window visibility. +/// +/// The argument will be a boolean controlling whether or not the window should +/// be visible. +const String _setWindowVisibilityMethod = 'setWindowVisibility'; + +// Keys for screen and window maps returned by _getScreenListMethod. + +/// The frame of a screen or window. The value is a list of four doubles: +/// [left, top, width, height] +const String _frameKey = 'frame'; + +/// The frame of a screen available for use by applications. The value format +/// is the same as _frameKey's. +/// +/// Only used for screens. +const String _visibleFrameKey = 'visibleFrame'; + +/// The scale factor for a screen or window, as a double. +/// +/// This is the number of pixels per screen coordinate, and thus the ratio +/// between sizes as seen by Flutter and sizes in native screen coordinates. +const String _scaleFactorKey = 'scaleFactor'; + +/// The screen containing this window, if any. The value is a screen map, or +/// null if the window is not visible on a screen. +/// +/// Only used for windows. +/// +/// If a window is on multiple screens, it is up to the platform to decide which +/// screen to report. +const String _screenKey = 'screen'; + +/// A singleton object that handles the interaction with the platform channel. +class WindowSizeChannel { + /// Private constructor. + WindowSizeChannel._(); + + final MethodChannel _platformChannel = + const MethodChannel(_windowSizeChannelName); + + /// The static instance of the menu channel. + static final WindowSizeChannel instance = new WindowSizeChannel._(); + + /// Returns a list of screens. + Future> getScreenList() async { + final screenList = []; + final response = await _platformChannel.invokeMethod(_getScreenListMethod); + + for (final screenInfo in response) { + screenList.add(_screenFromInfoMap(screenInfo)); + } + return screenList; + } + + /// Returns information about the window containing this Flutter instance. + Future getWindowInfo() async { + final response = await _platformChannel.invokeMethod(_getWindowInfoMethod); + + final screenInfo = response[_screenKey]; + final screen = screenInfo == null ? null : _screenFromInfoMap(screenInfo); + return PlatformWindow(_rectFromLTWHList(response[_frameKey].cast()), + response[_scaleFactorKey], screen); + } + + /// Sets the frame of the window containing this Flutter instance, in + /// screen coordinates. + /// + /// The platform may adjust the frame as necessary if the provided frame would + /// cause significant usability issues (e.g., a window with no visible portion + /// that can be used to move the window). + void setWindowFrame(Rect frame) async { + assert(!frame.isEmpty, 'Cannot set window frame to an empty rect.'); + assert(frame.isFinite, 'Cannot set window frame to a non-finite rect.'); + await _platformChannel.invokeMethod(_setWindowFrameMethod, + [frame.left, frame.top, frame.width, frame.height]); + } + + /// Sets the minimum size of the window containing this Flutter instance. + void setWindowMinSize(Size size) async { + await _platformChannel + .invokeMethod(_setWindowMinimumSizeMethod, [size.width, size.height]); + } + + /// Sets the visibility of the window. + void setWindowVisibility({required bool visible}) async { + await _platformChannel.invokeMethod(_setWindowVisibilityMethod, visible); + } + + // Window maximum size unconstrained is passed over the channel as -1. + double _channelRepresentationForMaxDimension(double size) { + return size == double.infinity ? -1 : size; + } + + /// Sets the maximum size of the window containing this Flutter instance. + void setWindowMaxSize(Size size) async { + await _platformChannel.invokeMethod(_setWindowMaximumSizeMethod, [ + _channelRepresentationForMaxDimension(size.width), + _channelRepresentationForMaxDimension(size.height), + ]); + } + + /// Sets the title of the window containing this Flutter instance. + void setWindowTitle(String title) async { + await _platformChannel.invokeMapMethod(_setWindowTitleMethod, title); + } + + /// Sets the title's represented URL of the window containing this Flutter instance. + void setWindowTitleRepresentedUrl(Uri file) async { + await _platformChannel.invokeMapMethod( + _setWindowTitleRepresentedUrlMethod, file.toString()); + } + + /// Gets the minimum size of the window containing this Flutter instance. + Future getWindowMinSize() async { + final response = + await _platformChannel.invokeMethod(_getWindowMinimumSizeMethod); + return _sizeFromWHList(List.from(response.cast())); + } + + // Window maximum size unconstrained is passed over the channel as -1. + double _maxDimensionFromChannelRepresentation(double size) { + return size == -1 ? double.infinity : size; + } + + /// Gets the maximum size of the window containing this Flutter instance. + Future getWindowMaxSize() async { + final response = + await _platformChannel.invokeMethod(_getWindowMaximumSizeMethod); + return _sizeFromWHList( + List.from( + response.cast().map(_maxDimensionFromChannelRepresentation), + ), + ); + } + + /// Given an array of the form [left, top, width, height], return the + /// corresponding [Rect]. + /// + /// Used for frame deserialization in the platform channel. + Rect _rectFromLTWHList(List ltwh) { + return Rect.fromLTWH(ltwh[0], ltwh[1], ltwh[2], ltwh[3]); + } + + /// Given an array of the form [width, height], return the corresponding + /// [Size]. + /// + /// Used for window size deserialization in the platform channel. + Size _sizeFromWHList(List wh) { + return Size(wh[0], wh[1]); + } + + /// Given a map of information about a screen, return the corresponding + /// [Screen] object. + /// + /// Used for screen deserialization in the platform channel. + Screen _screenFromInfoMap(Map map) { + return Screen( + _rectFromLTWHList(map[_frameKey].cast()), + _rectFromLTWHList(map[_visibleFrameKey].cast()), + map[_scaleFactorKey]); + } +} diff --git a/app/3rd_party/window_size/lib/src/window_size_utils.dart b/app/3rd_party/window_size/lib/src/window_size_utils.dart new file mode 100644 index 0000000..10b70e5 --- /dev/null +++ b/app/3rd_party/window_size/lib/src/window_size_utils.dart @@ -0,0 +1,89 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import 'dart:async'; +import 'dart:ui'; + +import 'platform_window.dart'; +import 'screen.dart'; +import 'window_size_channel.dart'; + +/// Returns a list of [Screen]s for the current screen configuration. +/// +/// It is possible for this list to be empty, if the machine is running in +/// a headless mode. +Future> getScreenList() async { + return await WindowSizeChannel.instance.getScreenList(); +} + +/// Returns the [Screen] showing the window that contains this Flutter instance. +/// +/// If the window is not being displayed, returns null. If the window is being +/// displayed on multiple screens, the platform can return any of those screens. +Future getCurrentScreen() async { + final windowInfo = await WindowSizeChannel.instance.getWindowInfo(); + return windowInfo.screen; +} + +/// Returns information about the window containing this Flutter instance. +Future getWindowInfo() async { + return await WindowSizeChannel.instance.getWindowInfo(); +} + +/// Sets the frame of the window containing this Flutter instance, in +/// screen coordinates. +/// +/// The platform may adjust the frame as necessary if the provided frame would +/// cause significant usability issues (e.g., a window with no visible portion +/// that can be used to move the window). +void setWindowFrame(Rect frame) async { + WindowSizeChannel.instance.setWindowFrame(frame); +} + +/// Sets the minimum [Size] of the window containing this Flutter instance. +void setWindowMinSize(Size size) async { + WindowSizeChannel.instance.setWindowMinSize(size); +} + +/// Sets the maximum [Size] of the window containing this Flutter instance. +void setWindowMaxSize(Size size) async { + WindowSizeChannel.instance.setWindowMaxSize(size); +} + +/// Sets the window title, as a [String], of the window containing this Flutter instance. +void setWindowTitle(String title) async { + WindowSizeChannel.instance.setWindowTitle(title); +} + +/// Shows or hides the window. +void setWindowVisibility({required bool visible}) async { + WindowSizeChannel.instance.setWindowVisibility(visible: visible); +} + +/// Sets the window title's represented [Uri], of the window containing this Flutter instance. +/// +/// Only implemented for macOS. If the URL is a file URL, the +/// window shows an icon in its title bar. +void setWindowTitleRepresentedUrl(Uri url) async { + WindowSizeChannel.instance.setWindowTitleRepresentedUrl(url); +} + +/// Gets the minimum [Size] of the window containing this Flutter instance. +Future getWindowMinSize() async { + return WindowSizeChannel.instance.getWindowMinSize(); +} + +/// Gets the maximum [Size] of the window containing this Flutter instance. +Future getWindowMaxSize() async { + return WindowSizeChannel.instance.getWindowMaxSize(); +} diff --git a/app/3rd_party/window_size/lib/window_size.dart b/app/3rd_party/window_size/lib/window_size.dart new file mode 100644 index 0000000..e038f96 --- /dev/null +++ b/app/3rd_party/window_size/lib/window_size.dart @@ -0,0 +1,16 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +export 'src/platform_window.dart'; +export 'src/screen.dart'; +export 'src/window_size_utils.dart'; diff --git a/app/3rd_party/window_size/linux/CMakeLists.txt b/app/3rd_party/window_size/linux/CMakeLists.txt new file mode 100644 index 0000000..3a0be19 --- /dev/null +++ b/app/3rd_party/window_size/linux/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.10) +set(PROJECT_NAME "window_size") +project(${PROJECT_NAME} LANGUAGES CXX) + +set(PLUGIN_NAME "${PROJECT_NAME}_plugin") + +add_library(${PLUGIN_NAME} SHARED + "${PLUGIN_NAME}.cc" +) +apply_standard_settings(${PLUGIN_NAME}) +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) diff --git a/app/3rd_party/window_size/linux/include/window_size/window_size_plugin.h b/app/3rd_party/window_size/linux/include/window_size/window_size_plugin.h new file mode 100644 index 0000000..5a7efa5 --- /dev/null +++ b/app/3rd_party/window_size/linux/include/window_size/window_size_plugin.h @@ -0,0 +1,40 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef PLUGINS_WINDOW_SIZE_LINUX_WINDOW_SIZE_PLUGIN_H_ +#define PLUGINS_WINDOW_SIZE_LINUX_WINDOW_SIZE_PLUGIN_H_ + +// A plugin to allow resizing the window. + +#include + +G_BEGIN_DECLS + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +G_DECLARE_FINAL_TYPE(FlWindowSizePlugin, fl_window_size_plugin, FL, + WINDOW_SIZE_PLUGIN, GObject) + +FLUTTER_PLUGIN_EXPORT FlWindowSizePlugin* fl_window_size_plugin_new( + FlPluginRegistrar* registrar); + +FLUTTER_PLUGIN_EXPORT void window_size_plugin_register_with_registrar( + FlPluginRegistrar* registrar); + +G_END_DECLS + +#endif // PLUGINS_WINDOW_SIZE_LINUX_WINDOW_SIZE_PLUGIN_H_ diff --git a/app/3rd_party/window_size/linux/window_size_plugin.cc b/app/3rd_party/window_size/linux/window_size_plugin.cc new file mode 100644 index 0000000..6d59bed --- /dev/null +++ b/app/3rd_party/window_size/linux/window_size_plugin.cc @@ -0,0 +1,411 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "include/window_size/window_size_plugin.h" + +#include +#include + +// See window_size_channel.dart for documentation. +const char kChannelName[] = "flutter/windowsize"; +const char kBadArgumentsError[] = "Bad Arguments"; +const char kNoScreenError[] = "No Screen"; +const char kGetScreenListMethod[] = "getScreenList"; +const char kGetWindowInfoMethod[] = "getWindowInfo"; +const char kSetWindowFrameMethod[] = "setWindowFrame"; +const char kSetWindowMinimumSizeMethod[] = "setWindowMinimumSize"; +const char kSetWindowMaximumSizeMethod[] = "setWindowMaximumSize"; +const char kSetWindowTitleMethod[] = "setWindowTitle"; +const char ksetWindowVisibilityMethod[] = "setWindowVisibility"; +const char kGetWindowMinimumSizeMethod[] = "getWindowMinimumSize"; +const char kGetWindowMaximumSizeMethod[] = "getWindowMaximumSize"; +const char kFrameKey[] = "frame"; +const char kVisibleFrameKey[] = "visibleFrame"; +const char kScaleFactorKey[] = "scaleFactor"; +const char kScreenKey[] = "screen"; + +struct _FlWindowSizePlugin { + GObject parent_instance; + + FlPluginRegistrar* registrar; + + // Connection to Flutter engine. + FlMethodChannel* channel; + + // Requested window geometry. + GdkGeometry window_geometry; +}; + +G_DEFINE_TYPE(FlWindowSizePlugin, fl_window_size_plugin, g_object_get_type()) + +// Gets the window being controlled. +GtkWindow* get_window(FlWindowSizePlugin* self) { + FlView* view = fl_plugin_registrar_get_view(self->registrar); + if (view == nullptr) return nullptr; + + return GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Gets the display connection. +GdkDisplay* get_display(FlWindowSizePlugin* self) { + FlView* view = fl_plugin_registrar_get_view(self->registrar); + if (view == nullptr) return nullptr; + + return gtk_widget_get_display(GTK_WIDGET(view)); +} + +// Converts frame dimensions into the Flutter representation. +FlValue* make_frame_value(gint x, gint y, gint width, gint height) { + g_autoptr(FlValue) value = fl_value_new_list(); + + fl_value_append_take(value, fl_value_new_float(x)); + fl_value_append_take(value, fl_value_new_float(y)); + fl_value_append_take(value, fl_value_new_float(width)); + fl_value_append_take(value, fl_value_new_float(height)); + + return fl_value_ref(value); +} + +// Converts monitor information into the Flutter representation. +FlValue* make_monitor_value(GdkMonitor* monitor) { + g_autoptr(FlValue) value = fl_value_new_map(); + + GdkRectangle frame; + gdk_monitor_get_geometry(monitor, &frame); + fl_value_set_string_take( + value, kFrameKey, + make_frame_value(frame.x, frame.y, frame.width, frame.height)); + + gdk_monitor_get_workarea(monitor, &frame); + fl_value_set_string_take( + value, kVisibleFrameKey, + make_frame_value(frame.x, frame.y, frame.width, frame.height)); + + gint scale_factor = gdk_monitor_get_scale_factor(monitor); + fl_value_set_string_take(value, kScaleFactorKey, + fl_value_new_float(scale_factor)); + + return fl_value_ref(value); +} + +// Gets the list of current screens. +static FlMethodResponse* get_screen_list(FlWindowSizePlugin* self) { + g_autoptr(FlValue) screens = fl_value_new_list(); + + GdkDisplay* display = get_display(self); + if (display == nullptr) { + return FL_METHOD_RESPONSE( + fl_method_error_response_new(kNoScreenError, nullptr, nullptr)); + } + + gint n_monitors = gdk_display_get_n_monitors(display); + for (gint i = 0; i < n_monitors; i++) { + GdkMonitor* monitor = gdk_display_get_monitor(display, i); + fl_value_append_take(screens, make_monitor_value(monitor)); + } + + return FL_METHOD_RESPONSE(fl_method_success_response_new(screens)); +} + +// Gets information about the Flutter window. +static FlMethodResponse* get_window_info(FlWindowSizePlugin* self) { + GtkWindow* window = get_window(self); + if (window == nullptr) { + return FL_METHOD_RESPONSE( + fl_method_error_response_new(kNoScreenError, nullptr, nullptr)); + } + + g_autoptr(FlValue) window_info = fl_value_new_map(); + + gint x, y, width, height; + gtk_window_get_position(window, &x, &y); + gtk_window_get_size(window, &width, &height); + fl_value_set_string_take(window_info, kFrameKey, + make_frame_value(x, y, width, height)); + + // Get the monitor this window is inside, or the primary monitor if doesn't + // appear to be in any. + GdkDisplay* display = get_display(self); + GdkMonitor* monitor_with_window = gdk_display_get_primary_monitor(display); + int n_monitors = gdk_display_get_n_monitors(display); + for (int i = 0; i < n_monitors; i++) { + GdkMonitor* monitor = gdk_display_get_monitor(display, i); + + GdkRectangle frame; + gdk_monitor_get_geometry(monitor, &frame); + if ((x >= frame.x && x <= frame.x + frame.width) && + (y >= frame.y && y <= frame.y + frame.width)) { + monitor_with_window = monitor; + break; + } + } + fl_value_set_string_take(window_info, kScreenKey, + make_monitor_value(monitor_with_window)); + + gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(window)); + fl_value_set_string_take(window_info, kScaleFactorKey, + fl_value_new_float(scale_factor)); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(window_info)); +} + +// Sets the window position and dimensions. +static FlMethodResponse* set_window_frame(FlWindowSizePlugin* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || + fl_value_get_length(args) != 4) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Expected 4-element list", nullptr)); + } + double x = fl_value_get_float(fl_value_get_list_value(args, 0)); + double y = fl_value_get_float(fl_value_get_list_value(args, 1)); + double width = fl_value_get_float(fl_value_get_list_value(args, 2)); + double height = fl_value_get_float(fl_value_get_list_value(args, 3)); + + GtkWindow* window = get_window(self); + if (window == nullptr) { + return FL_METHOD_RESPONSE( + fl_method_error_response_new(kNoScreenError, nullptr, nullptr)); + } + + gtk_window_move(window, static_cast(x), static_cast(y)); + gtk_window_resize(window, static_cast(width), + static_cast(height)); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Send updated window geometry to GTK. +static void update_window_geometry(FlWindowSizePlugin* self) { + gtk_window_set_geometry_hints( + get_window(self), nullptr, &self->window_geometry, + static_cast(GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE)); +} + +// Sets the window minimum size. +static FlMethodResponse* set_window_minimum_size(FlWindowSizePlugin* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || + fl_value_get_length(args) != 2) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Expected 2-element list", nullptr)); + } + double width = fl_value_get_float(fl_value_get_list_value(args, 0)); + double height = fl_value_get_float(fl_value_get_list_value(args, 1)); + + if (get_window(self) == nullptr) { + return FL_METHOD_RESPONSE( + fl_method_error_response_new(kNoScreenError, nullptr, nullptr)); + } + + if (width >= 0 && height >= 0) { + self->window_geometry.min_width = static_cast(width); + self->window_geometry.min_height = static_cast(height); + } + + update_window_geometry(self); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Sets the window maximum size. +static FlMethodResponse* set_window_maximum_size(FlWindowSizePlugin* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || + fl_value_get_length(args) != 2) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Expected 2-element list", nullptr)); + } + double width = fl_value_get_float(fl_value_get_list_value(args, 0)); + double height = fl_value_get_float(fl_value_get_list_value(args, 1)); + + if (get_window(self) == nullptr) { + return FL_METHOD_RESPONSE( + fl_method_error_response_new(kNoScreenError, nullptr, nullptr)); + } + + self->window_geometry.max_width = static_cast(width); + self->window_geometry.max_height = static_cast(height); + + // Flutter uses -1 as unconstrained, GTK doesn't have an unconstrained value. + if (self->window_geometry.max_width < 0) { + self->window_geometry.max_width = G_MAXINT; + } + if (self->window_geometry.max_height < 0) { + self->window_geometry.max_height = G_MAXINT; + } + + update_window_geometry(self); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Sets the window title. +static FlMethodResponse* set_window_title(FlWindowSizePlugin* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Expected string", nullptr)); + } + + GtkWindow* window = get_window(self); + if (window == nullptr) { + return FL_METHOD_RESPONSE( + fl_method_error_response_new(kNoScreenError, nullptr, nullptr)); + } + gtk_window_set_title(window, fl_value_get_string(args)); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Sets the window visibility. +static FlMethodResponse* set_window_visible(FlWindowSizePlugin* self, + FlValue* args) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_BOOL) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, "Expected bool", nullptr)); + } + + GtkWindow* window = get_window(self); + if (window == nullptr) { + return FL_METHOD_RESPONSE( + fl_method_error_response_new(kNoScreenError, nullptr, nullptr)); + } + if (fl_value_get_bool(args)) { + gtk_widget_show(GTK_WIDGET(window)); + } else { + gtk_widget_hide(GTK_WIDGET(window)); + } + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Gets the window minimum size. +static FlMethodResponse* get_window_minimum_size(FlWindowSizePlugin* self) { + g_autoptr(FlValue) size = fl_value_new_list(); + + gint min_width = self->window_geometry.min_width; + gint min_height = self->window_geometry.min_height; + + // GTK uses -1 for the requisition size (the size GTK has calculated). + // Report this as zero (smallest possible) so this doesn't look like Size(-1, -1). + if (min_width < 0) { + min_width = 0; + } + if (min_height < 0) { + min_height = 0; + } + + fl_value_append_take(size, fl_value_new_float(min_width)); + fl_value_append_take(size, fl_value_new_float(min_height)); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(size)); +} + +// Gets the window maximum size. +static FlMethodResponse* get_window_maximum_size(FlWindowSizePlugin* self) { + g_autoptr(FlValue) size = fl_value_new_list(); + + gint max_width = self->window_geometry.max_width; + gint max_height = self->window_geometry.max_height; + + // Flutter uses -1 as unconstrained, GTK doesn't have an unconstrained value. + if (max_width == G_MAXINT) { + max_width = -1; + } + if (max_height == G_MAXINT) { + max_height = -1; + } + + fl_value_append_take(size, fl_value_new_float(max_width)); + fl_value_append_take(size, fl_value_new_float(max_height)); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(size)); +} + +// Called when a method call is received from Flutter. +static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data) { + FlWindowSizePlugin* self = FL_WINDOW_SIZE_PLUGIN(user_data); + + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + g_autoptr(FlMethodResponse) response = nullptr; + if (strcmp(method, kGetScreenListMethod) == 0) { + response = get_screen_list(self); + } else if (strcmp(method, kGetWindowInfoMethod) == 0) { + response = get_window_info(self); + } else if (strcmp(method, kSetWindowFrameMethod) == 0) { + response = set_window_frame(self, args); + } else if (strcmp(method, kSetWindowMinimumSizeMethod) == 0) { + response = set_window_minimum_size(self, args); + } else if (strcmp(method, kSetWindowMaximumSizeMethod) == 0) { + response = set_window_maximum_size(self, args); + } else if (strcmp(method, kSetWindowTitleMethod) == 0) { + response = set_window_title(self, args); + } else if (strcmp(method, ksetWindowVisibilityMethod) == 0) { + response = set_window_visible(self, args); + } else if (strcmp(method, kGetWindowMinimumSizeMethod) == 0) { + response = get_window_minimum_size(self); + } else if (strcmp(method, kGetWindowMaximumSizeMethod) == 0) { + response = get_window_maximum_size(self); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + g_autoptr(GError) error = nullptr; + if (!fl_method_call_respond(method_call, response, &error)) + g_warning("Failed to send method call response: %s", error->message); +} + +static void fl_window_size_plugin_dispose(GObject* object) { + FlWindowSizePlugin* self = FL_WINDOW_SIZE_PLUGIN(object); + + g_clear_object(&self->registrar); + g_clear_object(&self->channel); + + G_OBJECT_CLASS(fl_window_size_plugin_parent_class)->dispose(object); +} + +static void fl_window_size_plugin_class_init(FlWindowSizePluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_window_size_plugin_dispose; +} + +static void fl_window_size_plugin_init(FlWindowSizePlugin* self) { + self->window_geometry.min_width = -1; + self->window_geometry.min_height = -1; + self->window_geometry.max_width = G_MAXINT; + self->window_geometry.max_height = G_MAXINT; +} + +FlWindowSizePlugin* fl_window_size_plugin_new(FlPluginRegistrar* registrar) { + FlWindowSizePlugin* self = FL_WINDOW_SIZE_PLUGIN( + g_object_new(fl_window_size_plugin_get_type(), nullptr)); + + self->registrar = FL_PLUGIN_REGISTRAR(g_object_ref(registrar)); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + self->channel = + fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), + kChannelName, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(self->channel, method_call_cb, + g_object_ref(self), g_object_unref); + + return self; +} + +void window_size_plugin_register_with_registrar(FlPluginRegistrar* registrar) { + FlWindowSizePlugin* plugin = fl_window_size_plugin_new(registrar); + g_object_unref(plugin); +} diff --git a/app/3rd_party/window_size/pubspec.yaml b/app/3rd_party/window_size/pubspec.yaml new file mode 100644 index 0000000..e6b24d0 --- /dev/null +++ b/app/3rd_party/window_size/pubspec.yaml @@ -0,0 +1,20 @@ +name: window_size +description: Allows resizing and repositioning the window containing Flutter. +version: 0.1.0 + +# Do not publish this plugin. See: +# https://github.com/google/flutter-desktop-embedding/blob/master/plugins/README.md#using-plugins +publish_to: none + +flutter: + plugin: + platforms: + linux: + pluginClass: WindowSizePlugin + +environment: + sdk: '>=2.12.0-0 <3.0.0' + +dependencies: + flutter: + sdk: flutter diff --git a/app/3rd_party/xdg_directories/.gitignore b/app/3rd_party/xdg_directories/.gitignore new file mode 100644 index 0000000..bb431f0 --- /dev/null +++ b/app/3rd_party/xdg_directories/.gitignore @@ -0,0 +1,75 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/app/3rd_party/xdg_directories/.metadata b/app/3rd_party/xdg_directories/.metadata new file mode 100644 index 0000000..990be38 --- /dev/null +++ b/app/3rd_party/xdg_directories/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: d8c0deb1b6c116be79ceeca005f893be34ab5df2 + channel: master + +project_type: package diff --git a/app/3rd_party/xdg_directories/CHANGELOG.md b/app/3rd_party/xdg_directories/CHANGELOG.md new file mode 100644 index 0000000..1a525f0 --- /dev/null +++ b/app/3rd_party/xdg_directories/CHANGELOG.md @@ -0,0 +1,15 @@ +## 0.2.0 + +* Migrated to null safety. + +## 0.1.2 + +* Broaden dependencies to allow nullsafety version of process, meta, and path to be OK. + +## 0.1.1 + +* Remove flutter, flutter_test from pubspec dependencies. + +## 0.1.0 + +* Initial release includes all the features described in the README.md diff --git a/app/3rd_party/xdg_directories/LICENSE b/app/3rd_party/xdg_directories/LICENSE new file mode 100644 index 0000000..4be0666 --- /dev/null +++ b/app/3rd_party/xdg_directories/LICENSE @@ -0,0 +1,27 @@ +Copyright 2020 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/app/3rd_party/xdg_directories/README.md b/app/3rd_party/xdg_directories/README.md new file mode 100644 index 0000000..d7fa16a --- /dev/null +++ b/app/3rd_party/xdg_directories/README.md @@ -0,0 +1,50 @@ +# `xdg_directories` + +A Dart package for reading XDG directory configuration information on Linux. + +## Getting Started + +On Linux, `xdg` is a system developed by [freedesktop.org](freedesktop.org), a +project to work on interoperability and shared base technology for free software +desktop environments for Linux. + +This Dart package can be used to determine the directory configuration +information defined by `xdg`, such as where the Documents or Desktop directories +are. These are called "user directories" and are defined in configuration file +in the user's home directory. + +See [this wiki](https://wiki.archlinux.org/index.php/XDG_Base_Directory) for +more details of the XDG Base Directory implementation. + +To use this package, the basic XDG values for the following are available via a Dart API: + + - `dataHome` - The single base directory relative to which user-specific data + files should be written. (Corresponds to `$XDG_DATA_HOME`). + + - `configHome` - The a single base directory relative to which user-specific + configuration files should be written. (Corresponds to `$XDG_CONFIG_HOME`). + + - `dataDirs` - The list of preference-ordered base directories relative to + which data files should be searched. (Corresponds to `$XDG_DATA_DIRS`). + + - `configDirs` - The list of preference-ordered base directories relative to + which configuration files should be searched. (Corresponds to + `$XDG_CONFIG_DIRS`). + + - `cacheHome` - The base directory relative to which user-specific + non-essential (cached) data should be written. (Corresponds to + `$XDG_CACHE_HOME`). + + - `runtimeDir` - The base directory relative to which user-specific runtime + files and other file objects should be placed. (Corresponds to + `$XDG_RUNTIME_DIR`). + + - `getUserDirectoryNames()` - Returns a set of the names of user directories + defined in the `xdg` configuration files. + + - `getUserDirectory(String dirName)` - Gets the value of the user dir with the + given name. Requesting a user dir that doesn't exist returns `null`. The + `dirName` argument is case-insensitive. See [this + wiki](https://wiki.archlinux.org/index.php/XDG_user_directories) for more + details and what values of `dirName` might be available. + diff --git a/app/3rd_party/xdg_directories/lib/xdg_directories.dart b/app/3rd_party/xdg_directories/lib/xdg_directories.dart new file mode 100644 index 0000000..d89aa66 --- /dev/null +++ b/app/3rd_party/xdg_directories/lib/xdg_directories.dart @@ -0,0 +1,68 @@ +// Copyright 2020 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +library xdg_directories; + +import 'package:universal_io/io.dart'; + +import 'xdg_directories_web.dart' if (dart.library.io) 'xdg_directories_linux.dart'; + +typedef EnvironmentAccessor = String? Function(String envVar); + +abstract class XDGDirectories { + factory XDGDirectories() => getXDGDirectories(); + + /// The base directory relative to which user-specific + /// non-essential (cached) data should be written. (Corresponds to + /// `$XDG_CACHE_HOME`). + /// + /// Throws [StateError] if the HOME environment variable is not set. + Directory get cacheHome; + + /// The list of preference-ordered base directories relative to + /// which configuration files should be searched. (Corresponds to + /// `$XDG_CONFIG_DIRS`). + /// + /// Throws [StateError] if the HOME environment variable is not set. + List get configDirs; + + /// The a single base directory relative to which user-specific + /// configuration files should be written. (Corresponds to `$XDG_CONFIG_HOME`). + /// + /// Throws [StateError] if the HOME environment variable is not set. + Directory get configHome; + + /// The list of preference-ordered base directories relative to + /// which data files should be searched. (Corresponds to `$XDG_DATA_DIRS`). + /// + /// Throws [StateError] if the HOME environment variable is not set. + List get dataDirs; + + /// The base directory relative to which user-specific data files should be + /// written. (Corresponds to `$XDG_DATA_HOME`). + /// + /// Throws [StateError] if the HOME environment variable is not set. + Directory get dataHome; + + /// The base directory relative to which user-specific runtime + /// files and other file objects should be placed. (Corresponds to + /// `$XDG_RUNTIME_DIR`). + /// + /// Throws [StateError] if the HOME environment variable is not set. + Directory? get runtimeDir; + + /// Gets the xdg user directory named by `dirName`. + /// + /// Use [getUserDirectoryNames] to find out the list of available names. + Directory? getUserDirectory(String dirName); + + /// Gets the set of user directory names that xdg knows about. + /// + /// These are not paths, they are names of xdg values. Call [getUserDirectory] + /// to get the associated directory. + /// + /// These are the names of the variables in "[configHome]/user-dirs.dirs", with + /// the `XDG_` prefix removed and the `_DIR` suffix removed. + Set getUserDirectoryNames(); +} diff --git a/app/3rd_party/xdg_directories/lib/xdg_directories_linux.dart b/app/3rd_party/xdg_directories/lib/xdg_directories_linux.dart new file mode 100644 index 0000000..cc5579f --- /dev/null +++ b/app/3rd_party/xdg_directories/lib/xdg_directories_linux.dart @@ -0,0 +1,154 @@ +// Copyright 2020 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +library xdg_directories; + +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:process/process.dart'; + +import 'xdg_directories.dart'; + +XDGDirectories getXDGDirectories() => XDGDirectoriesLinux(); + +class XDGDirectoriesLinux implements XDGDirectories { + EnvironmentAccessor _getenv = (String value) => Platform.environment[value]; + + ProcessManager _processManager = const LocalProcessManager(); + + List _directoryListFromEnvironment(String envVar, List fallback) { + ArgumentError.checkNotNull(envVar); + ArgumentError.checkNotNull(fallback); + final String? value = _getenv(envVar); + if (value == null || value.isEmpty) { + return fallback; + } + return value.split(':').where((String value) { + return value.isNotEmpty; + }).map((String entry) { + return Directory(entry); + }).toList(); + } + + Directory? _directoryFromEnvironment(String envVar) { + ArgumentError.checkNotNull(envVar); + final String? value = _getenv(envVar); + if (value == null || value.isEmpty) { + return null; + } + return Directory(value); + } + + Directory _directoryFromEnvironmentWithFallback(String envVar, String fallback) { + ArgumentError.checkNotNull(envVar); + final String? value = _getenv(envVar); + if (value == null || value.isEmpty) { + return _getDirectory(fallback); + } + return Directory(value); + } + +// Creates a Directory from a fallback path. + Directory _getDirectory(String subdir) { + ArgumentError.checkNotNull(subdir); + assert(subdir.isNotEmpty); + final String? homeDir = _getenv('HOME'); + if (homeDir == null || homeDir.isEmpty) { + throw StateError('The "HOME" environment variable is not set. This package (and POSIX) ' + 'requires that HOME be set.'); + } + return Directory(path.joinAll([homeDir, subdir])); + } + + /// The base directory relative to which user-specific + /// non-essential (cached) data should be written. (Corresponds to + /// `$XDG_CACHE_HOME`). + /// + /// Throws [StateError] if the HOME environment variable is not set. + Directory get cacheHome => _directoryFromEnvironmentWithFallback('XDG_CACHE_HOME', '.cache'); + + /// The list of preference-ordered base directories relative to + /// which configuration files should be searched. (Corresponds to + /// `$XDG_CONFIG_DIRS`). + /// + /// Throws [StateError] if the HOME environment variable is not set. + List get configDirs { + return _directoryListFromEnvironment( + 'XDG_CONFIG_DIRS', + [Directory('/etc/xdg')], + ); + } + + /// The a single base directory relative to which user-specific + /// configuration files should be written. (Corresponds to `$XDG_CONFIG_HOME`). + /// + /// Throws [StateError] if the HOME environment variable is not set. + Directory get configHome => _directoryFromEnvironmentWithFallback('XDG_CONFIG_HOME', '.config'); + + /// The list of preference-ordered base directories relative to + /// which data files should be searched. (Corresponds to `$XDG_DATA_DIRS`). + /// + /// Throws [StateError] if the HOME environment variable is not set. + List get dataDirs { + return _directoryListFromEnvironment( + 'XDG_DATA_DIRS', + [Directory('/usr/local/share'), Directory('/usr/share')], + ); + } + + /// The base directory relative to which user-specific data files should be + /// written. (Corresponds to `$XDG_DATA_HOME`). + /// + /// Throws [StateError] if the HOME environment variable is not set. + Directory get dataHome => _directoryFromEnvironmentWithFallback('XDG_DATA_HOME', '.local/share'); + + /// The base directory relative to which user-specific runtime + /// files and other file objects should be placed. (Corresponds to + /// `$XDG_RUNTIME_DIR`). + /// + /// Throws [StateError] if the HOME environment variable is not set. + Directory? get runtimeDir => _directoryFromEnvironment('XDG_RUNTIME_DIR'); + + /// Gets the xdg user directory named by `dirName`. + /// + /// Use [getUserDirectoryNames] to find out the list of available names. + Directory? getUserDirectory(String dirName) { + final ProcessResult result = _processManager.runSync( + ['xdg-user-dir', dirName], + includeParentEnvironment: true, + stdoutEncoding: utf8, + ); + final String path = result.stdout.split('\n')[0]; + return Directory(path); + } + + /// Gets the set of user directory names that xdg knows about. + /// + /// These are not paths, they are names of xdg values. Call [getUserDirectory] + /// to get the associated directory. + /// + /// These are the names of the variables in "[configHome]/user-dirs.dirs", with + /// the `XDG_` prefix removed and the `_DIR` suffix removed. + Set getUserDirectoryNames() { + final File configFile = File(path.join(configHome.path, 'user-dirs.dirs')); + List contents; + try { + contents = configFile.readAsLinesSync(); + } on FileSystemException { + return const {}; + } + final Set result = {}; + final RegExp dirRegExp = RegExp(r'^\s*XDG_(?[^=]*)_DIR\s*=\s*(?.*)\s*$'); + for (String line in contents) { + final RegExpMatch? match = dirRegExp.firstMatch(line); + if (match == null) { + continue; + } + result.add(match.namedGroup('dirname')!); + } + return result; + } +} diff --git a/app/3rd_party/xdg_directories/lib/xdg_directories_web.dart b/app/3rd_party/xdg_directories/lib/xdg_directories_web.dart new file mode 100644 index 0000000..8ac360f --- /dev/null +++ b/app/3rd_party/xdg_directories/lib/xdg_directories_web.dart @@ -0,0 +1,41 @@ +// Copyright 2020 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +library xdg_directories; + +import 'package:universal_io/io.dart'; + +import 'xdg_directories.dart'; + +XDGDirectories getXDGDirectories() => XDGDirectoriesWeb(); + +class XDGDirectoriesWeb implements XDGDirectories { + @override + Directory get cacheHome => throw UnimplementedError(); + + @override + List get configDirs => throw UnimplementedError(); + + @override + Directory get configHome => throw UnimplementedError(); + + @override + List get dataDirs => throw UnimplementedError(); + + @override + Directory get dataHome => throw UnimplementedError(); + + @override + Directory? getUserDirectory(String dirName) { + throw UnimplementedError(); + } + + @override + Set getUserDirectoryNames() { + throw UnimplementedError(); + } + + @override + Directory? get runtimeDir => throw UnimplementedError(); +} diff --git a/app/3rd_party/xdg_directories/pubspec.yaml b/app/3rd_party/xdg_directories/pubspec.yaml new file mode 100644 index 0000000..26e4d37 --- /dev/null +++ b/app/3rd_party/xdg_directories/pubspec.yaml @@ -0,0 +1,16 @@ +name: xdg_directories +description: A Dart package for reading XDG directory configuration information on Linux. +version: 0.2.0 +homepage: https://github.com/flutter/packages/tree/master/packages/xdg_directories + +environment: + sdk: ">=2.12.0-0 <3.0.0" + +dependencies: + meta: ^1.3.0 + path: ^1.8.0 + process: ^4.0.0 + universal_io: ^2.0.4 + +dev_dependencies: + test: ^1.16.0 diff --git a/app/lib/main.dart b/app/lib/main.dart index b9bfc38..7f3479a 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -1,25 +1,48 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:gsettings/gsettings.dart'; +import 'package:window_manager/window_manager.dart'; +import 'package:xdg_directories/xdg_directories.dart'; -void main() { +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + if (!kIsWeb) { + var xdgDir = XDGDirectories(); + print(await xdgDir.configHome); + print(await xdgDir.cacheHome); + print(await xdgDir.dataHome); + print('------'); + print(await xdgDir.configDirs.join('\n')); + print('------'); + print(await xdgDir.dataDirs.join('\n')); + print('------'); + print(await xdgDir.runtimeDir); + print('------'); + var windowManager = WindowManager.instance; + windowManager.setTitle('Gesture Manager For DDE'); + windowManager.setMinimumSize(const Size(800, 600)); + var xsettings = GSettings('com.deepin.xsettings'); + // xsettings.get('scale-factor').then((value) { + // print(value.toString()); + // }); + xsettings.get('theme-name').then((value) { + print(value.toString()); + }); + xsettings.keysChanged.listen((event) { + xsettings.get('theme-name').then((value) { + print(value.toString()); + }); + }); + } runApp(MyApp()); } class MyApp extends StatelessWidget { - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), @@ -30,15 +53,6 @@ class MyApp extends StatelessWidget { class MyHomePage extends StatefulWidget { MyHomePage({Key? key, required this.title}) : super(key: key); - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - final String title; @override @@ -50,47 +64,18 @@ class _MyHomePageState extends State { void _incrementCounter() { setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. _counter++; }); } @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). mainAxisAlignment: MainAxisAlignment.center, children: [ Text( @@ -107,7 +92,7 @@ class _MyHomePageState extends State { onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + ), ); } } diff --git a/app/linux/flutter/generated_plugin_registrant.cc b/app/linux/flutter/generated_plugin_registrant.cc index d38195a..11f1069 100644 --- a/app/linux/flutter/generated_plugin_registrant.cc +++ b/app/linux/flutter/generated_plugin_registrant.cc @@ -2,8 +2,18 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" +#include +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) window_manager_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); + window_manager_plugin_register_with_registrar(window_manager_registrar); + g_autoptr(FlPluginRegistrar) window_size_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin"); + window_size_plugin_register_with_registrar(window_size_registrar); } diff --git a/app/linux/flutter/generated_plugin_registrant.h b/app/linux/flutter/generated_plugin_registrant.h index 9bf7478..e0f0a47 100644 --- a/app/linux/flutter/generated_plugin_registrant.h +++ b/app/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/app/linux/flutter/generated_plugins.cmake b/app/linux/flutter/generated_plugins.cmake index 51436ae..185ff5b 100644 --- a/app/linux/flutter/generated_plugins.cmake +++ b/app/linux/flutter/generated_plugins.cmake @@ -3,6 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST + window_manager + window_size ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/app/linux/my_application.cc b/app/linux/my_application.cc index 0bb2b85..cfb1b12 100644 --- a/app/linux/my_application.cc +++ b/app/linux/my_application.cc @@ -48,7 +48,7 @@ static void my_application_activate(GApplication* application) { gtk_window_set_title(window, "DDE Gesture Manager"); } - gtk_window_set_default_size(window, 1280, 720); + gtk_window_set_default_size(window, 800, 600); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 5e1fa11..ce766a3 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -28,11 +28,26 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + window_manager: ^0.0.3 + localstorage: ^4.0.0+1 + shared_preferences: ^2.0.7 + window_size: + path: 3rd_party/window_size + xdg_directories: + path: 3rd_party/xdg_directories + gsettings: + path: 3rd_party/gsettings dev_dependencies: flutter_test: sdk: flutter +dependency_overrides: + xdg_directories: + path: 3rd_party/xdg_directories + path_provider_linux: + path: 3rd_party/path_provider_linux + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec