Compare commits
523 commits
Author | SHA1 | Date | |
---|---|---|---|
|
b3f2ce8500 | ||
|
235737381e | ||
|
ccbfd22db0 | ||
|
23d846c691 | ||
|
a16f686b14 | ||
|
3c9f345db3 | ||
|
bd71f6fa3b | ||
|
5c043cea17 | ||
|
dbd1e4e0da | ||
|
effaa6a79e | ||
|
88d0abc548 | ||
|
851c0917ec | ||
|
72a46d9d5b | ||
|
7305a74a1b | ||
|
ea1b7eb9bb | ||
|
b80802bd6e | ||
|
c1a821d03c | ||
|
1cde65f3da | ||
|
20d7f05edd | ||
|
5fbba28fa6 | ||
|
1b09ee8c9b | ||
|
784c671a81 | ||
|
cdb86df907 | ||
|
945114317f | ||
|
b30b9193b9 | ||
|
b2f3a938f1 | ||
|
432ca5f0e5 | ||
|
30d9dfe786 | ||
|
f4290f4905 | ||
|
9bc8847f3c | ||
|
7ea5b40248 | ||
|
68258eb104 | ||
|
9683e5c461 | ||
|
00b4c5ddcc | ||
|
e76790a5e1 | ||
|
2add7288ea | ||
|
49acc2ae27 | ||
|
a296df3da4 | ||
|
4cf4b980ea | ||
|
1f9a2483f4 | ||
|
12b0a99e76 | ||
|
6dd343e5f4 | ||
|
83944572da | ||
|
a131b2a3dd | ||
|
aa3dcbc38c | ||
|
1d4820c946 | ||
|
87dbd64c73 | ||
|
ad4942aad6 | ||
|
d01948563c | ||
|
52bff9dbf9 | ||
|
56122d04d5 | ||
|
3415a92820 | ||
|
b50b122d8c | ||
|
18f1570d99 | ||
|
5069e00eee | ||
|
0254018210 | ||
|
9fa3b70e39 | ||
|
729afdf67f | ||
|
fbbd0f1f54 | ||
|
3a37b16dfd | ||
|
e28c2724b1 | ||
|
fb0f07c19a | ||
|
effa675eac | ||
|
123bece791 | ||
|
2662b7540d | ||
|
1a9da04f78 | ||
|
8f1d04a66c | ||
|
14a83d7672 | ||
|
275b707bd2 | ||
|
41d0c76e5a | ||
|
a14d0296a3 | ||
|
480ad7b13b | ||
|
b2ce80f376 | ||
|
a14ea6fb32 | ||
|
663baa3f69 | ||
|
88bdabf5f2 | ||
|
9c43fb181f | ||
|
271a55d0ff | ||
|
5007203094 | ||
|
02b80946ce | ||
|
5cc40a1739 | ||
|
05c381bb70 | ||
|
2ef5922801 | ||
|
b30fdee093 | ||
|
22bfc56146 | ||
|
1fab03d91e | ||
|
919d88c7ed | ||
|
f2b34ea33f | ||
|
70a70acd02 | ||
|
d9ad6bdd7b | ||
|
49766341c1 | ||
|
3f23f0ebc2 | ||
|
7c8926a29a | ||
|
2c9dc941de | ||
|
75b0648983 | ||
|
aa91a4088f | ||
|
5677320a54 | ||
|
25ee3d2043 | ||
|
c0e962dd19 | ||
|
7211e4dff1 | ||
|
1ec1842bb0 | ||
|
e4c297e333 | ||
|
7c253fa28a | ||
|
bcb537d1ee | ||
|
d6093e46b6 | ||
|
7afcb95f5c | ||
|
f5b29b43cd | ||
|
48fcc40acd | ||
|
532716ea12 | ||
|
e5e1f848e4 | ||
|
c79cefd1c9 | ||
|
9dbbaf1b02 | ||
|
81dd170b62 | ||
|
76cde235c9 | ||
|
c614cdf71b | ||
|
d3c61273d3 | ||
|
faa32f96a2 | ||
|
ac70e4369a | ||
|
10b4c820ea | ||
|
a4836d10b9 | ||
|
4973212f15 | ||
|
94dd3ddd0c | ||
|
23f69c5fa8 | ||
|
62c7883ed6 | ||
|
c4debe75a8 | ||
|
d61dffbbd4 | ||
|
0988b5c211 | ||
|
07ed93d29e | ||
|
e12842085e | ||
|
1172bdd114 | ||
|
587ebcfc90 | ||
|
756838ae85 | ||
|
a16e8eb7c5 | ||
|
137a792431 | ||
|
cc502be893 | ||
|
9411780c48 | ||
|
e6d805145a | ||
|
4427f2e9e5 | ||
|
7c20b6088d | ||
|
73a5ea9cae | ||
|
1bbfe4354b | ||
|
ca8523752e | ||
|
37ec270c25 | ||
|
9234515fd9 | ||
|
f4b540f339 | ||
|
3c0ed17988 | ||
|
a9f3308b27 | ||
|
aef79c431a | ||
|
49eac15a87 | ||
|
a8643b00b1 | ||
|
eaf755ee81 | ||
|
7256910367 | ||
|
e6bd977f5c | ||
|
5eb471b077 | ||
|
afef0fa13f | ||
|
dfa24aadb1 | ||
|
74970ad7b7 | ||
|
38cca98da5 | ||
|
57c634149e | ||
|
ab4c30bb15 | ||
|
f8f50d6c27 | ||
|
44db922628 | ||
|
cd508b7584 | ||
|
411fc5ab07 | ||
|
c62670901e | ||
|
3a11885d72 | ||
|
39a0e750bd | ||
|
36ceb2b9af | ||
|
3ba03b8f9e | ||
|
ba69fe4df8 | ||
|
21b79ed0d4 | ||
|
3def2f40c0 | ||
|
37355bdfe1 | ||
|
021d1b2d85 | ||
|
b32f8aaffd | ||
|
168c67e992 | ||
|
461b4a9d17 | ||
|
4ddd229797 | ||
|
fc55672814 | ||
|
1ff825f598 | ||
|
fffa4b62f5 | ||
|
206dc1c3df | ||
|
fe2afeeb92 | ||
|
e214d71862 | ||
|
23848e8014 | ||
|
4c927a4225 | ||
|
2c4e9c656c | ||
|
86c0771c3e | ||
|
95181d34a5 | ||
|
f683797258 | ||
|
4ad0e3c7a2 | ||
|
754830eda4 | ||
|
16c25df2b0 | ||
|
282eede778 | ||
|
d6fc6941db | ||
|
7968ccf4a3 | ||
|
9e65ec3143 | ||
|
968cf08a36 | ||
|
9725c092e9 | ||
|
c219720685 | ||
|
101df84e73 | ||
|
27f2fed2f8 | ||
|
27a77d5b31 | ||
|
6b972b52cd | ||
|
a43c6e0d9c | ||
|
ea1d6a0d19 | ||
|
2ec51aa3e2 | ||
|
4bd9abe3f7 | ||
|
a98461a2c7 | ||
|
80a13f67e1 | ||
|
07c5a8a585 | ||
|
04fd61fade | ||
|
72b9f56e74 | ||
|
8354e9fece | ||
|
2242f2bc34 | ||
|
5533a2d749 | ||
|
0511468757 | ||
|
09a6b8927f | ||
|
50d0dc09d1 | ||
|
a4a13ecde5 | ||
|
fe4e172b9b | ||
|
4c600a8c66 | ||
|
2659d19283 | ||
|
6d28822851 | ||
|
d2705342c0 | ||
|
1c571291de | ||
|
7e1f646783 | ||
|
057e2281a6 | ||
|
18443a1682 | ||
|
555996a3cb | ||
|
c5d9b5aeda | ||
|
543ff7bc29 | ||
|
3ce1407477 | ||
|
a102c968f3 | ||
|
0d393684a0 | ||
|
8394704fec | ||
|
db46db002d | ||
|
401e1329f1 | ||
|
6d3832521f | ||
|
d0dfc22a37 | ||
|
dbc7d83eed | ||
|
1da1f489f4 | ||
|
94cf09f2b1 | ||
|
e8ca06e005 | ||
|
1aa6e7171d | ||
|
3dbafe4075 | ||
|
961131e1c1 | ||
|
1e9d94e70f | ||
|
6092273d52 | ||
|
6078d6cf8e | ||
|
b224692eea | ||
|
f80fbf6583 | ||
|
088412ddb4 | ||
|
31bae17632 | ||
|
76b04a0979 | ||
|
424bd195eb | ||
|
b4ad6b4ec1 | ||
|
1840afdd29 | ||
|
f2ac3fa66a | ||
|
e81e955da9 | ||
|
af7591617e | ||
|
adea63e089 | ||
|
a94bbb8bb5 | ||
|
466151311d | ||
|
5c2a4c5bc2 | ||
|
ddb8241528 | ||
|
c0351a534d | ||
|
a846f1b5e4 | ||
|
4fad5b17aa | ||
|
8233667fd5 | ||
|
2c04a00307 | ||
|
a5e68548d2 | ||
|
7ebdb01934 | ||
|
ab91572424 | ||
|
5df5fd77cd | ||
|
b1226d15a1 | ||
|
b7a7364b40 | ||
|
976ad23fc3 | ||
|
6611d4a7e8 | ||
|
58ee1f86ec | ||
|
8db78f8131 | ||
|
c2d5ca5062 | ||
|
7a0bfef122 | ||
|
79f0bbe740 | ||
|
92d7e058a9 | ||
|
898768ce71 | ||
|
08c6b09b2f | ||
|
bba1bedede | ||
|
83fa626b08 | ||
|
63a8300a68 | ||
|
a2accaa6e6 | ||
|
3dc0b487f0 | ||
|
cc9ebd4ecd | ||
|
8d0c0bb307 | ||
|
8021cbf02c | ||
|
c833817ab0 | ||
|
ac5a24dd3a | ||
|
b4f3f4f0a1 | ||
|
e5bb901a62 | ||
|
ff2ee98393 | ||
|
775500c5e4 | ||
|
9da079eea5 | ||
|
c073204193 | ||
|
c647cfef86 | ||
|
69e8cf4e6b | ||
|
7f4266fffb | ||
|
3dce420521 | ||
|
fd3cbd1f69 | ||
|
8c32dcbeb1 | ||
|
78325f1c61 | ||
|
5830f530c2 | ||
|
675facc773 | ||
|
e45c97b4f8 | ||
|
1f84df7300 | ||
|
53b2a5082d | ||
|
9333577a4f | ||
|
b70f966371 | ||
|
f2303853a1 | ||
|
a2507973db | ||
|
7f65ef0e7f | ||
|
fd554ec7ea | ||
|
ec7a2467f1 | ||
|
e4f3d6b67a | ||
|
6d8cf4da39 | ||
|
fc353ce760 | ||
|
a677a7a9b4 | ||
|
eee8b6b2f0 | ||
|
47f5c32655 | ||
|
467afe57d1 | ||
|
c84b7b3996 | ||
|
bc2899708a | ||
|
3ec663361f | ||
|
a140f13c23 | ||
|
ceb128b702 | ||
|
bf1629eb4c | ||
|
9189b0629d | ||
|
ae34aded47 | ||
|
cba17dd877 | ||
|
f2366c0512 | ||
|
680c093659 | ||
|
ed362a8f9d | ||
|
d3e4c655ae | ||
|
7c234efad6 | ||
|
f5234bcec3 | ||
|
8a6101d329 | ||
|
8cff4365fc | ||
|
15cf58c28c | ||
|
56ba365909 | ||
|
a30778401e | ||
|
a35ebda9d4 | ||
|
155d0679a8 | ||
|
49ea3cd7fe | ||
|
1e330d40a8 | ||
|
a16a3687fa | ||
|
e9efc8cd0b | ||
|
ee828466b2 | ||
|
f999504b9b | ||
|
5382f9f5af | ||
|
b217c55bc1 | ||
|
4919aa9afd | ||
|
30312cc025 | ||
|
b4f3e4e6b5 | ||
|
a8eec0d626 | ||
|
dc02aab5ab | ||
|
d669b15bde | ||
|
de57fce8f3 | ||
|
450fbfddf3 | ||
|
6d563f63ec | ||
|
64e7e93167 | ||
|
693f196225 | ||
|
8e83943044 | ||
|
0e7c59af0c | ||
|
8d5690928f | ||
|
b5d35ea0d5 | ||
|
a0cbd533cc | ||
|
f323abca72 | ||
|
520e056f6f | ||
|
76492e11dd | ||
|
bec9212d1b | ||
|
e921e1dc7e | ||
|
99fb5a0c08 | ||
|
b1dd42518c | ||
|
bf7a92f6b9 | ||
|
cc70299972 | ||
|
7c77c235c4 | ||
|
ee6e7b65f4 | ||
|
e55ed9870d | ||
|
2683a032c8 | ||
|
2c16ade92f | ||
|
6ca1cbaf5a | ||
|
edd66fb68c | ||
|
48a896ada6 | ||
|
4b13cd7129 | ||
|
0783a12727 | ||
|
4f38568896 | ||
|
577b9b7d5d | ||
|
35422f9ba5 | ||
|
8dd90406dc | ||
|
0a310aadb0 | ||
|
f430782d23 | ||
|
d5af7893ce | ||
|
97fda95805 | ||
|
0f745cd586 | ||
|
74c68b6d46 | ||
|
99f4c6e46c | ||
|
134536dbde | ||
|
39fb41a9b9 | ||
|
bb89755da6 | ||
|
6e17a69cf9 | ||
|
70f95a0991 | ||
|
61fdad66d9 | ||
|
7ee83dd8c9 | ||
|
7afcf103bf | ||
|
17079c28e6 | ||
|
fa12339659 | ||
|
7310f4c3a5 | ||
|
9ef27fd14e | ||
|
d644154c6d | ||
|
95059eaab2 | ||
|
0fa8b4cf84 | ||
|
c5f4add68b | ||
|
47ccdd7d21 | ||
|
e6f496a72e | ||
|
81b4bd037b | ||
|
e10620f717 | ||
|
0fe947e4bf | ||
|
3d22f41b2a | ||
|
fd6c3d5be2 | ||
|
f2cb823c54 | ||
|
58d99bf793 | ||
|
fae8fec3c3 | ||
|
b12ca85489 | ||
|
642b3a731e | ||
|
bdaebea71a | ||
|
27a018e5d0 | ||
|
366acc27ad | ||
|
0241f9c93c | ||
|
c0d71c6948 | ||
|
7b526db87e | ||
|
f4338c1c50 | ||
|
bc89f2403a | ||
|
3e205d8479 | ||
|
0044b88a79 | ||
|
d3318b65da | ||
|
30fe44980b | ||
|
2ee8779652 | ||
|
1aa3dec4d5 | ||
|
027e51407c | ||
|
a972ae3b70 | ||
|
bae4d72585 | ||
|
bd95ee3cd5 | ||
|
85587370f8 | ||
|
7bac914f51 | ||
|
4fefc4f253 | ||
|
d4fb5afdc7 | ||
|
cae6aa7922 | ||
|
0374261774 | ||
|
600d73d40e | ||
|
313d786555 | ||
|
5dd6828a31 | ||
|
5db49aebd0 | ||
|
b7038b5780 | ||
|
94bc94b626 | ||
|
ed8232b606 | ||
|
65bf8e55e9 | ||
|
b7c1c4fe6e | ||
|
b871d422e4 | ||
|
5ae2ca2e26 | ||
|
70f98c97d8 | ||
|
519c9c8fe4 | ||
|
b9acf24962 | ||
|
ec8f677593 | ||
|
c12ed3fc4e | ||
|
7301b61a42 | ||
|
88cab02cf0 | ||
|
15609e7240 | ||
|
2fd0270a1b | ||
|
a005582d5e | ||
|
c824c7a10b | ||
|
a12096ebfb | ||
|
b316929135 | ||
|
989ab5d3cc | ||
|
2b7f1740c4 | ||
|
2b4cf855fc | ||
|
7914950c9f | ||
|
49907998bb | ||
|
f17db4ed9f | ||
|
66b14a5c7f | ||
|
f5aece4741 | ||
|
ac28ab2b1c | ||
|
40ec45960b | ||
|
6c185f8767 | ||
|
2c7600661d | ||
|
d64e3df694 | ||
|
bfea86fa6a | ||
|
721ee0579e | ||
|
1a06c0ebe7 | ||
|
4200cb9cc0 | ||
|
8678196326 | ||
|
f92de22b2b | ||
|
2cea00c942 | ||
|
d894277d00 | ||
|
1edfa657ae | ||
|
11caf4ed91 | ||
|
afa153fd9b | ||
|
a175d6a718 | ||
|
903322eaa1 | ||
|
ceb55191f2 | ||
|
ecebddb0cb | ||
|
010af66b42 | ||
|
a5e1ff4c25 | ||
|
83a9d29da2 | ||
|
c1beb9ffa9 | ||
|
85fd0371b5 | ||
|
2932ddf1b6 | ||
|
9aa75c72b8 | ||
|
cdb5cb56f1 | ||
|
c00c02356a | ||
|
6162364577 | ||
|
f48306c27d | ||
|
7ff2768f94 | ||
|
bddf4ccea1 | ||
|
754c4a2218 |
79
.editorconfig
Normal file
|
@ -0,0 +1,79 @@
|
|||
[*.cs]
|
||||
|
||||
# WFAC010: Unsupported high DPI configuration
|
||||
dotnet_diagnostic.WFAC010.severity = silent
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_style_namespace_declarations = block_scoped:silent
|
||||
csharp_style_prefer_method_group_conversion = true:silent
|
||||
csharp_style_prefer_top_level_statements = true:silent
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
dotnet_diagnostic.SX1101.severity = warning
|
||||
dotnet_diagnostic.SA1101.severity = silent
|
||||
|
||||
[*.{cs,vb}]
|
||||
#### 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_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
|
12
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: Hofknecht # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
4
.github/workflows/dotnetframework.yml
vendored
|
@ -7,10 +7,10 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Nuget.exe
|
||||
uses: warrenbuckley/Setup-Nuget@v1
|
||||
uses: nuget/setup-nuget@v1
|
||||
- name: Nuget Restore
|
||||
run: nuget restore SystemTrayMenu.sln
|
||||
- name: Setup MSBuild.exe
|
||||
uses: warrenbuckley/Setup-MSBuild@v1
|
||||
uses: microsoft/setup-msbuild@v1
|
||||
- name: Build with MSBuild
|
||||
run: msbuild SystemTrayMenu.sln -p:Configuration=Release
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
namespace SystemTrayMenu
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32;
|
||||
using SystemTrayMenu.Business;
|
||||
using SystemTrayMenu.Helper.Updater;
|
||||
using SystemTrayMenu.UserInterface;
|
||||
using SystemTrayMenu.Utilities;
|
||||
|
||||
|
@ -15,32 +17,98 @@ namespace SystemTrayMenu
|
|||
/// </summary>
|
||||
internal class App : IDisposable
|
||||
{
|
||||
private readonly AppNotifyIcon menuNotifyIcon = new AppNotifyIcon();
|
||||
private readonly Menus menus = new Menus();
|
||||
private readonly AppNotifyIcon menuNotifyIcon = new();
|
||||
private readonly Menus menus = new();
|
||||
private readonly TaskbarForm taskbarForm = null;
|
||||
|
||||
public App()
|
||||
{
|
||||
AppRestart.BeforeRestarting += Dispose;
|
||||
SystemEvents.DisplaySettingsChanged += AppRestart.ByDisplaySettings;
|
||||
SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
|
||||
menus.LoadStarted += menuNotifyIcon.LoadingStart;
|
||||
menus.LoadStopped += menuNotifyIcon.LoadingStop;
|
||||
menuNotifyIcon.Exit += Application.Exit;
|
||||
menuNotifyIcon.Restart += AppRestart.ByMenuNotifyIcon;
|
||||
menuNotifyIcon.Click += MenuNotifyIcon_Click;
|
||||
void MenuNotifyIcon_Click()
|
||||
{
|
||||
menus.SwitchOpenClose(true);
|
||||
}
|
||||
|
||||
menuNotifyIcon.OpenLog += Log.OpenLogFile;
|
||||
menus.MainPreload();
|
||||
|
||||
if (Properties.Settings.Default.ShowInTaskbar)
|
||||
{
|
||||
taskbarForm = new TaskbarForm();
|
||||
taskbarForm.FormClosed += TaskbarForm_FormClosed;
|
||||
taskbarForm.Deactivate += SetStateNormal;
|
||||
taskbarForm.Resize += SetStateNormal;
|
||||
taskbarForm.Activated += TasbkarItemActivated;
|
||||
}
|
||||
|
||||
DllImports.NativeMethods.User32ShowInactiveTopmost(taskbarForm);
|
||||
|
||||
if (Properties.Settings.Default.CheckForUpdates)
|
||||
{
|
||||
new Thread((obj) => GitHubUpdate.ActivateNewVersionFormOrCheckForUpdates(
|
||||
showWhenUpToDate: false))
|
||||
.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SystemEvents.DisplaySettingsChanged -= AppRestart.ByDisplaySettings;
|
||||
menus.Dispose();
|
||||
menuNotifyIcon.Dispose();
|
||||
if (taskbarForm?.InvokeRequired == true)
|
||||
{
|
||||
taskbarForm.Invoke(Dispose);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppRestart.BeforeRestarting -= Dispose;
|
||||
SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged;
|
||||
menus.LoadStarted -= menuNotifyIcon.LoadingStart;
|
||||
menus.LoadStopped -= menuNotifyIcon.LoadingStop;
|
||||
menus.Dispose();
|
||||
menuNotifyIcon.Click -= MenuNotifyIcon_Click;
|
||||
menuNotifyIcon.OpenLog -= Log.OpenLogFile;
|
||||
menuNotifyIcon.Dispose();
|
||||
if (taskbarForm != null)
|
||||
{
|
||||
taskbarForm.FormClosed -= TaskbarForm_FormClosed;
|
||||
taskbarForm.Deactivate -= SetStateNormal;
|
||||
taskbarForm.Resize -= SetStateNormal;
|
||||
taskbarForm.Activated -= TasbkarItemActivated;
|
||||
taskbarForm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
|
||||
{
|
||||
menus.ReAdjustSizeAndLocation();
|
||||
}
|
||||
|
||||
private void MenuNotifyIcon_Click()
|
||||
{
|
||||
menus.SwitchOpenClose(true);
|
||||
}
|
||||
|
||||
private void TaskbarForm_FormClosed(object sender, FormClosedEventArgs e)
|
||||
{
|
||||
Application.Exit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This ensures that next click on taskbaritem works as activate event/click event.
|
||||
/// </summary>
|
||||
private void SetStateNormal(object sender, EventArgs e)
|
||||
{
|
||||
if (Form.ActiveForm == taskbarForm)
|
||||
{
|
||||
taskbarForm.WindowState = FormWindowState.Normal;
|
||||
}
|
||||
}
|
||||
|
||||
private void TasbkarItemActivated(object sender, EventArgs e)
|
||||
{
|
||||
SetStateNormal(sender, e);
|
||||
taskbarForm.Activate();
|
||||
taskbarForm.Focus();
|
||||
menus.SwitchOpenCloseByTaskbarItem();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,36 +17,37 @@ namespace SystemTrayMenu.Handler
|
|||
internal class KeyboardInput : IDisposable
|
||||
{
|
||||
private readonly Menu[] menus;
|
||||
private readonly KeyboardHook hook = new KeyboardHook();
|
||||
private readonly KeyboardHook hook = new();
|
||||
|
||||
private int iRowKey = -1;
|
||||
private int iMenuKey = 0;
|
||||
private int iMenuKey;
|
||||
|
||||
public KeyboardInput(Menu[] menus)
|
||||
{
|
||||
this.menus = menus;
|
||||
}
|
||||
|
||||
internal event EventHandlerEmpty HotKeyPressed;
|
||||
public event Action HotKeyPressed;
|
||||
|
||||
internal event EventHandlerEmpty ClosePressed;
|
||||
public event Action ClosePressed;
|
||||
|
||||
internal event Action<DataGridView, int> RowSelected;
|
||||
public event Action<DataGridView, int> RowSelected;
|
||||
|
||||
internal event Action<int, DataGridView> RowDeselected;
|
||||
public event Action<DataGridView, int> RowDeselected;
|
||||
|
||||
internal event Action<DataGridView, int> EnterPressed;
|
||||
public event Action<DataGridView, int> EnterPressed;
|
||||
|
||||
internal event EventHandlerEmpty Cleared;
|
||||
public event Action Cleared;
|
||||
|
||||
internal bool InUse { get; set; } = false;
|
||||
public bool InUse { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
hook.KeyPressed -= Hook_KeyPressed;
|
||||
hook.Dispose();
|
||||
}
|
||||
|
||||
internal void RegisterHotKey()
|
||||
public void RegisterHotKey()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Properties.Settings.Default.HotKey))
|
||||
{
|
||||
|
@ -54,10 +55,6 @@ namespace SystemTrayMenu.Handler
|
|||
{
|
||||
hook.RegisterHotKey();
|
||||
hook.KeyPressed += Hook_KeyPressed;
|
||||
void Hook_KeyPressed(object sender, KeyPressedEventArgs e)
|
||||
{
|
||||
HotKeyPressed?.Invoke();
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
|
@ -68,27 +65,38 @@ namespace SystemTrayMenu.Handler
|
|||
}
|
||||
}
|
||||
|
||||
internal void ResetSelectedByKey()
|
||||
public void ResetSelectedByKey()
|
||||
{
|
||||
iRowKey = -1;
|
||||
iMenuKey = 0;
|
||||
}
|
||||
|
||||
internal void CmdKeyProcessed(object sender, Keys keys)
|
||||
public void CmdKeyProcessed(object sender, Keys keys)
|
||||
{
|
||||
sender ??= menus[iMenuKey];
|
||||
|
||||
switch (keys)
|
||||
{
|
||||
case Keys.Enter:
|
||||
SelectByKey(keys);
|
||||
menus[iMenuKey]?.FocusTextBox();
|
||||
break;
|
||||
case Keys.Left:
|
||||
SelectByKey(keys);
|
||||
break;
|
||||
case Keys.Right:
|
||||
SelectByKey(keys);
|
||||
break;
|
||||
case Keys.Home:
|
||||
case Keys.End:
|
||||
case Keys.Up:
|
||||
case Keys.Down:
|
||||
case Keys.Left:
|
||||
case Keys.Right:
|
||||
case Keys.Escape:
|
||||
case Keys.Alt | Keys.F4:
|
||||
SelectByKey(keys);
|
||||
break;
|
||||
case Keys.Control | Keys.F:
|
||||
Menu menu = menus[iMenuKey];
|
||||
menu.FocusTextBox();
|
||||
menus[iMenuKey]?.FocusTextBox();
|
||||
break;
|
||||
case Keys.Tab:
|
||||
{
|
||||
|
@ -105,7 +113,7 @@ namespace SystemTrayMenu.Handler
|
|||
indexNew = indexMax;
|
||||
}
|
||||
|
||||
menus[indexNew].FocusTextBox();
|
||||
menus[indexNew]?.FocusTextBox();
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -124,21 +132,21 @@ namespace SystemTrayMenu.Handler
|
|||
indexNew = 0;
|
||||
}
|
||||
|
||||
menus[indexNew].FocusTextBox();
|
||||
menus[indexNew]?.FocusTextBox();
|
||||
}
|
||||
|
||||
break;
|
||||
case Keys.Apps:
|
||||
{
|
||||
DataGridView dgv = menus[iMenuKey].GetDataGridView();
|
||||
DataGridView dgv = menus[iMenuKey]?.GetDataGridView();
|
||||
|
||||
if (iRowKey > -1 &&
|
||||
dgv.Rows.Count > iRowKey)
|
||||
{
|
||||
Point pt = dgv.GetCellDisplayRectangle(2, iRowKey, false).Location;
|
||||
Point point = dgv.GetCellDisplayRectangle(2, iRowKey, false).Location;
|
||||
RowData trigger = (RowData)dgv.Rows[iRowKey].Cells[2].Value;
|
||||
MouseEventArgs mea = new MouseEventArgs(MouseButtons.Right, 1, pt.X, pt.Y, 0);
|
||||
trigger.MouseDown(dgv, mea);
|
||||
MouseEventArgs mouseEventArgs = new(MouseButtons.Right, 1, point.X, point.Y, 0);
|
||||
trigger.MouseDown(dgv, mouseEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,48 +172,30 @@ namespace SystemTrayMenu.Handler
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// While menu is open user presses a key to search for specific entries.
|
||||
/// </summary>
|
||||
/// <param name="sender">not used.</param>
|
||||
/// <param name="e">Key data of the pressed key.</param>
|
||||
internal void KeyPress(object sender, KeyPressEventArgs e)
|
||||
{
|
||||
if (char.IsLetterOrDigit(e.KeyChar) ||
|
||||
char.IsPunctuation(e.KeyChar) ||
|
||||
char.IsWhiteSpace(e.KeyChar) ||
|
||||
char.IsSeparator(e.KeyChar))
|
||||
{
|
||||
string letter = e.KeyChar.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
Menu menu = menus[iMenuKey];
|
||||
menu.KeyPressedSearch(letter);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal void SearchTextChanging()
|
||||
public void SearchTextChanging()
|
||||
{
|
||||
ClearIsSelectedByKey();
|
||||
}
|
||||
|
||||
internal void SearchTextChanged(object sender, EventArgs e)
|
||||
public void SearchTextChanged(Menu menu, bool isSearchStringEmpty)
|
||||
{
|
||||
Menu menu = (Menu)sender;
|
||||
DataGridView dgv = menu.GetDataGridView();
|
||||
if (dgv.Rows.Count > 0)
|
||||
if (isSearchStringEmpty)
|
||||
{
|
||||
ClearIsSelectedByKey();
|
||||
}
|
||||
else if (dgv.Rows.Count > 0)
|
||||
{
|
||||
Select(dgv, 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ClearIsSelectedByKey()
|
||||
public void ClearIsSelectedByKey()
|
||||
{
|
||||
ClearIsSelectedByKey(iMenuKey, iRowKey);
|
||||
}
|
||||
|
||||
internal void Select(DataGridView dgv, int i, bool refreshview)
|
||||
public void Select(DataGridView dgv, int i, bool refreshview)
|
||||
{
|
||||
int newiMenuKey = ((Menu)dgv.TopLevelControl).Level;
|
||||
if (i != iRowKey || newiMenuKey != iMenuKey)
|
||||
|
@ -220,7 +210,11 @@ namespace SystemTrayMenu.Handler
|
|||
{
|
||||
DataGridViewRow row = dgv.Rows[i];
|
||||
RowData rowData = (RowData)row.Cells[2].Value;
|
||||
rowData.IsSelected = true;
|
||||
if (rowData != null)
|
||||
{
|
||||
rowData.IsSelected = true;
|
||||
}
|
||||
|
||||
if (refreshview)
|
||||
{
|
||||
row.Selected = false;
|
||||
|
@ -229,6 +223,11 @@ namespace SystemTrayMenu.Handler
|
|||
}
|
||||
}
|
||||
|
||||
private void Hook_KeyPressed(object sender, KeyPressedEventArgs e)
|
||||
{
|
||||
HotKeyPressed?.Invoke();
|
||||
}
|
||||
|
||||
private bool IsAnyMenuSelectedByKey(
|
||||
ref DataGridView dgv,
|
||||
ref Menu menuFromSelected,
|
||||
|
@ -292,19 +291,29 @@ namespace SystemTrayMenu.Handler
|
|||
switch (keys)
|
||||
{
|
||||
case Keys.Enter:
|
||||
if (iRowKey > -1 &&
|
||||
dgv.Rows.Count > iRowKey)
|
||||
if (iRowKey > -1 && dgv.Rows.Count > iRowKey)
|
||||
{
|
||||
RowData trigger = (RowData)dgv.Rows[iRowKey].Cells[2].Value;
|
||||
if (trigger.IsMenuOpen || !trigger.ContainsMenu)
|
||||
{
|
||||
trigger.MouseDown(dgv, null);
|
||||
trigger.MouseClick(null, out bool toCloseByMouseClick);
|
||||
trigger.DoubleClick(
|
||||
new MouseEventArgs(MouseButtons.Left, 0, 0, 0, 0));
|
||||
new MouseEventArgs(MouseButtons.Left, 0, 0, 0, 0),
|
||||
out bool toCloseByDoubleClick);
|
||||
if (toCloseByMouseClick || toCloseByDoubleClick)
|
||||
{
|
||||
ClosePressed?.Invoke();
|
||||
}
|
||||
|
||||
if (iRowKey > -1 && dgv.Rows.Count > iRowKey)
|
||||
{
|
||||
// Raise Dgv_RowPostPaint to show ProcessStarted
|
||||
dgv.InvalidateRow(iRowKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RowDeselected(iRowBefore, dgvBefore);
|
||||
RowDeselected(dgvBefore, iRowBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
EnterPressed.Invoke(dgv, iRowKey);
|
||||
}
|
||||
|
@ -315,7 +324,7 @@ namespace SystemTrayMenu.Handler
|
|||
if (SelectMatchedReverse(dgv, iRowKey) ||
|
||||
SelectMatchedReverse(dgv, dgv.Rows.Count - 1))
|
||||
{
|
||||
RowDeselected(iRowBefore, dgvBefore);
|
||||
RowDeselected(dgvBefore, iRowBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
toClear = true;
|
||||
}
|
||||
|
@ -325,82 +334,59 @@ namespace SystemTrayMenu.Handler
|
|||
if (SelectMatched(dgv, iRowKey) ||
|
||||
SelectMatched(dgv, 0))
|
||||
{
|
||||
RowDeselected(iRowBefore, dgvBefore);
|
||||
RowDeselected(dgvBefore, iRowBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
toClear = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case Keys.Home:
|
||||
if (SelectMatched(dgv, 0))
|
||||
{
|
||||
RowDeselected(dgvBefore, iRowBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
toClear = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case Keys.End:
|
||||
if (SelectMatchedReverse(dgv, dgv.Rows.Count - 1))
|
||||
{
|
||||
RowDeselected(dgvBefore, iRowBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
toClear = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case Keys.Left:
|
||||
int iMenuKeyNext = iMenuKey + 1;
|
||||
if (isStillSelected)
|
||||
bool nextMenuLocationIsLeft = menus[iMenuKey + 1] != null && menus[iMenuKey + 1].Location.X < menus[iMenuKey].Location.X;
|
||||
bool previousMenuLocationIsRight = iMenuKey > 0 && menus[iMenuKey]?.Location.X < menus[iMenuKey - 1]?.Location.X;
|
||||
if (nextMenuLocationIsLeft || previousMenuLocationIsRight)
|
||||
{
|
||||
if (menuFromSelected != null &&
|
||||
menuFromSelected == menus[iMenuKeyNext])
|
||||
{
|
||||
dgv = menuFromSelected.GetDataGridView();
|
||||
if (dgv.Rows.Count > 0)
|
||||
{
|
||||
iMenuKey += 1;
|
||||
iRowKey = -1;
|
||||
if (SelectMatched(dgv, iRowKey) ||
|
||||
SelectMatched(dgv, 0))
|
||||
{
|
||||
RowDeselected(iRowBefore, dgvBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
toClear = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
SelectNextMenu(iRowBefore, ref dgv, dgvBefore, menuFromSelected, isStillSelected, ref toClear);
|
||||
}
|
||||
else
|
||||
else if (iMenuKey > 0)
|
||||
{
|
||||
iRowKey = -1;
|
||||
iMenuKey = menus.Where(m => m != null).Count() - 1;
|
||||
if (menus[iMenuKey] != null)
|
||||
{
|
||||
dgv = menus[iMenuKey].GetDataGridView();
|
||||
if (SelectMatched(dgv, iRowKey) ||
|
||||
SelectMatched(dgv, 0))
|
||||
{
|
||||
RowDeselected(iRowBefore, dgvBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
toClear = true;
|
||||
}
|
||||
}
|
||||
SelectPreviousMenu(iRowBefore, ref menu, ref dgv, dgvBefore, ref toClear);
|
||||
}
|
||||
|
||||
break;
|
||||
case Keys.Right:
|
||||
if (iMenuKey > 0)
|
||||
bool nextMenuLocationIsRight = menus[iMenuKey + 1]?.Location.X > menus[iMenuKey]?.Location.X;
|
||||
bool previousMenuLocationIsLeft = iMenuKey > 0 && menus[iMenuKey]?.Location.X > menus[iMenuKey - 1]?.Location.X;
|
||||
if (nextMenuLocationIsRight || previousMenuLocationIsLeft)
|
||||
{
|
||||
if (menus[iMenuKey - 1] != null)
|
||||
{
|
||||
iMenuKey -= 1;
|
||||
iRowKey = -1;
|
||||
menu = menus[iMenuKey];
|
||||
dgv = menu.GetDataGridView();
|
||||
if (SelectMatched(dgv, dgv.SelectedRows[0].Index) ||
|
||||
SelectMatched(dgv, 0))
|
||||
{
|
||||
RowDeselected(iRowBefore, dgvBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
toClear = true;
|
||||
}
|
||||
}
|
||||
SelectNextMenu(iRowBefore, ref dgv, dgvBefore, menuFromSelected, isStillSelected, ref toClear);
|
||||
}
|
||||
else
|
||||
else if (iMenuKey > 0)
|
||||
{
|
||||
RowDeselected(iRowBefore, dgvBefore);
|
||||
iMenuKey = 0;
|
||||
iRowKey = -1;
|
||||
toClear = true;
|
||||
Cleared?.Invoke();
|
||||
SelectPreviousMenu(iRowBefore, ref menu, ref dgv, dgvBefore, ref toClear);
|
||||
}
|
||||
|
||||
break;
|
||||
case Keys.Escape:
|
||||
RowDeselected(iRowBefore, dgvBefore);
|
||||
case Keys.Alt | Keys.F4:
|
||||
RowDeselected(dgvBefore, iRowBefore);
|
||||
iMenuKey = 0;
|
||||
iRowKey = -1;
|
||||
toClear = true;
|
||||
|
@ -412,7 +398,7 @@ namespace SystemTrayMenu.Handler
|
|||
if (SelectMatched(dgv, iRowKey, keyInput) ||
|
||||
SelectMatched(dgv, 0, keyInput))
|
||||
{
|
||||
RowDeselected(iRowBefore, null);
|
||||
RowDeselected(null, iRowBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
toClear = true;
|
||||
}
|
||||
|
@ -422,7 +408,7 @@ namespace SystemTrayMenu.Handler
|
|||
if (SelectMatched(dgv, iRowKey, keyInput) ||
|
||||
SelectMatched(dgv, 0, keyInput))
|
||||
{
|
||||
RowDeselected(iRowBefore, null);
|
||||
RowDeselected(null, iRowBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
}
|
||||
else
|
||||
|
@ -441,6 +427,77 @@ namespace SystemTrayMenu.Handler
|
|||
}
|
||||
}
|
||||
|
||||
private void SelectPreviousMenu(int iRowBefore, ref Menu menu, ref DataGridView dgv, DataGridView dgvBefore, ref bool toClear)
|
||||
{
|
||||
if (iMenuKey > 0)
|
||||
{
|
||||
if (menus[iMenuKey - 1] != null)
|
||||
{
|
||||
iMenuKey -= 1;
|
||||
iRowKey = -1;
|
||||
menu = menus[iMenuKey];
|
||||
dgv = menu.GetDataGridView();
|
||||
if ((dgv.SelectedRows.Count > 0 &&
|
||||
SelectMatched(dgv, dgv.SelectedRows[0].Index)) ||
|
||||
SelectMatched(dgv, 0))
|
||||
{
|
||||
RowDeselected(dgvBefore, iRowBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
toClear = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RowDeselected(dgvBefore, iRowBefore);
|
||||
iMenuKey = 0;
|
||||
iRowKey = -1;
|
||||
toClear = true;
|
||||
Cleared?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectNextMenu(int iRowBefore, ref DataGridView dgv, DataGridView dgvBefore, Menu menuFromSelected, bool isStillSelected, ref bool toClear)
|
||||
{
|
||||
int iMenuKeyNext = iMenuKey + 1;
|
||||
if (isStillSelected)
|
||||
{
|
||||
if (menuFromSelected != null &&
|
||||
menuFromSelected == menus[iMenuKeyNext])
|
||||
{
|
||||
dgv = menuFromSelected.GetDataGridView();
|
||||
if (dgv.Rows.Count > 0)
|
||||
{
|
||||
iMenuKey += 1;
|
||||
iRowKey = -1;
|
||||
if (SelectMatched(dgv, iRowKey) ||
|
||||
SelectMatched(dgv, 0))
|
||||
{
|
||||
RowDeselected(dgvBefore, iRowBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
toClear = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
iRowKey = -1;
|
||||
iMenuKey = menus.Where(m => m != null).Count() - 1;
|
||||
if (menus[iMenuKey] != null)
|
||||
{
|
||||
dgv = menus[iMenuKey].GetDataGridView();
|
||||
if (SelectMatched(dgv, iRowKey) ||
|
||||
SelectMatched(dgv, 0))
|
||||
{
|
||||
RowDeselected(dgvBefore, iRowBefore);
|
||||
SelectRow(dgv, iRowKey);
|
||||
toClear = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectRow(DataGridView dgv, int iRowKey)
|
||||
{
|
||||
InUse = true;
|
||||
|
@ -521,9 +578,13 @@ namespace SystemTrayMenu.Handler
|
|||
if (dgv.Rows.Count > rowIndex)
|
||||
{
|
||||
DataGridViewRow row = dgv.Rows[rowIndex];
|
||||
RowData rowData = (RowData)row.Cells[2].Value;
|
||||
rowData.IsSelected = false;
|
||||
row.Selected = false;
|
||||
RowData rowData = (RowData)row.Cells[2].Value;
|
||||
if (rowData != null)
|
||||
{
|
||||
rowData.IsSelected = false;
|
||||
rowData.IsClicking = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1540
Business/Menus.cs
269
Business/MenusHelpers.cs
Normal file
|
@ -0,0 +1,269 @@
|
|||
// <copyright file="MenusHelpers.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace SystemTrayMenu.Business
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SystemTrayMenu.DataClasses;
|
||||
using SystemTrayMenu.Helper;
|
||||
using SystemTrayMenu.Utilities;
|
||||
|
||||
internal static class MenusHelpers
|
||||
{
|
||||
internal static void GetItemsForMainMenu(BackgroundWorker worker, string path, ref MenuData menuData)
|
||||
{
|
||||
menuData.IsNetworkRoot = FileLnk.IsNetworkRoot(path);
|
||||
if (menuData.IsNetworkRoot)
|
||||
{
|
||||
GetNetworkRootDirectories(path, ref menuData);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetDirectories(worker, path, ref menuData);
|
||||
GetFiles(worker, path, ref menuData);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void GetAddionalItemsForMainMenu(ref MenuData menuData)
|
||||
{
|
||||
if (menuData.Level != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var path in GetAddionalPathsForMainMenu())
|
||||
{
|
||||
GetDirectoriesAndFilesRecursive(ref menuData, path.Path, path.OnlyFiles, path.Recursive);
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<(string Path, bool Recursive, bool OnlyFiles)> GetAddionalPathsForMainMenu()
|
||||
{
|
||||
foreach (string pathAndRecursivString in Properties.Settings.Default.PathsAddToMainMenu.Split(@"|"))
|
||||
{
|
||||
if (string.IsNullOrEmpty(pathAndRecursivString))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string pathAddForMainMenu = pathAndRecursivString.Split("recursiv:")[0].Trim();
|
||||
bool recursive = pathAndRecursivString.Split("recursiv:")[1].StartsWith("True");
|
||||
bool onlyFiles = pathAndRecursivString.Split("onlyFiles:")[1].StartsWith("True");
|
||||
yield return (Path: pathAddForMainMenu, Recursive: recursive, OnlyFiles: onlyFiles);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ReadHiddenAndReadIcons(BackgroundWorker worker, ref MenuData menuData)
|
||||
{
|
||||
List<RowData> rowDatasToRemove = new();
|
||||
foreach (RowData rowData in menuData.RowDatas)
|
||||
{
|
||||
if (worker?.CancellationPending == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!menuData.IsNetworkRoot && FolderOptions.IsHidden(rowData))
|
||||
{
|
||||
rowDatasToRemove.Add(rowData);
|
||||
continue;
|
||||
}
|
||||
|
||||
rowData.ReadIcon(true);
|
||||
}
|
||||
|
||||
menuData.RowDatas = menuData.RowDatas.Except(rowDatasToRemove).ToList();
|
||||
}
|
||||
|
||||
internal static void CheckIfValid(ref MenuData menuData)
|
||||
{
|
||||
if (menuData.Validity == MenuDataValidity.Undefined)
|
||||
{
|
||||
if (menuData.RowDatas.Count == 0)
|
||||
{
|
||||
menuData.Validity = MenuDataValidity.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
menuData.Validity = MenuDataValidity.Valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SortItemsWhenValid(ref MenuData menuData)
|
||||
{
|
||||
if (menuData.Validity != MenuDataValidity.Valid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
menuData.RowDatas = SortItems(menuData.RowDatas);
|
||||
}
|
||||
|
||||
internal static List<RowData> SortItems(List<RowData> rowDatas)
|
||||
{
|
||||
if (Properties.Settings.Default.SortByTypeAndNameWindowsExplorerSort)
|
||||
{
|
||||
rowDatas = rowDatas.OrderByDescending(x => x.IsFolder)
|
||||
.ThenBy(x => x.Text, new WindowsExplorerSort()).ToList();
|
||||
}
|
||||
else if (Properties.Settings.Default.SortByTypeAndDate)
|
||||
{
|
||||
rowDatas = rowDatas.OrderByDescending(x => x.IsFolder)
|
||||
.ThenByDescending(x => x.FileInfo.LastWriteTime).ToList();
|
||||
}
|
||||
else if (Properties.Settings.Default.SortByFileExtensionAndName)
|
||||
{
|
||||
rowDatas = rowDatas.OrderBy(x => x.FileExtension).ThenBy(x => x.Text).ToList();
|
||||
}
|
||||
else if (Properties.Settings.Default.SortByName)
|
||||
{
|
||||
rowDatas = rowDatas.OrderBy(x => x.Text).ToList();
|
||||
}
|
||||
else if (Properties.Settings.Default.SortByDate)
|
||||
{
|
||||
rowDatas = rowDatas.OrderByDescending(x => x.FileInfo.LastWriteTime).ToList();
|
||||
}
|
||||
|
||||
return rowDatas;
|
||||
}
|
||||
|
||||
private static void GetNetworkRootDirectories(string path, ref MenuData menuData)
|
||||
{
|
||||
Process cmd = new();
|
||||
cmd.StartInfo.FileName = "cmd.exe";
|
||||
cmd.StartInfo.RedirectStandardInput = true;
|
||||
cmd.StartInfo.RedirectStandardOutput = true;
|
||||
cmd.StartInfo.CreateNoWindow = true;
|
||||
cmd.StartInfo.UseShellExecute = false;
|
||||
cmd.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
|
||||
try
|
||||
{
|
||||
bool resolvedSomething = false;
|
||||
cmd.Start();
|
||||
cmd.StandardInput.WriteLine($"net view {path}");
|
||||
cmd.StandardInput.Flush();
|
||||
cmd.StandardInput.Close();
|
||||
string output = cmd.StandardOutput.ReadToEnd();
|
||||
cmd.WaitForExit();
|
||||
cmd.Close();
|
||||
List<string> lines = output
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
if (lines.Count > 8)
|
||||
{
|
||||
foreach (string line in lines.Skip(6).SkipLast(2))
|
||||
{
|
||||
int indexOfFirstSpace = line.IndexOf(" ", StringComparison.InvariantCulture);
|
||||
if (indexOfFirstSpace > 0)
|
||||
{
|
||||
string directory = Path.Combine(path, line[..indexOfFirstSpace]);
|
||||
menuData.RowDatas.Add(new RowData(true, false, true, menuData.Level, directory));
|
||||
resolvedSomething = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!resolvedSomething)
|
||||
{
|
||||
Log.Info($"Could not resolve network root folder: {path} , output:{output}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn($"path:'{path}'", ex);
|
||||
if (ex is UnauthorizedAccessException)
|
||||
{
|
||||
menuData.Validity = MenuDataValidity.NoAccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetDirectories(BackgroundWorker worker, string path, ref MenuData menuData)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var directory in Directory.GetDirectories(path))
|
||||
{
|
||||
if (worker?.CancellationPending == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
menuData.RowDatas.Add(new RowData(true, false, false, menuData.Level, directory));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn($"path:'{path}'", ex);
|
||||
if (ex is UnauthorizedAccessException)
|
||||
{
|
||||
menuData.Validity = MenuDataValidity.NoAccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetFiles(BackgroundWorker worker, string path, ref MenuData menuData)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (string file in DirectoryBySearchPattern.GetFiles(path, Config.SearchPattern))
|
||||
{
|
||||
if (worker?.CancellationPending == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
menuData.RowDatas.Add(new RowData(false, false, false, menuData.Level, file));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn($"path:'{path}'", ex);
|
||||
if (ex is UnauthorizedAccessException)
|
||||
{
|
||||
menuData.Validity = MenuDataValidity.NoAccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetDirectoriesAndFilesRecursive(
|
||||
ref MenuData menuData,
|
||||
string path,
|
||||
bool onlyFiles,
|
||||
bool recursiv)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (string file in DirectoryBySearchPattern.GetFiles(path, Config.SearchPattern))
|
||||
{
|
||||
menuData.RowDatas.Add(new RowData(false, true, false, menuData.Level, file));
|
||||
}
|
||||
|
||||
foreach (string directory in Directory.GetDirectories(path))
|
||||
{
|
||||
if (!onlyFiles)
|
||||
{
|
||||
menuData.RowDatas.Add(new RowData(true, true, false, menuData.Level, directory));
|
||||
}
|
||||
|
||||
if (recursiv)
|
||||
{
|
||||
GetDirectoriesAndFilesRecursive(ref menuData, directory, onlyFiles, recursiv);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn($"GetDirectoriesAndFilesRecursive path:'{path}'", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,45 +6,45 @@ namespace SystemTrayMenu
|
|||
{
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using SystemTrayMenu.Utilities;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private static bool isStartup = true;
|
||||
|
||||
[STAThread]
|
||||
private static void Main()
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Initialize();
|
||||
SingleAppInstance.Initialize();
|
||||
Translator.Initialize();
|
||||
Config.SetFolderByWindowsContextMenu(args);
|
||||
Config.LoadOrSetByUser();
|
||||
Config.Initialize();
|
||||
PrivilegeChecker.Initialize();
|
||||
|
||||
Config.UpgradeIfNotUpgraded();
|
||||
if (Config.LoadOrSetByUser())
|
||||
if (SingleAppInstance.Initialize())
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.ThreadException += ThreadException;
|
||||
static void ThreadException(object s, ThreadExceptionEventArgs t)
|
||||
{
|
||||
AskUserSendError(t.Exception);
|
||||
}
|
||||
|
||||
Application.ThreadException += Application_ThreadException;
|
||||
Scaling.Initialize();
|
||||
FolderOptions.Initialize();
|
||||
|
||||
using (new App())
|
||||
{
|
||||
isStartup = false;
|
||||
Log.WriteApplicationRuns();
|
||||
Application.Run();
|
||||
}
|
||||
}
|
||||
|
||||
Application.ThreadException -= Application_ThreadException;
|
||||
Config.Dispose();
|
||||
}
|
||||
#pragma warning disable CA1031 // Do not catch general exception types
|
||||
catch (Exception ex)
|
||||
#pragma warning restore CA1031 // => Represents ThreadException during attached to process
|
||||
{
|
||||
AskUserSendError(ex);
|
||||
}
|
||||
|
@ -52,24 +52,36 @@ namespace SystemTrayMenu
|
|||
{
|
||||
Log.Close();
|
||||
}
|
||||
}
|
||||
|
||||
static void AskUserSendError(Exception ex)
|
||||
private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
|
||||
{
|
||||
AskUserSendError(e.Exception);
|
||||
}
|
||||
|
||||
private static void AskUserSendError(Exception ex)
|
||||
{
|
||||
Log.Error("Application Crashed", ex);
|
||||
|
||||
DialogResult dialogResult = MessageBox.Show(
|
||||
"A problem has been encountered and the application needs to restart. " +
|
||||
"Reporting this error will help us make our product better. " +
|
||||
"Press 'Yes' to open your standard email app (emailto: Markus@Hofknecht.eu). " + Environment.NewLine +
|
||||
@"You can also create an issue manually here https://github.com/Hofknecht/SystemTrayMenu/issues" + Environment.NewLine +
|
||||
"Press 'Cancel' to quit SystemTrayMenu.",
|
||||
"SystemTrayMenu Crashed",
|
||||
MessageBoxButtons.YesNoCancel);
|
||||
|
||||
if (dialogResult == DialogResult.Yes)
|
||||
{
|
||||
Log.Error("Application Crashed", ex);
|
||||
|
||||
if (MessageBox.Show(
|
||||
"A problem has been encountered and the application needs to restart. " +
|
||||
"Reporting this error will help us make our product better. " +
|
||||
"Press yes to open your standard email app.",
|
||||
"SystemTrayMenu BugSplat",
|
||||
MessageBoxButtons.YesNo) == DialogResult.Yes)
|
||||
{
|
||||
Log.ProcessStart("mailto:" + "markus@hofknecht.eu" +
|
||||
"?subject=SystemTrayMenu Bug reported " +
|
||||
Assembly.GetEntryAssembly().GetName().Version +
|
||||
"&body=" + ex.ToString());
|
||||
}
|
||||
Log.ProcessStart("mailto:" + "markus@hofknecht.eu" +
|
||||
"?subject=SystemTrayMenu Bug reported " +
|
||||
Assembly.GetEntryAssembly().GetName().Version +
|
||||
"&body=" + ex.ToString());
|
||||
}
|
||||
|
||||
if (!isStartup && dialogResult != DialogResult.Cancel)
|
||||
{
|
||||
AppRestart.ByThreadException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace SystemTrayMenu.Handler
|
|||
|
||||
internal class WaitLeave : IDisposable
|
||||
{
|
||||
private readonly Timer timerLeaveCheck = new Timer();
|
||||
private readonly Timer timerLeaveCheck = new();
|
||||
|
||||
public WaitLeave(int timeUntilTriggered)
|
||||
{
|
||||
|
@ -18,10 +18,11 @@ namespace SystemTrayMenu.Handler
|
|||
timerLeaveCheck.Tick += TimerLeaveCheckTick;
|
||||
}
|
||||
|
||||
public event EventHandlerEmpty LeaveTriggered;
|
||||
public event Action LeaveTriggered;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
timerLeaveCheck.Tick -= TimerLeaveCheckTick;
|
||||
timerLeaveCheck.Dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,19 +13,19 @@ namespace SystemTrayMenu.Handler
|
|||
|
||||
internal class WaitToLoadMenu : IDisposable
|
||||
{
|
||||
private readonly Timer timerStartLoad = new Timer();
|
||||
private DataGridView dgv = null;
|
||||
private int rowIndex = 0;
|
||||
private DataGridView dgvTmp = null;
|
||||
private int rowIndexTmp = 0;
|
||||
private readonly Timer timerStartLoad = new();
|
||||
private DataGridView dgv;
|
||||
private int rowIndex;
|
||||
private DataGridView dgvTmp;
|
||||
private int rowIndexTmp;
|
||||
|
||||
private int mouseMoveEvents = 0;
|
||||
private int mouseMoveEvents;
|
||||
private DateTime dateTimeLastMouseMoveEvent = DateTime.Now;
|
||||
private bool checkForMouseActive = true;
|
||||
|
||||
internal WaitToLoadMenu()
|
||||
{
|
||||
timerStartLoad.Interval = 200;
|
||||
timerStartLoad.Interval = Properties.Settings.Default.TimeUntilOpens;
|
||||
timerStartLoad.Tick += WaitStartLoad_Tick;
|
||||
}
|
||||
|
||||
|
@ -33,14 +33,15 @@ namespace SystemTrayMenu.Handler
|
|||
|
||||
internal event Action<int> CloseMenu;
|
||||
|
||||
internal event EventHandlerEmpty StopLoadMenu;
|
||||
internal event Action StopLoadMenu;
|
||||
|
||||
internal event Action<DataGridView, int> MouseEnterOk;
|
||||
|
||||
internal bool MouseActive { get; set; } = false;
|
||||
internal bool MouseActive { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
timerStartLoad.Tick -= WaitStartLoad_Tick;
|
||||
timerStartLoad.Stop();
|
||||
timerStartLoad.Dispose();
|
||||
dgv?.Dispose();
|
||||
|
@ -92,7 +93,7 @@ namespace SystemTrayMenu.Handler
|
|||
}
|
||||
}
|
||||
|
||||
internal void RowDeselected(int rowIndex, DataGridView dgv)
|
||||
internal void RowDeselected(DataGridView dgv, int rowIndex)
|
||||
{
|
||||
timerStartLoad.Stop();
|
||||
StopLoadMenu?.Invoke();
|
||||
|
@ -129,7 +130,7 @@ namespace SystemTrayMenu.Handler
|
|||
{
|
||||
if (!MouseActive)
|
||||
{
|
||||
if (mouseMoveEvents > 3)
|
||||
if (mouseMoveEvents > 6)
|
||||
{
|
||||
MouseActive = true;
|
||||
if (dgvTmp != null && !dgvTmp.IsDisposed)
|
||||
|
@ -168,19 +169,17 @@ namespace SystemTrayMenu.Handler
|
|||
{
|
||||
RowData rowData = (RowData)dgv.Rows[rowIndex].Cells[2].Value;
|
||||
Menu menu = (Menu)dgv.FindForm();
|
||||
rowData.MenuLevel = menu.Level;
|
||||
rowData.Level = menu.Level;
|
||||
if (rowData.ContainsMenu)
|
||||
{
|
||||
CloseMenu.Invoke(rowData.MenuLevel + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseMenu.Invoke(rowData.MenuLevel + 1);
|
||||
CloseMenu.Invoke(rowData.Level + 2);
|
||||
}
|
||||
|
||||
CloseMenu.Invoke(rowData.Level + 1);
|
||||
|
||||
if (!rowData.IsContextMenuOpen &&
|
||||
rowData.ContainsMenu &&
|
||||
rowData.MenuLevel + 1 < MenuDefines.MenusMax)
|
||||
rowData.Level + 1 < MenuDefines.MenusMax)
|
||||
{
|
||||
StartLoadMenu.Invoke(rowData);
|
||||
}
|
||||
|
@ -193,7 +192,11 @@ namespace SystemTrayMenu.Handler
|
|||
this.dgv = dgv;
|
||||
this.rowIndex = rowIndex;
|
||||
RowData rowData = (RowData)dgv.Rows[rowIndex].Cells[2].Value;
|
||||
rowData.IsSelected = true;
|
||||
if (rowData != null)
|
||||
{
|
||||
rowData.IsSelected = true;
|
||||
}
|
||||
|
||||
dgv.Rows[rowIndex].Selected = false;
|
||||
dgv.Rows[rowIndex].Selected = true;
|
||||
}
|
||||
|
@ -203,10 +206,14 @@ namespace SystemTrayMenu.Handler
|
|||
if (dgv != null && dgv.Rows.Count > rowIndex)
|
||||
{
|
||||
RowData rowData = (RowData)dgv.Rows[rowIndex].Cells[2].Value;
|
||||
rowData.IsSelected = false;
|
||||
dgv.Rows[rowIndex].Selected = false;
|
||||
this.dgv = null;
|
||||
this.rowIndex = 0;
|
||||
if (rowData != null)
|
||||
{
|
||||
rowData.IsSelected = false;
|
||||
rowData.IsClicking = false;
|
||||
dgv.Rows[rowIndex].Selected = false;
|
||||
this.dgv = null;
|
||||
this.rowIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,21 +8,88 @@ namespace SystemTrayMenu
|
|||
|
||||
internal static class AppColors
|
||||
{
|
||||
internal static readonly Color Blue = Color.FromArgb(204, 232, 255);
|
||||
internal static readonly Color DarkModeBlue = Color.FromArgb(51, 51, 51);
|
||||
internal static readonly Color BlueBorder = Color.FromArgb(153, 209, 255);
|
||||
internal static readonly Color DarkModeBlueBorder = Color.FromArgb(20, 29, 75);
|
||||
internal static readonly Color Green = Color.FromArgb(194, 245, 222);
|
||||
internal static readonly Color DarkModeGreen = Color.FromArgb(20, 65, 42);
|
||||
internal static readonly Color GreenBorder = Color.FromArgb(153, 255, 165);
|
||||
internal static readonly Color DarkModeGreenBorder = Color.FromArgb(20, 75, 85);
|
||||
internal static readonly Color Red = Color.FromArgb(255, 204, 232);
|
||||
internal static readonly Color DarkModeRed = Color.FromArgb(75, 24, 52);
|
||||
internal static readonly Color Azure = Color.Azure;
|
||||
internal static readonly Color DarkModeAzure = DarkModeBackColor1;
|
||||
public static Color Arrow { get; internal set; }
|
||||
|
||||
internal static readonly Color DarkModeBackColor3 = Color.FromArgb(25, 25, 25);
|
||||
internal static readonly Color DarkModeBackColor2 = Color.FromArgb(32, 32, 32);
|
||||
internal static readonly Color DarkModeBackColor1 = Color.FromArgb(43, 43, 43);
|
||||
public static Color ArrowHoverBackground { get; internal set; }
|
||||
|
||||
public static Color ArrowHover { get; internal set; }
|
||||
|
||||
public static Color ArrowClick { get; internal set; }
|
||||
|
||||
public static Color ArrowClickBackground { get; internal set; }
|
||||
|
||||
public static Color SliderArrowsAndTrackHover { get; internal set; }
|
||||
|
||||
public static Color Slider { get; internal set; }
|
||||
|
||||
public static Color SliderHover { get; internal set; }
|
||||
|
||||
public static Color SliderDragging { get; internal set; }
|
||||
|
||||
public static Color ScrollbarBackground { get; internal set; }
|
||||
|
||||
public static Color ArrowDarkMode { get; internal set; }
|
||||
|
||||
public static Color ArrowHoverBackgroundDarkMode { get; internal set; }
|
||||
|
||||
public static Color ArrowHoverDarkMode { get; internal set; }
|
||||
|
||||
public static Color ArrowClickDarkMode { get; internal set; }
|
||||
|
||||
public static Color ArrowClickBackgroundDarkMode { get; internal set; }
|
||||
|
||||
public static Color SliderArrowsAndTrackHoverDarkMode { get; internal set; }
|
||||
|
||||
public static Color SliderDarkMode { get; internal set; }
|
||||
|
||||
public static Color SliderHoverDarkMode { get; internal set; }
|
||||
|
||||
public static Color SliderDraggingDarkMode { get; internal set; }
|
||||
|
||||
public static Color ScrollbarBackgroundDarkMode { get; internal set; }
|
||||
|
||||
public static Color SelectedItem { get; set; }
|
||||
|
||||
public static Color DarkModeSelecetedItem { get; set; }
|
||||
|
||||
public static Color SelectedItemBorder { get; set; }
|
||||
|
||||
public static Color DarkModeSelectedItemBorder { get; set; }
|
||||
|
||||
public static Color OpenFolder { get; set; }
|
||||
|
||||
public static Color DarkModeOpenFolder { get; set; }
|
||||
|
||||
public static Color OpenFolderBorder { get; set; }
|
||||
|
||||
public static Color DarkModeOpenFolderBorder { get; set; }
|
||||
|
||||
public static Color Background { get; set; }
|
||||
|
||||
public static Color DarkModeBackground { get; set; }
|
||||
|
||||
public static Color BackgroundBorder { get; set; }
|
||||
|
||||
public static Color DarkModeBackgroundBorder { get; set; }
|
||||
|
||||
public static Color SearchField { get; set; }
|
||||
|
||||
public static Color DarkModeSearchField { get; set; }
|
||||
|
||||
public static Bitmap BitmapOpenFolder { get; set; }
|
||||
|
||||
public static Bitmap BitmapPin { get; set; }
|
||||
|
||||
public static Bitmap BitmapSettings { get; set; }
|
||||
|
||||
public static Bitmap BitmapRestart { get; set; }
|
||||
|
||||
public static Bitmap BitmapPinActive { get; set; }
|
||||
|
||||
public static Bitmap BitmapSearch { get; set; }
|
||||
|
||||
public static Color Icons { get; set; }
|
||||
|
||||
public static Color DarkModeIcons { get; set; }
|
||||
}
|
||||
}
|
15
Config/ColorAndCode.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
// <copyright file="ColorAndCode.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace SystemTrayMenu
|
||||
{
|
||||
using System.Drawing;
|
||||
|
||||
internal struct ColorAndCode
|
||||
{
|
||||
public Color Color { get; set; }
|
||||
|
||||
public string HtmlColorCode { get; set; }
|
||||
}
|
||||
}
|
541
Config/Config.cs
|
@ -4,113 +4,164 @@
|
|||
|
||||
namespace SystemTrayMenu
|
||||
{
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32;
|
||||
using Svg;
|
||||
using SystemTrayMenu.Properties;
|
||||
using SystemTrayMenu.UserInterface.FolderBrowseDialog;
|
||||
using SystemTrayMenu.Utilities;
|
||||
using static SystemTrayMenu.Utilities.IconReader;
|
||||
|
||||
public static class Config
|
||||
{
|
||||
public const string Language = "en";
|
||||
private static readonly Icon SystemTrayMenu = new Icon(Properties.Resources.SystemTrayMenu, SystemInformation.SmallIconSize);
|
||||
private static readonly Icon IconRootFolder = GetIconSTA(Path, Path, false, IconSize.Small, true);
|
||||
|
||||
private static bool readDarkModeDone = false;
|
||||
private static bool isDarkModeFromFirstCall = false;
|
||||
private static bool readDarkModeDone;
|
||||
private static bool isDarkMode;
|
||||
private static bool readHideFileExtdone;
|
||||
private static bool isHideFileExtension;
|
||||
|
||||
public static string Path => Properties.Settings.Default.PathDirectory;
|
||||
public static string Path => Settings.Default.PathDirectory;
|
||||
|
||||
public static void UpgradeIfNotUpgraded()
|
||||
public static string SearchPattern => Settings.Default.SearchPattern;
|
||||
|
||||
public static bool ShowDirectoryTitleAtTop => Settings.Default.ShowDirectoryTitleAtTop;
|
||||
|
||||
public static bool ShowSearchBar => Settings.Default.ShowSearchBar;
|
||||
|
||||
public static bool ShowCountOfElementsBelow => Settings.Default.ShowCountOfElementsBelow;
|
||||
|
||||
public static bool ShowFunctionKeyOpenFolder => Settings.Default.ShowFunctionKeyOpenFolder;
|
||||
|
||||
public static bool ShowFunctionKeyPinMenu => Settings.Default.ShowFunctionKeyPinMenu;
|
||||
|
||||
public static bool ShowFunctionKeySettings => Settings.Default.ShowFunctionKeySettings;
|
||||
|
||||
public static bool ShowFunctionKeyRestart => Settings.Default.ShowFunctionKeyRestart;
|
||||
|
||||
public static bool AlwaysOpenByPin { get; internal set; }
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
if (!Properties.Settings.Default.IsUpgraded)
|
||||
UpgradeIfNotUpgraded();
|
||||
InitializeColors();
|
||||
if (string.IsNullOrEmpty(Settings.Default.PathIcoDirectory))
|
||||
{
|
||||
// configs located at "%localappdata%\<AssemblyCompany>\"
|
||||
Properties.Settings.Default.Upgrade();
|
||||
|
||||
Properties.Settings.Default.IsUpgraded = true;
|
||||
Properties.Settings.Default.Save();
|
||||
|
||||
FileVersionInfo versionInfo = FileVersionInfo.
|
||||
GetVersionInfo(Assembly.GetEntryAssembly().Location);
|
||||
Log.Info($"Settings upgraded from " +
|
||||
$"%localappdata%\\{versionInfo.CompanyName}\\");
|
||||
Settings.Default.PathIcoDirectory = System.IO.Path.Combine(
|
||||
System.IO.Path.Combine(
|
||||
Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.ApplicationData), $"SystemTrayMenu"), "ico");
|
||||
if (!Directory.Exists(Settings.Default.PathIcoDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(Settings.Default.PathIcoDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool LoadOrSetByUser()
|
||||
public static void Dispose()
|
||||
{
|
||||
bool pathOK = Directory.Exists(Path);
|
||||
AppColors.BitmapOpenFolder.Dispose();
|
||||
AppColors.BitmapPin.Dispose();
|
||||
AppColors.BitmapPinActive.Dispose();
|
||||
AppColors.BitmapSettings.Dispose();
|
||||
AppColors.BitmapRestart.Dispose();
|
||||
AppColors.BitmapSearch.Dispose();
|
||||
}
|
||||
|
||||
if (!pathOK)
|
||||
public static Icon GetAppIcon()
|
||||
{
|
||||
if (Settings.Default.UseIconFromRootFolder)
|
||||
{
|
||||
string textFirstStart = Translator.GetText("TextFirstStart");
|
||||
return IconRootFolder;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SystemTrayMenu;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetFolderByWindowsContextMenu(string[] args)
|
||||
{
|
||||
if (args != null && args.Length > 0 && args[0] != "-r")
|
||||
{
|
||||
string path = args[0];
|
||||
Log.Info($"SetFolderByWindowsContextMenu() path: {path}");
|
||||
Settings.Default.PathDirectory = path;
|
||||
Settings.Default.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadOrSetByUser()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path))
|
||||
{
|
||||
string textFirstStart = Translator.GetText(
|
||||
"Read the FAQ and then choose a root directory for SystemTrayMenu.");
|
||||
MessageBox.Show(
|
||||
textFirstStart,
|
||||
Translator.GetText("SystemTrayMenu"),
|
||||
"SystemTrayMenu",
|
||||
MessageBoxButtons.OK);
|
||||
ShowHelpFAQ();
|
||||
pathOK = SetFolderByUser();
|
||||
SetFolderByUser();
|
||||
}
|
||||
|
||||
return pathOK;
|
||||
}
|
||||
|
||||
public static bool SetFolderByUser(bool save = true)
|
||||
public static void SetFolderByUser(bool save = true)
|
||||
{
|
||||
bool pathOK = false;
|
||||
bool userAborted = false;
|
||||
using (FolderDialog dialog = new FolderDialog())
|
||||
using FolderDialog dialog = new();
|
||||
dialog.InitialFolder = Path;
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
dialog.InitialFolder = Path;
|
||||
|
||||
do
|
||||
Settings.Default.PathDirectory = dialog.Folder;
|
||||
if (save)
|
||||
{
|
||||
if (dialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
if (Directory.Exists(dialog.Folder))
|
||||
{
|
||||
pathOK = true;
|
||||
Properties.Settings.Default.PathDirectory =
|
||||
dialog.Folder;
|
||||
if (save)
|
||||
{
|
||||
Properties.Settings.Default.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
userAborted = true;
|
||||
}
|
||||
Settings.Default.Save();
|
||||
}
|
||||
while (!pathOK && !userAborted);
|
||||
}
|
||||
}
|
||||
|
||||
return pathOK;
|
||||
public static void SetFolderIcoByUser()
|
||||
{
|
||||
using FolderDialog dialog = new();
|
||||
dialog.InitialFolder = Settings.Default.PathIcoDirectory;
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
Settings.Default.PathIcoDirectory = dialog.Folder;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ShowHelpFAQ()
|
||||
{
|
||||
string browserPath = FileUrl.GetDefaultBrowserPath();
|
||||
if (!string.IsNullOrEmpty(browserPath))
|
||||
{
|
||||
Process.Start(browserPath, "https://github.com/Hofknecht/SystemTrayMenu#FAQ");
|
||||
}
|
||||
Log.ProcessStart("https://github.com/Hofknecht/SystemTrayMenu#FAQ");
|
||||
}
|
||||
|
||||
internal static void ShowSupportSystemTrayMenu()
|
||||
{
|
||||
Log.ProcessStart("https://github.com/Hofknecht/SystemTrayMenu#donations");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the OS setting whether dark mode is enabled.
|
||||
/// </summary>
|
||||
/// <returns>true = Dark mode; false = Light mode.</returns>
|
||||
internal static bool IsDarkMode()
|
||||
{
|
||||
bool isDarkMode = false;
|
||||
if (readDarkModeDone)
|
||||
if (!readDarkModeDone)
|
||||
{
|
||||
isDarkMode = isDarkModeFromFirstCall;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Properties.Settings.Default.IsDarkModeAlwaysOn || IsDarkModeActive())
|
||||
// 0 = Dark mode, 1 = Light mode
|
||||
if (Settings.Default.IsDarkModeAlwaysOn ||
|
||||
IsRegistryValueThisValue(
|
||||
@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
|
||||
"AppsUseLightTheme",
|
||||
"0"))
|
||||
{
|
||||
isDarkModeFromFirstCall = true;
|
||||
isDarkMode = true;
|
||||
}
|
||||
|
||||
|
@ -120,15 +171,359 @@ namespace SystemTrayMenu
|
|||
return isDarkMode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the OS setting whether dark mode is enabled.
|
||||
/// </summary>
|
||||
/// <returns>true = Dark mode; false = Light mode.</returns>
|
||||
private static bool IsDarkModeActive()
|
||||
internal static void ResetReadDarkModeDone()
|
||||
{
|
||||
// Check: AppsUseLightTheme (REG_DWORD)
|
||||
// 0 = Dark mode, 1 = Light mode
|
||||
return Registry.GetValue("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", "AppsUseLightTheme", 1).ToString() == "0";
|
||||
isDarkMode = false;
|
||||
readDarkModeDone = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the OS setting whether HideFileExt enabled.
|
||||
/// </summary>
|
||||
/// <returns>isHideFileExtension.</returns>
|
||||
internal static bool IsHideFileExtension()
|
||||
{
|
||||
if (!readHideFileExtdone)
|
||||
{
|
||||
// 0 = To show extensions, 1 = To hide extensions
|
||||
if (IsRegistryValueThisValue(
|
||||
@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced",
|
||||
"HideFileExt",
|
||||
"1"))
|
||||
{
|
||||
isHideFileExtension = true;
|
||||
}
|
||||
|
||||
readHideFileExtdone = true;
|
||||
}
|
||||
|
||||
return isHideFileExtension;
|
||||
}
|
||||
|
||||
internal static void InitializeColors(bool save = true)
|
||||
{
|
||||
ColorConverter converter = new();
|
||||
ColorAndCode colorAndCode = default;
|
||||
bool changed = false;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorSelectedItem;
|
||||
colorAndCode.Color = Color.FromArgb(204, 232, 255);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorSelectedItem = colorAndCode.HtmlColorCode;
|
||||
AppColors.SelectedItem = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorDarkModeSelecetedItem;
|
||||
colorAndCode.Color = Color.FromArgb(51, 51, 51);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorDarkModeSelecetedItem = colorAndCode.HtmlColorCode;
|
||||
AppColors.DarkModeSelecetedItem = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorSelectedItemBorder;
|
||||
colorAndCode.Color = Color.FromArgb(153, 209, 255);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorSelectedItemBorder = colorAndCode.HtmlColorCode;
|
||||
AppColors.SelectedItemBorder = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorDarkModeSelectedItemBorder;
|
||||
colorAndCode.Color = Color.FromArgb(20, 29, 75);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorDarkModeSelectedItemBorder = colorAndCode.HtmlColorCode;
|
||||
AppColors.DarkModeSelectedItemBorder = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorOpenFolder;
|
||||
colorAndCode.Color = Color.FromArgb(194, 245, 222);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorOpenFolder = colorAndCode.HtmlColorCode;
|
||||
AppColors.OpenFolder = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorDarkModeOpenFolder;
|
||||
colorAndCode.Color = Color.FromArgb(20, 65, 42);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorDarkModeOpenFolder = colorAndCode.HtmlColorCode;
|
||||
AppColors.DarkModeOpenFolder = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorOpenFolderBorder;
|
||||
colorAndCode.Color = Color.FromArgb(153, 255, 165);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorOpenFolderBorder = colorAndCode.HtmlColorCode;
|
||||
AppColors.OpenFolderBorder = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorDarkModeOpenFolderBorder;
|
||||
colorAndCode.Color = Color.FromArgb(20, 75, 85);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorDarkModeOpenFolderBorder = colorAndCode.HtmlColorCode;
|
||||
AppColors.DarkModeOpenFolderBorder = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorIcons;
|
||||
colorAndCode.Color = Color.FromArgb(149, 160, 166);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorIcons = colorAndCode.HtmlColorCode;
|
||||
AppColors.Icons = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorDarkModeIcons;
|
||||
colorAndCode.Color = Color.FromArgb(149, 160, 166);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorDarkModeIcons = colorAndCode.HtmlColorCode;
|
||||
AppColors.DarkModeIcons = colorAndCode.Color;
|
||||
|
||||
string htmlColorCodeIcons;
|
||||
if (IsDarkMode())
|
||||
{
|
||||
htmlColorCodeIcons = Settings.Default.ColorDarkModeIcons;
|
||||
}
|
||||
else
|
||||
{
|
||||
htmlColorCodeIcons = Settings.Default.ColorIcons;
|
||||
}
|
||||
|
||||
AppColors.BitmapOpenFolder =
|
||||
ReadSvg(Properties.Resources.ic_fluent_folder_arrow_right_48_regular, htmlColorCodeIcons);
|
||||
AppColors.BitmapPin =
|
||||
ReadSvg(Properties.Resources.ic_fluent_pin_48_regular, htmlColorCodeIcons);
|
||||
AppColors.BitmapSettings =
|
||||
ReadSvg(Properties.Resources.ic_fluent_settings_28_regular, htmlColorCodeIcons);
|
||||
AppColors.BitmapRestart =
|
||||
ReadSvg(Properties.Resources.ic_fluent_arrow_sync_24_regular, htmlColorCodeIcons);
|
||||
AppColors.BitmapPinActive =
|
||||
ReadSvg(Properties.Resources.ic_fluent_pin_48_filled, htmlColorCodeIcons);
|
||||
AppColors.BitmapSearch =
|
||||
ReadSvg(Properties.Resources.ic_fluent_search_48_regular, htmlColorCodeIcons);
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorSearchField;
|
||||
colorAndCode.Color = Color.FromArgb(255, 255, 255);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorSearchField = colorAndCode.HtmlColorCode;
|
||||
AppColors.SearchField = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorDarkModeSearchField;
|
||||
colorAndCode.Color = Color.FromArgb(25, 25, 25);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorDarkModeSearchField = colorAndCode.HtmlColorCode;
|
||||
AppColors.DarkModeSearchField = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorBackground;
|
||||
colorAndCode.Color = Color.FromArgb(255, 255, 255);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorBackground = colorAndCode.HtmlColorCode;
|
||||
AppColors.Background = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorDarkModeBackground;
|
||||
colorAndCode.Color = Color.FromArgb(32, 32, 32);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorDarkModeBackground = colorAndCode.HtmlColorCode;
|
||||
AppColors.DarkModeBackground = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorBackgroundBorder;
|
||||
colorAndCode.Color = Color.FromArgb(0, 0, 0);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorBackgroundBorder = colorAndCode.HtmlColorCode;
|
||||
AppColors.BackgroundBorder = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorDarkModeBackgroundBorder;
|
||||
colorAndCode.Color = Color.FromArgb(0, 0, 0);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorDarkModeBackgroundBorder = colorAndCode.HtmlColorCode;
|
||||
AppColors.DarkModeBackgroundBorder = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorArrow;
|
||||
colorAndCode.Color = Color.FromArgb(96, 96, 96);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorArrow = colorAndCode.HtmlColorCode;
|
||||
AppColors.Arrow = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorArrowHoverBackground;
|
||||
colorAndCode.Color = Color.FromArgb(218, 218, 218);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorArrowHoverBackground = colorAndCode.HtmlColorCode;
|
||||
AppColors.ArrowHoverBackground = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorArrowHover;
|
||||
colorAndCode.Color = Color.FromArgb(0, 0, 0);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorArrowHover = colorAndCode.HtmlColorCode;
|
||||
AppColors.ArrowHover = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorArrowClick;
|
||||
colorAndCode.Color = Color.FromArgb(255, 255, 255);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorArrowClick = colorAndCode.HtmlColorCode;
|
||||
AppColors.ArrowClick = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorArrowClickBackground;
|
||||
colorAndCode.Color = Color.FromArgb(96, 96, 96);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorArrowClickBackground = colorAndCode.HtmlColorCode;
|
||||
AppColors.ArrowClickBackground = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorSliderArrowsAndTrackHover;
|
||||
colorAndCode.Color = Color.FromArgb(192, 192, 192);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorSliderArrowsAndTrackHover = colorAndCode.HtmlColorCode;
|
||||
AppColors.SliderArrowsAndTrackHover = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorSlider;
|
||||
colorAndCode.Color = Color.FromArgb(205, 205, 205);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorSlider = colorAndCode.HtmlColorCode;
|
||||
AppColors.Slider = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorSliderHover;
|
||||
colorAndCode.Color = Color.FromArgb(166, 166, 166);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorSliderHover = colorAndCode.HtmlColorCode;
|
||||
AppColors.SliderHover = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorSliderDragging;
|
||||
colorAndCode.Color = Color.FromArgb(96, 96, 96);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorSliderDragging = colorAndCode.HtmlColorCode;
|
||||
AppColors.SliderDragging = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorScrollbarBackground;
|
||||
colorAndCode.Color = Color.FromArgb(240, 240, 240);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorScrollbarBackground = colorAndCode.HtmlColorCode;
|
||||
AppColors.ScrollbarBackground = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorArrowDarkMode;
|
||||
colorAndCode.Color = Color.FromArgb(103, 103, 103);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorArrowDarkMode = colorAndCode.HtmlColorCode;
|
||||
AppColors.ArrowDarkMode = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorArrowHoverBackgroundDarkMode;
|
||||
colorAndCode.Color = Color.FromArgb(55, 55, 55);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorArrowHoverBackgroundDarkMode = colorAndCode.HtmlColorCode;
|
||||
AppColors.ArrowHoverBackgroundDarkMode = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorArrowHoverDarkMode;
|
||||
colorAndCode.Color = Color.FromArgb(103, 103, 103);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorArrowHoverDarkMode = colorAndCode.HtmlColorCode;
|
||||
AppColors.ArrowHoverDarkMode = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorArrowClickDarkMode;
|
||||
colorAndCode.Color = Color.FromArgb(23, 23, 23);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorArrowClickDarkMode = colorAndCode.HtmlColorCode;
|
||||
AppColors.ArrowClickDarkMode = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorArrowClickBackgroundDarkMode;
|
||||
colorAndCode.Color = Color.FromArgb(166, 166, 166);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorArrowClickBackgroundDarkMode = colorAndCode.HtmlColorCode;
|
||||
AppColors.ArrowClickBackgroundDarkMode = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorSliderArrowsAndTrackHoverDarkMode;
|
||||
colorAndCode.Color = Color.FromArgb(77, 77, 77);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorSliderArrowsAndTrackHoverDarkMode = colorAndCode.HtmlColorCode;
|
||||
AppColors.SliderArrowsAndTrackHoverDarkMode = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorSliderDarkMode;
|
||||
colorAndCode.Color = Color.FromArgb(77, 77, 77);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorSliderDarkMode = colorAndCode.HtmlColorCode;
|
||||
AppColors.SliderDarkMode = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorSliderHoverDarkMode;
|
||||
colorAndCode.Color = Color.FromArgb(122, 122, 122);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorSliderHoverDarkMode = colorAndCode.HtmlColorCode;
|
||||
AppColors.SliderHoverDarkMode = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorSliderDraggingDarkMode;
|
||||
colorAndCode.Color = Color.FromArgb(166, 166, 166);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorSliderDraggingDarkMode = colorAndCode.HtmlColorCode;
|
||||
AppColors.SliderDraggingDarkMode = colorAndCode.Color;
|
||||
|
||||
colorAndCode.HtmlColorCode = Settings.Default.ColorScrollbarBackgroundDarkMode;
|
||||
colorAndCode.Color = Color.FromArgb(23, 23, 23);
|
||||
colorAndCode = ProcessColorAndCode(converter, colorAndCode, ref changed);
|
||||
Settings.Default.ColorScrollbarBackgroundDarkMode = colorAndCode.HtmlColorCode;
|
||||
AppColors.ScrollbarBackgroundDarkMode = colorAndCode.Color;
|
||||
|
||||
if (save && changed)
|
||||
{
|
||||
Settings.Default.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap ReadSvg(byte[] byteArray, string htmlColorCode)
|
||||
{
|
||||
string str = Encoding.UTF8.GetString(byteArray);
|
||||
str = str.Replace("#585858", htmlColorCode);
|
||||
byteArray = Encoding.UTF8.GetBytes(str);
|
||||
|
||||
using MemoryStream stream = new(byteArray);
|
||||
SvgDocument svgDocument = SvgDocument.Open<SvgDocument>(stream);
|
||||
svgDocument.Color = new SvgColourServer(Color.Black);
|
||||
return svgDocument.Draw();
|
||||
}
|
||||
|
||||
private static bool IsRegistryValueThisValue(string keyName, string valueName, string value)
|
||||
{
|
||||
bool isRegistryValueThisValue = false;
|
||||
|
||||
try
|
||||
{
|
||||
object registryHideFileExt = Registry.GetValue(keyName, valueName, 1);
|
||||
|
||||
if (registryHideFileExt == null)
|
||||
{
|
||||
Log.Info($"Could not read registry keyName:{keyName} valueName:{valueName}");
|
||||
}
|
||||
else if (registryHideFileExt.ToString() == value)
|
||||
{
|
||||
isRegistryValueThisValue = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is System.Security.SecurityException ||
|
||||
ex is IOException)
|
||||
{
|
||||
Log.Warn($"Could not read registry keyName:{keyName} valueName:{valueName}", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return isRegistryValueThisValue;
|
||||
}
|
||||
|
||||
private static void UpgradeIfNotUpgraded()
|
||||
{
|
||||
if (!Settings.Default.IsUpgraded)
|
||||
{
|
||||
Settings.Default.Upgrade();
|
||||
Settings.Default.IsUpgraded = true;
|
||||
Settings.Default.Save();
|
||||
Log.Info($"Settings upgraded from {CustomSettingsProvider.UserConfigPath}");
|
||||
}
|
||||
}
|
||||
|
||||
private static ColorAndCode ProcessColorAndCode(
|
||||
ColorConverter colorConverter,
|
||||
ColorAndCode colorAndCode,
|
||||
ref bool changedHtmlColorCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
colorAndCode.Color = (Color)colorConverter.ConvertFromString(colorAndCode.HtmlColorCode);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Log.Warn($"HtmlColorCode {colorAndCode.HtmlColorCode}", ex);
|
||||
colorAndCode.HtmlColorCode = ColorTranslator.ToHtml(colorAndCode.Color);
|
||||
changedHtmlColorCode = true;
|
||||
}
|
||||
|
||||
return colorAndCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,13 +8,9 @@ namespace SystemTrayMenu
|
|||
|
||||
internal static class MenuDefines
|
||||
{
|
||||
internal const int Scrollspeed = 4;
|
||||
internal const int TimeUntilClose = 1000;
|
||||
internal const int MenusMax = 50;
|
||||
internal const int LengthMax = 37;
|
||||
internal const float MaxMenuWidth = 300;
|
||||
internal static readonly Color File = Color.White;
|
||||
internal static readonly Color Folder = Color.White;
|
||||
internal const int Scrollspeed = 3;
|
||||
|
||||
public static Color ColorSelectedItem
|
||||
{
|
||||
|
@ -22,11 +18,11 @@ namespace SystemTrayMenu
|
|||
{
|
||||
if (Config.IsDarkMode())
|
||||
{
|
||||
return AppColors.DarkModeBlue;
|
||||
return AppColors.DarkModeSelecetedItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AppColors.Blue;
|
||||
return AppColors.SelectedItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,11 +33,11 @@ namespace SystemTrayMenu
|
|||
{
|
||||
if (Config.IsDarkMode())
|
||||
{
|
||||
return AppColors.DarkModeBlueBorder;
|
||||
return AppColors.DarkModeSelectedItemBorder;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AppColors.BlueBorder;
|
||||
return AppColors.SelectedItemBorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,11 +48,11 @@ namespace SystemTrayMenu
|
|||
{
|
||||
if (Config.IsDarkMode())
|
||||
{
|
||||
return AppColors.DarkModeGreen;
|
||||
return AppColors.DarkModeOpenFolder;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AppColors.Green;
|
||||
return AppColors.OpenFolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,56 +63,26 @@ namespace SystemTrayMenu
|
|||
{
|
||||
if (Config.IsDarkMode())
|
||||
{
|
||||
return AppColors.DarkModeGreenBorder;
|
||||
return AppColors.DarkModeOpenFolderBorder;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AppColors.GreenBorder;
|
||||
return AppColors.OpenFolderBorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Color ColorTitleWarning
|
||||
public static Color ColorIcons
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Config.IsDarkMode())
|
||||
{
|
||||
return AppColors.DarkModeRed;
|
||||
return AppColors.DarkModeIcons;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AppColors.Red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Color ColorTitleSelected
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Config.IsDarkMode())
|
||||
{
|
||||
return AppColors.DarkModeBlue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AppColors.Blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Color ColorTitleBackground
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Config.IsDarkMode())
|
||||
{
|
||||
return AppColors.DarkModeAzure;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AppColors.Azure;
|
||||
return AppColors.Icons;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace SystemTrayMenu.DataClasses
|
|||
|
||||
internal enum MenuDataValidity
|
||||
{
|
||||
AbortedOrUnknown,
|
||||
Undefined,
|
||||
Valid,
|
||||
Empty,
|
||||
NoAccess,
|
||||
|
@ -16,9 +16,23 @@ namespace SystemTrayMenu.DataClasses
|
|||
|
||||
internal struct MenuData
|
||||
{
|
||||
internal List<RowData> RowDatas;
|
||||
internal MenuDataValidity Validity;
|
||||
internal int Level;
|
||||
internal RowData RowDataParent;
|
||||
public MenuData(int level)
|
||||
{
|
||||
RowDatas = new List<RowData>();
|
||||
Validity = MenuDataValidity.Undefined;
|
||||
Level = level;
|
||||
RowDataParent = null;
|
||||
IsNetworkRoot = false;
|
||||
}
|
||||
|
||||
internal List<RowData> RowDatas { get; set; }
|
||||
|
||||
internal MenuDataValidity Validity { get; set; }
|
||||
|
||||
internal int Level { get; }
|
||||
|
||||
internal RowData RowDataParent { get; set; }
|
||||
|
||||
internal bool IsNetworkRoot { get; set; }
|
||||
}
|
||||
}
|
|
@ -5,170 +5,198 @@
|
|||
namespace SystemTrayMenu.DataClasses
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using IWshRuntimeLibrary;
|
||||
using SystemTrayMenu.Utilities;
|
||||
using TAFactory.IconPack;
|
||||
using static SystemTrayMenu.Utilities.IconReader;
|
||||
using Menu = SystemTrayMenu.UserInterface.Menu;
|
||||
|
||||
internal class RowData : IDisposable
|
||||
internal class RowData
|
||||
{
|
||||
private static DateTime contextMenuClosed;
|
||||
private string workingDirectory;
|
||||
private string arguments;
|
||||
private string text;
|
||||
private Icon icon = null;
|
||||
private bool diposeIcon = true;
|
||||
private bool isDisposed = false;
|
||||
private Icon icon;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RowData"/> class.
|
||||
/// empty dummy.
|
||||
/// </summary>
|
||||
internal RowData()
|
||||
{
|
||||
}
|
||||
|
||||
internal FileInfo FileInfo { get; set; }
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RowData"/> class.
|
||||
/// (Related replace "\x00" see #171.)
|
||||
/// </summary>
|
||||
/// <param name="isFolder">Flag if file or folder.</param>
|
||||
/// <param name="isAddionalItem">Flag if addional item, from other folder than root folder.</param>
|
||||
/// <param name="isNetworkRoot">Flag if resolved from network root folder.</param>
|
||||
/// <param name="level">The number of the menu level.</param>
|
||||
/// <param name="path">Path to item.</param>
|
||||
internal RowData(bool isFolder, bool isAddionalItem, bool isNetworkRoot, int level, string path)
|
||||
{
|
||||
IsFolder = isFolder;
|
||||
IsAddionalItem = isAddionalItem;
|
||||
IsNetworkRoot = isNetworkRoot;
|
||||
Level = level;
|
||||
|
||||
try
|
||||
{
|
||||
FileInfo = new FileInfo(path.Replace("\x00", string.Empty));
|
||||
Path = IsFolder ? $@"{FileInfo.FullName}\" : FileInfo.FullName;
|
||||
FileExtension = System.IO.Path.GetExtension(Path);
|
||||
IsLink = FileExtension.Equals(".lnk", StringComparison.InvariantCultureIgnoreCase);
|
||||
if (IsLink)
|
||||
{
|
||||
ResolvedPath = FileLnk.GetResolvedFileName(Path, out bool isLinkToFolder);
|
||||
IsLinkToFolder = isLinkToFolder || FileLnk.IsNetworkRoot(ResolvedPath);
|
||||
ShowOverlay = Properties.Settings.Default.ShowLinkOverlay;
|
||||
Text = System.IO.Path.GetFileNameWithoutExtension(Path);
|
||||
if (string.IsNullOrEmpty(ResolvedPath))
|
||||
{
|
||||
Log.Info($"Resolved path is empty: '{Path}'");
|
||||
ResolvedPath = Path;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ResolvedPath = Path;
|
||||
if (string.IsNullOrEmpty(FileInfo.Name))
|
||||
{
|
||||
int nameBegin = FileInfo.FullName.LastIndexOf(@"\", StringComparison.InvariantCulture) + 1;
|
||||
Text = FileInfo.FullName[nameBegin..];
|
||||
}
|
||||
else if (FileExtension.Equals(".url", StringComparison.InvariantCultureIgnoreCase) ||
|
||||
FileExtension.Equals(".appref-ms", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
ShowOverlay = Properties.Settings.Default.ShowLinkOverlay;
|
||||
Text = System.IO.Path.GetFileNameWithoutExtension(FileInfo.Name);
|
||||
}
|
||||
else if (!IsFolder && Config.IsHideFileExtension())
|
||||
{
|
||||
Text = System.IO.Path.GetFileNameWithoutExtension(FileInfo.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
Text = FileInfo.Name;
|
||||
}
|
||||
}
|
||||
|
||||
ContainsMenu = IsFolder;
|
||||
if (Properties.Settings.Default.ResolveLinksToFolders)
|
||||
{
|
||||
ContainsMenu |= IsLinkToFolder;
|
||||
}
|
||||
|
||||
IsMainMenu = Level == 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn($"path:'{path}'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
internal FileInfo FileInfo { get; }
|
||||
|
||||
internal string Path { get; }
|
||||
|
||||
internal bool IsFolder { get; }
|
||||
|
||||
internal bool IsAddionalItem { get; }
|
||||
|
||||
internal bool IsNetworkRoot { get; }
|
||||
|
||||
internal int Level { get; set; }
|
||||
|
||||
internal string FileExtension { get; }
|
||||
|
||||
internal bool IsLink { get; }
|
||||
|
||||
internal string ResolvedPath { get; }
|
||||
|
||||
internal bool IsLinkToFolder { get; }
|
||||
|
||||
internal bool ShowOverlay { get; }
|
||||
|
||||
internal string Text { get; }
|
||||
|
||||
internal bool ContainsMenu { get; }
|
||||
|
||||
internal bool IsMainMenu { get; }
|
||||
|
||||
internal Menu SubMenu { get; set; }
|
||||
|
||||
internal bool IsMenuOpen { get; set; }
|
||||
|
||||
internal bool IsSelected { get; set; }
|
||||
internal bool IsClicking { get; set; }
|
||||
|
||||
internal bool ContainsMenu { get; set; }
|
||||
internal bool IsSelected { get; set; }
|
||||
|
||||
internal bool IsContextMenuOpen { get; set; }
|
||||
|
||||
internal bool IsResolvedLnk { get; set; }
|
||||
|
||||
internal bool HiddenEntry { get; set; }
|
||||
|
||||
internal string TargetFilePath { get; set; }
|
||||
|
||||
internal string TargetFilePathOrig { get; set; }
|
||||
|
||||
internal int RowIndex { get; set; }
|
||||
|
||||
internal int MenuLevel { get; set; }
|
||||
internal bool IconLoading { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
internal void SetText(string text)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
internal bool ProcessStarted { get; set; }
|
||||
|
||||
internal void SetData(RowData data, DataTable dataTable)
|
||||
{
|
||||
DataRow row = dataTable.Rows.Add();
|
||||
data.RowIndex = dataTable.Rows.IndexOf(row);
|
||||
|
||||
if (icon == null)
|
||||
{
|
||||
icon = Properties.Resources.WhiteTransparency;
|
||||
}
|
||||
|
||||
if (HiddenEntry)
|
||||
{
|
||||
row[0] = IconReader.AddIconOverlay(
|
||||
data.icon,
|
||||
Properties.Resources.WhiteTransparency);
|
||||
row[0] = AddIconOverlay(data.icon, Properties.Resources.White50Percentage);
|
||||
}
|
||||
else
|
||||
{
|
||||
row[0] = data.icon;
|
||||
}
|
||||
|
||||
row[1] = data.text;
|
||||
row[1] = data.Text;
|
||||
row[2] = data;
|
||||
}
|
||||
|
||||
internal bool ReadIcon(bool isDirectory, ref string resolvedLnkPath)
|
||||
internal Icon ReadIcon(bool updateIconInBackground)
|
||||
{
|
||||
bool isLnkDirectory = false;
|
||||
|
||||
if (string.IsNullOrEmpty(TargetFilePath))
|
||||
if (IsFolder || IsLinkToFolder)
|
||||
{
|
||||
Log.Info($"TargetFilePath from {resolvedLnkPath} empty");
|
||||
}
|
||||
else if (isDirectory)
|
||||
{
|
||||
icon = IconReader.GetFolderIconSTA(
|
||||
TargetFilePath,
|
||||
IconReader.FolderType.Closed,
|
||||
false);
|
||||
icon = GetFolderIconWithCache(Path, ShowOverlay, updateIconInBackground, IsMainMenu, out bool loading);
|
||||
IconLoading = loading;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool handled = false;
|
||||
string fileExtension = Path.GetExtension(TargetFilePath);
|
||||
icon = GetFileIconWithCache(Path, ResolvedPath, ShowOverlay, updateIconInBackground, IsMainMenu, out bool loading);
|
||||
IconLoading = loading;
|
||||
}
|
||||
|
||||
if (fileExtension == ".lnk")
|
||||
if (!IconLoading)
|
||||
{
|
||||
if (icon == null)
|
||||
{
|
||||
handled = SetLnk(
|
||||
ref isLnkDirectory,
|
||||
ref resolvedLnkPath);
|
||||
icon = Properties.Resources.NotFound;
|
||||
}
|
||||
else if (fileExtension == ".url")
|
||||
else if (HiddenEntry)
|
||||
{
|
||||
handled = SetUrl();
|
||||
}
|
||||
else if (fileExtension == ".sln")
|
||||
{
|
||||
handled = SetSln();
|
||||
}
|
||||
|
||||
if (!handled)
|
||||
{
|
||||
try
|
||||
{
|
||||
icon = IconReader.GetFileIconWithCache(TargetFilePath, false);
|
||||
diposeIcon = false;
|
||||
|
||||
// other project -> fails sometimes
|
||||
// icon = IconHelper.ExtractIcon(TargetFilePath, 0);
|
||||
|
||||
// standard way -> fails sometimes
|
||||
// icon = Icon.ExtractAssociatedIcon(filePath);
|
||||
|
||||
// API Code Pack -> fails sometimes
|
||||
// ShellFile shellFile = ShellFile.FromFilePath(filePath);
|
||||
// Bitmap shellThumb = shellFile.Thumbnail.ExtraLargeBitmap;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is SecurityException ||
|
||||
ex is ArgumentException ||
|
||||
ex is UnauthorizedAccessException ||
|
||||
ex is PathTooLongException ||
|
||||
ex is NotSupportedException)
|
||||
{
|
||||
Log.Warn($"path:'{TargetFilePath}'", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
icon = AddIconOverlay(icon, Properties.Resources.White50Percentage);
|
||||
}
|
||||
}
|
||||
|
||||
return isLnkDirectory;
|
||||
return icon;
|
||||
}
|
||||
|
||||
internal void MouseDown(DataGridView dgv, MouseEventArgs e)
|
||||
{
|
||||
if (e.Button == MouseButtons.Left)
|
||||
{
|
||||
IsClicking = true;
|
||||
}
|
||||
|
||||
if (e != null &&
|
||||
e.Button == MouseButtons.Right &&
|
||||
FileInfo != null &&
|
||||
|
@ -178,232 +206,97 @@ namespace SystemTrayMenu.DataClasses
|
|||
{
|
||||
IsContextMenuOpen = true;
|
||||
|
||||
ShellContextMenu ctxMnu = new ShellContextMenu();
|
||||
ShellContextMenu ctxMnu = new();
|
||||
Point location = dgv.FindForm().Location;
|
||||
Point point = new Point(
|
||||
Point point = new(
|
||||
e.X + location.X + dgv.Location.X,
|
||||
e.Y + location.Y + dgv.Location.Y);
|
||||
if (ContainsMenu)
|
||||
{
|
||||
DirectoryInfo[] dir = new DirectoryInfo[1];
|
||||
dir[0] = new DirectoryInfo(TargetFilePathOrig);
|
||||
dir[0] = new DirectoryInfo(Path);
|
||||
ctxMnu.ShowContextMenu(dir, point);
|
||||
TriggerFileWatcherChangeWorkaround();
|
||||
}
|
||||
else
|
||||
{
|
||||
FileInfo[] arrFI = new FileInfo[1];
|
||||
arrFI[0] = new FileInfo(TargetFilePathOrig);
|
||||
arrFI[0] = FileInfo;
|
||||
ctxMnu.ShowContextMenu(arrFI, point);
|
||||
TriggerFileWatcherChangeWorkaround();
|
||||
}
|
||||
|
||||
IsContextMenuOpen = false;
|
||||
contextMenuClosed = DateTime.Now;
|
||||
}
|
||||
|
||||
if (Properties.Settings.Default.OpenItemWithOneClick)
|
||||
void TriggerFileWatcherChangeWorkaround()
|
||||
{
|
||||
OpenItem(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal void DoubleClick(MouseEventArgs e)
|
||||
{
|
||||
if (!Properties.Settings.Default.OpenItemWithOneClick)
|
||||
{
|
||||
OpenItem(e);
|
||||
}
|
||||
|
||||
if (ContainsMenu &&
|
||||
(e == null || e.Button == MouseButtons.Left))
|
||||
{
|
||||
Log.ProcessStart(TargetFilePath, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!isDisposed)
|
||||
{
|
||||
if (diposeIcon)
|
||||
try
|
||||
{
|
||||
icon?.Dispose();
|
||||
string parentFolder = System.IO.Path.GetDirectoryName(Path);
|
||||
Directory.GetFiles(parentFolder);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn($"{nameof(TriggerFileWatcherChangeWorkaround)} '{Path}'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
isDisposed = true;
|
||||
}
|
||||
|
||||
private void OpenItem(MouseEventArgs e)
|
||||
internal void MouseClick(MouseEventArgs e, out bool toCloseByDoubleClick)
|
||||
{
|
||||
IsClicking = false;
|
||||
toCloseByDoubleClick = false;
|
||||
if (Properties.Settings.Default.OpenItemWithOneClick)
|
||||
{
|
||||
OpenItem(e, ref toCloseByDoubleClick);
|
||||
}
|
||||
|
||||
if (Properties.Settings.Default.OpenDirectoryWithOneClick &&
|
||||
ContainsMenu && (e == null || e.Button == MouseButtons.Left))
|
||||
{
|
||||
Log.ProcessStart(Path);
|
||||
if (!Properties.Settings.Default.StaysOpenWhenItemClicked)
|
||||
{
|
||||
toCloseByDoubleClick = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void DoubleClick(MouseEventArgs e, out bool toCloseByDoubleClick)
|
||||
{
|
||||
IsClicking = false;
|
||||
toCloseByDoubleClick = false;
|
||||
if (!Properties.Settings.Default.OpenItemWithOneClick)
|
||||
{
|
||||
OpenItem(e, ref toCloseByDoubleClick);
|
||||
}
|
||||
|
||||
if (!Properties.Settings.Default.OpenDirectoryWithOneClick &&
|
||||
ContainsMenu && (e == null || e.Button == MouseButtons.Left))
|
||||
{
|
||||
Log.ProcessStart(Path);
|
||||
if (!Properties.Settings.Default.StaysOpenWhenItemClicked)
|
||||
{
|
||||
toCloseByDoubleClick = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenItem(MouseEventArgs e, ref bool toCloseByOpenItem)
|
||||
{
|
||||
if (!ContainsMenu &&
|
||||
(e == null || e.Button == MouseButtons.Left))
|
||||
{
|
||||
try
|
||||
ProcessStarted = true;
|
||||
string workingDirectory = System.IO.Path.GetDirectoryName(ResolvedPath);
|
||||
Log.ProcessStart(Path, string.Empty, false, workingDirectory, true, ResolvedPath);
|
||||
if (!Properties.Settings.Default.StaysOpenWhenItemClicked)
|
||||
{
|
||||
using Process p = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo(TargetFilePath)
|
||||
{
|
||||
FileName = TargetFilePathOrig,
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = workingDirectory,
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = true,
|
||||
},
|
||||
};
|
||||
p.Start();
|
||||
}
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
Log.Warn($"path:'{TargetFilePath}'", ex);
|
||||
MessageBox.Show(ex.Message);
|
||||
toCloseByOpenItem = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool SetLnk(
|
||||
ref bool isLnkDirectory,
|
||||
ref string resolvedLnkPath)
|
||||
{
|
||||
bool handled = false;
|
||||
resolvedLnkPath = FileLnk.GetResolvedFileName(TargetFilePath);
|
||||
if (FileLnk.IsDirectory(resolvedLnkPath))
|
||||
{
|
||||
icon = IconReader.GetFolderIconSTA(TargetFilePath, IconReader.FolderType.Open, true);
|
||||
handled = true;
|
||||
isLnkDirectory = true;
|
||||
}
|
||||
else if (FileLnk.IsNetworkRoot(resolvedLnkPath))
|
||||
{
|
||||
isLnkDirectory = true;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(resolvedLnkPath))
|
||||
{
|
||||
Log.Info($"Resolve *.LNK '{TargetFilePath}' has no icon");
|
||||
}
|
||||
else
|
||||
{
|
||||
IWshShell shell = new WshShell();
|
||||
IWshShortcut lnk = shell.CreateShortcut(TargetFilePath)
|
||||
as IWshShortcut;
|
||||
arguments = lnk.Arguments;
|
||||
workingDirectory = lnk.WorkingDirectory;
|
||||
string iconLocation = lnk.IconLocation;
|
||||
if (iconLocation.Length > 2)
|
||||
{
|
||||
iconLocation = iconLocation[0..^2];
|
||||
if (System.IO.File.Exists(iconLocation))
|
||||
{
|
||||
try
|
||||
{
|
||||
icon = Icon.ExtractAssociatedIcon(iconLocation);
|
||||
handled = true;
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Log.Warn($"iconLocation:'{iconLocation}'", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TargetFilePath = resolvedLnkPath;
|
||||
}
|
||||
|
||||
SetText(Path.GetFileNameWithoutExtension(TargetFilePathOrig));
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
private bool SetUrl()
|
||||
{
|
||||
bool handled = false;
|
||||
string iconFile = string.Empty;
|
||||
try
|
||||
{
|
||||
FileIni file = new FileIni(TargetFilePath);
|
||||
iconFile = file.Value("IconFile", string.Empty);
|
||||
if (string.IsNullOrEmpty(iconFile))
|
||||
{
|
||||
string browserPath = FileUrl.GetDefaultBrowserPath();
|
||||
if (string.IsNullOrEmpty(browserPath))
|
||||
{
|
||||
Log.Info($"Resolve *.URL '{TargetFilePath}'" +
|
||||
$"No default browser found!");
|
||||
}
|
||||
else
|
||||
{
|
||||
icon = IconReader.GetFileIconWithCache(browserPath, false);
|
||||
diposeIcon = false;
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
else if (System.IO.File.Exists(iconFile))
|
||||
{
|
||||
icon = Icon.ExtractAssociatedIcon(iconFile);
|
||||
handled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Info($"Resolve *.URL '{TargetFilePath}' has no icon");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is SecurityException ||
|
||||
ex is ArgumentException ||
|
||||
ex is UnauthorizedAccessException ||
|
||||
ex is PathTooLongException ||
|
||||
ex is NotSupportedException)
|
||||
{
|
||||
Log.Warn(
|
||||
$"path:'{TargetFilePath}', " +
|
||||
$"iconFile:'{iconFile}'", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
SetText($"{FileInfo.Name[0..^4]}");
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
private bool SetSln()
|
||||
{
|
||||
bool handled = false;
|
||||
StringBuilder executable = new StringBuilder(1024);
|
||||
try
|
||||
{
|
||||
DllImports.NativeMethods.Shell32FindExecutable(TargetFilePath, string.Empty, executable);
|
||||
|
||||
// icon = IconReader.GetFileIcon(executable, false);
|
||||
// e.g. VS 2019 icon, need another icom in imagelist
|
||||
List<Icon> extractedIcons = IconHelper.ExtractAllIcons(
|
||||
executable.ToString());
|
||||
icon = extractedIcons.Last();
|
||||
handled = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is SecurityException ||
|
||||
ex is ArgumentException ||
|
||||
ex is UnauthorizedAccessException ||
|
||||
ex is PathTooLongException ||
|
||||
ex is NotSupportedException)
|
||||
{
|
||||
Log.Warn(
|
||||
$"path:'{TargetFilePath}', " +
|
||||
$"executable:'{executable}'", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,4 +10,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1601:Partial elements should be documented", Justification = "we need to document")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "we need to document")]
|
||||
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1000:Keywords should be spaced correctly", Justification = "new() should not be replaced by new() ")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Standard codecleanup removes the this")]
|
||||
|
||||
[assembly: SuppressMessage("Interoperability", "CA1416:Check platform compatibility", Justification = "this is a long way to get platform compatibility")]
|
||||
|
|
|
@ -9,19 +9,19 @@ namespace SystemTrayMenu.Helper
|
|||
|
||||
public class DgvMouseRow : IDisposable
|
||||
{
|
||||
private readonly Timer timerRaiseRowMouseLeave = new Timer();
|
||||
private DataGridView dgv = null;
|
||||
private DataGridViewCellEventArgs eventArgs = null;
|
||||
private readonly Timer timerRaiseRowMouseLeave = new();
|
||||
private DataGridView dgv;
|
||||
private DataGridViewCellEventArgs eventArgs;
|
||||
|
||||
internal DgvMouseRow()
|
||||
{
|
||||
timerRaiseRowMouseLeave.Interval = 200;
|
||||
timerRaiseRowMouseLeave.Tick += Elapsed;
|
||||
void Elapsed(object sender, EventArgs e)
|
||||
{
|
||||
timerRaiseRowMouseLeave.Stop();
|
||||
TriggerRowMouseLeave();
|
||||
}
|
||||
timerRaiseRowMouseLeave.Tick += TimerRaiseRowMouseLeave_Tick;
|
||||
}
|
||||
|
||||
~DgvMouseRow() // the finalizer
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
internal event Action<object, DataGridViewCellEventArgs> RowMouseEnter;
|
||||
|
@ -75,11 +75,18 @@ namespace SystemTrayMenu.Helper
|
|||
{
|
||||
if (disposing)
|
||||
{
|
||||
timerRaiseRowMouseLeave.Tick -= TimerRaiseRowMouseLeave_Tick;
|
||||
timerRaiseRowMouseLeave.Dispose();
|
||||
dgv?.Dispose();
|
||||
dgv = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void TimerRaiseRowMouseLeave_Tick(object sender, EventArgs e)
|
||||
{
|
||||
timerRaiseRowMouseLeave.Stop();
|
||||
TriggerRowMouseLeave();
|
||||
}
|
||||
|
||||
private void TriggerRowMouseLeave()
|
||||
{
|
||||
if (dgv != null)
|
||||
|
|
190
Helpers/DragDropHelper.cs
Normal file
|
@ -0,0 +1,190 @@
|
|||
// <copyright file="DragDropHelper.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace SystemTrayMenu.Helper
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using SystemTrayMenu.DataClasses;
|
||||
using SystemTrayMenu.UserInterface;
|
||||
using SystemTrayMenu.Utilities;
|
||||
|
||||
public static class DragDropHelper
|
||||
{
|
||||
public static void DragEnter(object sender, DragEventArgs e)
|
||||
{
|
||||
object data = e.Data.GetData("UniformResourceLocator");
|
||||
|
||||
if (data is MemoryStream memoryStream)
|
||||
{
|
||||
byte[] bytes = memoryStream.ToArray();
|
||||
Encoding encod = Encoding.ASCII;
|
||||
string url = encod.GetString(bytes);
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
e.Effect = DragDropEffects.Copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DragDrop(object sender, DragEventArgs e)
|
||||
{
|
||||
Menu menu = (Menu)sender;
|
||||
string path;
|
||||
if (menu != null)
|
||||
{
|
||||
RowData rowData = (RowData)menu.Tag;
|
||||
if (rowData != null)
|
||||
{
|
||||
path = rowData.ResolvedPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
path = Config.Path;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
path = Config.Path;
|
||||
}
|
||||
|
||||
object data = e.Data.GetData("UniformResourceLocator");
|
||||
MemoryStream ms = data as MemoryStream;
|
||||
byte[] bytes = ms.ToArray();
|
||||
Encoding encod = Encoding.ASCII;
|
||||
string url = encod.GetString(bytes);
|
||||
|
||||
new Thread(CreateShortcutInBackground).Start();
|
||||
void CreateShortcutInBackground()
|
||||
{
|
||||
CreateShortcut(url.Replace("\0", string.Empty), path);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateShortcut(string url, string pathToStoreFile)
|
||||
{
|
||||
string title = GetUrlShortcutTitle(url);
|
||||
string fileNamePathShortcut = pathToStoreFile + "\\" + title.Trim() + ".url";
|
||||
WriteShortcut(url, null, fileNamePathShortcut);
|
||||
string pathIcon = DownloadUrlIcon(url);
|
||||
if (!string.IsNullOrEmpty(pathIcon))
|
||||
{
|
||||
WriteShortcut(url, pathIcon, fileNamePathShortcut);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetUrlShortcutTitle(string url)
|
||||
{
|
||||
string title = url
|
||||
.Replace("/", " ")
|
||||
.Replace("https", string.Empty)
|
||||
.Replace("http", string.Empty);
|
||||
string invalid =
|
||||
new string(Path.GetInvalidFileNameChars()) +
|
||||
new string(Path.GetInvalidPathChars());
|
||||
foreach (char character in invalid)
|
||||
{
|
||||
title = title.Replace(character.ToString(), string.Empty);
|
||||
}
|
||||
|
||||
title = Truncate(title, 128); // max 255
|
||||
return title;
|
||||
}
|
||||
|
||||
private static string Truncate(string value, int maxLength)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value) &&
|
||||
value.Length > maxLength)
|
||||
{
|
||||
value = value[..maxLength];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void WriteShortcut(string url, string pathIcon, string fileNamePathShortcut)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(fileNamePathShortcut))
|
||||
{
|
||||
File.Delete(fileNamePathShortcut);
|
||||
}
|
||||
|
||||
StreamWriter writer = new(fileNamePathShortcut);
|
||||
writer.WriteLine("[InternetShortcut]");
|
||||
writer.WriteLine($"URL={url.TrimEnd('\0')}");
|
||||
writer.WriteLine("IconIndex=0");
|
||||
writer.WriteLine($"HotKey=0");
|
||||
writer.WriteLine($"IDList=");
|
||||
if (!string.IsNullOrEmpty(pathIcon))
|
||||
{
|
||||
writer.WriteLine($"IconFile={pathIcon}");
|
||||
}
|
||||
|
||||
writer.Flush();
|
||||
writer.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn($"{nameof(WriteShortcut)} failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static string DownloadUrlIcon(string url)
|
||||
{
|
||||
string pathIcon = string.Empty;
|
||||
string pathToStoreIcons = Properties.Settings.Default.PathIcoDirectory;
|
||||
Uri uri = new(url);
|
||||
string hostname = uri.Host.ToString();
|
||||
string pathIconPng = Path.Combine(pathToStoreIcons, $"{hostname}.png");
|
||||
string urlGoogleIconDownload = @"http://www.google.com/s2/favicons?sz=32&domain=" + url;
|
||||
HttpClient client = new();
|
||||
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(pathToStoreIcons))
|
||||
{
|
||||
Directory.CreateDirectory(pathToStoreIcons);
|
||||
}
|
||||
|
||||
using HttpResponseMessage response = client.GetAsync(urlGoogleIconDownload).Result;
|
||||
using HttpContent content = response.Content;
|
||||
Stream stream = content.ReadAsStreamAsync().Result;
|
||||
using var fileStream = File.Create(pathIconPng);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
stream.CopyTo(fileStream);
|
||||
fileStream.Close();
|
||||
|
||||
pathIcon = Path.Combine(pathToStoreIcons, $"{hostname}.ico");
|
||||
if (!ImagingHelper.ConvertToIcon(pathIconPng, pathIcon, 32))
|
||||
{
|
||||
Log.Info("Failed to convert icon.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn($"{nameof(DownloadUrlIcon)} failed", ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(pathIconPng))
|
||||
{
|
||||
File.Delete(pathIconPng);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn($"{nameof(DownloadUrlIcon)} failed to delete {pathIconPng}", ex);
|
||||
}
|
||||
|
||||
return pathIcon;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace SystemTrayMenu.UserInterface
|
||||
namespace SystemTrayMenu.Helper
|
||||
{
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
@ -10,7 +10,7 @@ namespace SystemTrayMenu.UserInterface
|
|||
|
||||
public class Fading : IDisposable
|
||||
{
|
||||
private const int Interval60FPS = 16; // 60fps=>1s/60fps=~16.6ms
|
||||
private const int Interval100FPS = 10; // 100fps=>1s/100fps=~10ms
|
||||
|
||||
private const double StepIn = 0.20;
|
||||
private const double StepOut = 0.10;
|
||||
|
@ -20,26 +20,27 @@ namespace SystemTrayMenu.UserInterface
|
|||
private const double Shown = 1.00;
|
||||
private const double ShownMinus = 0.80; // Shown - StepIn
|
||||
|
||||
private readonly Timer timer = new Timer();
|
||||
private readonly Timer timer = new();
|
||||
private FadingState state = FadingState.Idle;
|
||||
private double opacity = 0.00;
|
||||
private bool visible = false;
|
||||
private double opacity;
|
||||
private bool visible;
|
||||
|
||||
internal Fading()
|
||||
{
|
||||
timer.Interval = Interval60FPS;
|
||||
timer.Tick += Tick;
|
||||
void Tick(object sender, EventArgs e)
|
||||
{
|
||||
FadeStep();
|
||||
}
|
||||
timer.Interval = Interval100FPS;
|
||||
timer.Tick += Timer_Tick;
|
||||
}
|
||||
|
||||
internal event EventHandlerEmpty Hide;
|
||||
~Fading() // the finalizer
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
internal event EventHandlerEmpty Show;
|
||||
internal event Action Hide;
|
||||
|
||||
internal event EventHandler<double> ChangeOpacity;
|
||||
internal event Action Show;
|
||||
|
||||
internal event Action<double> ChangeOpacity;
|
||||
|
||||
internal enum FadingState
|
||||
{
|
||||
|
@ -66,6 +67,7 @@ namespace SystemTrayMenu.UserInterface
|
|||
{
|
||||
if (disposing)
|
||||
{
|
||||
timer.Tick -= Timer_Tick;
|
||||
timer.Dispose();
|
||||
}
|
||||
}
|
||||
|
@ -81,10 +83,14 @@ namespace SystemTrayMenu.UserInterface
|
|||
{
|
||||
state = newState;
|
||||
timer.Start();
|
||||
FadeStep();
|
||||
}
|
||||
}
|
||||
|
||||
private void Timer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
FadeStep();
|
||||
}
|
||||
|
||||
private void FadeStep()
|
||||
{
|
||||
switch (state)
|
||||
|
@ -95,17 +101,25 @@ namespace SystemTrayMenu.UserInterface
|
|||
visible = true;
|
||||
Show?.Invoke();
|
||||
opacity = 0;
|
||||
ChangeOpacity?.Invoke(this, opacity);
|
||||
ChangeOpacity?.Invoke(opacity);
|
||||
}
|
||||
else if (opacity < ShownMinus)
|
||||
else if (Properties.Settings.Default.UseFading &&
|
||||
opacity < ShownMinus)
|
||||
{
|
||||
opacity += StepIn;
|
||||
ChangeOpacity?.Invoke(this, opacity);
|
||||
ChangeOpacity?.Invoke(opacity);
|
||||
}
|
||||
else if (opacity != Shown)
|
||||
else
|
||||
{
|
||||
if (!Properties.Settings.Default.UseFading)
|
||||
{
|
||||
// #393 provoke a redraw for the CS_DROPSHADOW to work
|
||||
opacity = ShownMinus;
|
||||
ChangeOpacity?.Invoke(opacity);
|
||||
}
|
||||
|
||||
opacity = Shown;
|
||||
ChangeOpacity?.Invoke(this, Shown);
|
||||
ChangeOpacity?.Invoke(opacity);
|
||||
StartStopTimer(FadingState.Idle);
|
||||
}
|
||||
|
||||
|
@ -116,39 +130,47 @@ namespace SystemTrayMenu.UserInterface
|
|||
visible = true;
|
||||
Show?.Invoke();
|
||||
opacity = 0;
|
||||
ChangeOpacity?.Invoke(this, opacity);
|
||||
ChangeOpacity?.Invoke(opacity);
|
||||
}
|
||||
else if (opacity < TransparentMinus)
|
||||
else if (Properties.Settings.Default.UseFading &&
|
||||
opacity < TransparentMinus)
|
||||
{
|
||||
opacity += StepIn;
|
||||
ChangeOpacity?.Invoke(this, opacity);
|
||||
ChangeOpacity?.Invoke(opacity);
|
||||
}
|
||||
else if (opacity > TransparentPlus)
|
||||
else if (Properties.Settings.Default.UseFading &&
|
||||
opacity > TransparentPlus)
|
||||
{
|
||||
opacity -= StepOut;
|
||||
ChangeOpacity?.Invoke(this, opacity);
|
||||
ChangeOpacity?.Invoke(opacity);
|
||||
}
|
||||
else if (opacity != Transparent)
|
||||
else
|
||||
{
|
||||
ChangeOpacity?.Invoke(this, Transparent);
|
||||
opacity = Transparent;
|
||||
ChangeOpacity?.Invoke(opacity);
|
||||
StartStopTimer(FadingState.Idle);
|
||||
}
|
||||
|
||||
break;
|
||||
case FadingState.Hide:
|
||||
if (opacity > StepOut)
|
||||
if (Properties.Settings.Default.UseFading &&
|
||||
opacity > StepOut)
|
||||
{
|
||||
opacity -= StepOut;
|
||||
ChangeOpacity?.Invoke(this, opacity);
|
||||
ChangeOpacity?.Invoke(opacity);
|
||||
}
|
||||
else if (visible)
|
||||
{
|
||||
opacity = 0;
|
||||
ChangeOpacity?.Invoke(this, opacity);
|
||||
ChangeOpacity?.Invoke(opacity);
|
||||
visible = false;
|
||||
Hide?.Invoke();
|
||||
StartStopTimer(FadingState.Idle);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartStopTimer(FadingState.Idle);
|
||||
}
|
||||
|
||||
break;
|
||||
case FadingState.Idle:
|
||||
|
|
147
Helpers/ImagingHelper.cs
Normal file
|
@ -0,0 +1,147 @@
|
|||
// <copyright file="ImagingHelper.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace SystemTrayMenu.Helper
|
||||
{
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Provides helper methods for imaging.
|
||||
/// </summary>
|
||||
public static class ImagingHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a PNG image to a icon (ico).
|
||||
/// </summary>
|
||||
/// <param name="input">The input stream.</param>
|
||||
/// <param name="output">The output stream.</param>
|
||||
/// <param name="size">The size (16x16 px by default).</param>
|
||||
/// <param name="preserveAspectRatio">Preserve the aspect ratio.</param>
|
||||
/// <returns>Wether or not the icon was succesfully generated.</returns>
|
||||
public static bool ConvertToIcon(Stream input, Stream output, int size = 16, bool preserveAspectRatio = false)
|
||||
{
|
||||
Bitmap inputBitmap = (Bitmap)Image.FromStream(input);
|
||||
if (inputBitmap != null)
|
||||
{
|
||||
int width, height;
|
||||
if (preserveAspectRatio)
|
||||
{
|
||||
width = size;
|
||||
height = inputBitmap.Height / inputBitmap.Width * size;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = height = size;
|
||||
}
|
||||
|
||||
Bitmap newBitmap = new(inputBitmap, new Size(width, height));
|
||||
if (newBitmap != null)
|
||||
{
|
||||
// save the resized png into a memory stream for future use
|
||||
using MemoryStream memoryStream = new();
|
||||
newBitmap.Save(memoryStream, ImageFormat.Png);
|
||||
|
||||
BinaryWriter iconWriter = new(output);
|
||||
if (output != null && iconWriter != null)
|
||||
{
|
||||
// 0-1 reserved, 0
|
||||
iconWriter.Write((byte)0);
|
||||
iconWriter.Write((byte)0);
|
||||
|
||||
// 2-3 image type, 1 = icon, 2 = cursor
|
||||
iconWriter.Write((short)1);
|
||||
|
||||
// 4-5 number of images
|
||||
iconWriter.Write((short)1);
|
||||
|
||||
// image entry 1
|
||||
// 0 image width
|
||||
iconWriter.Write((byte)width);
|
||||
|
||||
// 1 image height
|
||||
iconWriter.Write((byte)height);
|
||||
|
||||
// 2 number of colors
|
||||
iconWriter.Write((byte)0);
|
||||
|
||||
// 3 reserved
|
||||
iconWriter.Write((byte)0);
|
||||
|
||||
// 4-5 color planes
|
||||
iconWriter.Write((short)0);
|
||||
|
||||
// 6-7 bits per pixel
|
||||
iconWriter.Write((short)32);
|
||||
|
||||
// 8-11 size of image data
|
||||
iconWriter.Write((int)memoryStream.Length);
|
||||
|
||||
// 12-15 offset of image data
|
||||
iconWriter.Write(6 + 16);
|
||||
|
||||
// write image data
|
||||
// png data must contain the whole png data file
|
||||
iconWriter.Write(memoryStream.ToArray());
|
||||
|
||||
iconWriter.Flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a PNG image to a icon (ico).
|
||||
/// </summary>
|
||||
/// <param name="inputPath">The input path.</param>
|
||||
/// <param name="outputPath">The output path.</param>
|
||||
/// <param name="size">The size (16x16 px by default).</param>
|
||||
/// <param name="preserveAspectRatio">Preserve the aspect ratio.</param>
|
||||
/// <returns>Wether or not the icon was succesfully generated.</returns>
|
||||
public static bool ConvertToIcon(string inputPath, string outputPath, int size = 16, bool preserveAspectRatio = false)
|
||||
{
|
||||
using FileStream inputStream = new(inputPath, FileMode.Open);
|
||||
using FileStream outputStream = new(outputPath, FileMode.OpenOrCreate);
|
||||
return ConvertToIcon(inputStream, outputStream, size, preserveAspectRatio);
|
||||
}
|
||||
|
||||
public static Image RotateImage(Image img, float rotationAngle)
|
||||
{
|
||||
// create an empty Bitmap image
|
||||
Bitmap bmp = new(img.Width, img.Height);
|
||||
|
||||
// turn the Bitmap into a Graphics object
|
||||
Graphics gfx = Graphics.FromImage(bmp);
|
||||
|
||||
// now we set the rotation point to the center of our image
|
||||
gfx.TranslateTransform(0.5f + ((float)bmp.Width / 2), 0.5f + ((float)bmp.Height / 2));
|
||||
|
||||
// now rotate the image
|
||||
gfx.RotateTransform(rotationAngle);
|
||||
|
||||
gfx.TranslateTransform(0.5f - ((float)bmp.Width / 2), 0.5f - ((float)bmp.Height / 2));
|
||||
|
||||
// set the InterpolationMode to HighQualityBicubic so to ensure a high
|
||||
// quality image once it is transformed to the specified size
|
||||
gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
|
||||
// now draw our new image onto the graphics object
|
||||
gfx.DrawImage(img, new Point(0, 0));
|
||||
|
||||
// dispose of our Graphics object
|
||||
gfx.Dispose();
|
||||
|
||||
// return the image
|
||||
return bmp;
|
||||
}
|
||||
}
|
||||
}
|
215
Helpers/JoystickHelper.cs
Normal file
|
@ -0,0 +1,215 @@
|
|||
// <copyright file="JoystickHelper.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace SystemTrayMenu.Helpers
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using SharpDX.DirectInput;
|
||||
|
||||
public class JoystickHelper : IDisposable
|
||||
{
|
||||
private readonly System.Timers.Timer timerReadJoystick = new();
|
||||
private readonly object lockRead = new();
|
||||
private Joystick joystick;
|
||||
private Keys pressingKey;
|
||||
private int pressingKeyCounter;
|
||||
private bool joystickHelperEnabled;
|
||||
|
||||
public JoystickHelper()
|
||||
{
|
||||
timerReadJoystick.Interval = 80;
|
||||
timerReadJoystick.Elapsed += ReadJoystickLoop;
|
||||
timerReadJoystick.Enabled = false;
|
||||
if (Properties.Settings.Default.SupportGamepad)
|
||||
{
|
||||
timerReadJoystick.Start();
|
||||
}
|
||||
}
|
||||
|
||||
~JoystickHelper() // the finalizer
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public event Action<Keys> KeyPressed;
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
joystickHelperEnabled = true;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
joystickHelperEnabled = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
timerReadJoystick.Elapsed -= ReadJoystickLoop;
|
||||
timerReadJoystick?.Dispose();
|
||||
joystick?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static Keys ReadKeyFromState(JoystickUpdate state)
|
||||
{
|
||||
Keys keys = Keys.None;
|
||||
switch (state.Offset)
|
||||
{
|
||||
case JoystickOffset.PointOfViewControllers0:
|
||||
switch (state.Value)
|
||||
{
|
||||
case 0:
|
||||
keys = Keys.Up;
|
||||
break;
|
||||
case 9000:
|
||||
keys = Keys.Right;
|
||||
break;
|
||||
case 18000:
|
||||
keys = Keys.Down;
|
||||
break;
|
||||
case 27000:
|
||||
keys = Keys.Left;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case JoystickOffset.Buttons0:
|
||||
if (state.Value == 128)
|
||||
{
|
||||
keys = Keys.Enter;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
private void ReadJoystickLoop(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
if (joystickHelperEnabled)
|
||||
{
|
||||
lock (lockRead)
|
||||
{
|
||||
timerReadJoystick.Stop();
|
||||
if (joystick == null)
|
||||
{
|
||||
Thread.Sleep(3000);
|
||||
InitializeJoystick();
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadJoystick();
|
||||
}
|
||||
|
||||
timerReadJoystick.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadJoystick()
|
||||
{
|
||||
try
|
||||
{
|
||||
joystick.Poll();
|
||||
JoystickUpdate[] datas = joystick.GetBufferedData();
|
||||
foreach (JoystickUpdate state in datas)
|
||||
{
|
||||
if (state.Value < 0)
|
||||
{
|
||||
pressingKey = Keys.None;
|
||||
pressingKeyCounter = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
Keys key = ReadKeyFromState(state);
|
||||
if (key != Keys.None)
|
||||
{
|
||||
KeyPressed?.Invoke(key);
|
||||
if (state.Offset == JoystickOffset.PointOfViewControllers0)
|
||||
{
|
||||
pressingKeyCounter = 0;
|
||||
pressingKey = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pressingKey != Keys.None)
|
||||
{
|
||||
pressingKeyCounter += 1;
|
||||
if (pressingKeyCounter > 1)
|
||||
{
|
||||
KeyPressed?.Invoke(pressingKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
joystick?.Dispose();
|
||||
joystick = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeJoystick()
|
||||
{
|
||||
// Initialize DirectInput
|
||||
DirectInput directInput = new();
|
||||
|
||||
// Find a Joystick Guid
|
||||
Guid joystickGuid = Guid.Empty;
|
||||
|
||||
foreach (DeviceInstance deviceInstance in directInput.GetDevices(
|
||||
DeviceType.Gamepad,
|
||||
DeviceEnumerationFlags.AllDevices))
|
||||
{
|
||||
joystickGuid = deviceInstance.InstanceGuid;
|
||||
}
|
||||
|
||||
// If Gamepad not found, look for a Joystick
|
||||
if (joystickGuid == Guid.Empty)
|
||||
{
|
||||
foreach (DeviceInstance deviceInstance in directInput.GetDevices(
|
||||
DeviceType.Joystick,
|
||||
DeviceEnumerationFlags.AllDevices))
|
||||
{
|
||||
joystickGuid = deviceInstance.InstanceGuid;
|
||||
}
|
||||
}
|
||||
|
||||
// If Joystick found
|
||||
if (joystickGuid != Guid.Empty)
|
||||
{
|
||||
// Instantiate the joystick
|
||||
joystick = new Joystick(directInput, joystickGuid);
|
||||
|
||||
// Set BufferSize in order to use buffered data.
|
||||
joystick.Properties.BufferSize = 128;
|
||||
|
||||
var handle = Process.GetCurrentProcess().MainWindowHandle;
|
||||
joystick.SetCooperativeLevel(handle, CooperativeLevel.NonExclusive | CooperativeLevel.Background);
|
||||
|
||||
// Acquire the joystick
|
||||
joystick.Acquire();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,16 +12,15 @@ namespace SystemTrayMenu.Helper
|
|||
/// </summary>
|
||||
internal class KeyPressedEventArgs : EventArgs
|
||||
{
|
||||
private readonly KeyboardHookModifierKeys modifier;
|
||||
private readonly Keys key;
|
||||
|
||||
internal KeyPressedEventArgs(KeyboardHookModifierKeys modifier, Keys key)
|
||||
{
|
||||
this.modifier = modifier;
|
||||
Modifier = modifier;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
internal KeyboardHookModifierKeys Modifier => modifier;
|
||||
internal KeyboardHookModifierKeys Modifier { get; }
|
||||
|
||||
internal Keys Key => key;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace SystemTrayMenu.Helper
|
|||
/// The enumeration of possible modifiers.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
internal enum KeyboardHookModifierKeys : uint
|
||||
public enum KeyboardHookModifierKeys : uint
|
||||
{
|
||||
None = 0,
|
||||
Alt = 1,
|
||||
|
@ -24,16 +24,13 @@ namespace SystemTrayMenu.Helper
|
|||
|
||||
public sealed class KeyboardHook : IDisposable
|
||||
{
|
||||
private readonly Window window = new Window();
|
||||
private readonly Window window = new();
|
||||
private int currentId;
|
||||
|
||||
public KeyboardHook()
|
||||
{
|
||||
// register the event of the inner native window.
|
||||
window.KeyPressed += (sender, args) =>
|
||||
{
|
||||
KeyPressed?.Invoke(this, args);
|
||||
};
|
||||
window.KeyPressed += Window_KeyPressed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -50,6 +47,7 @@ namespace SystemTrayMenu.Helper
|
|||
}
|
||||
|
||||
// dispose the inner native window.
|
||||
window.KeyPressed -= Window_KeyPressed;
|
||||
window.Dispose();
|
||||
}
|
||||
|
||||
|
@ -107,6 +105,11 @@ namespace SystemTrayMenu.Helper
|
|||
RegisterHotKey((uint)modifier, key);
|
||||
}
|
||||
|
||||
private void Window_KeyPressed(object sender, KeyPressedEventArgs e)
|
||||
{
|
||||
KeyPressed?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void RegisterHotKey(uint modifier, Keys key)
|
||||
{
|
||||
currentId += 1;
|
||||
|
|
251
Helpers/Updater/GitHubUpdate.cs
Normal file
|
@ -0,0 +1,251 @@
|
|||
// <copyright file="GitHubUpdate.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace SystemTrayMenu.Helper.Updater
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
using SystemTrayMenu.Utilities;
|
||||
|
||||
public class GitHubUpdate
|
||||
{
|
||||
private static List<Dictionary<string, object>> releases;
|
||||
private static Form newVersionForm;
|
||||
|
||||
public static void ActivateNewVersionFormOrCheckForUpdates(bool showWhenUpToDate)
|
||||
{
|
||||
if (newVersionForm != null)
|
||||
{
|
||||
newVersionForm.HandleInvoke(newVersionForm.Activate);
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckForUpdates(showWhenUpToDate);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckForUpdates(bool showWhenUpToDate)
|
||||
{
|
||||
string urlGithubReleases = @"http://api.github.com/repos/Hofknecht/SystemTrayMenu/releases";
|
||||
HttpClient client = new();
|
||||
|
||||
// https://developer.github.com/v3/#user-agent-required
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "SystemTrayMenu/" + Application.ProductVersion.ToString());
|
||||
|
||||
// https://developer.github.com/v3/media/#request-specific-version
|
||||
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3.text+json");
|
||||
|
||||
try
|
||||
{
|
||||
using HttpResponseMessage response = client.GetAsync(urlGithubReleases).Result;
|
||||
using HttpContent content = response.Content;
|
||||
string responseString = content.ReadAsStringAsync().Result;
|
||||
releases = responseString.FromJson<List<Dictionary<string, object>>>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn($"{nameof(CheckForUpdates)} failed", ex);
|
||||
}
|
||||
|
||||
if (releases == null)
|
||||
{
|
||||
Log.Info($"{nameof(CheckForUpdates)} failed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveCurrentAndOlderVersions();
|
||||
ShowNewVersionOrUpToDateDialog(showWhenUpToDate);
|
||||
}
|
||||
|
||||
newVersionForm?.Dispose();
|
||||
newVersionForm = null;
|
||||
}
|
||||
|
||||
private static void RemoveCurrentAndOlderVersions()
|
||||
{
|
||||
int releasesCount = releases.Count;
|
||||
Version versionCurrent = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
for (int i = 0; i < releasesCount; i++)
|
||||
{
|
||||
string tagName = releases[i]["tag_name"].ToString();
|
||||
Version versionGitHub = new(tagName.Replace("v", string.Empty));
|
||||
if (versionGitHub.CompareTo(versionCurrent) < 1)
|
||||
{
|
||||
releases.RemoveRange(i, releasesCount - i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ShowNewVersionOrUpToDateDialog(bool showWhenUpToDate)
|
||||
{
|
||||
if (releases.Count > 0)
|
||||
{
|
||||
if (NewVersionDialog() == DialogResult.Yes)
|
||||
{
|
||||
Log.ProcessStart("https://github.com/Hofknecht/SystemTrayMenu/releases");
|
||||
}
|
||||
}
|
||||
else if (showWhenUpToDate)
|
||||
{
|
||||
MessageBox.Show(Translator.GetText("You have the latest version of SystemTrayMenu!"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a window to show changelog of new available versions.
|
||||
/// </summary>
|
||||
/// <param name="LatestVersionTitle">Name of latest release.</param>
|
||||
/// <param name="Changelog">Pathnotes.</param>
|
||||
/// <returns>OK = OK, Yes = Website, else = Cancel.</returns>
|
||||
private static DialogResult NewVersionDialog()
|
||||
{
|
||||
const int ClientPad = 15;
|
||||
newVersionForm = new()
|
||||
{
|
||||
StartPosition = FormStartPosition.CenterScreen,
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||||
Icon = Config.GetAppIcon(),
|
||||
ShowInTaskbar = false,
|
||||
};
|
||||
newVersionForm.FormBorderStyle = FormBorderStyle.Sizable;
|
||||
newVersionForm.MaximizeBox = true;
|
||||
newVersionForm.MinimizeBox = false;
|
||||
newVersionForm.ClientSize = new Size(600, 400);
|
||||
newVersionForm.MinimumSize = newVersionForm.ClientSize;
|
||||
newVersionForm.Text = Translator.GetText("New version available!");
|
||||
|
||||
Label label = new()
|
||||
{
|
||||
Size = new Size(newVersionForm.ClientSize.Width - ClientPad, 20),
|
||||
Location = new Point(ClientPad, ClientPad),
|
||||
Text = $"{Translator.GetText("Latest available version:")} {GetLatestVersionName()}",
|
||||
};
|
||||
newVersionForm.Controls.Add(label);
|
||||
|
||||
Button buttonOK = new()
|
||||
{
|
||||
DialogResult = DialogResult.OK,
|
||||
Name = "buttonOK",
|
||||
};
|
||||
buttonOK.Location = new Point(
|
||||
newVersionForm.ClientSize.Width - buttonOK.Size.Width - ClientPad,
|
||||
newVersionForm.ClientSize.Height - buttonOK.Size.Height - ClientPad);
|
||||
buttonOK.MinimumSize = new Size(75, 23);
|
||||
buttonOK.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
buttonOK.Text = Translator.GetText("OK");
|
||||
buttonOK.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
buttonOK.AutoSize = true;
|
||||
newVersionForm.Controls.Add(buttonOK);
|
||||
|
||||
Button buttonGoToDownloadPage = new()
|
||||
{
|
||||
DialogResult = DialogResult.Yes,
|
||||
Name = "buttonGoToDownloadPage",
|
||||
};
|
||||
buttonGoToDownloadPage.Location = new Point(
|
||||
newVersionForm.ClientSize.Width - buttonGoToDownloadPage.Size.Width - ClientPad - buttonOK.Size.Width - ClientPad,
|
||||
newVersionForm.ClientSize.Height - buttonGoToDownloadPage.Size.Height - ClientPad);
|
||||
buttonGoToDownloadPage.MinimumSize = new Size(75, 23);
|
||||
buttonGoToDownloadPage.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
buttonGoToDownloadPage.Text = Translator.GetText("Go to download page");
|
||||
buttonGoToDownloadPage.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
buttonGoToDownloadPage.AutoSize = true;
|
||||
newVersionForm.Controls.Add(buttonGoToDownloadPage);
|
||||
|
||||
TextBox textBox = new()
|
||||
{
|
||||
Location = new Point(ClientPad, label.Location.Y + label.Size.Height + 5),
|
||||
};
|
||||
textBox.Size = new Size(
|
||||
newVersionForm.ClientSize.Width - (ClientPad * 2),
|
||||
buttonOK.Location.Y - ClientPad - textBox.Location.Y);
|
||||
textBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
textBox.Multiline = true;
|
||||
textBox.Text = GetChangelog();
|
||||
textBox.ReadOnly = true;
|
||||
textBox.ScrollBars = ScrollBars.Both;
|
||||
textBox.BackColor = Color.FromKnownColor(KnownColor.Window);
|
||||
textBox.ForeColor = Color.FromKnownColor(KnownColor.ControlText);
|
||||
newVersionForm.Controls.Add(textBox);
|
||||
|
||||
newVersionForm.AcceptButton = buttonOK;
|
||||
return newVersionForm.ShowDialog();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the latest release version name.
|
||||
/// </summary>
|
||||
/// <returns>Version name.</returns>
|
||||
private static string GetLatestVersionName()
|
||||
{
|
||||
string result = "Unknown";
|
||||
|
||||
if (releases == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
result = releases[0]["tag_name"].ToString().Replace("v", string.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn($"{nameof(GetLatestVersionName)} failed", ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the change log from current version up to the latest release version.
|
||||
/// </summary>
|
||||
/// <returns>Change log summary or error text.</returns>
|
||||
private static string GetChangelog()
|
||||
{
|
||||
string result = string.Empty;
|
||||
string errorstr = "An error occurred during update check!" + Environment.NewLine;
|
||||
|
||||
if (releases == null)
|
||||
{
|
||||
return errorstr + "Could not receive changelog!";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < releases.Count; i++)
|
||||
{
|
||||
Dictionary<string, object> release = releases[i];
|
||||
|
||||
result += release["name"].ToString()
|
||||
+ Environment.NewLine
|
||||
+ release["body_text"].ToString()
|
||||
.Replace("\n\n", Environment.NewLine)
|
||||
.Replace("\n \n", Environment.NewLine)
|
||||
+ Environment.NewLine + Environment.NewLine;
|
||||
if (i < releases.Count)
|
||||
{
|
||||
result += "--------------------------------------------------" +
|
||||
"-------------------------------------------------------"
|
||||
+ Environment.NewLine;
|
||||
}
|
||||
}
|
||||
|
||||
result = result.Replace("\n", Environment.NewLine);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warn($"{nameof(GetChangelog)}", ex);
|
||||
result = errorstr + ex.Message.ToString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
480
Helpers/Updater/JsonParser.cs
Normal file
|
@ -0,0 +1,480 @@
|
|||
// <copyright file="JsonParser.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace SystemTrayMenu.Helper.Updater
|
||||
{
|
||||
// Copyright (c) 2018 Alex Parker
|
||||
// Copyright (c) 2018-2019 Peter Kirmeier
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
|
||||
// Really simple JSON parser in ~300 lines
|
||||
// - Attempts to parse JSON files with minimal GC allocation
|
||||
// - Nice and simple "[1,2,3]".FromJson<List<int>>() API
|
||||
// - Classes and structs can be parsed too!
|
||||
// class Foo { public int Value; }
|
||||
// "{\"Value\":10}".FromJson<Foo>()
|
||||
// - Can parse JSON without type information into Dictionary<string,object> and List<object> e.g.
|
||||
// "[1,2,3]".FromJson<object>().GetType() == typeof(List<object>)
|
||||
// "{\"Value\":10}".FromJson<object>().GetType() == typeof(Dictionary<string,object>)
|
||||
// - No JIT Emit support to support AOT compilation on iOS
|
||||
// - Attempts are made to NOT throw an exception if the JSON is corrupted or invalid: returns null instead.
|
||||
// - Only public fields and property setters on classes/structs will be written to
|
||||
//
|
||||
// Limitations:
|
||||
// - No JIT Emit support to parse structures quickly
|
||||
// - Limited to parsing <2GB JSON files (due to int.MaxValue)
|
||||
// - Parsing of abstract classes or interfaces is NOT supported and will throw an exception.
|
||||
public static class JSONParser
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static Stack<List<string>> splitArrayPool;
|
||||
[ThreadStatic]
|
||||
private static StringBuilder stringBuilder;
|
||||
[ThreadStatic]
|
||||
private static Dictionary<Type, Dictionary<string, FieldInfo>> fieldInfoCache;
|
||||
[ThreadStatic]
|
||||
private static Dictionary<Type, Dictionary<string, PropertyInfo>> propertyInfoCache;
|
||||
|
||||
public static T FromJson<T>(this string json)
|
||||
{
|
||||
// Initialize, if needed, the ThreadStatic variables
|
||||
propertyInfoCache ??= new Dictionary<Type, Dictionary<string, PropertyInfo>>();
|
||||
|
||||
fieldInfoCache ??= new Dictionary<Type, Dictionary<string, FieldInfo>>();
|
||||
|
||||
stringBuilder ??= new StringBuilder();
|
||||
|
||||
splitArrayPool ??= new Stack<List<string>>();
|
||||
|
||||
// Remove all whitespace not within strings to make parsing simpler
|
||||
stringBuilder.Length = 0;
|
||||
for (int i = 0; i < json.Length; i++)
|
||||
{
|
||||
char c = json[i];
|
||||
if (c == '"')
|
||||
{
|
||||
i = AppendUntilStringEnd(true, i, json);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char.IsWhiteSpace(c))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
stringBuilder.Append(c);
|
||||
}
|
||||
|
||||
// Parse the thing!
|
||||
return (T)ParseValue(typeof(T), stringBuilder.ToString());
|
||||
}
|
||||
|
||||
internal static object ParseValue(Type type, string json)
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
if (json.Length <= 2)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
StringBuilder parseStringBuilder = new(json.Length);
|
||||
for (int i = 1; i < json.Length - 1; ++i)
|
||||
{
|
||||
if (json[i] == '\\' && i + 1 < json.Length - 1)
|
||||
{
|
||||
int j = "\"\\nrtbf/".IndexOf(json[i + 1]);
|
||||
if (j >= 0)
|
||||
{
|
||||
parseStringBuilder.Append("\"\\\n\r\t\b\f/"[j]);
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (json[i + 1] == 'u' && i + 5 < json.Length - 1)
|
||||
{
|
||||
if (uint.TryParse(json.AsSpan(i + 2, 4), System.Globalization.NumberStyles.AllowHexSpecifier, null, out uint c))
|
||||
{
|
||||
parseStringBuilder.Append((char)c);
|
||||
i += 5;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parseStringBuilder.Append(json[i]);
|
||||
}
|
||||
|
||||
return parseStringBuilder.ToString();
|
||||
}
|
||||
|
||||
if (type.IsPrimitive)
|
||||
{
|
||||
var result = Convert.ChangeType(json, type, System.Globalization.CultureInfo.InvariantCulture);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (type == typeof(decimal))
|
||||
{
|
||||
decimal.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out decimal result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (json == "null")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
if (json[0] == '"')
|
||||
{
|
||||
json = json[1..^1];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Enum.Parse(type, json, false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (type.IsArray)
|
||||
{
|
||||
Type arrayType = type.GetElementType();
|
||||
if (json[0] != '[' || json[^1] != ']')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<string> elems = Split(json);
|
||||
Array newArray = Array.CreateInstance(arrayType, elems.Count);
|
||||
for (int i = 0; i < elems.Count; i++)
|
||||
{
|
||||
newArray.SetValue(ParseValue(arrayType, elems[i]), i);
|
||||
}
|
||||
|
||||
splitArrayPool.Push(elems);
|
||||
return newArray;
|
||||
}
|
||||
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
|
||||
{
|
||||
Type listType = type.GetGenericArguments()[0];
|
||||
if (json[0] != '[' || json[^1] != ']')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<string> elems = Split(json);
|
||||
var list = (IList)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count });
|
||||
for (int i = 0; i < elems.Count; i++)
|
||||
{
|
||||
list.Add(ParseValue(listType, elems[i]));
|
||||
}
|
||||
|
||||
splitArrayPool.Push(elems);
|
||||
return list;
|
||||
}
|
||||
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
{
|
||||
Type keyType, valueType;
|
||||
{
|
||||
Type[] args = type.GetGenericArguments();
|
||||
keyType = args[0];
|
||||
valueType = args[1];
|
||||
}
|
||||
|
||||
// Refuse to parse dictionary keys that aren't of type string
|
||||
if (keyType != typeof(string))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Must be a valid dictionary element
|
||||
if (json[0] != '{' || json[^1] != '}')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
|
||||
List<string> elems = Split(json);
|
||||
if (elems.Count % 2 != 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var dictionary = (IDictionary)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count / 2 });
|
||||
for (int i = 0; i < elems.Count; i += 2)
|
||||
{
|
||||
if (elems[i].Length <= 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string keyValue = elems[i][1..^1];
|
||||
object val = ParseValue(valueType, elems[i + 1]);
|
||||
dictionary.Add(keyValue, val);
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
if (type == typeof(object))
|
||||
{
|
||||
return ParseAnonymousValue(json);
|
||||
}
|
||||
|
||||
if (json[0] == '{' && json[^1] == '}')
|
||||
{
|
||||
return ParseObject(type, json);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json)
|
||||
{
|
||||
stringBuilder.Append(json[startIdx]);
|
||||
for (int i = startIdx + 1; i < json.Length; i++)
|
||||
{
|
||||
if (json[i] == '\\')
|
||||
{
|
||||
if (appendEscapeCharacter)
|
||||
{
|
||||
stringBuilder.Append(json[i]);
|
||||
}
|
||||
|
||||
stringBuilder.Append(json[i + 1]);
|
||||
i++; // Skip next character as it is escaped
|
||||
}
|
||||
else if (json[i] == '"')
|
||||
{
|
||||
stringBuilder.Append(json[i]);
|
||||
return i;
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(json[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return json.Length - 1;
|
||||
}
|
||||
|
||||
// Splits { <value>:<value>, <value>:<value> } and [ <value>, <value> ] into a list of <value> strings
|
||||
private static List<string> Split(string json)
|
||||
{
|
||||
List<string> splitArray = splitArrayPool.Count > 0 ? splitArrayPool.Pop() : new List<string>();
|
||||
splitArray.Clear();
|
||||
if (json.Length == 2)
|
||||
{
|
||||
return splitArray;
|
||||
}
|
||||
|
||||
int parseDepth = 0;
|
||||
stringBuilder.Length = 0;
|
||||
for (int i = 1; i < json.Length - 1; i++)
|
||||
{
|
||||
switch (json[i])
|
||||
{
|
||||
case '[':
|
||||
case '{':
|
||||
parseDepth++;
|
||||
break;
|
||||
case ']':
|
||||
case '}':
|
||||
parseDepth--;
|
||||
break;
|
||||
case '"':
|
||||
i = AppendUntilStringEnd(true, i, json);
|
||||
continue;
|
||||
case ',':
|
||||
case ':':
|
||||
if (parseDepth == 0)
|
||||
{
|
||||
splitArray.Add(stringBuilder.ToString());
|
||||
stringBuilder.Length = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
stringBuilder.Append(json[i]);
|
||||
}
|
||||
|
||||
splitArray.Add(stringBuilder.ToString());
|
||||
|
||||
return splitArray;
|
||||
}
|
||||
|
||||
private static object ParseAnonymousValue(string json)
|
||||
{
|
||||
if (json.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (json[0] == '{' && json[^1] == '}')
|
||||
{
|
||||
List<string> elems = Split(json);
|
||||
if (elems.Count % 2 != 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var dict = new Dictionary<string, object>(elems.Count / 2);
|
||||
for (int i = 0; i < elems.Count; i += 2)
|
||||
{
|
||||
dict.Add(elems[i][1..^1], ParseAnonymousValue(elems[i + 1]));
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
if (json[0] == '[' && json[^1] == ']')
|
||||
{
|
||||
List<string> items = Split(json);
|
||||
var finalList = new List<object>(items.Count);
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
finalList.Add(ParseAnonymousValue(items[i]));
|
||||
}
|
||||
|
||||
return finalList;
|
||||
}
|
||||
|
||||
if (json[0] == '"' && json[^1] == '"')
|
||||
{
|
||||
return ParseValue(typeof(string), json); // fix https://github.com/zanders3/json/issues/29
|
||||
}
|
||||
|
||||
if (char.IsDigit(json[0]) || json[0] == '-')
|
||||
{
|
||||
if (json.Contains('.'))
|
||||
{
|
||||
double.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double result);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = int.TryParse(json, out int result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (json == "true")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (json == "false")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// handles json == "null" as well as invalid JSON
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Dictionary<string, T> CreateMemberNameDictionary<T>(T[] members)
|
||||
where T : MemberInfo
|
||||
{
|
||||
Dictionary<string, T> nameToMember = new(StringComparer.OrdinalIgnoreCase);
|
||||
for (int i = 0; i < members.Length; i++)
|
||||
{
|
||||
/*T member = members[i];
|
||||
if (member.IsDefined(typeof(IgnoreDataMemberAttribute), true))
|
||||
continue;
|
||||
|
||||
string name = member.Name;
|
||||
if (member.IsDefined(typeof(DataMemberAttribute), true))
|
||||
{
|
||||
DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true);
|
||||
if (!string.IsNullOrEmpty(dataMemberAttribute.Name))
|
||||
name = dataMemberAttribute.Name;
|
||||
}
|
||||
|
||||
nameToMember.Add(name, member);*/
|
||||
// The above code is not working with .Net framework 2.0, so we ignore these attributes for compatibility reasons:
|
||||
nameToMember.Add(members[i].Name, members[i]);
|
||||
}
|
||||
|
||||
return nameToMember;
|
||||
}
|
||||
|
||||
private static object ParseObject(Type type, string json)
|
||||
{
|
||||
object instance = FormatterServices.GetUninitializedObject(type);
|
||||
|
||||
// The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
|
||||
List<string> elems = Split(json);
|
||||
if (elems.Count % 2 != 0)
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
|
||||
if (!fieldInfoCache.TryGetValue(type, out Dictionary<string, FieldInfo> nameToField))
|
||||
{
|
||||
nameToField = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
|
||||
fieldInfoCache.Add(type, nameToField);
|
||||
}
|
||||
|
||||
if (!propertyInfoCache.TryGetValue(type, out Dictionary<string, PropertyInfo> nameToProperty))
|
||||
{
|
||||
nameToProperty = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
|
||||
propertyInfoCache.Add(type, nameToProperty);
|
||||
}
|
||||
|
||||
for (int i = 0; i < elems.Count; i += 2)
|
||||
{
|
||||
if (elems[i].Length <= 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string key = elems[i][1..^1];
|
||||
string value = elems[i + 1];
|
||||
|
||||
if (nameToField.TryGetValue(key, out FieldInfo fieldInfo))
|
||||
{
|
||||
fieldInfo.SetValue(instance, ParseValue(fieldInfo.FieldType, value));
|
||||
}
|
||||
else if (nameToProperty.TryGetValue(key, out PropertyInfo propertyInfo))
|
||||
{
|
||||
propertyInfo.SetValue(instance, ParseValue(propertyInfo.PropertyType, value), null);
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ namespace SystemTrayMenu.Helper
|
|||
{
|
||||
IntPtr taskbarHandle = User32FindWindow(ClassName, null);
|
||||
|
||||
APPBARDATA data = new APPBARDATA
|
||||
APPBARDATA data = new()
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf(typeof(APPBARDATA)),
|
||||
hWnd = taskbarHandle,
|
||||
|
|
4
LICENSE
|
@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least
|
|||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
TAMAHO SystemTrayMenu - browse and open your files easily
|
||||
Copyright (C) 2020 Markus Hofknecht
|
||||
Copyright (C) 2022 Markus Hofknecht
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -652,7 +652,7 @@ You can contact me by mail Markus@Hofknecht.eu
|
|||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
TAMAHO SystemTrayMenu Copyright (C) 2020 Markus Hofknecht
|
||||
TAMAHO SystemTrayMenu Copyright (C) 2022 Markus Hofknecht
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
|
|
@ -42,27 +42,27 @@ namespace SystemTrayMenu.DllImports
|
|||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern bool IsIconic(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern uint GetCurrentThreadId();
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern bool BringWindowToTop(IntPtr hWnd);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace SystemTrayMenu.DllImports
|
|||
|
||||
// The CreatePopupMenu function creates a drop-down menu, submenu, or shortcut menu. The menu is initially empty. You can insert or append menu items by using the InsertMenuItem function. You can also use the InsertMenu function to insert menu items and the AppendMenu function to append menu items.
|
||||
[DllImport("user32", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern IntPtr CreatePopupMenu();
|
||||
}
|
||||
}
|
||||
|
|
40
NativeDllImport/CreateRoundRectRgn.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
// <copyright file="CreateRoundRectRgn.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace SystemTrayMenu.DllImports
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
/// <summary>
|
||||
/// wraps the methodcalls to native windows dll's.
|
||||
/// </summary>
|
||||
public static partial class NativeMethods
|
||||
{
|
||||
public static bool GetRegionRoundCorners(int width, int height, int widthEllipse, int heightEllipse, out System.Drawing.Region region)
|
||||
{
|
||||
bool success = false;
|
||||
region = null;
|
||||
|
||||
IntPtr handle = CreateRoundRectRgn(0, 0, width, height, widthEllipse, heightEllipse);
|
||||
if (handle != IntPtr.Zero)
|
||||
{
|
||||
region = System.Drawing.Region.FromHrgn(handle);
|
||||
_ = DeleteObject(handle);
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
[DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr CreateRoundRectRgn(
|
||||
int nLeftRect, // x-coordinate of upper-left corner
|
||||
int nTopRect, // y-coordinate of upper-left corner
|
||||
int nRightRect, // x-coordinate of lower-right corner
|
||||
int nBottomRect, // y-coordinate of lower-right corner
|
||||
int nWidthEllipse, // width of ellipse
|
||||
int nHeightEllipse); // height of ellipse
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// <copyright file="GetDeviceCaps.cs" company="PlaceholderCompany">
|
||||
// <copyright file="DeleteObject.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
|
@ -12,13 +12,8 @@ namespace SystemTrayMenu.DllImports
|
|||
/// </summary>
|
||||
public static partial class NativeMethods
|
||||
{
|
||||
public static int Gdi32GetDeviceCaps(IntPtr hdc, int nIndex)
|
||||
{
|
||||
return GetDeviceCaps(hdc, nIndex);
|
||||
}
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern int DeleteObject(IntPtr hIcon);
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ namespace SystemTrayMenu.DllImports
|
|||
}
|
||||
|
||||
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern int DestroyIcon(IntPtr hIcon);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace SystemTrayMenu.DllImports
|
|||
|
||||
// The DestroyMenu function destroys the specified menu and frees any memory that the menu occupies.
|
||||
[DllImport("user32", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern bool DestroyMenu(IntPtr hMenu);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace SystemTrayMenu.DllImports
|
|||
}
|
||||
|
||||
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern int FindExecutable(string lpFile, string lpDirectory, [Out] StringBuilder lpResult);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace SystemTrayMenu.DllImports
|
|||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ namespace SystemTrayMenu.DllImports
|
|||
/// </summary>
|
||||
public static partial class NativeMethods
|
||||
{
|
||||
#pragma warning disable SA1600 // Elements should be documented
|
||||
public const uint ShgfiIcon = 0x000000100; // get icon
|
||||
public const uint ShgfiSYSICONINDEX = 0x000004000; // get system icon index
|
||||
public const uint ShgfiLINKOVERLAY = 0x000008000; // put a link overlay on icon
|
||||
|
@ -22,16 +21,6 @@ namespace SystemTrayMenu.DllImports
|
|||
public const uint FileAttributeDirectory = 0x00000010;
|
||||
public const uint FileAttributeNormal = 0x00000080;
|
||||
public const int IldTransparent = 0x00000001;
|
||||
#pragma warning restore SA1600 // Elements should be documented
|
||||
|
||||
/// <summary>
|
||||
/// comctl32 ImageList_GetIcon(IntPtr hIcon).
|
||||
/// </summary>
|
||||
/// <param name="hIcon">hIcon.</param>
|
||||
public static void Comctl32ImageListGetIcon(IntPtr hIcon)
|
||||
{
|
||||
_ = DestroyIcon(hIcon);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// comctl32 ImageList_GetIcon(IntPtr himl, int i, int flags).
|
||||
|
@ -41,7 +30,7 @@ namespace SystemTrayMenu.DllImports
|
|||
/// <param name="flags">flags.</param>
|
||||
/// <returns>IntPtr.</returns>
|
||||
[DllImport("comctl32", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
internal static extern IntPtr ImageList_GetIcon(
|
||||
IntPtr himl,
|
||||
int i,
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
// <copyright file="GetMenuDefaultItem.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace SystemTrayMenu.DllImports
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
/// <summary>
|
||||
/// wraps the methodcalls to native windows dll's.
|
||||
/// </summary>
|
||||
public static partial class NativeMethods
|
||||
{
|
||||
public static int User32GetMenuDefaultItem(IntPtr hMenu, bool fByPos, uint gmdiFlags)
|
||||
{
|
||||
return GetMenuDefaultItem(hMenu, fByPos, gmdiFlags);
|
||||
}
|
||||
|
||||
// Determines the default menu item on the specified menu
|
||||
[DllImport("user32", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
private static extern int GetMenuDefaultItem(IntPtr hMenu, bool fByPos, uint gmdiFlags);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ namespace SystemTrayMenu.DllImports
|
|||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern int GetSystemMetrics(int nIndex);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,21 +34,21 @@ namespace SystemTrayMenu.DllImports
|
|||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
|
||||
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint virtualKeyCode);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern uint MapVirtualKey(uint uCode, uint uMapType);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern int GetKeyNameText(uint lParam, [Out] StringBuilder lpString, int nSize);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace SystemTrayMenu.DllImports
|
|||
}
|
||||
|
||||
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern IntPtr SHAppBarMessage(ABM dwMessage, [In] ref APPBARDATA pData);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace SystemTrayMenu.DllImports
|
|||
|
||||
// Retrieves the IShellFolder interface for the desktop folder, which is the root of the Shell's namespace.
|
||||
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern int SHGetDesktopFolder(out IntPtr ppshf);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace SystemTrayMenu.DllImports
|
|||
}
|
||||
|
||||
[DllImport("Shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern IntPtr SHGetFileInfo(
|
||||
string pszPath,
|
||||
uint dwFileAttributes,
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace SystemTrayMenu.DllImports
|
|||
}
|
||||
|
||||
[DllImport("shfolder.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern int SHGetFolderPath(IntPtr hwndOwner, int nFolder, IntPtr hToken, int dwFlags, StringBuilder lpszPath);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace SystemTrayMenu.DllImports
|
|||
}
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowPos", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern bool SetWindowPos(
|
||||
int hWnd, // Window handle
|
||||
int hWndInsertAfter, // Placement-order handle
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace SystemTrayMenu.DllImports
|
|||
public static partial class NativeMethods
|
||||
{
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace SystemTrayMenu.DllImports
|
|||
}
|
||||
|
||||
[DllImport("shlwapi.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern int StrCmpLogicalW(string x, string y);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace SystemTrayMenu.DllImports
|
|||
|
||||
// Takes a STRRET structure returned by IShellFolder::GetDisplayNameOf, converts it to a string, and places the result in a buffer.
|
||||
[DllImport("shlwapi.dll", EntryPoint = "StrRetToBuf", ExactSpelling = false, SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern int StrRetToBuf(IntPtr pstr, IntPtr pidl, StringBuilder pszBuf, int cchBuf);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ namespace SystemTrayMenu.DllImports
|
|||
[Flags]
|
||||
internal enum TPM : uint
|
||||
{
|
||||
#pragma warning disable SA1602 // Enumeration items should be documented
|
||||
LEFTBUTTON = 0x0000, // LEFTALIGN = 0x0000, // TOPALIGN = 0x0000, // HORIZONTAL = 0x0000,
|
||||
RIGHTBUTTON = 0x0002,
|
||||
CENTERALIGN = 0x0004,
|
||||
|
@ -35,7 +34,6 @@ namespace SystemTrayMenu.DllImports
|
|||
VERNEGANIMATION = 0x2000,
|
||||
NOANIMATION = 0x4000,
|
||||
LAYOUTRTL = 0x8000,
|
||||
#pragma warning restore SA1602 // Enumeration items should be documented
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -55,7 +53,7 @@ namespace SystemTrayMenu.DllImports
|
|||
|
||||
// The TrackPopupMenuEx function displays a shortcut menu at the specified location and tracks the selection of items on the shortcut menu. The shortcut menu can appear anywhere on the screen.
|
||||
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
|
||||
private static extern uint TrackPopupMenuEx(IntPtr hmenu, TPM flags, int x, int y, IntPtr hwnd, IntPtr lptpm);
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 241 B After Width: | Height: | Size: 170 B |
Before Width: | Height: | Size: 305 B After Width: | Height: | Size: 182 B |
Before Width: | Height: | Size: 377 B After Width: | Height: | Size: 213 B |
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 444 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 860 B |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 472 KiB After Width: | Height: | Size: 267 KiB |
BIN
Packaging/Images/SplashScreen.scale-400.xcf
Normal file
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 4 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 732 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 732 B |
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 637 B After Width: | Height: | Size: 517 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 883 B |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 1.9 KiB |