Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a8081d6d4 | ||
|
|
c89559acf7 | ||
|
|
2d3749635b | ||
|
|
2bfbb0e043 | ||
|
|
76f6893d69 | ||
|
|
82ee302db4 | ||
|
|
c4045b7c79 | ||
|
|
9abbcd1270 | ||
|
|
e889902057 | ||
|
|
4c31aae067 | ||
|
|
97f06feb43 | ||
|
|
a918f82607 | ||
|
|
54380dddee | ||
|
|
354dde53f6 | ||
|
|
796d737969 | ||
|
|
ba44d43e6e | ||
|
|
f476bedae9 | ||
|
|
be5d4044e5 | ||
|
|
db81ae9a5a | ||
|
|
b564dfdda2 | ||
|
|
3c82dc1d84 | ||
|
|
9630f90bba | ||
|
|
e79a2fff9f | ||
|
|
fcacb8fe40 | ||
|
|
5a04c82cbd | ||
|
|
116e72c0a9 | ||
|
|
7b8b0daee2 | ||
|
|
2b9e65edaf | ||
|
|
2290b1368b | ||
|
|
f0f70d12c8 | ||
|
|
bd5992a49c | ||
|
|
c2622b1208 | ||
|
|
52d9d7ad2e | ||
|
|
a92c69c37f | ||
|
|
a1adb08f0d | ||
|
|
b42b774af1 | ||
|
|
1168e1d9d5 | ||
|
|
ad9693a667 | ||
|
|
d1f762ee5d | ||
|
|
0db50a948e | ||
|
|
faec71ff99 | ||
|
|
3e1af20b7d | ||
|
|
70cbbdfa88 | ||
|
|
ba57a9f150 | ||
|
|
282959aa2b | ||
|
|
224f2a05d4 | ||
|
|
58a942f7be | ||
|
|
3d8bd68f05 | ||
|
|
7670fb9f5c | ||
|
|
b18c2e99a7 | ||
|
|
de33359baf | ||
|
|
540ad48f26 | ||
|
|
a8742af494 | ||
|
|
a04f2ddbee | ||
|
|
41f9d6a1a0 | ||
|
|
7d13448595 | ||
|
|
7ffb7c34e0 | ||
|
|
ada3f545f8 | ||
|
|
191bfaff5c | ||
|
|
de85ea2b41 | ||
|
|
bb671c2375 | ||
|
|
001b869de9 | ||
|
|
23e991bb39 | ||
|
|
a27ae38d60 | ||
|
|
be9433b7a3 | ||
|
|
24fe31b934 | ||
|
|
c733e68325 | ||
|
|
519936dbd9 | ||
|
|
3e911fa113 | ||
|
|
c391cddfa8 | ||
|
|
a3aa68f5a1 | ||
|
|
132528a678 | ||
|
|
8b1992e028 | ||
|
|
dc34d7d472 | ||
|
|
8df9e6c2fb | ||
|
|
85c56c3e69 |
No files matched your search
@@ -1,3 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [itsmikethetech, zjoasan, bud3699]
|
||||
github: [itsmikethetech, zjoasan]
|
||||
@@ -0,0 +1,94 @@
|
||||
name: Bug Report
|
||||
description: File a bug report.
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "triage"]
|
||||
assignees: ["zjoasan", "bud3699", "itsmikethetech"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: dropdown
|
||||
id: OS
|
||||
attributes:
|
||||
label: Which OS?
|
||||
description: What version of Windows are you running?
|
||||
options:
|
||||
- Win11 Pro
|
||||
- Win11 Home
|
||||
- Win10 Pro
|
||||
- Win10 Home
|
||||
- Other..
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: Version
|
||||
attributes:
|
||||
label: Which release?
|
||||
description: What version of Windows are you running?
|
||||
options:
|
||||
- 24H2
|
||||
- 23H2
|
||||
- 22H2
|
||||
- 21H2
|
||||
- 1809
|
||||
- 1607
|
||||
- 1507
|
||||
- Other..
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
If you struggle finding windows edition and version, Go to settings -> system -> about. Under windows specifications.
|
||||
- type: textarea
|
||||
id: bug-info
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: Free text description.
|
||||
placeholder: |
|
||||
Tell us what you see!
|
||||
A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduce-info
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: And if you can how to reproduce
|
||||
placeholder: |
|
||||
Tell us how to reproduce!
|
||||
Go to '...', 2. Click on '....', and so on.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: expected-info
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: Please inform us about expected behavior.
|
||||
placeholder: |
|
||||
Description of behavior!
|
||||
When i Clicked...
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: log-file
|
||||
attributes:
|
||||
label: Log File (Beta Only)
|
||||
description: Please attach the log file to help us diagnose the issue. You can drag and drop the file here or click to upload. The log can be retrieved by enabling the log file in the xml then restarting the driver.
|
||||
placeholder: Drag and drop the log file here or click to upload.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: contact
|
||||
attributes:
|
||||
label: Contact Details
|
||||
description: How can we get in touch with you if we need more info?
|
||||
placeholder: ex. email@example.com
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Feature Request
|
||||
description: Request a new Feature.
|
||||
title: "[Feature]: "
|
||||
labels: ["remove", "customize", "companion", "enhancement", "newcode"]
|
||||
assignees: ["zjoasan", "bud3699", "itsmikethetech"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this feature request report!
|
||||
- type: textarea
|
||||
id: frprob
|
||||
attributes:
|
||||
label: Is the feature related to a "problem"?
|
||||
description: Describe the problem, in detail.
|
||||
placeholder: Tell us how to reproduce
|
||||
value: "When start X I wish to.."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: your-solution
|
||||
attributes:
|
||||
label: Your suggestion
|
||||
description: Describe what would solve your problem, as detailed as possible.
|
||||
placeholder: Tell us how to progress!
|
||||
value: "Disable a specific VD via hotkey"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: contact
|
||||
attributes:
|
||||
label: Contact Details
|
||||
description: How can we get in touch with you if we need more info?
|
||||
placeholder: ex. email@example.com
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
|
||||
#include <wrl/client.h> // For ComPtr
|
||||
#include <dxgi.h> // For IDXGIAdapter, IDXGIFactory1
|
||||
#include <algorithm> // For sort
|
||||
|
||||
using namespace std;
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
// Structure to vector gpus
|
||||
struct GPUInfo {
|
||||
wstring name; // GPU name
|
||||
ComPtr<IDXGIAdapter> adapter;// COM pointer to the adapter
|
||||
DXGI_ADAPTER_DESC desc; // Adapter description
|
||||
};
|
||||
|
||||
// Sort function for GPUs by dedicated video memory
|
||||
bool CompareGPUs(const GPUInfo& a, const GPUInfo& b) {
|
||||
return a.desc.DedicatedVideoMemory > b.desc.DedicatedVideoMemory;
|
||||
}
|
||||
|
||||
// Get a enumerate list of available GPUs
|
||||
vector<GPUInfo> getAvailableGPUs() {
|
||||
vector<GPUInfo> gpus; // Vector to hold all GPU's information
|
||||
|
||||
ComPtr<IDXGIFactory1> factory;
|
||||
if (!SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&factory)))) {
|
||||
return gpus;
|
||||
}
|
||||
|
||||
// Enumerate all adapters (GPUs)
|
||||
for (UINT i = 0;; i++) {
|
||||
ComPtr<IDXGIAdapter> adapter;
|
||||
if (!SUCCEEDED(factory->EnumAdapters(i, &adapter))) {
|
||||
break;
|
||||
}
|
||||
|
||||
DXGI_ADAPTER_DESC desc;
|
||||
|
||||
if (!SUCCEEDED(adapter->GetDesc(&desc))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the adapter information to the list
|
||||
GPUInfo info{ desc.Description, adapter, desc };
|
||||
gpus.push_back(info);
|
||||
}
|
||||
|
||||
return gpus;
|
||||
}
|
||||
|
||||
class AdapterOption {
|
||||
public:
|
||||
bool hasTargetAdapter{}; // Indicates if a target adapter is selected
|
||||
LUID adapterLuid{}; // Adapter's unique identifier (LUID)
|
||||
wstring target_name{}; // Target adapter name
|
||||
|
||||
// Select the best GPU based on dedicated video memory
|
||||
wstring selectBestGPU() {
|
||||
auto gpus = getAvailableGPUs();
|
||||
if (gpus.empty()) {
|
||||
return L""; // Error check for headless / vm
|
||||
}
|
||||
|
||||
// Sort GPUs by dedicated video memory in descending order
|
||||
sort(gpus.begin(), gpus.end(), CompareGPUs);
|
||||
auto bestGPU = gpus.front(); // Get the GPU with the most memory
|
||||
|
||||
return bestGPU.name;
|
||||
}
|
||||
|
||||
// Load friendlyname from a file OR select the best GPU
|
||||
void load(const wchar_t* path) {
|
||||
ifstream ifs{ path };
|
||||
|
||||
if (!ifs.is_open()) {
|
||||
target_name = selectBestGPU();
|
||||
}
|
||||
else {
|
||||
string line;
|
||||
getline(ifs, line);
|
||||
target_name.assign(line.begin(), line.end());
|
||||
}
|
||||
|
||||
// Find and set the adapter based on the target name
|
||||
if (!findAndSetAdapter(target_name)) {
|
||||
// If the adapter is not found, select the best GPU and retry
|
||||
target_name = selectBestGPU();
|
||||
findAndSetAdapter(target_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the target adapter from a given name and validate it
|
||||
void xmlprovide(const wstring& xtarg) {
|
||||
target_name = xtarg;
|
||||
if (!findAndSetAdapter(target_name)) {
|
||||
// If the adapter is not found, select the best GPU and retry
|
||||
target_name = selectBestGPU();
|
||||
findAndSetAdapter(target_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the adapter settings to the specified adapter
|
||||
void apply(const IDDCX_ADAPTER& adapter) {
|
||||
if (hasTargetAdapter && IDD_IS_FUNCTION_AVAILABLE(IddCxAdapterSetRenderAdapter)) {
|
||||
IDARG_IN_ADAPTERSETRENDERADAPTER arg{};
|
||||
arg.PreferredRenderAdapter = adapterLuid;
|
||||
IddCxAdapterSetRenderAdapter(adapter, &arg);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Find and set the adapter by its name
|
||||
bool findAndSetAdapter(const wstring& adapterName) {
|
||||
auto gpus = getAvailableGPUs();
|
||||
|
||||
// Iterate through all available GPUs
|
||||
for (const auto& gpu : gpus) {
|
||||
if (_wcsicmp(gpu.name.c_str(), adapterName.c_str()) == 0) {
|
||||
adapterLuid = gpu.desc.AdapterLuid; // Set the adapter LUID
|
||||
hasTargetAdapter = true; // Indicate that a target adapter is selected
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
hasTargetAdapter = false; // Indicate that no target adapter is selected
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
Required include(s). Plans to combine it into the driver to reduce includes.
|
||||
@@ -0,0 +1 @@
|
||||
Note: Some of the files included with installer files may use code from outside sources and as such, would not be covered by the open source license. Only the virtual display driver itself is fully FOSS, while other terms may apply based on the installer packaging method used.
|
||||
@@ -0,0 +1,202 @@
|
||||
# Rules in this file were initially inferred by Visual Studio IntelliCode from the Template Studio codebase.
|
||||
# You can modify the rules from these initially generated values to suit your own policies.
|
||||
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference.
|
||||
|
||||
[*.cs]
|
||||
|
||||
#Core editorconfig formatting - indentation
|
||||
|
||||
#use soft tabs (spaces) for indentation
|
||||
indent_style = space
|
||||
|
||||
#Formatting - new line options
|
||||
|
||||
#place else statements on a new line
|
||||
csharp_new_line_before_else = true
|
||||
#require braces to be on a new line for lambdas, methods, control_blocks, types, properties, and accessors (also known as "Allman" style)
|
||||
csharp_new_line_before_open_brace = all
|
||||
|
||||
#Formatting - organize using options
|
||||
|
||||
#sort System.* using directives alphabetically, and place them before other usings
|
||||
dotnet_sort_system_directives_first = true
|
||||
|
||||
#Formatting - spacing options
|
||||
|
||||
#require NO space between a cast and the value
|
||||
csharp_space_after_cast = false
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
#require a space after a keyword in a control flow statement such as a for loop
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
#remove space within empty argument list parentheses
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
#remove space between method call name and opening parenthesis
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
#remove space within empty parameter list parentheses for a method declaration
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
|
||||
#Formatting - wrapping options
|
||||
|
||||
#leave code block on separate lines
|
||||
csharp_preserve_single_line_blocks = false
|
||||
|
||||
#Style - Code block preferences
|
||||
|
||||
#prefer curly braces even for one line of code
|
||||
csharp_prefer_braces = true:suggestion
|
||||
|
||||
#Style - expression bodied member options
|
||||
|
||||
#prefer expression bodies for accessors
|
||||
csharp_style_expression_bodied_accessors = true:warning
|
||||
#prefer block bodies for constructors
|
||||
csharp_style_expression_bodied_constructors = false:suggestion
|
||||
#prefer expression bodies for methods
|
||||
csharp_style_expression_bodied_methods = when_on_single_line:silent
|
||||
#prefer expression-bodied members for properties
|
||||
csharp_style_expression_bodied_properties = true:warning
|
||||
|
||||
#Style - expression level options
|
||||
|
||||
#prefer out variables to be declared before the method call
|
||||
csharp_style_inlined_variable_declaration = false:suggestion
|
||||
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
#Style - Expression-level preferences
|
||||
|
||||
#prefer default over default(T)
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
#prefer objects to be initialized using object initializers when possible
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
|
||||
#Style - implicit and explicit types
|
||||
|
||||
#prefer var over explicit type in all cases, unless overridden by another code style rule
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
#prefer var is used to declare variables with built-in system types such as int
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
#prefer var when the type is already mentioned on the right-hand side of a declaration expression
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
|
||||
#Style - language keyword and framework type options
|
||||
|
||||
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
|
||||
#Style - Language rules
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
|
||||
csharp_style_var_for_built_in_types = true:warning
|
||||
|
||||
#Style - modifier options
|
||||
|
||||
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||
|
||||
#Style - Modifier preferences
|
||||
|
||||
#when this rule is set to a list of modifiers, prefer the specified ordering.
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,async,readonly,override,sealed,abstract,virtual:warning
|
||||
dotnet_style_readonly_field = true:warning
|
||||
|
||||
#Style - Pattern matching
|
||||
|
||||
#prefer pattern matching instead of is expression with type casts
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:warning
|
||||
|
||||
#Style - qualification options
|
||||
|
||||
#prefer events not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
#prefer fields not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
#prefer methods not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
#prefer properties not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
csharp_prefer_simple_using_statement = true:warning
|
||||
csharp_style_namespace_declarations = file_scoped:warning
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
|
||||
[*.{cs,vb}]
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
end_of_line = crlf
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
[*.{cs,vb}]
|
||||
|
||||
#Style - Unnecessary code rules
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:warning
|
||||
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:warning
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"components": [
|
||||
"Microsoft.Component.MSBuild",
|
||||
"Microsoft.NetCore.Component.Runtime.7.0",
|
||||
"Microsoft.NetCore.Component.SDK",
|
||||
"Microsoft.VisualStudio.Component.ManagedDesktop.Core",
|
||||
"Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites",
|
||||
"Microsoft.VisualStudio.Component.NuGet",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK.19041",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK",
|
||||
"Microsoft.VisualStudio.ComponentGroup.MSIX.Packaging",
|
||||
"Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs",
|
||||
"Microsoft.VisualStudio.Workload.ManagedDesktop"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace VDD_Panel.Core.Contracts.Services;
|
||||
|
||||
public interface IFileService
|
||||
{
|
||||
T Read<T>(string folderPath, string fileName);
|
||||
|
||||
void Save<T>(string folderPath, string fileName, T content);
|
||||
|
||||
void Delete(string folderPath, string fileName);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace VDD_Panel.Core.Helpers;
|
||||
|
||||
public static class Json
|
||||
{
|
||||
public static async Task<T> ToObjectAsync<T>(string value)
|
||||
{
|
||||
return await Task.Run<T>(() =>
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(value);
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task<string> StringifyAsync(object value)
|
||||
{
|
||||
return await Task.Run<string>(() =>
|
||||
{
|
||||
return JsonConvert.SerializeObject(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
*Recommended Markdown Viewer: [Markdown Editor](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.MarkdownEditor2)*
|
||||
|
||||
## Getting Started
|
||||
|
||||
The Core project contains code that can be [reused across multiple application projects](https://docs.microsoft.com/dotnet/standard/net-standard#net-5-and-net-standard).
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Text;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using VDD_Panel.Core.Contracts.Services;
|
||||
|
||||
namespace VDD_Panel.Core.Services;
|
||||
|
||||
public class FileService : IFileService
|
||||
{
|
||||
public T Read<T>(string folderPath, string fileName)
|
||||
{
|
||||
var path = Path.Combine(folderPath, fileName);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var json = File.ReadAllText(path);
|
||||
return JsonConvert.DeserializeObject<T>(json);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public void Save<T>(string folderPath, string fileName, T content)
|
||||
{
|
||||
if (!Directory.Exists(folderPath))
|
||||
{
|
||||
Directory.CreateDirectory(folderPath);
|
||||
}
|
||||
|
||||
var fileContent = JsonConvert.SerializeObject(content);
|
||||
File.WriteAllText(Path.Combine(folderPath, fileName), fileContent, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public void Delete(string folderPath, string fileName)
|
||||
{
|
||||
if (fileName != null && File.Exists(Path.Combine(folderPath, fileName)))
|
||||
{
|
||||
File.Delete(Path.Combine(folderPath, fileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<RootNamespace>VDD_Panel.Core</RootNamespace>
|
||||
<Platforms>AnyCPU;x64;x86</Platforms>
|
||||
<Platforms>x86;x64;arm64;AnyCPU</Platforms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Models\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34622.214
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VDD Panel", "VDD Panel\VDD Panel.csproj", "{5FA23A93-E3D0-46A3-89AA-A909685E2784}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VDD Panel.Core", "VDD Panel.Core\VDD Panel.Core.csproj", "{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|arm64 = Debug|arm64
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|arm64 = Release|arm64
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Debug|Any CPU.Deploy.0 = Debug|x64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Debug|arm64.ActiveCfg = Debug|arm64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Debug|arm64.Build.0 = Debug|arm64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Debug|arm64.Deploy.0 = Debug|arm64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Debug|x64.Build.0 = Debug|x64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Debug|x86.Build.0 = Debug|x86
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Release|Any CPU.Build.0 = Release|x64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Release|Any CPU.Deploy.0 = Release|x64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Release|arm64.ActiveCfg = Release|arm64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Release|arm64.Build.0 = Release|arm64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Release|arm64.Deploy.0 = Release|arm64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Release|x64.ActiveCfg = Release|x64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Release|x64.Build.0 = Release|x64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Release|x64.Deploy.0 = Release|x64
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Release|x86.ActiveCfg = Release|x86
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Release|x86.Build.0 = Release|x86
|
||||
{5FA23A93-E3D0-46A3-89AA-A909685E2784}.Release|x86.Deploy.0 = Release|x86
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Debug|arm64.ActiveCfg = Debug|arm64
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Debug|arm64.Build.0 = Debug|arm64
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Debug|x64.Build.0 = Debug|x64
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Debug|x86.Build.0 = Debug|x86
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Release|arm64.ActiveCfg = Release|arm64
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Release|arm64.Build.0 = Release|arm64
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Release|x64.ActiveCfg = Release|x64
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Release|x64.Build.0 = Release|x64
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Release|x86.ActiveCfg = Release|x86
|
||||
{29E3EAF5-65AE-4ACA-B550-983DBCE38E03}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {81C98471-4829-40B4-B60F-CEB2472667FC}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace VDD_Panel.Activation;
|
||||
|
||||
// Extend this class to implement new ActivationHandlers. See DefaultActivationHandler for an example.
|
||||
// https://github.com/microsoft/TemplateStudio/blob/main/docs/WinUI/activation.md
|
||||
public abstract class ActivationHandler<T> : IActivationHandler
|
||||
where T : class
|
||||
{
|
||||
// Override this method to add the logic for whether to handle the activation.
|
||||
protected virtual bool CanHandleInternal(T args) => true;
|
||||
|
||||
// Override this method to add the logic for your activation handler.
|
||||
protected abstract Task HandleInternalAsync(T args);
|
||||
|
||||
public bool CanHandle(object args) => args is T && CanHandleInternal((args as T)!);
|
||||
|
||||
public async Task HandleAsync(object args) => await HandleInternalAsync((args as T)!);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Microsoft.Windows.AppNotifications;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.ViewModels;
|
||||
|
||||
namespace VDD_Panel.Activation;
|
||||
|
||||
public class AppNotificationActivationHandler : ActivationHandler<LaunchActivatedEventArgs>
|
||||
{
|
||||
private readonly INavigationService _navigationService;
|
||||
private readonly IAppNotificationService _notificationService;
|
||||
|
||||
public AppNotificationActivationHandler(INavigationService navigationService, IAppNotificationService notificationService)
|
||||
{
|
||||
_navigationService = navigationService;
|
||||
_notificationService = notificationService;
|
||||
}
|
||||
|
||||
protected override bool CanHandleInternal(LaunchActivatedEventArgs args)
|
||||
{
|
||||
return AppInstance.GetCurrent().GetActivatedEventArgs()?.Kind == ExtendedActivationKind.AppNotification;
|
||||
}
|
||||
|
||||
protected async override Task HandleInternalAsync(LaunchActivatedEventArgs args)
|
||||
{
|
||||
// TODO: Handle notification activations.
|
||||
|
||||
//// // Access the AppNotificationActivatedEventArgs.
|
||||
//// var activatedEventArgs = (AppNotificationActivatedEventArgs)AppInstance.GetCurrent().GetActivatedEventArgs().Data;
|
||||
|
||||
//// // Navigate to a specific page based on the notification arguments.
|
||||
//// if (_notificationService.ParseArguments(activatedEventArgs.Argument)["action"] == "Settings")
|
||||
//// {
|
||||
//// // Queue navigation with low priority to allow the UI to initialize.
|
||||
//// App.MainWindow.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
//// {
|
||||
//// _navigationService.NavigateTo(typeof(SettingsViewModel).FullName!);
|
||||
//// });
|
||||
//// }
|
||||
|
||||
App.MainWindow.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
App.MainWindow.ShowMessageDialogAsync("TODO: Handle notification activations.", "Notification Activation");
|
||||
});
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.ViewModels;
|
||||
|
||||
namespace VDD_Panel.Activation;
|
||||
|
||||
public class DefaultActivationHandler : ActivationHandler<LaunchActivatedEventArgs>
|
||||
{
|
||||
private readonly INavigationService _navigationService;
|
||||
|
||||
public DefaultActivationHandler(INavigationService navigationService)
|
||||
{
|
||||
_navigationService = navigationService;
|
||||
}
|
||||
|
||||
protected override bool CanHandleInternal(LaunchActivatedEventArgs args)
|
||||
{
|
||||
// None of the ActivationHandlers has handled the activation.
|
||||
return _navigationService.Frame?.Content == null;
|
||||
}
|
||||
|
||||
protected async override Task HandleInternalAsync(LaunchActivatedEventArgs args)
|
||||
{
|
||||
_navigationService.NavigateTo(typeof(MainViewModel).FullName!, args.Arguments);
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace VDD_Panel.Activation;
|
||||
|
||||
public interface IActivationHandler
|
||||
{
|
||||
bool CanHandle(object args);
|
||||
|
||||
Task HandleAsync(object args);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Application
|
||||
x:Class="VDD_Panel.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<ResourceDictionary Source="/Styles/FontSizes.xaml" />
|
||||
<ResourceDictionary Source="/Styles/Thickness.xaml" />
|
||||
<ResourceDictionary Source="/Styles/TextBlock.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,111 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
using VDD_Panel.Activation;
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.Core.Contracts.Services;
|
||||
using VDD_Panel.Core.Services;
|
||||
using VDD_Panel.Helpers;
|
||||
using VDD_Panel.Models;
|
||||
using VDD_Panel.Notifications;
|
||||
using VDD_Panel.Services;
|
||||
using VDD_Panel.ViewModels;
|
||||
using VDD_Panel.Views;
|
||||
|
||||
namespace VDD_Panel;
|
||||
|
||||
// To learn more about WinUI 3, see https://docs.microsoft.com/windows/apps/winui/winui3/.
|
||||
public partial class App : Application
|
||||
{
|
||||
// The .NET Generic Host provides dependency injection, configuration, logging, and other services.
|
||||
// https://docs.microsoft.com/dotnet/core/extensions/generic-host
|
||||
// https://docs.microsoft.com/dotnet/core/extensions/dependency-injection
|
||||
// https://docs.microsoft.com/dotnet/core/extensions/configuration
|
||||
// https://docs.microsoft.com/dotnet/core/extensions/logging
|
||||
public IHost Host
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public static T GetService<T>()
|
||||
where T : class
|
||||
{
|
||||
if ((App.Current as App)!.Host.Services.GetService(typeof(T)) is not T service)
|
||||
{
|
||||
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs.");
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
public static WindowEx MainWindow { get; } = new MainWindow();
|
||||
|
||||
public static UIElement? AppTitlebar { get; set; }
|
||||
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Host = Microsoft.Extensions.Hosting.Host.
|
||||
CreateDefaultBuilder().
|
||||
UseContentRoot(AppContext.BaseDirectory).
|
||||
ConfigureServices((context, services) =>
|
||||
{
|
||||
// Default Activation Handler
|
||||
services.AddTransient<ActivationHandler<LaunchActivatedEventArgs>, DefaultActivationHandler>();
|
||||
|
||||
// Other Activation Handlers
|
||||
services.AddTransient<IActivationHandler, AppNotificationActivationHandler>();
|
||||
|
||||
// Services
|
||||
services.AddSingleton<IAppNotificationService, AppNotificationService>();
|
||||
services.AddSingleton<ILocalSettingsService, LocalSettingsService>();
|
||||
services.AddSingleton<IThemeSelectorService, ThemeSelectorService>();
|
||||
services.AddTransient<IWebViewService, WebViewService>();
|
||||
services.AddTransient<INavigationViewService, NavigationViewService>();
|
||||
|
||||
services.AddSingleton<IActivationService, ActivationService>();
|
||||
services.AddSingleton<IPageService, PageService>();
|
||||
services.AddSingleton<INavigationService, NavigationService>();
|
||||
|
||||
// Core Services
|
||||
services.AddSingleton<IFileService, FileService>();
|
||||
|
||||
// Views and ViewModels
|
||||
services.AddTransient<SettingsViewModel>();
|
||||
services.AddTransient<SettingsPage>();
|
||||
services.AddTransient<GitHubViewModel>();
|
||||
services.AddTransient<GitHubPage>();
|
||||
services.AddTransient<ConfigureViewModel>();
|
||||
services.AddTransient<ConfigurePage>();
|
||||
services.AddTransient<MainViewModel>();
|
||||
services.AddTransient<MainPage>();
|
||||
services.AddTransient<ShellPage>();
|
||||
services.AddTransient<ShellViewModel>();
|
||||
|
||||
// Configuration
|
||||
services.Configure<LocalSettingsOptions>(context.Configuration.GetSection(nameof(LocalSettingsOptions)));
|
||||
}).
|
||||
Build();
|
||||
|
||||
App.GetService<IAppNotificationService>().Initialize();
|
||||
|
||||
UnhandledException += App_UnhandledException;
|
||||
}
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
// TODO: Log and handle exceptions as appropriate.
|
||||
// https://docs.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.application.unhandledexception.
|
||||
}
|
||||
|
||||
protected async override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
base.OnLaunched(args);
|
||||
|
||||
App.GetService<IAppNotificationService>().Show(string.Format("AppNotificationSamplePayload".GetLocalized(), AppContext.BaseDirectory));
|
||||
|
||||
await App.GetService<IActivationService>().ActivateAsync(args);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 65 KiB |
@@ -0,0 +1,122 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Microsoft.Xaml.Interactivity;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
|
||||
namespace VDD_Panel.Behaviors;
|
||||
|
||||
public class NavigationViewHeaderBehavior : Behavior<NavigationView>
|
||||
{
|
||||
private static NavigationViewHeaderBehavior? _current;
|
||||
|
||||
private Page? _currentPage;
|
||||
|
||||
public DataTemplate? DefaultHeaderTemplate
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public object DefaultHeader
|
||||
{
|
||||
get => GetValue(DefaultHeaderProperty);
|
||||
set => SetValue(DefaultHeaderProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DefaultHeaderProperty =
|
||||
DependencyProperty.Register("DefaultHeader", typeof(object), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(null, (d, e) => _current!.UpdateHeader()));
|
||||
|
||||
public static NavigationViewHeaderMode GetHeaderMode(Page item) => (NavigationViewHeaderMode)item.GetValue(HeaderModeProperty);
|
||||
|
||||
public static void SetHeaderMode(Page item, NavigationViewHeaderMode value) => item.SetValue(HeaderModeProperty, value);
|
||||
|
||||
public static readonly DependencyProperty HeaderModeProperty =
|
||||
DependencyProperty.RegisterAttached("HeaderMode", typeof(bool), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(NavigationViewHeaderMode.Always, (d, e) => _current!.UpdateHeader()));
|
||||
|
||||
public static object GetHeaderContext(Page item) => item.GetValue(HeaderContextProperty);
|
||||
|
||||
public static void SetHeaderContext(Page item, object value) => item.SetValue(HeaderContextProperty, value);
|
||||
|
||||
public static readonly DependencyProperty HeaderContextProperty =
|
||||
DependencyProperty.RegisterAttached("HeaderContext", typeof(object), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(null, (d, e) => _current!.UpdateHeader()));
|
||||
|
||||
public static DataTemplate GetHeaderTemplate(Page item) => (DataTemplate)item.GetValue(HeaderTemplateProperty);
|
||||
|
||||
public static void SetHeaderTemplate(Page item, DataTemplate value) => item.SetValue(HeaderTemplateProperty, value);
|
||||
|
||||
public static readonly DependencyProperty HeaderTemplateProperty =
|
||||
DependencyProperty.RegisterAttached("HeaderTemplate", typeof(DataTemplate), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(null, (d, e) => _current!.UpdateHeaderTemplate()));
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
|
||||
var navigationService = App.GetService<INavigationService>();
|
||||
navigationService.Navigated += OnNavigated;
|
||||
|
||||
_current = this;
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
base.OnDetaching();
|
||||
|
||||
var navigationService = App.GetService<INavigationService>();
|
||||
navigationService.Navigated -= OnNavigated;
|
||||
}
|
||||
|
||||
private void OnNavigated(object sender, NavigationEventArgs e)
|
||||
{
|
||||
if (sender is Frame frame && frame.Content is Page page)
|
||||
{
|
||||
_currentPage = page;
|
||||
|
||||
UpdateHeader();
|
||||
UpdateHeaderTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHeader()
|
||||
{
|
||||
if (_currentPage != null)
|
||||
{
|
||||
var headerMode = GetHeaderMode(_currentPage);
|
||||
if (headerMode == NavigationViewHeaderMode.Never)
|
||||
{
|
||||
AssociatedObject.Header = null;
|
||||
AssociatedObject.AlwaysShowHeader = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var headerFromPage = GetHeaderContext(_currentPage);
|
||||
if (headerFromPage != null)
|
||||
{
|
||||
AssociatedObject.Header = headerFromPage;
|
||||
}
|
||||
else
|
||||
{
|
||||
AssociatedObject.Header = DefaultHeader;
|
||||
}
|
||||
|
||||
if (headerMode == NavigationViewHeaderMode.Always)
|
||||
{
|
||||
AssociatedObject.AlwaysShowHeader = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AssociatedObject.AlwaysShowHeader = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHeaderTemplate()
|
||||
{
|
||||
if (_currentPage != null)
|
||||
{
|
||||
var headerTemplate = GetHeaderTemplate(_currentPage);
|
||||
AssociatedObject.HeaderTemplate = headerTemplate ?? DefaultHeaderTemplate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace VDD_Panel.Behaviors;
|
||||
|
||||
public enum NavigationViewHeaderMode
|
||||
{
|
||||
Always,
|
||||
Never,
|
||||
Minimal
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace VDD_Panel.Contracts.Services;
|
||||
|
||||
public interface IActivationService
|
||||
{
|
||||
Task ActivateAsync(object activationArgs);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace VDD_Panel.Contracts.Services;
|
||||
|
||||
public interface IAppNotificationService
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
bool Show(string payload);
|
||||
|
||||
NameValueCollection ParseArguments(string arguments);
|
||||
|
||||
void Unregister();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace VDD_Panel.Contracts.Services;
|
||||
|
||||
public interface ILocalSettingsService
|
||||
{
|
||||
Task<T?> ReadSettingAsync<T>(string key);
|
||||
|
||||
Task SaveSettingAsync<T>(string key, T value);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace VDD_Panel.Contracts.Services;
|
||||
|
||||
public interface INavigationService
|
||||
{
|
||||
event NavigatedEventHandler Navigated;
|
||||
|
||||
bool CanGoBack
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
Frame? Frame
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false);
|
||||
|
||||
bool GoBack();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace VDD_Panel.Contracts.Services;
|
||||
|
||||
public interface INavigationViewService
|
||||
{
|
||||
IList<object>? MenuItems
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
object? SettingsItem
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
void Initialize(NavigationView navigationView);
|
||||
|
||||
void UnregisterEvents();
|
||||
|
||||
NavigationViewItem? GetSelectedItem(Type pageType);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace VDD_Panel.Contracts.Services;
|
||||
|
||||
public interface IPageService
|
||||
{
|
||||
Type GetPageType(string key);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace VDD_Panel.Contracts.Services;
|
||||
|
||||
public interface IThemeSelectorService
|
||||
{
|
||||
ElementTheme Theme
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
Task InitializeAsync();
|
||||
|
||||
Task SetThemeAsync(ElementTheme theme);
|
||||
|
||||
Task SetRequestedThemeAsync();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
|
||||
namespace VDD_Panel.Contracts.Services;
|
||||
|
||||
public interface IWebViewService
|
||||
{
|
||||
Uri? Source
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
bool CanGoBack
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
bool CanGoForward
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
event EventHandler<CoreWebView2WebErrorStatus>? NavigationCompleted;
|
||||
|
||||
void Initialize(WebView2 webView);
|
||||
|
||||
void GoBack();
|
||||
|
||||
void GoForward();
|
||||
|
||||
void Reload();
|
||||
|
||||
void UnregisterEvents();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace VDD_Panel.Contracts.ViewModels;
|
||||
|
||||
public interface INavigationAware
|
||||
{
|
||||
void OnNavigatedTo(object parameter);
|
||||
|
||||
void OnNavigatedFrom();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace VDD_Panel.Helpers;
|
||||
|
||||
public class EnumToBooleanConverter : IValueConverter
|
||||
{
|
||||
public EnumToBooleanConverter()
|
||||
{
|
||||
}
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (parameter is string enumString)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(ElementTheme), value))
|
||||
{
|
||||
throw new ArgumentException("ExceptionEnumToBooleanConverterValueMustBeAnEnum");
|
||||
}
|
||||
|
||||
var enumValue = Enum.Parse(typeof(ElementTheme), enumString);
|
||||
|
||||
return enumValue.Equals(value);
|
||||
}
|
||||
|
||||
throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName");
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (parameter is string enumString)
|
||||
{
|
||||
return Enum.Parse(typeof(ElementTheme), enumString);
|
||||
}
|
||||
|
||||
throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace VDD_Panel.Helpers;
|
||||
|
||||
public static class FrameExtensions
|
||||
{
|
||||
public static object? GetPageViewModel(this Frame frame) => frame?.Content?.GetType().GetProperty("ViewModel")?.GetValue(frame.Content, null);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace VDD_Panel.Helpers;
|
||||
|
||||
// Helper class to set the navigation target for a NavigationViewItem.
|
||||
//
|
||||
// Usage in XAML:
|
||||
// <NavigationViewItem x:Uid="Shell_Main" Icon="Document" helpers:NavigationHelper.NavigateTo="AppName.ViewModels.MainViewModel" />
|
||||
//
|
||||
// Usage in code:
|
||||
// NavigationHelper.SetNavigateTo(navigationViewItem, typeof(MainViewModel).FullName);
|
||||
public class NavigationHelper
|
||||
{
|
||||
public static string GetNavigateTo(NavigationViewItem item) => (string)item.GetValue(NavigateToProperty);
|
||||
|
||||
public static void SetNavigateTo(NavigationViewItem item, string value) => item.SetValue(NavigateToProperty, value);
|
||||
|
||||
public static readonly DependencyProperty NavigateToProperty =
|
||||
DependencyProperty.RegisterAttached("NavigateTo", typeof(string), typeof(NavigationHelper), new PropertyMetadata(null));
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
|
||||
namespace VDD_Panel.Helpers;
|
||||
|
||||
public static class ResourceExtensions
|
||||
{
|
||||
private static readonly ResourceLoader _resourceLoader = new();
|
||||
|
||||
public static string GetLocalized(this string resourceKey) => _resourceLoader.GetString(resourceKey);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace VDD_Panel.Helpers;
|
||||
|
||||
public class RuntimeHelper
|
||||
{
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder? packageFullName);
|
||||
|
||||
public static bool IsMSIX
|
||||
{
|
||||
get
|
||||
{
|
||||
var length = 0;
|
||||
|
||||
return GetCurrentPackageFullName(ref length, null) != 15700L;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using VDD_Panel.Core.Helpers;
|
||||
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace VDD_Panel.Helpers;
|
||||
|
||||
// Use these extension methods to store and retrieve local and roaming app data
|
||||
// More details regarding storing and retrieving app data at https://docs.microsoft.com/windows/apps/design/app-settings/store-and-retrieve-app-data
|
||||
public static class SettingsStorageExtensions
|
||||
{
|
||||
private const string FileExtension = ".json";
|
||||
|
||||
public static bool IsRoamingStorageAvailable(this ApplicationData appData)
|
||||
{
|
||||
return appData.RoamingStorageQuota == 0;
|
||||
}
|
||||
|
||||
public static async Task SaveAsync<T>(this StorageFolder folder, string name, T content)
|
||||
{
|
||||
var file = await folder.CreateFileAsync(GetFileName(name), CreationCollisionOption.ReplaceExisting);
|
||||
var fileContent = await Json.StringifyAsync(content);
|
||||
|
||||
await FileIO.WriteTextAsync(file, fileContent);
|
||||
}
|
||||
|
||||
public static async Task<T?> ReadAsync<T>(this StorageFolder folder, string name)
|
||||
{
|
||||
if (!File.Exists(Path.Combine(folder.Path, GetFileName(name))))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var file = await folder.GetFileAsync($"{name}.json");
|
||||
var fileContent = await FileIO.ReadTextAsync(file);
|
||||
|
||||
return await Json.ToObjectAsync<T>(fileContent);
|
||||
}
|
||||
|
||||
public static async Task SaveAsync<T>(this ApplicationDataContainer settings, string key, T value)
|
||||
{
|
||||
settings.SaveString(key, await Json.StringifyAsync(value));
|
||||
}
|
||||
|
||||
public static void SaveString(this ApplicationDataContainer settings, string key, string value)
|
||||
{
|
||||
settings.Values[key] = value;
|
||||
}
|
||||
|
||||
public static async Task<T?> ReadAsync<T>(this ApplicationDataContainer settings, string key)
|
||||
{
|
||||
object? obj;
|
||||
|
||||
if (settings.Values.TryGetValue(key, out obj))
|
||||
{
|
||||
return await Json.ToObjectAsync<T>((string)obj);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static async Task<StorageFile> SaveFileAsync(this StorageFolder folder, byte[] content, string fileName, CreationCollisionOption options = CreationCollisionOption.ReplaceExisting)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(content));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
throw new ArgumentException("File name is null or empty. Specify a valid file name", nameof(fileName));
|
||||
}
|
||||
|
||||
var storageFile = await folder.CreateFileAsync(fileName, options);
|
||||
await FileIO.WriteBytesAsync(storageFile, content);
|
||||
return storageFile;
|
||||
}
|
||||
|
||||
public static async Task<byte[]?> ReadFileAsync(this StorageFolder folder, string fileName)
|
||||
{
|
||||
var item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
|
||||
|
||||
if ((item != null) && item.IsOfType(StorageItemTypes.File))
|
||||
{
|
||||
var storageFile = await folder.GetFileAsync(fileName);
|
||||
var content = await storageFile.ReadBytesAsync();
|
||||
return content;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static async Task<byte[]?> ReadBytesAsync(this StorageFile file)
|
||||
{
|
||||
if (file != null)
|
||||
{
|
||||
using IRandomAccessStream stream = await file.OpenReadAsync();
|
||||
using var reader = new DataReader(stream.GetInputStreamAt(0));
|
||||
await reader.LoadAsync((uint)stream.Size);
|
||||
var bytes = new byte[stream.Size];
|
||||
reader.ReadBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetFileName(string name)
|
||||
{
|
||||
return string.Concat(name, FileExtension);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
using Windows.UI;
|
||||
using Windows.UI.ViewManagement;
|
||||
|
||||
namespace VDD_Panel.Helpers;
|
||||
|
||||
// Helper class to workaround custom title bar bugs.
|
||||
// DISCLAIMER: The resource key names and color values used below are subject to change. Do not depend on them.
|
||||
// https://github.com/microsoft/TemplateStudio/issues/4516
|
||||
internal class TitleBarHelper
|
||||
{
|
||||
private const int WAINACTIVE = 0x00;
|
||||
private const int WAACTIVE = 0x01;
|
||||
private const int WMACTIVATE = 0x0006;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetActiveWindow();
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
|
||||
|
||||
public static void UpdateTitleBar(ElementTheme theme)
|
||||
{
|
||||
if (App.MainWindow.ExtendsContentIntoTitleBar)
|
||||
{
|
||||
if (theme == ElementTheme.Default)
|
||||
{
|
||||
var uiSettings = new UISettings();
|
||||
var background = uiSettings.GetColorValue(UIColorType.Background);
|
||||
|
||||
theme = background == Colors.White ? ElementTheme.Light : ElementTheme.Dark;
|
||||
}
|
||||
|
||||
if (theme == ElementTheme.Default)
|
||||
{
|
||||
theme = Application.Current.RequestedTheme == ApplicationTheme.Light ? ElementTheme.Light : ElementTheme.Dark;
|
||||
}
|
||||
|
||||
App.MainWindow.AppWindow.TitleBar.ButtonForegroundColor = theme switch
|
||||
{
|
||||
ElementTheme.Dark => Colors.White,
|
||||
ElementTheme.Light => Colors.Black,
|
||||
_ => Colors.Transparent
|
||||
};
|
||||
|
||||
App.MainWindow.AppWindow.TitleBar.ButtonHoverForegroundColor = theme switch
|
||||
{
|
||||
ElementTheme.Dark => Colors.White,
|
||||
ElementTheme.Light => Colors.Black,
|
||||
_ => Colors.Transparent
|
||||
};
|
||||
|
||||
App.MainWindow.AppWindow.TitleBar.ButtonHoverBackgroundColor = theme switch
|
||||
{
|
||||
ElementTheme.Dark => Color.FromArgb(0x33, 0xFF, 0xFF, 0xFF),
|
||||
ElementTheme.Light => Color.FromArgb(0x33, 0x00, 0x00, 0x00),
|
||||
_ => Colors.Transparent
|
||||
};
|
||||
|
||||
App.MainWindow.AppWindow.TitleBar.ButtonPressedBackgroundColor = theme switch
|
||||
{
|
||||
ElementTheme.Dark => Color.FromArgb(0x66, 0xFF, 0xFF, 0xFF),
|
||||
ElementTheme.Light => Color.FromArgb(0x66, 0x00, 0x00, 0x00),
|
||||
_ => Colors.Transparent
|
||||
};
|
||||
|
||||
App.MainWindow.AppWindow.TitleBar.BackgroundColor = Colors.Transparent;
|
||||
|
||||
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
|
||||
if (hwnd == GetActiveWindow())
|
||||
{
|
||||
SendMessage(hwnd, WMACTIVATE, WAINACTIVE, IntPtr.Zero);
|
||||
SendMessage(hwnd, WMACTIVATE, WAACTIVE, IntPtr.Zero);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendMessage(hwnd, WMACTIVATE, WAACTIVE, IntPtr.Zero);
|
||||
SendMessage(hwnd, WMACTIVATE, WAINACTIVE, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ApplySystemThemeToCaptionButtons()
|
||||
{
|
||||
var frame = App.AppTitlebar as FrameworkElement;
|
||||
if (frame != null)
|
||||
{
|
||||
UpdateTitleBar(frame.ActualTheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<windowex:WindowEx
|
||||
x:Class="VDD_Panel.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:VDD_Panel"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:windowex="using:WinUIEx"
|
||||
MinWidth="500"
|
||||
MinHeight="500"
|
||||
PersistenceId="MainWindow"
|
||||
mc:Ignorable="d">
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop/>
|
||||
</Window.SystemBackdrop>
|
||||
</windowex:WindowEx>
|
||||
@@ -0,0 +1,37 @@
|
||||
using VDD_Panel.Helpers;
|
||||
|
||||
using Windows.UI.ViewManagement;
|
||||
|
||||
namespace VDD_Panel;
|
||||
|
||||
public sealed partial class MainWindow : WindowEx
|
||||
{
|
||||
private Microsoft.UI.Dispatching.DispatcherQueue dispatcherQueue;
|
||||
|
||||
private UISettings settings;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
AppWindow.SetIcon(Path.Combine(AppContext.BaseDirectory, "Assets/WindowIcon.ico"));
|
||||
Content = null;
|
||||
Title = "AppDisplayName".GetLocalized();
|
||||
|
||||
// Theme change code picked from https://github.com/microsoft/WinUI-Gallery/pull/1239
|
||||
dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
|
||||
settings = new UISettings();
|
||||
settings.ColorValuesChanged += Settings_ColorValuesChanged; // cannot use FrameworkElement.ActualThemeChanged event
|
||||
}
|
||||
|
||||
// this handles updating the caption button colors correctly when indows system theme is changed
|
||||
// while the app is open
|
||||
private void Settings_ColorValuesChanged(UISettings sender, object args)
|
||||
{
|
||||
// This calls comes off-thread, hence we will need to dispatch it to current app's thread
|
||||
dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
TitleBarHelper.ApplySystemThemeToCaptionButtons();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace VDD_Panel.Models;
|
||||
|
||||
public class LocalSettingsOptions
|
||||
{
|
||||
public string? ApplicationDataFolder
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string? LocalSettingsFile
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<AppInstaller Uri="{AppInstallerUri}"
|
||||
Version="{Version}"
|
||||
xmlns="http://schemas.microsoft.com/appx/appinstaller/2017/2">
|
||||
|
||||
<!-- Read more about updating settings with Package.appinstaller at https://docs.microsoft.com/windows/msix/app-installer/update-settings -->
|
||||
|
||||
<MainBundle Name="{Name}"
|
||||
Version="{Version}"
|
||||
Publisher="{Publisher}"
|
||||
Uri="{MainPackageUri}"/>
|
||||
|
||||
<UpdateSettings>
|
||||
<OnLaunch HoursBetweenUpdateChecks="0" />
|
||||
</UpdateSettings>
|
||||
|
||||
</AppInstaller>
|
||||
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:genTemplate="http://schemas.microsoft.com/appx/developer/templatestudio"
|
||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
IgnorableNamespaces="uap rescap genTemplate">
|
||||
|
||||
<Identity
|
||||
Name="a7aa779e-510d-4d11-aad6-4cb00d23c8a5"
|
||||
Publisher="CN=alisa"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="a7aa779e-510d-4d11-aad6-4cb00d23c8a5" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>VDD Panel</DisplayName>
|
||||
<PublisherDisplayName>alisa</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="ms-resource:AppDisplayName"
|
||||
Description="ms-resource:AppDescription"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<desktop:Extension Category="windows.toastNotificationActivation">
|
||||
<desktop:ToastNotificationActivation ToastActivatorCLSID="17b96de3-608c-4471-85f1-1e9db4531575" />
|
||||
</desktop:Extension>
|
||||
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:ExeServer Executable="VDD Panel.exe" Arguments="----AppNotificationActivated:" DisplayName="Toast activator">
|
||||
<com:Class Id="17b96de3-608c-4471-85f1-1e9db4531575" DisplayName="Toast activator"/>
|
||||
</com:ExeServer>
|
||||
</com:ComServer>
|
||||
</com:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
|
||||
<genTemplate:Metadata>
|
||||
<genTemplate:Item Name="generator" Value="Template Studio"/>
|
||||
<genTemplate:Item Name="wizardVersion" Version="v5.5" />
|
||||
<genTemplate:Item Name="projectType" Value="NavView" />
|
||||
<genTemplate:Item Name="framework" Value="MVVMToolkit" />
|
||||
<genTemplate:Item Name="platform" Value="WinUI" />
|
||||
<genTemplate:Item Name="appmodel" Value="Desktop" />
|
||||
</genTemplate:Metadata>
|
||||
</Package>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>arm64</Platform>
|
||||
<RuntimeIdentifier>win10-arm64</RuntimeIdentifier>
|
||||
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
|
||||
<SelfContained>True</SelfContained>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>x64</Platform>
|
||||
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
|
||||
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
|
||||
<SelfContained>True</SelfContained>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>x86</Platform>
|
||||
<RuntimeIdentifier>win10-x86</RuntimeIdentifier>
|
||||
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
|
||||
<SelfContained>True</SelfContained>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"profiles": {
|
||||
"VDD Panel (Package)": {
|
||||
"commandName": "MsixPackage"
|
||||
},
|
||||
"VDD Panel (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
*Recommended Markdown Viewer: [Markdown Editor](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.MarkdownEditor2)*
|
||||
|
||||
## Getting Started
|
||||
|
||||
Browse and address `TODO:` comments in `View -> Task List` to learn the codebase and understand next steps for turning the generated code into production code.
|
||||
|
||||
Explore the [WinUI Gallery](https://www.microsoft.com/store/productId/9P3JFPWWDZRC) to learn about available controls and design patterns.
|
||||
|
||||
Relaunch Template Studio to modify the project by right-clicking on the project in `View -> Solution Explorer` then selecting `Add -> New Item (Template Studio)`.
|
||||
|
||||
## Publishing
|
||||
|
||||
For projects with MSIX packaging, right-click on the application project and select `Package and Publish -> Create App Packages...` to create an MSIX package.
|
||||
|
||||
For projects without MSIX packaging, follow the [deployment guide](https://docs.microsoft.com/windows/apps/windows-app-sdk/deploy-unpackaged-apps) or add the `Self-Contained` Feature to enable xcopy deployment.
|
||||
|
||||
## CI Pipelines
|
||||
|
||||
See [README.md](https://github.com/microsoft/TemplateStudio/blob/main/docs/WinUI/pipelines/README.md) for guidance on building and testing projects in CI pipelines.
|
||||
|
||||
## Changelog
|
||||
|
||||
See [releases](https://github.com/microsoft/TemplateStudio/releases) and [milestones](https://github.com/microsoft/TemplateStudio/milestones).
|
||||
|
||||
## Feedback
|
||||
|
||||
Bugs and feature requests should be filed at https://aka.ms/templatestudio.
|
||||
@@ -0,0 +1,72 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
using VDD_Panel.Activation;
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.Views;
|
||||
|
||||
namespace VDD_Panel.Services;
|
||||
|
||||
public class ActivationService : IActivationService
|
||||
{
|
||||
private readonly ActivationHandler<LaunchActivatedEventArgs> _defaultHandler;
|
||||
private readonly IEnumerable<IActivationHandler> _activationHandlers;
|
||||
private readonly IThemeSelectorService _themeSelectorService;
|
||||
private UIElement? _shell = null;
|
||||
|
||||
public ActivationService(ActivationHandler<LaunchActivatedEventArgs> defaultHandler, IEnumerable<IActivationHandler> activationHandlers, IThemeSelectorService themeSelectorService)
|
||||
{
|
||||
_defaultHandler = defaultHandler;
|
||||
_activationHandlers = activationHandlers;
|
||||
_themeSelectorService = themeSelectorService;
|
||||
}
|
||||
|
||||
public async Task ActivateAsync(object activationArgs)
|
||||
{
|
||||
// Execute tasks before activation.
|
||||
await InitializeAsync();
|
||||
|
||||
// Set the MainWindow Content.
|
||||
if (App.MainWindow.Content == null)
|
||||
{
|
||||
_shell = App.GetService<ShellPage>();
|
||||
App.MainWindow.Content = _shell ?? new Frame();
|
||||
}
|
||||
|
||||
// Handle activation via ActivationHandlers.
|
||||
await HandleActivationAsync(activationArgs);
|
||||
|
||||
// Activate the MainWindow.
|
||||
App.MainWindow.Activate();
|
||||
|
||||
// Execute tasks after activation.
|
||||
await StartupAsync();
|
||||
}
|
||||
|
||||
private async Task HandleActivationAsync(object activationArgs)
|
||||
{
|
||||
var activationHandler = _activationHandlers.FirstOrDefault(h => h.CanHandle(activationArgs));
|
||||
|
||||
if (activationHandler != null)
|
||||
{
|
||||
await activationHandler.HandleAsync(activationArgs);
|
||||
}
|
||||
|
||||
if (_defaultHandler.CanHandle(activationArgs))
|
||||
{
|
||||
await _defaultHandler.HandleAsync(activationArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
await _themeSelectorService.InitializeAsync().ConfigureAwait(false);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task StartupAsync()
|
||||
{
|
||||
await _themeSelectorService.SetRequestedThemeAsync();
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.Web;
|
||||
|
||||
using Microsoft.Windows.AppNotifications;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.ViewModels;
|
||||
|
||||
namespace VDD_Panel.Notifications;
|
||||
|
||||
public class AppNotificationService : IAppNotificationService
|
||||
{
|
||||
private readonly INavigationService _navigationService;
|
||||
|
||||
public AppNotificationService(INavigationService navigationService)
|
||||
{
|
||||
_navigationService = navigationService;
|
||||
}
|
||||
|
||||
~AppNotificationService()
|
||||
{
|
||||
Unregister();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
AppNotificationManager.Default.NotificationInvoked += OnNotificationInvoked;
|
||||
|
||||
AppNotificationManager.Default.Register();
|
||||
}
|
||||
|
||||
public void OnNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
|
||||
{
|
||||
// TODO: Handle notification invocations when your app is already running.
|
||||
|
||||
//// // Navigate to a specific page based on the notification arguments.
|
||||
//// if (ParseArguments(args.Argument)["action"] == "Settings")
|
||||
//// {
|
||||
//// App.MainWindow.DispatcherQueue.TryEnqueue(() =>
|
||||
//// {
|
||||
//// _navigationService.NavigateTo(typeof(SettingsViewModel).FullName!);
|
||||
//// });
|
||||
//// }
|
||||
|
||||
App.MainWindow.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
App.MainWindow.ShowMessageDialogAsync("TODO: Handle notification invocations when your app is already running.", "Notification Invoked");
|
||||
|
||||
App.MainWindow.BringToFront();
|
||||
});
|
||||
}
|
||||
|
||||
public bool Show(string payload)
|
||||
{
|
||||
var appNotification = new AppNotification(payload);
|
||||
|
||||
AppNotificationManager.Default.Show(appNotification);
|
||||
|
||||
return appNotification.Id != 0;
|
||||
}
|
||||
|
||||
public NameValueCollection ParseArguments(string arguments)
|
||||
{
|
||||
return HttpUtility.ParseQueryString(arguments);
|
||||
}
|
||||
|
||||
public void Unregister()
|
||||
{
|
||||
AppNotificationManager.Default.Unregister();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.Core.Contracts.Services;
|
||||
using VDD_Panel.Core.Helpers;
|
||||
using VDD_Panel.Helpers;
|
||||
using VDD_Panel.Models;
|
||||
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace VDD_Panel.Services;
|
||||
|
||||
public class LocalSettingsService : ILocalSettingsService
|
||||
{
|
||||
private const string _defaultApplicationDataFolder = "VDD Panel/ApplicationData";
|
||||
private const string _defaultLocalSettingsFile = "LocalSettings.json";
|
||||
|
||||
private readonly IFileService _fileService;
|
||||
private readonly LocalSettingsOptions _options;
|
||||
|
||||
private readonly string _localApplicationData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
private readonly string _applicationDataFolder;
|
||||
private readonly string _localsettingsFile;
|
||||
|
||||
private IDictionary<string, object> _settings;
|
||||
|
||||
private bool _isInitialized;
|
||||
|
||||
public LocalSettingsService(IFileService fileService, IOptions<LocalSettingsOptions> options)
|
||||
{
|
||||
_fileService = fileService;
|
||||
_options = options.Value;
|
||||
|
||||
_applicationDataFolder = Path.Combine(_localApplicationData, _options.ApplicationDataFolder ?? _defaultApplicationDataFolder);
|
||||
_localsettingsFile = _options.LocalSettingsFile ?? _defaultLocalSettingsFile;
|
||||
|
||||
_settings = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
_settings = await Task.Run(() => _fileService.Read<IDictionary<string, object>>(_applicationDataFolder, _localsettingsFile)) ?? new Dictionary<string, object>();
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<T?> ReadSettingAsync<T>(string key)
|
||||
{
|
||||
if (RuntimeHelper.IsMSIX)
|
||||
{
|
||||
if (ApplicationData.Current.LocalSettings.Values.TryGetValue(key, out var obj))
|
||||
{
|
||||
return await Json.ToObjectAsync<T>((string)obj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await InitializeAsync();
|
||||
|
||||
if (_settings != null && _settings.TryGetValue(key, out var obj))
|
||||
{
|
||||
return await Json.ToObjectAsync<T>((string)obj);
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public async Task SaveSettingAsync<T>(string key, T value)
|
||||
{
|
||||
if (RuntimeHelper.IsMSIX)
|
||||
{
|
||||
ApplicationData.Current.LocalSettings.Values[key] = await Json.StringifyAsync(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
await InitializeAsync();
|
||||
|
||||
_settings[key] = await Json.StringifyAsync(value);
|
||||
|
||||
await Task.Run(() => _fileService.Save(_applicationDataFolder, _localsettingsFile, _settings));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.Contracts.ViewModels;
|
||||
using VDD_Panel.Helpers;
|
||||
|
||||
namespace VDD_Panel.Services;
|
||||
|
||||
// For more information on navigation between pages see
|
||||
// https://github.com/microsoft/TemplateStudio/blob/main/docs/WinUI/navigation.md
|
||||
public class NavigationService : INavigationService
|
||||
{
|
||||
private readonly IPageService _pageService;
|
||||
private object? _lastParameterUsed;
|
||||
private Frame? _frame;
|
||||
|
||||
public event NavigatedEventHandler? Navigated;
|
||||
|
||||
public Frame? Frame
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_frame == null)
|
||||
{
|
||||
_frame = App.MainWindow.Content as Frame;
|
||||
RegisterFrameEvents();
|
||||
}
|
||||
|
||||
return _frame;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
UnregisterFrameEvents();
|
||||
_frame = value;
|
||||
RegisterFrameEvents();
|
||||
}
|
||||
}
|
||||
|
||||
[MemberNotNullWhen(true, nameof(Frame), nameof(_frame))]
|
||||
public bool CanGoBack => Frame != null && Frame.CanGoBack;
|
||||
|
||||
public NavigationService(IPageService pageService)
|
||||
{
|
||||
_pageService = pageService;
|
||||
}
|
||||
|
||||
private void RegisterFrameEvents()
|
||||
{
|
||||
if (_frame != null)
|
||||
{
|
||||
_frame.Navigated += OnNavigated;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterFrameEvents()
|
||||
{
|
||||
if (_frame != null)
|
||||
{
|
||||
_frame.Navigated -= OnNavigated;
|
||||
}
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
if (CanGoBack)
|
||||
{
|
||||
var vmBeforeNavigation = _frame.GetPageViewModel();
|
||||
_frame.GoBack();
|
||||
if (vmBeforeNavigation is INavigationAware navigationAware)
|
||||
{
|
||||
navigationAware.OnNavigatedFrom();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false)
|
||||
{
|
||||
var pageType = _pageService.GetPageType(pageKey);
|
||||
|
||||
if (_frame != null && (_frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(_lastParameterUsed))))
|
||||
{
|
||||
_frame.Tag = clearNavigation;
|
||||
var vmBeforeNavigation = _frame.GetPageViewModel();
|
||||
var navigated = _frame.Navigate(pageType, parameter);
|
||||
if (navigated)
|
||||
{
|
||||
_lastParameterUsed = parameter;
|
||||
if (vmBeforeNavigation is INavigationAware navigationAware)
|
||||
{
|
||||
navigationAware.OnNavigatedFrom();
|
||||
}
|
||||
}
|
||||
|
||||
return navigated;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnNavigated(object sender, NavigationEventArgs e)
|
||||
{
|
||||
if (sender is Frame frame)
|
||||
{
|
||||
var clearNavigation = (bool)frame.Tag;
|
||||
if (clearNavigation)
|
||||
{
|
||||
frame.BackStack.Clear();
|
||||
}
|
||||
|
||||
if (frame.GetPageViewModel() is INavigationAware navigationAware)
|
||||
{
|
||||
navigationAware.OnNavigatedTo(e.Parameter);
|
||||
}
|
||||
|
||||
Navigated?.Invoke(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.Helpers;
|
||||
using VDD_Panel.ViewModels;
|
||||
|
||||
namespace VDD_Panel.Services;
|
||||
|
||||
public class NavigationViewService : INavigationViewService
|
||||
{
|
||||
private readonly INavigationService _navigationService;
|
||||
|
||||
private readonly IPageService _pageService;
|
||||
|
||||
private NavigationView? _navigationView;
|
||||
|
||||
public IList<object>? MenuItems => _navigationView?.MenuItems;
|
||||
|
||||
public object? SettingsItem => _navigationView?.SettingsItem;
|
||||
|
||||
public NavigationViewService(INavigationService navigationService, IPageService pageService)
|
||||
{
|
||||
_navigationService = navigationService;
|
||||
_pageService = pageService;
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(_navigationView))]
|
||||
public void Initialize(NavigationView navigationView)
|
||||
{
|
||||
_navigationView = navigationView;
|
||||
_navigationView.BackRequested += OnBackRequested;
|
||||
_navigationView.ItemInvoked += OnItemInvoked;
|
||||
}
|
||||
|
||||
public void UnregisterEvents()
|
||||
{
|
||||
if (_navigationView != null)
|
||||
{
|
||||
_navigationView.BackRequested -= OnBackRequested;
|
||||
_navigationView.ItemInvoked -= OnItemInvoked;
|
||||
}
|
||||
}
|
||||
|
||||
public NavigationViewItem? GetSelectedItem(Type pageType)
|
||||
{
|
||||
if (_navigationView != null)
|
||||
{
|
||||
return GetSelectedItem(_navigationView.MenuItems, pageType) ?? GetSelectedItem(_navigationView.FooterMenuItems, pageType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnBackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) => _navigationService.GoBack();
|
||||
|
||||
private void OnItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
|
||||
{
|
||||
if (args.IsSettingsInvoked)
|
||||
{
|
||||
_navigationService.NavigateTo(typeof(SettingsViewModel).FullName!);
|
||||
}
|
||||
else
|
||||
{
|
||||
var selectedItem = args.InvokedItemContainer as NavigationViewItem;
|
||||
|
||||
if (selectedItem?.GetValue(NavigationHelper.NavigateToProperty) is string pageKey)
|
||||
{
|
||||
_navigationService.NavigateTo(pageKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NavigationViewItem? GetSelectedItem(IEnumerable<object> menuItems, Type pageType)
|
||||
{
|
||||
foreach (var item in menuItems.OfType<NavigationViewItem>())
|
||||
{
|
||||
if (IsMenuItemForPageType(item, pageType))
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
var selectedChild = GetSelectedItem(item.MenuItems, pageType);
|
||||
if (selectedChild != null)
|
||||
{
|
||||
return selectedChild;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsMenuItemForPageType(NavigationViewItem menuItem, Type sourcePageType)
|
||||
{
|
||||
if (menuItem.GetValue(NavigationHelper.NavigateToProperty) is string pageKey)
|
||||
{
|
||||
return _pageService.GetPageType(pageKey) == sourcePageType;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.ViewModels;
|
||||
using VDD_Panel.Views;
|
||||
|
||||
namespace VDD_Panel.Services;
|
||||
|
||||
public class PageService : IPageService
|
||||
{
|
||||
private readonly Dictionary<string, Type> _pages = new();
|
||||
|
||||
public PageService()
|
||||
{
|
||||
Configure<MainViewModel, MainPage>();
|
||||
Configure<ConfigureViewModel, ConfigurePage>();
|
||||
Configure<GitHubViewModel, GitHubPage>();
|
||||
Configure<SettingsViewModel, SettingsPage>();
|
||||
}
|
||||
|
||||
public Type GetPageType(string key)
|
||||
{
|
||||
Type? pageType;
|
||||
lock (_pages)
|
||||
{
|
||||
if (!_pages.TryGetValue(key, out pageType))
|
||||
{
|
||||
throw new ArgumentException($"Page not found: {key}. Did you forget to call PageService.Configure?");
|
||||
}
|
||||
}
|
||||
|
||||
return pageType;
|
||||
}
|
||||
|
||||
private void Configure<VM, V>()
|
||||
where VM : ObservableObject
|
||||
where V : Page
|
||||
{
|
||||
lock (_pages)
|
||||
{
|
||||
var key = typeof(VM).FullName!;
|
||||
if (_pages.ContainsKey(key))
|
||||
{
|
||||
throw new ArgumentException($"The key {key} is already configured in PageService");
|
||||
}
|
||||
|
||||
var type = typeof(V);
|
||||
if (_pages.ContainsValue(type))
|
||||
{
|
||||
throw new ArgumentException($"This type is already configured with key {_pages.First(p => p.Value == type).Key}");
|
||||
}
|
||||
|
||||
_pages.Add(key, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.Helpers;
|
||||
|
||||
namespace VDD_Panel.Services;
|
||||
|
||||
public class ThemeSelectorService : IThemeSelectorService
|
||||
{
|
||||
private const string SettingsKey = "AppBackgroundRequestedTheme";
|
||||
|
||||
public ElementTheme Theme { get; set; } = ElementTheme.Default;
|
||||
|
||||
private readonly ILocalSettingsService _localSettingsService;
|
||||
|
||||
public ThemeSelectorService(ILocalSettingsService localSettingsService)
|
||||
{
|
||||
_localSettingsService = localSettingsService;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
Theme = await LoadThemeFromSettingsAsync();
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task SetThemeAsync(ElementTheme theme)
|
||||
{
|
||||
Theme = theme;
|
||||
|
||||
await SetRequestedThemeAsync();
|
||||
await SaveThemeInSettingsAsync(Theme);
|
||||
}
|
||||
|
||||
public async Task SetRequestedThemeAsync()
|
||||
{
|
||||
if (App.MainWindow.Content is FrameworkElement rootElement)
|
||||
{
|
||||
rootElement.RequestedTheme = Theme;
|
||||
|
||||
TitleBarHelper.UpdateTitleBar(Theme);
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task<ElementTheme> LoadThemeFromSettingsAsync()
|
||||
{
|
||||
var themeName = await _localSettingsService.ReadSettingAsync<string>(SettingsKey);
|
||||
|
||||
if (Enum.TryParse(themeName, out ElementTheme cacheTheme))
|
||||
{
|
||||
return cacheTheme;
|
||||
}
|
||||
|
||||
return ElementTheme.Default;
|
||||
}
|
||||
|
||||
private async Task SaveThemeInSettingsAsync(ElementTheme theme)
|
||||
{
|
||||
await _localSettingsService.SaveSettingAsync(SettingsKey, theme.ToString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
|
||||
namespace VDD_Panel.Services;
|
||||
|
||||
public class WebViewService : IWebViewService
|
||||
{
|
||||
private WebView2? _webView;
|
||||
|
||||
public Uri? Source => _webView?.Source;
|
||||
|
||||
[MemberNotNullWhen(true, nameof(_webView))]
|
||||
public bool CanGoBack => _webView != null && _webView.CanGoBack;
|
||||
|
||||
[MemberNotNullWhen(true, nameof(_webView))]
|
||||
public bool CanGoForward => _webView != null && _webView.CanGoForward;
|
||||
|
||||
public event EventHandler<CoreWebView2WebErrorStatus>? NavigationCompleted;
|
||||
|
||||
public WebViewService()
|
||||
{
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(_webView))]
|
||||
public void Initialize(WebView2 webView)
|
||||
{
|
||||
_webView = webView;
|
||||
_webView.NavigationCompleted += OnWebViewNavigationCompleted;
|
||||
}
|
||||
|
||||
public void GoBack() => _webView?.GoBack();
|
||||
|
||||
public void GoForward() => _webView?.GoForward();
|
||||
|
||||
public void Reload() => _webView?.Reload();
|
||||
|
||||
public void UnregisterEvents()
|
||||
{
|
||||
if (_webView != null)
|
||||
{
|
||||
_webView.NavigationCompleted -= OnWebViewNavigationCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWebViewNavigationCompleted(WebView2 sender, CoreWebView2NavigationCompletedEventArgs args) => NavigationCompleted?.Invoke(this, args.WebErrorStatus);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AppDisplayName" xml:space="preserve">
|
||||
<value>VDD Panel</value>
|
||||
</data>
|
||||
<data name="AppDescription" xml:space="preserve">
|
||||
<value>VDD Panel</value>
|
||||
</data>
|
||||
<data name="Shell_Main.Content" xml:space="preserve">
|
||||
<value>Main</value>
|
||||
</data>
|
||||
<data name="Shell_Configure.Content" xml:space="preserve">
|
||||
<value>Configure</value>
|
||||
</data>
|
||||
<data name="WebView_Loading.Text" xml:space="preserve">
|
||||
<value>Loading...</value>
|
||||
</data>
|
||||
<data name="WebView_FailedMessage.Text" xml:space="preserve">
|
||||
<value>Page failed to load. Check connection and reload.</value>
|
||||
</data>
|
||||
<data name="BrowserBackButton.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Browser back</value>
|
||||
</data>
|
||||
<data name="BrowserBackButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Browser back</value>
|
||||
</data>
|
||||
<data name="BrowserForwardButton.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Browser forward</value>
|
||||
</data>
|
||||
<data name="BrowserForwardButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Browser forward</value>
|
||||
</data>
|
||||
<data name="WebView_Reload.Content" xml:space="preserve">
|
||||
<value>Reload</value>
|
||||
</data>
|
||||
<data name="ReloadButton.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Reload</value>
|
||||
</data>
|
||||
<data name="ReloadButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Reload</value>
|
||||
</data>
|
||||
<data name="OpenInBrowserButton.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Open in browser</value>
|
||||
</data>
|
||||
<data name="OpenInBrowserButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Open in browser</value>
|
||||
</data>
|
||||
<data name="Shell_GitHub.Content" xml:space="preserve">
|
||||
<value>GitHub</value>
|
||||
</data>
|
||||
<data name="Settings_Personalization.Text" xml:space="preserve">
|
||||
<value>Personalization</value>
|
||||
</data>
|
||||
<data name="Settings_Theme.Text" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Settings_Theme_Light.Content" xml:space="preserve">
|
||||
<value>Light</value>
|
||||
</data>
|
||||
<data name="Settings_Theme_Dark.Content" xml:space="preserve">
|
||||
<value>Dark</value>
|
||||
</data>
|
||||
<data name="Settings_Theme_Default.Content" xml:space="preserve">
|
||||
<value>Default</value>
|
||||
</data>
|
||||
<data name="Settings_About.Text" xml:space="preserve">
|
||||
<value>About this application</value>
|
||||
</data>
|
||||
<data name="Settings_AboutDescription.Text" xml:space="preserve">
|
||||
<value>TODO: Replace with your app description.</value>
|
||||
</data>
|
||||
<data name="SettingsPage_PrivacyTermsLink.Content" xml:space="preserve">
|
||||
<value>Privacy Statement</value>
|
||||
</data>
|
||||
<data name="SettingsPage_PrivacyTermsLink.NavigateUri" xml:space="preserve">
|
||||
<value>https://YourPrivacyUrlGoesHere/</value>
|
||||
</data>
|
||||
<data name="AppNotificationSamplePayload" xml:space="preserve">
|
||||
<value><toast launch="action=ToastClick">
|
||||
<visual>
|
||||
<binding template="ToastGeneric">
|
||||
<text>App Notification</text>
|
||||
<text></text>
|
||||
<image placement="appLogoOverride" hint-crop="circle" src="{0}Assets/WindowIcon.ico"/>
|
||||
</binding>
|
||||
</visual>
|
||||
<actions>
|
||||
<action content="Settings" arguments="action=Settings"/>
|
||||
</actions>
|
||||
</toast></value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,9 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<x:Double x:Key="LargeFontSize">24</x:Double>
|
||||
|
||||
<x:Double x:Key="MediumFontSize">16</x:Double>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,20 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Style x:Key="PageTitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="FontWeight" Value="SemiLight" />
|
||||
<Setter Property="FontSize" Value="{StaticResource LargeFontSize}" />
|
||||
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
|
||||
<Setter Property="TextWrapping" Value="NoWrap" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="BodyTextStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="{StaticResource MediumFontSize}" />
|
||||
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
|
||||
<Setter Property="TextWrapping" Value="Wrap" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,36 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Thickness x:Key="LargeTopMargin">0,36,0,0</Thickness>
|
||||
<Thickness x:Key="LargeTopBottomMargin">0,36,0,36</Thickness>
|
||||
|
||||
<Thickness x:Key="MediumTopMargin">0,24,0,0</Thickness>
|
||||
<Thickness x:Key="MediumTopBottomMargin">0,24,0,24</Thickness>
|
||||
<Thickness x:Key="MediumLeftRightMargin">24,0,24,0</Thickness>
|
||||
<Thickness x:Key="MediumBottomMargin">0,0,0,24</Thickness>
|
||||
|
||||
<Thickness x:Key="SmallLeftMargin">12,0,0,0</Thickness>
|
||||
<Thickness x:Key="SmallLeftRightMargin">12,0,12,0</Thickness>
|
||||
<Thickness x:Key="SmallTopMargin">0,12,0,0</Thickness>
|
||||
<Thickness x:Key="SmallRightMargin">0,0,12,0</Thickness>
|
||||
<Thickness x:Key="SmallTopBottomMargin">0,12,0,12</Thickness>
|
||||
|
||||
<Thickness x:Key="XSmallLeftMargin">8,0,0,0</Thickness>
|
||||
<Thickness x:Key="XSmallTopMargin">0,8,0,0</Thickness>
|
||||
<Thickness x:Key="XSmallLeftTopRightBottomMargin">8,8,8,8</Thickness>
|
||||
|
||||
<Thickness x:Key="XXSmallTopMargin">0,4,0,0</Thickness>
|
||||
<Thickness x:Key="XXSmallLeftTopRightBottomMargin">4,4,4,4</Thickness>
|
||||
|
||||
<Thickness x:Key="NavigationViewContentGridBorderThickness">1,1,0,0</Thickness>
|
||||
<CornerRadius x:Key="NavigationViewContentGridCornerRadius">8,0,0,0</CornerRadius>
|
||||
<Thickness x:Key="NavigationViewContentMargin">0,48,0,0</Thickness>
|
||||
<Thickness x:Key="NavigationViewHeaderMargin">56,34,0,0</Thickness>
|
||||
<Thickness x:Key="NavigationViewPageContentMargin">56,24,56,0</Thickness>
|
||||
|
||||
<Thickness x:Key="MenuBarContentMargin">36,24,36,0</Thickness>
|
||||
|
||||
<Thickness x:Key="SettingsPageHyperlinkButtonMargin">-12,4,0,0</Thickness>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,11 @@
|
||||
<xml
|
||||
xmlns:genTemplate="http://schemas.microsoft.com/appx/developer/templatestudio">
|
||||
<genTemplate:Metadata>
|
||||
<genTemplate:Item Name="generator" Value="Template Studio"/>
|
||||
<genTemplate:Item Name="wizardVersion" Version="v5.5" />
|
||||
<genTemplate:Item Name="projectType" Value="NavView" />
|
||||
<genTemplate:Item Name="framework" Value="MVVMToolkit" />
|
||||
<genTemplate:Item Name="platform" Value="WinUI" />
|
||||
<genTemplate:Item Name="appmodel" Value="Desktop" />
|
||||
</genTemplate:Metadata>
|
||||
</xml>
|
||||
@@ -0,0 +1 @@
|
||||
global using WinUIEx;
|
||||
@@ -0,0 +1,47 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>VDD_Panel</RootNamespace>
|
||||
<ApplicationIcon>Assets/WindowIcon.ico</ApplicationIcon>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Platforms>x86;x64;arm64</Platforms>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<PublishProfile>Properties\PublishProfiles\win10-$(Platform).pubxml</PublishProfile>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231008000" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||
<PackageReference Include="WinUIEx" Version="2.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VDD Panel.Core\VDD Panel.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,10 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace VDD_Panel.ViewModels;
|
||||
|
||||
public partial class ConfigureViewModel : ObservableRecipient
|
||||
{
|
||||
public ConfigureViewModel()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.Contracts.ViewModels;
|
||||
|
||||
namespace VDD_Panel.ViewModels;
|
||||
|
||||
// TODO: Review best practices and distribution guidelines for WebView2.
|
||||
// https://docs.microsoft.com/microsoft-edge/webview2/get-started/winui
|
||||
// https://docs.microsoft.com/microsoft-edge/webview2/concepts/developer-guide
|
||||
// https://docs.microsoft.com/microsoft-edge/webview2/concepts/distribution
|
||||
public partial class GitHubViewModel : ObservableRecipient, INavigationAware
|
||||
{
|
||||
// TODO: Set the default URL to display.
|
||||
[ObservableProperty]
|
||||
private Uri source = new("https://github.com/itsmikethetech/Virtual-Display-Driver");
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isLoading = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool hasFailures;
|
||||
|
||||
public IWebViewService WebViewService
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public GitHubViewModel(IWebViewService webViewService)
|
||||
{
|
||||
WebViewService = webViewService;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OpenInBrowser()
|
||||
{
|
||||
if (WebViewService.Source != null)
|
||||
{
|
||||
await Windows.System.Launcher.LaunchUriAsync(WebViewService.Source);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Reload()
|
||||
{
|
||||
WebViewService.Reload();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(BrowserCanGoForward))]
|
||||
private void BrowserForward()
|
||||
{
|
||||
if (WebViewService.CanGoForward)
|
||||
{
|
||||
WebViewService.GoForward();
|
||||
}
|
||||
}
|
||||
|
||||
private bool BrowserCanGoForward()
|
||||
{
|
||||
return WebViewService.CanGoForward;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(BrowserCanGoBack))]
|
||||
private void BrowserBack()
|
||||
{
|
||||
if (WebViewService.CanGoBack)
|
||||
{
|
||||
WebViewService.GoBack();
|
||||
}
|
||||
}
|
||||
|
||||
private bool BrowserCanGoBack()
|
||||
{
|
||||
return WebViewService.CanGoBack;
|
||||
}
|
||||
|
||||
public void OnNavigatedTo(object parameter)
|
||||
{
|
||||
WebViewService.NavigationCompleted += OnNavigationCompleted;
|
||||
}
|
||||
|
||||
public void OnNavigatedFrom()
|
||||
{
|
||||
WebViewService.UnregisterEvents();
|
||||
WebViewService.NavigationCompleted -= OnNavigationCompleted;
|
||||
}
|
||||
|
||||
private void OnNavigationCompleted(object? sender, CoreWebView2WebErrorStatus webErrorStatus)
|
||||
{
|
||||
IsLoading = false;
|
||||
BrowserBackCommand.NotifyCanExecuteChanged();
|
||||
BrowserForwardCommand.NotifyCanExecuteChanged();
|
||||
|
||||
if (webErrorStatus != default)
|
||||
{
|
||||
HasFailures = true;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OnRetry()
|
||||
{
|
||||
HasFailures = false;
|
||||
IsLoading = true;
|
||||
WebViewService?.Reload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace VDD_Panel.ViewModels;
|
||||
|
||||
public partial class MainViewModel : ObservableRecipient
|
||||
{
|
||||
public MainViewModel()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Reflection;
|
||||
using System.Windows.Input;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.Helpers;
|
||||
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace VDD_Panel.ViewModels;
|
||||
|
||||
public partial class SettingsViewModel : ObservableRecipient
|
||||
{
|
||||
private readonly IThemeSelectorService _themeSelectorService;
|
||||
|
||||
[ObservableProperty]
|
||||
private ElementTheme _elementTheme;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _versionDescription;
|
||||
|
||||
public ICommand SwitchThemeCommand
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public SettingsViewModel(IThemeSelectorService themeSelectorService)
|
||||
{
|
||||
_themeSelectorService = themeSelectorService;
|
||||
_elementTheme = _themeSelectorService.Theme;
|
||||
_versionDescription = GetVersionDescription();
|
||||
|
||||
SwitchThemeCommand = new RelayCommand<ElementTheme>(
|
||||
async (param) =>
|
||||
{
|
||||
if (ElementTheme != param)
|
||||
{
|
||||
ElementTheme = param;
|
||||
await _themeSelectorService.SetThemeAsync(param);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static string GetVersionDescription()
|
||||
{
|
||||
Version version;
|
||||
|
||||
if (RuntimeHelper.IsMSIX)
|
||||
{
|
||||
var packageVersion = Package.Current.Id.Version;
|
||||
|
||||
version = new(packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision);
|
||||
}
|
||||
else
|
||||
{
|
||||
version = Assembly.GetExecutingAssembly().GetName().Version!;
|
||||
}
|
||||
|
||||
return $"{"AppDisplayName".GetLocalized()} - {version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.Views;
|
||||
|
||||
namespace VDD_Panel.ViewModels;
|
||||
|
||||
public partial class ShellViewModel : ObservableRecipient
|
||||
{
|
||||
[ObservableProperty]
|
||||
private bool isBackEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private object? selected;
|
||||
|
||||
public INavigationService NavigationService
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public INavigationViewService NavigationViewService
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public ShellViewModel(INavigationService navigationService, INavigationViewService navigationViewService)
|
||||
{
|
||||
NavigationService = navigationService;
|
||||
NavigationService.Navigated += OnNavigated;
|
||||
NavigationViewService = navigationViewService;
|
||||
}
|
||||
|
||||
private void OnNavigated(object sender, NavigationEventArgs e)
|
||||
{
|
||||
IsBackEnabled = NavigationService.CanGoBack;
|
||||
|
||||
if (e.SourcePageType == typeof(SettingsPage))
|
||||
{
|
||||
Selected = NavigationViewService.SettingsItem;
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedItem = NavigationViewService.GetSelectedItem(e.SourcePageType);
|
||||
if (selectedItem != null)
|
||||
{
|
||||
Selected = selectedItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<Page
|
||||
x:Class="VDD_Panel.Views.ConfigurePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="ContentArea">
|
||||
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
using VDD_Panel.ViewModels;
|
||||
|
||||
namespace VDD_Panel.Views;
|
||||
|
||||
public sealed partial class ConfigurePage : Page
|
||||
{
|
||||
public ConfigureViewModel ViewModel
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public ConfigurePage()
|
||||
{
|
||||
ViewModel = App.GetService<ConfigureViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<Page
|
||||
x:Class="VDD_Panel.Views.GitHubPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:behaviors="using:VDD_Panel.Behaviors"
|
||||
behaviors:NavigationViewHeaderBehavior.HeaderMode="Never"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="ContentArea">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<WebView2
|
||||
x:Name="WebView"
|
||||
Source="{x:Bind ViewModel.Source, Mode=OneWay}" />
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{x:Bind ViewModel.IsLoading, Mode=OneWay}">
|
||||
<ProgressRing IsActive="{x:Bind ViewModel.IsLoading, Mode=OneWay}" />
|
||||
<TextBlock x:Uid="WebView_Loading" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{x:Bind ViewModel.HasFailures, Mode=OneWay}">
|
||||
<TextBlock x:Uid="WebView_FailedMessage" HorizontalAlignment="Center" TextWrapping="WrapWholeWords" />
|
||||
<HyperlinkButton x:Uid="WebView_Reload" Command="{x:Bind ViewModel.ReloadCommand}" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button x:Uid="BrowserBackButton" Command="{x:Bind ViewModel.BrowserBackCommand, Mode=OneWay}" Margin="{StaticResource XSmallLeftTopRightBottomMargin}" Padding="{StaticResource XXSmallLeftTopRightBottomMargin}">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</Button>
|
||||
<Button x:Uid="BrowserForwardButton" Command="{x:Bind ViewModel.BrowserForwardCommand, Mode=OneWay}" Margin="{StaticResource XSmallLeftTopRightBottomMargin}" Padding="{StaticResource XXSmallLeftTopRightBottomMargin}">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</Button>
|
||||
</StackPanel >
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button x:Uid="ReloadButton" Command="{x:Bind ViewModel.ReloadCommand}" Margin="{StaticResource XSmallLeftTopRightBottomMargin}" Padding="{StaticResource XXSmallLeftTopRightBottomMargin}">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</Button>
|
||||
<Button x:Uid="OpenInBrowserButton" Command="{x:Bind ViewModel.OpenInBrowserCommand}" Margin="{StaticResource XSmallLeftTopRightBottomMargin}" Padding="{StaticResource XXSmallLeftTopRightBottomMargin}">
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,22 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
using VDD_Panel.ViewModels;
|
||||
|
||||
namespace VDD_Panel.Views;
|
||||
|
||||
// To learn more about WebView2, see https://docs.microsoft.com/microsoft-edge/webview2/.
|
||||
public sealed partial class GitHubPage : Page
|
||||
{
|
||||
public GitHubViewModel ViewModel
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public GitHubPage()
|
||||
{
|
||||
ViewModel = App.GetService<GitHubViewModel>();
|
||||
InitializeComponent();
|
||||
|
||||
ViewModel.WebViewService.Initialize(WebView);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<Page
|
||||
x:Class="VDD_Panel.Views.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="ContentArea">
|
||||
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
using VDD_Panel.ViewModels;
|
||||
|
||||
namespace VDD_Panel.Views;
|
||||
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
public MainViewModel ViewModel
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
ViewModel = App.GetService<MainViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<Page
|
||||
x:Class="VDD_Panel.Views.SettingsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:helpers="using:VDD_Panel.Helpers"
|
||||
xmlns:xaml="using:Microsoft.UI.Xaml"
|
||||
mc:Ignorable="d">
|
||||
<Page.Resources>
|
||||
<helpers:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
|
||||
</Page.Resources>
|
||||
<Grid>
|
||||
<StackPanel
|
||||
x:Name="ContentArea">
|
||||
<TextBlock x:Uid="Settings_Personalization" Style="{ThemeResource SubtitleTextBlockStyle}" />
|
||||
<StackPanel Margin="{StaticResource SmallTopBottomMargin}">
|
||||
<TextBlock x:Uid="Settings_Theme" />
|
||||
|
||||
<StackPanel Margin="{StaticResource XSmallTopMargin}">
|
||||
<RadioButton
|
||||
x:Uid="Settings_Theme_Light"
|
||||
Command="{x:Bind ViewModel.SwitchThemeCommand}"
|
||||
IsChecked="{x:Bind ViewModel.ElementTheme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Light, Mode=OneWay}"
|
||||
FontSize="15"
|
||||
GroupName="AppTheme">
|
||||
<RadioButton.CommandParameter>
|
||||
<xaml:ElementTheme>Light</xaml:ElementTheme>
|
||||
</RadioButton.CommandParameter>
|
||||
</RadioButton>
|
||||
<RadioButton
|
||||
x:Uid="Settings_Theme_Dark"
|
||||
Command="{x:Bind ViewModel.SwitchThemeCommand}"
|
||||
IsChecked="{x:Bind ViewModel.ElementTheme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Dark, Mode=OneWay}"
|
||||
FontSize="15"
|
||||
GroupName="AppTheme">
|
||||
<RadioButton.CommandParameter>
|
||||
<xaml:ElementTheme>Dark</xaml:ElementTheme>
|
||||
</RadioButton.CommandParameter>
|
||||
</RadioButton>
|
||||
<RadioButton
|
||||
x:Uid="Settings_Theme_Default"
|
||||
Command="{x:Bind ViewModel.SwitchThemeCommand}"
|
||||
IsChecked="{x:Bind ViewModel.ElementTheme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Default, Mode=OneWay}"
|
||||
FontSize="15"
|
||||
GroupName="AppTheme">
|
||||
<RadioButton.CommandParameter>
|
||||
<xaml:ElementTheme>Default</xaml:ElementTheme>
|
||||
</RadioButton.CommandParameter>
|
||||
</RadioButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock x:Uid="Settings_About" Style="{ThemeResource SubtitleTextBlockStyle}" />
|
||||
|
||||
<StackPanel Margin="{StaticResource XSmallTopMargin}">
|
||||
<TextBlock Text="{x:Bind ViewModel.VersionDescription, Mode=OneWay}" Style="{ThemeResource BodyTextBlockStyle}" />
|
||||
|
||||
<TextBlock
|
||||
x:Uid="Settings_AboutDescription"
|
||||
Margin="{StaticResource XSmallTopMargin}"
|
||||
Style="{ThemeResource BodyTextBlockStyle}" />
|
||||
<HyperlinkButton x:Uid="SettingsPage_PrivacyTermsLink" Margin="{StaticResource SettingsPageHyperlinkButtonMargin}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
using VDD_Panel.ViewModels;
|
||||
|
||||
namespace VDD_Panel.Views;
|
||||
|
||||
// TODO: Set the URL for your privacy policy by updating SettingsPage_PrivacyTermsLink.NavigateUri in Resources.resw.
|
||||
public sealed partial class SettingsPage : Page
|
||||
{
|
||||
public SettingsViewModel ViewModel
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public SettingsPage()
|
||||
{
|
||||
ViewModel = App.GetService<SettingsViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<Page
|
||||
x:Class="VDD_Panel.Views.ShellPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:helpers="using:VDD_Panel.Helpers"
|
||||
xmlns:behaviors="using:VDD_Panel.Behaviors"
|
||||
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||
Loaded="OnLoaded">
|
||||
|
||||
<Grid>
|
||||
<Grid x:Name="AppTitleBar"
|
||||
Canvas.ZIndex="1"
|
||||
Height="{Binding ElementName=NavigationViewControl, Path=CompactPaneLength}"
|
||||
IsHitTestVisible="True"
|
||||
VerticalAlignment="Top">
|
||||
<Image Source="/Assets/WindowIcon.ico"
|
||||
HorizontalAlignment="Left"
|
||||
Width="16"
|
||||
Height="16" />
|
||||
<TextBlock x:Name="AppTitleBarText"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Margin="28,0,0,0"/>
|
||||
</Grid>
|
||||
<NavigationView
|
||||
x:Name="NavigationViewControl"
|
||||
Canvas.ZIndex="0"
|
||||
IsBackButtonVisible="Visible"
|
||||
IsBackEnabled="{x:Bind ViewModel.IsBackEnabled, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.Selected, Mode=OneWay}"
|
||||
IsSettingsVisible="True"
|
||||
ExpandedModeThresholdWidth="1280"
|
||||
DisplayModeChanged="NavigationViewControl_DisplayModeChanged"
|
||||
Header="{x:Bind ((ContentControl)ViewModel.Selected).Content, Mode=OneWay}">
|
||||
<NavigationView.MenuItems>
|
||||
<!--
|
||||
TODO: Update item titles by updating <x:Uid>.Content entries in Strings/en-us/Resources.resw.
|
||||
https://docs.microsoft.com/windows/uwp/app-resources/localize-strings-ui-manifest#refer-to-a-string-resource-identifier-from-xaml
|
||||
|
||||
TODO: Update item icons by updating FontIcon.Glyph properties.
|
||||
https://docs.microsoft.com/windows/apps/design/style/segoe-fluent-icons-font#icon-list
|
||||
-->
|
||||
<NavigationViewItem x:Uid="Shell_Main" helpers:NavigationHelper.NavigateTo="VDD_Panel.ViewModels.MainViewModel">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem x:Uid="Shell_Configure" helpers:NavigationHelper.NavigateTo="VDD_Panel.ViewModels.ConfigureViewModel">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem x:Uid="Shell_GitHub" helpers:NavigationHelper.NavigateTo="VDD_Panel.ViewModels.GitHubViewModel">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/>
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
</NavigationView.MenuItems>
|
||||
<NavigationView.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<TextBlock
|
||||
Text="{Binding}"
|
||||
Style="{ThemeResource TitleTextBlockStyle}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</NavigationView.HeaderTemplate>
|
||||
<i:Interaction.Behaviors>
|
||||
<behaviors:NavigationViewHeaderBehavior
|
||||
DefaultHeader="{x:Bind ((ContentControl)ViewModel.Selected).Content, Mode=OneWay}">
|
||||
<behaviors:NavigationViewHeaderBehavior.DefaultHeaderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<TextBlock
|
||||
Text="{Binding}"
|
||||
Style="{ThemeResource TitleTextBlockStyle}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</behaviors:NavigationViewHeaderBehavior.DefaultHeaderTemplate>
|
||||
</behaviors:NavigationViewHeaderBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
<Grid Margin="{StaticResource NavigationViewPageContentMargin}">
|
||||
<Frame x:Name="NavigationFrame" />
|
||||
</Grid>
|
||||
</NavigationView>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,85 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
using VDD_Panel.Contracts.Services;
|
||||
using VDD_Panel.Helpers;
|
||||
using VDD_Panel.ViewModels;
|
||||
|
||||
using Windows.System;
|
||||
|
||||
namespace VDD_Panel.Views;
|
||||
|
||||
// TODO: Update NavigationViewItem titles and icons in ShellPage.xaml.
|
||||
public sealed partial class ShellPage : Page
|
||||
{
|
||||
public ShellViewModel ViewModel
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public ShellPage(ShellViewModel viewModel)
|
||||
{
|
||||
ViewModel = viewModel;
|
||||
InitializeComponent();
|
||||
|
||||
ViewModel.NavigationService.Frame = NavigationFrame;
|
||||
ViewModel.NavigationViewService.Initialize(NavigationViewControl);
|
||||
|
||||
// TODO: Set the title bar icon by updating /Assets/WindowIcon.ico.
|
||||
// A custom title bar is required for full window theme and Mica support.
|
||||
// https://docs.microsoft.com/windows/apps/develop/title-bar?tabs=winui3#full-customization
|
||||
App.MainWindow.ExtendsContentIntoTitleBar = true;
|
||||
App.MainWindow.SetTitleBar(AppTitleBar);
|
||||
App.MainWindow.Activated += MainWindow_Activated;
|
||||
AppTitleBarText.Text = "AppDisplayName".GetLocalized();
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
TitleBarHelper.UpdateTitleBar(RequestedTheme);
|
||||
|
||||
KeyboardAccelerators.Add(BuildKeyboardAccelerator(VirtualKey.Left, VirtualKeyModifiers.Menu));
|
||||
KeyboardAccelerators.Add(BuildKeyboardAccelerator(VirtualKey.GoBack));
|
||||
}
|
||||
|
||||
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
App.AppTitlebar = AppTitleBarText as UIElement;
|
||||
}
|
||||
|
||||
private void NavigationViewControl_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
|
||||
{
|
||||
AppTitleBar.Margin = new Thickness()
|
||||
{
|
||||
Left = sender.CompactPaneLength * (sender.DisplayMode == NavigationViewDisplayMode.Minimal ? 2 : 1),
|
||||
Top = AppTitleBar.Margin.Top,
|
||||
Right = AppTitleBar.Margin.Right,
|
||||
Bottom = AppTitleBar.Margin.Bottom
|
||||
};
|
||||
}
|
||||
|
||||
private static KeyboardAccelerator BuildKeyboardAccelerator(VirtualKey key, VirtualKeyModifiers? modifiers = null)
|
||||
{
|
||||
var keyboardAccelerator = new KeyboardAccelerator() { Key = key };
|
||||
|
||||
if (modifiers.HasValue)
|
||||
{
|
||||
keyboardAccelerator.Modifiers = modifiers.Value;
|
||||
}
|
||||
|
||||
keyboardAccelerator.Invoked += OnKeyboardAcceleratorInvoked;
|
||||
|
||||
return keyboardAccelerator;
|
||||
}
|
||||
|
||||
private static void OnKeyboardAcceleratorInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
|
||||
{
|
||||
var navigationService = App.GetService<INavigationService>();
|
||||
|
||||
var result = navigationService.GoBack();
|
||||
|
||||
args.Handled = result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="VDD_Panel.app"/>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"LocalSettingsOptions": {
|
||||
"ApplicationDataFolder": "VDD_Panel/ApplicationData",
|
||||
"LocalSettingsFile": "LocalSettings.json"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# Self-elevate the script if required
|
||||
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
|
||||
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
|
||||
$CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments
|
||||
Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine
|
||||
Exit
|
||||
}
|
||||
}
|
||||
|
||||
$repoName = "PSGallery"
|
||||
$moduleName1 = "DisplayConfig"
|
||||
$moduleName2 = "MonitorConfig"
|
||||
if (!(Get-PSRepository -Name $repoName) -OR !(Find-Module -Name $moduleName1 | Where-Object {$_.Name -eq $moduleName1}) -OR !(Find-Module -Name $moduleName2 | Where-Object {$_.Name -eq $moduleName2})) {
|
||||
& .\dep_fix.ps1
|
||||
}
|
||||
|
||||
# Import the local module
|
||||
$dpth = Get-Module -ListAvailable $moduleName1 | select path | Select-Object -ExpandProperty path
|
||||
import-module -name $dpth -InformationAction:Ignore -Verbose:$false -WarningAction:SilentlyContinue
|
||||
|
||||
# Import the other localmodule
|
||||
$mpth = Get-Module -ListAvailable $moduleName2 | select path | Select-Object -ExpandProperty path
|
||||
import-module -name $mpth -InformationAction:Ignore -Verbose:$false -WarningAction:SilentlyContinue
|
||||
|
||||
$disp = Get-Monitor | Select-String -Pattern "MTT1337" | Select-Object LineNumber | Select-Object -ExpandProperty LineNumber
|
||||
$bpcc = Get-DisplayColorInfo -DisplayId $disp | Select-Object BitsPerColorChannel | Select-Object -ExpandProperty BitsPerColorChannel
|
||||
|
||||
if ( $bpcc -is [int] ) {
|
||||
if ( $bpcc -gt 8 ) {
|
||||
Disable-DisplayAdvancedColor -DisplayId $disp
|
||||
break
|
||||
}
|
||||
if ( $bpcc -lt 10 ) {
|
||||
Enable-DisplayAdvancedColor -DisplayId $disp
|
||||
break
|
||||
}
|
||||
}
|
||||
else { write-error "Something went wrong in identifying the Colormode of the display." ; break }
|
||||
@@ -0,0 +1,9 @@
|
||||
#Scripts
|
||||
|
||||
This is a collection of Powershell scripts, written during the whole driver development. There haven't been a focus on keeping the scripts "up to date" all the time. Which means changes that might have occurred in the modules that these scripts depend upon, haven't been corrected either.
|
||||
|
||||
This is more ment as a framework for "tinkers" to make use of Powershell to change settings on the fly.
|
||||
|
||||
To use these, you should be at least be comfortable with Powershell and understand Admin privilegiets, risk/reward.
|
||||
|
||||
PR's are welcome for improvements or other/more functions.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Self-elevate the script if required
|
||||
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
|
||||
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
|
||||
$CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments
|
||||
Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine
|
||||
Exit
|
||||
}
|
||||
}
|
||||
|
||||
$repoName = "PSGallery"
|
||||
$moduleName1 = "DisplayConfig"
|
||||
$moduleName2 = "MonitorConfig"
|
||||
if (!(Get-PSRepository -Name $repoName) -OR !(Find-Module -Name $moduleName1 | Where-Object {$_.Name -eq $moduleName1}) -OR !(Find-Module -Name $moduleName2 | Where-Object {$_.Name -eq $moduleName2})) {
|
||||
& .\dep_fix.ps1
|
||||
}
|
||||
|
||||
|
||||
# Import the local module
|
||||
$dpth = Get-Module -ListAvailable $moduleName1 | select path | Select-Object -ExpandProperty path
|
||||
import-module -name $dpth -InformationAction:Ignore -Verbose:$false -WarningAction:SilentlyContinue -Confirm:$false
|
||||
|
||||
# Import the other localmodule
|
||||
$mpth = Get-Module -ListAvailable $moduleName2 | select path | Select-Object -ExpandProperty path
|
||||
import-module -name $mpth -InformationAction:Ignore -Verbose:$false -WarningAction:SilentlyContinue -Confirm:$false
|
||||
|
||||
# Check if there are enough arguments
|
||||
$numArgs = $args.Count
|
||||
switch ($numArgs) {
|
||||
0 { Write-Error "This script requires at least 2 arguments Xres Yres."; break }
|
||||
1 { Write-Error "This script requires at least 2 arguments Xres Yres."; break }
|
||||
2 { $disp = Get-Monitor | Select-String -Pattern "MTT1337" | Select-Object LineNumber | Select-Object -ExpandProperty LineNumber
|
||||
$vres = $args[0]
|
||||
$hres = $args[1]
|
||||
continue
|
||||
}
|
||||
default { Write-Error "Invalid number of arguments: $numArgs"; break }
|
||||
}
|
||||
# Setting the resolution on the correct display.
|
||||
Get-DisplayConfig | Set-DisplayResolution -DisplayId $disp -Width $vres -Height $hres | Use-DisplayConfig
|
||||
@@ -0,0 +1,32 @@
|
||||
# Define the repository name and URL
|
||||
$repoName = "PSGallery"
|
||||
$repoUrl = "https://www.powershellgallery.com/api/v2"
|
||||
|
||||
# Check if the repository is registered
|
||||
if (-not (Get-PSRepository -Name $repoName)) {
|
||||
# Register the repository
|
||||
Register-PSRepository -Name $repoName -SourceLocation $repoUrl -Confirm:$false -InformationAction:Ignore -WarningAction:SilentlyContinue
|
||||
}
|
||||
#Trust the repo, either its allready installed or newly added
|
||||
Set-PSRepository -Name $repoName -InstallationPolicy Trusted -InformationAction:Ignore -WarningAction:SilentlyContinue
|
||||
|
||||
# Define the module name and version
|
||||
$moduleName1 = "DisplayConfig"
|
||||
$version1 = "1.1.1"
|
||||
|
||||
# Define the module name and version
|
||||
$moduleName2 = "MonitorConfig"
|
||||
$version2 = "1.0.3"
|
||||
|
||||
# Check if the module1 is installed
|
||||
if (-not (Get-InstalledModule -Name $moduleName1 -MinimumVersion $version1)) {
|
||||
# Save the module
|
||||
Save-Module -Name $moduleName1 -Repository $repoName -RequiredVersion $version1 -Confirm:$false -InformationAction:Ignore -WarningAction:SilentlyContinue
|
||||
}
|
||||
|
||||
# Check if the module2 is installed
|
||||
if (-not (Get-InstalledModule -Name $moduleName2 -MinimumVersion $version2)) {
|
||||
# Save the module
|
||||
Save-Module -Name $moduleName2 -Repository $repoName -RequiredVersion $version2 -Confirm:$false -InformationAction:Ignore -WarningAction:SilentlyContinue
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Self-elevate the script if required
|
||||
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
|
||||
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
|
||||
$CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments
|
||||
Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine
|
||||
Exit
|
||||
}
|
||||
}
|
||||
|
||||
$repoName = "PSGallery"
|
||||
$moduleName1 = "DisplayConfig"
|
||||
$moduleName2 = "MonitorConfig"
|
||||
if (!(Get-PSRepository -Name $repoName) -OR !(Find-Module -Name $moduleName1 | Where-Object {$_.Name -eq $moduleName1}) -OR !(Find-Module -Name $moduleName2 | Where-Object {$_.Name -eq $moduleName2})) {
|
||||
& .\dep_fix.ps1
|
||||
}
|
||||
|
||||
|
||||
# Import the local module
|
||||
$dpth = Get-Module -ListAvailable $moduleName1 | select path | Select-Object -ExpandProperty path; import-module -name $dpth
|
||||
|
||||
# Import the other localmodule
|
||||
$mpth = Get-Module -ListAvailable $moduleName2 | select path | Select-Object -ExpandProperty path; import-module -name $mpth
|
||||
|
||||
|
||||
$disp = Get-Monitor | Select-String -Pattern "MTT1337" | Select-Object LineNumber | Select-Object -ExpandProperty LineNumber
|
||||
Set-DisplayPrimary -DisplayId $disp
|
||||
@@ -0,0 +1,39 @@
|
||||
# Self-elevate the script if required
|
||||
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
|
||||
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
|
||||
$CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments
|
||||
Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine
|
||||
Exit
|
||||
}
|
||||
}
|
||||
|
||||
$repoName = "PSGallery"
|
||||
$moduleName1 = "DisplayConfig"
|
||||
$moduleName2 = "MonitorConfig"
|
||||
if (!(Get-PSRepository -Name $repoName) -OR !(Find-Module -Name $moduleName1 | Where-Object {$_.Name -eq $moduleName1}) -OR !(Find-Module -Name $moduleName2 | Where-Object {$_.Name -eq $moduleName2})) {
|
||||
& .\dep_fix.ps1
|
||||
}
|
||||
|
||||
|
||||
# Import the local module
|
||||
$dpth = Get-Module -ListAvailable $moduleName1 | select path | Select-Object -ExpandProperty path
|
||||
import-module -name $dpth -InformationAction:Ignore -Verbose:$false -WarningAction:SilentlyContinue -Confirm:$false
|
||||
|
||||
# Import the other localmodule
|
||||
$mpth = Get-Module -ListAvailable $moduleName2 | select path | Select-Object -ExpandProperty path
|
||||
import-module -name $mpth -InformationAction:Ignore -Verbose:$false -WarningAction:SilentlyContinue -Confirm:$false
|
||||
|
||||
|
||||
# Use the module's functions and cmdlets in your script
|
||||
# Check if there are enough arguments
|
||||
$numArgs = $args.Count
|
||||
switch ($numArgs) {
|
||||
0 { Write-Error "This script requires a refreshrate above 59 or reset"; break }
|
||||
1 { $disp = Get-Monitor | Select-String -Pattern "MTT1337" | Select-Object LineNumber | Select-Object -ExpandProperty LineNumber
|
||||
$rate = $args[0]
|
||||
continue
|
||||
}
|
||||
default { Write-Error "Invalid number of arguments: $numArgs"; break }
|
||||
}
|
||||
# Setting the resolution on the correct display.
|
||||
Get-DisplayConfig | Set-DisplayRefreshRate -DisplayId $disp -RefreshRate $rate | Use-DisplayConfig
|
||||
@@ -0,0 +1,46 @@
|
||||
# Self-elevate the script if required
|
||||
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
|
||||
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
|
||||
$CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments
|
||||
Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine
|
||||
Exit
|
||||
}
|
||||
}
|
||||
|
||||
$repoName = "PSGallery"
|
||||
$moduleName1 = "DisplayConfig"
|
||||
$moduleName2 = "MonitorConfig"
|
||||
if (!(Get-PSRepository -Name $repoName) -OR !(Find-Module -Name $moduleName1 | Where-Object {$_.Name -eq $moduleName1}) -OR !(Find-Module -Name $moduleName2 | Where-Object {$_.Name -eq $moduleName2})) {
|
||||
& .\dep_fix.ps1
|
||||
}
|
||||
|
||||
|
||||
# Import the local module
|
||||
$dpth = Get-Module -ListAvailable $moduleName1 | select path | Select-Object -ExpandProperty path
|
||||
import-module -name $dpth -InformationAction:Ignore -Verbose:$false -WarningAction:SilentlyContinue -Confirm:$false
|
||||
|
||||
# Import the other localmodule
|
||||
$mpth = Get-Module -ListAvailable $moduleName2 | select path | Select-Object -ExpandProperty path
|
||||
import-module -name $mpth -InformationAction:Ignore -Verbose:$false -WarningAction:SilentlyContinue -Confirm:$false
|
||||
|
||||
# Check if there are enough arguments
|
||||
$numArgs = $args.Count
|
||||
switch ($numArgs) {
|
||||
0 { Write-Error "This script requires at least 1, rotation in degrees ex. 90/180/270."; break }
|
||||
1 { $disp = Get-Monitor | Select-String -Pattern "MTT1337" | Select-Object LineNumber | Select-Object -ExpandProperty LineNumber
|
||||
switch ($args[0]) {
|
||||
90 { $degree = "Rotate90"
|
||||
continue
|
||||
}
|
||||
180 { $degree = "Rotate180"
|
||||
continue
|
||||
}
|
||||
270 { $degree = "Rotate270"
|
||||
continue
|
||||
}
|
||||
default { Write-Error "Invalid rotation angle(valid 90,180,270)."; break }
|
||||
}
|
||||
default { Write-Error "Invalid number of arguments: $numArgs"; break }
|
||||
}
|
||||
# Setting the resolution on the correct display.
|
||||
Get-DisplayConfig | Set-DisplayRotation -DisplayId $disp -Rotation $degree | Use-DisplayConfig
|
||||
@@ -0,0 +1,54 @@
|
||||
# Self-elevate the script if required
|
||||
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
|
||||
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
|
||||
$CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments
|
||||
Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine
|
||||
Exit
|
||||
}
|
||||
}
|
||||
|
||||
$repoName = "PSGallery"
|
||||
$moduleName1 = "DisplayConfig"
|
||||
$moduleName2 = "MonitorConfig"
|
||||
if (!(Get-PSRepository -Name $repoName) -OR !(Find-Module -Name $moduleName1 | Where-Object {$_.Name -eq $moduleName1}) -OR !(Find-Module -Name $moduleName2 | Where-Object {$_.Name -eq $moduleName2})) {
|
||||
& .\dep_fix.ps1
|
||||
}
|
||||
|
||||
|
||||
# Import the local module
|
||||
$dpth = Get-Module -ListAvailable $moduleName1 | select path | Select-Object -ExpandProperty path
|
||||
import-module -name $dpth -InformationAction:Ignore -Verbose:$false -WarningAction:SilentlyContinue -Confirm:$false
|
||||
|
||||
# Import the other localmodule
|
||||
$mpth = Get-Module -ListAvailable $moduleName2 | select path | Select-Object -ExpandProperty path
|
||||
import-module -name $mpth -InformationAction:Ignore -Verbose:$false -WarningAction:SilentlyContinue -Confirm:$false
|
||||
|
||||
# Check if there are enough arguments
|
||||
$numArgs = $args.Count
|
||||
switch ($numArgs) {
|
||||
0 { Write-Error "This script requires a scale 'reset' or an accepted percfentage value (100, 125, 150, 175, 200, 225, 250, 300, 350, 400, 450, 500) limited by the device max scale."; break }
|
||||
1 { $disp = Get-Monitor | Select-String -Pattern "MTT1337" | Select-Object LineNumber | Select-Object -ExpandProperty LineNumber
|
||||
$maxscale = Get-DisplayScale -DisplayId $disp | Select-Object -ExpandProperty MaxScale
|
||||
if ( $args[0] -is [int] ) {
|
||||
if ( $args[0] -le $maxscale -And $args[0] -ge 100 ) {
|
||||
Set-DisplayScale -DisplayId $disp -Scale $args[0]
|
||||
break
|
||||
}
|
||||
else {
|
||||
Write-Error "Invalid scale percentage."
|
||||
break
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( $args[0] -eq "reset" ) {
|
||||
Set-DisplayScale -DisplayId $disp -Recommended
|
||||
break
|
||||
}
|
||||
else {
|
||||
Write-Error "Invalid arguments: 'reset' or a valid percentage value like 150 is acceptable."
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
default { Write-Error "Invalid number of arguments: $numArgs"; break }
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
# submitted by zjoasan
|
||||
# Self-elevate the script if required
|
||||
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
|
||||
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
|
||||
@@ -0,0 +1,36 @@
|
||||
# Self-elevate the script if required
|
||||
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
|
||||
if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
|
||||
$CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments
|
||||
Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine
|
||||
Exit
|
||||
}
|
||||
}
|
||||
|
||||
$repoName = "PSGallery"
|
||||
$moduleName1 = "DisplayConfig"
|
||||
$moduleName2 = "MonitorConfig"
|
||||
if (!(Get-PSRepository -Name $repoName) -OR !(Find-Module -Name $moduleName1 | Where-Object {$_.Name -eq $moduleName1}) -OR !(Find-Module -Name $moduleName2 | Where-Object {$_.Name -eq $moduleName2})) {
|
||||
& .\dep_fix.ps1
|
||||
}
|
||||
|
||||
|
||||
# Import the local module
|
||||
$dpth = Get-Module -ListAvailable $moduleName1 | select path | Select-Object -ExpandProperty path
|
||||
import-module -name $dpth -InformationAction:Ignore -Verbose:$false -WarningAction:SilentlyContinue -Confirm:$false
|
||||
|
||||
# Import the other localmodule
|
||||
$mpth = Get-Module -ListAvailable $moduleName2 | select path | Select-Object -ExpandProperty path
|
||||
import-module -name $mpth -InformationAction:Ignore -Verbose:$false -WarningAction:SilentlyContinue -Confirm:$false
|
||||
|
||||
|
||||
$profi = Get-DisplayProfile
|
||||
|
||||
if ( $profi -eq "Extend" ) {
|
||||
Set-DisplayProfile Clone
|
||||
break
|
||||
}
|
||||
if ( $profi -eq "Clone" ) {
|
||||
Set-DisplayProfile Extend
|
||||
break
|
||||
}
|
||||
@@ -1,899 +0,0 @@
|
||||
/*++
|
||||
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
Abstract:
|
||||
|
||||
This module contains a sample implementation of an indirect display driver. See the included README.md file and the
|
||||
various TODO blocks throughout this file and all accompanying files for information on building a production driver.
|
||||
|
||||
MSDN documentation on indirect displays can be found at https://msdn.microsoft.com/en-us/library/windows/hardware/mt761968(v=vs.85).aspx.
|
||||
|
||||
Environment:
|
||||
|
||||
User Mode, UMDF
|
||||
|
||||
--*/
|
||||
|
||||
#include "Driver.h"
|
||||
//#include "Driver.tmh"
|
||||
#include<fstream>
|
||||
#include<sstream>
|
||||
#include<string>
|
||||
#include<tuple>
|
||||
#include<vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace Microsoft::IndirectDisp;
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
extern "C" DRIVER_INITIALIZE DriverEntry;
|
||||
|
||||
EVT_WDF_DRIVER_DEVICE_ADD IddSampleDeviceAdd;
|
||||
EVT_WDF_DEVICE_D0_ENTRY IddSampleDeviceD0Entry;
|
||||
|
||||
EVT_IDD_CX_ADAPTER_INIT_FINISHED IddSampleAdapterInitFinished;
|
||||
EVT_IDD_CX_ADAPTER_COMMIT_MODES IddSampleAdapterCommitModes;
|
||||
|
||||
EVT_IDD_CX_PARSE_MONITOR_DESCRIPTION IddSampleParseMonitorDescription;
|
||||
EVT_IDD_CX_MONITOR_GET_DEFAULT_DESCRIPTION_MODES IddSampleMonitorGetDefaultModes;
|
||||
EVT_IDD_CX_MONITOR_QUERY_TARGET_MODES IddSampleMonitorQueryModes;
|
||||
|
||||
EVT_IDD_CX_MONITOR_ASSIGN_SWAPCHAIN IddSampleMonitorAssignSwapChain;
|
||||
EVT_IDD_CX_MONITOR_UNASSIGN_SWAPCHAIN IddSampleMonitorUnassignSwapChain;
|
||||
|
||||
EVT_IDD_CX_ADAPTER_QUERY_TARGET_INFO IddSampleEvtIddCxAdapterQueryTargetInfo;
|
||||
EVT_IDD_CX_MONITOR_SET_DEFAULT_HDR_METADATA IddSampleEvtIddCxMonitorSetDefaultHdrMetadata;
|
||||
EVT_IDD_CX_PARSE_MONITOR_DESCRIPTION2 IddSampleEvtIddCxParseMonitorDescription2;
|
||||
EVT_IDD_CX_MONITOR_QUERY_TARGET_MODES2 IddSampleEvtIddCxMonitorQueryTargetModes2;
|
||||
EVT_IDD_CX_ADAPTER_COMMIT_MODES2 IddSampleEvtIddCxAdapterCommitModes2;
|
||||
|
||||
EVT_IDD_CX_MONITOR_SET_GAMMA_RAMP IddSampleEvtIddCxMonitorSetGammaRamp;
|
||||
|
||||
vector<tuple<int, int, int>> monitorModes;
|
||||
vector< DISPLAYCONFIG_VIDEO_SIGNAL_INFO> s_KnownMonitorModes2;
|
||||
UINT numVirtualDisplays;
|
||||
|
||||
struct IndirectDeviceContextWrapper
|
||||
{
|
||||
IndirectDeviceContext* pContext;
|
||||
|
||||
void Cleanup()
|
||||
{
|
||||
delete pContext;
|
||||
pContext = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// This macro creates the methods for accessing an IndirectDeviceContextWrapper as a context for a WDF object
|
||||
WDF_DECLARE_CONTEXT_TYPE(IndirectDeviceContextWrapper);
|
||||
|
||||
extern "C" BOOL WINAPI DllMain(
|
||||
_In_ HINSTANCE hInstance,
|
||||
_In_ UINT dwReason,
|
||||
_In_opt_ LPVOID lpReserved)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(hInstance);
|
||||
UNREFERENCED_PARAMETER(lpReserved);
|
||||
UNREFERENCED_PARAMETER(dwReason);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
extern "C" NTSTATUS DriverEntry(
|
||||
PDRIVER_OBJECT pDriverObject,
|
||||
PUNICODE_STRING pRegistryPath
|
||||
)
|
||||
{
|
||||
WDF_DRIVER_CONFIG Config;
|
||||
NTSTATUS Status;
|
||||
|
||||
WDF_OBJECT_ATTRIBUTES Attributes;
|
||||
WDF_OBJECT_ATTRIBUTES_INIT(&Attributes);
|
||||
|
||||
WDF_DRIVER_CONFIG_INIT(&Config,
|
||||
IddSampleDeviceAdd
|
||||
);
|
||||
|
||||
Status = WdfDriverCreate(pDriverObject, pRegistryPath, &Attributes, &Config, WDF_NO_HANDLE);
|
||||
if (!NT_SUCCESS(Status))
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
||||
vector<string> split(string& input, char delimiter)
|
||||
{
|
||||
istringstream stream(input);
|
||||
string field;
|
||||
vector<string> result;
|
||||
while (getline(stream, field, delimiter)) {
|
||||
result.push_back(field);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void loadOptions(string filepath) {
|
||||
ifstream ifs(filepath);
|
||||
|
||||
string line;
|
||||
vector<tuple<int, int, int>> res;
|
||||
getline(ifs, line);//num of displays
|
||||
numVirtualDisplays = stoi(line);
|
||||
while (getline(ifs, line)) {
|
||||
vector<string> strvec = split(line, ',');
|
||||
if (strvec.size() == 3 && strvec[0].substr(0, 1) != "#") {
|
||||
res.push_back({ stoi(strvec[0]),stoi(strvec[1]),stoi(strvec[2]) });
|
||||
}
|
||||
}
|
||||
monitorModes = res; return;
|
||||
}
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT pDeviceInit)
|
||||
{
|
||||
NTSTATUS Status = STATUS_SUCCESS;
|
||||
WDF_PNPPOWER_EVENT_CALLBACKS PnpPowerCallbacks;
|
||||
|
||||
UNREFERENCED_PARAMETER(Driver);
|
||||
|
||||
// Register for power callbacks - in this sample only power-on is needed
|
||||
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&PnpPowerCallbacks);
|
||||
PnpPowerCallbacks.EvtDeviceD0Entry = IddSampleDeviceD0Entry;
|
||||
WdfDeviceInitSetPnpPowerEventCallbacks(pDeviceInit, &PnpPowerCallbacks);
|
||||
|
||||
IDD_CX_CLIENT_CONFIG IddConfig;
|
||||
IDD_CX_CLIENT_CONFIG_INIT(&IddConfig);
|
||||
|
||||
// If the driver wishes to handle custom IoDeviceControl requests, it's necessary to use this callback since IddCx
|
||||
// redirects IoDeviceControl requests to an internal queue. This sample does not need this.
|
||||
// IddConfig.EvtIddCxDeviceIoControl = IddSampleIoDeviceControl;
|
||||
loadOptions("C:\\IddSampleDriver\\option.txt");
|
||||
IddConfig.EvtIddCxAdapterInitFinished = IddSampleAdapterInitFinished;
|
||||
|
||||
IddConfig.EvtIddCxMonitorGetDefaultDescriptionModes = IddSampleMonitorGetDefaultModes;
|
||||
IddConfig.EvtIddCxMonitorAssignSwapChain = IddSampleMonitorAssignSwapChain;
|
||||
IddConfig.EvtIddCxMonitorUnassignSwapChain = IddSampleMonitorUnassignSwapChain;
|
||||
|
||||
if (IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxAdapterQueryTargetInfo))
|
||||
{
|
||||
IddConfig.EvtIddCxAdapterQueryTargetInfo = IddSampleEvtIddCxAdapterQueryTargetInfo;
|
||||
IddConfig.EvtIddCxMonitorSetDefaultHdrMetaData = IddSampleEvtIddCxMonitorSetDefaultHdrMetadata;
|
||||
IddConfig.EvtIddCxParseMonitorDescription2 = IddSampleEvtIddCxParseMonitorDescription2;
|
||||
IddConfig.EvtIddCxMonitorQueryTargetModes2 = IddSampleEvtIddCxMonitorQueryTargetModes2;
|
||||
IddConfig.EvtIddCxAdapterCommitModes2 = IddSampleEvtIddCxAdapterCommitModes2;
|
||||
IddConfig.EvtIddCxMonitorSetGammaRamp = IddSampleEvtIddCxMonitorSetGammaRamp;
|
||||
}
|
||||
else {
|
||||
IddConfig.EvtIddCxParseMonitorDescription = IddSampleParseMonitorDescription;
|
||||
IddConfig.EvtIddCxMonitorQueryTargetModes = IddSampleMonitorQueryModes;
|
||||
IddConfig.EvtIddCxAdapterCommitModes = IddSampleAdapterCommitModes;
|
||||
}
|
||||
|
||||
Status = IddCxDeviceInitConfig(pDeviceInit, &IddConfig);
|
||||
if (!NT_SUCCESS(Status))
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
|
||||
WDF_OBJECT_ATTRIBUTES Attr;
|
||||
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attr, IndirectDeviceContextWrapper);
|
||||
Attr.EvtCleanupCallback = [](WDFOBJECT Object)
|
||||
{
|
||||
// Automatically cleanup the context when the WDF object is about to be deleted
|
||||
auto* pContext = WdfObjectGet_IndirectDeviceContextWrapper(Object);
|
||||
if (pContext)
|
||||
{
|
||||
pContext->Cleanup();
|
||||
}
|
||||
};
|
||||
|
||||
WDFDEVICE Device = nullptr;
|
||||
Status = WdfDeviceCreate(&pDeviceInit, &Attr, &Device);
|
||||
if (!NT_SUCCESS(Status))
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
|
||||
Status = IddCxDeviceInitialize(Device);
|
||||
|
||||
// Create a new device context object and attach it to the WDF device object
|
||||
auto* pContext = WdfObjectGet_IndirectDeviceContextWrapper(Device);
|
||||
pContext->pContext = new IndirectDeviceContext(Device);
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleDeviceD0Entry(WDFDEVICE Device, WDF_POWER_DEVICE_STATE PreviousState)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(PreviousState);
|
||||
|
||||
// This function is called by WDF to start the device in the fully-on power state.
|
||||
|
||||
auto* pContext = WdfObjectGet_IndirectDeviceContextWrapper(Device);
|
||||
pContext->pContext->InitAdapter();
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#pragma region Direct3DDevice
|
||||
|
||||
Direct3DDevice::Direct3DDevice(LUID AdapterLuid) : AdapterLuid(AdapterLuid)
|
||||
{
|
||||
}
|
||||
|
||||
Direct3DDevice::Direct3DDevice()
|
||||
{
|
||||
}
|
||||
|
||||
HRESULT Direct3DDevice::Init()
|
||||
{
|
||||
// The DXGI factory could be cached, but if a new render adapter appears on the system, a new factory needs to be
|
||||
// created. If caching is desired, check DxgiFactory->IsCurrent() each time and recreate the factory if !IsCurrent.
|
||||
HRESULT hr = CreateDXGIFactory2(0, IID_PPV_ARGS(&DxgiFactory));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Find the specified render adapter
|
||||
hr = DxgiFactory->EnumAdapterByLuid(AdapterLuid, IID_PPV_ARGS(&Adapter));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Create a D3D device using the render adapter. BGRA support is required by the WHQL test suite.
|
||||
hr = D3D11CreateDevice(Adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, 0, D3D11_SDK_VERSION, &Device, nullptr, &DeviceContext);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
// If creating the D3D device failed, it's possible the render GPU was lost (e.g. detachable GPU) or else the
|
||||
// system is in a transient state.
|
||||
return hr;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region SwapChainProcessor
|
||||
|
||||
SwapChainProcessor::SwapChainProcessor(IDDCX_SWAPCHAIN hSwapChain, shared_ptr<Direct3DDevice> Device, HANDLE NewFrameEvent)
|
||||
: m_hSwapChain(hSwapChain), m_Device(Device), m_hAvailableBufferEvent(NewFrameEvent)
|
||||
{
|
||||
m_hTerminateEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr));
|
||||
|
||||
// Immediately create and run the swap-chain processing thread, passing 'this' as the thread parameter
|
||||
m_hThread.Attach(CreateThread(nullptr, 0, RunThread, this, 0, nullptr));
|
||||
}
|
||||
|
||||
SwapChainProcessor::~SwapChainProcessor()
|
||||
{
|
||||
// Alert the swap-chain processing thread to terminate
|
||||
SetEvent(m_hTerminateEvent.Get());
|
||||
|
||||
if (m_hThread.Get())
|
||||
{
|
||||
// Wait for the thread to terminate
|
||||
WaitForSingleObject(m_hThread.Get(), INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
DWORD CALLBACK SwapChainProcessor::RunThread(LPVOID Argument)
|
||||
{
|
||||
reinterpret_cast<SwapChainProcessor*>(Argument)->Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SwapChainProcessor::Run()
|
||||
{
|
||||
// For improved performance, make use of the Multimedia Class Scheduler Service, which will intelligently
|
||||
// prioritize this thread for improved throughput in high CPU-load scenarios.
|
||||
DWORD AvTask = 0;
|
||||
HANDLE AvTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &AvTask);
|
||||
|
||||
RunCore();
|
||||
|
||||
// Always delete the swap-chain object when swap-chain processing loop terminates in order to kick the system to
|
||||
// provide a new swap-chain if necessary.
|
||||
WdfObjectDelete((WDFOBJECT)m_hSwapChain);
|
||||
m_hSwapChain = nullptr;
|
||||
|
||||
AvRevertMmThreadCharacteristics(AvTaskHandle);
|
||||
}
|
||||
|
||||
void SwapChainProcessor::RunCore()
|
||||
{
|
||||
// Get the DXGI device interface
|
||||
ComPtr<IDXGIDevice> DxgiDevice;
|
||||
HRESULT hr = m_Device->Device.As(&DxgiDevice);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IDARG_IN_SWAPCHAINSETDEVICE SetDevice = {};
|
||||
SetDevice.pDevice = DxgiDevice.Get();
|
||||
|
||||
hr = IddCxSwapChainSetDevice(m_hSwapChain, &SetDevice);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Acquire and release buffers in a loop
|
||||
for (;;)
|
||||
{
|
||||
ComPtr<IDXGIResource> AcquiredBuffer;
|
||||
|
||||
// Ask for the next buffer from the producer
|
||||
IDARG_IN_RELEASEANDACQUIREBUFFER2 BufferInArgs = {};
|
||||
BufferInArgs.Size = sizeof(BufferInArgs);
|
||||
IDXGIResource* pSurface;
|
||||
|
||||
if (IDD_IS_FUNCTION_AVAILABLE(IddCxSwapChainReleaseAndAcquireBuffer2)) {
|
||||
IDARG_OUT_RELEASEANDACQUIREBUFFER2 Buffer = {};
|
||||
hr = IddCxSwapChainReleaseAndAcquireBuffer2(m_hSwapChain, &BufferInArgs, &Buffer);
|
||||
pSurface = Buffer.MetaData.pSurface;
|
||||
}
|
||||
else
|
||||
{
|
||||
IDARG_OUT_RELEASEANDACQUIREBUFFER Buffer = {};
|
||||
hr = IddCxSwapChainReleaseAndAcquireBuffer(m_hSwapChain, &Buffer);
|
||||
pSurface = Buffer.MetaData.pSurface;
|
||||
}
|
||||
// AcquireBuffer immediately returns STATUS_PENDING if no buffer is yet available
|
||||
if (hr == E_PENDING)
|
||||
{
|
||||
// We must wait for a new buffer
|
||||
HANDLE WaitHandles[] =
|
||||
{
|
||||
m_hAvailableBufferEvent,
|
||||
m_hTerminateEvent.Get()
|
||||
};
|
||||
DWORD WaitResult = WaitForMultipleObjects(ARRAYSIZE(WaitHandles), WaitHandles, FALSE, 16);
|
||||
if (WaitResult == WAIT_OBJECT_0 || WaitResult == WAIT_TIMEOUT)
|
||||
{
|
||||
// We have a new buffer, so try the AcquireBuffer again
|
||||
continue;
|
||||
}
|
||||
else if (WaitResult == WAIT_OBJECT_0 + 1)
|
||||
{
|
||||
// We need to terminate
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The wait was cancelled or something unexpected happened
|
||||
hr = HRESULT_FROM_WIN32(WaitResult);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (SUCCEEDED(hr))
|
||||
{
|
||||
AcquiredBuffer.Attach(pSurface);
|
||||
|
||||
// ==============================
|
||||
// TODO: Process the frame here
|
||||
//
|
||||
// This is the most performance-critical section of code in an IddCx driver. It's important that whatever
|
||||
// is done with the acquired surface be finished as quickly as possible. This operation could be:
|
||||
// * a GPU copy to another buffer surface for later processing (such as a staging surface for mapping to CPU memory)
|
||||
// * a GPU encode operation
|
||||
// * a GPU VPBlt to another surface
|
||||
// * a GPU custom compute shader encode operation
|
||||
// ==============================
|
||||
|
||||
AcquiredBuffer.Reset();
|
||||
hr = IddCxSwapChainFinishedProcessingFrame(m_hSwapChain);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// TODO: Report frame statistics once the asynchronous encode/send work is completed
|
||||
//
|
||||
// Drivers should report information about sub-frame timings, like encode time, send time, etc.
|
||||
// ==============================
|
||||
// IddCxSwapChainReportFrameStatistics(m_hSwapChain, ...);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The swap-chain was likely abandoned (e.g. DXGI_ERROR_ACCESS_LOST), so exit the processing loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IndirectDeviceContext
|
||||
|
||||
const UINT64 MHZ = 1000000;
|
||||
const UINT64 KHZ = 1000;
|
||||
|
||||
constexpr DISPLAYCONFIG_VIDEO_SIGNAL_INFO dispinfo(UINT32 h, UINT32 v, UINT32 r) {
|
||||
const UINT32 clock_rate = r * (v + 4) * (v + 4) + 1000;
|
||||
return {
|
||||
clock_rate, // pixel clock rate [Hz]
|
||||
{ clock_rate, v + 4 }, // fractional horizontal refresh rate [Hz]
|
||||
{ clock_rate, (v + 4) * (v + 4) }, // fractional vertical refresh rate [Hz]
|
||||
{ h, v }, // (horizontal, vertical) active pixel resolution
|
||||
{ h + 4, v + 4 }, // (horizontal, vertical) total pixel resolution
|
||||
{ { 255, 0 }}, // video standard and vsync divider
|
||||
DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE
|
||||
};
|
||||
}
|
||||
|
||||
// This is a sample monitor EDID - FOR SAMPLE PURPOSES ONLY
|
||||
const BYTE IndirectDeviceContext::s_KnownMonitorEdid[] =
|
||||
{
|
||||
/* 0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x79,0x5E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA6,0x01,0x03,0x80,0x28,
|
||||
0x1E,0x78,0x0A,0xEE,0x91,0xA3,0x54,0x4C,0x99,0x26,0x0F,0x50,0x54,0x20,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,
|
||||
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0xA0,0x0F,0x20,0x00,0x31,0x58,0x1C,0x20,0x28,0x80,0x14,0x00,
|
||||
0x90,0x2C,0x11,0x00,0x00,0x1E,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6E */
|
||||
|
||||
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x31, 0xD8, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x18, 0x1F, 0x01, 0x04, 0xB5, 0x77, 0x22, 0x78,
|
||||
0x3A, 0xC7, 0x25, 0xB1, 0x4B, 0x46, 0xA8, 0x26, 0x0E, 0x50, 0x54, 0x2F,
|
||||
0xCF, 0x00, 0x71, 0x4F, 0x81, 0x00, 0x81, 0xC0, 0x81, 0x80, 0xA9, 0xC0,
|
||||
0xB3, 0x00, 0x95, 0x00, 0xD1, 0xC0, 0x74, 0xD6, 0x00, 0xA0, 0xF0, 0x38,
|
||||
0x40, 0x40, 0x30, 0x20, 0x3A, 0x00, 0xC0, 0x0E, 0x31, 0x00, 0x00, 0x1A,
|
||||
0x00, 0x00, 0x00, 0xFC, 0x00, 0x49, 0x44, 0x44, 0x20, 0x48, 0x44, 0x52,
|
||||
0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x85, 0x02, 0x03, 0x15, 0x00,
|
||||
0xE3, 0x05, 0xC0, 0x00, 0xE6, 0x06, 0x05, 0x01, 0x8B, 0x73, 0x02, 0xE5,
|
||||
0x01, 0x8B, 0x84, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xC6
|
||||
};
|
||||
|
||||
IndirectDeviceContext::IndirectDeviceContext(_In_ WDFDEVICE WdfDevice) :
|
||||
m_WdfDevice(WdfDevice)
|
||||
{
|
||||
m_Adapter = {};
|
||||
}
|
||||
|
||||
IndirectDeviceContext::~IndirectDeviceContext()
|
||||
{
|
||||
m_ProcessingThread.reset();
|
||||
}
|
||||
|
||||
#define NUM_VIRTUAL_DISPLAYS 1
|
||||
|
||||
void IndirectDeviceContext::InitAdapter()
|
||||
{
|
||||
// ==============================
|
||||
// TODO: Update the below diagnostic information in accordance with the target hardware. The strings and version
|
||||
// numbers are used for telemetry and may be displayed to the user in some situations.
|
||||
//
|
||||
// This is also where static per-adapter capabilities are determined.
|
||||
// ==============================
|
||||
|
||||
IDDCX_ADAPTER_CAPS AdapterCaps = {};
|
||||
AdapterCaps.Size = sizeof(AdapterCaps);
|
||||
|
||||
if (IDD_IS_FUNCTION_AVAILABLE(IddCxSwapChainReleaseAndAcquireBuffer2)) {
|
||||
AdapterCaps.Flags = IDDCX_ADAPTER_FLAGS_CAN_PROCESS_FP16;
|
||||
}
|
||||
|
||||
// Declare basic feature support for the adapter (required)
|
||||
AdapterCaps.MaxMonitorsSupported = numVirtualDisplays;
|
||||
AdapterCaps.EndPointDiagnostics.Size = sizeof(AdapterCaps.EndPointDiagnostics);
|
||||
AdapterCaps.EndPointDiagnostics.GammaSupport = IDDCX_FEATURE_IMPLEMENTATION_NONE;
|
||||
AdapterCaps.EndPointDiagnostics.TransmissionType = IDDCX_TRANSMISSION_TYPE_WIRED_OTHER;
|
||||
|
||||
// Declare your device strings for telemetry (required)
|
||||
AdapterCaps.EndPointDiagnostics.pEndPointFriendlyName = L"IddSample Device";
|
||||
AdapterCaps.EndPointDiagnostics.pEndPointManufacturerName = L"Microsoft";
|
||||
AdapterCaps.EndPointDiagnostics.pEndPointModelName = L"IddSample Model";
|
||||
|
||||
// Declare your hardware and firmware versions (required)
|
||||
IDDCX_ENDPOINT_VERSION Version = {};
|
||||
Version.Size = sizeof(Version);
|
||||
Version.MajorVer = 1;
|
||||
AdapterCaps.EndPointDiagnostics.pFirmwareVersion = &Version;
|
||||
AdapterCaps.EndPointDiagnostics.pHardwareVersion = &Version;
|
||||
|
||||
// Initialize a WDF context that can store a pointer to the device context object
|
||||
WDF_OBJECT_ATTRIBUTES Attr;
|
||||
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attr, IndirectDeviceContextWrapper);
|
||||
|
||||
IDARG_IN_ADAPTER_INIT AdapterInit = {};
|
||||
AdapterInit.WdfDevice = m_WdfDevice;
|
||||
AdapterInit.pCaps = &AdapterCaps;
|
||||
AdapterInit.ObjectAttributes = &Attr;
|
||||
|
||||
// Start the initialization of the adapter, which will trigger the AdapterFinishInit callback later
|
||||
IDARG_OUT_ADAPTER_INIT AdapterInitOut;
|
||||
NTSTATUS Status = IddCxAdapterInitAsync(&AdapterInit, &AdapterInitOut);
|
||||
|
||||
if (NT_SUCCESS(Status))
|
||||
{
|
||||
// Store a reference to the WDF adapter handle
|
||||
m_Adapter = AdapterInitOut.AdapterObject;
|
||||
|
||||
// Store the device context object into the WDF object context
|
||||
auto* pContext = WdfObjectGet_IndirectDeviceContextWrapper(AdapterInitOut.AdapterObject);
|
||||
pContext->pContext = this;
|
||||
}
|
||||
}
|
||||
|
||||
void IndirectDeviceContext::FinishInit()
|
||||
{
|
||||
for (unsigned int i = 0; i < numVirtualDisplays; i++) {
|
||||
CreateMonitor(i);
|
||||
}
|
||||
}
|
||||
|
||||
void IndirectDeviceContext::CreateMonitor(unsigned int index) {
|
||||
// ==============================
|
||||
// TODO: In a real driver, the EDID should be retrieved dynamically from a connected physical monitor. The EDID
|
||||
// provided here is purely for demonstration, as it describes only 640x480 @ 60 Hz and 800x600 @ 60 Hz. Monitor
|
||||
// manufacturers are required to correctly fill in physical monitor attributes in order to allow the OS to optimize
|
||||
// settings like viewing distance and scale factor. Manufacturers should also use a unique serial number every
|
||||
// single device to ensure the OS can tell the monitors apart.
|
||||
// ==============================
|
||||
|
||||
WDF_OBJECT_ATTRIBUTES Attr;
|
||||
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attr, IndirectDeviceContextWrapper);
|
||||
|
||||
IDDCX_MONITOR_INFO MonitorInfo = {};
|
||||
MonitorInfo.Size = sizeof(MonitorInfo);
|
||||
MonitorInfo.MonitorType = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
|
||||
MonitorInfo.ConnectorIndex = index;
|
||||
MonitorInfo.MonitorDescription.Size = sizeof(MonitorInfo.MonitorDescription);
|
||||
MonitorInfo.MonitorDescription.Type = IDDCX_MONITOR_DESCRIPTION_TYPE_EDID;
|
||||
MonitorInfo.MonitorDescription.DataSize = sizeof(s_KnownMonitorEdid);
|
||||
MonitorInfo.MonitorDescription.pData = const_cast<BYTE*>(s_KnownMonitorEdid);
|
||||
|
||||
// ==============================
|
||||
// TODO: The monitor's container ID should be distinct from "this" device's container ID if the monitor is not
|
||||
// permanently attached to the display adapter device object. The container ID is typically made unique for each
|
||||
// monitor and can be used to associate the monitor with other devices, like audio or input devices. In this
|
||||
// sample we generate a random container ID GUID, but it's best practice to choose a stable container ID for a
|
||||
// unique monitor or to use "this" device's container ID for a permanent/integrated monitor.
|
||||
// ==============================
|
||||
|
||||
// Create a container ID
|
||||
CoCreateGuid(&MonitorInfo.MonitorContainerId);
|
||||
|
||||
IDARG_IN_MONITORCREATE MonitorCreate = {};
|
||||
MonitorCreate.ObjectAttributes = &Attr;
|
||||
MonitorCreate.pMonitorInfo = &MonitorInfo;
|
||||
|
||||
// Create a monitor object with the specified monitor descriptor
|
||||
IDARG_OUT_MONITORCREATE MonitorCreateOut;
|
||||
NTSTATUS Status = IddCxMonitorCreate(m_Adapter, &MonitorCreate, &MonitorCreateOut);
|
||||
if (NT_SUCCESS(Status))
|
||||
{
|
||||
m_Monitor = MonitorCreateOut.MonitorObject;
|
||||
|
||||
// Associate the monitor with this device context
|
||||
auto* pContext = WdfObjectGet_IndirectDeviceContextWrapper(MonitorCreateOut.MonitorObject);
|
||||
pContext->pContext = this;
|
||||
|
||||
// Tell the OS that the monitor has been plugged in
|
||||
IDARG_OUT_MONITORARRIVAL ArrivalOut;
|
||||
Status = IddCxMonitorArrival(m_Monitor, &ArrivalOut);
|
||||
}
|
||||
}
|
||||
|
||||
void IndirectDeviceContext::AssignSwapChain(IDDCX_SWAPCHAIN SwapChain, LUID RenderAdapter, HANDLE NewFrameEvent)
|
||||
{
|
||||
m_ProcessingThread.reset();
|
||||
|
||||
auto Device = make_shared<Direct3DDevice>(RenderAdapter);
|
||||
if (FAILED(Device->Init()))
|
||||
{
|
||||
// It's important to delete the swap-chain if D3D initialization fails, so that the OS knows to generate a new
|
||||
// swap-chain and try again.
|
||||
WdfObjectDelete(SwapChain);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new swap-chain processing thread
|
||||
m_ProcessingThread.reset(new SwapChainProcessor(SwapChain, Device, NewFrameEvent));
|
||||
}
|
||||
}
|
||||
|
||||
void IndirectDeviceContext::UnassignSwapChain()
|
||||
{
|
||||
// Stop processing the last swap-chain
|
||||
m_ProcessingThread.reset();
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region DDI Callbacks
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleAdapterInitFinished(IDDCX_ADAPTER AdapterObject, const IDARG_IN_ADAPTER_INIT_FINISHED* pInArgs)
|
||||
{
|
||||
// This is called when the OS has finished setting up the adapter for use by the IddCx driver. It's now possible
|
||||
// to report attached monitors.
|
||||
|
||||
auto* pContext = WdfObjectGet_IndirectDeviceContextWrapper(AdapterObject);
|
||||
if (NT_SUCCESS(pInArgs->AdapterInitStatus))
|
||||
{
|
||||
pContext->pContext->FinishInit();
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleAdapterCommitModes(IDDCX_ADAPTER AdapterObject, const IDARG_IN_COMMITMODES* pInArgs)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(AdapterObject);
|
||||
UNREFERENCED_PARAMETER(pInArgs);
|
||||
|
||||
// For the sample, do nothing when modes are picked - the swap-chain is taken care of by IddCx
|
||||
|
||||
// ==============================
|
||||
// TODO: In a real driver, this function would be used to reconfigure the device to commit the new modes. Loop
|
||||
// through pInArgs->pPaths and look for IDDCX_PATH_FLAGS_ACTIVE. Any path not active is inactive (e.g. the monitor
|
||||
// should be turned off).
|
||||
// ==============================
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleParseMonitorDescription(const IDARG_IN_PARSEMONITORDESCRIPTION* pInArgs, IDARG_OUT_PARSEMONITORDESCRIPTION* pOutArgs)
|
||||
{
|
||||
// ==============================
|
||||
// TODO: In a real driver, this function would be called to generate monitor modes for an EDID by parsing it. In
|
||||
// this sample driver, we hard-code the EDID, so this function can generate known modes.
|
||||
// ==============================
|
||||
|
||||
for (int i = 0; i < monitorModes.size(); i++) {
|
||||
s_KnownMonitorModes2.push_back(dispinfo(std::get<0>(monitorModes[i]), std::get<1>(monitorModes[i]), std::get<2>(monitorModes[i])));
|
||||
}
|
||||
pOutArgs->MonitorModeBufferOutputCount = (UINT)monitorModes.size();
|
||||
|
||||
if (pInArgs->MonitorModeBufferInputCount < monitorModes.size())
|
||||
{
|
||||
// Return success if there was no buffer, since the caller was only asking for a count of modes
|
||||
return (pInArgs->MonitorModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy the known modes to the output buffer
|
||||
for (DWORD ModeIndex = 0; ModeIndex < monitorModes.size(); ModeIndex++)
|
||||
{
|
||||
pInArgs->pMonitorModes[ModeIndex].Size = sizeof(IDDCX_MONITOR_MODE);
|
||||
pInArgs->pMonitorModes[ModeIndex].Origin = IDDCX_MONITOR_MODE_ORIGIN_MONITORDESCRIPTOR;
|
||||
pInArgs->pMonitorModes[ModeIndex].MonitorVideoSignalInfo = s_KnownMonitorModes2[ModeIndex];
|
||||
}
|
||||
|
||||
// Set the preferred mode as represented in the EDID
|
||||
pOutArgs->PreferredMonitorModeIdx = 0;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleMonitorGetDefaultModes(IDDCX_MONITOR MonitorObject, const IDARG_IN_GETDEFAULTDESCRIPTIONMODES* pInArgs, IDARG_OUT_GETDEFAULTDESCRIPTIONMODES* pOutArgs)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(MonitorObject);
|
||||
UNREFERENCED_PARAMETER(pInArgs);
|
||||
UNREFERENCED_PARAMETER(pOutArgs);
|
||||
|
||||
// Should never be called since we create a single monitor with a known EDID in this sample driver.
|
||||
|
||||
// ==============================
|
||||
// TODO: In a real driver, this function would be called to generate monitor modes for a monitor with no EDID.
|
||||
// Drivers should report modes that are guaranteed to be supported by the transport protocol and by nearly all
|
||||
// monitors (such 640x480, 800x600, or 1024x768). If the driver has access to monitor modes from a descriptor other
|
||||
// than an EDID, those modes would also be reported here.
|
||||
// ==============================
|
||||
|
||||
return STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a target mode from the fundamental mode attributes.
|
||||
/// </summary>
|
||||
void CreateTargetMode(DISPLAYCONFIG_VIDEO_SIGNAL_INFO& Mode, UINT Width, UINT Height, UINT VSync)
|
||||
{
|
||||
Mode.totalSize.cx = Mode.activeSize.cx = Width;
|
||||
Mode.totalSize.cy = Mode.activeSize.cy = Height;
|
||||
Mode.AdditionalSignalInfo.vSyncFreqDivider = 1;
|
||||
Mode.AdditionalSignalInfo.videoStandard = 255;
|
||||
Mode.vSyncFreq.Numerator = VSync;
|
||||
Mode.vSyncFreq.Denominator = Mode.hSyncFreq.Denominator = 1;
|
||||
Mode.hSyncFreq.Numerator = VSync * Height;
|
||||
Mode.scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE;
|
||||
Mode.pixelRate = VSync * Width * Height;
|
||||
}
|
||||
|
||||
void CreateTargetMode(IDDCX_TARGET_MODE& Mode, UINT Width, UINT Height, UINT VSync)
|
||||
{
|
||||
Mode.Size = sizeof(Mode);
|
||||
CreateTargetMode(Mode.TargetVideoSignalInfo.targetVideoSignalInfo, Width, Height, VSync);
|
||||
}
|
||||
|
||||
void CreateTargetMode2(IDDCX_TARGET_MODE2& Mode, UINT Width, UINT Height, UINT VSync)
|
||||
{
|
||||
Mode.Size = sizeof(Mode);
|
||||
Mode.BitsPerComponent.Rgb = IDDCX_BITS_PER_COMPONENT_8 | IDDCX_BITS_PER_COMPONENT_10;
|
||||
CreateTargetMode(Mode.TargetVideoSignalInfo.targetVideoSignalInfo, Width, Height, VSync);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleMonitorQueryModes(IDDCX_MONITOR MonitorObject, const IDARG_IN_QUERYTARGETMODES* pInArgs, IDARG_OUT_QUERYTARGETMODES* pOutArgs)////////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
UNREFERENCED_PARAMETER(MonitorObject);
|
||||
|
||||
vector<IDDCX_TARGET_MODE> TargetModes(monitorModes.size());
|
||||
|
||||
// Create a set of modes supported for frame processing and scan-out. These are typically not based on the
|
||||
// monitor's descriptor and instead are based on the static processing capability of the device. The OS will
|
||||
// report the available set of modes for a given output as the intersection of monitor modes with target modes.
|
||||
|
||||
for (int i = 0; i < monitorModes.size(); i++) {
|
||||
CreateTargetMode(TargetModes[i], std::get<0>(monitorModes[i]), std::get<1>(monitorModes[i]), std::get<2>(monitorModes[i]));
|
||||
}
|
||||
|
||||
pOutArgs->TargetModeBufferOutputCount = (UINT)TargetModes.size();
|
||||
|
||||
if (pInArgs->TargetModeBufferInputCount >= TargetModes.size())
|
||||
{
|
||||
copy(TargetModes.begin(), TargetModes.end(), pInArgs->pTargetModes);
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleMonitorAssignSwapChain(IDDCX_MONITOR MonitorObject, const IDARG_IN_SETSWAPCHAIN* pInArgs)
|
||||
{
|
||||
auto* pContext = WdfObjectGet_IndirectDeviceContextWrapper(MonitorObject);
|
||||
pContext->pContext->AssignSwapChain(pInArgs->hSwapChain, pInArgs->RenderAdapterLuid, pInArgs->hNextSurfaceAvailable);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleMonitorUnassignSwapChain(IDDCX_MONITOR MonitorObject)
|
||||
{
|
||||
auto* pContext = WdfObjectGet_IndirectDeviceContextWrapper(MonitorObject);
|
||||
pContext->pContext->UnassignSwapChain();
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleEvtIddCxAdapterQueryTargetInfo(
|
||||
IDDCX_ADAPTER AdapterObject,
|
||||
IDARG_IN_QUERYTARGET_INFO* pInArgs,
|
||||
IDARG_OUT_QUERYTARGET_INFO* pOutArgs
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(AdapterObject);
|
||||
UNREFERENCED_PARAMETER(pInArgs);
|
||||
pOutArgs->TargetCaps = IDDCX_TARGET_CAPS_HIGH_COLOR_SPACE;
|
||||
pOutArgs->DitheringSupport.Rgb = IDDCX_BITS_PER_COMPONENT_10;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleEvtIddCxMonitorSetDefaultHdrMetadata(
|
||||
IDDCX_MONITOR MonitorObject,
|
||||
const IDARG_IN_MONITOR_SET_DEFAULT_HDR_METADATA* pInArgs
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(MonitorObject);
|
||||
UNREFERENCED_PARAMETER(pInArgs);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleEvtIddCxParseMonitorDescription2(
|
||||
const IDARG_IN_PARSEMONITORDESCRIPTION2* pInArgs,
|
||||
IDARG_OUT_PARSEMONITORDESCRIPTION* pOutArgs
|
||||
)
|
||||
{
|
||||
// ==============================
|
||||
// TODO: In a real driver, this function would be called to generate monitor modes for an EDID by parsing it. In
|
||||
// this sample driver, we hard-code the EDID, so this function can generate known modes.
|
||||
// ==============================
|
||||
|
||||
for (int i = 0; i < monitorModes.size(); i++) {
|
||||
s_KnownMonitorModes2.push_back(dispinfo(std::get<0>(monitorModes[i]), std::get<1>(monitorModes[i]), std::get<2>(monitorModes[i])));
|
||||
}
|
||||
pOutArgs->MonitorModeBufferOutputCount = (UINT)monitorModes.size();
|
||||
|
||||
if (pInArgs->MonitorModeBufferInputCount < monitorModes.size())
|
||||
{
|
||||
// Return success if there was no buffer, since the caller was only asking for a count of modes
|
||||
return (pInArgs->MonitorModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy the known modes to the output buffer
|
||||
for (DWORD ModeIndex = 0; ModeIndex < monitorModes.size(); ModeIndex++)
|
||||
{
|
||||
pInArgs->pMonitorModes[ModeIndex].Size = sizeof(IDDCX_MONITOR_MODE2);
|
||||
pInArgs->pMonitorModes[ModeIndex].Origin = IDDCX_MONITOR_MODE_ORIGIN_MONITORDESCRIPTOR;
|
||||
pInArgs->pMonitorModes[ModeIndex].MonitorVideoSignalInfo = s_KnownMonitorModes2[ModeIndex];
|
||||
pInArgs->pMonitorModes[ModeIndex].BitsPerComponent.Rgb = IDDCX_BITS_PER_COMPONENT_8 | IDDCX_BITS_PER_COMPONENT_10;
|
||||
}
|
||||
|
||||
// Set the preferred mode as represented in the EDID
|
||||
pOutArgs->PreferredMonitorModeIdx = 0;
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleEvtIddCxMonitorQueryTargetModes2(
|
||||
IDDCX_MONITOR MonitorObject,
|
||||
const IDARG_IN_QUERYTARGETMODES2* pInArgs,
|
||||
IDARG_OUT_QUERYTARGETMODES* pOutArgs
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(MonitorObject);
|
||||
|
||||
vector<IDDCX_TARGET_MODE2> TargetModes(monitorModes.size());
|
||||
|
||||
// Create a set of modes supported for frame processing and scan-out. These are typically not based on the
|
||||
// monitor's descriptor and instead are based on the static processing capability of the device. The OS will
|
||||
// report the available set of modes for a given output as the intersection of monitor modes with target modes.
|
||||
|
||||
for (int i = 0; i < monitorModes.size(); i++) {
|
||||
CreateTargetMode2(TargetModes[i], std::get<0>(monitorModes[i]), std::get<1>(monitorModes[i]), std::get<2>(monitorModes[i]));
|
||||
}
|
||||
|
||||
pOutArgs->TargetModeBufferOutputCount = (UINT)TargetModes.size();
|
||||
|
||||
if (pInArgs->TargetModeBufferInputCount >= TargetModes.size())
|
||||
{
|
||||
copy(TargetModes.begin(), TargetModes.end(), pInArgs->pTargetModes);
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleEvtIddCxAdapterCommitModes2(
|
||||
IDDCX_ADAPTER AdapterObject,
|
||||
const IDARG_IN_COMMITMODES2* pInArgs
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(AdapterObject);
|
||||
UNREFERENCED_PARAMETER(pInArgs);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS IddSampleEvtIddCxMonitorSetGammaRamp(
|
||||
IDDCX_MONITOR MonitorObject,
|
||||
const IDARG_IN_SET_GAMMARAMP* pInArgs
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(MonitorObject);
|
||||
UNREFERENCED_PARAMETER(pInArgs);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2013
|
||||
VisualStudioVersion = 12.0
|
||||
MinimumVisualStudioVersion = 12.0
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SampleDisplay", "Sample\SampleDisplay.vcxproj", "{D3C62015-3845-4DA8-8522-5DC45B735063}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
Release|Win32 = Release|Win32
|
||||
Debug|x64 = Debug|x64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D3C62015-3845-4DA8-8522-5DC45B735063}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{D3C62015-3845-4DA8-8522-5DC45B735063}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{D3C62015-3845-4DA8-8522-5DC45B735063}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{D3C62015-3845-4DA8-8522-5DC45B735063}.Release|Win32.Build.0 = Release|Win32
|
||||
{D3C62015-3845-4DA8-8522-5DC45B735063}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D3C62015-3845-4DA8-8522-5DC45B735063}.Debug|x64.Build.0 = Debug|x64
|
||||
{D3C62015-3845-4DA8-8522-5DC45B735063}.Release|x64.ActiveCfg = Release|x64
|
||||
{D3C62015-3845-4DA8-8522-5DC45B735063}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||