diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..eb23941 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ + + +## Description + + + +## Related Issue + + + +## Type of Change + + + +- [ ] โœจ `feat` -- New feature (non-breaking change which adds functionality) +- [ ] ๐Ÿ› ๏ธ `fix` -- Bug fix (non-breaking change which fixes an issue) +- [ ] โŒ `!` -- Breaking change (fix or feature that would cause existing functionality to change) +- [ ] ๐Ÿงน `refactor` -- Code refactor +- [ ] โœ… `ci` -- Build configuration change +- [ ] ๐Ÿ“ `docs` -- Documentation +- [ ] ๐Ÿงช `test` -- Test +- [ ] ๐Ÿ—‘๏ธ `chore` -- Chore diff --git a/CHANGELOG.md b/CHANGELOG.md index c84593b..ba15e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,70 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [2.0.0] - 2024-11-02 + +This release introduces significant changes to the API with a streamlined +structure, new methods for specific registry data handling, and enhanced support +for monitoring registry key changes. + +### ๐Ÿ”„ Breaking Changes + +- **`AccessRights`**: + - `win32Value` field renamed to `value`. + +- **`Registry`**: + - Now an `abstract final` class. + - Removed `performanceData` static getter. + +- **`RegistryHive`**: + - Constructor is now private. + - `win32Value` field renamed to `value`. + +- **`RegistryKey`**: + - Now a `final` class. + +- **`RegistryValue`**: + - Now a `sealed` class. + - Removed `fromWin32` factory constructor. + - Removed `toWin32` getter. + - Removed `data` field. + +- **`RegistryValueType`**: + - Constructor is now private. + - `win32Value` field renamed to `value`. + - Removed `unknown` value. + - Removed `win32Type` getter. + +### โœจ New Features + +- **Registry Hive Construction**: + - Added `RegistryHive.fromWin32` factory constructor for creating hives based + on Win32 constants. + +- **Enhanced Data Retrieval**: + - Added type-specific methods to `RegistryKey`: `getBinaryValue`, + `getIntValue`, `getStringValue`, and `getStringArrayValue` for retrieving + data in specific formats. + - Deprecated `getValueAsInt` and `getValueAsString` methods in favor of the + new type-specific methods. + +- **Registry Change Notifications**: + - Introduced `RegistryKey.onChanged` stream for monitoring registry key + changes, with optional subkey tracking. + +- **Typed Registry Values**: + - Introduced specialized subclasses in `RegistryValue` for each registry value + type, enhancing clarity and simplifying data handling. + +- **Registry Value Type Construction**: + - Added `RegistryValueType.fromWin32` factory constructor to handle value type + creation based on Win32 constants. + +### ๐Ÿšจ Dart SDK Requirement + +- Bumped the minimum required Dart SDK version to `3.5.0`. + ## 1.1.5 - Fix issue where creating `REG_SZ` or `REG_EXPANDED_SZ` string registry values @@ -44,3 +111,5 @@ ## 1.0.0 - First stable release. + +[2.0.0]: https://github.com/halildurmus/win32_registry/compare/v1.1.5...v2.0.0 diff --git a/README.md b/README.md index eaf0afd..ef96993 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,17 @@ This package builds on top of the Dart [win32][win32_pub_dev_link] package, offering a high-level Dart wrapper that avoids the need for users to understand FFI or write directly to the Win32 API. +## Features + +- **Manage Registry Keys**: Create, open, delete, and rename registry keys. +- **Set and Get Values**: Store and retrieve strings, integers, binary data, and + string arrays. +- **Monitor Changes**: Listen for changes in registry keys. +- **Query Key Details**: Get information about subkeys and values within a + registry key. + +To learn more, see the [API Documentation][api_documentation_link]. + ## Usage A simple example that reads the Windows build number from the Windows Registry: @@ -22,10 +33,8 @@ void main() { const keyPath = r'Software\Microsoft\Windows NT\CurrentVersion'; final key = Registry.openPath(RegistryHive.localMachine, path: keyPath); - final buildNumber = key.getValueAsString('CurrentBuild'); - if (buildNumber != null) { - print('Windows build number: $buildNumber'); - } + final buildNumber = key.getStringValue('CurrentBuild'); + if (buildNumber != null) print('Windows build number: $buildNumber'); key.close(); } @@ -33,11 +42,18 @@ void main() { More examples can be found in the [example] subdirectory. +## Feature requests and bugs + +Please file feature requests and bugs at the +[issue tracker][issue_tracker_link]. + +[api_documentation_link]: https://pub.dev/documentation/win32_registry/latest/ [ci_badge]: https://github.com/halildurmus/win32_registry/actions/workflows/build.yml/badge.svg [ci_link]: https://github.com/halildurmus/win32_registry/actions/workflows/build.yml [codecov_badge_link]: https://codecov.io/gh/halildurmus/win32_registry/branch/main/graph/badge.svg?token=6ThVC4ejhx [codecov_link]: https://codecov.io/gh/halildurmus/win32_registry [example]: https://github.com/halildurmus/win32_registry/tree/main/example +[issue_tracker_link]: https://github.com/halildurmus/win32_registry/issues [language_badge]: https://img.shields.io/badge/language-Dart-blue.svg [language_link]: https://dart.dev [license_badge]: https://img.shields.io/github/license/halildurmus/win32_registry?color=blue diff --git a/analysis_options.yaml b/analysis_options.yaml index 572dd23..9cd10f0 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1,85 @@ include: package:lints/recommended.yaml + +linter: + rules: + - always_declare_return_types + - always_put_required_named_parameters_first + - avoid_bool_literals_in_conditional_expressions + - avoid_catching_errors + - avoid_dynamic_calls + - avoid_escaping_inner_quotes + - avoid_final_parameters + - avoid_multiple_declarations_per_line + - avoid_positional_boolean_parameters + - avoid_private_typedef_functions + - avoid_redundant_argument_values + - avoid_returning_this + - avoid_setters_without_getters + - avoid_slow_async_io + - avoid_type_to_string + - avoid_types_on_closure_parameters + - avoid_unused_constructor_parameters + - avoid_void_async + - cancel_subscriptions + - cascade_invocations + - cast_nullable_to_non_nullable + - close_sinks + - combinators_ordering + - comment_references + - deprecated_consistency + - deprecated_member_use_from_same_package + - directives_ordering + - discarded_futures + - flutter_style_todos + - join_return_with_assignment + - leading_newlines_in_multiline_strings + - literal_only_boolean_expressions + - matching_super_parameters + - missing_code_block_language_in_doc_comment + - missing_whitespace_between_adjacent_strings + - no_literal_bool_comparisons + - no_runtimeType_toString + - no_self_assignments + - noop_primitive_operations + - omit_local_variable_types + # - omit_obvious_property_types + - one_member_abstracts + - only_throw_errors + - parameter_assignments + - prefer_asserts_in_initializer_lists + - prefer_asserts_with_message + - prefer_const_constructors + - prefer_const_declarations + - prefer_expression_function_bodies + - prefer_final_in_for_each + - prefer_final_locals + - prefer_if_elements_to_conditional_expressions + - prefer_int_literals + - prefer_mixin + - prefer_null_aware_method_calls + - prefer_relative_imports + - prefer_single_quotes + - prefer_void_to_null + - sort_constructors_first + - sort_pub_dependencies + - sort_unnamed_constructors_first + # - specify_nonobvious_property_types + - throw_in_finally + - unawaited_futures + - unintended_html_in_doc_comment + - unnecessary_await_in_return + - unnecessary_breaks + - unnecessary_lambdas + - unnecessary_library_directive + - unnecessary_library_name + - unnecessary_parenthesis + - unnecessary_raw_strings + - unnecessary_statements + - unreachable_from_main + - use_enums + - use_if_null_to_convert_nulls_to_bools + - use_is_even_rather_than_modulo + - use_named_constants + - use_raw_strings + - use_string_buffers + - use_to_and_as_if_applicable diff --git a/example/create_values.dart b/example/create_values.dart index c6761dc..dae76ae 100644 --- a/example/create_values.dart +++ b/example/create_values.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:win32_registry/win32_registry.dart'; void main() { @@ -5,20 +7,39 @@ void main() { const subkeyName = 'DemoTestKey'; final subkey = hkcu.createKey(subkeyName); - const dword = RegistryValue('TestDWORD', RegistryValueType.int32, 0xFACEFEED); + const dword = RegistryValue.int32('TestDWORD', 0xFACEFEED); subkey.createValue(dword); - const qword = - RegistryValue('TestQWORD', RegistryValueType.int64, 0x0123456789ABCDEF); + const qword = RegistryValue.int64('TestQWORD', 0x0123456789ABCDEF); subkey.createValue(qword); - const string = RegistryValue( + const string = RegistryValue.string( 'TestString', - RegistryValueType.string, 'The human race has one really effective weapon, and that is laughter.', ); + subkey.createValue(string); + + const stringArray = RegistryValue.stringArray( + 'TestStringArray', + ['One', 'Two', 'Three'], + ); + subkey.createValue(stringArray); + + const unexpandedString = RegistryValue.unexpandedString( + 'TestUnexpandedString', + r'%SystemRoot%\System32', + ); + subkey.createValue(unexpandedString); + + final binary = RegistryValue.binary( + 'TestBinary', + Uint8List.fromList([0xFF, 0x33, 0x77, 0xAA]), + ); + subkey.createValue(binary); + + const none = RegistryValue.none('TestNone'); subkey - ..createValue(string) + ..createValue(none) ..close(); hkcu.close(); diff --git a/example/main.dart b/example/main.dart index 9dfb363..3dd9a16 100644 --- a/example/main.dart +++ b/example/main.dart @@ -4,10 +4,8 @@ void main() { const keyPath = r'Software\Microsoft\Windows NT\CurrentVersion'; final key = Registry.openPath(RegistryHive.localMachine, path: keyPath); - final buildNumber = key.getValueAsString('CurrentBuild'); - if (buildNumber != null) { - print('Windows build number: $buildNumber'); - } + final buildNumber = key.getStringValue('CurrentBuild'); + if (buildNumber != null) print('Windows build number: $buildNumber'); key.close(); } diff --git a/example/monitor_key.dart b/example/monitor_key.dart new file mode 100644 index 0000000..b2337c0 --- /dev/null +++ b/example/monitor_key.dart @@ -0,0 +1,33 @@ +import 'package:win32_registry/win32_registry.dart'; + +void main() async { + final hkcu = Registry.currentUser; + const subkeyName = 'DemoTestKey'; + final subkey = hkcu.createKey(subkeyName); + + const string = RegistryValue.string( + 'TestString', + 'The human race has one really effective weapon, and that is laughter.', + ); + subkey.createValue(string); + + // Subscribe to the onChanged stream to monitor changes to the subkey. + final subscription = subkey + .onChanged() + .listen((_) => print('Subkey changed.'), cancelOnError: true); + + print('Monitoring registry key changes for 30 seconds...'); + // Now, make a change to the subkey in RegEdit to trigger the onChanged event. + // For example, change the value of the "TestString" value in the subkey or + // add a new value to the subkey. + + // Stop monitoring after 30 seconds. + await Future.delayed(const Duration(seconds: 30), () async { + await subscription.cancel(); + subkey.close(); + hkcu + ..deleteKey(subkeyName) + ..close(); + print('Stopped monitoring.'); + }); +} diff --git a/example/read_values.dart b/example/read_values.dart index e491293..db70196 100644 --- a/example/read_values.dart +++ b/example/read_values.dart @@ -5,8 +5,22 @@ void main() { final key = Registry.openPath(RegistryHive.localMachine, path: keyPath); print('Values:'); - for (final value in key.values) { - print(' - ${value.toString()}'); + for (final registryValue in key.values) { + final RegistryValue(:name, :type) = registryValue; + switch (registryValue) { + case BinaryValue(:final value): + print(' - $name ($type): $value'); + case Int32Value(:final value) || Int64Value(:final value): + print(' - $name ($type): $value'); + case LinkValue(:final value) || + StringValue(:final value) || + UnexpandedStringValue(:final value): + print(' - $name ($type): $value'); + case StringArrayValue(:final value): + print(' - $name ($type): $value'); + case NoneValue(): + print(' - $name ($type)'); + } } print('\n${'-' * 80}\n'); diff --git a/lib/src/access_rights.dart b/lib/src/access_rights.dart new file mode 100644 index 0000000..483c40f --- /dev/null +++ b/lib/src/access_rights.dart @@ -0,0 +1,36 @@ +import 'package:win32/win32.dart'; + +/// Defines the access rights for registry operations. +enum AccessRights { + /// Grants read-only access to a registry key. + /// + /// Use this option to read the contents of the key and its values. + readOnly(REG_SAM_FLAGS.KEY_READ), + + /// Grants write-only access to a registry key. + /// + /// Use this option to modify the contents of the key or create subkeys. + writeOnly(REG_SAM_FLAGS.KEY_WRITE), + + /// Grants full access to a registry key. + /// + /// Use this option to perform any operation on the key, including reading, + /// writing, and changing permissions. + allAccess(REG_SAM_FLAGS.KEY_ALL_ACCESS); + + /// Creates an [AccessRights] enum instance with the specified access flag. + /// + /// The [value] parameter corresponds to a Win32 API constant representing + /// the access level. + const AccessRights(this.value); + + /// The access level as an integer, corresponding to the Win32 API constant. + final int value; + + @override + String toString() => switch (this) { + readOnly => 'KEY_READ', + writeOnly => 'KEY_WRITE', + allAccess => 'KEY_ALL_ACCESS' + }; +} diff --git a/lib/src/models/access_rights.dart b/lib/src/models/access_rights.dart deleted file mode 100644 index 9f61c12..0000000 --- a/lib/src/models/access_rights.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:win32/win32.dart'; - -/// Represents a requested access right. -enum AccessRights { - readOnly(REG_SAM_FLAGS.KEY_READ), - writeOnly(REG_SAM_FLAGS.KEY_WRITE), - allAccess(REG_SAM_FLAGS.KEY_ALL_ACCESS); - - /// The Win32 value represented by the enumeration. - final int win32Value; - - const AccessRights(this.win32Value); -} diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart deleted file mode 100644 index 8cfde1c..0000000 --- a/lib/src/models/models.dart +++ /dev/null @@ -1,5 +0,0 @@ -export 'access_rights.dart'; -export 'pointer_data.dart'; -export 'registry_hive.dart'; -export 'registry_key_info.dart'; -export 'registry_value_type.dart'; diff --git a/lib/src/models/pointer_data.dart b/lib/src/models/pointer_data.dart deleted file mode 100644 index be77918..0000000 --- a/lib/src/models/pointer_data.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:ffi'; - -/// A class representing pointer data and its length in bytes. -class PointerData { - /// Creates an instance of [PointerData] with the given pointer [data] and - /// its [lengthInBytes]. - const PointerData(this.data, this.lengthInBytes); - - /// The pointer to the data. - final Pointer data; - - /// The length of the data in bytes. - final int lengthInBytes; -} diff --git a/lib/src/models/registry_hive.dart b/lib/src/models/registry_hive.dart deleted file mode 100644 index b39649e..0000000 --- a/lib/src/models/registry_hive.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:win32/win32.dart'; - -/// One of the predefined keys that point into one or more hives that Windows -/// stores. -/// -/// An application can use handles to these keys as entry points to the -/// Registry. Predefined keys help an application navigate in the registry and -/// make it possible to develop tools that allow a system administrator to -/// manipulate categories of data. Applications that add data to the registry -/// should always work within the framework of predefined keys, so -/// administrative tools can find and use the new data. -enum RegistryHive { - /// Registry entries subordinate to this key define the physical state of the - /// computer, including data about the bus type, system memory, and installed - /// hardware and software. - localMachine(HKEY_LOCAL_MACHINE), - - /// Registry entries subordinate to this key define the preferences of the - /// current user. These preferences include the settings of environment - /// variables, data about program groups, colors, printers, network - /// connections, and application preferences. This key makes it easier to - /// establish the current user's settings; the key maps to the current user's - /// branch in `HKEY_USERS`. - currentUser(HKEY_CURRENT_USER), - - /// Registry entries subordinate to this key define the default user - /// configuration for new users on the local computer and the user - /// configuration for the current user. - allUsers(HKEY_USERS), - - /// Registry entries subordinate to this key define types (or classes) of - /// documents and the properties associated with those types. Shell and COM - /// applications use the information stored under this key. - classesRoot(HKEY_CLASSES_ROOT), - - /// Contains information about the current hardware profile of the local - /// computer system. The information under `HKEY_CURRENT_CONFIG` describes - /// only the differences between the current hardware configuration and the - /// standard configuration. - currentConfig(HKEY_CURRENT_CONFIG), - - /// Registry entries subordinate to this key allow you to access performance - /// data. The data is not actually stored in the registry; the registry - /// functions cause the system to collect the data from its source. - performanceData(HKEY_PERFORMANCE_DATA); - - /// Returns the handle for a predefined key. - final int win32Value; - - const RegistryHive(this.win32Value); -} diff --git a/lib/src/models/registry_key_info.dart b/lib/src/models/registry_key_info.dart deleted file mode 100644 index 8bd9c32..0000000 --- a/lib/src/models/registry_key_info.dart +++ /dev/null @@ -1,42 +0,0 @@ -/// Represents information about a Windows Registry key. -class RegistryKeyInfo { - /// Creates an instance of [RegistryKeyInfo] with the specified attributes. - const RegistryKeyInfo( - this.className, - this.subKeyCount, - this.subKeyNameMaxLength, - this.subKeyClassNameMaxLength, - this.valuesCount, - this.valueNameMaxLength, - this.valueDataMaxSizeInBytes, - this.securityDescriptorLength, - this.lastWriteTime, - ); - - /// The class name of the Registry key. - final String className; - - /// The number of subkeys under this Registry key. - final int subKeyCount; - - /// The maximum length of subkey names. - final int subKeyNameMaxLength; - - /// The maximum length of subkey class names. - final int subKeyClassNameMaxLength; - - /// The number of values associated with this Registry key. - final int valuesCount; - - /// The maximum length of value names. - final int valueNameMaxLength; - - /// The maximum size of value data associated with this Registry key. - final int valueDataMaxSizeInBytes; - - /// The length of the security descriptor associated with this Registry key. - final int securityDescriptorLength; - - /// The date and time when this Registry key was last written to. - final DateTime? lastWriteTime; -} diff --git a/lib/src/models/registry_value_type.dart b/lib/src/models/registry_value_type.dart deleted file mode 100644 index 376aa0c..0000000 --- a/lib/src/models/registry_value_type.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:win32/win32.dart'; - -/// A data type stored in the Windows Registry. -/// -/// These do not directly map onto either Win32 or Dart types, but represent the -/// kinds of entities that the registry understands. -/// -/// More information about the kinds of data that can be found in the registry -/// can be found here: -/// https://learn.microsoft.com/windows/win32/sysinfo/registry-value-types -enum RegistryValueType { - /// Binary data in any form. This value is equivalent to the Windows API - /// registry data type `REG_BINARY`. - binary(REG_VALUE_TYPE.REG_BINARY), - - /// A 32-bit binary number. This value is equivalent to the Windows API - /// registry data type `REG_DWORD`. - int32(REG_VALUE_TYPE.REG_DWORD), - - /// A null-terminated string that contains unexpanded references to - /// environment variables, such as %PATH%, that are expanded when the value is - /// retrieved. This value is equivalent to the Windows API registry data type - /// `REG_EXPAND_SZ`. - unexpandedString(REG_VALUE_TYPE.REG_EXPAND_SZ), - - /// A null-terminated string that contains the target path of a symbolic link. - /// This value is equivalent to the Windows API registry data type `REG_LINK`. - link(REG_VALUE_TYPE.REG_LINK), - - /// An array of null-terminated strings, terminated by two null characters. - /// This value is equivalent to the Windows API registry data type - /// `REG_MULTI_SZ`. - stringArray(REG_VALUE_TYPE.REG_MULTI_SZ), - - /// No data type. - none(REG_VALUE_TYPE.REG_NONE), - - /// A 64-bit binary number. This value is equivalent to the Windows API - /// registry data type `REG_QWORD`. - int64(REG_VALUE_TYPE.REG_QWORD), - - /// A null-terminated string. This value is equivalent to the Windows API - /// registry data type `REG_SZ`. - string(REG_VALUE_TYPE.REG_SZ), - - /// An unknown type. - unknown(-1); - - /// Return the Win32 value that represents the stored type. - final int win32Value; - - const RegistryValueType(this.win32Value); - - /// Return a string representing the Win32 type stored. - String get win32Type => switch (this) { - RegistryValueType.binary => 'REG_BINARY', - RegistryValueType.int32 => 'REG_DWORD', - RegistryValueType.unexpandedString => 'REG_EXPAND_SZ', - RegistryValueType.link => 'REG_LINK', - RegistryValueType.stringArray => 'REG_MULTI_SZ', - RegistryValueType.int64 => 'REG_QWORD', - RegistryValueType.string => 'REG_SZ', - RegistryValueType.none => 'REG_NONE', - _ => '' - }; -} diff --git a/lib/src/registry.dart b/lib/src/registry.dart index 989b023..05a24e1 100644 --- a/lib/src/registry.dart +++ b/lib/src/registry.dart @@ -3,17 +3,16 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:win32/win32.dart'; -import 'models/access_rights.dart'; -import 'models/registry_hive.dart'; +import 'access_rights.dart'; +import 'registry_hive.dart'; import 'registry_key.dart'; -/// Provides access to the Windows Registry as a database. +/// Provides access to the Windows Registry, allowing interaction with registry +/// keys and their values. /// -/// Use this object to work with Registry keys and values, such as opening keys, -/// viewing subkeys, creating new keys, and managing key values. -/// -/// You can either open a specific key within the Registry or use one of the -/// predefined root keys (e.g., [localMachine], [currentUser], or [classesRoot]). +/// Use this class to open, create, and manage registry keys and values. You can +/// open specific registry keys or use predefined root keys, such as +/// [localMachine], [currentUser], or [classesRoot]. /// /// Example of opening a key: /// @@ -22,20 +21,21 @@ import 'registry_key.dart'; /// final key = Registry.openPath(RegistryHive.localMachine, path: keyPath); /// ``` /// -/// Once you have a key, you can manipulate it by performing various operations, -/// including creating, updating, renaming, or deleting values stored under that -/// key. +/// After obtaining a key, you can perform various operations such as creating, +/// updating, renaming, or deleting values stored within the key. Additionally, +/// you can monitor the key for any changes, allowing for responsive updates to +/// registry modifications. /// -/// **Note:** Always ensure that you properly close keys when you are finished -/// with them to release system resources using the [RegistryKey.close] method. +/// **Note:** To conserve system resources, ensure that opened keys are closed +/// with the [RegistryKey.close] method once you're finished. /// -/// For additional information on working with keys and values, see the -/// documentation for the [RegistryKey] class. -interface class Registry { - /// Opens a Registry key based on the given path and Registry hive. +/// For further details on key and value management, refer to [RegistryKey]. +abstract final class Registry { + /// Opens a registry key at the specified [path] within the given [hive]. /// - /// When you are finished with the key, make sure to close it using the - /// [RegistryKey.close] method to release system resources. + /// The [desiredAccessRights] parameter specifies the level of access required. + /// Once a key is opened, remember to close it with [RegistryKey.close] when + /// done to free system resources. /// /// Throws a [WindowsException] if the key cannot be opened. static RegistryKey openPath( @@ -47,7 +47,12 @@ interface class Registry { final lpSubKey = path.toNativeUtf16(); try { final lStatus = RegOpenKeyEx( - hive.win32Value, lpSubKey, 0, desiredAccessRights.win32Value, phKey); + hive.value, + lpSubKey, + 0, + desiredAccessRights.value, + phKey, + ); if (lStatus == WIN32_ERROR.ERROR_SUCCESS) return RegistryKey(phKey.value); throw WindowsException(HRESULT_FROM_WIN32(lStatus)); } finally { @@ -56,23 +61,21 @@ interface class Registry { } } - /// Gets the `HKEY_USERS` Registry key with read-only access. - static RegistryKey get allUsers => openPath(RegistryHive.allUsers, - desiredAccessRights: AccessRights.readOnly); + /// Opens the `HKEY_USERS` root registry key with read-only access. + static RegistryKey get allUsers => openPath(RegistryHive.allUsers); - /// Gets the `HKEY_CLASSES_ROOT` Registry key with read-only access. - static RegistryKey get classesRoot => openPath(RegistryHive.classesRoot, - desiredAccessRights: AccessRights.readOnly); + /// Opens the `HKEY_CLASSES_ROOT` root registry key with read-only access. + static RegistryKey get classesRoot => openPath(RegistryHive.classesRoot); - /// Gets the `HKEY_CURRENT_CONFIG` Registry key with read-only access. - static RegistryKey get currentConfig => openPath(RegistryHive.currentConfig, - desiredAccessRights: AccessRights.readOnly); + /// Opens the `HKEY_CURRENT_CONFIG` root registry key with read-only access. + static RegistryKey get currentConfig => openPath(RegistryHive.currentConfig); - /// Gets the `HKEY_CURRENT_USER` Registry key with read-only access. + /// Opens the `HKEY_CURRENT_USER` root registry key with read-only access. + /// + /// This method directly invokes the Windows API to retrieve the appropriate + /// user context, useful when the thread might be impersonating another user + /// (e.g., through "Run As" operations). static RegistryKey get currentUser { - // Instead of opening HKEY_CURRENT_USER, this calls the appropriate Windows - // API, since the thread may be impersonating a different user (e.g. Run - // As...) final phKey = calloc(); try { final lStatus = RegOpenCurrentUser(REG_SAM_FLAGS.KEY_ALL_ACCESS, phKey); @@ -83,12 +86,6 @@ interface class Registry { } } - /// Gets the `HKEY_LOCAL_MACHINE` Registry key with read-only access. - static RegistryKey get localMachine => openPath(RegistryHive.localMachine, - desiredAccessRights: AccessRights.readOnly); - - /// Gets the `HKEY_PERFORMANCE_DATA` Registry key with read-only access. - static RegistryKey get performanceData => - openPath(RegistryHive.performanceData, - desiredAccessRights: AccessRights.readOnly); + /// Opens the `HKEY_LOCAL_MACHINE` root registry key with read-only access. + static RegistryKey get localMachine => openPath(RegistryHive.localMachine); } diff --git a/lib/src/registry_hive.dart b/lib/src/registry_hive.dart new file mode 100644 index 0000000..1fd4092 --- /dev/null +++ b/lib/src/registry_hive.dart @@ -0,0 +1,80 @@ +import 'package:win32/win32.dart'; + +/// Represents one of the predefined registry keys in Windows, each pointing to +/// a specific area of the Windows registry. +/// +/// These predefined keys serve as entry points into the Windows registry and +/// help applications navigate the registry structure. They allow system +/// administrators and applications to access, modify, and categorize registry +/// data. When adding data to the registry, applications should ideally work +/// within the framework of these predefined keys to maintain compatibility +/// with administrative tools. +enum RegistryHive { + /// Points to registry entries defining types (or classes) of documents and + /// the properties associated with those types. + /// + /// Used by Shell and COM applications for storing class-specific information. + classesRoot._(HKEY_CLASSES_ROOT), + + /// Points to registry entries defining the preferences and settings for the + /// current user, including environment variables, network connections, + /// application preferences, and more. + /// + /// This key is mapped to the current user's data in `HKEY_USERS`. + currentUser._(HKEY_CURRENT_USER), + + /// Points to registry entries defining the physical and software + /// configuration of the local machine, such as system memory, hardware, + /// installed software, and other system-wide configurations. + localMachine._(HKEY_LOCAL_MACHINE), + + /// Points to registry entries defining the default settings for new users on + /// the local machine as well as the configuration settings for the current + /// user. + allUsers._(HKEY_USERS), + + /// Points to a location providing access to performance data, dynamically + /// generated by the system. + /// + /// The data itself is not stored in the registry but is collected when + /// requested. + performanceData._(HKEY_PERFORMANCE_DATA), + + /// Contains information about the current hardware profile of the local + /// computer system, describing differences between the current configuration + /// and the standard hardware configuration. + currentConfig._(HKEY_CURRENT_CONFIG); + + const RegistryHive._(this.value); + + /// Creates a [RegistryHive] instance from a Win32 handle. + /// + /// Throws an [ArgumentError] if the provided handle does not match any + /// predefined registry hive. + factory RegistryHive.fromWin32(int value) => switch (value) { + HKEY_CLASSES_ROOT => classesRoot, + HKEY_CURRENT_USER => currentUser, + HKEY_LOCAL_MACHINE => localMachine, + HKEY_USERS => allUsers, + HKEY_PERFORMANCE_DATA => performanceData, + HKEY_CURRENT_CONFIG => currentConfig, + _ => throw ArgumentError.value( + value, + 'value', + 'Unknown registry hive: $value', + ), + }; + + /// The handle representing a predefined registry key. + final int value; + + @override + String toString() => switch (this) { + classesRoot => 'HKEY_CLASSES_ROOT', + currentUser => 'HKEY_CURRENT_USER', + localMachine => 'HKEY_LOCAL_MACHINE', + allUsers => 'HKEY_USERS', + performanceData => 'HKEY_PERFORMANCE_DATA', + currentConfig => 'HKEY_CURRENT_CONFIG', + }; +} diff --git a/lib/src/registry_key.dart b/lib/src/registry_key.dart index b94e46c..09006be 100644 --- a/lib/src/registry_key.dart +++ b/lib/src/registry_key.dart @@ -1,35 +1,29 @@ +import 'dart:async'; import 'dart:ffi'; +import 'dart:isolate'; +import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:win32/win32.dart'; -import 'models/registry_key_info.dart'; -import 'models/registry_value_type.dart'; +import 'registry_key_info.dart'; import 'registry_value.dart'; +import 'registry_value_type.dart'; import 'utils.dart'; -/// An individual node in the Windows Registry. -/// -/// Registry data is structured in a tree format. Each node in the tree is -/// called a key. Keys can contain data entries called values. Keys are somewhat -/// analagous to a directory in a file system, with values being analagous to -/// files. -/// -/// Sometimes, the presence of a key is all the data that an application -/// requires; other times, an application opens a key and uses the values -/// associated with the key. -class RegistryKey { - /// Creates an instance of the [RegistryKey] with the specified handle. +/// Represents a node in the Windows Registry, structured as a tree of keys +/// that may contain values analogous to files in a filesystem. +final class RegistryKey { + /// Creates a [RegistryKey] instance with the given [hkey] handle. const RegistryKey(this.hkey); - /// A handle to the current Registry key + /// Handle to the current registry key. final int hkey; - /// Creates the specified Registry key. + /// Creates a new registry key specified by [keyName], or opens it if it + /// already exists. /// - /// If the key already exists, the function opens it. - /// - /// Note that key names are not case sensitive. + /// **Note:** Key names are not case-sensitive. RegistryKey createKey(String keyName) { final lpSubKey = keyName.toNativeUtf16(); final phkResult = calloc(); @@ -45,13 +39,11 @@ class RegistryKey { } } - /// Deletes a subkey and its values from the specified platform-specific view - /// of the Registry. + /// Deletes the specified subkey. /// - /// Set [recursive] to `true` if you want to delete subkey with all its - /// subkeys. + /// If [recursive] is `true`, all subkeys within it are also deleted. /// - /// Note that key names are not case sensitive. + /// **Note:** Key names are not case-sensitive. void deleteKey(String keyName, {bool recursive = false}) { final lpSubKey = keyName.toNativeUtf16(); try { @@ -60,7 +52,6 @@ class RegistryKey { if (!recursive) throw WindowsException(HRESULT_FROM_WIN32(retcode)); final key = createKey(keyName); - try { for (final subKeyName in key.subkeyNames.toList()) { key.deleteKey(subKeyName, recursive: true); @@ -69,20 +60,26 @@ class RegistryKey { key.close(); } - deleteKey(keyName, recursive: false); + deleteKey(keyName); } } finally { free(lpSubKey); } } - /// Sets the data and type of a specified value under a Registry key. + /// Sets a registry [value] for the current registry key. void createValue(RegistryValue value) { final lpValueName = value.name.toNativeUtf16(); - final lpWin32Data = value.toWin32; + final lpWin32Data = value.toWin32(); try { - final retcode = RegSetValueEx(hkey, lpValueName, NULL, - value.type.win32Value, lpWin32Data.data, lpWin32Data.lengthInBytes); + final retcode = RegSetValueEx( + hkey, + lpValueName, + NULL, + value.type.value, + lpWin32Data.data, + lpWin32Data.lengthInBytes, + ); if (retcode != WIN32_ERROR.ERROR_SUCCESS) { throw WindowsException(HRESULT_FROM_WIN32(retcode)); } @@ -92,63 +89,112 @@ class RegistryKey { } } - /// Retrieves the type and data for the specified Registry value. + /// Retrieves the registry value identified by [valueName]. + /// + /// The optional [path] parameter specifies the subkey path to the value. + /// If omitted, the search defaults to the current key. + /// + /// When [expandPaths] is `true`, any environment variables in the string + /// will be expanded. + /// + /// Returns `null` if the value is not found. RegistryValue? getValue( String valueName, { String path = '', bool expandPaths = false, - }) { - return using((arena) { - final lpSubKey = path.toNativeUtf16(allocator: arena); - final lpValue = valueName.toNativeUtf16(allocator: arena); - final pdwType = arena(); - final pcbData = arena(); - - final flags = expandPaths - ? REG_ROUTINE_FLAGS.RRF_RT_ANY - : REG_ROUTINE_FLAGS.RRF_RT_ANY | REG_ROUTINE_FLAGS.RRF_NOEXPAND; - - // Call first time to find out how much memory we need to allocate - var retcode = RegGetValue( - hkey, lpSubKey, lpValue, flags, pdwType, nullptr, pcbData); - if (retcode == WIN32_ERROR.ERROR_FILE_NOT_FOUND) return null; - - // Now call for real to get the data we need. - final pvData = arena(pcbData.value); - retcode = - RegGetValue(hkey, lpSubKey, lpValue, flags, pdwType, pvData, pcbData); - return RegistryValue.fromWin32( - lpValue.toDartString(), pdwType.value, pvData, pcbData.value); - }); - } + }) => + using((arena) { + final lpSubKey = path.toNativeUtf16(allocator: arena); + final lpValue = valueName.toNativeUtf16(allocator: arena); + final pdwType = arena(); + final pcbData = arena(); - /// Retrieves the string data for the specified Registry value. - String? getValueAsString(String valueName, {bool expandPaths = false}) { - final registryValue = getValue(valueName, expandPaths: expandPaths); - if (registryValue == null) return null; - return switch (registryValue.type) { - RegistryValueType.string || - RegistryValueType.unexpandedString || - RegistryValueType.link => - registryValue.data as String, - _ => null - }; - } + final flags = expandPaths + ? REG_ROUTINE_FLAGS.RRF_RT_ANY + : REG_ROUTINE_FLAGS.RRF_RT_ANY | REG_ROUTINE_FLAGS.RRF_NOEXPAND; - /// Retrieves the integer data for the specified Registry value. - int? getValueAsInt(String valueName) { - final registryValue = getValue(valueName); - if (registryValue == null) return null; - return switch (registryValue.type) { - RegistryValueType.int32 || - RegistryValueType.int64 => - registryValue.data as int, - _ => null - }; - } + // Call first time to find out how much memory we need to allocate + var retcode = RegGetValue( + hkey, + lpSubKey, + lpValue, + flags, + pdwType, + nullptr, + pcbData, + ); + if (retcode == WIN32_ERROR.ERROR_FILE_NOT_FOUND) return null; + + // Now call for real to get the data we need. + final pvData = arena(pcbData.value); + retcode = RegGetValue( + hkey, + lpSubKey, + lpValue, + flags, + pdwType, + pvData, + pcbData, + ); + final valueType = RegistryValueType.fromWin32(pdwType.value); + return valueType.toRegistryValue( + lpValue.toDartString(), + pvData, + pcbData.value, + ); + }); + + /// Retrieves a binary value identified by [valueName]. + /// + /// Returns `null` if the value does not exist or is not a binary value. + Uint8List? getBinaryValue(String valueName) => switch (getValue(valueName)) { + BinaryValue(:final value) => value, + _ => null + }; + + /// Retrieves an integer value identified by [valueName]. + /// + /// Returns `null` if the value does not exist or is not an integer. + int? getIntValue(String valueName) => switch (getValue(valueName)) { + Int32Value(:final value) || Int64Value(:final value) => value, + _ => null + }; - /// Removes a named value from the specified Registry key. Note that value - /// names are not case sensitive. + /// Retrieves a string value identified by [valueName], with optional path + /// expansion. + /// + /// If [expandPaths] is `true`, environment variables in the string will be + /// expanded. + /// + /// Returns `null` if the value does not exist or is not a string. + String? getStringValue(String valueName, {bool expandPaths = false}) => + switch (getValue(valueName, expandPaths: expandPaths)) { + LinkValue(:final value) || + StringValue(:final value) || + UnexpandedStringValue(:final value) => + value, + _ => null + }; + + /// Retrieves a string array value identified by [valueName]. + /// + /// Returns `null` if the value does not exist or is not a string array. + List? getStringArrayValue(String valueName) => + switch (getValue(valueName)) { + StringArrayValue(:final value) => value, + _ => null + }; + + @Deprecated('Use getIntValue instead') + int? getValueAsInt(String valueName) => getIntValue(valueName); + + @Deprecated('Use getStringValue instead') + String? getValueAsString(String valueName, {bool expandPaths = false}) => + getStringValue(valueName, expandPaths: expandPaths); + + /// Deletes a value identified by [valueName] from the current registry key. + /// + /// **Note:** Value names are not case-sensitive. void deleteValue(String valueName) { final lpValueName = valueName.toNativeUtf16(); try { @@ -161,7 +207,7 @@ class RegistryKey { } } - /// Changes the name of the specified Registry key. + /// Renames a subkey from [oldName] to [newName]. void renameSubkey(String oldName, String newName) { final lpSubKeyName = oldName.toNativeUtf16(); final lpNewKeyName = newName.toNativeUtf16(); @@ -176,25 +222,149 @@ class RegistryKey { } } - /// Retrieves information about the specified Registry key. - RegistryKeyInfo queryInfo() { - return using((Arena arena) { - final lpClass = arena(256).cast(); - final lpcchClass = arena()..value = 256; - final lpcSubKeys = arena(); - final lpcbMaxSubKeyLen = arena(); - final lpcbMaxClassLen = arena(); - final lpcValues = arena(); - final lpcbMaxValueNameLen = arena(); - final lpcbMaxValueLen = arena(); - final lpcbSecurityDescriptor = arena(); - final lpftLastWriteTime = arena(); - - final retcode = RegQueryInfoKey( + /// Emits an event when the current registry key changes. + /// + /// If [includeSubkeys] is `true`, changes in the subkeys will also trigger + /// events. + Stream onChanged({bool includeSubkeys = false}) { + ReceivePort? receivePort; + SendPort? isolateStopPort; + StreamSubscription? receivePortSubscription; + StreamController? controller; + + controller = StreamController.broadcast( + onListen: () async { + // Create a ReceivePort to listen for messages from the isolate. + receivePort = ReceivePort(); + + // Start the isolate and pass the message port. + await Isolate.spawn( + _startListening, + (receivePort!.sendPort, includeSubkeys), + ); + + // Listen for messages from the isolate. + receivePortSubscription = receivePort!.listen((message) { + if (message is SendPort) { + isolateStopPort = message; + } else if (message == null) { + controller?.add(null); // Notify listeners of clipboard change. + } else if (message is Error) { + receivePortSubscription?.cancel(); + receivePort?.close(); + controller?.addError(message); + controller?.close(); + } + }); + }, + onCancel: () async { + isolateStopPort?.send(null); // Signal to stop the isolate. + // Give the isolate time to shut down gracefully. + await Future.delayed(const Duration(milliseconds: 5)); + await receivePortSubscription?.cancel(); + receivePort?.close(); + await controller?.close(); + }, + ); + + return controller.stream; + } + + // Run the listening operation in a separate isolate. + Future _startListening( + (SendPort mainSendPort, bool includeSubkey) arg, + ) async { + final mainSendPort = arg.$1; + final includeSubkeys = arg.$2; + + // Create a ReceivePort for stopping the isolate. + final stopPort = ReceivePort(); + + // Send the SendPort of stopPort back to the main isolate. + mainSendPort.send(stopPort.sendPort); + + final hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (hEvent == NULL) { + stopPort.close(); + mainSendPort.send(WindowsException(HRESULT_FROM_WIN32(GetLastError()))); + return; + } + + // Whether the message loop should continue running. + var isRunning = true; + + StreamSubscription? stopPortSubscription; + + // Listen for the stop signal to gracefully shut down the isolate. + stopPortSubscription = stopPort.listen((message) async { + await stopPortSubscription?.cancel(); + stopPort.close(); + isRunning = false; + }); + + try { + while (isRunning) { + // Set up registry change notification. + final result = RegNotifyChangeKeyValue( + hkey, + includeSubkeys ? TRUE : FALSE, + REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_NAME | + REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_ATTRIBUTES | + REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_LAST_SET | + REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_SECURITY, + hEvent, + TRUE, + ); + if (result != WIN32_ERROR.ERROR_SUCCESS) { + mainSendPort.send(WindowsException(HRESULT_FROM_WIN32(result))); + break; + } + + // Polling with 10ms timeout to allow periodic check for stop signal. + while (true) { + final result = WaitForSingleObject(hEvent, 10); + if (result == WAIT_EVENT.WAIT_OBJECT_0) { + mainSendPort.send(null); // Notify listeners of the change. + ResetEvent(hEvent); // Reset the event for future notifications. + break; + } else if (result == WAIT_EVENT.WAIT_TIMEOUT) { + // Yield control to the Dart event loop to allow for the stop + // signal to be processed. + await Future.delayed(Duration.zero); + } else { + // An unexpected result, stop listening. + await stopPortSubscription.cancel(); + stopPort.close(); + isRunning = false; + final error = WindowsException(HRESULT_FROM_WIN32(GetLastError())); + mainSendPort.send(error); + break; + } + } + } + } finally { + CloseHandle(hEvent); + } + } + + /// Retrieves details about the current registry key. + RegistryKeyInfo queryInfo() => using((arena) { + final lpClass = arena(256).cast(); + final lpcchClass = arena()..value = 256; + final lpcSubKeys = arena(); + final lpcbMaxSubKeyLen = arena(); + final lpcbMaxClassLen = arena(); + final lpcValues = arena(); + final lpcbMaxValueNameLen = arena(); + final lpcbMaxValueLen = arena(); + final lpcbSecurityDescriptor = arena(); + final lpftLastWriteTime = arena(); + + final retcode = RegQueryInfoKey( hkey, lpClass, lpcchClass, - nullptr, // reserved, must be NULL + nullptr, lpcSubKeys, lpcbMaxSubKeyLen, lpcbMaxClassLen, @@ -202,14 +372,15 @@ class RegistryKey { lpcbMaxValueNameLen, lpcbMaxValueLen, lpcbSecurityDescriptor, - lpftLastWriteTime); + lpftLastWriteTime, + ); - if (retcode != WIN32_ERROR.ERROR_SUCCESS) { - throw WindowsException(HRESULT_FROM_WIN32(retcode)); - } + if (retcode != WIN32_ERROR.ERROR_SUCCESS) { + throw WindowsException(HRESULT_FROM_WIN32(retcode)); + } - final lastWriteTime = convertToDartDateTime(lpftLastWriteTime); - return RegistryKeyInfo( + final lastWriteTime = lpftLastWriteTime.toDateTime(); + return RegistryKeyInfo( lpClass.toDartString(), lpcSubKeys.value, lpcbMaxSubKeyLen.value, @@ -218,11 +389,11 @@ class RegistryKey { lpcbMaxValueNameLen.value, lpcbMaxValueLen.value, lpcbSecurityDescriptor.value, - lastWriteTime); - }); - } + lastWriteTime, + ); + }); - /// Enumerates the values for the specified open Registry key. + /// Enumerates the names of all subkeys within the current registry key. Iterable get values sync* { final keyInfo = queryInfo(); @@ -243,10 +414,22 @@ class RegistryKey { lpcchData.value = keyInfo.valueDataMaxSizeInBytes; final retcode = RegEnumValue( - hkey, idx, lpName, lpcchName, nullptr, lpType, lpData, lpcchData); + hkey, + idx, + lpName, + lpcchName, + nullptr, + lpType, + lpData, + lpcchData, + ); if (retcode == WIN32_ERROR.ERROR_SUCCESS) { - yield RegistryValue.fromWin32( - lpName.toDartString(), lpType.value, lpData, lpcchData.value); + final valueType = RegistryValueType.fromWin32(lpType.value); + yield valueType.toRegistryValue( + lpName.toDartString(), + lpData, + lpcchData.value, + ); } } } finally { @@ -258,7 +441,7 @@ class RegistryKey { } } - /// Enumerates the values for the specified open Registry key. + /// Enumerates the values under the current registry key. Iterable get subkeyNames sync* { final keyInfo = queryInfo(); @@ -275,7 +458,15 @@ class RegistryKey { lpcchName.value = keyNameLength; final retcode = RegEnumKeyEx( - hkey, idx, lpName, lpcchName, nullptr, nullptr, nullptr, nullptr); + hkey, + idx, + lpName, + lpcchName, + nullptr, + nullptr, + nullptr, + nullptr, + ); if (retcode == WIN32_ERROR.ERROR_SUCCESS) yield lpName.toDartString(); } } finally { @@ -284,6 +475,6 @@ class RegistryKey { } } - /// Closes a handle to the specified Registry key. + /// Releases the handle associated with the registry key. void close() => RegCloseKey(hkey); } diff --git a/lib/src/registry_key_info.dart b/lib/src/registry_key_info.dart new file mode 100644 index 0000000..76308ba --- /dev/null +++ b/lib/src/registry_key_info.dart @@ -0,0 +1,44 @@ +/// Contains information about a Windows Registry key and its associated +/// attributes. +class RegistryKeyInfo { + /// Constructs a [RegistryKeyInfo] instance with detailed information about + /// the specified registry key attributes. + const RegistryKeyInfo( + this.className, + this.subKeyCount, + this.subKeyNameMaxLength, + this.subKeyClassNameMaxLength, + this.valuesCount, + this.valueNameMaxLength, + this.valueDataMaxSizeInBytes, + this.securityDescriptorLength, + this.lastWriteTime, + ); + + /// The class name associated with the registry key. + final String className; + + /// The number of subkeys contained within this registry key. + final int subKeyCount; + + /// The maximum length, in characters, of the subkey names. + final int subKeyNameMaxLength; + + /// The maximum length, in characters, of the subkey class names. + final int subKeyClassNameMaxLength; + + /// The number of values directly associated with this registry key. + final int valuesCount; + + /// The maximum length, in characters, of the value names. + final int valueNameMaxLength; + + /// The maximum size, in bytes, of the value data entries under this key. + final int valueDataMaxSizeInBytes; + + /// The size, in bytes, of the security descriptor associated with this key. + final int securityDescriptorLength; + + /// The date and time of the last modification to this registry key. + final DateTime? lastWriteTime; +} diff --git a/lib/src/registry_value.dart b/lib/src/registry_value.dart index 14c5fb4..b112208 100644 --- a/lib/src/registry_value.dart +++ b/lib/src/registry_value.dart @@ -1,115 +1,239 @@ -import 'dart:ffi'; import 'dart:typed_data'; -import 'package:ffi/ffi.dart'; -import 'package:win32/win32.dart'; +import 'registry_value_type.dart'; -import 'models/models.dart'; +/// Represents a data value stored in the Windows Registry. +/// +/// This class serves as a base for various types of registry values, including +/// binary data, integers, links, strings, and arrays of strings. +/// +/// Each instance of [RegistryValue] contains a [name] that identifies the +/// registry value and a [type] that indicates its data type. +sealed class RegistryValue { + /// Constructs a [RegistryValue] with the specified [name] and [type]. + const RegistryValue._(this.name, this.type); -/// Represents an individual data value in the Windows Registry. -class RegistryValue { - /// Creates an instance of [RegistryValue] with the specified [name], [type], - /// and [data]. - const RegistryValue(this.name, this.type, this.data); + /// Constructs a [RegistryValue] in binary data format with the specified + /// [name] and [value]. + const factory RegistryValue.binary(String name, Uint8List value) = + BinaryValue; - /// The name of the Registry value. + /// Constructs a [RegistryValue] in 32-bit integer format with the specified + /// [name] and [value]. + const factory RegistryValue.int32(String name, int value) = Int32Value; + + /// Constructs a [RegistryValue] in 64-bit integer format with the specified + /// [name] and [value]. + const factory RegistryValue.int64(String name, int value) = Int64Value; + + /// Constructs a [RegistryValue] in symbolic link format with the specified + /// [name] and [value]. + const factory RegistryValue.link(String name, String value) = LinkValue; + + /// Constructs a [RegistryValue] in null format with the specified [name]. + const factory RegistryValue.none(String name) = NoneValue; + + /// Constructs a [RegistryValue] in string format with the specified [name] + /// and [value]. + const factory RegistryValue.string(String name, String value) = StringValue; + + /// Constructs a [RegistryValue] in array of string format with the specified + /// [name] and [value]. + const factory RegistryValue.stringArray(String name, List value) = + StringArrayValue; + + /// Constructs a [RegistryValue] in unexpanded string format with the + /// specified [name] and [value]. + const factory RegistryValue.unexpandedString(String name, String value) = + UnexpandedStringValue; + + /// The name of the registry value. final String name; - /// The type of the Registry value. + /// The type of the registry value. final RegistryValueType type; +} + +/// Represents a binary data value in the Windows Registry, corresponding to the +/// `REG_BINARY` type. +final class BinaryValue extends RegistryValue { + /// Constructs a [BinaryValue] with the specified [name] and [value]. + const BinaryValue(String name, this.value) + : super._(name, RegistryValueType.binary); + + /// The binary data value represented as a [Uint8List]. + final Uint8List value; - /// The data associated with the Registry value. - final Object data; - - /// Constructs a [RegistryValue] from Win32 Registry data. - factory RegistryValue.fromWin32( - String name, - int type, - Pointer byteData, - int dataLength, - ) => - switch (type) { - REG_VALUE_TYPE.REG_SZ => RegistryValue( - name, - RegistryValueType.string, - byteData.cast().toDartString(), - ), - REG_VALUE_TYPE.REG_EXPAND_SZ => RegistryValue( - name, - RegistryValueType.unexpandedString, - byteData.cast().toDartString(), - ), - REG_VALUE_TYPE.REG_LINK => RegistryValue( - name, - RegistryValueType.link, - byteData.cast().toDartString(), - ), - REG_VALUE_TYPE.REG_MULTI_SZ => RegistryValue( - name, - RegistryValueType.stringArray, - byteData.cast().unpackStringArray(dataLength), - ), - REG_VALUE_TYPE.REG_DWORD => RegistryValue( - name, - RegistryValueType.int32, - byteData.cast().value, - ), - REG_VALUE_TYPE.REG_QWORD => RegistryValue( - name, - RegistryValueType.int64, - byteData.cast().value, - ), - REG_VALUE_TYPE.REG_BINARY => RegistryValue( - name, - RegistryValueType.binary, - Uint8List.fromList(byteData.asTypedList(dataLength)), - ), - REG_VALUE_TYPE.REG_NONE => - RegistryValue(name, RegistryValueType.none, 0), - _ => RegistryValue(name, RegistryValueType.unknown, 0) - }; - - /// Converts the [RegistryValue] to Win32-compatible data. - PointerData get toWin32 { - switch (type) { - case RegistryValueType.int32: - final ptr = calloc()..value = data as int; - return PointerData(ptr.cast(), sizeOf()); - case RegistryValueType.int64: - final ptr = calloc()..value = data as int; - return PointerData(ptr.cast(), sizeOf()); - case RegistryValueType.string: - case RegistryValueType.unexpandedString: - case RegistryValueType.link: - final strData = data as String; - final ptr = strData.toNativeUtf16(); - // Reserve 2 bytes per character (UTF-16 encoding) and 2 extra bytes for - // the null terminator. - return PointerData(ptr.cast(), strData.length * 2 + 2); - case RegistryValueType.stringArray: - final strArray = (data as List).map((s) => '$s\x00').join(); - final ptr = strArray.toNativeUtf16(); - return PointerData(ptr.cast(), strArray.length * 2); - case RegistryValueType.binary: - final dataList = Uint8List.fromList(data as List); - final ptr = dataList.allocatePointer(); - return PointerData(ptr, dataList.length); - case RegistryValueType.none: - case RegistryValueType.unknown: - return PointerData(nullptr, 0); - } + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! BinaryValue) return false; + + // Compare the list contents for equality. + return value.length == other.value.length && + value.every((file) => other.value.contains(file)); } + @override + int get hashCode => Object.hash(name, Object.hashAll(value)); + + @override + String toString() => 'BinaryValue($name, $value)'; +} + +/// Represents a 32-bit integer value in the Windows Registry, corresponding to +/// the `REG_DWORD` type. +final class Int32Value extends RegistryValue { + /// Constructs a [Int32Value] with the specified [name] and [value]. + const Int32Value(String name, this.value) + : super._(name, RegistryValueType.int32); + + /// The 32-bit integer value. + final int value; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Int32Value && name == other.name && value == other.value; + + @override + int get hashCode => Object.hash(name, value); + + @override + String toString() => 'Int32Value($name, $value)'; +} + +/// Represents a 64-bit integer value in the Windows Registry, corresponding to +/// the `REG_QWORD` type. +final class Int64Value extends RegistryValue { + /// Constructs a [Int64Value] with the specified [name] and [value]. + const Int64Value(String name, this.value) + : super._(name, RegistryValueType.int64); + + /// The 64-bit integer value. + final int value; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Int64Value && name == other.name && value == other.value; + + @override + int get hashCode => Object.hash(name, value); + + @override + String toString() => 'Int64Value($name, $value)'; +} + +/// Represents a symbolic link value in the Windows Registry, corresponding to +/// the `REG_LINK` type. +final class LinkValue extends RegistryValue { + /// Constructs a [LinkValue] with the specified [name] and [value]. + const LinkValue(String name, this.value) + : super._(name, RegistryValueType.link); + + /// The symbolic link value as a string. + final String value; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is LinkValue && name == other.name && value == other.value; + + @override + int get hashCode => Object.hash(name, value); + + @override + String toString() => 'LinkValue($name, $value)'; +} + +/// Represents a null value in the Windows Registry, corresponding to the +/// `REG_NONE` type. +final class NoneValue extends RegistryValue { + /// Constructs a [NoneValue] with the specified [name]. + const NoneValue(String name) : super._(name, RegistryValueType.none); + + @override + bool operator ==(Object other) => + identical(this, other) || other is NoneValue && name == other.name; + + @override + int get hashCode => name.hashCode; + + @override + String toString() => 'NoneValue($name)'; +} + +/// Represents a string value in the Windows Registry, corresponding to the +/// `REG_SZ` type. +final class StringValue extends RegistryValue { + /// Constructs a [StringValue] with the specified [name] and [value]. + const StringValue(String name, this.value) + : super._(name, RegistryValueType.string); + + /// The string value. + final String value; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is StringValue && name == other.name && value == other.value; + + @override + int get hashCode => Object.hash(name, value); + + @override + String toString() => 'StringValue($name, $value)'; +} + +/// Represents an array of string values in the Windows Registry, corresponding +/// to the `REG_MULTI_SZ` type. +final class StringArrayValue extends RegistryValue { + /// Constructs a [StringArrayValue] with the specified [name] and [value]. + const StringArrayValue(String name, this.value) + : super._(name, RegistryValueType.stringArray); + + /// The list of string values. + final List value; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! StringArrayValue) return false; + + // Compare the list contents for equality. + return value.length == other.value.length && + value.every((file) => other.value.contains(file)); + } + + @override + int get hashCode => Object.hash(name, Object.hashAll(value)); + + @override + String toString() => 'StringArrayValue($name, $value)'; +} + +/// Represents an unexpanded string value in the Windows Registry, corresponding +/// to the `REG_EXPAND_SZ` type. +final class UnexpandedStringValue extends RegistryValue { + /// Constructs a [UnexpandedStringValue] with the specified [name] and + /// [value]. + const UnexpandedStringValue(String name, this.value) + : super._(name, RegistryValueType.unexpandedString); + + /// The unexpanded string value. + final String value; + @override bool operator ==(Object other) => - other is RegistryValue && - other.name == name && - other.type == type && - other.data == data; + identical(this, other) || + other is UnexpandedStringValue && + name == other.name && + value == other.value; @override - int get hashCode => name.hashCode * data.hashCode; + int get hashCode => Object.hash(name, value); @override - String toString() => '$name\t$type\t$data'; + String toString() => 'UnexpandedStringValue($name, $value)'; } diff --git a/lib/src/registry_value_type.dart b/lib/src/registry_value_type.dart new file mode 100644 index 0000000..73b2afc --- /dev/null +++ b/lib/src/registry_value_type.dart @@ -0,0 +1,77 @@ +import 'package:win32/win32.dart'; + +/// Represents a data type stored within the Windows Registry. +/// +/// These types define the kinds of data entities that the registry can store +/// and handle, although they do not directly correspond to specific Win32 or +/// Dart types. +/// +/// For more information on these registry value types, refer to: +/// https://learn.microsoft.com/windows/win32/sysinfo/registry-value-types +enum RegistryValueType { + /// No specific data type. + none._(REG_VALUE_TYPE.REG_NONE), + + /// A null-terminated string, equivalent to the Windows API type `REG_SZ`. + string._(REG_VALUE_TYPE.REG_SZ), + + /// A null-terminated string with unexpanded references to environment + /// variables (e.g., %PATH%), expanded when retrieved. + /// + /// Corresponds to `REG_EXPAND_SZ`. + unexpandedString._(REG_VALUE_TYPE.REG_EXPAND_SZ), + + /// Binary data of arbitrary format, matching the Windows API type + /// `REG_BINARY`. + binary._(REG_VALUE_TYPE.REG_BINARY), + + /// A 32-bit unsigned integer, represented by `REG_DWORD`. + int32._(REG_VALUE_TYPE.REG_DWORD), + + /// A null-terminated string indicating the target path of a symbolic link, + /// equivalent to `REG_LINK`. + link._(REG_VALUE_TYPE.REG_LINK), + + /// An array of null-terminated strings, terminated by an additional null + /// character. + /// + /// Corresponds to `REG_MULTI_SZ`. + stringArray._(REG_VALUE_TYPE.REG_MULTI_SZ), + + /// A 64-bit unsigned integer, represented by `REG_QWORD`. + int64._(REG_VALUE_TYPE.REG_QWORD); + + const RegistryValueType._(this.value); + + /// Creates a [RegistryValueType] from a Win32 registry data type value. + factory RegistryValueType.fromWin32(int value) => switch (value) { + REG_VALUE_TYPE.REG_NONE => none, + REG_VALUE_TYPE.REG_SZ => string, + REG_VALUE_TYPE.REG_EXPAND_SZ => unexpandedString, + REG_VALUE_TYPE.REG_BINARY => binary, + REG_VALUE_TYPE.REG_DWORD => int32, + REG_VALUE_TYPE.REG_LINK => link, + REG_VALUE_TYPE.REG_MULTI_SZ => stringArray, + REG_VALUE_TYPE.REG_QWORD => int64, + _ => throw ArgumentError.value( + value, + 'value', + 'Unknown registry value type: $value', + ), + }; + + /// The integer value representing the underlying Win32 registry type. + final int value; + + @override + String toString() => switch (this) { + none => 'REG_NONE', + string => 'REG_SZ', + unexpandedString => 'REG_EXPAND_SZ', + binary => 'REG_BINARY', + int32 => 'REG_DWORD', + link => 'REG_LINK', + stringArray => 'REG_MULTI_SZ', + int64 => 'REG_QWORD', + }; +} diff --git a/lib/src/utils.dart b/lib/src/utils.dart index b85668e..d5d2279 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,27 +1,131 @@ import 'dart:ffi'; +import 'dart:typed_data'; import 'package:ffi/ffi.dart'; +import 'package:meta/meta.dart'; import 'package:win32/win32.dart'; -/// Convert a Win32 `FILETIME` struct into its Dart equivalent. -DateTime? convertToDartDateTime(Pointer lpFileTime) { - if (lpFileTime == nullptr) return null; +import 'registry_value.dart'; +import 'registry_value_type.dart'; - final lpSystemTime = calloc(); - try { - final result = FileTimeToSystemTime(lpFileTime, lpSystemTime); - if (result == FALSE) return null; +@internal +extension FILETIMEPointerExtension on Pointer { + /// Converts the [FILETIME] structure to its Dart equivalent [DateTime]. + /// + /// Returns a [DateTime] object representing the date and time, or `null` if + /// the pointer is `nullptr` or the conversion fails. + @internal + DateTime? toDateTime() { + if (this == nullptr) return null; - final systemTime = lpSystemTime.ref; - return DateTime.utc( + final lpSystemTime = calloc(); + try { + final result = FileTimeToSystemTime(this, lpSystemTime); + if (result == FALSE) return null; + + final systemTime = lpSystemTime.ref; + return DateTime.utc( systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, systemTime.wSecond, - systemTime.wMilliseconds); - } finally { - free(lpSystemTime); + systemTime.wMilliseconds, + ); + } finally { + free(lpSystemTime); + } } } + +/// A class representing pointer data and its associated length in bytes. +@internal +final class PointerData { + /// Creates an instance of [PointerData] with the given pointer [data] and + /// its [lengthInBytes]. + const PointerData(this.data, this.lengthInBytes); + + /// The pointer to the data. + final Pointer data; + + /// The length of the data in bytes. + final int lengthInBytes; + + @override + String toString() => 'PointerData($data, $lengthInBytes)'; +} + +@internal +extension RegistryValueExtension on RegistryValue { + /// Converts the [RegistryValue] to a Win32-compatible data format. + /// + /// This method transforms the current registry value into a format that can + /// be used by Win32 APIs, returning a [PointerData] instance containing th + /// pointer to the data and its length in bytes. + @internal + PointerData toWin32() => switch (this) { + BinaryValue(:final value) => + PointerData(value.allocatePointer(), value.length), + Int32Value(:final value) => PointerData( + (calloc()..value = value).cast(), sizeOf()), + Int64Value(:final value) => PointerData( + (calloc()..value = value).cast(), sizeOf()), + NoneValue() => PointerData(nullptr, 0), + LinkValue(:final value) || + StringValue(:final value) || + UnexpandedStringValue(:final value) => + PointerData( + value.toNativeUtf16().cast(), + // Reserve 2 bytes per character (UTF-16 encoding) and 2 extra bytes + // for the null terminator. + value.length * 2 + 2, + ), + StringArrayValue(:final value) => PointerData( + value.toWideCharArray().cast(), + value.fold( + 2, // 2 bytes for the null terminator at the end of the array. + (prev, element) => prev + element.length * 2 + 2, + ), + ), + }; +} + +@internal +extension RegistryValueTypeExtension on RegistryValueType { + /// Converts the [RegistryValueType] to its corresponding [RegistryValue]. + /// + /// This method constructs a [RegistryValue] instance based on the type of the + /// registry value and the provided data. + /// + /// - [name]: The name of the registry value. + /// - [data]: A pointer to the data represented as `Pointer`. + /// - [dataLength]: The length of the data in bytes. + @internal + RegistryValue toRegistryValue( + String name, + Pointer data, + int dataLength, + ) => + switch (this) { + RegistryValueType.none => NoneValue(name), + RegistryValueType.string => + StringValue(name, data.cast().toDartString()), + RegistryValueType.unexpandedString => + UnexpandedStringValue(name, data.cast().toDartString()), + RegistryValueType.binary => BinaryValue( + name, + // Copy the data to a new Uint8List as the original pointer will be + // freed when the function exits. + Uint8List.fromList(data.asTypedList(dataLength)), + ), + RegistryValueType.int32 => Int32Value(name, data.cast().value), + RegistryValueType.stringArray => StringArrayValue( + name, + data.cast().unpackStringArray((dataLength - 2) ~/ 2), + ), + RegistryValueType.link => + LinkValue(name, data.cast().toDartString()), + RegistryValueType.int64 => Int64Value(name, data.cast().value), + }; +} diff --git a/lib/win32_registry.dart b/lib/win32_registry.dart index bfcc0ab..c693cc1 100644 --- a/lib/win32_registry.dart +++ b/lib/win32_registry.dart @@ -1,36 +1,16 @@ -/// A Dart library that provides support for querying and accessing the Windows -/// Registry from Dart, with idiomatic classes and collections of objects that -/// facilitate reading and writing values. +/// A Dart library for accessing and managing the Windows Registry, providing +/// easy-to-use classes and collections to read, write, and monitor registry +/// values. /// -/// The Windows Registry is a system-defined database in which applications and -/// system components store and retrieve configuration data. The data stored in -/// the registry varies according to the version of Microsoft Windows. -/// Applications use the registry API to retrieve, modify, or delete registry -/// data. -/// -/// For example: -/// -/// ```dart -/// import 'package:win32_registry/win32_registry.dart'; -/// -/// void main() { -/// const keyPath = r'Software\Microsoft\Windows NT\CurrentVersion'; -/// final key = Registry.openPath(RegistryHive.localMachine, path: keyPath); -/// -/// final buildNumber = key.getValueAsString('CurrentBuild'); -/// if (buildNumber != null) { -/// print('Windows build number: $buildNumber'); -/// } -/// -/// key.close(); -/// } -/// ``` -/// -/// The example demonstrates how to use this library to open a registry key, -/// retrieve a value, and print it to the console. +/// The Windows Registry stores configuration data for applications and system +/// components. This library allows applications to interact with the registry, +/// supporting data retrieval, modification, deletion, and monitoring of +/// registry changes to react to updates made by other processes or system +/// components. library; -export 'src/models/models.dart'; export 'src/registry.dart'; +export 'src/registry_hive.dart'; export 'src/registry_key.dart'; export 'src/registry_value.dart'; +export 'src/registry_value_type.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 783850c..936d49a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: win32_registry description: A package that provides a friendly Dart API for accessing the Windows Registry. -version: 1.1.5 +version: 2.0.0 repository: https://github.com/halildurmus/win32_registry issue_tracker: https://github.com/halildurmus/win32_registry/issues @@ -12,16 +12,17 @@ topics: - windows environment: - sdk: ^3.4.0 + sdk: ^3.5.0 platforms: windows: dependencies: ffi: ^2.1.3 - win32: ^5.5.4 + meta: ^1.16.0 + win32: ^5.8.0 dev_dependencies: checks: ^0.3.0 - lints: ">=4.0.0 <6.0.0" + lints: ^5.0.0 test: ^1.25.8 diff --git a/test/registry_hive_test.dart b/test/registry_hive_test.dart new file mode 100644 index 0000000..394ff78 --- /dev/null +++ b/test/registry_hive_test.dart @@ -0,0 +1,62 @@ +import 'package:checks/checks.dart'; +import 'package:test/scaffolding.dart'; +import 'package:win32/win32.dart'; +import 'package:win32_registry/win32_registry.dart'; + +void main() { + group('RegistryHive', () { + test('classesRoot', () { + const classesRoot = RegistryHive.classesRoot; + check(classesRoot.value).equals(HKEY_CLASSES_ROOT); + check(classesRoot.toString()).equals('HKEY_CLASSES_ROOT'); + }); + + test('currentUser', () { + const currentUser = RegistryHive.currentUser; + check(currentUser.value).equals(HKEY_CURRENT_USER); + check(currentUser.toString()).equals('HKEY_CURRENT_USER'); + }); + + test('localMachine', () { + const localMachine = RegistryHive.localMachine; + check(localMachine.value).equals(HKEY_LOCAL_MACHINE); + check(localMachine.toString()).equals('HKEY_LOCAL_MACHINE'); + }); + + test('allUsers', () { + const allUsers = RegistryHive.allUsers; + check(allUsers.value).equals(HKEY_USERS); + check(allUsers.toString()).equals('HKEY_USERS'); + }); + + test('performanceData', () { + const performanceData = RegistryHive.performanceData; + check(performanceData.value).equals(HKEY_PERFORMANCE_DATA); + check(performanceData.toString()).equals('HKEY_PERFORMANCE_DATA'); + }); + + test('currentConfig', () { + const currentConfig = RegistryHive.currentConfig; + check(currentConfig.value).equals(HKEY_CURRENT_CONFIG); + check(currentConfig.toString()).equals('HKEY_CURRENT_CONFIG'); + }); + + test('fromWin32', () { + check(RegistryHive.fromWin32(HKEY_CLASSES_ROOT)) + .equals(RegistryHive.classesRoot); + check(RegistryHive.fromWin32(HKEY_CURRENT_USER)) + .equals(RegistryHive.currentUser); + check(RegistryHive.fromWin32(HKEY_LOCAL_MACHINE)) + .equals(RegistryHive.localMachine); + check(RegistryHive.fromWin32(HKEY_USERS)).equals(RegistryHive.allUsers); + check(RegistryHive.fromWin32(HKEY_PERFORMANCE_DATA)) + .equals(RegistryHive.performanceData); + check(RegistryHive.fromWin32(HKEY_CURRENT_CONFIG)) + .equals(RegistryHive.currentConfig); + check(() => RegistryHive.fromWin32(0)) + .throws() + .has((e) => e.message, 'message') + .equals('Unknown registry hive: 0'); + }); + }); +} diff --git a/test/registry_key_test.dart b/test/registry_key_test.dart new file mode 100644 index 0000000..7ecbee4 --- /dev/null +++ b/test/registry_key_test.dart @@ -0,0 +1,425 @@ +@TestOn('windows') +library; + +import 'dart:ffi'; +import 'dart:typed_data'; + +import 'package:checks/checks.dart'; +import 'package:test/scaffolding.dart'; +import 'package:win32/win32.dart'; +import 'package:win32_registry/src/utils.dart'; +import 'package:win32_registry/win32_registry.dart'; + +void main() { + group('RegistryKey', () { + group('getValue', () { + test('binary', () { + const keyPath = r'Control Panel\Desktop\WindowMetrics'; + final windowMetrics = + Registry.openPath(RegistryHive.currentUser, path: keyPath); + final iconFont = windowMetrics.getValue('IconFont'); + check(iconFont).isNotNull(); + final type = iconFont!.type; + check(type).equals(RegistryValueType.binary); + check(type.value).equals(REG_VALUE_TYPE.REG_BINARY); + check(type.toString()).equals('REG_BINARY'); + check(windowMetrics.getBinaryValue('IconFont')).isNotNull(); + windowMetrics.close(); + }); + + test('int32', () { + final console = + Registry.openPath(RegistryHive.currentUser, path: 'Console'); + final fullScreen = console.getValue('FullScreen'); + check(fullScreen).isNotNull(); + final type = fullScreen!.type; + check(type).equals(RegistryValueType.int32); + check(type.value).equals(REG_VALUE_TYPE.REG_DWORD); + check(type.toString()).equals('REG_DWORD'); + check(console.getIntValue('FullScreen')).isNotNull(); + console.close(); + }); + + test('int64', () { + const keyPath = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion'; + final ntCurrentVersion = + Registry.openPath(RegistryHive.localMachine, path: keyPath); + final installTime = ntCurrentVersion.getValue('InstallTime'); + check(installTime).isNotNull(); + final type = installTime!.type; + check(type).equals(RegistryValueType.int64); + check(type.value).equals(REG_VALUE_TYPE.REG_QWORD); + check(type.toString()).equals('REG_QWORD'); + check(ntCurrentVersion.getIntValue('InstallTime')).isNotNull(); + ntCurrentVersion.close(); + }); + + test('string', () { + const keyPath = r'SOFTWARE\Microsoft\Windows\CurrentVersion'; + final currentVersion = + Registry.openPath(RegistryHive.localMachine, path: keyPath); + final programFilesDir = currentVersion.getValue('ProgramFilesDir'); + check(programFilesDir).isNotNull(); + final type = programFilesDir!.type; + check(type).equals(RegistryValueType.string); + check(type.value).equals(REG_VALUE_TYPE.REG_SZ); + check(type.toString()).equals('REG_SZ'); + check(currentVersion.getStringValue('ProgramFilesDir')).isNotNull(); + currentVersion.close(); + }); + + test('unexpanded string', () { + final env = + Registry.openPath(RegistryHive.currentUser, path: 'Environment'); + final path = env.getValue('Path'); + check(path).isNotNull(); + final type = path!.type; + check(type).equals(RegistryValueType.unexpandedString); + check(type.value).equals(REG_VALUE_TYPE.REG_EXPAND_SZ); + check(type.toString()).equals('REG_EXPAND_SZ'); + check(env.getStringValue('Path')).isNotNull(); + env.close(); + }); + + test('missing value returns null', () { + const keyPath = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion'; + final currentVersion = + Registry.openPath(RegistryHive.localMachine, path: keyPath); + check(currentVersion.getValue('NotepadPlusPlus')).isNull(); + currentVersion.close(); + }); + }); + + test('create a subkey', () { + final hkcu = Registry.currentUser; + const subkeyName = 'Win32RegistryTestKey'; + check(hkcu.subkeyNames.contains(subkeyName)).isFalse(); + final subkey = hkcu.createKey(subkeyName); + check(hkcu.subkeyNames.contains(subkeyName)).isTrue(); + subkey.close(); + hkcu.deleteKey(subkeyName); + check(hkcu.subkeyNames.contains(subkeyName)).isFalse(); + hkcu.close(); + }); + + test('create a subkey without write access should fail', () { + final hkcu = Registry.openPath(RegistryHive.localMachine); + const subkeyName = 'Win32RegistryTestKey'; + check(hkcu.subkeyNames.contains(subkeyName)).isFalse(); + check(() => hkcu.createKey(subkeyName)).throws(); + check(hkcu.subkeyNames.contains(subkeyName)).isFalse(); + hkcu.close(); + }); + + test('rename a subkey', () { + final hkcu = Registry.openPath(RegistryHive.currentUser); + final subkey = hkcu.createKey('Win32RegistryTestKey'); + check(hkcu.subkeyNames.contains('Win32RegistryTestKey')).isTrue(); + hkcu.renameSubkey('Win32RegistryTestKey', 'MyNewTestKey'); + check(hkcu.subkeyNames.contains('Win32RegistryTestKey')).isFalse(); + check(hkcu.subkeyNames.contains('MyNewTestKey')).isTrue(); + subkey.close(); + hkcu.deleteKey('MyNewTestKey'); + check(hkcu.subkeyNames.contains('Win32RegistryTestKey')).isFalse(); + check(hkcu.subkeyNames.contains('MyNewTestKey')).isFalse(); + hkcu.close(); + }); + + test('create a binary value (REG_BINARY)', () { + final hkcu = Registry.openPath(RegistryHive.currentUser); + const subkeyName = 'Win32RegistryTestKey'; + final subkey = hkcu.createKey(subkeyName); + final registryValue = RegistryValue.binary( + 'TestValue', Uint8List.fromList([0xFF, 0x33, 0x77, 0xAA])); + subkey.createValue(registryValue); + final retrievedValue = subkey.getValue('TestValue'); + check(retrievedValue).isNotNull(); + check(retrievedValue).isA(); + final BinaryValue(:name, :type, :value) = retrievedValue! as BinaryValue; + check(name).equals('TestValue'); + check(type).equals(RegistryValueType.binary); + check(value.length).equals(4); + check(value).deepEquals([0xFF, 0x33, 0x77, 0xAA]); + check(subkey.getBinaryValue('TestValue')!).deepEquals(value); + subkey.deleteValue('TestValue'); + check(subkey.values.where((e) => e.name == 'TestValue')).isEmpty(); + subkey.close(); + hkcu + ..deleteKey(subkeyName) + ..close(); + }); + + test('create a int32 value (REG_DWORD)', () { + final hkcu = Registry.currentUser; + const subkeyName = 'Win32RegistryTestKey'; + final subkey = hkcu.createKey(subkeyName); + const value = RegistryValue.int32('TestValue', 1234); + subkey.createValue(value); + check(subkey.values.where((v) => v.name == 'TestValue').first) + .equals(value); + check(subkey.getIntValue('TestValue')).equals(1234); + subkey.deleteValue('TestValue'); + check(subkey.values.where((e) => e.name == 'TestValue')).isEmpty(); + subkey.close(); + hkcu + ..deleteKey(subkeyName) + ..close(); + }); + + test('create a int64 value (REG_QWORD)', () { + final hkcu = Registry.openPath(RegistryHive.currentUser); + const subkeyName = 'Win32RegistryTestKey'; + final subkey = hkcu.createKey(subkeyName); + const registryValue = + RegistryValue.int64('TestValue', 0xFEEDFACECAFEBEEF); + subkey.createValue(registryValue); + final retrievedValue = subkey.getValue('TestValue'); + check(retrievedValue).isNotNull(); + check(retrievedValue).isA(); + final Int64Value(:name, :type, :value) = retrievedValue! as Int64Value; + check(name).equals('TestValue'); + check(type).equals(RegistryValueType.int64); + check(value).equals(0xFEEDFACECAFEBEEF); + check(subkey.getIntValue('TestValue')).equals(0xFEEDFACECAFEBEEF); + subkey.deleteValue('TestValue'); + check(subkey.values.where((e) => e.name == 'TestValue')).isEmpty(); + subkey.close(); + hkcu + ..deleteKey(subkeyName) + ..close(); + }); + + test('create a link value (REG_LINK)', () { + final hkcu = Registry.openPath(RegistryHive.currentUser); + const subkeyName = 'Win32RegistryTestKey'; + final subkey = hkcu.createKey(subkeyName); + const registryValue = RegistryValue.link('TestValue', r'C:\Windows\Temp'); + subkey.createValue(registryValue); + final retrievedValue = subkey.getValue('TestValue'); + check(retrievedValue).isNotNull(); + check(retrievedValue).isA(); + final LinkValue(:name, :type, :value) = retrievedValue! as LinkValue; + check(name).equals('TestValue'); + check(type).equals(RegistryValueType.link); + check(value).equals(r'C:\Windows\Temp'); + check(subkey.getStringValue('TestValue')).equals(r'C:\Windows\Temp'); + subkey.deleteValue('TestValue'); + check(subkey.values.where((e) => e.name == 'TestValue')).isEmpty(); + subkey.close(); + hkcu + ..deleteKey(subkeyName) + ..close(); + }); + + test('create a none value (REG_NONE)', () { + final hkcu = Registry.openPath(RegistryHive.currentUser); + const subkeyName = 'Win32RegistryTestKey'; + final subkey = hkcu.createKey(subkeyName); + const registryValue = RegistryValue.none('TestValue'); + subkey.createValue(registryValue); + final retrievedValue = subkey.getValue('TestValue'); + check(retrievedValue).isNotNull(); + check(retrievedValue).isA(); + final NoneValue(:name, :type) = retrievedValue! as NoneValue; + check(name).equals('TestValue'); + check(type).equals(RegistryValueType.none); + subkey.deleteValue('TestValue'); + check(subkey.values.where((e) => e.name == 'TestValue')).isEmpty(); + subkey.close(); + hkcu + ..deleteKey(subkeyName) + ..close(); + }); + + test('create a string value (REG_SZ)', () { + final hkcu = Registry.openPath(RegistryHive.currentUser); + const subkeyName = 'Win32RegistryTestKey'; + final subkey = hkcu.createKey(subkeyName); + const registryValue = + RegistryValue.string('TestValue', 'Some text here.'); + final pointerData = registryValue.toWin32(); + check(pointerData.lengthInBytes).equals(sizeOf() * 16); + free(pointerData.data); + subkey.createValue(registryValue); + final retrievedValue = subkey.getValue('TestValue'); + check(retrievedValue).isNotNull(); + check(retrievedValue).isA(); + final StringValue(:name, :type, :value) = retrievedValue! as StringValue; + check(name).equals('TestValue'); + check(type).equals(RegistryValueType.string); + check(value).equals('Some text here.'); + check(subkey.getStringValue('TestValue')).equals('Some text here.'); + subkey.deleteValue('TestValue'); + check(subkey.values.where((e) => e.name == 'TestValue')).isEmpty(); + subkey.close(); + hkcu + ..deleteKey(subkeyName) + ..close(); + }); + + test('create a string array (REG_MULTI_SZ)', () { + final hkcu = Registry.openPath(RegistryHive.currentUser); + const subkeyName = 'Win32RegistryTestKey'; + final subkey = hkcu.createKey(subkeyName); + const registryValue = + RegistryValue.stringArray('TestValue', ['One', 'Two', 'Three']); + final pointerData = registryValue.toWin32(); + check(pointerData.lengthInBytes).equals(sizeOf() * 15); + free(pointerData.data); + subkey.createValue(registryValue); + final retrievedValue = subkey.getValue('TestValue'); + check(retrievedValue).isNotNull(); + check(retrievedValue).isA(); + final StringArrayValue(:name, :type, :value) = + retrievedValue! as StringArrayValue; + check(name).equals('TestValue'); + check(type).equals(RegistryValueType.stringArray); + check(value.length).equals(3); + check(value).deepEquals(['One', 'Two', 'Three']); + check(subkey.getStringArrayValue('TestValue')!).deepEquals(value); + subkey.deleteValue('TestValue'); + check(subkey.values.where((e) => e.name == 'TestValue')).isEmpty(); + subkey.close(); + hkcu + ..deleteKey(subkeyName) + ..close(); + }); + + test('create an unexpanded string value (REG_EXPAND_SZ)', () { + final hkcu = Registry.openPath(RegistryHive.currentUser); + const subkeyName = 'Win32RegistryTestKey'; + final subkey = hkcu.createKey(subkeyName); + const value = RegistryValue.unexpandedString( + 'TestValue', + r'%LOCALAPPDATA%\win32_registry\win32_registry.dart', + ); + final pointerData = value.toWin32(); + check(pointerData.lengthInBytes).equals(sizeOf() * 50); + free(pointerData.data); + subkey.createValue(value); + final retrievedValue = subkey.getValue('TestValue'); + check(retrievedValue).isNotNull(); + check(retrievedValue!.type).equals(RegistryValueType.unexpandedString); + + // Unexpanded should be as stored. + check(subkey.getStringValue('TestValue')) + .equals(r'%LOCALAPPDATA%\win32_registry\win32_registry.dart'); + + // Expanded should replace %LOCALAPPDATA% with an actual system path. + check(subkey.getStringValue('TestValue', expandPaths: true)!) + .which((it) => it + ..not((it) => it.contains('%LOCALAPPDATA%')) + ..endsWith(r'\win32_registry\win32_registry.dart')); + + subkey.deleteValue('TestValue'); + check(subkey.values.where((e) => e.name == 'TestValue')).isEmpty(); + subkey.close(); + hkcu + ..deleteKey(subkeyName) + ..close(); + }); + + test('monitor registry key changes', () async { + final hkcu = Registry.openPath(RegistryHive.currentUser); + const keyName = 'Win32RegistryTestKey'; + final key = hkcu.createKey(keyName); + const subkeyName = 'Win32RegistryTestSubKey'; + final subkey = key.createKey(subkeyName); + + var numberOfEvents = 0; + final subscription = key.onChanged().listen((_) => numberOfEvents++); + + // Allow time for the subscription to be set up. + await Future.delayed(const Duration(milliseconds: 50)); + + // Set some string value to the registry to simulate a change. + const value = RegistryValue.string('TestValue', 'Some text here.'); + key.createValue(value); + + // Yield control to the Dart event loop to allow time for the registry key + // change to be detected. + await Future.delayed(const Duration(milliseconds: 10)); + + // Expect the stream to emit exactly one event. + check(numberOfEvents).equals(1); + + // Set some int value to the registry to simulate another change. + const intValue = RegistryValue.int32('TestIntValue', 1234); + key.createValue(intValue); + await Future.delayed(const Duration(milliseconds: 10)); + + // Expect the stream to emit exactly one event. + check(numberOfEvents).equals(2); + + // Set some string value to the subkey. + subkey.createValue(value); + await Future.delayed(const Duration(milliseconds: 10)); + + // The changes in the subkey should not trigger an event. + check(numberOfEvents).equals(2); + + await subscription.cancel(); + key.deleteValue('TestValue'); + check(key.values.where((e) => e.name == 'TestValue')).isEmpty(); + subkey.close(); + key.deleteKey(subkeyName); + check(key.subkeyNames.contains(subkeyName)).isFalse(); + key.close(); + hkcu + ..deleteKey(keyName) + ..close(); + }); + + test('monitor registry key changes (including subkey)', () async { + final hkcu = Registry.openPath(RegistryHive.currentUser); + const keyName = 'Win32RegistryTestKey'; + final key = hkcu.createKey(keyName); + const subkeyName = 'Win32RegistryTestSubKey'; + final subkey = key.createKey(subkeyName); + + var numberOfEvents = 0; + final subscription = + key.onChanged(includeSubkeys: true).listen((_) => numberOfEvents++); + + // Allow time for the subscription to be set up. + await Future.delayed(const Duration(milliseconds: 50)); + + // Set some string value to the registry to simulate a change. + const value = RegistryValue.string('TestValue', 'Some text here.'); + key.createValue(value); + + // Yield control to the Dart event loop to allow time for the registry key + // change to be detected. + await Future.delayed(const Duration(milliseconds: 10)); + + // Expect the stream to emit exactly one event. + check(numberOfEvents).equals(1); + + // Set some int value to the registry to simulate another change. + const intValue = RegistryValue.int32('TestIntValue', 1234); + key.createValue(intValue); + await Future.delayed(const Duration(milliseconds: 10)); + + // Expect the stream to emit exactly one event. + check(numberOfEvents).equals(2); + + // Set some string value to the subkey to simulate another change. + subkey.createValue(value); + await Future.delayed(const Duration(milliseconds: 10)); + + // The changes in the subkey should trigger an event. + check(numberOfEvents).equals(3); + + await subscription.cancel(); + key.deleteValue('TestValue'); + check(key.values.where((e) => e.name == 'TestValue')).isEmpty(); + subkey.close(); + key.deleteKey(subkeyName); + check(key.subkeyNames.contains(subkeyName)).isFalse(); + key.close(); + hkcu + ..deleteKey(keyName) + ..close(); + }); + }); +} diff --git a/test/registry_test.dart b/test/registry_test.dart new file mode 100644 index 0000000..4260a85 --- /dev/null +++ b/test/registry_test.dart @@ -0,0 +1,64 @@ +@TestOn('windows') +library; + +import 'package:checks/checks.dart'; +import 'package:test/scaffolding.dart'; +import 'package:win32/win32.dart'; +import 'package:win32_registry/win32_registry.dart'; + +void main() { + group('Registry', () { + group('openPath', () { + test('opens a registry key successfully', () { + // In any working Windows configuration, this key should have a sizable + // number of values. + const keyPath = r'Software\Microsoft\Windows NT\CurrentVersion'; + final key = Registry.openPath(RegistryHive.localMachine, path: keyPath); + check(key.values.length).isGreaterThan(0); + key.close(); + }); + + test('throws due to missing key', () { + check( + () => Registry.openPath( + RegistryHive.localMachine, + path: r'SOFTWARE\Dart\Missing\Key', + ), + ) + .throws() + .has((e) => e.hr, 'hr') + .equals(HRESULT_FROM_WIN32(WIN32_ERROR.ERROR_FILE_NOT_FOUND)); + }); + }); + + test('allUsers', () { + late RegistryKey key; + check(() => key = Registry.allUsers).returnsNormally(); + key.close(); + }); + + test('classesRoot', () { + late RegistryKey key; + check(() => key = Registry.classesRoot).returnsNormally(); + key.close(); + }); + + test('currentConfig', () { + late RegistryKey key; + check(() => key = Registry.currentConfig).returnsNormally(); + key.close(); + }); + + test('currentUser', () { + late RegistryKey key; + check(() => key = Registry.currentUser).returnsNormally(); + key.close(); + }); + + test('localMachine', () { + late RegistryKey key; + check(() => key = Registry.localMachine).returnsNormally(); + key.close(); + }); + }); +} diff --git a/test/registry_value_test.dart b/test/registry_value_test.dart new file mode 100644 index 0000000..c1c7594 --- /dev/null +++ b/test/registry_value_test.dart @@ -0,0 +1,115 @@ +import 'dart:typed_data'; + +import 'package:checks/checks.dart'; +import 'package:test/scaffolding.dart'; +import 'package:win32_registry/win32_registry.dart'; + +void main() { + group('RegistryValue', () { + test('BinaryValue', () { + final value1 = BinaryValue( + 'Test', + Uint8List.fromList([0xFF, 0x33, 0x77, 0xAA]), + ); + check(value1.name).equals('Test'); + check(value1.type).equals(RegistryValueType.binary); + check(value1.value) + .deepEquals(Uint8List.fromList([0xFF, 0x33, 0x77, 0xAA])); + check(value1.toString()).equals('BinaryValue(Test, [255, 51, 119, 170])'); + final value2 = BinaryValue( + 'Test', + Uint8List.fromList([0xFF, 0x33, 0x77, 0xAA]), + ); + check(value1).equals(value2); + final value3 = BinaryValue( + 'Test', + Uint8List.fromList([0xFF, 0x33, 0x77, 0xAB]), + ); + check(value1).not((it) => it.equals(value3)); + }); + + test('Int32Value', () { + const value1 = Int32Value('Test', 1234); + check(value1.name).equals('Test'); + check(value1.type).equals(RegistryValueType.int32); + check(value1.value).equals(1234); + check(value1.toString()).equals('Int32Value(Test, 1234)'); + const value2 = Int32Value('Test', 1234); + check(value1).equals(value2); + const value3 = Int32Value('Test', 1235); + check(value1).not((it) => it.equals(value3)); + }); + + test('Int64Value', () { + const value1 = Int64Value('Test', 0xFEEDFACECAFEBEEF); + check(value1.name).equals('Test'); + check(value1.type).equals(RegistryValueType.int64); + check(value1.value).equals(0xFEEDFACECAFEBEEF); + check(value1.toString()).equals('Int64Value(Test, -77129852519530769)'); + const value2 = Int64Value('Test', 0xFEEDFACECAFEBEEF); + check(value1).equals(value2); + const value3 = Int64Value('Test', 0xFEEDFACECAFEBEEE); + check(value1).not((it) => it.equals(value3)); + }); + + test('Link', () { + const value1 = LinkValue('Test', r'C:\Windows\System32'); + check(value1.name).equals('Test'); + check(value1.type).equals(RegistryValueType.link); + check(value1.value).equals(r'C:\Windows\System32'); + check(value1.toString()).equals(r'LinkValue(Test, C:\Windows\System32)'); + const value2 = LinkValue('Test', r'C:\Windows\System32'); + check(value1).equals(value2); + const value3 = LinkValue('Test', r'C:\Windows\System31'); + check(value1).not((it) => it.equals(value3)); + }); + + test('NoneValue', () { + const value1 = NoneValue('Test'); + check(value1.name).equals('Test'); + check(value1.type).equals(RegistryValueType.none); + check(value1.toString()).equals('NoneValue(Test)'); + const value2 = NoneValue('Test'); + check(value1).equals(value2); + const value3 = NoneValue('Test2'); + check(value1).not((it) => it.equals(value3)); + }); + + test('StringValue', () { + const value1 = StringValue('Test', 'Hello, world!'); + check(value1.name).equals('Test'); + check(value1.type).equals(RegistryValueType.string); + check(value1.value).equals('Hello, world!'); + check(value1.toString()).equals('StringValue(Test, Hello, world!)'); + const value2 = StringValue('Test', 'Hello, world!'); + check(value1).equals(value2); + const value3 = StringValue('Test', 'Hello, world?'); + check(value1).not((it) => it.equals(value3)); + }); + + test('StringArrayValue', () { + const value1 = StringArrayValue('Test', ['Hello', 'world']); + check(value1.name).equals('Test'); + check(value1.type).equals(RegistryValueType.stringArray); + check(value1.value).deepEquals(['Hello', 'world']); + check(value1.toString()).equals('StringArrayValue(Test, [Hello, world])'); + const value2 = StringArrayValue('Test', ['Hello', 'world']); + check(value1).equals(value2); + const value3 = StringArrayValue('Test', ['Hello', 'world?']); + check(value1).not((it) => it.equals(value3)); + }); + + test('UnexpandedStringValue', () { + const value1 = UnexpandedStringValue('Test', r'%SystemRoot%\System32'); + check(value1.name).equals('Test'); + check(value1.type).equals(RegistryValueType.unexpandedString); + check(value1.value).equals(r'%SystemRoot%\System32'); + check(value1.toString()) + .equals(r'UnexpandedStringValue(Test, %SystemRoot%\System32)'); + const value2 = UnexpandedStringValue('Test', r'%SystemRoot%\System32'); + check(value1).equals(value2); + const value3 = UnexpandedStringValue('Test', r'%SystemRoot%\System31'); + check(value1).not((it) => it.equals(value3)); + }); + }); +} diff --git a/test/registry_value_type_test.dart b/test/registry_value_type_test.dart new file mode 100644 index 0000000..becc6f2 --- /dev/null +++ b/test/registry_value_type_test.dart @@ -0,0 +1,79 @@ +import 'package:checks/checks.dart'; +import 'package:test/scaffolding.dart'; +import 'package:win32/win32.dart'; +import 'package:win32_registry/win32_registry.dart'; + +void main() { + group('RegistryValueType', () { + test('none', () { + const none = RegistryValueType.none; + check(none.value).equals(REG_VALUE_TYPE.REG_NONE); + check(none.toString()).equals('REG_NONE'); + }); + + test('string', () { + const string = RegistryValueType.string; + check(string.value).equals(REG_VALUE_TYPE.REG_SZ); + check(string.toString()).equals('REG_SZ'); + }); + + test('unexpandedString', () { + const unexpandedString = RegistryValueType.unexpandedString; + check(unexpandedString.value).equals(REG_VALUE_TYPE.REG_EXPAND_SZ); + check(unexpandedString.toString()).equals('REG_EXPAND_SZ'); + }); + + test('binary', () { + const binary = RegistryValueType.binary; + check(binary.value).equals(REG_VALUE_TYPE.REG_BINARY); + check(binary.toString()).equals('REG_BINARY'); + }); + + test('int32', () { + const int32 = RegistryValueType.int32; + check(int32.value).equals(REG_VALUE_TYPE.REG_DWORD); + check(int32.toString()).equals('REG_DWORD'); + }); + + test('link', () { + const link = RegistryValueType.link; + check(link.value).equals(REG_VALUE_TYPE.REG_LINK); + check(link.toString()).equals('REG_LINK'); + }); + + test('stringArray', () { + const stringArray = RegistryValueType.stringArray; + check(stringArray.value).equals(REG_VALUE_TYPE.REG_MULTI_SZ); + check(stringArray.toString()).equals('REG_MULTI_SZ'); + }); + + test('int64', () { + const int64 = RegistryValueType.int64; + check(int64.value).equals(REG_VALUE_TYPE.REG_QWORD); + check(int64.toString()).equals('REG_QWORD'); + }); + + test('fromWin32', () { + check(RegistryValueType.fromWin32(REG_VALUE_TYPE.REG_NONE)) + .equals(RegistryValueType.none); + check(RegistryValueType.fromWin32(REG_VALUE_TYPE.REG_SZ)) + .equals(RegistryValueType.string); + check(RegistryValueType.fromWin32(REG_VALUE_TYPE.REG_EXPAND_SZ)) + .equals(RegistryValueType.unexpandedString); + check(RegistryValueType.fromWin32(REG_VALUE_TYPE.REG_BINARY)) + .equals(RegistryValueType.binary); + check(RegistryValueType.fromWin32(REG_VALUE_TYPE.REG_DWORD)) + .equals(RegistryValueType.int32); + check(RegistryValueType.fromWin32(REG_VALUE_TYPE.REG_LINK)) + .equals(RegistryValueType.link); + check(RegistryValueType.fromWin32(REG_VALUE_TYPE.REG_MULTI_SZ)) + .equals(RegistryValueType.stringArray); + check(RegistryValueType.fromWin32(REG_VALUE_TYPE.REG_QWORD)) + .equals(RegistryValueType.int64); + check(() => RegistryValueType.fromWin32(-1)) + .throws() + .has((e) => e.message, 'message') + .equals('Unknown registry value type: -1'); + }); + }); +} diff --git a/test/win32_registry_test.dart b/test/win32_registry_test.dart deleted file mode 100644 index 84b7633..0000000 --- a/test/win32_registry_test.dart +++ /dev/null @@ -1,260 +0,0 @@ -@TestOn('windows') -library; - -import 'dart:ffi'; - -import 'package:checks/checks.dart'; -import 'package:test/scaffolding.dart'; -import 'package:win32/win32.dart'; -import 'package:win32_registry/win32_registry.dart'; - -void main() { - test('Basic test', () { - // In any working Windows configuration, this key should have a sizable - // number of values. - const keyPath = r'Software\Microsoft\Windows NT\CurrentVersion'; - final key = Registry.openPath(RegistryHive.localMachine, path: keyPath); - check(key.values.length).isGreaterThan(0); - key.close(); - }); - - test('Test key types 1', () { - // Uses key types that should exist in any standard Windows configuration. - final env = - Registry.openPath(RegistryHive.currentUser, path: 'Environment'); - final value = env.getValue('Path'); - check(value).isNotNull(); - final type = value!.type; - check(type).equals(RegistryValueType.unexpandedString); - check(type.win32Value).equals(REG_VALUE_TYPE.REG_EXPAND_SZ); - check(type.win32Type).equals('REG_EXPAND_SZ'); - env.close(); - }); - - test('Test key types 2', () { - // Uses key types that should exist in any standard Windows configuration. - final console = - Registry.openPath(RegistryHive.currentUser, path: 'Console'); - final value = console.getValue('FullScreen'); - check(value).isNotNull(); - final type = value!.type; - check(type).equals(RegistryValueType.int32); - check(type.win32Value).equals(REG_VALUE_TYPE.REG_DWORD); - check(type.win32Type).equals('REG_DWORD'); - console.close(); - }); - - test('Test key types 3', () { - // Uses key types that should exist in any standard Windows configuration. - const keyPath = r'Control Panel\Desktop\WindowMetrics'; - final windowMetrics = - Registry.openPath(RegistryHive.currentUser, path: keyPath); - final value = windowMetrics.getValue('IconFont'); - check(value).isNotNull(); - final type = value!.type; - check(type).equals(RegistryValueType.binary); - check(type.win32Value).equals(REG_VALUE_TYPE.REG_BINARY); - check(type.win32Type).equals('REG_BINARY'); - windowMetrics.close(); - }); - - test('Test key types 4', () { - // Uses key types that should exist in any standard Windows configuration. - const keyPath = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion'; - final ntCurrentVersion = - Registry.openPath(RegistryHive.localMachine, path: keyPath); - final value = ntCurrentVersion.getValue('InstallTime'); - check(value).isNotNull(); - final type = value!.type; - check(type).equals(RegistryValueType.int64); - check(type.win32Value).equals(REG_VALUE_TYPE.REG_QWORD); - check(type.win32Type).equals('REG_QWORD'); - ntCurrentVersion.close(); - }); - - test('Test key types 5', () { - // Uses key types that should exist in any standard Windows configuration. - const keyPath = r'SOFTWARE\Microsoft\Windows\CurrentVersion'; - final currentVersion = - Registry.openPath(RegistryHive.localMachine, path: keyPath); - final value = currentVersion.getValue('ProgramFilesDir'); - check(value).isNotNull(); - final type = value!.type; - check(type).equals(RegistryValueType.string); - check(type.win32Value).equals(REG_VALUE_TYPE.REG_SZ); - check(type.win32Type).equals('REG_SZ'); - currentVersion.close(); - }); - - test('Test missing key', () { - // Uses key that should be missing. - check(() => Registry.openPath( - RegistryHive.localMachine, - path: r'SOFTWARE\Dart\Missing\Key', - )) - .throws() - .has((e) => e.hr, 'hr') - .equals(HRESULT_FROM_WIN32(WIN32_ERROR.ERROR_FILE_NOT_FOUND)); - }); - - test('Test missing value', () { - // Uses key that should be present and value that should be missing. - const keyPath = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion'; - final currentVersion = - Registry.openPath(RegistryHive.localMachine, path: keyPath); - check(currentVersion.getValue('NotepadPlusPlus')).isNull(); - }); - - test('Create a subkey', () { - final hkcu = Registry.currentUser; - const subkeyName = 'Win32RegistryTestKey'; - check(hkcu.subkeyNames.contains(subkeyName)).isFalse(); - hkcu.createKey(subkeyName); - check(hkcu.subkeyNames.contains(subkeyName)).isTrue(); - hkcu.deleteKey(subkeyName); - check(hkcu.subkeyNames.contains(subkeyName)).isFalse(); - }); - - test('Create a DWORD value (REG_DWORD)', () { - final hkcu = Registry.currentUser; - const subkeyName = 'Win32RegistryTestKey'; - final subkey = hkcu.createKey(subkeyName); - final value = - const RegistryValue('TestValue', RegistryValueType.int32, 1234); - subkey.createValue(value); - check(subkey.values.where((v) => v.name == 'TestValue').first) - .equals(value); - check(subkey.getValueAsInt('TestValue')).equals(1234); - subkey.deleteValue('TestValue'); - check(subkey.values.where((elem) => elem.name == 'TestValue')).isEmpty(); - hkcu.deleteKey(subkeyName); - }); - - test('Create a QWORD value (REG_QWORD)', () { - final hkcu = Registry.openPath(RegistryHive.currentUser); - const keyName = 'Win32RegistryTestKey'; - final key = hkcu.createKey(keyName); - final value = const RegistryValue( - 'TestValue', RegistryValueType.int64, 0xFEEDFACECAFEBEEF); - key.createValue(value); - final retrievedValue = key.getValue('TestValue'); - check(retrievedValue).isNotNull(); - check(retrievedValue!.name).equals('TestValue'); - check(retrievedValue.type).equals(RegistryValueType.int64); - check(retrievedValue.data as int).equals(0xFEEDFACECAFEBEEF); - check(key.getValueAsInt('TestValue')).equals(0xFEEDFACECAFEBEEF); - key.deleteValue('TestValue'); - check(key.values.where((elem) => elem.name == 'TestValue')).isEmpty(); - hkcu.deleteKey(keyName); - }); - - test('Create a string value (REG_SZ)', () { - final hkcu = Registry.openPath(RegistryHive.currentUser); - const keyName = 'Win32RegistryTestKey'; - final key = hkcu.createKey(keyName); - final value = const RegistryValue( - 'TestValue', RegistryValueType.string, 'Some text here.'); - final pointerData = value.toWin32; - check(pointerData.lengthInBytes).equals(sizeOf() * 16); - free(pointerData.data); - key.createValue(value); - final retrievedValue = key.getValue('TestValue'); - check(retrievedValue).isNotNull(); - check(retrievedValue!.type).equals(RegistryValueType.string); - final data = retrievedValue.data as String; - check(data).equals('Some text here.'); - check(key.getValueAsString('TestValue')).equals('Some text here.'); - key.deleteValue('TestValue'); - check(key.values.where((elem) => elem.name == 'TestValue')).isEmpty(); - hkcu.deleteKey(keyName); - }); - - test('Create a string array (REG_MULTI_SZ)', () { - final hkcu = Registry.openPath(RegistryHive.currentUser); - const keyName = 'Win32RegistryTestKey'; - final key = hkcu.createKey(keyName); - final value = const RegistryValue( - 'TestValue', RegistryValueType.stringArray, ['One', 'Two', 'Three']); - key.createValue(value); - final retrievedValue = key.getValue('TestValue'); - check(retrievedValue).isNotNull(); - check(retrievedValue!.type).equals(RegistryValueType.stringArray); - final array = retrievedValue.data as List; - check(array.length).equals(3); - check(array.first).equals('One'); - key.deleteValue('TestValue'); - check(key.values.where((elem) => elem.name == 'TestValue')).isEmpty(); - hkcu.deleteKey(keyName); - }); - - test('Create a binary value (REG_BINARY)', () { - final hkcu = Registry.openPath(RegistryHive.currentUser); - const keyName = 'Win32RegistryTestKey'; - final key = hkcu.createKey(keyName); - final value = const RegistryValue( - 'TestValue', RegistryValueType.binary, [0xFF, 0x33, 0x77, 0xAA]); - key.createValue(value); - final retrievedValue = key.getValue('TestValue'); - check(retrievedValue).isNotNull(); - check(retrievedValue!.type).equals(RegistryValueType.binary); - final array = retrievedValue.data as List; - check(array.length).equals(4); - check(array.first).equals(0xFF); - check(array.last).equals(0xAA); - key.deleteValue('TestValue'); - check(key.values.where((elem) => elem.name == 'TestValue')).isEmpty(); - hkcu.deleteKey(keyName); - }); - - test('Create an expanded string value (REG_EXPAND_SZ)', () { - final hkcu = Registry.openPath(RegistryHive.currentUser); - const keyName = 'Win32RegistryTestKey'; - final key = hkcu.createKey(keyName); - final value = const RegistryValue( - 'TestValue', - RegistryValueType.unexpandedString, - r'%SystemRoot%\System32\MirrorDrvCompat.dll'); - final pointerData = value.toWin32; - check(pointerData.lengthInBytes).equals(sizeOf() * 42); - free(pointerData.data); - key.createValue(value); - final retrievedValue = key.getValue('TestValue', expandPaths: false); - check(retrievedValue).isNotNull(); - check(retrievedValue!.type).equals(RegistryValueType.unexpandedString); - - // Unexpanded should be as stored - check(key.getValueAsString('TestValue', expandPaths: false)) - .equals(r'%SystemRoot%\System32\MirrorDrvCompat.dll'); - - // Expanded should replace %SystemRoot% with an actual system path - check(key.getValueAsString('TestValue', expandPaths: true)!) - .not((it) => it.contains('%SystemRoot%')); - - check(key.getValueAsString('TestValue', expandPaths: true)!) - .endsWith(r'\System32\MirrorDrvCompat.dll'); - - key.deleteValue('TestValue'); - check(key.values.where((elem) => elem.name == 'TestValue')).isEmpty(); - hkcu.deleteKey(keyName); - }); - - test('Rename a subkey', () { - final hkcu = Registry.openPath(RegistryHive.currentUser) - ..createKey('Win32RegistryTestKey'); - check(hkcu.subkeyNames.contains('Win32RegistryTestKey')).isTrue(); - hkcu.renameSubkey('Win32RegistryTestKey', 'MyNewTestKey'); - check(hkcu.subkeyNames.contains('Win32RegistryTestKey')).isFalse(); - check(hkcu.subkeyNames.contains('MyNewTestKey')).isTrue(); - hkcu.deleteKey('MyNewTestKey'); - check(hkcu.subkeyNames.contains('Win32RegistryTestKey')).isFalse(); - check(hkcu.subkeyNames.contains('MyNewTestKey')).isFalse(); - }); - - test('Create a subkey without write access should fail', () { - final hkcu = Registry.openPath(RegistryHive.localMachine); - const subkeyName = 'Win32RegistryTestKey'; - check(hkcu.subkeyNames.contains(subkeyName)).isFalse(); - check(() => hkcu.createKey(subkeyName)).throws(); - check(hkcu.subkeyNames.contains(subkeyName)).isFalse(); - }); -}