feat: add some plugins.

pull/1/head
DebuggerX 4 years ago
parent 01327ddfe0
commit 9166c80979

@ -0,0 +1,6 @@
# Changelog
## 0.2.0
* Initial release, replacing the existing ffi bindings.

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

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

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

@ -0,0 +1 @@
include: package:pedantic/analysis_options.yaml

@ -0,0 +1 @@
export 'src/gsettings.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<String> 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<DConfNotifyEvent> get notify => _notifyController.stream;
final _notifyController = StreamController<DConfNotifyEvent>();
/// 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>> list(String dir) async {
var sources = await _loadSources();
var keys = <String>{};
for (var source in sources) {
keys.addAll(await source.database.list(dir: dir));
}
return keys.toList();
}
/// Gets the value of a given [key].
Future<DBusValue?> 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<String> write(Map<String, DBusValue?> 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<void> close() async {
if (_closeSystemBus) {
await _systemBus.close();
}
if (_closeSessionBus) {
await _sessionBus.close();
}
}
// Load the DConf sources in use.
Future<List<DConfEngineSource>> _loadSources() async {
// Generate list of files to look for the profile in.
var paths = <String>[];
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<String> _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<List<DConfEngineSource>?> _loadProfileFile(String path) async {
var file = File(path);
List<String> lines;
try {
lines = await file.readAsLines();
} on FileSystemException {
return null;
}
var sources = <DConfEngineSource>[];
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<String> parts) {
var path = parts.join('/');
while (true) {
var updatedPath = path.replaceAll('//', '/');
if (updatedPath == path) {
return path;
}
path = updatedPath;
}
}

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

@ -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();
}

@ -0,0 +1,4 @@
/// Gets the user ID of the current user.
int getuid() {
throw 'Unable to determine UID on this system';
}

@ -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<List<String>> listGSettingsSchemas() async {
var schemaNames = <String>{};
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<List<String>> get keysChanged => _keysChangedController.stream;
final _keysChangedController = StreamController<List<String>>();
// 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<String>> 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<DBusValue> 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<DBusValue> 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<bool> 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<void> 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<void> 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<void> setAll(Map<String, DBusValue?> 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<void> close() async {
await _dconfClient.close();
}
// Get the database entry for this schema.
Future<GVariantDatabaseTable> _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<int>? words;
DBusValue? minimumValue;
DBusValue? maximumValue;
Map<String, DBusValue>? 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<Directory> _getSchemaDirs() {
var schemaDirs = <Directory>[];
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<int>? words;
final DBusValue? minimumValue;
final DBusValue? maximumValue;
final Map<String, DBusValue>? 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)';
}

@ -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<DBusValue> values, Endian endian) {
var isVariable = false;
var endOffsets = <int>[];
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<DBusValue> values, Endian endian) {
var endOffsets = <int>[];
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<DBusValue, DBusValue> values, Endian endian) {
var endOffsets = <int>[];
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<String> childTypes, ByteData data,
{required Endian endian}) {
var children = <DBusValue>[];
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<String> childTypes, ByteData data,
{required Endian endian}) {
var offsetSize = _getOffsetSize(data.lengthInBytes);
var dataEnd = data.lengthInBytes;
var children = <DBusValue>[];
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 = <DBusValue, DBusValue>{};
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 = <DBusValue>[];
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 = <DBusValue>[];
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');
}
}
}

@ -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>> list({String? dir, String? type}) async {
var root = await _loadRootTable();
return root.list(dir: dir, type: type);
}
Future<DBusValue?> lookup(String key) async {
var root = await _loadRootTable();
return root.lookup(key);
}
Future<GVariantDatabaseTable?> lookupTable(String key) async {
var root = await _loadRootTable();
return root.lookupTable(key);
}
Future<GVariantDatabaseTable> _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<String> list({String? dir, String? type}) {
var dirHash = dir != null ? _hashKey(dir) : 0;
var children = <String>[];
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;
}
}

@ -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 = <DBusValue>[];
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 = <DBusValue>[];
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 = <DBusValue, DBusValue>{};
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;
}
}

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

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

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

@ -0,0 +1,66 @@
# Below is a list of people and organizations that have contributed
# to the Flutter project. Names should be added to the list like so:
#
# Name/Organization <email address>
Google Inc.
The Chromium Authors
German Saprykin <saprykin.h@gmail.com>
Benjamin Sauer <sauer.benjamin@gmail.com>
larsenthomasj@gmail.com
Ali Bitek <alibitek@protonmail.ch>
Pol Batlló <pol.batllo@gmail.com>
Anatoly Pulyaevskiy
Hayden Flinner <haydenflinner@gmail.com>
Stefano Rodriguez <hlsroddy@gmail.com>
Salvatore Giordano <salvatoregiordanoo@gmail.com>
Brian Armstrong <brian@flutter.institute>
Paul DeMarco <paulmdemarco@gmail.com>
Fabricio Nogueira <feufeu@gmail.com>
Simon Lightfoot <simon@devangels.london>
Ashton Thomas <ashton@acrinta.com>
Thomas Danner <thmsdnnr@gmail.com>
Diego Velásquez <diego.velasquez.lopez@gmail.com>
Hajime Nakamura <nkmrhj@gmail.com>
Tuyển Vũ Xuân <netsoft1985@gmail.com>
Miguel Ruivo <miguel@miguelruivo.com>
Sarthak Verma <sarthak@artiosys.com>
Mike Diarmid <mike@invertase.io>
Invertase <oss@invertase.io>
Elliot Hesp <elliot@invertase.io>
Vince Varga <vince.varga@smaho.com>
Aawaz Gyawali <awazgyawali@gmail.com>
EUI Limited <ian.evans3@admiralgroup.co.uk>
Katarina Sheremet <katarina@sheremet.ch>
Thomas Stockx <thomas@stockxit.com>
Sarbagya Dhaubanjar <sarbagyastha@gmail.com>
Ozkan Eksi <ozeksi@gmail.com>
Rishab Nayak <rishab@bu.edu>
ko2ic <ko2ic.dev@gmail.com>
Jonathan Younger <jonathan@daikini.com>
Jose Sanchez <josesm82@gmail.com>
Debkanchan Samadder <debu.samadder@gmail.com>
Audrius Karosevicius <audrius.karosevicius@gmail.com>
Lukasz Piliszczuk <lukasz@intheloup.io>
SoundReply Solutions GmbH <ch@soundreply.com>
Rafal Wachol <rwachol@gmail.com>
Pau Picas <pau.picas@gmail.com>
Christian Weder <chrstian.weder@yapeal.ch>
Alexandru Tuca <salexandru.tuca@outlook.com>
Christian Weder <chrstian.weder@yapeal.ch>
Rhodes Davis Jr. <rody.davis.jr@gmail.com>
Luigi Agosti <luigi@tengio.com>
Quentin Le Guennec <quentin@tengio.com>
Koushik Ravikumar <koushik@tengio.com>
Nissim Dsilva <nissim@tengio.com>
Giancarlo Rocha <giancarloiff@gmail.com>
Ryo Miyake <ryo@miyake.id>
Théo Champion <contact.theochampion@gmail.com>
Kazuki Yamaguchi <y.kazuki0614n@gmail.com>
Eitan Schwartz <eshvartz@gmail.com>
Chris Rutkowski <chrisrutkowski89@gmail.com>
Juan Alvarez <juan.alvarez@resideo.com>
Aleksandr Yurkovskiy <sanekyy@gmail.com>
Anton Borries <mail@antonborri.es>
Alex Li <google@alexv525.com>
Rahul Raj <64.rahulraj@gmail.com>

@ -0,0 +1,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

@ -0,0 +1,25 @@
Copyright 2013 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,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

@ -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<String?> getTemporaryPath() {
return Future<String?>.value('/tmp');
}
@override
Future<String?> 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<String?> getApplicationDocumentsPath() {
return Future<String?>.value(xdg.getUserDirectory('DOCUMENTS')?.path);
}
@override
Future<String?> getDownloadsPath() {
return Future<String?>.value(xdg.getUserDirectory('DOWNLOAD')?.path);
}
}

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

@ -0,0 +1,5 @@
.dart_tool
.packages
.flutter-plugins
.flutter-plugins-dependencies
pubspec.lock

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

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

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

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

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

@ -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<List<Screen>> getScreenList() async {
final screenList = <Screen>[];
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<PlatformWindow> 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<double>()),
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<Size> getWindowMinSize() async {
final response =
await _platformChannel.invokeMethod(_getWindowMinimumSizeMethod);
return _sizeFromWHList(List<double>.from(response.cast<double>()));
}
// 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<Size> getWindowMaxSize() async {
final response =
await _platformChannel.invokeMethod(_getWindowMaximumSizeMethod);
return _sizeFromWHList(
List<double>.from(
response.cast<double>().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<double> 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<double> 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<dynamic, dynamic> map) {
return Screen(
_rectFromLTWHList(map[_frameKey].cast<double>()),
_rectFromLTWHList(map[_visibleFrameKey].cast<double>()),
map[_scaleFactorKey]);
}
}

@ -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<List<Screen>> 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<Screen?> getCurrentScreen() async {
final windowInfo = await WindowSizeChannel.instance.getWindowInfo();
return windowInfo.screen;
}
/// Returns information about the window containing this Flutter instance.
Future<PlatformWindow> 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<Size> getWindowMinSize() async {
return WindowSizeChannel.instance.getWindowMinSize();
}
/// Gets the maximum [Size] of the window containing this Flutter instance.
Future<Size> getWindowMaxSize() async {
return WindowSizeChannel.instance.getWindowMaxSize();
}

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

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

@ -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 <flutter_linux/flutter_linux.h>
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_

@ -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 <flutter_linux/flutter_linux.h>
#include <gtk/gtk.h>
// 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<gint>(x), static_cast<gint>(y));
gtk_window_resize(window, static_cast<gint>(width),
static_cast<gint>(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<GdkWindowHints>(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<gint>(width);
self->window_geometry.min_height = static_cast<gint>(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<gint>(width);
self->window_geometry.max_height = static_cast<gint>(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);
}

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

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

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

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

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

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

@ -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<Directory> 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<Directory> 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<String> getUserDirectoryNames();
}

@ -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<Directory> _directoryListFromEnvironment(String envVar, List<Directory> 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<Directory>((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(<String>[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<Directory> get configDirs {
return _directoryListFromEnvironment(
'XDG_CONFIG_DIRS',
<Directory>[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<Directory> get dataDirs {
return _directoryListFromEnvironment(
'XDG_DATA_DIRS',
<Directory>[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(
<String>['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<String> getUserDirectoryNames() {
final File configFile = File(path.join(configHome.path, 'user-dirs.dirs'));
List<String> contents;
try {
contents = configFile.readAsLinesSync();
} on FileSystemException {
return const <String>{};
}
final Set<String> result = <String>{};
final RegExp dirRegExp = RegExp(r'^\s*XDG_(?<dirname>[^=]*)_DIR\s*=\s*(?<dir>.*)\s*$');
for (String line in contents) {
final RegExpMatch? match = dirRegExp.firstMatch(line);
if (match == null) {
continue;
}
result.add(match.namedGroup('dirname')!);
}
return result;
}
}

@ -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<Directory> get configDirs => throw UnimplementedError();
@override
Directory get configHome => throw UnimplementedError();
@override
List<Directory> get dataDirs => throw UnimplementedError();
@override
Directory get dataHome => throw UnimplementedError();
@override
Directory? getUserDirectory(String dirName) {
throw UnimplementedError();
}
@override
Set<String> getUserDirectoryNames() {
throw UnimplementedError();
}
@override
Directory? get runtimeDir => throw UnimplementedError();
}

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

@ -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<void> 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<MyHomePage> {
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: <Widget>[
Text(
@ -107,7 +92,7 @@ class _MyHomePageState extends State<MyHomePage> {
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
),
);
}
}

@ -2,8 +2,18 @@
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <window_manager/window_manager_plugin.h>
#include <window_size/window_size_plugin.h>
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);
}

@ -2,6 +2,8 @@
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_

@ -3,6 +3,8 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
window_manager
window_size
)
set(PLUGIN_BUNDLED_LIBRARIES)

@ -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();

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

Loading…
Cancel
Save