1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-09-14 19:32:10 +02:00

Compare commits

...

88 Commits

Author SHA1 Message Date
6040337bb4 Release 1.21.2 2022-01-22 14:38:27 +01:00
1ced72388b Add option to hide tweets by users with NFT avatars 2022-01-22 14:27:15 +01:00
4751a948e7 Fix JSDoc issues 2022-01-22 02:00:25 +01:00
3939c2263a Move some options from the General tab to the Advanced tab 2022-01-21 13:22:14 +01:00
b0ba4595ae Remove unnecessary 'internal' keyword on classes 2022-01-21 10:55:50 +01:00
38b1057a4c Fix downloading images from DMs 2022-01-21 10:55:50 +01:00
af5d785ff2 Move IScriptExecutor.RunFunction into an extension method 2022-01-18 15:07:55 +01:00
655d334714 Fix login session export not working across different computers after a Chromium update 2022-01-18 14:35:37 +01:00
eee72959e6 Release 1.21.1 2022-01-18 11:15:35 +01:00
89b8977f7d Fix not setting custom scheme response status text correctly 2022-01-18 11:15:35 +01:00
9ede2e1ccc Move some settings from user config to system config (touch adjustment, color profile detection, system proxy)
Closes #327
2022-01-17 23:59:43 +01:00
03d1bc0f4c Remove option for 6 columns on screen in 'Edit layout & design' to reduce drop-down height, since custom values are now possible 2022-01-17 23:05:10 +01:00
cde9f66111 Add option for a custom number of visible columns in 'Edit layout & design' plugin
Closes #322
2022-01-17 22:58:53 +01:00
8149ed50e1 Reformat plugin code 2022-01-17 22:32:01 +01:00
24f5075116 Fix clicking 'Play' in 'Options - Sounds' not playing notification if notifications are muted
Closes #326
2022-01-17 21:03:46 +01:00
2a636245b4 Remove directives to expose assembly internals to now removed test projects 2022-01-17 20:51:28 +01:00
3f4844f6f6 Add numbered lines to example notification to make adjusting scroll speed easier 2022-01-17 20:46:33 +01:00
29308de3ee Fix .csproj issues 2022-01-17 07:22:44 +01:00
0d3d744d94 Move custom build script from F# to MSBuild directives and Powershell 2022-01-17 02:20:19 +01:00
d38e525fed Fix exceptions during app launch not showing error message dialogs 2022-01-16 18:28:48 +01:00
e2ac38ed0b Disable CefSharp's default parent process monitor 2022-01-16 17:49:55 +01:00
fa534f9eb3 Work on abstracting app logic and making some implementation optional 2022-01-16 17:49:55 +01:00
ec7827df24 Fix compile errors in Release configuration 2022-01-08 14:07:17 +01:00
b915488651 Add command line argument to use http:// for video playback in case WMP has issues with https:// 2022-01-08 13:54:34 +01:00
bf9a0226be Major refactor to abstract app logic into libraries 2022-01-08 13:50:21 +01:00
68582f6973 Fix not disposing frame object when handling key events 2022-01-08 05:44:24 +01:00
03f3d4d450 Fix compile error in FormBrowser for Release builds 2022-01-01 19:53:55 +01:00
115428ec50 Fix popups for Google & Apple sign-in 2022-01-01 19:53:36 +01:00
5f60852fbb Fix broken forced redirect from plain twitter.com to TweetDeck 2022-01-01 19:44:01 +01:00
a7a5723c4b Revert removal of default browser background color 2021-12-30 10:40:30 +01:00
17e42df42d Minor tweak to load error handling 2021-12-30 10:25:47 +01:00
7e692460d8 Hide VC++ redist files from project 2021-12-30 10:24:24 +01:00
f41a5946e4 Reorganize libraries and unit tests 2021-12-28 15:40:45 +01:00
29fee155d7 Release 1.21.0.1 2021-12-25 19:22:34 +01:00
32728fc20a Fix JS error when hiding update notification 2021-12-25 19:22:34 +01:00
394cb80022 Fix custom CSS and sound notification settings not applying on launch 2021-12-25 19:09:22 +01:00
21e49505d1 Release 1.21 2021-12-25 08:06:21 +01:00
e0025e02d1 Add option to use system proxy for all connections
Closes #319
2021-12-25 08:06:21 +01:00
4a9590c1d9 Disable color profile detection by default & add option to re-enable it 2021-12-25 08:06:20 +01:00
7de0c50387 Apply default background color only to Twitter and TweetDeck websites 2021-12-25 05:52:59 +01:00
ccb87351c1 Fix notifications having outdated theme-related attributes on the <html> element 2021-12-24 15:50:01 +01:00
273e7266eb Fix bootstrap script issues (wrong ID and stylesheets appearing in notifications) 2021-12-24 15:31:10 +01:00
427975e5ce Automatically update relative time shown in notifications 2021-12-24 14:54:58 +01:00
adb304b6a2 Remove section on Dev Tools in the guide 2021-12-24 12:38:15 +01:00
3a89a28f8b Move the official guide directly into the app 2021-12-24 11:30:42 +01:00
cff93dcc97 Remove scripts folder from pre-build script 2021-12-24 11:00:12 +01:00
dfde38ea3b Fix screenshotting wrong tweet if cursor moves away from right-clicked tweet too quickly 2021-12-24 10:14:12 +01:00
a8e7f065cf Rewrite screenshot functionality using new DevTools API 2021-12-24 09:12:46 +01:00
5ebfc67e48 Replace ScriptLoader & reimplement resource hot swap 2021-12-24 06:41:34 +01:00
57b03baad9 Add in-memory caching to td:// and tdp:// schemes 2021-12-23 18:26:42 +01:00
c91f1d0e5e Remove "scripts" folder and JS/CSS minification 2021-12-23 18:22:21 +01:00
6a421292b3 Refactor notification bootstrapping 2021-12-23 16:00:35 +01:00
ceae748503 Fix the possibility of plugin functions running in wrong order if main module loading gets delayed 2021-12-23 05:23:02 +01:00
008de87e55 Refactor plugin bootstrapping 2021-12-23 05:02:53 +01:00
e7479ef9e3 Fix missing module import 2021-12-22 13:25:03 +01:00
13f8f12ac8 Refactor error page 2021-12-22 10:05:01 +01:00
fd634379d7 Improve resistance to errors in bootstrapping script and modules 2021-12-22 08:49:00 +01:00
e854315a81 Reorganize resources folder 2021-12-22 08:37:45 +01:00
86136d7692 Refactor update checker as a bootstrapped module 2021-12-22 07:53:52 +01:00
db251bfdfd Add new resources folder to installers & remove old CEF files on update 2021-12-22 07:25:39 +01:00
9d39f26d54 Refactor introduction dialog as a bootstrapped module 2021-12-22 06:40:14 +01:00
9cd813c02c Refactor login page scripts & styles into modules and remove stuff broken by Twitter updates 2021-12-22 06:35:47 +01:00
8e1f87e062 Expand module bootstrapping mechanism 2021-12-22 06:35:47 +01:00
dda954285c Refactor main browser CSS injections 2021-12-22 05:08:04 +01:00
ed4f7b6b72 Refactor main browser code into JS modules 2021-12-22 03:00:14 +01:00
7239dcf4d2 Add td:// scheme for modularized resources 2021-12-21 12:48:45 +01:00
901cca268e Minor refactoring of custom URL schemes 2021-12-21 12:48:45 +01:00
b909341988 Delete devtools_resources.pak when updating 2021-12-21 12:48:45 +01:00
3e607ae0fe Fix JS script execution after updating CefSharp 2021-12-21 12:27:29 +01:00
587060f73c Update Rider project settings 2021-12-21 12:27:28 +01:00
3ea6f6ac18 Make opening dev tools via context menu immediately focus right-clicked element 2021-12-18 03:13:18 +01:00
c162761464 Reduce minification of resource files 2021-12-18 01:49:01 +01:00
994f9635ef Fix post build scripts to support moved F# executables 2021-12-18 01:29:31 +01:00
ab8752845d Fix importing/exporting login session after CEF updates 2021-12-17 23:48:02 +01:00
a94ee2fe4b Update CefSharp to 96 & add VC++ 2019 and UCRT DLLs to the repository 2021-12-17 23:48:02 +01:00
763c999b09 Remove analytics 2021-12-17 20:27:48 +01:00
b18cd2658c Fix source code indentation problems 2021-12-17 01:46:08 +01:00
d9782f554f Move TweetLib.Communication library to .NET Standard 2.0 2021-12-17 00:38:40 +01:00
f1d9663709 Update CefSharp to 92 2021-12-16 23:31:40 +01:00
bcf77052a5 Reformat solution & setup Rider project 2021-08-07 09:41:00 +02:00
85d15b32e9 Release 1.20 2021-02-03 04:27:46 +01:00
3d3b695c85 Fix broken login page after Twitter changed it
Closes #313
2021-02-03 03:52:00 +01:00
88b7cac298 Add Rider module 2021-02-03 03:51:02 +01:00
c7efa631e1 Enforce LF line endings 2021-02-01 00:39:45 +01:00
aeb90cbb1a Update CefSharp to 86 2021-02-01 00:00:34 +01:00
ae1c59847f Refactor locking mechanism & improve error reporting for failed locks 2020-06-16 00:28:35 +02:00
651d9be57c Release 1.19.0.2 2020-06-14 15:54:19 +02:00
eeb32db6fb Work around CEF tooltip showing for links 2020-06-14 15:22:58 +02:00
500 changed files with 22211 additions and 19207 deletions

4
.gitattributes vendored
View File

@@ -1,5 +1,3 @@
# Auto detect text files and perform LF normalization # Auto detect text files and perform LF normalization
* text=auto * text=auto eof=lf
*.txt text eof=lf
*.cs diff=csharp *.cs diff=csharp

7
.gitignore vendored
View File

@@ -5,8 +5,14 @@
bld/* bld/*
!bld/*.iss !bld/*.iss
!bld/*.bat !bld/*.bat
!bld/*.ps1
!bld/Redist
!bld/Resources !bld/Resources
# Rider
.idea/.idea.TweetDuck/.idea/dictionaries
.idea/.idea.TweetDuck/.idea/misc.xml
# User-specific files # User-specific files
*.suo *.suo
*.user *.user
@@ -140,4 +146,3 @@ _UpgradeReport_Files/
Backup*/ Backup*/
UpgradeLog*.XML UpgradeLog*.XML
UpgradeLog*.htm UpgradeLog*.htm

8
.idea/.idea.TweetDuck/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/projectSettingsUpdater.xml
/modules.xml
/contentModel.xml
/.idea.TweetDuck.iml

View File

@@ -0,0 +1,450 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="AUTODETECT_INDENTS" value="false" />
<option name="OTHER_INDENT_OPTIONS">
<value>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</value>
</option>
<option name="LINE_SEPARATOR" value="&#10;" />
<option name="RIGHT_MARGIN" value="999" />
<option name="FORMATTER_TAGS_ENABLED" value="true" />
<CssCodeStyleSettings>
<option name="HEX_COLOR_LOWER_CASE" value="true" />
</CssCodeStyleSettings>
<DB2CodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</DB2CodeStyleSettings>
<DerbyCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</DerbyCodeStyleSettings>
<GoCodeStyleSettings>
<option name="MOVE_ALL_STDLIB_IMPORTS_IN_ONE_GROUP" value="true" />
<option name="GROUP_STDLIB_IMPORTS" value="true" />
<option name="WRAP_COMP_LIT" value="5" />
<option name="WRAP_FUNC_PARAMS" value="5" />
<option name="WRAP_FUNC_RESULT" value="5" />
</GoCodeStyleSettings>
<H2CodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</H2CodeStyleSettings>
<H2CodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</H2CodeStyleSettings>
<HSQLCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</HSQLCodeStyleSettings>
<HTMLCodeStyleSettings>
<option name="HTML_ALIGN_TEXT" value="true" />
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_ELEMENTS_TO_REMOVE_NEW_LINE_BEFORE" value="" />
<option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACKETS" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="USE_EXPLICIT_JS_EXTENSION" value="TRUE" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
<option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
<option name="IMPORT_SORT_MODULE_NAME" value="true" />
</JSCodeStyleSettings>
<JSON>
<option name="OBJECT_WRAPPING" value="5" />
<option name="ARRAY_WRAPPING" value="5" />
</JSON>
<JavaCodeStyleSettings>
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="false" />
<package name="javax" withSubpackages="true" static="false" />
<package name="java" withSubpackages="true" static="false" />
<package name="" withSubpackages="true" static="true" />
</value>
</option>
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
</value>
</option>
<option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="IMPORT_NESTED_CLASSES" value="true" />
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
<option name="ALLOW_TRAILING_COMMA" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<LessCodeStyleSettings>
<option name="HEX_COLOR_LOWER_CASE" value="true" />
</LessCodeStyleSettings>
<MSSQLCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</MSSQLCodeStyleSettings>
<MySQLCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</MySQLCodeStyleSettings>
<Objective-C>
<option name="INDENT_DIRECTIVE_AS_CODE" value="true" />
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
<option name="KEEP_CASE_EXPRESSIONS_IN_ONE_LINE" value="true" />
<option name="SPACE_BEFORE_INIT_LIST" value="true" />
<option name="SPACE_AFTER_DICTIONARY_LITERAL_COLON" value="false" />
</Objective-C>
<OracleCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</OracleCodeStyleSettings>
<PHPCodeStyleSettings>
<option name="ALIGN_KEY_VALUE_PAIRS" value="true" />
<option name="CONCAT_SPACES" value="false" />
<option name="COMMA_AFTER_LAST_ARRAY_ELEMENT" value="true" />
<option name="PHPDOC_BLANK_LINE_BEFORE_TAGS" value="true" />
<option name="LOWER_CASE_BOOLEAN_CONST" value="true" />
<option name="LOWER_CASE_NULL_CONST" value="true" />
<option name="ELSE_IF_STYLE" value="COMBINE" />
<option name="VARIABLE_NAMING_STYLE" value="SNAKE_CASE" />
<option name="KEEP_BLANK_LINES_AFTER_LBRACE" value="0" />
<option name="SPACE_BEFORE_CLOSURE_LEFT_PARENTHESIS" value="false" />
<option name="FORCE_SHORT_DECLARATION_ARRAY_STYLE" value="true" />
<option name="NEW_LINE_AFTER_PHP_OPENING_TAG" value="true" />
<option name="SPACE_AROUND_ASSIGNMENT_IN_DECLARE" value="true" />
</PHPCodeStyleSettings>
<PostgresCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</PostgresCodeStyleSettings>
<Properties>
<option name="KEEP_BLANK_LINES" value="true" />
</Properties>
<Python>
<option name="SPACE_AROUND_EQ_IN_NAMED_PARAMETER" value="true" />
<option name="SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT" value="true" />
<option name="NEW_LINE_AFTER_COLON" value="true" />
<option name="DICT_WRAPPING" value="5" />
<option name="DICT_NEW_LINE_AFTER_LEFT_BRACE" value="true" />
<option name="DICT_NEW_LINE_BEFORE_RIGHT_BRACE" value="true" />
</Python>
<RsCodeStyleSettings>
<option name="ALIGN_RET_TYPE" value="false" />
<option name="ALIGN_TYPE_PARAMS" value="true" />
<option name="ALLOW_ONE_LINE_MATCH" value="true" />
<option name="SPACE_AROUND_ASSOC_TYPE_BINDING" value="true" />
</RsCodeStyleSettings>
<Ruby>
<option name="INDENT_PRIVATE_METHODS" value="true" />
<option name="INDENT_PROTECTED_METHODS" value="true" />
<option name="INDENT_PUBLIC_METHODS" value="true" />
<option name="INDENT_WHEN_CASES" value="true" />
<option name="CHAIN_CALLS_ALIGNMENT" value="2" />
</Ruby>
<SQLiteCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</SQLiteCodeStyleSettings>
<ScssCodeStyleSettings>
<option name="HEX_COLOR_LOWER_CASE" value="true" />
</ScssCodeStyleSettings>
<SqlCodeStyleSettings version="6">
<option name="KEYWORD_CASE" value="2" />
<option name="TYPE_CASE" value="2" />
<option name="CUSTOM_TYPE_CASE" value="2" />
<option name="SUBQUERY_CONTENT" value="1" />
<option name="SUBQUERY_CLOSING" value="1" />
<option name="INSERT_TABLE_EL_LINE" value="0" />
<option name="INSERT_EL_WRAP" value="2" />
<option name="SET_EL_WRAP" value="2" />
<option name="SET_ALIGN_EQUAL_SIGN" value="false" />
<option name="FROM_EL_WRAP" value="2" />
<option name="FROM_ALIGN_JOIN_TABLES" value="true" />
<option name="FROM_INDENT_JOIN" value="false" />
<option name="FROM_ONLY_JOIN_INDENT" value="2" />
<option name="WHERE_EL_WRAP" value="2" />
<option name="TABLE_OPENING" value="1" />
<option name="TABLE_CONTENT" value="2" />
<option name="TABLE_CLOSING" value="3" />
<option name="TABLE_DEFAULTS_ALIGN" value="false" />
<option name="TABLE_NULLABILITIES_ALIGN" value="false" />
<option name="CONSTRAINT_WRAP_1" value="false" />
<option name="CONSTRAINT_WRAP_3" value="true" />
<option name="CONSTRAINT_WRAP_4" value="true" />
<option name="VIEW_INDENT_QUERY" value="true" />
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
</SqlCodeStyleSettings>
<SybaseCodeStyleSettings version="6">
<option name="USE_GENERIC_STYLE" value="true" />
</SybaseCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
<option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
</TypeScriptCodeStyleSettings>
<XML>
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</XML>
<codeStyleSettings language="CMake">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="CSS">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Groovy">
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="HTML">
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="RIGHT_MARGIN" value="999" />
<option name="BLANK_LINES_AFTER_PACKAGE" value="0" />
<option name="BLANK_LINES_BEFORE_IMPORTS" value="0" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="SPACE_AFTER_TYPE_CAST" value="false" />
<option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JSON">
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="SPACE_WITHIN_BRACKETS" value="true" />
<option name="SPACE_WITHIN_BRACES" value="true" />
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="LESS">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Lua">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="ObjectiveC">
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="PHP">
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
<option name="CLASS_BRACE_STYLE" value="1" />
<option name="METHOD_BRACE_STYLE" value="1" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="SPECIAL_ELSE_IF_TREATMENT" value="true" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Puppet">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Python">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="RHTML">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Rust">
<option name="RIGHT_MARGIN" value="140" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="SASS">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="SCSS">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="SQL">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Shell Script">
<indentOptions>
<option name="INDENT_SIZE" value="4" />
<option name="TAB_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TOML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="go">
<option name="CALL_PARAMETERS_WRAP" value="5" />
<indentOptions>
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="METHOD_ANNOTATION_WRAP" value="0" />
<option name="CLASS_ANNOTATION_WRAP" value="0" />
<option name="FIELD_ANNOTATION_WRAP" value="0" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="liquid">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="ruby">
<option name="SPACE_WITHIN_BRACES" value="true" />
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="yaml">
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@@ -0,0 +1,770 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project" />
<inspection_tool class="AccessToNonThreadSafeStaticFieldFromInstance" enabled="true" level="WARNING" enabled_by_default="true">
<option name="nonThreadSafeClasses">
<value />
</option>
<option name="nonThreadSafeTypes" value="" />
</inspection_tool>
<inspection_tool class="AccessToStaticFieldLockedOnInstance" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AddOperatorModifier" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="AmbiguousFieldAccess" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AmbiguousMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AnonymousInnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ArrayEquality" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssertEqualsCalledOnArray" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssertsWithoutMessagesTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentOrReturnOfFieldWithMutableType" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentToCatchBlockParameter" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentToLambdaParameter" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentToMethodParameter" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreTransformationOfOriginalParameter" value="true" />
</inspection_tool>
<inspection_tool class="AssignmentToStaticFieldFromInstanceMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AutoBoxing" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAddedToCollection" value="false" />
</inspection_tool>
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredTypes" value="java.util.stream.Stream,java.util.stream.IntStream,java.util.stream.LongStream,java.util.stream.DoubleStream,net.minecraft.client.Minecraft,net.minecraft.client.MainWindow" />
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,net.minecraft.client.MinecraftClient,getInstance|getWindow" />
</inspection_tool>
<inspection_tool class="AutoUnboxing" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AwaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AwaitWithoutCorrespondingSignal" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="BadOddness" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="BigDecimalEquals" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="BigDecimalLegacyMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="BooleanExpressionMayBeConditional" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CallToNativeMethodWhileLocked" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CallableParameterUseCaseInTypeContextInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CascadeStringReplacementInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="USE_SHORT_ARRAYS_SYNTAX" value="true" />
</inspection_tool>
<inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CastToIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ChainedEquality" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ChannelResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="insideTryAllowed" value="false" />
</inspection_tool>
<inspection_tool class="ClassIndependentOfModule" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassLoaderInstantiation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassMayBeInterface" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="reportClassesWithNonAbstractMethods" value="true" />
</inspection_tool>
<inspection_tool class="ClassMethodNameMatchesFieldNameInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassNewInstance" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassOnlyUsedInOneModule" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassOnlyUsedInOnePackage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassOverridesFieldOfSuperClassInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="REPORT_PRIVATE_REDEFINITION" value="false" />
</inspection_tool>
<inspection_tool class="ClassUnconnectedToPackage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassWithOnlyPrivateConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CloneableClassInSecureContext" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CollectionContainsUrl" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CollectionsFieldAccessReplaceableByMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ComparableImplementedButEqualsNotOverridden" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ComparatorNotSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CompareToUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ComposeMissingKeys" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="ConditionSignal" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConditionalExpression" enabled="true" level="INFORMATION" enabled_by_default="true">
<option name="ignoreSimpleAssignmentsAndReturns" value="true" />
</inspection_tool>
<inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConfusingElse" enabled="false" level="WEAK WARNING" enabled_by_default="false">
<option name="reportWhenNoStatementFollow" value="true" />
</inspection_tool>
<inspection_tool class="ConfusingMainMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConfusingOctalEscape" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConfusingPlusesOrMinusesJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConstantJUnitAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConstantMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConstantTestNGAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ConvertJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConvertLambdaToReference" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ConvertOldAnnotations" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CssConvertColorToHexInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="CssConvertColorToRgbInspection" enabled="true" level="INFORMATION" enabled_by_default="true" />
<inspection_tool class="CssMissingSemicolon" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CssReplaceWithShorthandUnsafely" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="CustomClassloader" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CustomSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CyclicClassDependency" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CyclicPackageDependency" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DateToString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DisallowWritingIntoStaticPropertiesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="DisconnectedForeachInstructionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="DisjointPackage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DivideByZeroJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DocumentWriteJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DoubleBraceInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DoubleCheckedLocking" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreOnVolatileVariables" value="false" />
</inspection_tool>
<inspection_tool class="DriverManagerGetConnection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DuplicateBooleanBranch" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DuplicateConditionJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DynamicallyGeneratedCodeJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ES6ConvertIndexedForToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ES6ConvertLetToConst" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ES6ConvertToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ES6TopLevelAwaitExpression" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="EmptyDirectory" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="EmptyStatementBody" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_reportEmptyBlocks" value="true" />
<option name="commentsAreContent" value="true" />
</inspection_tool>
<inspection_tool class="EmptySynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="EnumSwitchStatementWhichMissesCases" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoreSwitchStatementsWithDefault" value="true" />
</inspection_tool>
<inspection_tool class="EqualsCalledOnEnumConstant" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="EqualsHashCodeCalledOnUrl" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="EqualsUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ErrorRethrown" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExceptionNameDoesntEndWithException" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExceptionPackage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExpectedExceptionNeverThrownTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExplicitArgumentCanBeLambda" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ExtendsThread" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExtendsThrowable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExternalizableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FallthruInSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FieldAccessedSynchronizedAndUnsynchronized" enabled="true" level="WARNING" enabled_by_default="true">
<option name="countGettersAndSetters" value="false" />
</inspection_tool>
<inspection_tool class="FieldDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="FieldHidesSuperclassField" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreInvisibleFields" value="false" />
<option name="ignoreStaticFields" value="false" />
</inspection_tool>
<inspection_tool class="FieldMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="Finalize" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreTrivialFinalizers" value="true" />
</inspection_tool>
<inspection_tool class="FinalizeNotProtected" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FixedTimeStartWithInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FloatingPointEquality" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FoldInitializerAndIfToElvis" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="ForLoopThatDoesntUseLoopVariableJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ForgottenDebugOutputInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="configuration">
<list>
<option value="\Codeception\Util\Debug::debug" />
<option value="\Codeception\Util\Debug::pause" />
<option value="\Doctrine\Common\Util\Debug::dump" />
<option value="\Doctrine\Common\Util\Debug::export" />
<option value="\Illuminate\Support\Debug\Dumper::dump" />
<option value="\Symfony\Component\Debug\Debug::enable" />
<option value="\Symfony\Component\Debug\DebugClassLoader::enable" />
<option value="\Symfony\Component\Debug\ErrorHandler::register" />
<option value="\Symfony\Component\Debug\ExceptionHandler::register" />
<option value="\TYPO3\CMS\Core\Utility\DebugUtility::debug" />
<option value="\Zend\Debug\Debug::dump" />
<option value="\Zend\Di\Display\Console::export" />
<option value="dd" />
<option value="debug_print_backtrace" />
<option value="debug_zval_dump" />
<option value="dpm" />
<option value="dpq" />
<option value="dsm" />
<option value="dump" />
<option value="dvm" />
<option value="error_log" />
<option value="kpr" />
<option value="phpinfo" />
<option value="print_r" />
<option value="var_dump" />
<option value="var_export" />
<option value="wp_die" />
<option value="xdebug_break" />
<option value="xdebug_call_class" />
<option value="xdebug_call_file" />
<option value="xdebug_call_function" />
<option value="xdebug_call_line" />
<option value="xdebug_code_coverage_started" />
<option value="xdebug_debug_zval" />
<option value="xdebug_debug_zval_stdout" />
<option value="xdebug_dump_superglobals" />
<option value="xdebug_enable" />
<option value="xdebug_get_code_coverage" />
<option value="xdebug_get_collected_errors" />
<option value="xdebug_get_declared_vars" />
<option value="xdebug_get_function_stack" />
<option value="xdebug_get_headers" />
<option value="xdebug_get_monitored_functions" />
<option value="xdebug_get_profiler_filename" />
<option value="xdebug_get_stack_depth" />
<option value="xdebug_get_tracefile_name" />
<option value="xdebug_is_enabled" />
<option value="xdebug_memory_usage" />
<option value="xdebug_peak_memory_usage" />
<option value="xdebug_print_function_stack" />
<option value="xdebug_start_code_coverage" />
<option value="xdebug_start_error_collection" />
<option value="xdebug_start_function_monitor" />
<option value="xdebug_start_trace" />
<option value="xdebug_stop_code_coverage" />
<option value="xdebug_stop_error_collection" />
<option value="xdebug_stop_function_monitor" />
<option value="xdebug_stop_trace" />
<option value="xdebug_time_index" />
<option value="xdebug_var_dump" />
</list>
</option>
<option name="migratedIntoUserSpace" value="true" />
</inspection_tool>
<inspection_tool class="FunctionNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_regex" value="[a-z][A-Za-z]*" />
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="99" />
</inspection_tool>
<inspection_tool class="FunctionWithInconsistentReturnsJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="HashCodeUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="HibernateResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="insideTryAllowed" value="false" />
</inspection_tool>
<inspection_tool class="HtmlMissingClosingTag" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="HtmlPresentationalElement" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="HtmlRequiredTitleAttribute" enabled="true" level="INFORMATION" enabled_by_default="true" />
<inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
<option name="insideTryAllowed" value="false" />
</inspection_tool>
<inspection_tool class="IfStatementWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ImplicitDefaultCharsetUsage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InconsistentLanguageLevel" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="IncrementDecrementOperationEquivalentInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="InnerClassOnInterface" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreInnerInterfaces" value="true" />
</inspection_tool>
<inspection_tool class="InnerClassReferencedViaSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InnerClassVariableHidesOuterClassVariable" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreInvisibleFields" value="false" />
</inspection_tool>
<inspection_tool class="InsertLiteralUnderscores" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="InstanceofCatchParameter" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InstanceofIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InstanceofThis" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InterfaceMayBeAnnotatedFunctional" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="IsEmptyFunctionUsageInspection" enabled="false" level="WARNING" enabled_by_default="false">
<option name="SUGGEST_TO_USE_COUNT_CHECK" value="true" />
</inspection_tool>
<inspection_tool class="IsNullFunctionUsageInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="IteratorNextDoesNotThrowNoSuchElementException" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JDBCExecuteWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JDBCPrepareStatementWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JDBCResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="insideTryAllowed" value="false" />
</inspection_tool>
<inspection_tool class="JNDIResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="insideTryAllowed" value="false" />
</inspection_tool>
<inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="JSClassNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="99" />
</inspection_tool>
<inspection_tool class="JSConstructorReturnsPrimitive" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JSEqualityComparisonWithCoercion.TS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="mySeverity" value="Always" />
</inspection_tool>
<inspection_tool class="JSJoinVariableDeclarationAndAssignment" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="JSMissingSwitchBranches" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="JSNonASCIINames" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myAllowOnlyAscii" value="true" />
</inspection_tool>
<inspection_tool class="JSNonStrictModeUsed" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JSOctalInteger" enabled="true" level="ERROR" enabled_by_default="true">
<option name="myReportNonStrictEs5" value="true" />
</inspection_tool>
<inspection_tool class="JSUndeclaredVariable" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="myCheckGlobalDefinitions" value="true" />
</inspection_tool>
<inspection_tool class="JSUnusedGlobalSymbols" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myReportUnusedDefinitions" value="true" />
<option name="myReportUnusedProperties" value="true" />
</inspection_tool>
<inspection_tool class="JUnitDatapoint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JUnitRule" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JUnitTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JavadocHtmlLint" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="JoinDeclarationAndAssignmentJava" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="LambdaCanBeMethodCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="LengthOneStringInIndexOf" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="LengthOneStringsInConcatenation" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="LoadLibraryWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
<option name="REPORT_VARIABLES" value="true" />
<option name="REPORT_PARAMETERS" value="true" />
</inspection_tool>
<inspection_tool class="LocalVariableDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="LocalVariableNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_regex" value="[a-z][A-Za-z]*" />
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="99" />
</inspection_tool>
<inspection_tool class="MalformedSetUpTearDown" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MapReplaceableByEnumMap" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MethodCallSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="MethodMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_onlyPrivateOrFinal" value="false" />
<option name="m_ignoreEmptyMethods" value="true" />
</inspection_tool>
<inspection_tool class="MethodMayBeSynchronized" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MethodOverloadsParentMethod" enabled="true" level="WARNING" enabled_by_default="true">
<option name="reportIncompatibleParameters" value="true" />
</inspection_tool>
<inspection_tool class="MethodOverridesInaccessibleMethodOfSuper" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MethodOverridesStaticMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MethodSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="MisorderedAssertEqualsArgumentsTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MisorderedAssertEqualsParameters" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MisorderedModifiersInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreObjectMethods" value="false" />
<option name="ignoreAnonymousClassMethods" value="false" />
</inspection_tool>
<inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_requireAnnotationsFirst" value="true" />
</inspection_tool>
<inspection_tool class="MultipleTopLevelClassesInFile" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NakedNotify" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NegatedConditional" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreNegatedNullComparison" value="true" />
</inspection_tool>
<inspection_tool class="NestedAssignment" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NestedClassSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="NestedConditionalExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NestedSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NestedSynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NestedTernaryOperatorInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NewExpressionSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="NonBlockStatementBodyJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonExceptionNameEndsWithException" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonFinalClone" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonFinalFieldInEnum" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonFinalFieldOfException" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonFinalUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonReproducibleMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonSerializableFieldInSerializableClass" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignorableAnnotations">
<value />
</option>
<option name="ignoreAnonymousInnerClasses" value="false" />
<option name="superClassString" value="java.awt.Component" />
</inspection_tool>
<inspection_tool class="NonSerializableObjectBoundToHttpSession" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonSerializableObjectPassedToObjectStream" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonSerializableWithSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonSerializableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonShortCircuitBoolean" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonSynchronizedMethodOverridesSynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NonThreadSafeLazyInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NotOptimalIfConditionsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="REPORT_DUPLICATE_CONDITIONS" value="false" />
<option name="SUGGEST_OPTIMIZING_CONDITIONS" value="false" />
</inspection_tool>
<inspection_tool class="NotifyCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NotifyWithoutCorrespondingWait" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NullThrown" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NumericToString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="OCInconsistentNaming" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ObjectAllocationIgnoredJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ObjectInstantiationInEqualsHashCode" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ObjectNotify" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ObjectToString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ObsoleteCollection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreRequiredObsoleteCollectionTypes" value="true" />
</inspection_tool>
<inspection_tool class="OctalAndDecimalIntegersMixed" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="OffsetOperationsInspection" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="OneTimeUseVariablesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ALLOW_LONG_STATEMENTS" value="true" />
</inspection_tool>
<inspection_tool class="OverloadedVarargsMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PackageInMultipleModules" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ParameterHidingMemberVariable" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreInvisibleFields" value="true" />
<option name="m_ignoreStaticMethodParametersHidingInstanceFields" value="true" />
<option name="m_ignoreForConstructors" value="true" />
<option name="m_ignoreForPropertySetters" value="true" />
<option name="m_ignoreForAbstractMethods" value="false" />
</inspection_tool>
<inspection_tool class="ParameterNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_regex" value="[a-z][A-Za-z]*" />
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="99" />
</inspection_tool>
<inspection_tool class="ParameterizedParametersStaticCollection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpAssignmentInConditionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpAssignmentReplaceableWithOperatorAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpAssignmentReplaceableWithPrefixExpressionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpCSValidationInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
<option name="EXTENSIONS" value="php,js,css,inc" />
</inspection_tool>
<inspection_tool class="PhpClassNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="0" />
</inspection_tool>
<inspection_tool class="PhpClosureCanBeConvertedToShortArrowFunctionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpCompoundNamespaceDepthInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpConstantNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="0" />
</inspection_tool>
<inspection_tool class="PhpConstantReassignmentInspection" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="PhpDivisionByZeroInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpDocMissingThrowsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="SKIP_ON_EMPTY_PHPDOC" value="false" />
</inspection_tool>
<inspection_tool class="PhpFunctionNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="0" />
</inspection_tool>
<inspection_tool class="PhpInconsistentReturnPointsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ALLOW_RETURN_NULL_IN_VOID" value="false" />
</inspection_tool>
<inspection_tool class="PhpLongTypeFormInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpLoopCanBeConvertedToArrayFillInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpLoopCanBeConvertedToArrayFilterInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpLoopCanBeConvertedToArrayMapInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpMethodNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="0" />
</inspection_tool>
<inspection_tool class="PhpMethodOrClassCallIsNotCaseSensitiveInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpMissingParentCallMagicInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpMissingReturnTypeInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpMissingStrictTypesDeclarationInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpMissingVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpNewClassMissingParameterListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpNonCanonicalElementsOrderInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpOverridingMethodVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpPropertyNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_regex" value="[a-z][_a-z\d]*" />
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="0" />
</inspection_tool>
<inspection_tool class="PhpRedundantClosingTagInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PhpSeparateElseIfInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpShortOpenTagInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpSingleStatementWithBracesInspection" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="PhpStatementHasEmptyBodyInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myCommentsCountAsContent" value="true" />
</inspection_tool>
<inspection_tool class="PhpStatementWithoutBracesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpStaticAsDynamicMethodCallInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="SHOW_FOR_MAGIC" value="false" />
</inspection_tool>
<inspection_tool class="PhpTraditionalSyntaxArrayLiteralInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpTraitsUseListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpUndefinedCallbackInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PhpUndefinedClassConstantInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="DOWNGRADE_SEVERITY" value="true" />
</inspection_tool>
<inspection_tool class="PhpUndefinedMethodInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="DOWNGRADE_SEVERITY" value="true" />
</inspection_tool>
<inspection_tool class="PhpUnused" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="SHOW_UNUSED_BY_ENTRIES" value="false" />
</inspection_tool>
<inspection_tool class="PhpUnusedParameterInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="DONT_REPORT_ANONYMOUS" value="true" />
</inspection_tool>
<inspection_tool class="PhpVarUsageInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpVariableNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_minLength" value="0" />
<option name="m_maxLength" value="0" />
</inspection_tool>
<inspection_tool class="PhpVariableVariableInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PointlessBitwiseExpressionJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreExpressionsContainingConstants" value="false" />
</inspection_tool>
<inspection_tool class="ProblematicVarargsMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ProblematicWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PropertyCanBeStaticInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ProtectedMemberInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PublicFieldAccessedInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PublicStaticArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PublicStaticCollectionField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyAugmentAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PyClassicStyleClassInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyMissingTypeHintsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="m_onlyWhenTypesAreKnown" value="false" />
</inspection_tool>
<inspection_tool class="RawTypeCanBeGeneric" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReadObjectAndWriteObjectPrivate" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ReadObjectInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ReadResolveAndWriteReplaceProtected" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RedundantElseClauseInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantImplements" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreSerializable" value="true" />
<option name="ignoreCloneable" value="true" />
</inspection_tool>
<inspection_tool class="RedundantObjectTypeCheck" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="RedundantSuppression" enabled="true" level="WARNING" enabled_by_default="true">
<option name="IGNORE_ALL" value="true" />
</inspection_tool>
<inspection_tool class="ReferencingObjectsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RegExpOctalEscape" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceAssignmentWithOperatorAssignment" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreLazyOperators" value="true" />
<option name="ignoreObscureOperators" value="true" />
</inspection_tool>
<inspection_tool class="ReplaceAssignmentWithOperatorAssignmentJS" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceCollectionCountWithSize" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceGuardClauseWithFunctionCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceStringFormatWithLiteral" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceSubstringWithDropLast" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceSubstringWithIndexingOperation" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceSubstringWithSubstringAfter" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceSubstringWithSubstringBefore" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceSubstringWithTake" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ResultOfObjectAllocationIgnored" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ResultSetIndexZero" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ReturnOfInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RsSimplifyBooleanExpression" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RuntimeExec" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RuntimeExecWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SafeLock" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="optionConfiguration">
<list>
<option value="barryvdh/laravel-debugbar" />
<option value="behat/behat" />
<option value="brianium/paratest" />
<option value="codeception/codeception" />
<option value="codedungeon/phpunit-result-printer" />
<option value="composer/composer" />
<option value="doctrine/coding-standard" />
<option value="filp/whoops" />
<option value="friendsofphp/php-cs-fixer" />
<option value="humbug/humbug" />
<option value="infection/infection" />
<option value="jakub-onderka/php-parallel-lint" />
<option value="johnkary/phpunit-speedtrap" />
<option value="kalessil/production-dependencies-guard" />
<option value="mikey179/vfsStream" />
<option value="mockery/mockery" />
<option value="mybuilder/phpunit-accelerator" />
<option value="orchestra/testbench" />
<option value="pdepend/pdepend" />
<option value="phan/phan" />
<option value="phing/phing" />
<option value="phpcompatibility/php-compatibility" />
<option value="phpmd/phpmd" />
<option value="phpro/grumphp" />
<option value="phpspec/phpspec" />
<option value="phpspec/prophecy" />
<option value="phpstan/phpstan" />
<option value="phpunit/phpunit" />
<option value="povils/phpmnd" />
<option value="roave/security-advisories" />
<option value="satooshi/php-coveralls" />
<option value="sebastian/phpcpd" />
<option value="slevomat/coding-standard" />
<option value="spatie/phpunit-watcher" />
<option value="squizlabs/php_codesniffer" />
<option value="sstalle/php7cc" />
<option value="symfony/debug" />
<option value="symfony/maker-bundle" />
<option value="symfony/phpunit-bridge" />
<option value="symfony/var-dumper" />
<option value="vimeo/psalm" />
<option value="wimg/php-compatibility" />
<option value="wp-coding-standards/wpcs" />
<option value="yiisoft/yii2-coding-standards" />
<option value="yiisoft/yii2-debug" />
<option value="yiisoft/yii2-gii" />
<option value="zendframework/zend-coding-standard" />
<option value="zendframework/zend-debug" />
<option value="zendframework/zend-test" />
</list>
</option>
</inspection_tool>
<inspection_tool class="SerialPersistentFieldsWithWrongSignature" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SerialVersionUIDNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SerializableHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnonymousInnerClasses" value="false" />
<option name="superClassString" value="java.awt.Component" />
</inspection_tool>
<inspection_tool class="SerializableHasSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnonymousInnerClasses" value="false" />
<option name="superClassString" value="java.awt.Component" />
</inspection_tool>
<inspection_tool class="SerializableInnerClassHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnonymousInnerClasses" value="false" />
<option name="superClassString" value="java.awt.Component" />
</inspection_tool>
<inspection_tool class="SerializableInnerClassWithNonSerializableOuterClass" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnonymousInnerClasses" value="false" />
<option name="superClassString" value="java.awt.Component" />
</inspection_tool>
<inspection_tool class="SerializableStoresNonSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SerializableWithUnconstructableAncestor" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SetReplaceableByEnumSet" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SeveralTargetsMessage" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SharedThreadLocalRandom" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ShortEchoTagCanBeUsedInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="SignalWithoutCorrespondingAwait" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SimpleDateFormatWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SimplifiableAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SimplifiableIfStatement" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="SingleStatementInBlock" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SleepWhileHoldingLock" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SocketResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="insideTryAllowed" value="false" />
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="SqlGotoInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SqlRedundantOrderingDirectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlWithoutWhereInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myDontWarnForLimit" value="false" />
</inspection_tool>
<inspection_tool class="StrTrUsageAsStrReplaceInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="StringBufferToStringInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="StringConcatenationInFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="StringConcatenationInMessageFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="StringConcatenationMissingWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="StringEqualsEmptyString" enabled="true" level="WARNING" enabled_by_default="true">
<option name="SUPPRESS_FOR_VALUES_WHICH_COULD_BE_NULL" value="true" />
</inspection_tool>
<inspection_tool class="StringReplaceableByStringBuffer" enabled="true" level="WARNING" enabled_by_default="true">
<option name="onlyWarnOnLoop" value="true" />
</inspection_tool>
<inspection_tool class="StringToUpperWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SubStrUsedAsStrPosInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SubtractionInCompareTo" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SuspiciousArrayCast" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SuspiciousLiteralUnderscore" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SwitchStatementWithConfusingDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SynchronizationOnStaticField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SynchronizeOnLock" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SynchronizeOnThis" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_includeNativeMethods" value="true" />
<option name="ignoreSynchronizedSuperMethods" value="true" />
</inspection_tool>
<inspection_tool class="SynchronizedOnLiteralObject" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SystemGC" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SystemGetenv" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SystemProperties" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SystemRunFinalizersOnExit" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SystemSetSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestCaseInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestCaseWithConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestCaseWithNoTestMethods" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreSupers" value="true" />
</inspection_tool>
<inspection_tool class="TestMethodInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestMethodWithoutAssertion" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TextLabelInSwitchStatementJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ThreadLocalNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ThreadPriority" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ThreadStartInConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ThreadStopSuspendResume" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ThreadYield" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ThrowCaughtLocally" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreRethrownExceptions" value="true" />
</inspection_tool>
<inspection_tool class="ThrowRawExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="TimeToString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TooBroadScope" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="m_allowConstructorAsInitializer" value="false" />
<option name="m_onlyLookAtBlocks" value="true" />
</inspection_tool>
<inspection_tool class="TransientFieldInNonSerializableClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TransientFieldNotInitialized" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TrivialStringConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnNecessaryDoubleQuotesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="UnaryPlus" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnclearBinaryExpression" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="UnconditionalWait" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnconstrainedVariableType" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UndeclaredTests" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnknownInspectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="UnnecessarilyQualifiedStaticUsage" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreStaticFieldAccesses" value="false" />
<option name="m_ignoreStaticMethodCalls" value="false" />
<option name="m_ignoreStaticAccessFromStaticContext" value="false" />
</inspection_tool>
<inspection_tool class="UnnecessarilyQualifiedStaticallyImportedElement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryBoxing" enabled="true" level="WARNING" enabled_by_default="true">
<option name="onlyReportSuperfluouslyBoxed" value="true" />
</inspection_tool>
<inspection_tool class="UnnecessaryConstantArrayCreationExpression" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreAnnotations" value="true" />
</inspection_tool>
<inspection_tool class="UnnecessaryLocalVariable" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreImmediatelyReturnedVariables" value="true" />
<option name="m_ignoreAnnotatedVariables" value="false" />
</inspection_tool>
<inspection_tool class="UnnecessaryLocalVariableJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreImmediatelyReturnedVariables" value="true" />
<option name="m_ignoreAnnotatedVariables" value="false" />
</inspection_tool>
<inspection_tool class="UnnecessaryParentheses" enabled="true" level="INFORMATION" enabled_by_default="true">
<option name="ignoreClarifyingParentheses" value="true" />
<option name="ignoreParenthesesOnConditionals" value="false" />
<option name="ignoreParenthesesOnLambdaParameter" value="false" />
</inspection_tool>
<inspection_tool class="UnnecessaryToStringCall" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryUnaryMinus" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryUnboxing" enabled="true" level="WARNING" enabled_by_default="true">
<option name="onlyReportSuperfluouslyUnboxed" value="true" />
</inspection_tool>
<inspection_tool class="UnsetConstructsCanBeMergedInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreSemicolonAtEndOfBlock" value="false" />
</inspection_tool>
<inspection_tool class="UnusedCatchParameterJS" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreCatchBlocksWithComments" value="true" />
</inspection_tool>
<inspection_tool class="UnusedProperty" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UseOfAWTPeerClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfJDBCDriverClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfObsoleteAssert" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfObsoleteDateTimeApi" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfProcessBuilder" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfPropertiesAsHashtable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfSunClasses" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UsingInclusionReturnValueInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="UtilityClassWithPublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UtilityClassWithoutPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignorableAnnotations">
<value />
</option>
<option name="ignoreClassesWithOnlyMain" value="true" />
</inspection_tool>
<inspection_tool class="VariableNotUsedInsideIf" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="VariableUseSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="VoidExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="VolatileArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WaitCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WaitNotifyNotInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WaitOrAwaitWithoutTimeout" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WaitWhileHoldingTwoLocks" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WaitWithoutCorrespondingNotify" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="WhileLoopSpinsOnField" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreNonEmtpyLoops" value="false" />
</inspection_tool>
<inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project" />
<version value="1.0" />
</settings>
</component>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="PROJECT" libraries="{@types/jquery}" />
</component>
</project>

View File

@@ -0,0 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="TweetDuck" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/bin/x86/Debug/TweetDuck.exe" />
<option name="PROGRAM_PARAMETERS" value="-datafolder TweetDuckDebug -nogdpr" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/bin/x86/Debug" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/TweetDuck.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="Console" />
<option name="PROJECT_TFM" value=".NETFramework,Version=v4.7.2" />
<method v="2">
<option name="Build" />
</method>
</configuration>
</component>

11
.idea/.idea.TweetDuck/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitSharedSettings">
<option name="FORCE_PUSH_PROHIBITED_PATTERNS">
<list />
</option>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,43 @@
using System;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using TweetDuck.Management;
using TweetLib.Core.Application;
using TweetLib.Core.Systems.Dialogs;
namespace TweetDuck.Application {
sealed class FileDialogs : IAppFileDialogs {
public void SaveFile(SaveFileDialogSettings settings, Action<string> onAccepted) {
static string FormatFilter(FileDialogFilter filter) {
var builder = new StringBuilder();
builder.Append(filter.Name);
var extensions = string.Join(";", filter.Extensions.Select(ext => "*" + ext));
if (extensions.Length > 0) {
builder.Append(" (");
builder.Append(extensions);
builder.Append(")");
}
builder.Append('|');
builder.Append(extensions.Length == 0 ? "*.*" : extensions);
return builder.ToString();
}
FormManager.RunOnUIThreadAsync(() => {
using SaveFileDialog dialog = new SaveFileDialog {
AutoUpgradeEnabled = true,
OverwritePrompt = settings.OverwritePrompt,
Title = settings.DialogTitle,
FileName = settings.FileName,
Filter = settings.Filters == null ? null : string.Join("|", settings.Filters.Select(FormatFilter))
};
if (dialog.ShowDialog() == DialogResult.OK) {
onAccepted(dialog.FileName);
}
});
}
}
}

View File

@@ -1,58 +0,0 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using TweetDuck.Utils;
using TweetLib.Core.Application;
namespace TweetDuck.Application{
class LockHandler : IAppLockHandler{
private const int WaitRetryDelay = 250;
private const int RestoreFailTimeout = 2000;
private const int CloseNaturallyTimeout = 10000;
private const int CloseKillTimeout = 5000;
bool IAppLockHandler.RestoreProcess(Process process){
if (process.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)process.Id, 0);
if (WindowsUtils.TrySleepUntil(() => CheckProcessExited(process) || (process.MainWindowHandle != IntPtr.Zero && process.Responding), RestoreFailTimeout, WaitRetryDelay)){
return true;
}
}
return false;
}
bool IAppLockHandler.CloseProcess(Process process){
try{
if (process.CloseMainWindow()){
// ReSharper disable once AccessToDisposedClosure
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseNaturallyTimeout, WaitRetryDelay);
}
if (!process.HasExited){
process.Kill();
// ReSharper disable once AccessToDisposedClosure
WindowsUtils.TrySleepUntil(() => CheckProcessExited(process), CloseKillTimeout, WaitRetryDelay);
}
if (process.HasExited){
process.Dispose();
return true;
}
else{
return false;
}
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
bool hasExited = CheckProcessExited(process);
process.Dispose();
return hasExited;
}
}
private static bool CheckProcessExited(Process process){
process.Refresh();
return process.HasExited;
}
}
}

View File

@@ -0,0 +1,15 @@
using TweetDuck.Dialogs;
using TweetDuck.Management;
using TweetLib.Core.Application;
namespace TweetDuck.Application {
sealed class MessageDialogs : IAppMessageDialogs {
public void Information(string caption, string text, string buttonAccept) {
FormManager.RunOnUIThreadAsync(() => FormMessage.Information(caption, text, buttonAccept));
}
public void Error(string caption, string text, string buttonAccept) {
FormManager.RunOnUIThreadAsync(() => FormMessage.Error(caption, text, buttonAccept));
}
}
}

View File

@@ -1,28 +1,155 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing;
using System.IO; using System.IO;
using System.Windows.Forms;
using TweetDuck.Browser;
using TweetDuck.Dialogs;
using TweetDuck.Management;
using TweetLib.Core;
using TweetLib.Core.Application; using TweetLib.Core.Application;
using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Application{ namespace TweetDuck.Application {
class SystemHandler : IAppSystemHandler{ sealed class SystemHandler : IAppSystemHandler {
void IAppSystemHandler.OpenAssociatedProgram(string path){ public void OpenBrowser(string url) {
try{ if (string.IsNullOrWhiteSpace(url)) {
using(Process.Start(new ProcessStartInfo{ return;
}
FormManager.RunOnUIThreadAsync(() => {
var config = Program.Config.User;
switch (TwitterUrls.Check(url)) {
case TwitterUrls.UrlType.Fine:
string browserPath = config.BrowserPath;
if (browserPath == null || !File.Exists(browserPath)) {
OpenAssociatedProgram(url);
}
else {
string quotedUrl = '"' + url + '"';
string browserArgs = config.BrowserPathArgs == null ? quotedUrl : config.BrowserPathArgs + ' ' + quotedUrl;
try {
using (Process.Start(browserPath, browserArgs)) {}
} catch (Exception e) {
App.ErrorHandler.HandleException("Error Opening Browser", "Could not open the browser.", true, e);
}
}
break;
case TwitterUrls.UrlType.Tracking:
if (config.IgnoreTrackingUrlWarning) {
goto case TwitterUrls.UrlType.Fine;
}
using (FormMessage form = new FormMessage("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n" + url, MessageBoxIcon.Warning)) {
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel | ControlType.Focused);
form.AddButton(FormMessage.Yes, DialogResult.Yes, ControlType.Accept);
form.AddButton("Always Visit", DialogResult.Ignore);
DialogResult result = form.ShowDialog();
if (result == DialogResult.Ignore) {
config.IgnoreTrackingUrlWarning = true;
config.Save();
}
if (result == DialogResult.Ignore || result == DialogResult.Yes) {
goto case TwitterUrls.UrlType.Fine;
}
}
break;
case TwitterUrls.UrlType.Invalid:
FormMessage.Warning("Blocked URL", "A potentially malicious or invalid URL was blocked from opening:\n" + url, FormMessage.OK);
break;
}
});
}
public void OpenFileExplorer(string path) {
if (File.Exists(path)) {
using (Process.Start("explorer.exe", "/select,\"" + path.Replace('/', '\\') + "\"")) {}
}
else if (Directory.Exists(path)) {
using (Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')) {}
}
}
public IAppSystemHandler.OpenAssociatedProgramFunc OpenAssociatedProgram { get; } = path => {
try {
using (Process.Start(new ProcessStartInfo {
FileName = path, FileName = path,
ErrorDialog = true ErrorDialog = true
})){} })) {}
}catch(Exception e){ } catch (Exception e) {
Program.Reporter.HandleException("Error Opening Program", "Could not open the associated program for " + path, true, e); App.ErrorHandler.HandleException("Error Opening Program", "Could not open the associated program for " + path, true, e);
}
};
public IAppSystemHandler.CopyImageFromFileFunc CopyImageFromFile { get; } = path => {
FormManager.RunOnUIThreadAsync(() => {
Image image;
try {
image = Image.FromFile(path);
} catch (Exception ex) {
FormMessage.Error("Copy Image", "An error occurred while copying the image: " + ex.Message, FormMessage.OK);
return;
}
ClipboardManager.SetImage(image);
});
};
public IAppSystemHandler.CopyTextFunc CopyText { get; } = text => {
FormManager.RunOnUIThreadAsync(() => ClipboardManager.SetText(text, TextDataFormat.UnicodeText));
};
public IAppSystemHandler.SearchTextFunc SearchText { get; } = text => {
if (string.IsNullOrWhiteSpace(text)) {
return;
}
void PerformSearch() {
var config = Program.Config.User;
string searchUrl = config.SearchEngineUrl;
if (string.IsNullOrEmpty(searchUrl)) {
if (FormMessage.Question("Search Options", "You have not configured a default search engine yet, would you like to do it now?", FormMessage.Yes, FormMessage.No)) {
bool wereSettingsOpen = FormManager.TryFind<FormSettings>() != null;
FormManager.TryFind<FormBrowser>()?.OpenSettings();
if (wereSettingsOpen) {
return;
}
FormSettings settings = FormManager.TryFind<FormSettings>();
if (settings == null) {
return;
}
settings.FormClosed += (sender, args) => {
if (args.CloseReason == CloseReason.UserClosing && config.SearchEngineUrl != searchUrl) {
PerformSearch();
}
};
}
}
else {
App.SystemHandler.OpenBrowser(searchUrl + Uri.EscapeUriString(text));
} }
} }
void IAppSystemHandler.OpenFileExplorer(string path){ FormManager.RunOnUIThreadAsync(PerformSearch);
if (File.Exists(path)){ };
using(Process.Start("explorer.exe", "/select,\"" + path.Replace('/', '\\') + "\"")){}
}
else if (Directory.Exists(path)){
using(Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')){}
}
}
} }
} }

View File

@@ -0,0 +1,125 @@
using System;
using System.IO;
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Browser.Handling;
using TweetDuck.Management;
using TweetDuck.Utils;
using TweetLib.Browser.Base;
using TweetLib.Browser.Events;
using TweetLib.Browser.Interfaces;
using TweetLib.Utils.Static;
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
namespace TweetDuck.Browser.Adapters {
abstract class CefBrowserComponent : IBrowserComponent {
public bool Ready { get; private set; }
public string Url => browser.Address;
public string CacheFolder => BrowserCache.CacheFolder;
public event EventHandler<BrowserLoadedEventArgs> BrowserLoaded;
public event EventHandler<PageLoadEventArgs> PageLoadStart;
public event EventHandler<PageLoadEventArgs> PageLoadEnd;
private readonly ChromiumWebBrowser browser;
protected CefBrowserComponent(ChromiumWebBrowser browser) {
this.browser = browser;
this.browser.JsDialogHandler = new JavaScriptDialogHandler();
this.browser.LifeSpanHandler = new CustomLifeSpanHandler();
this.browser.LoadingStateChanged += OnLoadingStateChanged;
this.browser.LoadError += OnLoadError;
this.browser.FrameLoadStart += OnFrameLoadStart;
this.browser.FrameLoadEnd += OnFrameLoadEnd;
this.browser.SetupZoomEvents();
}
void IBrowserComponent.Setup(BrowserSetup setup) {
browser.MenuHandler = SetupContextMenu(setup.ContextMenuHandler);
browser.ResourceRequestHandlerFactory = SetupResourceHandlerFactory(setup.ResourceRequestHandler);
}
protected abstract ContextMenuBase SetupContextMenu(IContextMenuHandler handler);
protected abstract CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler);
private void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
if (!e.IsLoading) {
Ready = true;
browser.LoadingStateChanged -= OnLoadingStateChanged;
BrowserLoaded?.Invoke(this, new BrowserLoadedEventArgsImpl(browser));
BrowserLoaded = null;
}
}
private sealed class BrowserLoadedEventArgsImpl : BrowserLoadedEventArgs {
private readonly IWebBrowser browser;
public BrowserLoadedEventArgsImpl(IWebBrowser browser) {
this.browser = browser;
}
public override void AddDictionaryWords(params string[] words) {
foreach (string word in words) {
browser.AddWordToDictionary(word);
}
}
}
private void OnLoadError(object sender, LoadErrorEventArgs e) {
if (e.ErrorCode == CefErrorCode.Aborted) {
return;
}
if (!e.FailedUrl.StartsWithOrdinal("td://resources/error/")) {
string errorName = Enum.GetName(typeof(CefErrorCode), e.ErrorCode);
string errorTitle = StringUtils.ConvertPascalCaseToScreamingSnakeCase(errorName ?? string.Empty);
browser.Load("td://resources/error/error.html#" + Uri.EscapeDataString(errorTitle));
}
}
private void OnFrameLoadStart(object sender, FrameLoadStartEventArgs e) {
if (e.Frame.IsMain) {
PageLoadStart?.Invoke(this, new PageLoadEventArgs(e.Url));
}
}
private void OnFrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
if (e.Frame.IsMain) {
PageLoadEnd?.Invoke(this, new PageLoadEventArgs(e.Url));
}
}
public void AttachBridgeObject(string name, object bridge) {
browser.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true;
browser.JavascriptObjectRepository.Register(name, bridge, isAsync: true, BindingOptions.DefaultBinder);
}
public void RunScript(string identifier, string script) {
using IFrame frame = browser.GetMainFrame();
frame.ExecuteJavaScriptAsync(script, identifier, 1);
}
public void DownloadFile(string url, string path, Action onSuccess, Action<Exception> onError) {
Cef.UIThreadTaskFactory.StartNew(() => {
try {
using IFrame frame = browser.GetMainFrame();
var request = frame.CreateRequest(false);
request.Method = "GET";
request.Url = url;
request.Flags = UrlRequestFlags.AllowStoredCredentials;
request.SetReferrer(Url, ReferrerPolicy.Default);
var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
var client = new DownloadRequestClient(fileStream, onSuccess, onError);
frame.CreateUrlRequest(request, client);
} catch (Exception e) {
onError?.Invoke(e);
}
});
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using CefSharp;
namespace TweetDuck.Browser.Adapters {
sealed class CefContextMenuActionRegistry {
private readonly Dictionary<CefMenuCommand, Action> actions = new Dictionary<CefMenuCommand, Action>();
public CefMenuCommand AddAction(Action action) {
CefMenuCommand id = CefMenuCommand.UserFirst + 500 + actions.Count;
actions[id] = action;
return id;
}
public bool Execute(CefMenuCommand id) {
if (actions.TryGetValue(id, out var action)) {
action();
return true;
}
return false;
}
public void Clear() {
actions.Clear();
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
using CefSharp;
using TweetLib.Browser.Contexts;
using TweetLib.Browser.Interfaces;
using TweetLib.Core.Features.TweetDeck;
using TweetLib.Core.Features.Twitter;
namespace TweetDuck.Browser.Adapters {
sealed class CefContextMenuModel : IContextMenuBuilder {
private readonly IMenuModel model;
private readonly CefContextMenuActionRegistry actionRegistry;
public CefContextMenuModel(IMenuModel model, CefContextMenuActionRegistry actionRegistry) {
this.model = model;
this.actionRegistry = actionRegistry;
}
public void AddAction(string name, Action action) {
var id = actionRegistry.AddAction(action);
model.AddItem(id, name);
}
public void AddActionWithCheck(string name, bool isChecked, Action action) {
var id = actionRegistry.AddAction(action);
model.AddCheckItem(id, name);
model.SetChecked(id, isChecked);
}
public void AddSeparator() {
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) { // do not add separators if there is nothing to separate
model.AddSeparator();
}
}
public static Context CreateContext(IContextMenuParams parameters, TweetDeckExtraContext extraContext, ImageQuality imageQuality) {
var context = new Context();
var flags = parameters.TypeFlags;
var tweet = extraContext?.Tweet;
if (tweet != null && !flags.HasFlag(ContextMenuType.Editable)) {
context.Tweet = tweet;
}
context.Link = GetLink(parameters, extraContext);
context.Media = GetMedia(parameters, extraContext, imageQuality);
if (flags.HasFlag(ContextMenuType.Selection)) {
context.Selection = new Selection(parameters.SelectionText, flags.HasFlag(ContextMenuType.Editable));
}
return context;
}
private static Link? GetLink(IContextMenuParams parameters, TweetDeckExtraContext extraContext) {
var link = extraContext?.Link;
if (link != null) {
return link;
}
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && extraContext?.Media == null) {
return new Link(parameters.LinkUrl, parameters.UnfilteredLinkUrl);
}
return null;
}
private static Media? GetMedia(IContextMenuParams parameters, TweetDeckExtraContext extraContext, ImageQuality imageQuality) {
var media = extraContext?.Media;
if (media != null) {
return media;
}
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) {
return new Media(Media.Type.Image, TwitterUrls.GetMediaLink(parameters.SourceUrl, imageQuality));
}
return null;
}
}
}

View File

@@ -0,0 +1,23 @@
using System.Diagnostics.CodeAnalysis;
using CefSharp;
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
namespace TweetDuck.Browser.Adapters {
sealed class CefResourceHandlerFactory : IResourceRequestHandlerFactory {
bool IResourceRequestHandlerFactory.HasHandlers => registry != null;
private readonly CefResourceRequestHandler resourceRequestHandler;
private readonly CefResourceHandlerRegistry registry;
public CefResourceHandlerFactory(IResourceRequestHandler resourceRequestHandler, CefResourceHandlerRegistry registry) {
this.resourceRequestHandler = new CefResourceRequestHandler(registry, resourceRequestHandler);
this.registry = registry;
}
[SuppressMessage("ReSharper", "RedundantAssignment")]
CefSharp.IResourceRequestHandler IResourceRequestHandlerFactory.GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling) {
disableDefaultHandling = registry != null && registry.HasHandler(request.Url);
return resourceRequestHandler;
}
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Concurrent;
using System.Text;
using CefSharp;
namespace TweetDuck.Browser.Adapters {
sealed class CefResourceHandlerRegistry {
private readonly ConcurrentDictionary<string, Func<IResourceHandler>> resourceHandlers = new ConcurrentDictionary<string, Func<IResourceHandler>>(StringComparer.OrdinalIgnoreCase);
public bool HasHandler(string url) {
return resourceHandlers.ContainsKey(url);
}
public IResourceHandler GetHandler(string url) {
return resourceHandlers.TryGetValue(url, out var handler) ? handler() : null;
}
private void Register(string url, Func<IResourceHandler> factory) {
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) {
throw new ArgumentException("Resource handler URL must be absolute!");
}
resourceHandlers.AddOrUpdate(uri.AbsoluteUri, factory, (key, prev) => factory);
}
public void RegisterStatic(string url, byte[] staticData, string mimeType = ResourceHandler.DefaultMimeType) {
Register(url, () => ResourceHandler.FromByteArray(staticData, mimeType));
}
public void RegisterStatic(string url, string staticData, string mimeType = ResourceHandler.DefaultMimeType) {
Register(url, () => ResourceHandler.FromString(staticData, Encoding.UTF8, mimeType: mimeType));
}
public void RegisterDynamic(string url, IResourceHandler handler) {
Register(url, () => handler);
}
public void Unregister(string url) {
resourceHandlers.TryRemove(url, out _);
}
}
}

View File

@@ -0,0 +1,77 @@
using System.Collections.Generic;
using CefSharp;
using CefSharp.Handler;
using TweetDuck.Browser.Handling;
using TweetLib.Browser.Interfaces;
using TweetLib.Browser.Request;
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
using ResourceType = TweetLib.Browser.Request.ResourceType;
namespace TweetDuck.Browser.Adapters {
sealed class CefResourceRequestHandler : ResourceRequestHandler {
private readonly CefResourceHandlerRegistry resourceHandlerRegistry;
private readonly IResourceRequestHandler resourceRequestHandler;
private readonly Dictionary<ulong, IResponseProcessor> responseProcessors = new Dictionary<ulong, IResponseProcessor>();
public CefResourceRequestHandler(CefResourceHandlerRegistry resourceHandlerRegistry, IResourceRequestHandler resourceRequestHandler) {
this.resourceHandlerRegistry = resourceHandlerRegistry;
this.resourceRequestHandler = resourceRequestHandler;
}
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) {
if (request.ResourceType == CefSharp.ResourceType.CspReport) {
callback.Dispose();
return CefReturnValue.Cancel;
}
if (resourceRequestHandler != null) {
var result = resourceRequestHandler.Handle(request.Url, TranslateResourceType(request.ResourceType));
switch (result) {
case RequestHandleResult.Redirect redirect:
request.Url = redirect.Url;
break;
case RequestHandleResult.Process process:
request.SetHeaderByName("Accept-Encoding", "identity", overwrite: true);
responseProcessors[request.Identifier] = process.Processor;
break;
case RequestHandleResult.Cancel _:
callback.Dispose();
return CefReturnValue.Cancel;
}
}
return base.OnBeforeResourceLoad(chromiumWebBrowser, browser, frame, request, callback);
}
protected override IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request) {
return resourceHandlerRegistry?.GetHandler(request.Url);
}
protected override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) {
if (responseProcessors.TryGetValue(request.Identifier, out var processor) && int.TryParse(response.Headers["Content-Length"], out int totalBytes)) {
return new ResponseFilter(processor, totalBytes);
}
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
}
protected override void OnResourceLoadComplete(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength) {
responseProcessors.Remove(request.Identifier);
base.OnResourceLoadComplete(chromiumWebBrowser, browser, frame, request, response, status, receivedContentLength);
}
private static ResourceType TranslateResourceType(CefSharp.ResourceType resourceType) {
return resourceType switch {
CefSharp.ResourceType.MainFrame => ResourceType.MainFrame,
CefSharp.ResourceType.Script => ResourceType.Script,
CefSharp.ResourceType.Stylesheet => ResourceType.Stylesheet,
CefSharp.ResourceType.Xhr => ResourceType.Xhr,
CefSharp.ResourceType.Image => ResourceType.Image,
_ => ResourceType.Unknown
};
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using CefSharp;
using CefSharp.WinForms;
using TweetLib.Browser.Interfaces;
namespace TweetDuck.Browser.Adapters {
sealed class CefSchemeHandlerFactory : ISchemeHandlerFactory {
public static void Register(CefSettings settings, ICustomSchemeHandler handler) {
settings.RegisterScheme(new CefCustomScheme {
SchemeName = handler.Protocol,
IsStandard = false,
IsSecure = true,
IsCorsEnabled = true,
IsCSPBypassing = true,
SchemeHandlerFactory = new CefSchemeHandlerFactory(handler)
});
}
private readonly ICustomSchemeHandler handler;
private CefSchemeHandlerFactory(ICustomSchemeHandler handler) {
this.handler = handler;
}
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) {
return Uri.TryCreate(request.Url, UriKind.Absolute, out var uri) ? handler.Resolve(uri)?.Visit(CefSchemeResourceVisitor.Instance) : null;
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.IO;
using System.Net;
using CefSharp;
using TweetLib.Browser.Interfaces;
using TweetLib.Browser.Request;
namespace TweetDuck.Browser.Adapters {
sealed class CefSchemeResourceVisitor : ISchemeResourceVisitor<IResourceHandler> {
public static CefSchemeResourceVisitor Instance { get; } = new CefSchemeResourceVisitor();
private static readonly SchemeResource.Status FileIsEmpty = new SchemeResource.Status(HttpStatusCode.NoContent, "File is empty.");
private CefSchemeResourceVisitor() {}
public IResourceHandler Status(SchemeResource.Status status) {
var handler = CreateHandler(Array.Empty<byte>());
handler.StatusCode = (int) status.Code;
handler.StatusText = status.Message;
return handler;
}
public IResourceHandler File(SchemeResource.File file) {
byte[] contents = file.Contents;
if (contents.Length == 0) {
return Status(FileIsEmpty); // FromByteArray crashes CEF internals with no contents
}
var handler = CreateHandler(contents);
handler.MimeType = Cef.GetMimeType(file.Extension);
return handler;
}
private static ResourceHandler CreateHandler(byte[] bytes) {
return ResourceHandler.FromStream(new MemoryStream(bytes), autoDisposeStream: true);
}
}
}

View File

@@ -1,41 +0,0 @@
using System.IO;
using CefSharp;
using TweetLib.Core.Browser;
namespace TweetDuck.Browser.Adapters{
sealed class CefScriptExecutor : IScriptExecutor{
private readonly IWebBrowser browser;
public CefScriptExecutor(IWebBrowser browser){
this.browser = browser;
}
public void RunFunction(string name, params object[] args){
browser.ExecuteScriptAsync(name, args);
}
public void RunScript(string identifier, string script){
using IFrame frame = browser.GetMainFrame();
RunScript(frame, script, identifier);
}
public bool RunFile(string file){
using IFrame frame = browser.GetMainFrame();
return RunFile(frame, file);
}
// Helpers
public static void RunScript(IFrame frame, string script, string identifier){
if (script != null){
frame.ExecuteJavaScriptAsync(script, identifier, 1);
}
}
public static bool RunFile(IFrame frame, string file){
string script = Program.Resources.Load(file);
RunScript(frame, script, "root:" + Path.GetFileNameWithoutExtension(file));
return script != null;
}
}
}

View File

@@ -1,38 +0,0 @@
using System.Text;
using TweetDuck.Configuration;
using TweetLib.Core;
using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Bridge{
static class PropertyBridge{
public enum Environment{
Browser, Notification
}
public static string GenerateScript(Environment environment){
static string Bool(bool value) => value ? "true;" : "false;";
static string Str(string value) => $"\"{value}\";";
UserConfig config = Program.Config.User;
StringBuilder build = new StringBuilder(384).Append("(function(x){");
build.Append("x.expandLinksOnHover=").Append(Bool(config.ExpandLinksOnHover));
if (environment == Environment.Browser){
build.Append("x.focusDmInput=").Append(Bool(config.FocusDmInput));
build.Append("x.openSearchInFirstColumn=").Append(Bool(config.OpenSearchInFirstColumn));
build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(config.KeepLikeFollowDialogsOpen));
build.Append("x.muteNotifications=").Append(Bool(config.MuteNotifications));
build.Append("x.notificationMediaPreviews=").Append(Bool(config.NotificationMediaPreviews));
build.Append("x.translationTarget=").Append(Str(config.TranslationTarget));
build.Append("x.firstDayOfWeek=").Append(config.CalendarFirstDay == -1 ? LocaleUtils.GetJQueryDayOfWeek(Lib.Culture.DateTimeFormat.FirstDayOfWeek) : config.CalendarFirstDay);
}
if (environment == Environment.Notification){
build.Append("x.skipOnLinkClick=").Append(Bool(config.NotificationSkipOnLinkClick));
}
return build.Append("})(window.$TDX=window.$TDX||{});if(window.TDGF_onPropertiesUpdated)window.TDGF_onPropertiesUpdated()").ToString();
}
}
}

View File

@@ -1,161 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Browser.Handling;
using TweetDuck.Browser.Notification;
using TweetDuck.Controls;
using TweetDuck.Dialogs;
using TweetDuck.Management;
using TweetDuck.Utils;
using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Bridge{
[SuppressMessage("ReSharper", "UnusedMember.Global")]
class TweetDeckBridge{
public static void ResetStaticProperties(){
FormNotificationBase.FontSize = null;
FormNotificationBase.HeadLayout = null;
}
private readonly FormBrowser form;
private readonly FormNotificationMain notification;
protected TweetDeckBridge(FormBrowser form, FormNotificationMain notification){
this.form = form;
this.notification = notification;
}
// Browser only
public sealed class Browser : TweetDeckBridge{
public Browser(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
public void OpenContextMenu(){
form.InvokeAsyncSafe(form.OpenContextMenu);
}
public void OpenProfileImport(){
form.InvokeAsyncSafe(form.OpenProfileImport);
}
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
form.InvokeAsyncSafe(() => form.OnIntroductionClosed(showGuide, allowDataCollection));
}
public void LoadNotificationLayout(string fontSize, string headLayout){
form.InvokeAsyncSafe(() => {
FormNotificationBase.FontSize = fontSize;
FormNotificationBase.HeadLayout = headLayout;
});
}
public void SetRightClickedLink(string type, string url){
ContextMenuBase.CurrentInfo.SetLink(type, url);
}
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
ContextMenuBase.CurrentInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
}
public void DisplayTooltip(string text){
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
}
}
// Notification only
public sealed class Notification : TweetDeckBridge{
public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
public void DisplayTooltip(string text){
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
}
public void LoadNextNotification(){
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
}
public void ShowTweetDetail(){
notification.InvokeAsyncSafe(notification.ShowTweetDetail);
}
}
// Global
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
notification.InvokeAsyncSafe(() => {
form.OnTweetNotification();
notification.ShowNotification(new DesktopNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
});
}
public void OnTweetSound(){
form.InvokeAsyncSafe(() => {
form.OnTweetNotification();
form.OnTweetSound();
});
}
public void ScreenshotTweet(string html, int width){
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
}
public void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay){
form.InvokeAsyncSafe(() => form.PlayVideo(videoUrl, tweetUrl, username, callShowOverlay));
}
public void StopVideo(){
form.InvokeAsyncSafe(form.StopVideo);
}
public void FixClipboard(){
form.InvokeAsyncSafe(ClipboardManager.StripHtmlStyles);
}
public void OpenBrowser(string url){
form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
}
public void MakeGetRequest(string url, IJavascriptCallback onSuccess, IJavascriptCallback onError){
Task.Run(async () => {
var client = WebUtils.NewClient(BrowserUtils.UserAgentVanilla);
try{
var result = await client.DownloadStringTaskAsync(url);
await onSuccess.ExecuteAsync(result);
}catch(Exception e){
await onError.ExecuteAsync(e.Message);
}finally{
onSuccess.Dispose();
onError.Dispose();
client.Dispose();
}
});
}
public int GetIdleSeconds(){
return NativeMethods.GetIdleSeconds();
}
public void Alert(string type, string contents){
MessageBoxIcon icon = type switch{
"error" => MessageBoxIcon.Error,
"warning" => MessageBoxIcon.Warning,
"info" => MessageBoxIcon.Information,
_ => MessageBoxIcon.None
};
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
}
public void CrashDebug(string message){
#if DEBUG
System.Diagnostics.Debug.WriteLine(message);
System.Diagnostics.Debugger.Break();
#endif
}
}
}

View File

@@ -1,65 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Windows.Forms;
using TweetDuck.Controls;
using TweetLib.Core.Systems.Updates;
namespace TweetDuck.Browser.Bridge{
[SuppressMessage("ReSharper", "UnusedMember.Global")]
class UpdateBridge{
private readonly UpdateHandler updates;
private readonly Control sync;
private UpdateInfo nextUpdate = null;
public event EventHandler<UpdateInfo> UpdateAccepted;
public event EventHandler<UpdateInfo> UpdateDismissed;
public UpdateBridge(UpdateHandler updates, Control sync){
this.sync = sync;
this.updates = updates;
this.updates.CheckFinished += updates_CheckFinished;
}
internal void Cleanup(){
updates.CheckFinished -= updates_CheckFinished;
nextUpdate?.DeleteInstaller();
}
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
UpdateInfo foundUpdate = e.Result.HasValue ? e.Result.Value : null;
if (nextUpdate != null && !nextUpdate.Equals(foundUpdate)){
nextUpdate.DeleteInstaller();
}
nextUpdate = foundUpdate;
}
private void HandleInteractionEvent(EventHandler<UpdateInfo> eventHandler){
UpdateInfo tmpInfo = nextUpdate;
if (tmpInfo != null){
sync.InvokeAsyncSafe(() => eventHandler?.Invoke(this, tmpInfo));
}
}
// Bridge methods
public void TriggerUpdateCheck(){
updates.Check(false);
}
public void OnUpdateAccepted(){
HandleInteractionEvent(UpdateAccepted);
}
public void OnUpdateDismissed(){
HandleInteractionEvent(UpdateDismissed);
nextUpdate?.DeleteInstaller();
nextUpdate = null;
}
}
}

View File

@@ -1,161 +0,0 @@
using System;
using CefSharp;
using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Data{
sealed class ContextInfo{
private LinkInfo link;
private ChirpInfo? chirp;
public ContextInfo(){
Reset();
}
public void SetLink(string type, string url){
link = string.IsNullOrEmpty(url) ? null : new LinkInfo(type, url);
}
public void SetChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
chirp = string.IsNullOrEmpty(tweetUrl) ? (ChirpInfo?)null : new ChirpInfo(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
}
public ContextData Reset(){
link = null;
chirp = null;
return ContextData.Empty;
}
public ContextData Create(IContextMenuParams parameters){
ContextData.Builder builder = new ContextData.Builder();
builder.AddContext(parameters);
if (link != null){
builder.AddOverride(link.Type, link.Url);
}
if (chirp.HasValue){
builder.AddChirp(chirp.Value);
}
return builder.Build();
}
// Data structures
private sealed class LinkInfo{
public string Type { get; }
public string Url { get; }
public LinkInfo(string type, string url){
this.Type = type;
this.Url = url;
}
}
public struct ChirpInfo{
public string TweetUrl { get; }
public string QuoteUrl { get; }
public string[] Authors => chirpAuthors?.Split(';') ?? StringUtils.EmptyArray;
public string[] Images => chirpImages?.Split(';') ?? StringUtils.EmptyArray;
private readonly string chirpAuthors;
private readonly string chirpImages;
public ChirpInfo(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
this.TweetUrl = tweetUrl;
this.QuoteUrl = quoteUrl;
this.chirpAuthors = chirpAuthors;
this.chirpImages = chirpImages;
}
}
// Constructed context
[Flags]
public enum ContextType{
Unknown = 0,
Link = 0b0001,
Image = 0b0010,
Video = 0b0100,
Chirp = 0b1000
}
public sealed class ContextData{
public static readonly ContextData Empty = new Builder().Build();
public ContextType Types { get; }
public string LinkUrl { get; }
public string UnsafeLinkUrl { get; }
public string MediaUrl { get; }
public ChirpInfo Chirp { get; }
public ContextData(ContextType types, string linkUrl, string unsafeLinkUrl, string mediaUrl, ChirpInfo chirp){
Types = types;
LinkUrl = linkUrl;
UnsafeLinkUrl = unsafeLinkUrl;
MediaUrl = mediaUrl;
Chirp = chirp;
}
public sealed class Builder{
private ContextType types = ContextType.Unknown;
private string linkUrl = string.Empty;
private string unsafeLinkUrl = string.Empty;
private string mediaUrl = string.Empty;
private ChirpInfo chirp = default;
public void AddContext(IContextMenuParams parameters){
ContextMenuType flags = parameters.TypeFlags;
if (flags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){
types |= ContextType.Image;
types &= ~ContextType.Video;
mediaUrl = parameters.SourceUrl;
}
if (flags.HasFlag(ContextMenuType.Link)){
types |= ContextType.Link;
linkUrl = parameters.LinkUrl;
unsafeLinkUrl = parameters.UnfilteredLinkUrl;
}
}
public void AddOverride(string type, string url){
switch(type){
case "link":
types |= ContextType.Link;
linkUrl = url;
unsafeLinkUrl = url;
break;
case "image":
types |= ContextType.Image;
types &= ~(ContextType.Video | ContextType.Link);
mediaUrl = url;
break;
case "video":
types |= ContextType.Video;
types &= ~(ContextType.Image | ContextType.Link);
mediaUrl = url;
break;
}
}
public void AddChirp(ChirpInfo chirp){
this.types |= ContextType.Chirp;
this.chirp = chirp;
}
public ContextData Build(){
return new ContextData(types, linkUrl, unsafeLinkUrl, mediaUrl, chirp);
}
}
}
}
}

View File

@@ -1,50 +0,0 @@
using System;
using System.Collections.Concurrent;
using CefSharp;
namespace TweetDuck.Browser.Data{
sealed class ResourceHandlers{
private readonly ConcurrentDictionary<string, Func<IResourceHandler>> handlers = new ConcurrentDictionary<string, Func<IResourceHandler>>(StringComparer.OrdinalIgnoreCase);
public bool HasHandler(IRequest request){
return handlers.ContainsKey(request.Url);
}
public IResourceHandler GetHandler(IRequest request){
return handlers.TryGetValue(request.Url, out var factory) ? factory() : null;
}
public bool Register(string url, Func<IResourceHandler> factory){
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
handlers.AddOrUpdate(uri.AbsoluteUri, factory, (key, prev) => factory);
return true;
}
return false;
}
public bool Register(ResourceLink link){
return Register(link.Url, link.Factory);
}
public bool Unregister(string url){
return handlers.TryRemove(url, out _);
}
public bool Unregister(ResourceLink link){
return Unregister(link.Url);
}
public static Func<IResourceHandler> ForString(string str){
return () => ResourceHandler.FromString(str);
}
public static Func<IResourceHandler> ForString(string str, string mimeType){
return () => ResourceHandler.FromString(str, mimeType: mimeType);
}
public static Func<IResourceHandler> ForBytes(byte[] bytes, string mimeType){
return () => ResourceHandler.FromByteArray(bytes, mimeType);
}
}
}

View File

@@ -1,14 +0,0 @@
using System;
using CefSharp;
namespace TweetDuck.Browser.Data{
sealed class ResourceLink{
public string Url { get; }
public Func<IResourceHandler> Factory { get; }
public ResourceLink(string url, Func<IResourceHandler> factory){
this.Url = url;
this.Factory = factory;
}
}
}

View File

@@ -1,42 +0,0 @@
using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Controls;
using TweetLib.Core.Serialization.Converters;
using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Data{
sealed class WindowState{
private Rectangle rect;
private bool isMaximized;
public void Save(Form form){
rect = form.WindowState == FormWindowState.Normal ? form.DesktopBounds : form.RestoreBounds;
isMaximized = form.WindowState == FormWindowState.Maximized;
}
public void Restore(Form form, bool firstTimeFullscreen){
if (rect != Rectangle.Empty){
form.DesktopBounds = rect;
form.WindowState = isMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
}
if ((rect == Rectangle.Empty && firstTimeFullscreen) || form.IsFullyOutsideView()){
form.DesktopBounds = Screen.PrimaryScreen.WorkingArea;
form.WindowState = FormWindowState.Maximized;
Save(form);
}
}
public static readonly SingleTypeConverter<WindowState> Converter = new SingleTypeConverter<WindowState>{
ConvertToString = value => $"{(value.isMaximized ? 'M' : '_')}{value.rect.X} {value.rect.Y} {value.rect.Width} {value.rect.Height}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value.Substring(1), ' ');
return new WindowState{
rect = new Rectangle(elements[0], elements[1], elements[2], elements[3]),
isMaximized = value[0] == 'M'
};
}
};
}
}

View File

@@ -27,7 +27,7 @@
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = TweetDuck.Utils.TwitterUtils.BackgroundColor; this.BackColor = TweetDuck.Browser.TweetDeckBrowser.BackgroundColor;
this.ClientSize = new System.Drawing.Size(1008, 730); this.ClientSize = new System.Drawing.Size(1008, 730);
this.Icon = Properties.Resources.icon; this.Icon = Properties.Resources.icon;
this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation; this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation;

View File

@@ -2,12 +2,11 @@
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using TweetDuck.Browser.Bridge;
using TweetDuck.Browser.Handling; using TweetDuck.Browser.Handling;
using TweetDuck.Browser.Handling.General;
using TweetDuck.Browser.Notification; using TweetDuck.Browser.Notification;
using TweetDuck.Browser.Notification.Screenshot; using TweetDuck.Browser.Notification.Screenshot;
using TweetDuck.Configuration; using TweetDuck.Configuration;
@@ -15,29 +14,31 @@ using TweetDuck.Controls;
using TweetDuck.Dialogs; using TweetDuck.Dialogs;
using TweetDuck.Dialogs.Settings; using TweetDuck.Dialogs.Settings;
using TweetDuck.Management; using TweetDuck.Management;
using TweetDuck.Management.Analytics;
using TweetDuck.Plugins;
using TweetDuck.Updates; using TweetDuck.Updates;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core;
using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Events; using TweetLib.Core.Features.TweetDeck;
using TweetLib.Core.Resources;
using TweetLib.Core.Systems.Configuration;
using TweetLib.Core.Systems.Updates; using TweetLib.Core.Systems.Updates;
namespace TweetDuck.Browser{ namespace TweetDuck.Browser {
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{ sealed partial class FormBrowser : Form, CustomKeyboardHandler.IBrowserKeyHandler {
private static UserConfig Config => Program.Config.User; private static UserConfig Config => Program.Config.User;
public bool IsWaiting{ public bool IsWaiting {
set{ set {
if (value){ if (value) {
browser.Enabled = false; browser.Enabled = false;
Cursor = Cursors.WaitCursor; Cursor = Cursors.WaitCursor;
} }
else{ else {
browser.Enabled = true; browser.Enabled = true;
Cursor = Cursors.Default; Cursor = Cursors.Default;
if (Focused){ // re-focus browser only if the window or a child is activated if (Focused) { // re-focus browser only if the window or a child is activated
browser.Focus(); browser.Focus();
} }
} }
@@ -45,52 +46,52 @@ namespace TweetDuck.Browser{
} }
public UpdateInstaller UpdateInstaller { get; private set; } public UpdateInstaller UpdateInstaller { get; private set; }
private bool ignoreUpdateCheckError;
public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
#pragma warning disable IDE0069 // Disposable fields should be disposed #pragma warning disable IDE0069 // Disposable fields should be disposed
private readonly TweetDeckBrowser browser; private readonly TweetDeckBrowser browser;
private readonly FormNotificationTweet notification; private readonly FormNotificationTweet notification;
#pragma warning restore IDE0069 // Disposable fields should be disposed #pragma warning restore IDE0069 // Disposable fields should be disposed
private readonly ResourceCache resourceCache;
private readonly ITweetDeckInterface tweetDeckInterface;
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly UpdateHandler updates; private readonly UpdateChecker updates;
private readonly ContextMenu contextMenu; private readonly ContextMenu contextMenu;
private readonly UpdateBridge updateBridge; private readonly uint windowRestoreMessage;
private bool isLoaded; private bool isLoaded;
private FormWindowState prevState; private FormWindowState prevState;
private TweetScreenshotManager notificationScreenshotManager; private TweetScreenshotManager notificationScreenshotManager;
private VideoPlayer videoPlayer; private VideoPlayer videoPlayer;
private AnalyticsManager analytics;
public FormBrowser(PluginSchemeFactory pluginScheme){ public FormBrowser(ResourceCache resourceCache, PluginManager pluginManager, IUpdateCheckClient updateCheckClient, uint windowRestoreMessage) {
InitializeComponent(); InitializeComponent();
Text = Program.BrandName; Text = Program.BrandName;
this.plugins = new PluginManager(Program.Config.Plugins, Program.PluginPath, Program.PluginDataPath); this.resourceCache = resourceCache;
this.plugins.Reloaded += plugins_Reloaded;
this.plugins.Executed += plugins_Executed;
this.plugins.Reload();
pluginScheme.Setup(plugins);
this.notification = new FormNotificationTweet(this, plugins); this.plugins = pluginManager;
this.tweetDeckInterface = new TweetDeckInterfaceImpl(this);
this.notification = new FormNotificationTweet(this, tweetDeckInterface, plugins);
this.notification.Show(); this.notification.Show();
this.updates = new UpdateHandler(new UpdateCheckClient(Program.InstallerPath), TaskScheduler.FromCurrentSynchronizationContext()); this.updates = new UpdateChecker(updateCheckClient, TaskScheduler.FromCurrentSynchronizationContext());
this.updates.CheckFinished += updates_CheckFinished; this.updates.InteractionManager.UpdateAccepted += updateInteractionManager_UpdateAccepted;
this.updates.InteractionManager.UpdateDismissed += updateInteractionManager_UpdateDismissed;
this.updateBridge = new UpdateBridge(updates, this); this.browser = new TweetDeckBrowser(this, plugins, tweetDeckInterface, updates);
this.updateBridge.UpdateAccepted += updateBridge_UpdateAccepted;
this.updateBridge.UpdateDismissed += updateBridge_UpdateDismissed;
this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge.Browser(this, notification), updateBridge);
this.contextMenu = ContextMenuBrowser.CreateMenu(this); this.contextMenu = ContextMenuBrowser.CreateMenu(this);
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update this.windowRestoreMessage = windowRestoreMessage;
Controls.Add(new MenuStrip { Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
Config.MuteToggled += Config_MuteToggled;
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
Disposed += (sender, args) => { Disposed += (sender, args) => {
Config.MuteToggled -= Config_MuteToggled; Config.MuteToggled -= Config_MuteToggled;
@@ -98,27 +99,20 @@ namespace TweetDuck.Browser{
browser.Dispose(); browser.Dispose();
}; };
Config.MuteToggled += Config_MuteToggled;
this.trayIcon.ClickRestore += trayIcon_ClickRestore; this.trayIcon.ClickRestore += trayIcon_ClickRestore;
this.trayIcon.ClickClose += trayIcon_ClickClose; this.trayIcon.ClickClose += trayIcon_ClickClose;
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
UpdateTray(); UpdateTray();
if (Config.MuteNotifications){ if (Config.MuteNotifications) {
UpdateFormIcon(); UpdateFormIcon();
} }
if (Config.AllowDataCollection){
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
}
RestoreWindow(); RestoreWindow();
} }
protected override void Dispose(bool disposing){ protected override void Dispose(bool disposing) {
if (disposing){ if (disposing) {
components?.Dispose(); components?.Dispose();
updates.Dispose(); updates.Dispose();
@@ -126,25 +120,24 @@ namespace TweetDuck.Browser{
notificationScreenshotManager?.Dispose(); notificationScreenshotManager?.Dispose();
videoPlayer?.Dispose(); videoPlayer?.Dispose();
analytics?.Dispose();
} }
base.Dispose(disposing); base.Dispose(disposing);
} }
private void ShowChildForm(Form form){ private void ShowChildForm(Form form) {
form.VisibleChanged += (sender, args) => form.MoveToCenter(this); form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
form.Show(this); form.Show(this);
} }
public void ForceClose(){ public void ForceClose() {
trayIcon.Visible = false; // checked in FormClosing event trayIcon.Visible = false; // checked in FormClosing event
Close(); Close();
} }
// window setup // window setup
private void RestoreWindow(){ private void RestoreWindow() {
Config.BrowserWindow.Restore(this, true); Config.BrowserWindow.Restore(this, true);
browser.PrepareSize(ClientSize); browser.PrepareSize(ClientSize);
@@ -152,34 +145,34 @@ namespace TweetDuck.Browser{
isLoaded = true; isLoaded = true;
} }
private void UpdateFormIcon(){ // TODO fix to show icon in taskbar too private void UpdateFormIcon() { // TODO fix to show icon in taskbar too
Icon = Config.MuteNotifications ? Properties.Resources.icon_muted : Properties.Resources.icon; Icon = Config.MuteNotifications ? Properties.Resources.icon_muted : Properties.Resources.icon;
} }
private void UpdateTray(){ private void UpdateTray() {
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon(); trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
} }
// event handlers // event handlers
private void timerResize_Tick(object sender, EventArgs e){ private void timerResize_Tick(object sender, EventArgs e) {
FormBrowser_ResizeEnd(this, e); // also stops timer FormBrowser_ResizeEnd(this, e); // also stops timer
} }
private void FormBrowser_Activated(object sender, EventArgs e){ private void FormBrowser_Activated(object sender, EventArgs e) {
if (!isLoaded){ if (!isLoaded) {
return; return;
} }
trayIcon.HasNotifications = false; trayIcon.HasNotifications = false;
if (!browser.Enabled){ // when taking a screenshot, the window is unfocused and if (!browser.Enabled) { // when taking a screenshot, the window is unfocused and
browser.Enabled = true; // the browser is disabled; if the user clicks back into browser.Enabled = true; // the browser is disabled; if the user clicks back into
} // the window, enable the browser again } // the window, enable the browser again
} }
private void FormBrowser_LocationChanged(object sender, EventArgs e){ private void FormBrowser_LocationChanged(object sender, EventArgs e) {
if (!isLoaded){ if (!isLoaded) {
return; return;
} }
@@ -187,147 +180,109 @@ namespace TweetDuck.Browser{
timerResize.Start(); timerResize.Start();
} }
private void FormBrowser_Resize(object sender, EventArgs e){ private void FormBrowser_Resize(object sender, EventArgs e) {
if (!isLoaded){ if (!isLoaded) {
return; return;
} }
if (WindowState != prevState){ if (WindowState != prevState) {
prevState = WindowState; prevState = WindowState;
if (WindowState == FormWindowState.Minimized){ if (WindowState == FormWindowState.Minimized) {
if (Config.TrayBehavior.ShouldHideOnMinimize()){ if (Config.TrayBehavior.ShouldHideOnMinimize()) {
Hide(); // hides taskbar too?! welp that works I guess Hide(); // hides taskbar too?! welp that works I guess
} }
} }
else{ else {
FormBrowser_ResizeEnd(sender, e); FormBrowser_ResizeEnd(sender, e);
} }
} }
else{ else {
timerResize.Stop(); timerResize.Stop();
timerResize.Start(); timerResize.Start();
} }
} }
private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves private void FormBrowser_ResizeEnd(object sender, EventArgs e) { // also triggers when the window moves
if (!isLoaded){ if (!isLoaded) {
return; return;
} }
timerResize.Stop(); timerResize.Stop();
browser.PrepareSize(ClientSize); // needed to pre-size browser control when launched in maximized state browser.PrepareSize(ClientSize); // needed to pre-size browser control when launched in maximized state
if (Location != ControlExtensions.InvisibleLocation){ if (Location != ControlExtensions.InvisibleLocation) {
Config.BrowserWindow.Save(this); Config.BrowserWindow.Save(this);
Config.Save(); Config.Save();
} }
} }
private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e){ private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e) {
if (!isLoaded){ if (!isLoaded) {
return; return;
} }
if (Config.TrayBehavior.ShouldHideOnClose() && trayIcon.Visible && e.CloseReason == CloseReason.UserClosing){ if (Config.TrayBehavior.ShouldHideOnClose() && trayIcon.Visible && e.CloseReason == CloseReason.UserClosing) {
Hide(); // hides taskbar too?! welp that works I guess Hide(); // hides taskbar too?! welp that works I guess
e.Cancel = true; e.Cancel = true;
} }
} }
private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e){ private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e) {
if (isLoaded && UpdateInstaller == null){ if (isLoaded && UpdateInstaller == null) {
updateBridge.Cleanup(); updates.InteractionManager.ClearUpdate();
updates.InteractionManager.Dispose();
} }
} }
private void Config_MuteToggled(object sender, EventArgs e){ private void Config_MuteToggled(object sender, EventArgs e) {
UpdateFormIcon(); UpdateFormIcon();
AnalyticsFile.NotificationMutes.Trigger();
} }
private void Config_TrayBehaviorChanged(object sender, EventArgs e){ private void Config_TrayBehaviorChanged(object sender, EventArgs e) {
UpdateTray(); UpdateTray();
} }
private void trayIcon_ClickRestore(object sender, EventArgs e){ private void trayIcon_ClickRestore(object sender, EventArgs e) {
Show(); Show();
RestoreWindow(); RestoreWindow();
Activate(); Activate();
UpdateTray(); UpdateTray();
} }
private void trayIcon_ClickClose(object sender, EventArgs e){ private void trayIcon_ClickClose(object sender, EventArgs e) {
ForceClose(); ForceClose();
} }
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){ private void updateInteractionManager_UpdateAccepted(object sender, UpdateInfo update) {
if (e.HasErrors){
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n" + string.Join("\n\n", e.Errors), FormMessage.OK);
}
if (isLoaded){
browser.ReloadToTweetDeck();
}
}
private void plugins_Executed(object sender, PluginErrorEventArgs e){
if (e.HasErrors){
this.InvokeAsyncSafe(() => { this.InvokeAsyncSafe(() => {
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n" + string.Join("\n\n", e.Errors), FormMessage.OK);
});
}
}
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
e.Result.Handle(update => {
string tag = update.VersionTag;
if (tag != Program.VersionTag && tag != Config.DismissedUpdate){
update.BeginSilentDownload();
browser.ShowUpdateNotification(tag, update.ReleaseNotes);
}
else{
updates.StartTimer();
}
}, ex => {
if (!ignoreUpdateCheckError){
Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
updates.StartTimer();
}
});
ignoreUpdateCheckError = true;
}
private void updateBridge_UpdateAccepted(object sender, UpdateInfo update){
FormManager.CloseAllDialogs(); FormManager.CloseAllDialogs();
if (!string.IsNullOrEmpty(Config.DismissedUpdate)){ if (!string.IsNullOrEmpty(Config.DismissedUpdate)) {
Config.DismissedUpdate = null; Config.DismissedUpdate = null;
Config.Save(); Config.Save();
} }
void OnFinished(){ void OnFinished() {
UpdateDownloadStatus status = update.DownloadStatus; UpdateDownloadStatus status = update.DownloadStatus;
if (status == UpdateDownloadStatus.Done){ if (status == UpdateDownloadStatus.Done) {
UpdateInstaller = new UpdateInstaller(update.InstallerPath); UpdateInstaller = new UpdateInstaller(update.InstallerPath);
ForceClose(); ForceClose();
} }
else if (status != UpdateDownloadStatus.Canceled && FormMessage.Error("Update Has Failed", "Could not automatically download the update: " + (update.DownloadError?.Message ?? "unknown error") + "\n\nWould you like to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){ else if (status != UpdateDownloadStatus.Canceled && FormMessage.Error("Update Has Failed", "Could not automatically download the update: " + (update.DownloadError?.Message ?? "unknown error") + "\n\nWould you like to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)) {
BrowserUtils.OpenExternalBrowser(Program.Website); App.SystemHandler.OpenBrowser(Program.Website);
ForceClose(); ForceClose();
} }
else{ else {
Show(); Show();
} }
} }
if (update.DownloadStatus.IsFinished(true)){ if (update.DownloadStatus.IsFinished(true)) {
OnFinished(); OnFinished();
} }
else{ else {
FormUpdateDownload downloadForm = new FormUpdateDownload(update); FormUpdateDownload downloadForm = new FormUpdateDownload(update);
downloadForm.VisibleChanged += (sender2, args2) => { downloadForm.VisibleChanged += (sender2, args2) => {
@@ -336,7 +291,7 @@ namespace TweetDuck.Browser{
}; };
downloadForm.FormClosed += (sender2, args2) => { downloadForm.FormClosed += (sender2, args2) => {
if (downloadForm.DialogResult != DialogResult.OK){ if (downloadForm.DialogResult != DialogResult.OK) {
update.CancelDownload(); update.CancelDownload();
} }
@@ -346,31 +301,33 @@ namespace TweetDuck.Browser{
downloadForm.Show(); downloadForm.Show();
} }
});
} }
private void updateBridge_UpdateDismissed(object sender, UpdateInfo update){ private void updateInteractionManager_UpdateDismissed(object sender, UpdateInfo update) {
this.InvokeAsyncSafe(() => {
Config.DismissedUpdate = update.VersionTag; Config.DismissedUpdate = update.VersionTag;
Config.Save(); Config.Save();
});
} }
protected override void WndProc(ref Message m){ protected override void WndProc(ref Message m) {
if (isLoaded && m.Msg == Program.WindowRestoreMessage){ if (isLoaded && m.Msg == windowRestoreMessage) {
using Process me = Process.GetCurrentProcess(); using Process me = Process.GetCurrentProcess();
if (me.Id == m.WParam.ToInt32()){ if (me.Id == m.WParam.ToInt32()) {
trayIcon_ClickRestore(trayIcon, EventArgs.Empty); trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
} }
return; return;
} }
if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){ if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN) {
if (videoPlayer != null && videoPlayer.Running){ if (videoPlayer is { Running: true }) {
videoPlayer.Close(); videoPlayer.Close();
} }
else{ else {
browser.OnMouseClickExtra(m.WParam); browser.Functions.OnMouseClickExtra((m.WParam.ToInt32() >> 16) & 0xFFFF);
AnalyticsFile.BrowserExtraMouseButtons.Trigger();
} }
return; return;
@@ -381,164 +338,135 @@ namespace TweetDuck.Browser{
// bridge methods // bridge methods
public void PauseNotification(){ public void PauseNotification() {
notification.PauseNotification(); notification.PauseNotification();
} }
public void ResumeNotification(){ public void ResumeNotification() {
notification.ResumeNotification(); notification.ResumeNotification();
} }
public void ReinjectCustomCSS(string css){ public void ReloadToTweetDeck() {
browser.ReinjectCustomCSS(css); #if DEBUG
Resources.ResourceHotSwap.Run();
resourceCache.ClearCache();
#else
if (ModifierKeys.HasFlag(Keys.Shift)) {
resourceCache.ClearCache();
} }
#endif
public void ReloadToTweetDeck(){
Program.Resources.OnReloadTriggered();
ignoreUpdateCheckError = false;
browser.ReloadToTweetDeck(); browser.ReloadToTweetDeck();
AnalyticsFile.BrowserReloads.Trigger();
} }
public void AddSearchColumn(string query){ public void OpenDevTools() {
browser.AddSearchColumn(query);
}
public void TriggerTweetScreenshot(){
browser.TriggerTweetScreenshot();
}
public void ReloadColumns(){
browser.ReloadColumns();
}
public void PlaySoundNotification(){
browser.PlaySoundNotification();
}
public void ApplyROT13(){
browser.ApplyROT13();
AnalyticsFile.UsedROT13.Trigger();
}
public void OpenDevTools(){
browser.OpenDevTools(); browser.OpenDevTools();
} }
// callback handlers // callback handlers
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){ private void OnIntroductionClosed(bool showGuide) {
if (Config.FirstRun){ if (Config.FirstRun) {
Config.FirstRun = false; Config.FirstRun = false;
Config.AllowDataCollection = allowDataCollection;
Config.Save(); Config.Save();
if (allowDataCollection && analytics == null){
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
}
} }
if (showGuide){ if (showGuide) {
FormGuide.Show(); FormGuide.Show();
} }
} }
public void OpenContextMenu(){ private void OpenContextMenu() {
contextMenu.Show(this, PointToClient(Cursor.Position)); contextMenu.Show(this, PointToClient(Cursor.Position));
} }
public void OpenSettings(){ public void OpenSettings() {
OpenSettings(null); OpenSettings(null);
} }
public void OpenSettings(Type startTab){ public void OpenSettings(Type startTab) {
if (!FormManager.TryBringToFront<FormSettings>()){ if (!FormManager.TryBringToFront<FormSettings>()) {
bool prevEnableUpdateCheck = Config.EnableUpdateCheck; bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
FormSettings form = new FormSettings(this, plugins, updates, analytics, startTab); FormSettings form = new FormSettings(this, plugins, updates, browser.Functions, startTab);
form.FormClosed += (sender, args) => { form.FormClosed += (sender, args) => {
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){ if (!prevEnableUpdateCheck && Config.EnableUpdateCheck) {
Config.DismissedUpdate = null; Config.DismissedUpdate = null;
Config.Save(); Config.Save();
updates.Check(true); updates.Check(true);
} }
if (!Config.EnableTrayHighlight){ if (!Config.EnableTrayHighlight) {
trayIcon.HasNotifications = false; trayIcon.HasNotifications = false;
} }
if (Config.AllowDataCollection){
if (analytics == null){
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
}
}
else if (analytics != null){
analytics.Dispose();
analytics = null;
}
BrowserCache.RefreshTimer(); BrowserCache.RefreshTimer();
if (form.ShouldReloadBrowser){ if (form.ShouldReloadBrowser) {
FormManager.TryFind<FormPlugins>()?.Close(); FormManager.TryFind<FormPlugins>()?.Close();
plugins.Reload(); // also reloads the browser plugins.Reload(); // also reloads the browser
} }
else{ else {
browser.UpdateProperties(); Program.Config.User.TriggerOptionsDialogClosed();
} }
notification.RequiresResize = true; notification.RequiresResize = true;
form.Dispose(); form.Dispose();
}; };
AnalyticsFile.OpenOptions.Trigger();
ShowChildForm(form); ShowChildForm(form);
} }
} }
public void OpenAbout(){ public void OpenAbout() {
if (!FormManager.TryBringToFront<FormAbout>()){ if (!FormManager.TryBringToFront<FormAbout>()) {
AnalyticsFile.OpenAbout.Trigger();
ShowChildForm(new FormAbout()); ShowChildForm(new FormAbout());
} }
} }
public void OpenPlugins(){ public void OpenPlugins() {
if (!FormManager.TryBringToFront<FormPlugins>()){ if (!FormManager.TryBringToFront<FormPlugins>()) {
AnalyticsFile.OpenPlugins.Trigger();
ShowChildForm(new FormPlugins(plugins)); ShowChildForm(new FormPlugins(plugins));
} }
} }
public void OpenProfileImport(){ private void OpenProfileImport() {
FormManager.TryFind<FormSettings>()?.Close(); FormManager.TryFind<FormSettings>()?.Close();
using DialogSettingsManage dialog = new DialogSettingsManage(plugins, true); using DialogSettingsManage dialog = new DialogSettingsManage(plugins, true);
if (!dialog.IsDisposed && dialog.ShowDialog() == DialogResult.OK && !dialog.IsRestarting){ // needs disposal check because the dialog may be closed in constructor if (!dialog.IsDisposed && dialog.ShowDialog() == DialogResult.OK && !dialog.IsRestarting) { // needs disposal check because the dialog may be closed in constructor
BrowserProcessHandler.UpdatePrefs(); BrowserProcessHandler.UpdatePrefs();
FormManager.TryFind<FormPlugins>()?.Close(); FormManager.TryFind<FormPlugins>()?.Close();
plugins.Reload(); // also reloads the browser plugins.Reload(); // also reloads the browser
} }
} }
public void OnTweetNotification(){ // may be called multiple times, once for each type of notification private void ShowDesktopNotification(DesktopNotification notification) {
if (Config.EnableTrayHighlight && !ContainsFocus){ this.notification.ShowNotification(notification);
}
private void OnTweetNotification() { // may be called multiple times, once for each type of notification
if (Config.EnableTrayHighlight && !ContainsFocus) {
trayIcon.HasNotifications = true; trayIcon.HasNotifications = true;
} }
} }
public void OnTweetSound(){ public void SaveVideo(string url, string username) {
AnalyticsFile.SoundNotifications.Trigger(); browser.SaveVideo(url, username);
}
private void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay) {
if (Arguments.HasFlag(Arguments.ArgHttpVideo)) {
videoUrl = Regex.Replace(videoUrl, "^https://", "http://");
} }
public void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay){
string playerPath = Config.VideoPlayerPath; string playerPath = Config.VideoPlayerPath;
if (playerPath == null || !File.Exists(playerPath)){ if (playerPath == null || !File.Exists(playerPath)) {
if (videoPlayer == null){ if (videoPlayer == null) {
videoPlayer = new VideoPlayer(this); videoPlayer = new VideoPlayer(this);
videoPlayer.ProcessExited += (sender, args) => browser.HideVideoOverlay(true); videoPlayer.ProcessExited += (sender, args) => browser.HideVideoOverlay(true);
} }
@@ -548,28 +476,58 @@ namespace TweetDuck.Browser{
videoPlayer.Launch(videoUrl, tweetUrl, username); videoPlayer.Launch(videoUrl, tweetUrl, username);
} }
else{ else {
callShowOverlay.Dispose(); callShowOverlay.Dispose();
string quotedUrl = '"' + videoUrl + '"'; string quotedUrl = '"' + videoUrl + '"';
string playerArgs = Config.VideoPlayerPathArgs == null ? quotedUrl : Config.VideoPlayerPathArgs + ' ' + quotedUrl; string playerArgs = Config.VideoPlayerPathArgs == null ? quotedUrl : Config.VideoPlayerPathArgs + ' ' + quotedUrl;
try{ try {
using(Process.Start(playerPath, playerArgs)){} using (Process.Start(playerPath, playerArgs)) {}
}catch(Exception e){ } catch (Exception e) {
Program.Reporter.HandleException("Error Opening Video Player", "Could not open the video player.", true, e); App.ErrorHandler.HandleException("Error Opening Video Player", "Could not open the video player.", true, e);
}
} }
} }
AnalyticsFile.VideoPlays.Trigger(); private void StopVideo() {
}
public void StopVideo(){
videoPlayer?.Close(); videoPlayer?.Close();
} }
public bool ProcessBrowserKey(Keys key){ public bool ShowTweetDetail(string columnId, string chirpId, string fallbackUrl) {
if (videoPlayer != null && videoPlayer.Running){ Activate();
if (!browser.IsTweetDeckWebsite) {
FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
return false;
}
browser.Functions.ShowTweetDetail(columnId, chirpId, fallbackUrl);
return true;
}
private void OnTweetScreenshotReady(string html, int width) {
notificationScreenshotManager ??= new TweetScreenshotManager(this, plugins);
notificationScreenshotManager.Trigger(html, width);
}
private void DisplayTooltip(string text) {
if (string.IsNullOrEmpty(text)) {
toolTip.Hide(this);
}
else {
Point position = PointToClient(Cursor.Position);
position.Offset(20, 10);
toolTip.Show(text, this, position);
}
}
public FormNotificationExample CreateExampleNotification() {
return new FormNotificationExample(this, tweetDeckInterface, plugins);
}
bool CustomKeyboardHandler.IBrowserKeyHandler.HandleBrowserKey(Keys key) {
if (videoPlayer is { Running: true }) {
videoPlayer.SendKeyEvent(key); videoPlayer.SendKeyEvent(key);
return true; return true;
} }
@@ -577,36 +535,73 @@ namespace TweetDuck.Browser{
return false; return false;
} }
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){ private sealed class TweetDeckInterfaceImpl : ITweetDeckInterface {
Activate(); private readonly FormBrowser form;
if (!browser.IsTweetDeckWebsite){ public TweetDeckInterfaceImpl(FormBrowser form) {
FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK); this.form = form;
return;
} }
notification.FinishCurrentNotification(); public void Alert(string type, string contents) {
browser.ShowTweetDetail(columnId, chirpId, fallbackUrl); MessageBoxIcon icon = type switch {
AnalyticsFile.TweetDetails.Trigger(); "error" => MessageBoxIcon.Error,
"warning" => MessageBoxIcon.Warning,
"info" => MessageBoxIcon.Information,
_ => MessageBoxIcon.None
};
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
} }
public void OnTweetScreenshotReady(string html, int width){ public void DisplayTooltip(string text) {
if (notificationScreenshotManager == null){ form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
} }
notificationScreenshotManager.Trigger(html, width); public void FixClipboard() {
AnalyticsFile.TweetScreenshots.Trigger(); form.InvokeAsyncSafe(ClipboardManager.StripHtmlStyles);
} }
public void DisplayTooltip(string text){ public int GetIdleSeconds() {
if (string.IsNullOrEmpty(text)){ return NativeMethods.GetIdleSeconds();
toolTip.Hide(this);
} }
else{
Point position = PointToClient(Cursor.Position); public void OnIntroductionClosed(bool showGuide) {
position.Offset(20, 10); form.InvokeAsyncSafe(() => form.OnIntroductionClosed(showGuide));
toolTip.Show(text, this, position); }
public void OnSoundNotification() {
form.InvokeAsyncSafe(form.OnTweetNotification);
}
public void OpenContextMenu() {
form.InvokeAsyncSafe(form.OpenContextMenu);
}
public void OpenProfileImport() {
form.InvokeAsyncSafe(form.OpenProfileImport);
}
public void PlayVideo(string videoUrl, string tweetUrl, string username, object callShowOverlay) {
form.InvokeAsyncSafe(() => form.PlayVideo(videoUrl, tweetUrl, username, (IJavascriptCallback) callShowOverlay));
}
public void ScreenshotTweet(string html, int width) {
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
}
public void ShowDesktopNotification(DesktopNotification notification) {
form.InvokeAsyncSafe(() => {
form.OnTweetNotification();
form.ShowDesktopNotification(notification);
});
}
public void StopVideo() {
form.InvokeAsyncSafe(form.StopVideo);
}
public Task ExecuteCallback(object callback, params object[] parameters) {
return ((IJavascriptCallback) callback).ExecuteAsync(parameters);
} }
} }
} }

View File

@@ -0,0 +1,28 @@
using System;
using System.Threading.Tasks;
using CefSharp;
using TweetDuck.Configuration;
namespace TweetDuck.Browser.Handling {
sealed class BrowserProcessHandler : IBrowserProcessHandler {
public static Task UpdatePrefs() {
return Cef.UIThreadTaskFactory.StartNew(UpdatePrefsInternal);
}
private static void UpdatePrefsInternal() {
UserConfig config = Program.Config.User;
using IRequestContext ctx = Cef.GetGlobalRequestContext();
ctx.SetPreference("browser.enable_spellchecking", config.EnableSpellCheck, out string _);
ctx.SetPreference("spellcheck.dictionary", config.SpellCheckLanguage, out string _);
ctx.SetPreference("settings.a11y.animation_policy", config.EnableAnimatedImages ? "allowed" : "none", out string _);
}
void IBrowserProcessHandler.OnContextInitialized() {
UpdatePrefsInternal();
}
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay) {}
void IDisposable.Dispose() {}
}
}

View File

@@ -1,242 +1,107 @@
using System; using System.Collections.Generic;
using System.Linq; using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using CefSharp; using CefSharp;
using TweetDuck.Browser.Adapters; using TweetDuck.Browser.Adapters;
using TweetDuck.Browser.Data;
using TweetDuck.Browser.Notification;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Controls;
using TweetDuck.Dialogs;
using TweetDuck.Management;
using TweetDuck.Management.Analytics;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core.Features.Twitter; using TweetLib.Browser.Contexts;
using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
abstract class ContextMenuBase : IContextMenuHandler{ abstract class ContextMenuBase : IContextMenuHandler {
public static ContextInfo CurrentInfo { get; } = new ContextInfo(); private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand) 26500;
private static readonly HashSet<CefMenuCommand> AllowedCefCommands = new HashSet<CefMenuCommand> {
CefMenuCommand.NotFound,
CefMenuCommand.Undo,
CefMenuCommand.Redo,
CefMenuCommand.Cut,
CefMenuCommand.Copy,
CefMenuCommand.Paste,
CefMenuCommand.Delete,
CefMenuCommand.SelectAll,
CefMenuCommand.SpellCheckSuggestion0,
CefMenuCommand.SpellCheckSuggestion1,
CefMenuCommand.SpellCheckSuggestion2,
CefMenuCommand.SpellCheckSuggestion3,
CefMenuCommand.SpellCheckSuggestion4,
CefMenuCommand.SpellCheckNoSuggestions,
CefMenuCommand.AddToDictionary
};
protected static UserConfig Config => Program.Config.User; protected static UserConfig Config => Program.Config.User;
private static ImageQuality ImageQuality => Config.TwitterImageQuality;
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500; private readonly TweetLib.Browser.Interfaces.IContextMenuHandler handler;
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501; private readonly CefContextMenuActionRegistry actionRegistry;
private const CefMenuCommand MenuCopyUsername = (CefMenuCommand)26502;
private const CefMenuCommand MenuViewImage = (CefMenuCommand)26503;
private const CefMenuCommand MenuOpenMediaUrl = (CefMenuCommand)26504;
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26505;
private const CefMenuCommand MenuCopyImage = (CefMenuCommand)26506;
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26507;
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26508;
private const CefMenuCommand MenuSearchInBrowser = (CefMenuCommand)26509;
private const CefMenuCommand MenuReadApplyROT13 = (CefMenuCommand)26510;
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
protected ContextInfo.ContextData Context { get; private set; } protected ContextMenuBase(TweetLib.Browser.Interfaces.IContextMenuHandler handler) {
this.handler = handler;
private readonly AnalyticsFile.IProvider analytics; this.actionRegistry = new CefContextMenuActionRegistry();
protected ContextMenuBase(AnalyticsFile.IProvider analytics){
this.analytics = analytics;
} }
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ protected virtual Context CreateContext(IContextMenuParams parameters) {
if (!TwitterUrls.IsTweetDeck(frame.Url) || browser.IsLoading){ return CefContextMenuModel.CreateContext(parameters, null, Config.TwitterImageQuality);
Context = CurrentInfo.Reset();
}
else{
Context = CurrentInfo.Create(parameters);
} }
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){ public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
model.AddItem(MenuSearchInBrowser, "Search in browser"); for (int i = model.Count - 1; i >= 0; i--) {
model.AddSeparator(); CefMenuCommand command = model.GetCommandIdAt(i);
model.AddItem(MenuReadApplyROT13, "Apply ROT13");
model.AddSeparator();
}
static string TextOpen(string name) => "Open " + name + " in browser"; if (!AllowedCefCommands.Contains(command) && !(command >= CefMenuCommand.CustomFirst && command <= CefMenuCommand.CustomLast)) {
static string TextCopy(string name) => "Copy " + name + " address"; model.RemoveAt(i);
static string TextSave(string name) => "Save " + name + " as...";
if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
if (TwitterUrls.RegexAccount.IsMatch(Context.UnsafeLinkUrl)){
model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
model.AddItem(MenuCopyUsername, "Copy account username");
}
else{
model.AddItem(MenuOpenLinkUrl, TextOpen("link"));
model.AddItem(MenuCopyLinkUrl, TextCopy("link"));
}
model.AddSeparator();
}
if (Context.Types.HasFlag(ContextInfo.ContextType.Video)){
model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
model.AddItem(MenuCopyMediaUrl, TextCopy("video"));
model.AddItem(MenuSaveMedia, TextSave("video"));
model.AddSeparator();
}
else if (Context.Types.HasFlag(ContextInfo.ContextType.Image) && Context.MediaUrl != FormNotificationBase.AppLogo.Url){
model.AddItem(MenuViewImage, "View image in photo viewer");
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
model.AddItem(MenuCopyImage, "Copy image");
model.AddItem(MenuSaveMedia, TextSave("image"));
if (Context.Chirp.Images.Length > 1){
model.AddItem(MenuSaveTweetImages, TextSave("all images"));
}
model.AddSeparator();
} }
} }
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ for (int i = model.Count - 2; i >= 0; i--) {
Control control = browserControl.AsControl(); if (model.GetTypeAt(i) == MenuItemType.Separator && model.GetTypeAt(i + 1) == MenuItemType.Separator) {
model.RemoveAt(i);
switch(commandId){ }
case MenuOpenLinkUrl:
OpenBrowser(control, Context.LinkUrl);
break;
case MenuCopyLinkUrl:
SetClipboardText(control, Context.UnsafeLinkUrl);
break;
case MenuCopyUsername: {
string url = Context.UnsafeLinkUrl;
Match match = TwitterUrls.RegexAccount.Match(url);
SetClipboardText(control, match.Success ? match.Groups[1].Value : url);
control.InvokeAsyncSafe(analytics.AnalyticsFile.CopiedUsernames.Trigger);
break;
} }
case MenuOpenMediaUrl: if (model.Count > 0 && model.GetTypeAt(0) == MenuItemType.Separator) {
OpenBrowser(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality)); model.RemoveAt(0);
break;
case MenuCopyMediaUrl:
SetClipboardText(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality));
break;
case MenuCopyImage: {
string url = Context.MediaUrl;
control.InvokeAsyncSafe(() => {
TwitterUtils.CopyImage(url, ImageQuality);
});
break;
} }
case MenuViewImage: { AddSeparator(model);
string url = Context.MediaUrl; handler.Show(new CefContextMenuModel(model, actionRegistry), CreateContext(parameters));
RemoveSeparatorIfLast(model);
control.InvokeAsyncSafe(() => {
TwitterUtils.ViewImage(url, ImageQuality);
analytics.AnalyticsFile.ViewedImages.Trigger();
});
break;
} }
case MenuSaveMedia: { public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
bool isVideo = Context.Types.HasFlag(ContextInfo.ContextType.Video); if (actionRegistry.Execute(commandId)) {
string url = Context.MediaUrl;
string username = Context.Chirp.Authors.LastOrDefault();
control.InvokeAsyncSafe(() => {
if (isVideo){
TwitterUtils.DownloadVideo(url, username);
analytics.AnalyticsFile.DownloadedVideos.Trigger();
}
else{
TwitterUtils.DownloadImage(url, username, ImageQuality);
analytics.AnalyticsFile.DownloadedImages.Trigger();
}
});
break;
}
case MenuSaveTweetImages: {
string[] urls = Context.Chirp.Images;
string username = Context.Chirp.Authors.LastOrDefault();
control.InvokeAsyncSafe(() => {
TwitterUtils.DownloadImages(urls, username, ImageQuality);
analytics.AnalyticsFile.DownloadedImages.Trigger();
});
break;
}
case MenuReadApplyROT13:
string selection = parameters.SelectionText;
control.InvokeAsyncSafe(() => FormMessage.Information("ROT13", StringUtils.ConvertRot13(selection), FormMessage.OK));
control.InvokeAsyncSafe(analytics.AnalyticsFile.UsedROT13.Trigger);
return true; return true;
}
case MenuSearchInBrowser: if (commandId == MenuOpenDevTools) {
string query = parameters.SelectionText; browserControl.OpenDevToolsCustom(new Point(parameters.XCoord, parameters.YCoord));
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalSearch(query)); return true;
DeselectAll(frame);
break;
case MenuOpenDevTools:
browserControl.OpenDevToolsCustom();
break;
} }
return false; return false;
} }
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){ public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
Context = CurrentInfo.Reset(); actionRegistry.Clear();
} }
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){ public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback) {
return false; return false;
} }
protected static void DeselectAll(IFrame frame){ protected static void AddDebugMenuItems(IMenuModel model) {
CefScriptExecutor.RunScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect"); if (Config.DevToolsInContextMenu) {
}
protected static void OpenBrowser(Control control, string url){
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
}
protected static void SetClipboardText(Control control, string text){
control.InvokeAsyncSafe(() => ClipboardManager.SetText(text, TextDataFormat.UnicodeText));
}
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel){
model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser) + 1, insertCommand, insertLabel);
}
protected static void AddDebugMenuItems(IMenuModel model){
if (BrowserUtils.HasDevTools){
AddSeparator(model); AddSeparator(model);
model.AddItem(MenuOpenDevTools, "Open dev tools"); model.AddItem(MenuOpenDevTools, "Open dev tools");
} }
} }
protected static void RemoveSeparatorIfLast(IMenuModel model){ protected static void RemoveSeparatorIfLast(IMenuModel model) {
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) == MenuItemType.Separator){ if (model.Count > 0 && model.GetTypeAt(model.Count - 1) == MenuItemType.Separator) {
model.RemoveAt(model.Count - 1); model.RemoveAt(model.Count - 1);
} }
} }
protected static void AddSeparator(IMenuModel model){ protected static void AddSeparator(IMenuModel model) {
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator){ // do not add separators if there is nothing to separate if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) { // do not add separators if there is nothing to separate
model.AddSeparator(); model.AddSeparator();
} }
} }

View File

@@ -1,24 +1,20 @@
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using TweetDuck.Browser.Data; using TweetDuck.Browser.Adapters;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetLib.Browser.Contexts;
using TweetLib.Core.Features.TweetDeck;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Systems.Configuration;
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class ContextMenuBrowser : ContextMenuBase{ sealed class ContextMenuBrowser : ContextMenuBase {
private const CefMenuCommand MenuGlobal = (CefMenuCommand)26600; private const CefMenuCommand MenuGlobal = (CefMenuCommand) 26600;
private const CefMenuCommand MenuMute = (CefMenuCommand)26601; private const CefMenuCommand MenuMute = (CefMenuCommand) 26601;
private const CefMenuCommand MenuSettings = (CefMenuCommand)26602; private const CefMenuCommand MenuSettings = (CefMenuCommand) 26602;
private const CefMenuCommand MenuPlugins = (CefMenuCommand)26003; private const CefMenuCommand MenuPlugins = (CefMenuCommand) 26003;
private const CefMenuCommand MenuAbout = (CefMenuCommand)26604; private const CefMenuCommand MenuAbout = (CefMenuCommand) 26604;
private const CefMenuCommand MenuOpenTweetUrl = (CefMenuCommand)26610;
private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand)26611;
private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand)26612;
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613;
private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand)26614;
private const CefMenuCommand MenuWriteApplyROT13 = (CefMenuCommand)26615;
private const CefMenuCommand MenuSearchInColumn = (CefMenuCommand)26616;
private const string TitleReloadBrowser = "Reload browser"; private const string TitleReloadBrowser = "Reload browser";
private const string TitleMuteNotifications = "Mute notifications"; private const string TitleMuteNotifications = "Mute notifications";
@@ -27,51 +23,28 @@ namespace TweetDuck.Browser.Handling{
private const string TitleAboutProgram = "About " + Program.BrandName; private const string TitleAboutProgram = "About " + Program.BrandName;
private readonly FormBrowser form; private readonly FormBrowser form;
private readonly TweetDeckExtraContext extraContext;
public ContextMenuBrowser(FormBrowser form) : base(form){ public ContextMenuBrowser(FormBrowser form, IContextMenuHandler handler, TweetDeckExtraContext extraContext) : base(handler) {
this.form = form; this.form = form;
this.extraContext = extraContext;
} }
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ protected override Context CreateContext(IContextMenuParams parameters) {
bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection); return CefContextMenuModel.CreateContext(parameters, extraContext, Config.TwitterImageQuality);
bool isEditing = parameters.TypeFlags.HasFlag(ContextMenuType.Editable);
model.Remove(CefMenuCommand.Back);
model.Remove(CefMenuCommand.Forward);
model.Remove(CefMenuCommand.Print);
model.Remove(CefMenuCommand.ViewSource);
RemoveSeparatorIfLast(model);
if (isSelecting){
if (isEditing){
model.AddSeparator();
model.AddItem(MenuWriteApplyROT13, "Apply ROT13");
} }
model.AddSeparator(); public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
if (!TwitterUrls.IsTweetDeck(frame.Url) || browser.IsLoading) {
extraContext.Reset();
} }
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
if (isSelecting && !isEditing && TwitterUrls.IsTweetDeck(frame.Url)){ bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection);
InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column"); bool isEditing = parameters.TypeFlags.HasFlag(ContextMenuType.Editable);
}
if (Context.Types.HasFlag(ContextInfo.ContextType.Chirp) && !isSelecting && !isEditing){ if (!isSelecting && !isEditing) {
model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
if (!string.IsNullOrEmpty(Context.Chirp.QuoteUrl)){
model.AddSeparator();
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
}
model.AddSeparator();
}
if (!isSelecting && !isEditing){
AddSeparator(model); AddSeparator(model);
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName); IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
@@ -87,18 +60,14 @@ namespace TweetDuck.Browser.Handling{
AddDebugMenuItems(globalMenu); AddDebugMenuItems(globalMenu);
} }
RemoveSeparatorIfLast(model);
form.InvokeAsyncSafe(form.AnalyticsFile.BrowserContextMenus.Trigger);
} }
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)){ if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)) {
return true; return true;
} }
switch(commandId){ switch (commandId) {
case CefMenuCommand.Reload: case CefMenuCommand.Reload:
form.InvokeAsyncSafe(form.ReloadToTweetDeck); form.InvokeAsyncSafe(form.ReloadToTweetDeck);
return true; return true;
@@ -119,41 +88,17 @@ namespace TweetDuck.Browser.Handling{
form.InvokeAsyncSafe(ToggleMuteNotifications); form.InvokeAsyncSafe(ToggleMuteNotifications);
return true; return true;
case MenuOpenTweetUrl: default:
OpenBrowser(form, Context.Chirp.TweetUrl);
return true;
case MenuCopyTweetUrl:
SetClipboardText(form, Context.Chirp.TweetUrl);
return true;
case MenuScreenshotTweet:
form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
return true;
case MenuOpenQuotedTweetUrl:
OpenBrowser(form, Context.Chirp.QuoteUrl);
return true;
case MenuCopyQuotedTweetUrl:
SetClipboardText(form, Context.Chirp.QuoteUrl);
return true;
case MenuWriteApplyROT13:
form.InvokeAsyncSafe(form.ApplyROT13);
return true;
case MenuSearchInColumn:
string query = parameters.SelectionText;
form.InvokeAsyncSafe(() => form.AddSearchColumn(query));
DeselectAll(frame);
break;
}
return false; return false;
} }
}
public static ContextMenu CreateMenu(FormBrowser form){ public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
base.OnContextMenuDismissed(browserControl, browser, frame);
extraContext.Reset();
}
public static ContextMenu CreateMenu(FormBrowser form) {
ContextMenu menu = new ContextMenu(); ContextMenu menu = new ContextMenu();
menu.MenuItems.Add(TitleReloadBrowser, (sender, args) => form.ReloadToTweetDeck()); menu.MenuItems.Add(TitleReloadBrowser, (sender, args) => form.ReloadToTweetDeck());
@@ -165,13 +110,12 @@ namespace TweetDuck.Browser.Handling{
menu.Popup += (sender, args) => { menu.Popup += (sender, args) => {
menu.MenuItems[1].Checked = Config.MuteNotifications; menu.MenuItems[1].Checked = Config.MuteNotifications;
form.AnalyticsFile.BrowserContextMenus.Trigger();
}; };
return menu; return menu;
} }
private static void ToggleMuteNotifications(){ private static void ToggleMuteNotifications() {
Config.MuteNotifications = !Config.MuteNotifications; Config.MuteNotifications = !Config.MuteNotifications;
Config.Save(); Config.Save();
} }

View File

@@ -1,12 +1,11 @@
using CefSharp; using CefSharp;
using TweetDuck.Management.Analytics; using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class ContextMenuGuide : ContextMenuBase{ sealed class ContextMenuGuide : ContextMenuBase {
public ContextMenuGuide(AnalyticsFile.IProvider analytics) : base(analytics){} public ContextMenuGuide(IContextMenuHandler handler) : base(handler) {}
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
model.Clear();
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
AddDebugMenuItems(model); AddDebugMenuItems(model);
} }

View File

@@ -1,92 +1,30 @@
using CefSharp; using CefSharp;
using TweetDuck.Browser.Notification; using TweetDuck.Browser.Notification;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetLib.Browser.Contexts;
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class ContextMenuNotification : ContextMenuBase{ sealed class ContextMenuNotification : ContextMenuBase {
private const CefMenuCommand MenuViewDetail = (CefMenuCommand)26600;
private const CefMenuCommand MenuSkipTweet = (CefMenuCommand)26601;
private const CefMenuCommand MenuFreeze = (CefMenuCommand)26602;
private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand)26603;
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26604;
private readonly FormNotificationBase form; private readonly FormNotificationBase form;
private readonly bool enableCustomMenu;
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){ public ContextMenuNotification(FormNotificationBase form, IContextMenuHandler handler) : base(handler) {
this.form = form; this.form = form;
this.enableCustomMenu = enableCustomMenu;
} }
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ protected override Context CreateContext(IContextMenuParams parameters) {
model.Clear(); Context context = base.CreateContext(parameters);
context.Notification = new TweetLib.Browser.Contexts.Notification(form.CurrentTweetUrl, form.CurrentQuoteUrl);
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){ return context;
model.AddItem(CefMenuCommand.Copy, "Copy");
model.AddSeparator();
} }
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
if (enableCustomMenu){
if (form.CanViewDetail){
model.AddItem(MenuViewDetail, "View detail");
}
model.AddItem(MenuSkipTweet, "Skip tweet");
model.AddCheckItem(MenuFreeze, "Freeze");
model.SetChecked(MenuFreeze, form.FreezeTimer);
if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){
model.AddSeparator();
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
}
}
}
AddDebugMenuItems(model); AddDebugMenuItems(model);
RemoveSeparatorIfLast(model); form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
form.InvokeAsyncSafe(() => {
form.ContextMenuOpen = true;
form.AnalyticsFile.NotificationContextMenus.Trigger();
});
} }
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)){
return true;
}
switch(commandId){
case MenuSkipTweet:
form.InvokeAsyncSafe(form.FinishCurrentNotification);
return true;
case MenuFreeze:
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
return true;
case MenuViewDetail:
form.InvokeSafe(form.ShowTweetDetail);
return true;
case MenuCopyTweetUrl:
SetClipboardText(form, form.CurrentTweetUrl);
return true;
case MenuCopyQuotedTweetUrl:
SetClipboardText(form, form.CurrentQuoteUrl);
return true;
}
return false;
}
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
base.OnContextMenuDismissed(browserControl, browser, frame); base.OnContextMenuDismissed(browserControl, browser, frame);
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false); form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
} }

View File

@@ -0,0 +1,43 @@
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Utils;
using TweetLib.Utils.Static;
namespace TweetDuck.Browser.Handling {
sealed class CustomKeyboardHandler : IKeyboardHandler {
private readonly IBrowserKeyHandler handler;
public CustomKeyboardHandler(IBrowserKeyHandler handler) {
this.handler = handler;
}
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut) {
if (type != KeyType.RawKeyDown) {
return false;
}
using (var frame = browser.FocusedFrame) {
if (frame.Url.StartsWithOrdinal("devtools://")) {
return false;
}
}
Keys key = (Keys) windowsKeyCode;
if (modifiers == (CefEventFlags.ControlDown | CefEventFlags.ShiftDown) && key == Keys.I) {
browserControl.OpenDevToolsCustom();
return true;
}
return handler != null && handler.HandleBrowserKey(key);
}
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey) {
return false;
}
public interface IBrowserKeyHandler {
bool HandleBrowserKey(Keys key);
}
}
}

View File

@@ -0,0 +1,37 @@
using CefSharp;
using CefSharp.Handler;
using TweetLib.Core;
using TweetLib.Utils.Static;
namespace TweetDuck.Browser.Handling {
sealed class CustomLifeSpanHandler : LifeSpanHandler {
private static bool IsPopupAllowed(string url) {
return url.StartsWithOrdinal("https://twitter.com/teams/authorize?") ||
url.StartsWithOrdinal("https://accounts.google.com/") ||
url.StartsWithOrdinal("https://appleid.apple.com/");
}
public static bool HandleLinkClick(WindowOpenDisposition targetDisposition, string targetUrl) {
switch (targetDisposition) {
case WindowOpenDisposition.NewBackgroundTab:
case WindowOpenDisposition.NewForegroundTab:
case WindowOpenDisposition.NewPopup when !IsPopupAllowed(targetUrl):
case WindowOpenDisposition.NewWindow:
App.SystemHandler.OpenBrowser(targetUrl);
return true;
default:
return false;
}
}
protected override bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) {
newBrowser = null;
return HandleLinkClick(targetDisposition, targetUrl);
}
protected override bool DoClose(IWebBrowser browserControl, IBrowser browser) {
return false;
}
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.IO;
using CefSharp;
namespace TweetDuck.Browser.Handling {
sealed class DownloadRequestClient : UrlRequestClient {
private readonly FileStream fileStream;
private readonly Action onSuccess;
private readonly Action<Exception> onError;
private bool hasFailed;
public DownloadRequestClient(FileStream fileStream, Action onSuccess, Action<Exception> onError) {
this.fileStream = fileStream;
this.onSuccess = onSuccess;
this.onError = onError;
}
protected override bool GetAuthCredentials(bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) {
onError?.Invoke(new Exception("This URL requires authentication."));
fileStream.Dispose();
hasFailed = true;
return false;
}
protected override void OnDownloadData(IUrlRequest request, Stream data) {
if (hasFailed) {
return;
}
try {
data.CopyTo(fileStream);
} catch (Exception e) {
fileStream.Dispose();
onError?.Invoke(e);
hasFailed = true;
}
}
protected override void OnRequestComplete(IUrlRequest request) {
if (hasFailed) {
return;
}
bool isEmpty = fileStream.Position == 0;
fileStream.Dispose();
var status = request.RequestStatus;
if (status == UrlRequestStatus.Failed) {
onError?.Invoke(new Exception("Unknown error."));
}
else if (status == UrlRequestStatus.Success) {
if (isEmpty) {
onError?.Invoke(new Exception("File is empty."));
return;
}
onSuccess?.Invoke();
}
}
}
}

View File

@@ -2,34 +2,34 @@
using CefSharp; using CefSharp;
using CefSharp.Enums; using CefSharp.Enums;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class DragHandlerBrowser : IDragHandler{ sealed class DragHandlerBrowser : IDragHandler {
private readonly RequestHandlerBrowser requestHandler; private readonly RequestHandlerBrowser requestHandler;
public DragHandlerBrowser(RequestHandlerBrowser requestHandler){ public DragHandlerBrowser(RequestHandlerBrowser requestHandler) {
this.requestHandler = requestHandler; this.requestHandler = requestHandler;
} }
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask){ public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask) {
void TriggerDragStart(string type, string data = null){ void TriggerDragStart(string type, string data = null) {
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data); browserControl.BrowserCore.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data);
} }
requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
if (dragData.IsLink){ if (dragData.IsLink) {
TriggerDragStart("link", dragData.LinkUrl); TriggerDragStart("link", dragData.LinkUrl);
} }
else if (dragData.IsFragment){ else if (dragData.IsFragment) {
TriggerDragStart("text", dragData.FragmentText.Trim()); TriggerDragStart("text", dragData.FragmentText.Trim());
} }
else{ else {
TriggerDragStart("unknown"); TriggerDragStart("unknown");
} }
return false; return false;
} }
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IFrame frame, IList<DraggableRegion> regions){} public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IFrame frame, IList<DraggableRegion> regions) {}
} }
} }

View File

@@ -0,0 +1,68 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using CefSharp;
using TweetLib.Core;
using TweetLib.Utils.Static;
namespace TweetDuck.Browser.Handling {
sealed class FileDialogHandler : IDialogHandler {
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback) {
if (mode == CefFileDialogMode.Open || mode == CefFileDialogMode.OpenMultiple) {
string allFilters = string.Join(";", acceptFilters.SelectMany(ParseFileType).Where(filter => !string.IsNullOrEmpty(filter)).Select(filter => "*" + filter));
using OpenFileDialog dialog = new OpenFileDialog {
AutoUpgradeEnabled = true,
DereferenceLinks = true,
Multiselect = mode == CefFileDialogMode.OpenMultiple,
Title = "Open Files",
Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
};
if (dialog.ShowDialog() == DialogResult.OK) {
string ext = Path.GetExtension(dialog.FileName)?.ToLower();
callback.Continue(acceptFilters.FindIndex(filter => ParseFileType(filter).Contains(ext)), dialog.FileNames.ToList());
}
else {
callback.Cancel();
}
callback.Dispose();
return true;
}
else {
callback.Dispose();
return false;
}
}
private static IEnumerable<string> ParseFileType(string type) {
if (string.IsNullOrEmpty(type)) {
return StringUtils.EmptyArray;
}
if (type[0] == '.') {
return new string[] { type };
}
string[] extensions = type switch {
"image/jpeg" => new string[] { ".jpg", ".jpeg" },
"image/png" => new string[] { ".png" },
"image/gif" => new string[] { ".gif" },
"image/webp" => new string[] { ".webp" },
"video/mp4" => new string[] { ".mp4" },
"video/quicktime" => new string[] { ".mov", ".qt" },
_ => StringUtils.EmptyArray
};
if (extensions.Length == 0) {
App.Logger.Warn("Unknown file type: " + type);
Debugger.Break();
}
return extensions;
}
}
}

View File

@@ -1,72 +0,0 @@
using System;
using System.IO;
using System.Text;
using CefSharp;
namespace TweetDuck.Browser.Handling.Filters{
abstract class ResponseFilterBase : IResponseFilter{
private enum State{
Reading, Writing, Done
}
private readonly Encoding encoding;
private byte[] responseData;
private State state;
private int offset;
protected ResponseFilterBase(int totalBytes, Encoding encoding){
this.responseData = new byte[totalBytes];
this.encoding = encoding;
this.state = State.Reading;
}
FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten){
int responseLength = responseData.Length;
if (state == State.Reading){
int bytesToRead = Math.Min(responseLength - offset, (int)Math.Min(dataIn?.Length ?? 0, int.MaxValue));
dataIn?.Read(responseData, offset, bytesToRead);
offset += bytesToRead;
dataInRead = bytesToRead;
dataOutWritten = 0;
if (offset >= responseLength){
responseData = encoding.GetBytes(ProcessResponse(encoding.GetString(responseData)));
state = State.Writing;
offset = 0;
}
return FilterStatus.NeedMoreData;
}
else if (state == State.Writing){
int bytesToWrite = Math.Min(responseLength - offset, (int)Math.Min(dataOut.Length, int.MaxValue));
if (bytesToWrite > 0){
dataOut.Write(responseData, offset, bytesToWrite);
offset += bytesToWrite;
}
dataOutWritten = bytesToWrite;
dataInRead = 0;
if (offset < responseLength){
return FilterStatus.NeedMoreData;
}
else{
state = State.Done;
return FilterStatus.Done;
}
}
else{
throw new InvalidOperationException("This resource filter cannot be reused.");
}
}
public abstract bool InitFilter();
protected abstract string ProcessResponse(string text);
public abstract void Dispose();
}
}

View File

@@ -1,20 +0,0 @@
using System.Text;
using System.Text.RegularExpressions;
namespace TweetDuck.Browser.Handling.Filters{
sealed class ResponseFilterVendor : ResponseFilterBase{
private static readonly Regex RegexRestoreJQuery = new Regex(@"(\w+)\.fn=\1\.prototype", RegexOptions.Compiled);
public ResponseFilterVendor(int totalBytes) : base(totalBytes, Encoding.UTF8){}
public override bool InitFilter(){
return true;
}
protected override string ProcessResponse(string text){
return RegexRestoreJQuery.Replace(text, "window.$$=$1;$&", 1);
}
public override void Dispose(){}
}
}

View File

@@ -1,28 +0,0 @@
using System;
using System.Threading.Tasks;
using CefSharp;
using TweetDuck.Configuration;
namespace TweetDuck.Browser.Handling.General{
sealed class BrowserProcessHandler : IBrowserProcessHandler{
public static Task UpdatePrefs(){
return Cef.UIThreadTaskFactory.StartNew(UpdatePrefsInternal);
}
private static void UpdatePrefsInternal(){
UserConfig config = Program.Config.User;
using IRequestContext ctx = Cef.GetGlobalRequestContext();
ctx.SetPreference("browser.enable_spellchecking", config.EnableSpellCheck, out string _);
ctx.SetPreference("spellcheck.dictionary", config.SpellCheckLanguage, out string _);
ctx.SetPreference("settings.a11y.animation_policy", config.EnableAnimatedImages ? "allowed" : "none", out string _);
}
void IBrowserProcessHandler.OnContextInitialized(){
UpdatePrefsInternal();
}
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay){}
void IDisposable.Dispose(){}
}
}

View File

@@ -1,61 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using CefSharp;
namespace TweetDuck.Browser.Handling.General{
sealed class FileDialogHandler : IDialogHandler{
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback){
if (mode == CefFileDialogMode.Open || mode == CefFileDialogMode.OpenMultiple){
string allFilters = string.Join(";", acceptFilters.SelectMany(ParseFileType).Where(filter => !string.IsNullOrEmpty(filter)).Select(filter => "*" + filter));
using(OpenFileDialog dialog = new OpenFileDialog{
AutoUpgradeEnabled = true,
DereferenceLinks = true,
Multiselect = mode == CefFileDialogMode.OpenMultiple,
Title = "Open Files",
Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
}){
if (dialog.ShowDialog() == DialogResult.OK){
string ext = Path.GetExtension(dialog.FileName)?.ToLower();
callback.Continue(acceptFilters.FindIndex(filter => ParseFileType(filter).Contains(ext)), dialog.FileNames.ToList());
}
else{
callback.Cancel();
}
callback.Dispose();
}
return true;
}
else{
callback.Dispose();
return false;
}
}
private static IEnumerable<string> ParseFileType(string type){
if (string.IsNullOrEmpty(type)){
return new string[0];
}
if (type[0] == '.'){
return new string[]{ type };
}
switch(type){
case "image/jpeg": return new string[]{ ".jpg", ".jpeg" };
case "image/png": return new string[]{ ".png" };
case "image/gif": return new string[]{ ".gif" };
case "image/webp": return new string[]{ ".webp" };
case "video/mp4": return new string[]{ ".mp4" };
case "video/quicktime": return new string[]{ ".mov", ".qt" };
}
System.Diagnostics.Debugger.Break();
return new string[0];
}
}
}

View File

@@ -1,93 +0,0 @@
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Controls;
using TweetDuck.Dialogs;
using TweetDuck.Utils;
namespace TweetDuck.Browser.Handling.General{
sealed class JavaScriptDialogHandler : IJsDialogHandler{
private static FormMessage CreateMessageForm(string caption, string text){
MessageBoxIcon icon = MessageBoxIcon.None;
int pipe = text.IndexOf('|');
if (pipe != -1){
icon = text.Substring(0, pipe) switch{
"error" => MessageBoxIcon.Error,
"warning" => MessageBoxIcon.Warning,
"info" => MessageBoxIcon.Information,
"question" => MessageBoxIcon.Question,
_ => MessageBoxIcon.None
};
if (icon != MessageBoxIcon.None){
text = text.Substring(pipe + 1);
}
}
return new FormMessage(caption, text, icon);
}
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
browserControl.AsControl().InvokeSafe(() => {
FormMessage form;
TextBox input = null;
if (dialogType == CefJsDialogType.Alert){
form = CreateMessageForm("Browser Message", messageText);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
}
else if (dialogType == CefJsDialogType.Confirm){
form = CreateMessageForm("Browser Confirmation", messageText);
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
form.AddButton(FormMessage.Yes, ControlType.Focused);
}
else if (dialogType == CefJsDialogType.Prompt){
form = CreateMessageForm("Browser Prompt", messageText);
form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
float dpiScale = form.GetDPIScale();
int inputPad = form.HasIcon ? 43 : 0;
input = new TextBox{
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
Font = SystemFonts.MessageBoxFont,
Location = new Point(BrowserUtils.Scale(22 + inputPad, dpiScale), form.ActionPanelY - BrowserUtils.Scale(46, dpiScale)),
Size = new Size(form.ClientSize.Width - BrowserUtils.Scale(44 + inputPad, dpiScale), BrowserUtils.Scale(23, dpiScale))
};
form.Controls.Add(input);
form.ActiveControl = input;
form.Height += input.Size.Height + input.Margin.Vertical;
}
else{
callback.Continue(false);
return;
}
bool success = form.ShowDialog() == DialogResult.OK;
if (input == null){
callback.Continue(success);
}
else{
callback.Continue(success, input.Text);
input.Dispose();
}
form.Dispose();
});
return true;
}
bool IJsDialogHandler.OnBeforeUnloadDialog(IWebBrowser browserControl, IBrowser browser, string messageText, bool isReload, IJsDialogCallback callback){
callback.Dispose();
return false;
}
void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser){}
void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser){}
}
}

View File

@@ -1,38 +0,0 @@
using CefSharp;
using TweetDuck.Controls;
using TweetDuck.Utils;
namespace TweetDuck.Browser.Handling.General{
sealed class LifeSpanHandler : ILifeSpanHandler{
private static bool IsPopupAllowed(string url){
return url.StartsWith("https://twitter.com/teams/authorize?");
}
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
switch(targetDisposition){
case WindowOpenDisposition.NewBackgroundTab:
case WindowOpenDisposition.NewForegroundTab:
case WindowOpenDisposition.NewPopup when !IsPopupAllowed(targetUrl):
case WindowOpenDisposition.NewWindow:
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
return true;
default:
return false;
}
}
public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser){
newBrowser = null;
return HandleLinkClick(browserControl, targetDisposition, targetUrl);
}
public void OnAfterCreated(IWebBrowser browserControl, IBrowser browser){}
public bool DoClose(IWebBrowser browserControl, IBrowser browser){
return false;
}
public void OnBeforeClose(IWebBrowser browserControl, IBrowser browser){}
}
}

View File

@@ -0,0 +1,96 @@
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Controls;
using TweetDuck.Dialogs;
using TweetDuck.Utils;
namespace TweetDuck.Browser.Handling {
sealed class JavaScriptDialogHandler : IJsDialogHandler {
private static FormMessage CreateMessageForm(string caption, string text) {
MessageBoxIcon icon = MessageBoxIcon.None;
int pipe = text.IndexOf('|');
if (pipe != -1) {
icon = text.Substring(0, pipe) switch {
"error" => MessageBoxIcon.Error,
"warning" => MessageBoxIcon.Warning,
"info" => MessageBoxIcon.Information,
"question" => MessageBoxIcon.Question,
_ => MessageBoxIcon.None
};
if (icon != MessageBoxIcon.None) {
text = text.Substring(pipe + 1);
}
}
return new FormMessage(caption, text, icon);
}
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage) {
var control = (ChromiumWebBrowser) browserControl;
control.InvokeSafe(() => {
FormMessage form;
TextBox input = null;
if (dialogType == CefJsDialogType.Alert) {
form = CreateMessageForm("Browser Message", messageText);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
}
else if (dialogType == CefJsDialogType.Confirm) {
form = CreateMessageForm("Browser Confirmation", messageText);
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
form.AddButton(FormMessage.Yes, ControlType.Focused);
}
else if (dialogType == CefJsDialogType.Prompt) {
form = CreateMessageForm("Browser Prompt", messageText);
form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
float dpiScale = form.GetDPIScale();
int inputPad = form.HasIcon ? 43 : 0;
input = new TextBox {
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
Font = SystemFonts.MessageBoxFont,
Location = new Point(BrowserUtils.Scale(22 + inputPad, dpiScale), form.ActionPanelY - BrowserUtils.Scale(46, dpiScale)),
Size = new Size(form.ClientSize.Width - BrowserUtils.Scale(44 + inputPad, dpiScale), BrowserUtils.Scale(23, dpiScale))
};
form.Controls.Add(input);
form.ActiveControl = input;
form.Height += input.Size.Height + input.Margin.Vertical;
}
else {
callback.Continue(false);
return;
}
bool success = form.ShowDialog() == DialogResult.OK;
if (input == null) {
callback.Continue(success);
}
else {
callback.Continue(success, input.Text);
input.Dispose();
}
form.Dispose();
});
return true;
}
bool IJsDialogHandler.OnBeforeUnloadDialog(IWebBrowser browserControl, IBrowser browser, string messageText, bool isReload, IJsDialogCallback callback) {
callback.Dispose();
return false;
}
void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser) {}
void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser) {}
}
}

View File

@@ -1,47 +0,0 @@
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Controls;
using TweetDuck.Dialogs;
using TweetDuck.Utils;
namespace TweetDuck.Browser.Handling{
class KeyboardHandlerBase : IKeyboardHandler{
protected virtual bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
if (modifiers == (CefEventFlags.ControlDown | CefEventFlags.ShiftDown) && key == Keys.I){
if (BrowserUtils.HasDevTools){
browserControl.OpenDevToolsCustom();
}
else{
browserControl.AsControl().InvokeSafe(() => {
string extraMessage;
if (Program.IsPortable){
extraMessage = "Please download the portable installer, select the folder with your current installation of TweetDuck Portable, and tick 'Install dev tools' during the installation process.";
}
else{
extraMessage = "Please download the installer, and tick 'Install dev tools' during the installation process. The installer will automatically find and update your current installation of TweetDuck.";
}
FormMessage.Information("Dev Tools", "You do not have dev tools installed. " + extraMessage, FormMessage.OK);
});
}
return true;
}
return false;
}
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
if (type == KeyType.RawKeyDown && !browser.FocusedFrame.Url.StartsWith("devtools://")){
return HandleRawKey(browserControl, browser, (Keys)windowsKeyCode, modifiers);
}
return false;
}
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
return false;
}
}
}

View File

@@ -1,20 +0,0 @@
using System.Windows.Forms;
using CefSharp;
namespace TweetDuck.Browser.Handling{
sealed class KeyboardHandlerBrowser : KeyboardHandlerBase{
private readonly FormBrowser form;
public KeyboardHandlerBrowser(FormBrowser form){
this.form = form;
}
protected override bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
if (base.HandleRawKey(browserControl, browser, key, modifiers)){
return true;
}
return form.ProcessBrowserKey(key);
}
}
}

View File

@@ -1,44 +0,0 @@
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Browser.Notification;
using TweetDuck.Controls;
namespace TweetDuck.Browser.Handling{
sealed class KeyboardHandlerNotification : KeyboardHandlerBase{
private readonly FormNotificationBase notification;
public KeyboardHandlerNotification(FormNotificationBase notification){
this.notification = notification;
}
private void TriggerKeyboardShortcutAnalytics(){
notification.InvokeAsyncSafe(notification.AnalyticsFile.NotificationKeyboardShortcuts.Trigger);
}
protected override bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
if (base.HandleRawKey(browserControl, browser, key, modifiers)){
return true;
}
switch(key){
case Keys.Enter:
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
TriggerKeyboardShortcutAnalytics();
return true;
case Keys.Escape:
notification.InvokeAsyncSafe(notification.HideNotification);
TriggerKeyboardShortcutAnalytics();
return true;
case Keys.Space:
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
TriggerKeyboardShortcutAnalytics();
return true;
default:
return false;
}
}
}
}

View File

@@ -1,21 +1,20 @@
using CefSharp; using CefSharp;
using CefSharp.Handler; using CefSharp.Handler;
using TweetDuck.Browser.Handling.General;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
class RequestHandlerBase : RequestHandler{ class RequestHandlerBase : RequestHandler {
private readonly bool autoReload; private readonly bool autoReload;
public RequestHandlerBase(bool autoReload){ public RequestHandlerBase(bool autoReload) {
this.autoReload = autoReload; this.autoReload = autoReload;
} }
protected override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){ protected override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) {
return LifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl); return CustomLifeSpanHandler.HandleLinkClick(targetDisposition, targetUrl);
} }
protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){ protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status) {
if (autoReload){ if (autoReload) {
browser.Reload(); browser.Reload();
} }
} }

View File

@@ -1,19 +1,19 @@
using CefSharp; using CefSharp;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class RequestHandlerBrowser : RequestHandlerBase{ sealed class RequestHandlerBrowser : RequestHandlerBase {
public string BlockNextUserNavUrl { get; set; } public string BlockNextUserNavUrl { get; set; }
public RequestHandlerBrowser() : base(true){} public RequestHandlerBrowser() : base(true) {}
protected override bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect){ protected override bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect) {
if (userGesture && request.TransitionType == TransitionType.LinkClicked){ if (userGesture && request.TransitionType == TransitionType.LinkClicked) {
bool block = request.Url == BlockNextUserNavUrl; bool block = request.Url == BlockNextUserNavUrl;
BlockNextUserNavUrl = string.Empty; BlockNextUserNavUrl = string.Empty;
return block; return block;
} }
else if (request.TransitionType.HasFlag(TransitionType.ForwardBack) && TwitterUrls.IsTweetDeck(frame.Url)){ else if (request.TransitionType.HasFlag(TransitionType.ForwardBack) && TwitterUrls.IsTweetDeck(frame.Url)) {
return true; return true;
} }

View File

@@ -5,35 +5,35 @@ using System.Text;
using CefSharp; using CefSharp;
using CefSharp.Callback; using CefSharp.Callback;
namespace TweetDuck.Browser.Handling{ namespace TweetDuck.Browser.Handling {
sealed class ResourceHandlerNotification : IResourceHandler{ sealed class ResourceHandlerNotification : IResourceHandler {
private readonly NameValueCollection headers = new NameValueCollection(0); private readonly NameValueCollection headers = new NameValueCollection(0);
private MemoryStream dataIn; private MemoryStream dataIn;
public void SetHTML(string html){ public void SetHTML(string html) {
dataIn?.Dispose(); dataIn?.Dispose();
dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8); dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8);
} }
public void Dispose(){ public void Dispose() {
if (dataIn != null){ if (dataIn != null) {
dataIn.Dispose(); dataIn.Dispose();
dataIn = null; dataIn = null;
} }
} }
bool IResourceHandler.Open(IRequest request, out bool handleRequest, ICallback callback){ bool IResourceHandler.Open(IRequest request, out bool handleRequest, ICallback callback) {
callback.Dispose(); callback.Dispose();
handleRequest = true; handleRequest = true;
if (dataIn != null){ if (dataIn != null) {
dataIn.Position = 0; dataIn.Position = 0;
} }
return true; return true;
} }
void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl){ void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl) {
redirectUrl = null; redirectUrl = null;
response.MimeType = "text/html"; response.MimeType = "text/html";
@@ -43,37 +43,37 @@ namespace TweetDuck.Browser.Handling{
responseLength = dataIn?.Length ?? 0; responseLength = dataIn?.Length ?? 0;
} }
bool IResourceHandler.Read(Stream dataOut, out int bytesRead, IResourceReadCallback callback){ bool IResourceHandler.Read(Stream dataOut, out int bytesRead, IResourceReadCallback callback) {
callback?.Dispose(); // TODO unnecessary null check once ReadResponse is removed callback?.Dispose(); // TODO unnecessary null check once ReadResponse is removed
try{ try {
byte[] buffer = new byte[Math.Min(dataIn.Length - dataIn.Position, dataOut.Length)]; byte[] buffer = new byte[Math.Min(dataIn.Length - dataIn.Position, dataOut.Length)];
int length = buffer.Length; int length = buffer.Length;
dataIn.Read(buffer, 0, length); dataIn.Read(buffer, 0, length);
dataOut.Write(buffer, 0, length); dataOut.Write(buffer, 0, length);
bytesRead = length; bytesRead = length;
}catch{ // catch IOException, possibly NullReferenceException if dataIn is null } catch { // catch IOException, possibly NullReferenceException if dataIn is null
bytesRead = 0; bytesRead = 0;
} }
return bytesRead > 0; return bytesRead > 0;
} }
bool IResourceHandler.Skip(long bytesToSkip, out long bytesSkipped, IResourceSkipCallback callback){ bool IResourceHandler.Skip(long bytesToSkip, out long bytesSkipped, IResourceSkipCallback callback) {
bytesSkipped = -2; // ERR_FAILED bytesSkipped = -2; // ERR_FAILED
callback.Dispose(); callback.Dispose();
return false; return false;
} }
bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback){ bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback) {
return ((IResourceHandler)this).Open(request, out bool _, callback); return ((IResourceHandler) this).Open(request, out bool _, callback);
} }
bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback){ bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback) {
return ((IResourceHandler)this).Read(dataOut, out bytesRead, null); return ((IResourceHandler) this).Read(dataOut, out bytesRead, null);
} }
void IResourceHandler.Cancel(){} void IResourceHandler.Cancel() {}
} }
} }

View File

@@ -1,35 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using CefSharp;
using TweetDuck.Browser.Data;
namespace TweetDuck.Browser.Handling{
abstract class ResourceRequestHandler : CefSharp.Handler.ResourceRequestHandler{
private class SelfFactoryImpl : IResourceRequestHandlerFactory{
private readonly ResourceRequestHandler me;
public SelfFactoryImpl(ResourceRequestHandler me){
this.me = me;
}
bool IResourceRequestHandlerFactory.HasHandlers { get; } = true;
[SuppressMessage("ReSharper", "RedundantAssignment")]
IResourceRequestHandler IResourceRequestHandlerFactory.GetResourceRequestHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling){
disableDefaultHandling = me.ResourceHandlers.HasHandler(request);
return me;
}
}
public IResourceRequestHandlerFactory SelfFactory { get; }
public ResourceHandlers ResourceHandlers { get; }
protected ResourceRequestHandler(){
this.SelfFactory = new SelfFactoryImpl(this);
this.ResourceHandlers = new ResourceHandlers();
}
protected override IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request){
return ResourceHandlers.GetHandler(request);
}
}
}

View File

@@ -1,69 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text.RegularExpressions;
using CefSharp;
using TweetDuck.Utils;
using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Handling{
class ResourceRequestHandlerBase : ResourceRequestHandler{
private static readonly Regex TweetDeckResourceUrl = new Regex(@"/dist/(.*?)\.(.*?)\.(css|js)$");
private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(4);
public static void LoadResourceRewriteRules(string rules){
if (string.IsNullOrEmpty(rules)){
return;
}
TweetDeckHashes.Clear();
foreach(string rule in rules.Replace(" ", "").ToLower().Split(',')){
var (key, hash) = StringUtils.SplitInTwo(rule, '=') ?? throw new ArgumentException("A rule must have one '=' character: " + rule);
if (hash.All(chr => char.IsDigit(chr) || (chr >= 'a' && chr <= 'f'))){
TweetDeckHashes.Add(key, hash);
}
else{
throw new ArgumentException("Invalid hash characters: " + rule);
}
}
}
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
if (request.ResourceType == ResourceType.CspReport){
callback.Dispose();
return CefReturnValue.Cancel;
}
if (BrowserUtils.HasDevTools){
NameValueCollection headers = request.Headers;
headers.Remove("x-devtools-emulate-network-conditions-client-id");
request.Headers = headers;
}
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
}
protected override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
if ((request.ResourceType == ResourceType.Script || request.ResourceType == ResourceType.Stylesheet) && TweetDeckHashes.Count > 0){
string url = request.Url;
Match match = TweetDeckResourceUrl.Match(url);
if (match.Success && TweetDeckHashes.TryGetValue($"{match.Groups[1]}.{match.Groups[3]}", out string hash)){
if (match.Groups[2].Value == hash){
Program.Reporter.LogVerbose("[RequestHandlerBase] Accepting " + url);
}
else{
Program.Reporter.LogVerbose("[RequestHandlerBase] Replacing " + url + " hash with " + hash);
request.Url = TweetDeckResourceUrl.Replace(url, $"/dist/$1.{hash}.$3");
return true;
}
}
}
return base.OnResourceResponse(browserControl, browser, frame, request, response);
}
}
}

View File

@@ -1,52 +0,0 @@
using CefSharp;
using TweetDuck.Browser.Handling.Filters;
using TweetDuck.Utils;
using TweetLib.Core.Features.Twitter;
namespace TweetDuck.Browser.Handling{
class ResourceRequestHandlerBrowser : ResourceRequestHandlerBase{
private const string UrlVendorResource = "/dist/vendor";
private const string UrlLoadingSpinner = "/backgrounds/spinner_blue";
private const string UrlVersionCheck = "/web/dist/version.json";
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
if (request.ResourceType == ResourceType.MainFrame){
if (request.Url.EndsWith("//twitter.com/")){
request.Url = TwitterUrls.TweetDeck; // redirect plain twitter.com requests, fixes bugs with login 2FA
}
}
else if (request.ResourceType == ResourceType.Image){
if (request.Url.Contains(UrlLoadingSpinner)){
request.Url = TwitterUtils.LoadingSpinner.Url;
}
}
else if (request.ResourceType == ResourceType.Script){
string url = request.Url;
if (url.Contains("analytics.")){
callback.Dispose();
return CefReturnValue.Cancel;
}
else if (url.Contains(UrlVendorResource)){
request.SetHeaderByName("Accept-Encoding", "identity", overwrite: true);
}
}
else if (request.ResourceType == ResourceType.Xhr){
if (request.Url.Contains(UrlVersionCheck)){
callback.Dispose();
return CefReturnValue.Cancel;
}
}
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
}
protected override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
if (request.ResourceType == ResourceType.Script && request.Url.Contains(UrlVendorResource) && int.TryParse(response.Headers["Content-Length"], out int totalBytes)){
return new ResponseFilterVendor(totalBytes);
}
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
}
}
}

View File

@@ -0,0 +1,76 @@
using System;
using System.IO;
using CefSharp;
using TweetLib.Browser.Interfaces;
namespace TweetDuck.Browser.Handling {
sealed class ResponseFilter : IResponseFilter {
private enum State {
Reading,
Writing,
Done
}
private readonly IResponseProcessor processor;
private byte[] responseData;
private State state;
private int offset;
public ResponseFilter(IResponseProcessor processor, int totalBytes) {
this.processor = processor;
this.responseData = new byte[totalBytes];
this.state = State.Reading;
}
public bool InitFilter() {
return true;
}
FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten) {
int responseLength = responseData.Length;
if (state == State.Reading) {
int bytesToRead = Math.Min(responseLength - offset, (int) Math.Min(dataIn?.Length ?? 0, int.MaxValue));
dataIn?.Read(responseData, offset, bytesToRead);
offset += bytesToRead;
dataInRead = bytesToRead;
dataOutWritten = 0;
if (offset >= responseLength) {
responseData = processor.Process(responseData);
state = State.Writing;
offset = 0;
}
return FilterStatus.NeedMoreData;
}
else if (state == State.Writing) {
int bytesToWrite = Math.Min(responseLength - offset, (int) Math.Min(dataOut.Length, int.MaxValue));
if (bytesToWrite > 0) {
dataOut.Write(responseData, offset, bytesToWrite);
offset += bytesToWrite;
}
dataOutWritten = bytesToWrite;
dataInRead = 0;
if (offset < responseLength) {
return FilterStatus.NeedMoreData;
}
else {
state = State.Done;
return FilterStatus.Done;
}
}
else {
throw new InvalidOperationException("This resource filter cannot be reused.");
}
}
public void Dispose() {}
}
}

View File

@@ -1,68 +0,0 @@
using System;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Controls;
using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Plugins;
namespace TweetDuck.Browser.Notification.Example{
sealed class FormNotificationExample : FormNotificationMain{
public override bool RequiresResize => true;
protected override bool CanDragWindow => Config.NotificationPosition == DesktopNotification.Position.Custom;
protected override FormBorderStyle NotificationBorderStyle{
get{
if (Config.NotificationSize == DesktopNotification.Size.Custom){
switch(base.NotificationBorderStyle){
case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
}
}
return base.NotificationBorderStyle;
}
}
protected override string BodyClasses => base.BodyClasses + " td-example";
public event EventHandler Ready;
private readonly DesktopNotification exampleNotification;
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
browser.LoadingStateChanged += browser_LoadingStateChanged;
string exampleTweetHTML = Program.Resources.LoadSilent("pages/example.html")?.Replace("{avatar}", AppLogo.Url) ?? string.Empty;
#if DEBUG
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
#endif
exampleNotification = new DesktopNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
}
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){
Ready?.Invoke(this, EventArgs.Empty);
browser.LoadingStateChanged -= browser_LoadingStateChanged;
}
}
public override void HideNotification(){
Location = ControlExtensions.InvisibleLocation;
}
public override void FinishCurrentNotification(){}
public void ShowExampleNotification(bool reset){
if (reset){
LoadTweet(exampleNotification);
}
else{
PrepareAndDisplayWindow();
}
UpdateTitle();
}
}
}

View File

@@ -1,51 +1,36 @@
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp.WinForms; using CefSharp.WinForms;
using TweetDuck.Browser.Data; using TweetDuck.Browser.Adapters;
using TweetDuck.Browser.Handling; using TweetDuck.Browser.Handling;
using TweetDuck.Browser.Handling.General;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Management.Analytics;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Browser.Interfaces;
using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Browser.Notification{ namespace TweetDuck.Browser.Notification {
abstract partial class FormNotificationBase : Form, AnalyticsFile.IProvider{ abstract partial class FormNotificationBase : Form {
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandlers.ForBytes(Properties.Resources.avatar, "image/png"));
protected const string BlankURL = TwitterUrls.TweetDeck + "/?blank";
public static string FontSize = null;
public static string HeadLayout = null;
protected static UserConfig Config => Program.Config.User; protected static UserConfig Config => Program.Config.User;
protected static int FontSizeLevel{ protected delegate NotificationBrowser CreateBrowserImplFunc(FormNotificationBase form, IBrowserComponent browserComponent);
get => FontSize switch{
"largest" => 4,
"large" => 3,
"small" => 1,
"smallest" => 0,
_ => 2
};
}
protected virtual Point PrimaryLocation{ protected virtual Point PrimaryLocation {
get{ get {
Screen screen; Screen screen;
if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length){ if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length) {
screen = Screen.AllScreens[Config.NotificationDisplay - 1]; screen = Screen.AllScreens[Config.NotificationDisplay - 1];
} }
else{ else {
screen = Screen.FromControl(owner); screen = Screen.FromControl(owner);
} }
int edgeDist = Config.NotificationEdgeDistance; int edgeDist = Config.NotificationEdgeDistance;
switch(Config.NotificationPosition){ switch (Config.NotificationPosition) {
case DesktopNotification.Position.TopLeft: case DesktopNotification.Position.TopLeft:
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + edgeDist); return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + edgeDist);
@@ -59,7 +44,7 @@ namespace TweetDuck.Browser.Notification{
return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height); return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height);
case DesktopNotification.Position.Custom: case DesktopNotification.Position.Custom:
if (!Config.IsCustomNotificationPositionSet){ if (!Config.IsCustomNotificationPositionSet) {
Config.CustomNotificationPosition = new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist); Config.CustomNotificationPosition = new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
Config.Save(); Config.Save();
} }
@@ -74,36 +59,37 @@ namespace TweetDuck.Browser.Notification{
protected bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation; protected bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
protected virtual bool CanDragWindow => true; protected virtual bool CanDragWindow => true;
public new Point Location{ public new Point Location {
get{ get {
return base.Location; return base.Location;
} }
set{ set {
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation; Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
FormBorderStyle = NotificationBorderStyle; FormBorderStyle = NotificationBorderStyle;
} }
} }
protected virtual FormBorderStyle NotificationBorderStyle{ protected virtual FormBorderStyle NotificationBorderStyle {
get{ get {
if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab if (WindowsUtils.ShouldAvoidToolWindow && Visible) { // Visible = workaround for alt+tab
return FormBorderStyle.FixedSingle; return FormBorderStyle.FixedSingle;
} }
else{ else {
return FormBorderStyle.FixedToolWindow; return FormBorderStyle.FixedToolWindow;
} }
} }
} }
public AnalyticsFile AnalyticsFile => owner.AnalyticsFile;
protected override bool ShowWithoutActivation => true; protected override bool ShowWithoutActivation => true;
protected float DpiScale { get; } protected float DpiScale { get; }
protected double SizeScale => DpiScale * Config.ZoomLevel / 100.0; protected double SizeScale => DpiScale * Config.ZoomLevel / 100.0;
protected readonly FormBrowser owner; private readonly FormBrowser owner;
protected readonly IBrowserComponent browserComponent;
private readonly NotificationBrowser browserImpl;
#pragma warning disable IDE0069 // Disposable fields should be disposed #pragma warning disable IDE0069 // Disposable fields should be disposed
protected readonly ChromiumWebBrowser browser; protected readonly ChromiumWebBrowser browser;
@@ -117,43 +103,33 @@ namespace TweetDuck.Browser.Notification{
public string CurrentTweetUrl => currentNotification?.TweetUrl; public string CurrentTweetUrl => currentNotification?.TweetUrl;
public string CurrentQuoteUrl => currentNotification?.QuoteUrl; public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId);
protected bool IsPaused => pauseCounter > 0; protected bool IsPaused => pauseCounter > 0;
protected bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position)); protected internal bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
public bool FreezeTimer { get; set; } public bool FreezeTimer { get; set; }
public bool ContextMenuOpen { get; set; } public bool ContextMenuOpen { get; set; }
protected FormNotificationBase(FormBrowser owner, bool enableContextMenu){ protected FormNotificationBase(FormBrowser owner, CreateBrowserImplFunc createBrowserImpl) {
InitializeComponent(); InitializeComponent();
this.owner = owner; this.owner = owner;
this.owner.FormClosed += owner_FormClosed; this.owner.FormClosed += owner_FormClosed;
var resourceRequestHandler = new ResourceRequestHandlerBase(); this.browser = new ChromiumWebBrowser(NotificationBrowser.BlankURL) {
var resourceHandlers = resourceRequestHandler.ResourceHandlers; RequestHandler = new RequestHandlerBase(false)
resourceHandlers.Register(BlankURL, ResourceHandlers.ForString(string.Empty));
resourceHandlers.Register(TwitterUrls.TweetDeck, () => this.resourceHandler);
resourceHandlers.Register(AppLogo);
this.browser = new ChromiumWebBrowser(BlankURL){
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBase(false),
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
}; };
this.browserComponent = new ComponentImpl(browser, this);
this.browserImpl = createBrowserImpl(this, browserComponent);
this.browser.Dock = DockStyle.None; this.browser.Dock = DockStyle.None;
this.browser.ClientSize = ClientSize; this.browser.ClientSize = ClientSize;
this.browser.SetupZoomEvents();
Controls.Add(browser); Controls.Add(browser);
Disposed += (sender, args) => { Disposed += (sender, args) => {
this.owner.FormClosed -= owner_FormClosed; this.owner.FormClosed -= owner_FormClosed;
this.browserImpl.Dispose();
this.browser.Dispose(); this.browser.Dispose();
}; };
@@ -163,8 +139,27 @@ namespace TweetDuck.Browser.Notification{
UpdateTitle(); UpdateTitle();
} }
protected override void Dispose(bool disposing){ protected sealed class ComponentImpl : CefBrowserComponent {
if (disposing){ private readonly FormNotificationBase owner;
public ComponentImpl(ChromiumWebBrowser browser, FormNotificationBase owner) : base(browser) {
this.owner = owner;
}
protected override ContextMenuBase SetupContextMenu(IContextMenuHandler handler) {
return new ContextMenuNotification(owner, handler);
}
protected override CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler) {
var registry = new CefResourceHandlerRegistry();
registry.RegisterStatic(NotificationBrowser.BlankURL, string.Empty);
registry.RegisterDynamic(TwitterUrls.TweetDeck, owner.resourceHandler);
return new CefResourceHandlerFactory(handler, registry);
}
}
protected override void Dispose(bool disposing) {
if (disposing) {
components?.Dispose(); components?.Dispose();
resourceHandler.Dispose(); resourceHandler.Dispose();
} }
@@ -172,8 +167,8 @@ namespace TweetDuck.Browser.Notification{
base.Dispose(disposing); base.Dispose(disposing);
} }
protected override void WndProc(ref Message m){ protected override void WndProc(ref Message m) {
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow){ // WM_SYSCOMMAND, SC_MOVE if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow) { // WM_SYSCOMMAND, SC_MOVE
return; return;
} }
@@ -182,73 +177,71 @@ namespace TweetDuck.Browser.Notification{
// event handlers // event handlers
private void owner_FormClosed(object sender, FormClosedEventArgs e){ private void owner_FormClosed(object sender, FormClosedEventArgs e) {
Close(); Close();
} }
// notification methods // notification methods
public virtual void HideNotification(){ public virtual void HideNotification() {
browser.Load(BlankURL); browser.Load(NotificationBrowser.BlankURL);
DisplayTooltip(null); DisplayTooltip(null);
Location = ControlExtensions.InvisibleLocation; Location = ControlExtensions.InvisibleLocation;
currentNotification = null; currentNotification = null;
} }
public virtual void FinishCurrentNotification(){} public virtual void FinishCurrentNotification() {}
public virtual void PauseNotification(){ public virtual void PauseNotification() {
if (pauseCounter++ == 0 && IsNotificationVisible){ if (pauseCounter++ == 0 && IsNotificationVisible) {
Location = ControlExtensions.InvisibleLocation; Location = ControlExtensions.InvisibleLocation;
} }
} }
public virtual void ResumeNotification(){ public virtual void ResumeNotification() {
if (pauseCounter > 0){ if (pauseCounter > 0) {
--pauseCounter; --pauseCounter;
} }
} }
protected abstract string GetTweetHTML(DesktopNotification tweet); protected virtual void LoadTweet(DesktopNotification tweet) {
protected virtual void LoadTweet(DesktopNotification tweet){
currentNotification = tweet; currentNotification = tweet;
resourceHandler.SetHTML(GetTweetHTML(tweet)); resourceHandler.SetHTML(browserImpl.GetTweetHTML(tweet));
browser.Load(TwitterUrls.TweetDeck); browser.Load(TwitterUrls.TweetDeck);
DisplayTooltip(null); DisplayTooltip(null);
} }
protected virtual void SetNotificationSize(int width, int height){ protected virtual void SetNotificationSize(int width, int height) {
browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale)); browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale));
} }
protected virtual void UpdateTitle(){ protected virtual void UpdateTitle() {
string title = currentNotification?.ColumnTitle; string title = currentNotification?.ColumnTitle;
Text = string.IsNullOrEmpty(title) || !Config.DisplayNotificationColumn ? Program.BrandName : $"{Program.BrandName} - {title}"; Text = string.IsNullOrEmpty(title) || !Config.DisplayNotificationColumn ? Program.BrandName : $"{Program.BrandName} - {title}";
} }
public void ShowTweetDetail(){ public void ShowTweetDetail() {
if (currentNotification != null){ if (currentNotification != null && owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl)) {
owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl); FinishCurrentNotification();
} }
} }
public void MoveToVisibleLocation(){ public void MoveToVisibleLocation() {
bool needsReactivating = Location == ControlExtensions.InvisibleLocation; bool needsReactivating = Location == ControlExtensions.InvisibleLocation;
Location = PrimaryLocation; Location = PrimaryLocation;
if (needsReactivating){ if (needsReactivating) {
NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE); NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
} }
} }
public void DisplayTooltip(string text){ public void DisplayTooltip(string text) {
if (string.IsNullOrEmpty(text)){ if (string.IsNullOrEmpty(text)) {
toolTip.Hide(this); toolTip.Hide(this);
} }
else{ else {
Point position = PointToClient(Cursor.Position); Point position = PointToClient(Cursor.Position);
position.Offset(20, 5); position.Offset(20, 5);
toolTip.Show(text, this, position); toolTip.Show(text, this, position);

View File

@@ -0,0 +1,62 @@
using System;
using System.Windows.Forms;
using TweetDuck.Controls;
using TweetLib.Browser.Interfaces;
using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.TweetDeck;
using TweetLib.Core.Resources;
namespace TweetDuck.Browser.Notification {
sealed class FormNotificationExample : FormNotificationMain {
private static NotificationBrowser CreateBrowserImpl(IBrowserComponent browserComponent, INotificationInterface notificationInterface, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) {
return new NotificationBrowser.Example(browserComponent, notificationInterface, tweetDeckInterface, pluginManager);
}
public override bool RequiresResize => true;
protected override bool CanDragWindow => Config.NotificationPosition == DesktopNotification.Position.Custom;
protected override FormBorderStyle NotificationBorderStyle {
get {
if (Config.NotificationSize == DesktopNotification.Size.Custom) {
switch (base.NotificationBorderStyle) {
case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
}
}
return base.NotificationBorderStyle;
}
}
public event EventHandler Ready;
private readonly DesktopNotification exampleNotification;
public FormNotificationExample(FormBrowser owner, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) : base(owner, (form, browserComponent) => CreateBrowserImpl(browserComponent, new NotificationInterfaceImpl(form), tweetDeckInterface, pluginManager)) {
browserComponent.BrowserLoaded += (sender, args) => {
Ready?.Invoke(this, EventArgs.Empty);
};
string exampleTweetHTML = ResourceUtils.ReadFileOrNull("notification/example/example.html") ?? string.Empty;
exampleNotification = new DesktopNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
}
public override void HideNotification() {
Location = ControlExtensions.InvisibleLocation;
}
public override void FinishCurrentNotification() {}
public void ShowExampleNotification(bool reset) {
if (reset) {
LoadTweet(exampleNotification);
}
else {
PrepareAndDisplayWindow();
}
UpdateTitle();
}
}
}

View File

@@ -2,21 +2,50 @@
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using TweetDuck.Browser.Adapters;
using TweetDuck.Browser.Bridge;
using TweetDuck.Browser.Handling; using TweetDuck.Browser.Handling;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Plugins;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums;
using TweetLib.Core.Features.Twitter;
namespace TweetDuck.Browser.Notification{ namespace TweetDuck.Browser.Notification {
abstract partial class FormNotificationMain : FormNotificationBase{ abstract partial class FormNotificationMain : FormNotificationBase, CustomKeyboardHandler.IBrowserKeyHandler {
private readonly PluginManager plugins; protected sealed class NotificationInterfaceImpl : INotificationInterface {
public bool FreezeTimer {
get => notification.FreezeTimer;
set => notification.FreezeTimer = value;
}
public bool IsHovered => notification.IsCursorOverBrowser;
private readonly FormNotificationBase notification;
public NotificationInterfaceImpl(FormNotificationBase notification) {
this.notification = notification;
}
public void DisplayTooltip(string text) {
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
}
public void FinishCurrentNotification() {
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
}
public void ShowTweetDetail() {
notification.InvokeAsyncSafe(notification.ShowTweetDetail);
}
}
private static int FontSizeLevel {
get => NotificationBrowser.FontSize switch {
"largest" => 4,
"large" => 3,
"small" => 1,
"smallest" => 0,
_ => 2
};
}
private readonly int timerBarHeight; private readonly int timerBarHeight;
protected int timeLeft, totalTime; protected int timeLeft, totalTime;
@@ -31,63 +60,54 @@ namespace TweetDuck.Browser.Notification{
private bool? prevDisplayTimer; private bool? prevDisplayTimer;
private int? prevFontSize; private int? prevFontSize;
public virtual bool RequiresResize{ public virtual bool RequiresResize {
get{ get {
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel; return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
} }
set{ set {
if (value){ if (value) {
prevDisplayTimer = null; prevDisplayTimer = null;
prevFontSize = null; prevFontSize = null;
} }
else{ else {
prevDisplayTimer = Config.DisplayNotificationTimer; prevDisplayTimer = Config.DisplayNotificationTimer;
prevFontSize = FontSizeLevel; prevFontSize = FontSizeLevel;
} }
} }
} }
private int BaseClientWidth{ private int BaseClientWidth {
get => Config.NotificationSize switch{ get => Config.NotificationSize switch {
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Width, DesktopNotification.Size.Custom => Config.CustomNotificationSize.Width,
_ => BrowserUtils.Scale(284, SizeScale * (1.0 + 0.05 * FontSizeLevel)) _ => BrowserUtils.Scale(284, SizeScale * (1.0 + 0.05 * FontSizeLevel))
}; };
} }
private int BaseClientHeight{ private int BaseClientHeight {
get => Config.NotificationSize switch{ get => Config.NotificationSize switch {
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Height, DesktopNotification.Size.Custom => Config.CustomNotificationSize.Height,
_ => BrowserUtils.Scale(122, SizeScale * (1.0 + 0.08 * FontSizeLevel)) _ => BrowserUtils.Scale(122, SizeScale * (1.0 + 0.08 * FontSizeLevel))
}; };
} }
protected virtual string BodyClasses => IsCursorOverBrowser ? "td-notification td-hover" : "td-notification";
public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height - timerBarHeight) : ClientSize; public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height - timerBarHeight) : ClientSize;
protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){ protected FormNotificationMain(FormBrowser owner, CreateBrowserImplFunc createBrowserImpl) : base(owner, createBrowserImpl) {
InitializeComponent(); InitializeComponent();
this.plugins = pluginManager;
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale); this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
browser.KeyboardHandler = new KeyboardHandlerNotification(this); browser.KeyboardHandler = new CustomKeyboardHandler(this);
browser.RegisterJsBridge("$TD", new TweetDeckBridge.Notification(owner, this));
browser.LoadingStateChanged += Browser_LoadingStateChanged; browser.LoadingStateChanged += Browser_LoadingStateChanged;
browser.FrameLoadEnd += Browser_FrameLoadEnd;
plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(browser, url => TwitterUrls.IsTweetDeck(url) && url != BlankURL));
mouseHookDelegate = MouseHookProc; mouseHookDelegate = MouseHookProc;
Disposed += (sender, args) => StopMouseHook(true); Disposed += (sender, args) => StopMouseHook(true);
} }
// helpers private void SetOpacity(int opacity) {
if (currentOpacity != opacity) {
private void SetOpacity(int opacity){
if (currentOpacity != opacity){
currentOpacity = opacity; currentOpacity = opacity;
Opacity = opacity / 100.0; Opacity = opacity / 100.0;
} }
@@ -95,54 +115,53 @@ namespace TweetDuck.Browser.Notification{
// mouse wheel hook // mouse wheel hook
private void StartMouseHook(){ private void StartMouseHook() {
if (mouseHook == IntPtr.Zero){ if (mouseHook == IntPtr.Zero) {
mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WM_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0); mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WM_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0);
} }
} }
private void StopMouseHook(bool force){ private void StopMouseHook(bool force) {
if (mouseHook != IntPtr.Zero && (force || !blockXButtonUp)){ if (mouseHook != IntPtr.Zero && (force || !blockXButtonUp)) {
NativeMethods.UnhookWindowsHookEx(mouseHook); NativeMethods.UnhookWindowsHookEx(mouseHook);
mouseHook = IntPtr.Zero; mouseHook = IntPtr.Zero;
blockXButtonUp = false; blockXButtonUp = false;
} }
} }
private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam){ private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam) {
if (nCode == 0){ if (nCode == 0) {
int eventType = wParam.ToInt32(); int eventType = wParam.ToInt32();
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){ if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser) {
int delta = BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed * 0.01); int delta = BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed * 0.01);
if (Config.EnableSmoothScrolling){ if (Config.EnableSmoothScrolling) {
browser.ExecuteScriptAsync("window.TDGF_scrollSmoothly", (int)Math.Round(-delta / 0.6)); browser.BrowserCore.ExecuteScriptAsync("window.TDGF_scrollSmoothly", (int) Math.Round(-delta / 0.6));
} }
else{ else {
browser.SendMouseWheelEvent(0, 0, 0, delta, CefEventFlags.None); browser.SendMouseWheelEvent(0, 0, 0, delta, CefEventFlags.None);
} }
return NativeMethods.HOOK_HANDLED; return NativeMethods.HOOK_HANDLED;
} }
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){ else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)) {
int extraButton = NativeMethods.GetMouseHookData(lParam); int extraButton = NativeMethods.GetMouseHookData(lParam);
if (extraButton == 2){ // forward button if (extraButton == 2) { // forward button
this.InvokeAsyncSafe(FinishCurrentNotification); this.InvokeAsyncSafe(FinishCurrentNotification);
} }
else if (extraButton == 1){ // back button else if (extraButton == 1) { // back button
this.InvokeAsyncSafe(Close); this.InvokeAsyncSafe(Close);
} }
blockXButtonUp = true; blockXButtonUp = true;
this.InvokeAsyncSafe(AnalyticsFile.NotificationExtraMouseButtons.Trigger);
return NativeMethods.HOOK_HANDLED; return NativeMethods.HOOK_HANDLED;
} }
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){ else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp) {
blockXButtonUp = false; blockXButtonUp = false;
if (!Visible){ if (!Visible) {
StopMouseHook(false); StopMouseHook(false);
} }
@@ -155,15 +174,15 @@ namespace TweetDuck.Browser.Notification{
// event handlers // event handlers
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){ private void FormNotification_FormClosing(object sender, FormClosingEventArgs e) {
if (e.CloseReason == CloseReason.UserClosing){ if (e.CloseReason == CloseReason.UserClosing) {
HideNotification(); HideNotification();
e.Cancel = true; e.Cancel = true;
} }
} }
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){ private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
if (!e.IsLoading && browser.Address != BlankURL){ if (!e.IsLoading && browser.Address != NotificationBrowser.BlankURL) {
this.InvokeSafe(() => { this.InvokeSafe(() => {
Visible = true; // ensures repaint before moving the window to a visible location Visible = true; // ensures repaint before moving the window to a visible location
timerDisplayDelay.Start(); timerDisplayDelay.Start();
@@ -171,53 +190,44 @@ namespace TweetDuck.Browser.Notification{
} }
} }
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ private void timerDisplayDelay_Tick(object sender, EventArgs e) {
IFrame frame = e.Frame;
if (frame.IsMain && browser.Address != BlankURL){
frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
CefScriptExecutor.RunFile(frame, "notification.js");
}
}
private void timerDisplayDelay_Tick(object sender, EventArgs e){
OnNotificationReady(); OnNotificationReady();
timerDisplayDelay.Stop(); timerDisplayDelay.Stop();
} }
private void timerHideProgress_Tick(object sender, EventArgs e){ private void timerHideProgress_Tick(object sender, EventArgs e) {
bool isCursorInside = Bounds.Contains(Cursor.Position); bool isCursorInside = Bounds.Contains(Cursor.Position);
if (isCursorInside){ if (isCursorInside) {
StartMouseHook(); StartMouseHook();
SetOpacity(100); SetOpacity(100);
} }
else{ else {
StopMouseHook(false); StopMouseHook(false);
SetOpacity(Config.NotificationWindowOpacity); SetOpacity(Config.NotificationWindowOpacity);
} }
if (isCursorInside || FreezeTimer || ContextMenuOpen){ if (isCursorInside || FreezeTimer || ContextMenuOpen) {
return; return;
} }
timeLeft -= timerProgress.Interval; timeLeft -= timerProgress.Interval;
int value = BrowserUtils.Scale(progressBarTimer.Maximum + 25, (totalTime - timeLeft) / (double)totalTime); int value = BrowserUtils.Scale(progressBarTimer.Maximum + 25, (totalTime - timeLeft) / (double) totalTime);
progressBarTimer.SetValueInstant(Config.NotificationTimerCountDown ? progressBarTimer.Maximum - value : value); progressBarTimer.SetValueInstant(Config.NotificationTimerCountDown ? progressBarTimer.Maximum - value : value);
if (timeLeft <= 0){ if (timeLeft <= 0) {
FinishCurrentNotification(); FinishCurrentNotification();
} }
} }
// notification methods // notification methods
public virtual void ShowNotification(DesktopNotification notification){ public virtual void ShowNotification(DesktopNotification notification) {
LoadTweet(notification); LoadTweet(notification);
} }
public override void HideNotification(){ public override void HideNotification() {
base.HideNotification(); base.HideNotification();
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum; progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
@@ -227,12 +237,12 @@ namespace TweetDuck.Browser.Notification{
StopMouseHook(false); StopMouseHook(false);
} }
public override void FinishCurrentNotification(){ public override void FinishCurrentNotification() {
timerProgress.Stop(); timerProgress.Stop();
} }
public override void PauseNotification(){ public override void PauseNotification() {
if (!IsPaused){ if (!IsPaused) {
pausedDuringNotification = IsNotificationVisible; pausedDuringNotification = IsNotificationVisible;
timerProgress.Stop(); timerProgress.Stop();
StopMouseHook(true); StopMouseHook(true);
@@ -241,26 +251,16 @@ namespace TweetDuck.Browser.Notification{
base.PauseNotification(); base.PauseNotification();
} }
public override void ResumeNotification(){ public override void ResumeNotification() {
bool wasPaused = IsPaused; bool wasPaused = IsPaused;
base.ResumeNotification(); base.ResumeNotification();
if (wasPaused && !IsPaused && pausedDuringNotification){ if (wasPaused && !IsPaused && pausedDuringNotification) {
OnNotificationReady(); OnNotificationReady();
} }
} }
protected override string GetTweetHTML(DesktopNotification tweet){ protected override void LoadTweet(DesktopNotification tweet) {
string html = tweet.GenerateHtml(BodyClasses, HeadLayout, Config.CustomNotificationCSS);
foreach(InjectedHTML injection in plugins.NotificationInjections){
html = injection.InjectInto(html);
}
return html;
}
protected override void LoadTweet(DesktopNotification tweet){
timerProgress.Stop(); timerProgress.Stop();
totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue); totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue);
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum; progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
@@ -268,12 +268,12 @@ namespace TweetDuck.Browser.Notification{
base.LoadTweet(tweet); base.LoadTweet(tweet);
} }
protected override void SetNotificationSize(int width, int height){ protected override void SetNotificationSize(int width, int height) {
if (Config.DisplayNotificationTimer){ if (Config.DisplayNotificationTimer) {
ClientSize = new Size(width, height + timerBarHeight); ClientSize = new Size(width, height + timerBarHeight);
progressBarTimer.Visible = true; progressBarTimer.Visible = true;
} }
else{ else {
ClientSize = new Size(width, height); ClientSize = new Size(width, height);
progressBarTimer.Visible = false; progressBarTimer.Visible = false;
} }
@@ -281,8 +281,8 @@ namespace TweetDuck.Browser.Notification{
browser.ClientSize = new Size(width, height); browser.ClientSize = new Size(width, height);
} }
protected void PrepareAndDisplayWindow(){ protected void PrepareAndDisplayWindow() {
if (RequiresResize){ if (RequiresResize) {
RequiresResize = false; RequiresResize = false;
SetNotificationSize(BaseClientWidth, BaseClientHeight); SetNotificationSize(BaseClientWidth, BaseClientHeight);
} }
@@ -291,9 +291,28 @@ namespace TweetDuck.Browser.Notification{
MoveToVisibleLocation(); MoveToVisibleLocation();
} }
protected virtual void OnNotificationReady(){ protected virtual void OnNotificationReady() {
PrepareAndDisplayWindow(); PrepareAndDisplayWindow();
timerProgress.Start(); timerProgress.Start();
} }
bool CustomKeyboardHandler.IBrowserKeyHandler.HandleBrowserKey(Keys key) {
switch (key) {
case Keys.Enter:
this.InvokeAsyncSafe(FinishCurrentNotification);
return true;
case Keys.Escape:
this.InvokeAsyncSafe(HideNotification);
return true;
case Keys.Space:
this.InvokeAsyncSafe(() => FreezeTimer = !FreezeTimer);
return true;
default:
return false;
}
}
} }
} }

View File

@@ -3,24 +3,30 @@ using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Browser.Interfaces;
using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.TweetDeck;
namespace TweetDuck.Browser.Notification {
sealed partial class FormNotificationTweet : FormNotificationMain {
private static NotificationBrowser CreateBrowserImpl(IBrowserComponent browserComponent, INotificationInterface notificationInterface, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) {
return new NotificationBrowser.Tweet(browserComponent, notificationInterface, tweetDeckInterface, pluginManager);
}
namespace TweetDuck.Browser.Notification{
sealed partial class FormNotificationTweet : FormNotificationMain{
private const int NonIntrusiveIdleLimit = 30; private const int NonIntrusiveIdleLimit = 30;
private const int TrimMinimum = 32; private const int TrimMinimum = 32;
protected override Point PrimaryLocation => hasTemporarilyMoved && IsNotificationVisible ? Location : base.PrimaryLocation; protected override Point PrimaryLocation => hasTemporarilyMoved && IsNotificationVisible ? Location : base.PrimaryLocation;
private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position); private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position);
protected override bool CanDragWindow{ protected override bool CanDragWindow {
get{ get {
if (ModifierKeys.HasFlag(Keys.Alt)){ if (ModifierKeys.HasFlag(Keys.Alt)) {
hasTemporarilyMoved = true; hasTemporarilyMoved = true;
return true; return true;
} }
else{ else {
return false; return false;
} }
} }
@@ -30,22 +36,22 @@ namespace TweetDuck.Browser.Notification{
private bool needsTrim; private bool needsTrim;
private bool hasTemporarilyMoved; private bool hasTemporarilyMoved;
public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true){ public FormNotificationTweet(FormBrowser owner, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) : base(owner, (form, browserComponent) => CreateBrowserImpl(browserComponent, new NotificationInterfaceImpl(form), tweetDeckInterface, pluginManager)) {
InitializeComponent(); InitializeComponent();
Config.MuteToggled += Config_MuteToggled; Config.MuteToggled += Config_MuteToggled;
Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled; Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
if (Config.MuteNotifications){ if (Config.MuteNotifications) {
PauseNotification(); PauseNotification();
} }
} }
protected override void WndProc(ref Message m){ protected override void WndProc(ref Message m) {
if (m.Msg == 0x00A7){ // WM_NCMBUTTONDOWN if (m.Msg == 0x00A7) { // WM_NCMBUTTONDOWN
int hitTest = m.WParam.ToInt32(); int hitTest = m.WParam.ToInt32();
if (hitTest == 2 || hitTest == 20){ // HTCAPTION, HTCLOSE if (hitTest == 2 || hitTest == 20) { // HTCAPTION, HTCLOSE
hasTemporarilyMoved = false; hasTemporarilyMoved = false;
MoveToVisibleLocation(); MoveToVisibleLocation();
return; return;
@@ -57,24 +63,24 @@ namespace TweetDuck.Browser.Notification{
// event handlers // event handlers
private void Config_MuteToggled(object sender, EventArgs e){ private void Config_MuteToggled(object sender, EventArgs e) {
if (Config.MuteNotifications){ if (Config.MuteNotifications) {
PauseNotification(); PauseNotification();
} }
else{ else {
ResumeNotification(); ResumeNotification();
} }
} }
private void timerCursorCheck_Tick(object sender, EventArgs e){ private void timerCursorCheck_Tick(object sender, EventArgs e) {
if (!IsCursorOverNotificationArea){ if (!IsCursorOverNotificationArea) {
ResumeNotification(); ResumeNotification();
timerCursorCheck.Stop(); timerCursorCheck.Stop();
} }
} }
private void timerIdlePauseCheck_Tick(object sender, EventArgs e){ private void timerIdlePauseCheck_Tick(object sender, EventArgs e) {
if (NativeMethods.GetIdleSeconds() < Config.NotificationIdlePauseSeconds){ if (NativeMethods.GetIdleSeconds() < Config.NotificationIdlePauseSeconds) {
ResumeNotification(); ResumeNotification();
timerIdlePauseCheck.Stop(); timerIdlePauseCheck.Stop();
} }
@@ -82,26 +88,25 @@ namespace TweetDuck.Browser.Notification{
// notification methods // notification methods
public override void ShowNotification(DesktopNotification notification){ public override void ShowNotification(DesktopNotification notification) {
tweetQueue.Enqueue(notification); tweetQueue.Enqueue(notification);
if (!IsPaused){ if (!IsPaused) {
UpdateTitle(); UpdateTitle();
if (totalTime == 0){ if (totalTime == 0) {
LoadNextNotification(); LoadNextNotification();
} }
} }
needsTrim |= tweetQueue.Count >= TrimMinimum; needsTrim |= tweetQueue.Count >= TrimMinimum;
AnalyticsFile.DesktopNotifications.Trigger();
} }
public override void HideNotification(){ public override void HideNotification() {
base.HideNotification(); base.HideNotification();
tweetQueue.Clear(); tweetQueue.Clear();
if (needsTrim){ if (needsTrim) {
tweetQueue.TrimExcess(); tweetQueue.TrimExcess();
needsTrim = false; needsTrim = false;
} }
@@ -109,36 +114,36 @@ namespace TweetDuck.Browser.Notification{
hasTemporarilyMoved = false; hasTemporarilyMoved = false;
} }
public override void FinishCurrentNotification(){ public override void FinishCurrentNotification() {
if (tweetQueue.Count > 0){ if (tweetQueue.Count > 0) {
LoadNextNotification(); LoadNextNotification();
} }
else{ else {
HideNotification(); HideNotification();
} }
} }
public override void ResumeNotification(){ public override void ResumeNotification() {
bool wasPaused = IsPaused; bool wasPaused = IsPaused;
base.ResumeNotification(); base.ResumeNotification();
if (wasPaused && !IsPaused && !pausedDuringNotification && tweetQueue.Count > 0){ if (wasPaused && !IsPaused && !pausedDuringNotification && tweetQueue.Count > 0) {
LoadNextNotification(); LoadNextNotification();
} }
} }
private void LoadNextNotification(){ private void LoadNextNotification() {
if (!IsNotificationVisible){ if (!IsNotificationVisible) {
if (Config.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit){ if (Config.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit) {
if (!timerCursorCheck.Enabled){ if (!timerCursorCheck.Enabled) {
PauseNotification(); PauseNotification();
timerCursorCheck.Start(); timerCursorCheck.Start();
} }
return; return;
} }
else if (Config.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Config.NotificationIdlePauseSeconds){ else if (Config.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Config.NotificationIdlePauseSeconds) {
if (!timerIdlePauseCheck.Enabled){ if (!timerIdlePauseCheck.Enabled) {
PauseNotification(); PauseNotification();
timerIdlePauseCheck.Start(); timerIdlePauseCheck.Start();
} }
@@ -150,15 +155,15 @@ namespace TweetDuck.Browser.Notification{
LoadTweet(tweetQueue.Dequeue()); LoadTweet(tweetQueue.Dequeue());
} }
protected override void UpdateTitle(){ protected override void UpdateTitle() {
base.UpdateTitle(); base.UpdateTitle();
if (tweetQueue.Count > 0){ if (tweetQueue.Count > 0) {
Text = Text + " (" + tweetQueue.Count + " more left)"; Text = Text + " (" + tweetQueue.Count + " more left)";
} }
} }
protected override void OnNotificationReady(){ protected override void OnNotificationReady() {
UpdateTitle(); UpdateTitle();
base.OnNotificationReady(); base.OnNotificationReady();
} }

View File

@@ -1,98 +1,84 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.IO;
using System.Windows.Forms; using System.Threading.Tasks;
using CefSharp; using CefSharp;
using TweetDuck.Browser.Adapters; using CefSharp.DevTools.Page;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Dialogs; using TweetDuck.Dialogs;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core.Data; using TweetLib.Browser.Interfaces;
using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Resources;
namespace TweetDuck.Browser.Notification.Screenshot{ namespace TweetDuck.Browser.Notification.Screenshot {
sealed class FormNotificationScreenshotable : FormNotificationBase{ sealed class FormNotificationScreenshotable : FormNotificationBase {
protected override bool CanDragWindow => false; private static NotificationBrowser CreateBrowserImpl( IBrowserComponent browserComponent, PluginManager pluginManager) {
return new NotificationBrowser.Screenshot(browserComponent, pluginManager.NotificationInjections);
private readonly PluginManager plugins;
private int height;
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager, string html, int width) : base(owner, false){
this.plugins = pluginManager;
int realWidth = BrowserUtils.Scale(width, DpiScale);
browser.RegisterJsBridge("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
browser.LoadingStateChanged += (sender, args) => {
if (args.IsLoading){
return;
} }
string script = Program.Resources.LoadSilent("screenshot.js"); protected override bool CanDragWindow => false;
if (script == null){ private int height;
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager, string html, int width) : base(owner, (_, browserComponent) => CreateBrowserImpl(browserComponent, pluginManager)) {
int realWidth = BrowserUtils.Scale(width, DpiScale);
browserComponent.AttachBridgeObject("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
browserComponent.BrowserLoaded += (sender, args) => {
string script = ResourceUtils.ReadFileOrNull("notification/screenshot/screenshot.js");
if (script == null) {
this.InvokeAsyncSafe(callback); this.InvokeAsyncSafe(callback);
return; return;
} }
using IFrame frame = args.Browser.MainFrame; string substituted = script.Replace("{width}", realWidth.ToString()).Replace("1/*FRAMES*/", TweetScreenshotManager.WaitFrames.ToString());
CefScriptExecutor.RunScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot"); browserComponent.RunScript("gen:screenshot", substituted);
}; };
SetNotificationSize(realWidth, 1024); SetNotificationSize(realWidth, 1024);
LoadTweet(new DesktopNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty)); LoadTweet(new DesktopNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
} }
protected override string GetTweetHTML(DesktopNotification tweet){ private void SetScreenshotHeight(int browserHeight) {
string html = tweet.GenerateHtml("td-screenshot", HeadLayout, Config.CustomNotificationCSS);
foreach(InjectedHTML injection in plugins.NotificationInjections){
html = injection.InjectInto(html);
}
return html;
}
private void SetScreenshotHeight(int browserHeight){
this.height = BrowserUtils.Scale(browserHeight, SizeScale); this.height = BrowserUtils.Scale(browserHeight, SizeScale);
} }
public bool TakeScreenshot(bool ignoreHeightError = false){ public Task<Image> TakeScreenshot(bool ignoreHeightError = false) {
if (!ignoreHeightError){ if (!ignoreHeightError) {
if (height == 0){ if (height == 0) {
FormMessage.Error("Screenshot Failed", "Could not detect screenshot size.", FormMessage.OK); FormMessage.Error("Screenshot Failed", "Could not detect screenshot size.", FormMessage.OK);
return false; return null;
} }
else if (height > ClientSize.Height){ else if (height > ClientSize.Height) {
FormMessage.Error("Screenshot Failed", $"Screenshot is too large: {height}px > {ClientSize.Height}px", FormMessage.OK); FormMessage.Error("Screenshot Failed", $"Screenshot is too large: {height}px > {ClientSize.Height}px", FormMessage.OK);
return false; return null;
} }
} }
if (!WindowsUtils.IsAeroEnabled){ return Task.Run(TakeScreenshotImpl);
MoveToVisibleLocation(); // TODO make this look nicer I guess
} }
IntPtr context = NativeMethods.GetDC(this.Handle); private async Task<Image> TakeScreenshotImpl() {
if (this.height == 0) {
if (context == IntPtr.Zero){ return null;
FormMessage.Error("Screenshot Failed", "Could not retrieve a graphics context handle for the notification window to take the screenshot.", FormMessage.OK);
return false;
}
else{
using Bitmap bmp = new Bitmap(ClientSize.Width, Math.Max(1, height), PixelFormat.Format32bppRgb);
try{
NativeMethods.RenderSourceIntoBitmap(context, bmp);
}finally{
NativeMethods.ReleaseDC(this.Handle, context);
} }
Clipboard.SetImage(bmp); Viewport viewport = new Viewport {
return true; Width = this.ClientSize.Width,
Height = this.height,
Scale = 1
};
byte[] data;
using (var devToolsClient = browser.GetDevToolsClient()) {
data = (await devToolsClient.Page.CaptureScreenshotAsync(CaptureScreenshotFormat.Png, clip: viewport)).Data;
} }
return Image.FromStream(new MemoryStream(data));
} }
} }
} }

View File

@@ -3,25 +3,25 @@ using System.Diagnostics.CodeAnalysis;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Controls; using TweetDuck.Controls;
namespace TweetDuck.Browser.Notification.Screenshot{ namespace TweetDuck.Browser.Notification.Screenshot {
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
sealed class ScreenshotBridge{ sealed class ScreenshotBridge {
private readonly Control owner; private readonly Control owner;
private readonly Action<int> safeSetHeight; private readonly Action<int> safeSetHeight;
private readonly Action safeTriggerScreenshot; private readonly Action safeTriggerScreenshot;
public ScreenshotBridge(Control owner, Action<int> safeSetHeight, Action safeTriggerScreenshot){ public ScreenshotBridge(Control owner, Action<int> safeSetHeight, Action safeTriggerScreenshot) {
this.owner = owner; this.owner = owner;
this.safeSetHeight = safeSetHeight; this.safeSetHeight = safeSetHeight;
this.safeTriggerScreenshot = safeTriggerScreenshot; this.safeTriggerScreenshot = safeTriggerScreenshot;
} }
public void SetHeight(int tweetHeight){ public void SetHeight(int tweetHeight) {
owner.InvokeSafe(() => safeSetHeight(tweetHeight)); owner.InvokeSafe(() => safeSetHeight(tweetHeight));
} }
public void TriggerScreenshot(){ public void TriggerScreenshot() {
owner.InvokeSafe(safeTriggerScreenshot); owner.InvokeSafe(safeTriggerScreenshot);
} }
} }

View File

@@ -7,18 +7,20 @@
#endif #endif
using System; using System;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetLib.Core;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
#if GEN_SCREENSHOT_FRAMES #if GEN_SCREENSHOT_FRAMES
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO; using System.IO;
using TweetDuck.Core.Utils; using TweetDuck.Utils;
#endif #endif
namespace TweetDuck.Browser.Notification.Screenshot{ namespace TweetDuck.Browser.Notification.Screenshot {
sealed class TweetScreenshotManager : IDisposable{ sealed class TweetScreenshotManager : IDisposable {
private readonly FormBrowser owner; private readonly FormBrowser owner;
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly Timer timeout; private readonly Timer timeout;
@@ -35,35 +37,35 @@ namespace TweetDuck.Browser.Notification.Screenshot{
private FormNotificationScreenshotable screenshot; private FormNotificationScreenshotable screenshot;
public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager){ public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager) {
this.owner = owner; this.owner = owner;
this.plugins = pluginManager; this.plugins = pluginManager;
this.timeout = new Timer{ Interval = 8000 }; this.timeout = new Timer { Interval = 8000 };
this.timeout.Tick += timeout_Tick; this.timeout.Tick += timeout_Tick;
this.disposer = new Timer{ Interval = 1 }; this.disposer = new Timer { Interval = 1 };
this.disposer.Tick += disposer_Tick; this.disposer.Tick += disposer_Tick;
#if GEN_SCREENSHOT_FRAMES #if GEN_SCREENSHOT_FRAMES
this.debugger = new Timer{ Interval = 16 }; this.debugger = new Timer { Interval = 16 };
this.debugger.Tick += debugger_Tick; this.debugger.Tick += debugger_Tick;
#endif #endif
} }
private void timeout_Tick(object sender, EventArgs e){ private void timeout_Tick(object sender, EventArgs e) {
timeout.Stop(); timeout.Stop();
OnFinished(); OnFinished();
} }
private void disposer_Tick(object sender, EventArgs e){ private void disposer_Tick(object sender, EventArgs e) {
disposer.Stop(); disposer.Stop();
screenshot.Dispose(); screenshot.Dispose();
screenshot = null; screenshot = null;
} }
public void Trigger(string html, int width){ public void Trigger(string html, int width) {
if (screenshot != null){ if (screenshot != null) {
return; return;
} }
@@ -80,14 +82,21 @@ namespace TweetDuck.Browser.Notification.Screenshot{
#endif #endif
} }
private void Callback(){ private void Callback() {
if (!timeout.Enabled){ if (!timeout.Enabled) {
return; return;
} }
timeout.Stop(); timeout.Stop();
screenshot.TakeScreenshot(); screenshot.TakeScreenshot().ContinueWith(HandleResult, TaskScheduler.FromCurrentSynchronizationContext());
}
private void HandleResult(Task<Image> task) {
if (task.IsFaulted) {
App.ErrorHandler.HandleException("Screenshot Failed", "An error occurred while taking a screenshot.", true, task.Exception!.InnerException);
}
else if (task.IsCompleted) {
Clipboard.SetImage(task.Result);
#if !NO_HIDE_SCREENSHOTS #if !NO_HIDE_SCREENSHOTS
OnFinished(); OnFinished();
#else #else
@@ -95,8 +104,9 @@ namespace TweetDuck.Browser.Notification.Screenshot{
screenshot.FormClosed += (sender, args) => disposer.Start(); screenshot.FormClosed += (sender, args) => disposer.Start();
#endif #endif
} }
}
private void OnFinished(){ private void OnFinished() {
#if GEN_SCREENSHOT_FRAMES #if GEN_SCREENSHOT_FRAMES
debugger.Stop(); debugger.Stop();
#endif #endif
@@ -106,7 +116,7 @@ namespace TweetDuck.Browser.Notification.Screenshot{
disposer.Start(); disposer.Start();
} }
public void Dispose(){ public void Dispose() {
#if GEN_SCREENSHOT_FRAMES #if GEN_SCREENSHOT_FRAMES
debugger.Dispose(); debugger.Dispose();
#endif #endif
@@ -119,30 +129,36 @@ namespace TweetDuck.Browser.Notification.Screenshot{
#if GEN_SCREENSHOT_FRAMES #if GEN_SCREENSHOT_FRAMES
private static readonly string DebugScreenshotPath = Path.Combine(Program.StoragePath, "TD_Screenshots"); private static readonly string DebugScreenshotPath = Path.Combine(Program.StoragePath, "TD_Screenshots");
private void StartDebugger(){ private void StartDebugger() {
frameCounter = 0; frameCounter = 0;
try{ try {
Directory.Delete(DebugScreenshotPath, true); Directory.Delete(DebugScreenshotPath, true);
WindowsUtils.TrySleepUntil(() => !Directory.Exists(DebugScreenshotPath), 1000, 10); WindowsUtils.TrySleepUntil(() => !Directory.Exists(DebugScreenshotPath), 1000, 10);
}catch(DirectoryNotFoundException){} } catch (DirectoryNotFoundException) {}
Directory.CreateDirectory(DebugScreenshotPath); Directory.CreateDirectory(DebugScreenshotPath);
debugger.Start(); debugger.Start();
} }
private void debugger_Tick(object sender, EventArgs e){ private void debugger_Tick(object sender, EventArgs e) {
if (frameCounter < 63 && screenshot.TakeScreenshot(true)){ if (frameCounter < 63) {
try{ int frame = ++frameCounter;
Clipboard.GetImage()?.Save(Path.Combine(DebugScreenshotPath, "frame_" + (++frameCounter) + ".png"), ImageFormat.Png); screenshot.TakeScreenshot(true).ContinueWith(task => SaveDebugFrame(task, frame), TaskScheduler.FromCurrentSynchronizationContext());
}catch{
System.Diagnostics.Debug.WriteLine("Failed generating frame " + frameCounter);
} }
} else {
else{
debugger.Stop(); debugger.Stop();
} }
} }
private static void SaveDebugFrame(Task<Image> task, int frame) {
if (task.IsFaulted) {
System.Diagnostics.Debug.WriteLine("Failed generating frame " + frame + ": " + task.Exception!.InnerException);
}
else if (task.IsCompleted) {
task.Result?.Save(Path.Combine(DebugScreenshotPath, "frame_" + (++frame) + ".png"), ImageFormat.Png);
}
}
#endif #endif
} }
} }

View File

@@ -1,20 +1,37 @@
using System; using System.Drawing;
using System.Drawing;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using TweetDuck.Browser.Adapters;
using TweetDuck.Browser.Data;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Dialogs; using TweetDuck.Dialogs;
using TweetDuck.Dialogs.Settings; using TweetDuck.Dialogs.Settings;
using TweetDuck.Management; using TweetDuck.Management;
using TweetLib.Core.Features.TweetDeck;
namespace TweetDuck.Browser.Notification{ namespace TweetDuck.Browser.Notification {
static class SoundNotification{ sealed class SoundNotification : ISoundNotificationHandler {
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm"; public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
public static Func<IResourceHandler> CreateFileHandler(string path){ private readonly CefResourceHandlerRegistry registry;
string mimeType = Path.GetExtension(path) switch{
public SoundNotification(CefResourceHandlerRegistry registry) {
this.registry = registry;
}
public void Unregister(string url) {
registry.Unregister(url);
}
public void Register(string url, string path) {
var fileHandler = CreateFileHandler(path);
if (fileHandler.HasValue) {
var (data, mimeType) = fileHandler.Value;
registry.RegisterStatic(url, data, mimeType);
}
}
private static (byte[] data, string mimeType)? CreateFileHandler(string path) {
string mimeType = Path.GetExtension(path) switch {
".weba" => "audio/webm", ".weba" => "audio/webm",
".webm" => "audio/webm", ".webm" => "audio/webm",
".wav" => "audio/wav", ".wav" => "audio/wav",
@@ -25,9 +42,9 @@ namespace TweetDuck.Browser.Notification{
_ => null _ => null
}; };
try{ try {
return ResourceHandlers.ForBytes(File.ReadAllBytes(path), mimeType); return (File.ReadAllBytes(path), mimeType);
}catch{ } catch {
FormBrowser browser = FormManager.TryFind<FormBrowser>(); FormBrowser browser = FormManager.TryFind<FormBrowser>();
browser?.InvokeAsyncSafe(() => { browser?.InvokeAsyncSafe(() => {
@@ -38,7 +55,7 @@ namespace TweetDuck.Browser.Notification{
btnViewOptions.Width += 16; btnViewOptions.Width += 16;
btnViewOptions.Location = new Point(btnViewOptions.Location.X - 16, btnViewOptions.Location.Y); btnViewOptions.Location = new Point(btnViewOptions.Location.X - 16, btnViewOptions.Location.Y);
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions){ if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions) {
browser.OpenSettings(typeof(TabSettingsSounds)); browser.OpenSettings(typeof(TabSettingsSounds));
} }
}); });

View File

@@ -2,12 +2,17 @@
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetLib.Core.Systems.Configuration;
using Res = TweetDuck.Properties.Resources; using Res = TweetDuck.Properties.Resources;
namespace TweetDuck.Browser{ namespace TweetDuck.Browser {
sealed partial class TrayIcon : Component{ sealed partial class TrayIcon : Component {
public enum Behavior{ // keep order public enum Behavior { // keep order
Disabled, DisplayOnly, MinimizeToTray, CloseToTray, Combined Disabled,
DisplayOnly,
MinimizeToTray,
CloseToTray,
Combined
} }
private static UserConfig Config => Program.Config.User; private static UserConfig Config => Program.Config.User;
@@ -15,25 +20,25 @@ namespace TweetDuck.Browser{
public event EventHandler ClickRestore; public event EventHandler ClickRestore;
public event EventHandler ClickClose; public event EventHandler ClickClose;
public bool Visible{ public bool Visible {
get{ get {
return notifyIcon.Visible; return notifyIcon.Visible;
} }
set{ set {
notifyIcon.Visible = value; notifyIcon.Visible = value;
hasNotifications = false; hasNotifications = false;
UpdateIcon(); UpdateIcon();
} }
} }
public bool HasNotifications{ public bool HasNotifications {
get{ get {
return hasNotifications; return hasNotifications;
} }
set{ set {
if (hasNotifications != value){ if (hasNotifications != value) {
hasNotifications = value; hasNotifications = value;
UpdateIcon(); UpdateIcon();
} }
@@ -43,7 +48,7 @@ namespace TweetDuck.Browser{
private readonly ContextMenu contextMenu; private readonly ContextMenu contextMenu;
private bool hasNotifications; private bool hasNotifications;
public TrayIcon(){ public TrayIcon() {
InitializeComponent(); InitializeComponent();
this.contextMenu = new ContextMenu(); this.contextMenu = new ContextMenu();
@@ -59,12 +64,12 @@ namespace TweetDuck.Browser{
Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled; Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
} }
public TrayIcon(IContainer container) : this(){ public TrayIcon(IContainer container) : this() {
container.Add(this); container.Add(this);
} }
protected override void Dispose(bool disposing){ protected override void Dispose(bool disposing) {
if (disposing){ if (disposing) {
components?.Dispose(); components?.Dispose();
contextMenu.Dispose(); contextMenu.Dispose();
} }
@@ -72,52 +77,52 @@ namespace TweetDuck.Browser{
base.Dispose(disposing); base.Dispose(disposing);
} }
private void UpdateIcon(){ private void UpdateIcon() {
if (Visible){ if (Visible) {
notifyIcon.Icon = hasNotifications ? Res.icon_tray_new : Config.MuteNotifications ? Res.icon_tray_muted : Res.icon_tray; notifyIcon.Icon = HasNotifications ? Res.icon_tray_new : Config.MuteNotifications ? Res.icon_tray_muted : Res.icon_tray;
} }
} }
// event handlers // event handlers
private void Config_MuteToggled(object sender, EventArgs e){ private void Config_MuteToggled(object sender, EventArgs e) {
UpdateIcon(); UpdateIcon();
} }
private void trayIcon_MouseClick(object sender, MouseEventArgs e){ private void trayIcon_MouseClick(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left){ if (e.Button == MouseButtons.Left) {
menuItemRestore_Click(sender, e); menuItemRestore_Click(sender, e);
} }
} }
private void contextMenu_Popup(object sender, EventArgs e){ private void contextMenu_Popup(object sender, EventArgs e) {
contextMenu.MenuItems[1].Checked = Config.MuteNotifications; contextMenu.MenuItems[1].Checked = Config.MuteNotifications;
} }
private void menuItemRestore_Click(object sender, EventArgs e){ private void menuItemRestore_Click(object sender, EventArgs e) {
ClickRestore?.Invoke(this, e); ClickRestore?.Invoke(this, e);
} }
private void menuItemMuteNotifications_Click(object sender, EventArgs e){ private void menuItemMuteNotifications_Click(object sender, EventArgs e) {
Config.MuteNotifications = !contextMenu.MenuItems[1].Checked; Config.MuteNotifications = !contextMenu.MenuItems[1].Checked;
Config.Save(); Config.Save();
} }
private void menuItemClose_Click(object sender, EventArgs e){ private void menuItemClose_Click(object sender, EventArgs e) {
ClickClose?.Invoke(this, e); ClickClose?.Invoke(this, e);
} }
} }
static class BehaviorExtensions{ static class BehaviorExtensions {
public static bool ShouldDisplayIcon(this TrayIcon.Behavior behavior){ public static bool ShouldDisplayIcon(this TrayIcon.Behavior behavior) {
return behavior != TrayIcon.Behavior.Disabled; return behavior != TrayIcon.Behavior.Disabled;
} }
public static bool ShouldHideOnMinimize(this TrayIcon.Behavior behavior){ public static bool ShouldHideOnMinimize(this TrayIcon.Behavior behavior) {
return behavior == TrayIcon.Behavior.MinimizeToTray || behavior == TrayIcon.Behavior.Combined; return behavior == TrayIcon.Behavior.MinimizeToTray || behavior == TrayIcon.Behavior.Combined;
} }
public static bool ShouldHideOnClose(this TrayIcon.Behavior behavior){ public static bool ShouldHideOnClose(this TrayIcon.Behavior behavior) {
return behavior == TrayIcon.Behavior.CloseToTray || behavior == TrayIcon.Behavior.Combined; return behavior == TrayIcon.Behavior.CloseToTray || behavior == TrayIcon.Behavior.Combined;
} }
} }

View File

@@ -1,41 +1,34 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Text;
using System.Windows.Forms;
using CefSharp; using CefSharp;
using CefSharp.WinForms; using CefSharp.WinForms;
using TweetDuck.Browser.Adapters; using TweetDuck.Browser.Adapters;
using TweetDuck.Browser.Bridge;
using TweetDuck.Browser.Data;
using TweetDuck.Browser.Handling; using TweetDuck.Browser.Handling;
using TweetDuck.Browser.Handling.General;
using TweetDuck.Browser.Notification; using TweetDuck.Browser.Notification;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Controls;
using TweetDuck.Plugins;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums; using TweetLib.Core.Features.TweetDeck;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Utils; using TweetLib.Core.Systems.Updates;
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
using TweetDeckBrowserImpl = TweetLib.Core.Features.TweetDeck.TweetDeckBrowser;
namespace TweetDuck.Browser{ namespace TweetDuck.Browser {
sealed class TweetDeckBrowser : IDisposable{ sealed class TweetDeckBrowser : IDisposable {
private static UserConfig Config => Program.Config.User; public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
private const string ErrorUrl = "http://td/error"; public bool Ready => browserComponent.Ready;
private const string TwitterStyleUrl = "https://abs.twimg.com/tduck/css";
public bool Ready { get; private set; } public bool Enabled {
public bool Enabled{
get => browser.Enabled; get => browser.Enabled;
set => browser.Enabled = value; set => browser.Enabled = value;
} }
public bool IsTweetDeckWebsite{ public bool IsTweetDeckWebsite {
get{ get {
if (!Ready){ if (!Ready) {
return false; return false;
} }
@@ -44,246 +37,96 @@ namespace TweetDuck.Browser{
} }
} }
public TweetDeckFunctions Functions => browserImpl.Functions;
private readonly CefBrowserComponent browserComponent;
private readonly TweetDeckBrowserImpl browserImpl;
private readonly ChromiumWebBrowser browser; private readonly ChromiumWebBrowser browser;
private readonly ResourceHandlers resourceHandlers;
private string prevSoundNotificationPath = null;
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge tdBridge, UpdateBridge updateBridge){
var resourceRequestHandler = new ResourceRequestHandlerBrowser();
resourceHandlers = resourceRequestHandler.ResourceHandlers;
resourceHandlers.Register(FormNotificationBase.AppLogo);
resourceHandlers.Register(TwitterUtils.LoadingSpinner);
public TweetDeckBrowser(FormBrowser owner, PluginManager pluginManager, ITweetDeckInterface tweetDeckInterface, UpdateChecker updateChecker) {
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser(); RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck){ this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck) {
DialogHandler = new FileDialogHandler(), DialogHandler = new FileDialogHandler(),
DragHandler = new DragHandlerBrowser(requestHandler), DragHandler = new DragHandlerBrowser(requestHandler),
MenuHandler = new ContextMenuBrowser(owner), KeyboardHandler = new CustomKeyboardHandler(owner),
JsDialogHandler = new JavaScriptDialogHandler(), RequestHandler = requestHandler
KeyboardHandler = new KeyboardHandlerBrowser(owner),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = requestHandler,
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
}; };
this.browser.LoadingStateChanged += browser_LoadingStateChanged; // ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable
this.browser.FrameLoadStart += browser_FrameLoadStart; this.browser.BrowserSettings.BackgroundColor = (uint) BackgroundColor.ToArgb();
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
this.browser.LoadError += browser_LoadError;
this.browser.RegisterJsBridge("$TD", tdBridge); var extraContext = new TweetDeckExtraContext();
this.browser.RegisterJsBridge("$TDU", updateBridge); var resourceHandlerRegistry = new CefResourceHandlerRegistry();
var soundNotificationHandler = new SoundNotification(resourceHandlerRegistry);
this.browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb(); this.browserComponent = new ComponentImpl(browser, owner, extraContext, resourceHandlerRegistry);
this.browser.Dock = DockStyle.None; this.browserImpl = new TweetDeckBrowserImpl(browserComponent, tweetDeckInterface, extraContext, soundNotificationHandler, pluginManager, updateChecker);
this.browser.Location = ControlExtensions.InvisibleLocation;
this.browser.SetupZoomEvents();
owner.Controls.Add(browser); if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)) {
plugins.Register(PluginEnvironment.Browser, new PluginDispatcher(browser, TwitterUrls.IsTweetDeck)); browserComponent.PageLoadEnd += (sender, args) => {
if (TwitterUrls.IsTweetDeck(args.Url)) {
Config.MuteToggled += Config_MuteToggled; browserComponent.RunScript("gen:gdpr", "TD.storage.Account.prototype.requiresConsent = function() { return false; }");
Config.SoundNotificationChanged += Config_SoundNotificationInfoChanged; }
};
} }
// setup and management owner.Controls.Add(browser);
}
public void PrepareSize(Size size){ private sealed class ComponentImpl : CefBrowserComponent {
if (!Ready){ private readonly FormBrowser owner;
private readonly TweetDeckExtraContext extraContext;
private readonly CefResourceHandlerRegistry registry;
public ComponentImpl(ChromiumWebBrowser browser, FormBrowser owner, TweetDeckExtraContext extraContext, CefResourceHandlerRegistry registry) : base(browser) {
this.owner = owner;
this.extraContext = extraContext;
this.registry = registry;
}
protected override ContextMenuBase SetupContextMenu(IContextMenuHandler handler) {
return new ContextMenuBrowser(owner, handler, extraContext);
}
protected override CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler) {
return new CefResourceHandlerFactory(handler, registry);
}
}
public void PrepareSize(Size size) {
if (!Ready) {
browser.Size = size; browser.Size = size;
} }
} }
private void OnBrowserReady(){ public void Dispose() {
if (!Ready){ browserImpl.Dispose();
browser.Location = Point.Empty;
browser.Dock = DockStyle.Fill;
Ready = true;
}
}
public void Focus(){
browser.Focus();
}
public void Dispose(){
Config.MuteToggled -= Config_MuteToggled;
Config.SoundNotificationChanged -= Config_SoundNotificationInfoChanged;
browser.Dispose(); browser.Dispose();
} }
// event handlers public void Focus() {
browser.Focus();
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){
foreach(string word in TwitterUtils.DictionaryWords){
browser.AddWordToDictionary(word);
} }
browser.BeginInvoke(new Action(OnBrowserReady)); public void OpenDevTools() {
browser.LoadingStateChanged -= browser_LoadingStateChanged; browser.OpenDevToolsCustom();
}
} }
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){ public void ReloadToTweetDeck() {
IFrame frame = e.Frame; browserImpl.ReloadToTweetDeck();
if (frame.IsMain){
string url = frame.Url;
if (TwitterUrls.IsTwitter(url)){
string css = Program.Resources.Load("styles/twitter.css");
resourceHandlers.Register(TwitterStyleUrl, ResourceHandlers.ForString(css, "text/css"));
CefScriptExecutor.RunFile(frame, "twitter.js");
} }
if (!TwitterUrls.IsTwitterLogin2Factor(url)){ public void SaveVideo(string url, string username) {
frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorOverride); browserImpl.FileDownloadManager.SaveVideo(url, username);
}
}
} }
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ public void HideVideoOverlay(bool focus) {
IFrame frame = e.Frame; if (focus) {
string url = frame.Url;
if (frame.IsMain){
if (TwitterUrls.IsTweetDeck(url)){
UpdateProperties();
CefScriptExecutor.RunFile(frame, "code.js");
InjectBrowserCSS();
ReinjectCustomCSS(Config.CustomBrowserCSS);
Config_SoundNotificationInfoChanged(null, EventArgs.Empty);
TweetDeckBridge.ResetStaticProperties();
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)){
CefScriptExecutor.RunScript(frame, "TD.storage.Account.prototype.requiresConsent = function(){ return false; }", "gen:gdpr");
}
if (Config.FirstRun){
CefScriptExecutor.RunFile(frame, "introduction.js");
}
}
CefScriptExecutor.RunFile(frame, "update.js");
}
if (url == ErrorUrl){
resourceHandlers.Unregister(ErrorUrl);
}
}
private void browser_LoadError(object sender, LoadErrorEventArgs e){
if (e.ErrorCode == CefErrorCode.Aborted){
return;
}
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
string errorPage = Program.Resources.LoadSilent("pages/error.html");
if (errorPage != null){
string errorName = Enum.GetName(typeof(CefErrorCode), e.ErrorCode);
string errorTitle = StringUtils.ConvertPascalCaseToScreamingSnakeCase(errorName ?? string.Empty);
resourceHandlers.Register(ErrorUrl, ResourceHandlers.ForString(errorPage.Replace("{err}", errorTitle)));
browser.Load(ErrorUrl);
}
}
}
private void Config_MuteToggled(object sender, EventArgs e){
UpdateProperties();
}
private void Config_SoundNotificationInfoChanged(object sender, EventArgs e){
const string soundUrl = "https://ton.twimg.com/tduck/updatesnd";
bool hasCustomSound = Config.IsCustomSoundNotificationSet;
string newNotificationPath = Config.NotificationSoundPath;
if (prevSoundNotificationPath != newNotificationPath){
prevSoundNotificationPath = newNotificationPath;
if (hasCustomSound){
resourceHandlers.Register(soundUrl, SoundNotification.CreateFileHandler(newNotificationPath));
}
else{
resourceHandlers.Unregister(soundUrl);
}
}
browser.ExecuteScriptAsync("TDGF_setSoundNotificationData", hasCustomSound, Config.NotificationSoundVolume);
}
// external handling
public void HideVideoOverlay(bool focus){
if (focus){
browser.GetBrowser().GetHost().SendFocusEvent(true); browser.GetBrowser().GetHost().SendFocusEvent(true);
} }
browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()"); browserComponent.RunScript("gen:hidevideo", "$('#td-video-player-overlay').remove()");
}
// javascript calls
public void ReloadToTweetDeck(){
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUrls.TweetDeck}'");
}
public void UpdateProperties(){
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Browser));
}
public void InjectBrowserCSS(){
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", Program.Resources.Load("styles/browser.css")?.TrimEnd() ?? string.Empty);
}
public void ReinjectCustomCSS(string css){
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
}
public void OnMouseClickExtra(IntPtr param){
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (param.ToInt32() >> 16) & 0xFFFF);
}
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){
browser.ExecuteScriptAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
}
public void AddSearchColumn(string query){
browser.ExecuteScriptAsync("TDGF_performSearch", query);
}
public void TriggerTweetScreenshot(){
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
}
public void ReloadColumns(){
browser.ExecuteScriptAsync("TDGF_reloadColumns()");
}
public void PlaySoundNotification(){
browser.ExecuteScriptAsync("TDGF_playSoundNotification()");
}
public void ApplyROT13(){
browser.ExecuteScriptAsync("TDGF_applyROT13()");
}
public void ShowUpdateNotification(string versionTag, string releaseNotes){
browser.ExecuteScriptAsync("TDUF_displayNotification", versionTag, Convert.ToBase64String(Encoding.GetEncoding("iso-8859-1").GetBytes(releaseNotes)));
}
public void OpenDevTools(){
browser.OpenDevToolsCustom();
} }
} }
} }

View File

@@ -1,47 +1,44 @@
using System; using System;
using TweetLib.Core.Collections; using TweetLib.Utils.Collections;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration {
static class Arguments{ static class Arguments {
// public args // public args
public const string ArgDataFolder = "-datafolder"; public const string ArgDataFolder = "-datafolder";
public const string ArgLogging = "-log"; public const string ArgLogging = "-log";
public const string ArgIgnoreGDPR = "-nogdpr"; public const string ArgIgnoreGDPR = "-nogdpr";
public const string ArgHttpVideo = "-httpvideo";
public const string ArgFreeze = "-freeze"; public const string ArgFreeze = "-freeze";
// internal args // internal args
public const string ArgRestart = "-restart"; public const string ArgRestart = "-restart";
public const string ArgImportCookies = "-importcookies";
public const string ArgDeleteCookies = "-deletecookies";
public const string ArgUpdated = "-updated"; public const string ArgUpdated = "-updated";
// class data and methods // class data and methods
private static readonly CommandLineArgs Current = CommandLineArgs.FromStringArray('-', Environment.GetCommandLineArgs()); private static readonly CommandLineArgs Current = CommandLineArgs.FromStringArray('-', Environment.GetCommandLineArgs());
public static bool HasFlag(string flag){ public static bool HasFlag(string flag) {
return Current.HasFlag(flag); return Current.HasFlag(flag);
} }
public static string GetValue(string key){ public static string GetValue(string key) {
return Current.GetValue(key); return Current.GetValue(key);
} }
public static CommandLineArgs GetCurrentClean(){ public static CommandLineArgs GetCurrentClean() {
CommandLineArgs args = Current.Clone(); CommandLineArgs args = Current.Clone();
args.RemoveFlag(ArgRestart); args.RemoveFlag(ArgRestart);
args.RemoveFlag(ArgImportCookies);
args.RemoveFlag(ArgDeleteCookies);
args.RemoveFlag(ArgUpdated); args.RemoveFlag(ArgUpdated);
return args; return args;
} }
public static CommandLineArgs GetCurrentForInstaller(){ public static CommandLineArgs GetCurrentForInstaller() {
CommandLineArgs args = GetCurrentClean(); CommandLineArgs args = GetCurrentClean();
args.AddFlag(ArgUpdated); args.AddFlag(ArgUpdated);
return args; return args;
} }
public static string GetCurrentForInstallerCmd(){ public static string GetCurrentForInstallerCmd() {
return GetCurrentForInstaller().ToString().Replace("\"", "::"); return GetCurrentForInstaller().ToString().Replace("\"", "::");
} }
} }

View File

@@ -1,82 +0,0 @@
using System;
using System.Drawing;
using TweetDuck.Browser.Data;
using TweetLib.Core.Features.Plugins.Config;
using TweetLib.Core.Serialization.Converters;
using TweetLib.Core.Systems.Configuration;
using TweetLib.Core.Utils;
namespace TweetDuck.Configuration{
sealed class ConfigManager : IConfigManager{
public UserConfig User { get; }
public SystemConfig System { get; }
public PluginConfig Plugins { get; }
public event EventHandler ProgramRestartRequested;
private readonly FileConfigInstance<UserConfig> infoUser;
private readonly FileConfigInstance<SystemConfig> infoSystem;
private readonly PluginConfigInstance<PluginConfig> infoPlugins;
private readonly IConfigInstance<BaseConfig>[] infoList;
public ConfigManager(){
User = new UserConfig(this);
System = new SystemConfig(this);
Plugins = new PluginConfig(this);
infoList = new IConfigInstance<BaseConfig>[]{
infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options"),
infoPlugins = new PluginConfigInstance<PluginConfig>(Program.PluginConfigFilePath, Plugins)
};
// TODO refactor further
infoUser.Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
infoUser.Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point>{
ConvertToString = value => $"{value.X} {value.Y}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value, ' ');
return new Point(elements[0], elements[1]);
}
});
infoUser.Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{
ConvertToString = value => $"{value.Width} {value.Height}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value, ' ');
return new Size(elements[0], elements[1]);
}
});
}
public void LoadAll(){
infoUser.Load();
infoSystem.Load();
infoPlugins.Load();
}
public void SaveAll(){
infoUser.Save();
infoSystem.Save();
infoPlugins.Save();
}
public void ReloadAll(){
infoUser.Reload();
infoSystem.Reload();
infoPlugins.Reload();
}
void IConfigManager.TriggerProgramRestartRequested(){
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
}
IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance){
Type instanceType = instance.GetType();
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
}
}
}

View File

@@ -1,51 +0,0 @@
using System;
using System.Collections.Generic;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Config;
using TweetLib.Core.Features.Plugins.Events;
using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Configuration{
sealed class PluginConfig : BaseConfig, IPluginConfig{
private static readonly string[] DefaultDisabled = {
"official/clear-columns",
"official/reply-account"
};
// CONFIGURATION DATA
private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled);
// EVENTS
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
// END OF CONFIG
public PluginConfig(IConfigManager configManager) : base(configManager){}
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
return new PluginConfig(configManager);
}
// INTERFACE IMPLEMENTATION
IEnumerable<string> IPluginConfig.DisabledPlugins => disabled;
void IPluginConfig.Reset(IEnumerable<string> newDisabledPlugins){
disabled.Clear();
disabled.UnionWith(newDisabledPlugins);
}
public void SetEnabled(Plugin plugin, bool enabled){
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){
PluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
Save();
}
}
public bool IsEnabled(Plugin plugin){
return !disabled.Contains(plugin.Identifier);
}
}
}

View File

@@ -1,28 +1,69 @@
using TweetLib.Core.Systems.Configuration; using System.Diagnostics.CodeAnalysis;
using TweetLib.Core;
using TweetLib.Core.Application;
using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration {
sealed class SystemConfig : BaseConfig{ sealed class SystemConfig : BaseConfig<SystemConfig>, IAppSystemConfiguration {
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public int MigrationVersion { get; set; } = 0;
// CONFIGURATION DATA private bool _hardwareAcceleration = true;
private bool _enableTouchAdjustment = false;
public bool _hardwareAcceleration = true; private bool _enableColorProfileDetection = false;
private bool _useSystemProxyForAllConnections = false;
public bool ClearCacheAutomatically { get; set; } = true; public bool ClearCacheAutomatically { get; set; } = true;
public int ClearCacheThreshold { get; set; } = 250; public int ClearCacheThreshold { get; set; } = 250;
// SPECIAL PROPERTIES // SPECIAL PROPERTIES
public bool HardwareAcceleration{ public bool HardwareAcceleration {
get => _hardwareAcceleration; get => _hardwareAcceleration;
set => UpdatePropertyWithRestartRequest(ref _hardwareAcceleration, value); set => UpdatePropertyWithCallback(ref _hardwareAcceleration, value, App.ConfigManager.TriggerProgramRestartRequested);
}
public bool EnableTouchAdjustment {
get => _enableTouchAdjustment;
set => UpdatePropertyWithCallback(ref _enableTouchAdjustment, value, App.ConfigManager.TriggerProgramRestartRequested);
}
public bool EnableColorProfileDetection {
get => _enableColorProfileDetection;
set => UpdatePropertyWithCallback(ref _enableColorProfileDetection, value, App.ConfigManager.TriggerProgramRestartRequested);
}
public bool UseSystemProxyForAllConnections {
get => _useSystemProxyForAllConnections;
set => UpdatePropertyWithCallback(ref _useSystemProxyForAllConnections, value, App.ConfigManager.TriggerProgramRestartRequested);
} }
// END OF CONFIG // END OF CONFIG
public SystemConfig(IConfigManager configManager) : base(configManager){} #pragma warning disable CS0618
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){ public bool Migrate() {
return new SystemConfig(configManager); bool hasChanged = false;
if (MigrationVersion < 1) {
MigrationVersion = 1;
hasChanged = true;
var userConfig = Program.Config.User;
_enableTouchAdjustment = userConfig.EnableTouchAdjustment;
_enableColorProfileDetection = userConfig.EnableColorProfileDetection;
_useSystemProxyForAllConnections = userConfig.UseSystemProxyForAllConnections;
}
return hasChanged;
}
#pragma warning restore CS0618
public override SystemConfig ConstructWithDefaults() {
return new SystemConfig {
MigrationVersion = 1
};
} }
} }
} }

View File

@@ -1,18 +1,20 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing; using System.Drawing;
using TweetDuck.Browser; using TweetDuck.Browser;
using TweetDuck.Browser.Data;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetLib.Core;
using TweetLib.Core.Application;
using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Twitter; using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Systems.Configuration; using TweetLib.Core.Systems.Configuration;
using TweetLib.Utils.Data;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration {
sealed class UserConfig : BaseConfig{ sealed class UserConfig : BaseConfig<UserConfig>, IAppUserConfiguration {
// CONFIGURATION DATA
public bool FirstRun { get; set; } = true; public bool FirstRun { get; set; } = true;
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public bool AllowDataCollection { get; set; } = false; public bool AllowDataCollection { get; set; } = false;
public WindowState BrowserWindow { get; set; } = new WindowState(); public WindowState BrowserWindow { get; set; } = new WindowState();
@@ -24,9 +26,9 @@ namespace TweetDuck.Configuration{
public bool KeepLikeFollowDialogsOpen { get; set; } = true; public bool KeepLikeFollowDialogsOpen { get; set; } = true;
public bool BestImageQuality { get; set; } = true; public bool BestImageQuality { get; set; } = true;
public bool EnableAnimatedImages { get; set; } = true; public bool EnableAnimatedImages { get; set; } = true;
public bool HideTweetsByNftUsers { get; set; } = false;
private bool _enableSmoothScrolling = true; private bool _enableSmoothScrolling = true;
private bool _enableTouchAdjustment = false;
private string _customCefArgs = null; private string _customCefArgs = null;
public string BrowserPath { get; set; } = null; public string BrowserPath { get; set; } = null;
@@ -79,6 +81,7 @@ namespace TweetDuck.Configuration{
public string CustomBrowserCSS { get; set; } = null; public string CustomBrowserCSS { get; set; } = null;
public string CustomNotificationCSS { get; set; } = null; public string CustomNotificationCSS { get; set; } = null;
public bool DevToolsInContextMenu { get; set; } = false;
public bool DevToolsWindowOnTop { get; set; } = true; public bool DevToolsWindowOnTop { get; set; } = true;
// SPECIAL PROPERTIES // SPECIAL PROPERTIES
@@ -89,64 +92,76 @@ namespace TweetDuck.Configuration{
public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default; public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default;
public string NotificationSoundPath{ public string NotificationSoundPath {
get => _notificationSoundPath ?? string.Empty; get => _notificationSoundPath ?? string.Empty;
set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged); set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged);
} }
public int NotificationSoundVolume{ public int NotificationSoundVolume {
get => _notificationSoundVolume; get => _notificationSoundVolume;
set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged); set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged);
} }
public bool MuteNotifications{ public bool MuteNotifications {
get => _muteNotifications; get => _muteNotifications;
set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled); set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled);
} }
public int ZoomLevel{ public int ZoomLevel {
get => _zoomLevel; get => _zoomLevel;
set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged); set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged);
} }
public TrayIcon.Behavior TrayBehavior{ public TrayIcon.Behavior TrayBehavior {
get => _trayBehavior; get => _trayBehavior;
set => UpdatePropertyWithEvent(ref _trayBehavior, value, TrayBehaviorChanged); set => UpdatePropertyWithEvent(ref _trayBehavior, value, TrayBehaviorChanged);
} }
public bool EnableSmoothScrolling{ public bool EnableSmoothScrolling {
get => _enableSmoothScrolling; get => _enableSmoothScrolling;
set => UpdatePropertyWithRestartRequest(ref _enableSmoothScrolling, value); set => UpdatePropertyWithCallback(ref _enableSmoothScrolling, value, App.ConfigManager.TriggerProgramRestartRequested);
} }
public bool EnableTouchAdjustment{ public string CustomCefArgs {
get => _enableTouchAdjustment;
set => UpdatePropertyWithRestartRequest(ref _enableTouchAdjustment, value);
}
public string CustomCefArgs{
get => _customCefArgs; get => _customCefArgs;
set => UpdatePropertyWithRestartRequest(ref _customCefArgs, value); set => UpdatePropertyWithCallback(ref _customCefArgs, value, App.ConfigManager.TriggerProgramRestartRequested);
} }
public string SpellCheckLanguage{ public string SpellCheckLanguage {
get => _spellCheckLanguage; get => _spellCheckLanguage;
set => UpdatePropertyWithRestartRequest(ref _spellCheckLanguage, value); set => UpdatePropertyWithCallback(ref _spellCheckLanguage, value, App.ConfigManager.TriggerProgramRestartRequested);
} }
// DEPRECATED
[Obsolete("Moved to SystemConfig")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public bool EnableTouchAdjustment { get; set; }
[Obsolete("Moved to SystemConfig")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public bool EnableColorProfileDetection { get; set; }
[Obsolete("Moved to SystemConfig")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public bool UseSystemProxyForAllConnections { get; set; }
// EVENTS // EVENTS
public event EventHandler MuteToggled; public event EventHandler MuteToggled;
public event EventHandler ZoomLevelChanged; public event EventHandler ZoomLevelChanged;
public event EventHandler TrayBehaviorChanged; public event EventHandler TrayBehaviorChanged;
public event EventHandler SoundNotificationChanged; public event EventHandler SoundNotificationChanged;
public event EventHandler OptionsDialogClosed;
public void TriggerOptionsDialogClosed() {
OptionsDialogClosed?.Invoke(this, EventArgs.Empty);
}
// END OF CONFIG // END OF CONFIG
public UserConfig(IConfigManager configManager) : base(configManager){} public override UserConfig ConstructWithDefaults() {
return new UserConfig();
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
return new UserConfig(configManager);
} }
} }
} }

View File

@@ -2,78 +2,102 @@ using System;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetLib.Utils.Data;
namespace TweetDuck.Controls{ namespace TweetDuck.Controls {
static class ControlExtensions{ static class ControlExtensions {
public static readonly Point InvisibleLocation = new Point(-32000, -32000); public static readonly Point InvisibleLocation = new Point(-32000, -32000);
public static void InvokeSafe(this Control control, Action func){ public static void InvokeSafe(this Control control, Action func) {
if (control.InvokeRequired){ if (control.InvokeRequired) {
control.Invoke(func); control.Invoke(func);
} }
else{ else {
func(); func();
} }
} }
public static void InvokeAsyncSafe(this Control control, Action func){ public static void InvokeAsyncSafe(this Control control, Action func) {
if (control.InvokeRequired) {
control.BeginInvoke(func); control.BeginInvoke(func);
} }
else {
func();
}
}
public static float GetDPIScale(this Control control){ public static float GetDPIScale(this Control control) {
using Graphics graphics = control.CreateGraphics(); using Graphics graphics = control.CreateGraphics();
return graphics.DpiY / 96F; return graphics.DpiY / 96F;
} }
public static bool IsFullyOutsideView(this Form form){ public static bool IsFullyOutsideView(this Form form) {
return !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds)); return !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds));
} }
public static void MoveToCenter(this Form targetForm, Form parentForm){ public static void MoveToCenter(this Form targetForm, Form parentForm) {
targetForm.Location = new Point(parentForm.Location.X + (parentForm.Width / 2) - (targetForm.Width / 2), parentForm.Location.Y + (parentForm.Height / 2) - (targetForm.Height / 2)); targetForm.Location = new Point(parentForm.Location.X + (parentForm.Width / 2) - (targetForm.Width / 2), parentForm.Location.Y + (parentForm.Height / 2) - (targetForm.Height / 2));
} }
public static void SetValueInstant(this ProgressBar bar, int value){ public static void SetValueInstant(this ProgressBar bar, int value) {
if (value == bar.Maximum){ if (value == bar.Maximum) {
bar.Value = value; bar.Value = value;
bar.Value = value - 1; bar.Value = value - 1;
bar.Value = value; bar.Value = value;
} }
else{ else {
bar.Value = value + 1; bar.Value = value + 1;
bar.Value = value; bar.Value = value;
} }
} }
public static void SetValueSafe(this NumericUpDown numUpDown, int value){ public static void SetValueSafe(this NumericUpDown numUpDown, int value) {
if (value >= numUpDown.Minimum && value <= numUpDown.Maximum){ if (value >= numUpDown.Minimum && value <= numUpDown.Maximum) {
numUpDown.Value = value; numUpDown.Value = value;
} }
} }
public static void SetValueSafe(this TrackBar trackBar, int value){ public static void SetValueSafe(this TrackBar trackBar, int value) {
if (value >= trackBar.Minimum && value <= trackBar.Maximum){ if (value >= trackBar.Minimum && value <= trackBar.Maximum) {
trackBar.Value = value; trackBar.Value = value;
} }
} }
public static bool AlignValueToTick(this TrackBar trackBar){ public static bool AlignValueToTick(this TrackBar trackBar) {
if (trackBar.Value % trackBar.SmallChange != 0){ if (trackBar.Value % trackBar.SmallChange != 0) {
trackBar.Value = trackBar.SmallChange * (int)Math.Floor(((double)trackBar.Value / trackBar.SmallChange) + 0.5); trackBar.Value = trackBar.SmallChange * (int) Math.Floor(((double) trackBar.Value / trackBar.SmallChange) + 0.5);
return false; return false;
} }
return true; return true;
} }
public static void EnableMultilineShortcuts(this TextBox textBox){ public static void EnableMultilineShortcuts(this TextBox textBox) {
textBox.KeyDown += (sender, args) => { textBox.KeyDown += (sender, args) => {
if (args.Control && args.KeyCode == Keys.A){ if (args.Control && args.KeyCode == Keys.A) {
((TextBox)sender).SelectAll(); ((TextBox) sender).SelectAll();
args.SuppressKeyPress = true; args.SuppressKeyPress = true;
args.Handled = true; args.Handled = true;
} }
}; };
} }
public static void Save(this WindowState state, Form form) {
state.Bounds = form.WindowState == FormWindowState.Normal ? form.DesktopBounds : form.RestoreBounds;
state.IsMaximized = form.WindowState == FormWindowState.Maximized;
}
public static void Restore(this WindowState state, Form form, bool firstTimeFullscreen) {
if (state.Bounds != Rectangle.Empty) {
form.DesktopBounds = state.Bounds;
form.WindowState = state.IsMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
}
if ((state.Bounds == Rectangle.Empty && firstTimeFullscreen) || form.IsFullyOutsideView()) {
form.DesktopBounds = Screen.PrimaryScreen.WorkingArea;
form.WindowState = FormWindowState.Maximized;
state.Save(form);
}
}
} }
} }

View File

@@ -1,15 +1,15 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Controls{ namespace TweetDuck.Controls {
sealed class FlatButton : Button{ sealed class FlatButton : Button {
protected override bool ShowFocusCues => false; protected override bool ShowFocusCues => false;
public FlatButton(){ public FlatButton() {
GotFocus += FlatButton_GotFocus; GotFocus += FlatButton_GotFocus;
} }
private void FlatButton_GotFocus(object sender, EventArgs e){ // removes extra border when focused private void FlatButton_GotFocus(object sender, EventArgs e) { // removes extra border when focused
NotifyDefault(false); NotifyDefault(false);
} }
} }

View File

@@ -2,35 +2,35 @@
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Controls{ namespace TweetDuck.Controls {
sealed class FlatProgressBar : ProgressBar{ sealed class FlatProgressBar : ProgressBar {
private readonly SolidBrush brush; private readonly SolidBrush brush;
public FlatProgressBar(){ public FlatProgressBar() {
brush = new SolidBrush(Color.White); brush = new SolidBrush(Color.White);
SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true); SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
} }
public void SetValueInstant(int value){ public void SetValueInstant(int value) {
ControlExtensions.SetValueInstant(this, Math.Max(Minimum, Math.Min(Maximum, value))); ControlExtensions.SetValueInstant(this, Math.Max(Minimum, Math.Min(Maximum, value)));
} }
protected override void OnPaint(PaintEventArgs e){ protected override void OnPaint(PaintEventArgs e) {
if (brush.Color != ForeColor){ if (brush.Color != ForeColor) {
brush.Color = ForeColor; brush.Color = ForeColor;
} }
Rectangle rect = e.ClipRectangle; Rectangle rect = e.ClipRectangle;
rect.Width = (int)(rect.Width * ((double)Value / Maximum)); rect.Width = (int) (rect.Width * ((double) Value / Maximum));
e.Graphics.FillRectangle(brush, rect); e.Graphics.FillRectangle(brush, rect);
} }
protected override void Dispose(bool disposing){ protected override void Dispose(bool disposing) {
base.Dispose(disposing); base.Dispose(disposing);
if (disposing){ if (disposing) {
brush.Dispose(); brush.Dispose();
} }
} }

View File

@@ -1,10 +1,10 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Utils; using TweetDuck.Utils;
namespace TweetDuck.Controls{ namespace TweetDuck.Controls {
sealed class FlowLayoutPanelNoHScroll : FlowLayoutPanel{ sealed class FlowLayoutPanelNoHScroll : FlowLayoutPanel {
protected override void WndProc(ref Message m){ protected override void WndProc(ref Message m) {
if (m.Msg == 0x85){ // WM_NCPAINT if (m.Msg == 0x85) { // WM_NCPAINT
NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much
} }

View File

@@ -2,15 +2,15 @@
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Controls{ namespace TweetDuck.Controls {
sealed class LabelVertical : Label{ sealed class LabelVertical : Label {
public int LineHeight { get; set; } public int LineHeight { get; set; }
protected override void OnPaint(PaintEventArgs e){ protected override void OnPaint(PaintEventArgs e) {
int y = (int)Math.Floor((ClientRectangle.Height - Text.Length * LineHeight) / 2F) - 1; int y = (int) Math.Floor((ClientRectangle.Height - Text.Length * LineHeight) / 2F) - 1;
using Brush brush = new SolidBrush(ForeColor); using Brush brush = new SolidBrush(ForeColor);
foreach(char chr in Text){ foreach (char chr in Text) {
string str = chr.ToString(); string str = chr.ToString();
float x = (ClientRectangle.Width - e.Graphics.MeasureString(str, Font).Width) / 2F; float x = (ClientRectangle.Width - e.Graphics.MeasureString(str, Font).Width) / 2F;

View File

@@ -1,14 +1,14 @@
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Controls{ namespace TweetDuck.Controls {
sealed class NumericUpDownEx : NumericUpDown{ sealed class NumericUpDownEx : NumericUpDown {
public string TextSuffix { get; set ; } public string TextSuffix { get; set ; }
protected override void UpdateEditText(){ protected override void UpdateEditText() {
base.UpdateEditText(); base.UpdateEditText();
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime){ if (LicenseManager.UsageMode != LicenseUsageMode.Designtime) {
ChangingText = true; ChangingText = true;
Text += TextSuffix; Text += TextSuffix;
ChangingText = false; ChangingText = false;

View File

@@ -1,16 +1,16 @@
using System.ComponentModel; using System;
using System.ComponentModel;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Management; using TweetDuck.Management;
using TweetDuck.Utils; using TweetLib.Core;
namespace TweetDuck.Dialogs{ namespace TweetDuck.Dialogs {
sealed partial class FormAbout : Form, FormManager.IAppDialog{ sealed partial class FormAbout : Form, FormManager.IAppDialog {
private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki"; private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki";
private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues";
public FormAbout(){ public FormAbout() {
InitializeComponent(); InitializeComponent();
Text = "About " + Program.BrandName + " " + Program.VersionTag; Text = "About " + Program.BrandName + " " + Program.VersionTag;
@@ -19,27 +19,29 @@ namespace TweetDuck.Dialogs{
labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website)); labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink)); labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink)); labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, Lib.IssueTrackerUrl));
MemoryStream logoStream = new MemoryStream(Properties.Resources.avatar); try {
pictureLogo.Image = Image.FromStream(logoStream); pictureLogo.Image = Image.FromFile(Path.Combine(App.ResourcesPath, "images/logo.png"));
Disposed += (sender, args) => logoStream.Dispose(); } catch (Exception) {
// ignore
}
} }
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){ private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string); App.SystemHandler.OpenBrowser(e.Link.LinkData as string);
} }
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){ private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent) {
ShowGuide(); ShowGuide();
} }
private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e){ private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e) {
e.Cancel = true; e.Cancel = true;
ShowGuide(); ShowGuide();
} }
private void ShowGuide(){ private void ShowGuide() {
FormGuide.Show(); FormGuide.Show();
Close(); Close();
} }

View File

@@ -1,56 +1,31 @@
using System.Drawing; using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms; using CefSharp.WinForms;
using TweetDuck.Browser; using TweetDuck.Browser;
using TweetDuck.Browser.Adapters; using TweetDuck.Browser.Adapters;
using TweetDuck.Browser.Data;
using TweetDuck.Browser.Handling; using TweetDuck.Browser.Handling;
using TweetDuck.Browser.Handling.General;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Management; using TweetDuck.Management;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Browser.Interfaces;
using TweetLib.Core.Features;
namespace TweetDuck.Dialogs{ namespace TweetDuck.Dialogs {
sealed partial class FormGuide : Form, FormManager.IAppDialog{ sealed partial class FormGuide : Form, FormManager.IAppDialog {
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v2/"; private const string GuideUrl = "td://guide/index.html";
private const string GuidePathRegex = @"^guide(?:/v\d+)?(?:/(#.*))?";
private static readonly ResourceLink DummyPage = new ResourceLink("http://td/dummy", ResourceHandlers.ForString(string.Empty)); public static void Show(string hash = null) {
string url = GuideUrl + (string.IsNullOrEmpty(hash) ? string.Empty : "#" + hash);
public static bool CheckGuideUrl(string url, out string hash){
if (!url.Contains("//tweetduck.chylex.com/guide")){
hash = null;
return false;
}
string path = url.Substring(url.IndexOf("/guide")+1);
Match match = Regex.Match(path, GuidePathRegex);
if (match.Success){
hash = match.Groups[1].Value;
return true;
}
else{
hash = null;
return false;
}
}
public static void Show(string hash = null){
string url = GuideUrl + (hash ?? string.Empty);
FormGuide guide = FormManager.TryFind<FormGuide>(); FormGuide guide = FormManager.TryFind<FormGuide>();
if (guide == null){ if (guide == null) {
FormBrowser owner = FormManager.TryFind<FormBrowser>(); FormBrowser owner = FormManager.TryFind<FormBrowser>();
if (owner != null){ if (owner != null) {
owner.AnalyticsFile.OpenGuide.Trigger();
new FormGuide(url, owner).Show(owner); new FormGuide(url, owner).Show(owner);
} }
} }
else{ else {
guide.Reload(url); guide.Reload(url);
guide.Activate(); guide.Activate();
} }
@@ -60,73 +35,55 @@ namespace TweetDuck.Dialogs{
private readonly ChromiumWebBrowser browser; private readonly ChromiumWebBrowser browser;
#pragma warning restore IDE0069 // Disposable fields should be disposed #pragma warning restore IDE0069 // Disposable fields should be disposed
private string nextUrl; private FormGuide(string url, Form owner) {
private FormGuide(string url, FormBrowser owner){
InitializeComponent(); InitializeComponent();
Text = Program.BrandName + " Guide"; Text = Program.BrandName + " Guide";
Size = new Size(owner.Size.Width * 3 / 4, owner.Size.Height * 3 / 4); Size = new Size(owner.Size.Width * 3 / 4, owner.Size.Height * 3 / 4);
VisibleChanged += (sender, args) => this.MoveToCenter(owner); VisibleChanged += (sender, args) => this.MoveToCenter(owner);
var resourceRequestHandler = new ResourceRequestHandlerBase(); browser = new ChromiumWebBrowser(url) {
resourceRequestHandler.ResourceHandlers.Register(DummyPage); KeyboardHandler = new CustomKeyboardHandler(null),
RequestHandler = new RequestHandlerBase(true)
this.browser = new ChromiumWebBrowser(url){
MenuHandler = new ContextMenuGuide(owner),
JsDialogHandler = new JavaScriptDialogHandler(),
KeyboardHandler = new KeyboardHandlerBase(),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBase(true),
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
}; };
browser.LoadingStateChanged += browser_LoadingStateChanged; browser.BrowserSettings.BackgroundColor = (uint) BackColor.ToArgb();
browser.FrameLoadEnd += browser_FrameLoadEnd;
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb(); var browserComponent = new ComponentImpl(browser);
browser.Dock = DockStyle.None; var browserImpl = new BaseBrowser(browserComponent);
browser.Location = ControlExtensions.InvisibleLocation;
browser.SetupZoomEvents(); BrowserUtils.SetupDockOnLoad(browserComponent, browser);
Controls.Add(browser); Controls.Add(browser);
Disposed += (sender, args) => browser.Dispose();
Disposed += (sender, args) => {
browserImpl.Dispose();
browser.Dispose();
};
} }
protected override void Dispose(bool disposing){ private sealed class ComponentImpl : CefBrowserComponent {
if (disposing){ public ComponentImpl(ChromiumWebBrowser browser) : base(browser) {}
protected override ContextMenuBase SetupContextMenu(IContextMenuHandler handler) {
return new ContextMenuGuide(handler);
}
protected override CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler) {
return new CefResourceHandlerFactory(handler, null);
}
}
protected override void Dispose(bool disposing) {
if (disposing) {
components?.Dispose(); components?.Dispose();
} }
base.Dispose(disposing); base.Dispose(disposing);
} }
private void Reload(string url){ private void Reload(string url) {
nextUrl = url; browser.Load(url);
browser.LoadingStateChanged += browser_LoadingStateChanged;
browser.Dock = DockStyle.None;
browser.Location = ControlExtensions.InvisibleLocation;
browser.Load(DummyPage.Url);
}
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){
if (browser.Address == DummyPage.Url){
browser.Load(nextUrl);
}
else{
this.InvokeAsyncSafe(() => {
browser.Location = Point.Empty;
browser.Dock = DockStyle.Fill;
});
browser.LoadingStateChanged -= browser_LoadingStateChanged;
}
}
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
CefScriptExecutor.RunScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
} }
} }
} }

View File

@@ -4,16 +4,16 @@ using System.Windows.Forms;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Utils; using TweetDuck.Utils;
namespace TweetDuck.Dialogs{ namespace TweetDuck.Dialogs {
[Flags] [Flags]
enum ControlType{ enum ControlType {
None = 0, None = 0,
Accept = 1, // triggered by pressing enter when a non-button is focused Accept = 1, // triggered by pressing enter when a non-button is focused
Cancel = 2, // triggered by closing the dialog without pressing a button Cancel = 2, // triggered by closing the dialog without pressing a button
Focused = 4 // active control after the dialog is showed Focused = 4 // active control after the dialog is showed
} }
sealed partial class FormMessage : Form{ sealed partial class FormMessage : Form {
public const string OK = "OK"; public const string OK = "OK";
public const string Yes = "Yes"; public const string Yes = "Yes";
public const string No = "No"; public const string No = "No";
@@ -22,33 +22,29 @@ namespace TweetDuck.Dialogs{
public const string Ignore = "Ignore"; public const string Ignore = "Ignore";
public const string Exit = "Exit"; public const string Exit = "Exit";
public static bool Information(string caption, string text, string buttonAccept, string buttonCancel = null){ public static bool Information(string caption, string text, string buttonAccept, string buttonCancel = null) {
return Show(caption, text, MessageBoxIcon.Information, buttonAccept, buttonCancel); return Show(caption, text, MessageBoxIcon.Information, buttonAccept, buttonCancel);
} }
public static bool Warning(string caption, string text, string buttonAccept, string buttonCancel = null){ public static bool Warning(string caption, string text, string buttonAccept, string buttonCancel = null) {
return Show(caption, text, MessageBoxIcon.Warning, buttonAccept, buttonCancel); return Show(caption, text, MessageBoxIcon.Warning, buttonAccept, buttonCancel);
} }
public static bool Error(string caption, string text, string buttonAccept, string buttonCancel = null){ public static bool Error(string caption, string text, string buttonAccept, string buttonCancel = null) {
return Show(caption, text, MessageBoxIcon.Error, buttonAccept, buttonCancel); return Show(caption, text, MessageBoxIcon.Error, buttonAccept, buttonCancel);
} }
public static bool Question(string caption, string text, string buttonAccept, string buttonCancel = null){ public static bool Question(string caption, string text, string buttonAccept, string buttonCancel = null) {
return Show(caption, text, MessageBoxIcon.Question, buttonAccept, buttonCancel); return Show(caption, text, MessageBoxIcon.Question, buttonAccept, buttonCancel);
} }
public static bool Show(string caption, string text, MessageBoxIcon icon, string button){ public static bool Show(string caption, string text, MessageBoxIcon icon, string buttonAccept, string buttonCancel = null) {
return Show(caption, text, icon, button, null);
}
public static bool Show(string caption, string text, MessageBoxIcon icon, string buttonAccept, string buttonCancel){
using FormMessage message = new FormMessage(caption, text, icon); using FormMessage message = new FormMessage(caption, text, icon);
if (buttonCancel == null){ if (buttonCancel == null) {
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Cancel | ControlType.Focused); message.AddButton(buttonAccept, DialogResult.OK, ControlType.Cancel | ControlType.Focused);
} }
else{ else {
message.AddButton(buttonCancel, DialogResult.Cancel, ControlType.Cancel); message.AddButton(buttonCancel, DialogResult.Cancel, ControlType.Cancel);
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Accept | ControlType.Focused); message.AddButton(buttonAccept, DialogResult.OK, ControlType.Accept | ControlType.Focused);
} }
@@ -63,12 +59,12 @@ namespace TweetDuck.Dialogs{
public bool HasIcon => icon != null; public bool HasIcon => icon != null;
public int ActionPanelY => panelActions.Location.Y; public int ActionPanelY => panelActions.Location.Y;
private int ClientWidth{ private int ClientWidth {
get => ClientSize.Width; get => ClientSize.Width;
set => ClientSize = new Size(value, ClientSize.Height); set => ClientSize = new Size(value, ClientSize.Height);
} }
private int ButtonDistance{ private int ButtonDistance {
get => BrowserUtils.Scale(96, dpiScale); get => BrowserUtils.Scale(96, dpiScale);
} }
@@ -81,7 +77,7 @@ namespace TweetDuck.Dialogs{
private int prevLabelWidth, prevLabelHeight; private int prevLabelWidth, prevLabelHeight;
private bool wasLabelMultiline; private bool wasLabelMultiline;
public FormMessage(string caption, string text, MessageBoxIcon messageIcon){ public FormMessage(string caption, string text, MessageBoxIcon messageIcon) {
InitializeComponent(); InitializeComponent();
this.dpiScale = this.GetDPIScale(); this.dpiScale = this.GetDPIScale();
@@ -90,7 +86,7 @@ namespace TweetDuck.Dialogs{
this.prevLabelHeight = labelMessage.Height; this.prevLabelHeight = labelMessage.Height;
this.minFormWidth = BrowserUtils.Scale(42, dpiScale); this.minFormWidth = BrowserUtils.Scale(42, dpiScale);
switch(messageIcon){ switch (messageIcon) {
case MessageBoxIcon.Information: case MessageBoxIcon.Information:
icon = SystemIcons.Information; icon = SystemIcons.Information;
break; break;
@@ -119,16 +115,16 @@ namespace TweetDuck.Dialogs{
this.labelMessage.Text = text.Replace("\r", "").Replace("\n", Environment.NewLine); this.labelMessage.Text = text.Replace("\r", "").Replace("\n", Environment.NewLine);
} }
private void FormMessage_SizeChanged(object sender, EventArgs e){ private void FormMessage_SizeChanged(object sender, EventArgs e) {
RecalculateButtonLocation(); RecalculateButtonLocation();
} }
public Button AddButton(string title, ControlType type){ public Button AddButton(string title, ControlType type) {
return AddButton(title, DialogResult.OK, type); return AddButton(title, DialogResult.OK, type);
} }
public Button AddButton(string title, DialogResult result = DialogResult.OK, ControlType type = ControlType.None){ public Button AddButton(string title, DialogResult result = DialogResult.OK, ControlType type = ControlType.None) {
Button button = new Button{ Button button = new Button {
Anchor = AnchorStyles.Bottom, Anchor = AnchorStyles.Bottom,
Font = SystemFonts.MessageBoxFont, Font = SystemFonts.MessageBoxFont,
Location = new Point(0, 12), Location = new Point(0, 12),
@@ -139,7 +135,7 @@ namespace TweetDuck.Dialogs{
}; };
button.Click += (sender, args) => { button.Click += (sender, args) => {
ClickedButton = (Button)sender; ClickedButton = (Button) sender;
DialogResult = result; DialogResult = result;
Close(); Close();
}; };
@@ -151,22 +147,22 @@ namespace TweetDuck.Dialogs{
ClientWidth = Math.Max(realFormWidth, minFormWidth); ClientWidth = Math.Max(realFormWidth, minFormWidth);
RecalculateButtonLocation(); RecalculateButtonLocation();
if (type.HasFlag(ControlType.Accept)){ if (type.HasFlag(ControlType.Accept)) {
AcceptButton = button; AcceptButton = button;
} }
if (type.HasFlag(ControlType.Cancel)){ if (type.HasFlag(ControlType.Cancel)) {
CancelButton = button; CancelButton = button;
} }
if (type.HasFlag(ControlType.Focused)){ if (type.HasFlag(ControlType.Focused)) {
ActiveControl = button; ActiveControl = button;
} }
return button; return button;
} }
public void AddActionControl(Control control){ public void AddActionControl(Control control) {
panelActions.Controls.Add(control); panelActions.Controls.Add(control);
control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale)); control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale));
@@ -175,29 +171,29 @@ namespace TweetDuck.Dialogs{
ClientWidth = Math.Max(realFormWidth, minFormWidth); ClientWidth = Math.Max(realFormWidth, minFormWidth);
} }
private void RecalculateButtonLocation(){ private void RecalculateButtonLocation() {
int dist = ButtonDistance; int dist = ButtonDistance;
int start = ClientWidth - dist; int start = ClientWidth - dist;
for(int index = 0; index < buttonCount; index++){ for (int index = 0; index < buttonCount; index++) {
Control control = panelActions.Controls[index]; Control control = panelActions.Controls[index];
control.Location = new Point(start - index * dist, control.Location.Y); control.Location = new Point(start - index * dist, control.Location.Y);
} }
} }
private void labelMessage_SizeChanged(object sender, EventArgs e){ private void labelMessage_SizeChanged(object sender, EventArgs e) {
if (!isReady){ if (!isReady) {
return; return;
} }
bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height; bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height;
int labelOffset = BrowserUtils.Scale(8, dpiScale); int labelOffset = BrowserUtils.Scale(8, dpiScale);
if (isMultiline && !wasLabelMultiline){ if (isMultiline && !wasLabelMultiline) {
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y - labelOffset); labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y - labelOffset);
prevLabelHeight += labelOffset; prevLabelHeight += labelOffset;
} }
else if (!isMultiline && wasLabelMultiline){ else if (!isMultiline && wasLabelMultiline) {
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y + labelOffset); labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y + labelOffset);
prevLabelHeight -= labelOffset; prevLabelHeight -= labelOffset;
} }
@@ -211,8 +207,8 @@ namespace TweetDuck.Dialogs{
wasLabelMultiline = isMultiline; wasLabelMultiline = isMultiline;
} }
protected override void OnPaint(PaintEventArgs e){ protected override void OnPaint(PaintEventArgs e) {
if (icon != null){ if (icon != null) {
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1 + BrowserUtils.Scale(25, dpiScale)); e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1 + BrowserUtils.Scale(25, dpiScale));
} }

View File

@@ -7,23 +7,25 @@ using TweetDuck.Management;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetLib.Core; using TweetLib.Core;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums;
using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Dialogs{ namespace TweetDuck.Dialogs {
sealed partial class FormPlugins : Form, FormManager.IAppDialog{ sealed partial class FormPlugins : Form, FormManager.IAppDialog {
private static UserConfig Config => Program.Config.User; private static UserConfig Config => Program.Config.User;
private readonly PluginManager pluginManager; private readonly PluginManager pluginManager;
public FormPlugins(){ public FormPlugins() {
InitializeComponent(); InitializeComponent();
Text = Program.BrandName + " Plugins"; Text = Program.BrandName + " Plugins";
} }
public FormPlugins(PluginManager pluginManager) : this(){ public FormPlugins(PluginManager pluginManager) : this() {
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
if (!Config.PluginsWindowSize.IsEmpty){ if (!Config.PluginsWindowSize.IsEmpty) {
Size targetSize = Config.PluginsWindowSize; Size targetSize = Config.PluginsWindowSize;
Size = new Size(Math.Max(MinimumSize.Width, targetSize.Width), Math.Max(MinimumSize.Height, targetSize.Height)); Size = new Size(Math.Max(MinimumSize.Width, targetSize.Width), Math.Max(MinimumSize.Height, targetSize.Height));
} }
@@ -42,18 +44,18 @@ namespace TweetDuck.Dialogs{
}; };
} }
private int GetPluginOrderIndex(Plugin plugin){ private int GetPluginOrderIndex(Plugin plugin) {
return !plugin.CanRun ? 0 : pluginManager.Config.IsEnabled(plugin) ? 1 : 2; return !plugin.CanRun ? 0 : pluginManager.Config.IsEnabled(plugin) ? 1 : 2;
} }
private void ReloadPluginList(){ private void ReloadPluginList() {
flowLayoutPlugins.Controls.Clear(); flowLayoutPlugins.Controls.Clear();
flowLayoutPlugins.SuspendLayout(); flowLayoutPlugins.SuspendLayout();
foreach(Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)){ foreach (Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)) {
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin)); flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin));
flowLayoutPlugins.Controls.Add(new Panel{ flowLayoutPlugins.Controls.Add(new Panel {
BackColor = Color.DimGray, BackColor = Color.DimGray,
Margin = new Padding(0), Margin = new Padding(0),
Size = new Size(1, 1) Size = new Size(1, 1)
@@ -66,7 +68,7 @@ namespace TweetDuck.Dialogs{
timerLayout.Start(); timerLayout.Start();
} }
private void timerLayout_Tick(object sender, EventArgs e){ private void timerLayout_Tick(object sender, EventArgs e) {
timerLayout.Stop(); timerLayout.Stop();
// stupid WinForms scrollbars and panels // stupid WinForms scrollbars and panels
@@ -74,10 +76,10 @@ namespace TweetDuck.Dialogs{
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right - 1, Padding.Bottom - 1); Padding = new Padding(Padding.Left, Padding.Top, Padding.Right - 1, Padding.Bottom - 1);
} }
public void flowLayoutPlugins_Resize(object sender, EventArgs e){ private void flowLayoutPlugins_Resize(object sender, EventArgs e) {
Control lastPlugin = flowLayoutPlugins.Controls.OfType<PluginControl>().LastOrDefault(); Control lastPlugin = flowLayoutPlugins.Controls.OfType<PluginControl>().LastOrDefault();
if (lastPlugin == null){ if (lastPlugin == null) {
return; return;
} }
@@ -87,7 +89,7 @@ namespace TweetDuck.Dialogs{
flowLayoutPlugins.AutoScroll = showScrollBar; flowLayoutPlugins.AutoScroll = showScrollBar;
flowLayoutPlugins.VerticalScroll.Visible = showScrollBar; flowLayoutPlugins.VerticalScroll.Visible = showScrollBar;
foreach(Control control in flowLayoutPlugins.Controls){ foreach (Control control in flowLayoutPlugins.Controls) {
control.Width = flowLayoutPlugins.Width - control.Margin.Horizontal - horizontalOffset; control.Width = flowLayoutPlugins.Width - control.Margin.Horizontal - horizontalOffset;
} }
@@ -95,18 +97,18 @@ namespace TweetDuck.Dialogs{
flowLayoutPlugins.Focus(); flowLayoutPlugins.Focus();
} }
private void btnOpenFolder_Click(object sender, EventArgs e){ private void btnOpenFolder_Click(object sender, EventArgs e) {
App.SystemHandler.OpenFileExplorer(pluginManager.PathCustomPlugins); App.SystemHandler.OpenFileExplorer(pluginManager.GetPluginFolder(PluginGroup.Custom));
} }
private void btnReload_Click(object sender, EventArgs e){ private void btnReload_Click(object sender, EventArgs e) {
if (FormMessage.Warning("Reloading Plugins", "This will also reload the browser window. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){ if (FormMessage.Warning("Reloading Plugins", "This will also reload the browser window. Do you want to proceed?", FormMessage.Yes, FormMessage.No)) {
pluginManager.Reload(); pluginManager.Reload();
ReloadPluginList(); ReloadPluginList();
} }
} }
private void btnClose_Click(object sender, EventArgs e){ private void btnClose_Click(object sender, EventArgs e) {
Close(); Close();
} }
} }

View File

@@ -3,19 +3,19 @@ using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Browser; using TweetDuck.Browser;
using TweetDuck.Browser.Handling.General; using TweetDuck.Browser.Handling;
using TweetDuck.Browser.Notification.Example;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Dialogs.Settings; using TweetDuck.Dialogs.Settings;
using TweetDuck.Management; using TweetDuck.Management;
using TweetDuck.Management.Analytics;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.TweetDeck;
using TweetLib.Core.Systems.Updates; using TweetLib.Core.Systems.Updates;
namespace TweetDuck.Dialogs{ namespace TweetDuck.Dialogs {
sealed partial class FormSettings : Form, FormManager.IAppDialog{ sealed partial class FormSettings : Form, FormManager.IAppDialog {
public bool ShouldReloadBrowser { get; private set; } public bool ShouldReloadBrowser { get; private set; }
private readonly FormBrowser browser; private readonly FormBrowser browser;
@@ -26,7 +26,7 @@ namespace TweetDuck.Dialogs{
private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(8); private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(8);
private SettingsTab currentTab; private SettingsTab currentTab;
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, AnalyticsManager analytics, Type startTab){ public FormSettings(FormBrowser browser, PluginManager plugins, UpdateChecker updates, TweetDeckFunctions tweetDeckFunctions, Type startTab) {
InitializeComponent(); InitializeComponent();
Text = Program.BrandName + " Options"; Text = Program.BrandName + " Options";
@@ -40,38 +40,38 @@ namespace TweetDuck.Dialogs{
PrepareLoad(); PrepareLoad();
AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadColumns, updates)); AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadToTweetDeck, tweetDeckFunctions.ReloadColumns, updates));
AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins))); AddButton("Notifications", () => new TabSettingsNotifications(this.browser.CreateExampleNotification()));
AddButton("Sounds", () => new TabSettingsSounds(this.browser.PlaySoundNotification)); AddButton("Sounds", () => new TabSettingsSounds(() => tweetDeckFunctions.PlaySoundNotification(true)));
AddButton("Tray", () => new TabSettingsTray()); AddButton("Tray", () => new TabSettingsTray());
AddButton("Feedback", () => new TabSettingsFeedback(analytics, AnalyticsReportGenerator.ExternalInfo.From(this.browser), this.plugins)); AddButton("Feedback", () => new TabSettingsFeedback());
AddButton("Advanced", () => new TabSettingsAdvanced(this.browser.ReinjectCustomCSS, this.browser.OpenDevTools)); AddButton("Advanced", () => new TabSettingsAdvanced(tweetDeckFunctions.ReinjectCustomCSS, this.browser.OpenDevTools));
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]); SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
} }
private void PrepareLoad(){ private void PrepareLoad() {
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested; App.ConfigManager.ProgramRestartRequested += Config_ProgramRestartRequested;
} }
private void PrepareUnload(){ // TODO refactor this further later private void PrepareUnload() { // TODO refactor this further later
currentTab.Control.OnClosing(); currentTab.Control.OnClosing();
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested; App.ConfigManager.ProgramRestartRequested -= Config_ProgramRestartRequested;
Program.Config.SaveAll(); App.ConfigManager.SaveAll();
} }
private void Config_ProgramRestartRequested(object sender, EventArgs e){ private void Config_ProgramRestartRequested(object sender, EventArgs e) {
if (FormMessage.Information("TweetDuck Options", "The application must restart for the option to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){ if (FormMessage.Information("TweetDuck Options", "The application must restart for the option to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)) {
Program.Restart(); Program.Restart();
} }
} }
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){ private void FormSettings_FormClosing(object sender, FormClosingEventArgs e) {
PrepareUnload(); PrepareUnload();
foreach(SettingsTab tab in tabs.Values){ foreach (SettingsTab tab in tabs.Values) {
if (tab.IsInitialized){ if (tab.IsInitialized) {
tab.Control.Dispose(); tab.Control.Dispose();
} }
} }
@@ -79,17 +79,17 @@ namespace TweetDuck.Dialogs{
browser.ResumeNotification(); browser.ResumeNotification();
} }
private void btnManageOptions_Click(object sender, EventArgs e){ private void btnManageOptions_Click(object sender, EventArgs e) {
PrepareUnload(); PrepareUnload();
using DialogSettingsManage dialog = new DialogSettingsManage(plugins); using DialogSettingsManage dialog = new DialogSettingsManage(plugins);
FormClosing -= FormSettings_FormClosing; FormClosing -= FormSettings_FormClosing;
if (dialog.ShowDialog() == DialogResult.OK){ if (dialog.ShowDialog() == DialogResult.OK) {
if (!dialog.IsRestarting){ if (!dialog.IsRestarting) {
browser.ResumeNotification(); browser.ResumeNotification();
if (dialog.ShouldReloadBrowser){ if (dialog.ShouldReloadBrowser) {
BrowserProcessHandler.UpdatePrefs(); BrowserProcessHandler.UpdatePrefs();
ShouldReloadBrowser = true; ShouldReloadBrowser = true;
} }
@@ -97,18 +97,18 @@ namespace TweetDuck.Dialogs{
Close(); Close();
} }
else{ else {
FormClosing += FormSettings_FormClosing; FormClosing += FormSettings_FormClosing;
PrepareLoad(); PrepareLoad();
} }
} }
private void btnClose_Click(object sender, EventArgs e){ private void btnClose_Click(object sender, EventArgs e) {
Close(); Close();
} }
private void AddButton<T>(string title, Func<T> constructor) where T : BaseTab{ private void AddButton<T>(string title, Func<T> constructor) where T : BaseTab {
FlatButton btn = new FlatButton{ FlatButton btn = new FlatButton {
BackColor = SystemColors.Control, BackColor = SystemColors.Control,
FlatStyle = FlatStyle.Flat, FlatStyle = FlatStyle.Flat,
Font = SystemFonts.MessageBoxFont, Font = SystemFonts.MessageBoxFont,
@@ -125,7 +125,7 @@ namespace TweetDuck.Dialogs{
panelButtons.Controls.Add(btn); panelButtons.Controls.Add(btn);
panelButtons.Controls.Add(new Panel{ panelButtons.Controls.Add(new Panel {
BackColor = Color.DimGray, BackColor = Color.DimGray,
Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count - 1].Location.Y + buttonHeight), Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count - 1].Location.Y + buttonHeight),
Margin = new Padding(0), Margin = new Padding(0),
@@ -137,29 +137,29 @@ namespace TweetDuck.Dialogs{
btn.Click += (sender, args) => SelectTab<T>(); btn.Click += (sender, args) => SelectTab<T>();
} }
private void SelectTab<T>() where T : BaseTab{ private void SelectTab<T>() where T : BaseTab {
SelectTab(tabs[typeof(T)]); SelectTab(tabs[typeof(T)]);
} }
private void SelectTab(SettingsTab tab){ private void SelectTab(SettingsTab tab) {
if (currentTab != null){ if (currentTab != null) {
currentTab.Button.BackColor = SystemColors.Control; currentTab.Button.BackColor = SystemColors.Control;
currentTab.Control.OnClosing(); currentTab.Control.OnClosing();
} }
tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor; tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor;
if (!tab.IsInitialized){ if (!tab.IsInitialized) {
foreach(Control control in tab.Control.InteractiveControls){ foreach (Control control in tab.Control.InteractiveControls) {
if (control is ComboBox){ if (control is ComboBox) {
control.MouseLeave += control_MouseLeave; control.MouseLeave += control_MouseLeave;
} }
else if (control is TrackBar){ else if (control is TrackBar) {
control.MouseWheel += control_MouseWheel; control.MouseWheel += control_MouseWheel;
} }
} }
if (tab.Control.Height < panelContents.Height - 2){ if (tab.Control.Height < panelContents.Height - 2) {
tab.Control.Height = panelContents.Height - 2; // fixes off-by-pixel error on high DPI tab.Control.Height = panelContents.Height - 2; // fixes off-by-pixel error on high DPI
} }
@@ -181,20 +181,20 @@ namespace TweetDuck.Dialogs{
currentTab = tab; currentTab = tab;
} }
private void control_MouseLeave(object sender, EventArgs e){ private void control_MouseLeave(object sender, EventArgs e) {
if (sender is ComboBox cb && cb.DroppedDown){ if (sender is ComboBox { DroppedDown: true } ) {
return; // prevents comboboxes from closing when MouseLeave event triggers during opening animation return; // prevents comboboxes from closing when MouseLeave event triggers during opening animation
} }
panelContents.Focus(); panelContents.Focus();
} }
private void control_MouseWheel(object sender, MouseEventArgs e){ private void control_MouseWheel(object sender, MouseEventArgs e) {
((HandledMouseEventArgs)e).Handled = true; ((HandledMouseEventArgs) e).Handled = true;
panelContents.Focus(); panelContents.Focus();
} }
private sealed class SettingsTab{ private sealed class SettingsTab {
public Button Button { get; } public Button Button { get; }
public BaseTab Control => control ??= constructor(); public BaseTab Control => control ??= constructor();
@@ -203,26 +203,26 @@ namespace TweetDuck.Dialogs{
private readonly Func<BaseTab> constructor; private readonly Func<BaseTab> constructor;
private BaseTab control; private BaseTab control;
public SettingsTab(Button button, Func<BaseTab> constructor){ public SettingsTab(Button button, Func<BaseTab> constructor) {
this.Button = button; this.Button = button;
this.constructor = constructor; this.constructor = constructor;
} }
} }
internal abstract class BaseTab : UserControl{ internal class BaseTab : UserControl {
protected static UserConfig Config => Program.Config.User; protected static UserConfig Config => Program.Config.User;
protected static SystemConfig SysConfig => Program.Config.System; protected static SystemConfig SysConfig => Program.Config.System;
public IEnumerable<Control> InteractiveControls{ public IEnumerable<Control> InteractiveControls {
get{ get {
static IEnumerable<Control> FindInteractiveControls(Control parent){ static IEnumerable<Control> FindInteractiveControls(Control parent) {
foreach(Control control in parent.Controls){ foreach (Control control in parent.Controls) {
if (control is Panel subPanel){ if (control is Panel subPanel) {
foreach(Control subControl in FindInteractiveControls(subPanel)){ foreach (Control subControl in FindInteractiveControls(subPanel)) {
yield return subControl; yield return subControl;
} }
} }
else{ else {
yield return control; yield return control;
} }
} }
@@ -232,12 +232,12 @@ namespace TweetDuck.Dialogs{
} }
} }
protected BaseTab(){ protected BaseTab() {
Padding = new Padding(6); Padding = new Padding(6);
} }
public virtual void OnReady(){} public virtual void OnReady() {}
public virtual void OnClosing(){} public virtual void OnClosing() {}
} }
} }
} }

View File

@@ -1,94 +0,0 @@
namespace TweetDuck.Dialogs.Settings {
partial class DialogSettingsAnalytics {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.textBoxReport = new System.Windows.Forms.TextBox();
this.btnClose = new System.Windows.Forms.Button();
this.labelInfo = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// textBoxReport
//
this.textBoxReport.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBoxReport.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular);
this.textBoxReport.Location = new System.Drawing.Point(12, 45);
this.textBoxReport.Multiline = true;
this.textBoxReport.Name = "textBoxReport";
this.textBoxReport.ReadOnly = true;
this.textBoxReport.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxReport.Size = new System.Drawing.Size(435, 474);
this.textBoxReport.TabIndex = 1;
//
// btnClose
//
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnClose.AutoSize = true;
this.btnClose.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
this.btnClose.Location = new System.Drawing.Point(397, 525);
this.btnClose.Name = "btnClose";
this.btnClose.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnClose.Size = new System.Drawing.Size(50, 25);
this.btnClose.TabIndex = 2;
this.btnClose.Text = "Close";
this.btnClose.UseVisualStyleBackColor = true;
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
//
// labelInfo
//
this.labelInfo.AutoSize = true;
this.labelInfo.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
this.labelInfo.Location = new System.Drawing.Point(12, 9);
this.labelInfo.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
this.labelInfo.Name = "labelInfo";
this.labelInfo.Size = new System.Drawing.Size(434, 30);
this.labelInfo.TabIndex = 0;
this.labelInfo.Text = "When enabled, this data will be sent over a secure network roughly every 14 days." +
"\r\nSome numbers in the report were made imprecise on purpose.";
//
// DialogSettingsAnalytics
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(459, 562);
this.Controls.Add(this.labelInfo);
this.Controls.Add(this.btnClose);
this.Controls.Add(this.textBoxReport);
this.MinimumSize = new System.Drawing.Size(475, 340);
this.Name = "DialogSettingsAnalytics";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox textBoxReport;
private System.Windows.Forms.Button btnClose;
private System.Windows.Forms.Label labelInfo;
}
}

View File

@@ -1,22 +0,0 @@
using System;
using System.Windows.Forms;
using TweetDuck.Controls;
using TweetDuck.Management.Analytics;
namespace TweetDuck.Dialogs.Settings{
sealed partial class DialogSettingsAnalytics : Form{
public DialogSettingsAnalytics(AnalyticsReport report){
InitializeComponent();
Text = Program.BrandName + " Options - Analytics Report";
textBoxReport.EnableMultilineShortcuts();
textBoxReport.Text = report.ToString().TrimEnd();
textBoxReport.Select(0, 0);
}
private void btnClose_Click(object sender, EventArgs e){
Close();
}
}
}

View File

@@ -3,17 +3,16 @@ using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Utils;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsCSS : Form{ sealed partial class DialogSettingsCSS : Form {
public string BrowserCSS => textBoxBrowserCSS.Text; public string BrowserCSS => textBoxBrowserCSS.Text;
public string NotificationCSS => textBoxNotificationCSS.Text; public string NotificationCSS => textBoxNotificationCSS.Text;
private readonly Action<string> reinjectBrowserCSS; private readonly Action<string> reinjectBrowserCSS;
private readonly Action openDevTools; private readonly Action openDevTools;
public DialogSettingsCSS(string browserCSS, string notificationCSS, Action<string> reinjectBrowserCSS, Action openDevTools){ public DialogSettingsCSS(string browserCSS, string notificationCSS, Action<string> reinjectBrowserCSS, Action openDevTools) {
InitializeComponent(); InitializeComponent();
Text = Program.BrandName + " Options - CSS"; Text = Program.BrandName + " Options - CSS";
@@ -27,44 +26,40 @@ namespace TweetDuck.Dialogs.Settings{
textBoxNotificationCSS.EnableMultilineShortcuts(); textBoxNotificationCSS.EnableMultilineShortcuts();
textBoxNotificationCSS.Text = notificationCSS ?? ""; textBoxNotificationCSS.Text = notificationCSS ?? "";
if (!BrowserUtils.HasDevTools){
btnOpenDevTools.Enabled = false;
}
ActiveControl = textBoxBrowserCSS; ActiveControl = textBoxBrowserCSS;
textBoxBrowserCSS.Select(textBoxBrowserCSS.TextLength, 0); textBoxBrowserCSS.Select(textBoxBrowserCSS.TextLength, 0);
} }
private void tabPanel_SelectedIndexChanged(object sender, EventArgs e){ private void tabPanel_SelectedIndexChanged(object sender, EventArgs e) {
TextBox tb = tabPanel.SelectedTab.Controls.OfType<TextBox>().FirstOrDefault(); TextBox tb = tabPanel.SelectedTab.Controls.OfType<TextBox>().FirstOrDefault();
if (tb != null){ if (tb != null) {
tb.Focus(); tb.Focus();
tb.Select(tb.TextLength, 0); tb.Select(tb.TextLength, 0);
} }
} }
private void textBoxCSS_KeyDown(object sender, KeyEventArgs e){ private void textBoxCSS_KeyDown(object sender, KeyEventArgs e) {
TextBox tb = (TextBox)sender; TextBox tb = (TextBox) sender;
string text = tb.Text; string text = tb.Text;
if (e.KeyCode == Keys.Back && e.Modifiers == Keys.Control){ if (e.KeyCode == Keys.Back && e.Modifiers == Keys.Control) {
e.SuppressKeyPress = true; e.SuppressKeyPress = true;
int deleteTo = tb.SelectionStart; int deleteTo = tb.SelectionStart;
if (deleteTo > 0){ if (deleteTo > 0) {
char initialChar = text[--deleteTo]; char initialChar = text[--deleteTo];
bool shouldDeleteAlphanumeric = char.IsLetterOrDigit(initialChar); bool shouldDeleteAlphanumeric = char.IsLetterOrDigit(initialChar);
while(--deleteTo >= 0){ while (--deleteTo >= 0) {
if ((shouldDeleteAlphanumeric && !char.IsLetterOrDigit(text[deleteTo])) || if ((shouldDeleteAlphanumeric && !char.IsLetterOrDigit(text[deleteTo])) ||
(!shouldDeleteAlphanumeric && text[deleteTo] != initialChar)){ (!shouldDeleteAlphanumeric && text[deleteTo] != initialChar)) {
break; break;
} }
} }
if (!(deleteTo < text.Length - 1 && text[deleteTo] == '\r' && text[deleteTo + 1] == '\n')){ if (!(deleteTo < text.Length - 1 && text[deleteTo] == '\r' && text[deleteTo + 1] == '\n')) {
++deleteTo; ++deleteTo;
} }
@@ -72,42 +67,42 @@ namespace TweetDuck.Dialogs.Settings{
tb.SelectedText = string.Empty; tb.SelectedText = string.Empty;
} }
} }
else if (e.KeyCode == Keys.Back && e.Modifiers == Keys.None){ else if (e.KeyCode == Keys.Back && e.Modifiers == Keys.None) {
int deleteTo = tb.SelectionStart; int deleteTo = tb.SelectionStart;
if (deleteTo > 1 && text[deleteTo - 1] == ' ' && text[deleteTo - 2] == ' '){ if (deleteTo > 1 && text[deleteTo - 1] == ' ' && text[deleteTo - 2] == ' ') {
e.SuppressKeyPress = true; e.SuppressKeyPress = true;
tb.Select(deleteTo - 2, 2); tb.Select(deleteTo - 2, 2);
tb.SelectedText = string.Empty; tb.SelectedText = string.Empty;
} }
} }
else if (e.KeyCode == Keys.Enter && e.Modifiers == Keys.None && tb.SelectionLength == 0){ else if (e.KeyCode == Keys.Enter && e.Modifiers == Keys.None && tb.SelectionLength == 0) {
int insertAt = tb.SelectionStart, cursorOffset = 0; int insertAt = tb.SelectionStart, cursorOffset = 0;
string insertText; string insertText;
if (insertAt == 0){ if (insertAt == 0) {
return; return;
} }
else if (text[insertAt - 1] == '{'){ else if (text[insertAt - 1] == '{') {
insertText = Environment.NewLine + " "; insertText = Environment.NewLine + " ";
int nextBracket = insertAt < text.Length ? text.IndexOfAny(new char[]{ '{', '}' }, insertAt + 1) : -1; int nextBracket = insertAt < text.Length ? text.IndexOfAny(new char[] { '{', '}' }, insertAt + 1) : -1;
if (nextBracket == -1 || text[nextBracket] == '{'){ if (nextBracket == -1 || text[nextBracket] == '{') {
string insertExtra = Environment.NewLine + "}"; string insertExtra = Environment.NewLine + "}";
insertText += insertExtra; insertText += insertExtra;
cursorOffset -= insertExtra.Length; cursorOffset -= insertExtra.Length;
} }
} }
else{ else {
int lineStart = text.LastIndexOf('\n', tb.SelectionStart - 1); int lineStart = text.LastIndexOf('\n', tb.SelectionStart - 1);
Match match = Regex.Match(text.Substring(lineStart == -1 ? 0 : lineStart + 1), "^([ \t]+)"); Match match = Regex.Match(text.Substring(lineStart == -1 ? 0 : lineStart + 1), "^([ \t]+)");
insertText = match.Success ? Environment.NewLine + match.Groups[1].Value : null; insertText = match.Success ? Environment.NewLine + match.Groups[1].Value : null;
} }
if (!string.IsNullOrEmpty(insertText)){ if (!string.IsNullOrEmpty(insertText)) {
e.SuppressKeyPress = true; e.SuppressKeyPress = true;
tb.Text = text.Insert(insertAt, insertText); tb.Text = text.Insert(insertAt, insertText);
tb.SelectionStart = insertAt + cursorOffset + insertText.Length; tb.SelectionStart = insertAt + cursorOffset + insertText.Length;
@@ -115,26 +110,26 @@ namespace TweetDuck.Dialogs.Settings{
} }
} }
private void textBoxBrowserCSS_KeyUp(object sender, KeyEventArgs e){ private void textBoxBrowserCSS_KeyUp(object sender, KeyEventArgs e) {
timerTestBrowser.Stop(); timerTestBrowser.Stop();
timerTestBrowser.Start(); timerTestBrowser.Start();
} }
private void timerTestBrowser_Tick(object sender, EventArgs e){ private void timerTestBrowser_Tick(object sender, EventArgs e) {
reinjectBrowserCSS(textBoxBrowserCSS.Text); reinjectBrowserCSS(textBoxBrowserCSS.Text);
timerTestBrowser.Stop(); timerTestBrowser.Stop();
} }
private void btnOpenDevTools_Click(object sender, EventArgs e){ private void btnOpenDevTools_Click(object sender, EventArgs e) {
openDevTools(); openDevTools();
} }
private void btnApply_Click(object sender, EventArgs e){ private void btnApply_Click(object sender, EventArgs e) {
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();
} }
private void btnCancel_Click(object sender, EventArgs e){ private void btnCancel_Click(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel; DialogResult = DialogResult.Cancel;
Close(); Close();
} }

View File

@@ -1,16 +1,16 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Controls; using TweetDuck.Controls;
using TweetDuck.Utils; using TweetLib.Core;
using TweetLib.Core.Collections; using TweetLib.Core.Features.Chromium;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsCefArgs : Form{ sealed partial class DialogSettingsCefArgs : Form {
public string CefArgs => textBoxArgs.Text; public string CefArgs => textBoxArgs.Text;
private readonly string initialArgs; private readonly string initialArgs;
public DialogSettingsCefArgs(string args){ public DialogSettingsCefArgs(string args) {
InitializeComponent(); InitializeComponent();
Text = Program.BrandName + " Options - CEF Arguments"; Text = Program.BrandName + " Options - CEF Arguments";
@@ -20,27 +20,27 @@ namespace TweetDuck.Dialogs.Settings{
textBoxArgs.Select(textBoxArgs.Text.Length, 0); textBoxArgs.Select(textBoxArgs.Text.Length, 0);
} }
private void btnHelp_Click(object sender, EventArgs e){ private void btnHelp_Click(object sender, EventArgs e) {
BrowserUtils.OpenExternalBrowser("http://peter.sh/experiments/chromium-command-line-switches/"); App.SystemHandler.OpenBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
} }
private void btnApply_Click(object sender, EventArgs e){ private void btnApply_Click(object sender, EventArgs e) {
if (CefArgs == initialArgs){ if (CefArgs == initialArgs) {
DialogResult = DialogResult.Cancel; DialogResult = DialogResult.Cancel;
Close(); Close();
return; return;
} }
int count = CommandLineArgs.ReadCefArguments(CefArgs).Count; int count = CefUtils.ParseCommandLineArguments(CefArgs).Count;
string prompt = count == 0 && !string.IsNullOrWhiteSpace(initialArgs) ? "All current arguments will be removed. Continue?" : count + (count == 1 ? " argument was" : " arguments were") + " detected. Continue?"; string prompt = count == 0 && !string.IsNullOrWhiteSpace(initialArgs) ? "All current arguments will be removed. Continue?" : count + (count == 1 ? " argument was" : " arguments were") + " detected. Continue?";
if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){ if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)) {
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();
} }
} }
private void btnCancel_Click(object sender, EventArgs e){ private void btnCancel_Click(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel; DialogResult = DialogResult.Cancel;
Close(); Close();
} }

View File

@@ -1,23 +1,23 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetLib.Core.Utils; using TweetLib.Utils.Static;
using IOPath = System.IO.Path; using IOPath = System.IO.Path;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsExternalProgram : Form{ sealed partial class DialogSettingsExternalProgram : Form {
public string Path{ public string Path {
get => StringUtils.NullIfEmpty(textBoxPath.Text); get => StringUtils.NullIfEmpty(textBoxPath.Text);
set => textBoxPath.Text = value ?? string.Empty; set => textBoxPath.Text = value ?? string.Empty;
} }
public string Args{ public string Args {
get => StringUtils.NullIfEmpty(textBoxArgs.Text); get => StringUtils.NullIfEmpty(textBoxArgs.Text);
set => textBoxArgs.Text = value ?? string.Empty; set => textBoxArgs.Text = value ?? string.Empty;
} }
private readonly string fileDialogTitle; private readonly string fileDialogTitle;
public DialogSettingsExternalProgram(string windowTitle, string fileDialogTitle){ public DialogSettingsExternalProgram(string windowTitle, string fileDialogTitle) {
InitializeComponent(); InitializeComponent();
Text = Program.BrandName + " Options - " + windowTitle; Text = Program.BrandName + " Options - " + windowTitle;
@@ -27,8 +27,8 @@ namespace TweetDuck.Dialogs.Settings{
this.fileDialogTitle = fileDialogTitle; this.fileDialogTitle = fileDialogTitle;
} }
private void btnBrowse_Click(object sender, EventArgs e){ private void btnBrowse_Click(object sender, EventArgs e) {
using OpenFileDialog dialog = new OpenFileDialog{ using OpenFileDialog dialog = new OpenFileDialog {
AutoUpgradeEnabled = true, AutoUpgradeEnabled = true,
DereferenceLinks = true, DereferenceLinks = true,
InitialDirectory = IOPath.GetDirectoryName(Path), // returns null if argument is null InitialDirectory = IOPath.GetDirectoryName(Path), // returns null if argument is null
@@ -36,18 +36,18 @@ namespace TweetDuck.Dialogs.Settings{
Filter = "Executables (*.exe;*.bat;*.cmd)|*.exe;*.bat;*.cmd|All Files (*.*)|*.*" Filter = "Executables (*.exe;*.bat;*.cmd)|*.exe;*.bat;*.cmd|All Files (*.*)|*.*"
}; };
if (dialog.ShowDialog() == DialogResult.OK && Path != dialog.FileName){ if (dialog.ShowDialog() == DialogResult.OK && Path != dialog.FileName) {
Path = dialog.FileName; Path = dialog.FileName;
Args = string.Empty; Args = string.Empty;
} }
} }
private void btnApply_Click(object sender, EventArgs e){ private void btnApply_Click(object sender, EventArgs e) {
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();
} }
private void btnCancel_Click(object sender, EventArgs e){ private void btnCancel_Click(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel; DialogResult = DialogResult.Cancel;
Close(); Close();
} }

View File

@@ -121,7 +121,7 @@
this.cbSystemConfig.Size = new System.Drawing.Size(109, 19); this.cbSystemConfig.Size = new System.Drawing.Size(109, 19);
this.cbSystemConfig.TabIndex = 1; this.cbSystemConfig.TabIndex = 1;
this.cbSystemConfig.Text = "System Options"; this.cbSystemConfig.Text = "System Options";
this.toolTip.SetToolTip(this.cbSystemConfig, "Hardware acceleration and cache options."); this.toolTip.SetToolTip(this.cbSystemConfig, "Options related to the current system and hardware,\r\nsuch as hardware acceleration, proxy, and cache settings.");
this.cbSystemConfig.UseVisualStyleBackColor = true; this.cbSystemConfig.UseVisualStyleBackColor = true;
this.cbSystemConfig.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged); this.cbSystemConfig.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged);
// //

View File

@@ -2,32 +2,31 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Management; using TweetDuck.Management;
using TweetLib.Core;
using TweetLib.Core.Features.Plugins; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Utils; using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsManage : Form{ sealed partial class DialogSettingsManage : Form {
private enum State{ private enum State {
Deciding, Reset, Import, Export Deciding,
Reset,
Import,
Export
} }
private ProfileManager.Items SelectedItems{ private ProfileManager.Items SelectedItems {
get => _selectedItems; get => _selectedItems;
set{ set {
// this will call events and SetFlag, which also updates the UI // this will call events and SetFlag, which also updates the UI
foreach(KeyValuePair<CheckBox, ProfileManager.Items> kvp in checkBoxMap){ foreach (KeyValuePair<CheckBox, ProfileManager.Items> kvp in checkBoxMap) {
kvp.Key.Checked = value.HasFlag(kvp.Value); kvp.Key.Checked = value.HasFlag(kvp.Value);
} }
} }
} }
private bool SelectedItemsForceRestart{
get => _selectedItems.HasFlag(ProfileManager.Items.Session);
}
public bool IsRestarting { get; private set; } public bool IsRestarting { get; private set; }
public bool ShouldReloadBrowser { get; private set; } public bool ShouldReloadBrowser { get; private set; }
@@ -41,7 +40,7 @@ namespace TweetDuck.Dialogs.Settings{
private ProfileManager.Items _selectedItems = ProfileManager.Items.None; private ProfileManager.Items _selectedItems = ProfileManager.Items.None;
public DialogSettingsManage(PluginManager plugins, bool openImportImmediately = false){ public DialogSettingsManage(PluginManager plugins, bool openImportImmediately = false) {
InitializeComponent(); InitializeComponent();
this.plugins = plugins; this.plugins = plugins;
@@ -54,28 +53,28 @@ namespace TweetDuck.Dialogs.Settings{
this.openImportImmediately = openImportImmediately; this.openImportImmediately = openImportImmediately;
if (openImportImmediately){ if (openImportImmediately) {
radioImport.Checked = true; radioImport.Checked = true;
btnContinue_Click(null, EventArgs.Empty); btnContinue_Click(null, EventArgs.Empty);
} }
} }
private void radioDecision_CheckedChanged(object sender, EventArgs e){ private void radioDecision_CheckedChanged(object sender, EventArgs e) {
btnContinue.Enabled = true; btnContinue.Enabled = true;
} }
private void checkBoxSelection_CheckedChanged(object sender, EventArgs e){ private void checkBoxSelection_CheckedChanged(object sender, EventArgs e) {
CheckBox cb = (CheckBox)sender; CheckBox cb = (CheckBox) sender;
SetFlag(checkBoxMap[cb], cb.Checked); SetFlag(checkBoxMap[cb], cb.Checked);
} }
private void btnContinue_Click(object sender, EventArgs e){ private void btnContinue_Click(object sender, EventArgs e) {
string file; string file;
switch(currentState){ switch (currentState) {
case State.Deciding: case State.Deciding:
// Reset // Reset
if (radioReset.Checked){ if (radioReset.Checked) {
currentState = State.Reset; currentState = State.Reset;
Text = "Restore Defaults"; Text = "Restore Defaults";
@@ -83,15 +82,15 @@ namespace TweetDuck.Dialogs.Settings{
} }
// Import // Import
else if (radioImport.Checked){ else if (radioImport.Checked) {
using(OpenFileDialog dialog = new OpenFileDialog{ using (OpenFileDialog dialog = new OpenFileDialog {
AutoUpgradeEnabled = true, AutoUpgradeEnabled = true,
DereferenceLinks = true, DereferenceLinks = true,
Title = "Import TweetDuck Profile", Title = "Import TweetDuck Profile",
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings" Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
}){ }) {
if (dialog.ShowDialog() != DialogResult.OK){ if (dialog.ShowDialog() != DialogResult.OK) {
if (openImportImmediately){ if (openImportImmediately) {
Close(); Close();
} }
@@ -107,13 +106,13 @@ namespace TweetDuck.Dialogs.Settings{
Text = "Import Profile"; Text = "Import Profile";
SelectedItems = importManager.FindImportItems(); SelectedItems = importManager.FindImportItems();
foreach(CheckBox cb in checkBoxMap.Keys){ foreach (CheckBox cb in checkBoxMap.Keys) {
cb.Enabled = cb.Checked; cb.Enabled = cb.Checked;
} }
} }
// Export // Export
else if (radioExport.Checked){ else if (radioExport.Checked) {
currentState = State.Export; currentState = State.Export;
Text = "Export Profile"; Text = "Export Profile";
@@ -128,37 +127,36 @@ namespace TweetDuck.Dialogs.Settings{
break; break;
case State.Reset: case State.Reset:
if (FormMessage.Warning("Reset TweetDuck Options", "This will reset the selected items. Are you sure you want to proceed?", FormMessage.Yes, FormMessage.No)){ if (FormMessage.Warning("Reset TweetDuck Options", "This will reset the selected items. Are you sure you want to proceed?", FormMessage.Yes, FormMessage.No)) {
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested; App.ConfigManager.ProgramRestartRequested += Config_ProgramRestartRequested;
if (SelectedItems.HasFlag(ProfileManager.Items.UserConfig)){ if (SelectedItems.HasFlag(ProfileManager.Items.UserConfig)) {
Program.Config.User.Reset(); Program.Config.User.Reset();
} }
if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)){ if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)) {
Program.Config.System.Reset(); Program.Config.System.Reset();
} }
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested; App.ConfigManager.ProgramRestartRequested -= Config_ProgramRestartRequested;
if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)){ if (SelectedItems.HasFlag(ProfileManager.Items.Session)) {
ProfileManager.DeleteAuthCookie();
}
if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)) {
Program.Config.Plugins.Reset(); Program.Config.Plugins.Reset();
try{ try {
Directory.Delete(Program.PluginDataPath, true); Directory.Delete(plugins.PluginDataFolder, true);
}catch(Exception ex){ } catch (Exception ex) {
Program.Reporter.HandleException("Profile Reset", "Could not delete plugin data.", true, ex); App.ErrorHandler.HandleException("Profile Reset", "Could not delete plugin data.", true, ex);
} }
} }
if (SelectedItemsForceRestart){ if (requestedRestartFromConfig && FormMessage.Information("Profile Reset", "The application must restart for some of the restored options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)) {
RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[]{ Arguments.ArgDeleteCookies } : StringUtils.EmptyArray);
}
else if (requestedRestartFromConfig){
if (FormMessage.Information("Profile Reset", "The application must restart for some of the restored options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
RestartProgram(); RestartProgram();
} }
}
ShouldReloadBrowser = true; ShouldReloadBrowser = true;
@@ -169,20 +167,16 @@ namespace TweetDuck.Dialogs.Settings{
break; break;
case State.Import: case State.Import:
if (importManager.Import(SelectedItems)){ if (importManager.Import(SelectedItems)) {
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested; App.ConfigManager.ProgramRestartRequested += Config_ProgramRestartRequested;
Program.Config.ReloadAll(); App.ConfigManager.ReloadAll();
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested; App.ConfigManager.SaveAll();
App.ConfigManager.ProgramRestartRequested -= Config_ProgramRestartRequested;
if (SelectedItemsForceRestart){ if (requestedRestartFromConfig && FormMessage.Information("Profile Import", "The application must restart for some of the imported options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)) {
RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[]{ Arguments.ArgImportCookies } : StringUtils.EmptyArray);
}
else if (requestedRestartFromConfig){
if (FormMessage.Information("Profile Import", "The application must restart for some of the imported options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
RestartProgram(); RestartProgram();
} }
} }
}
ShouldReloadBrowser = true; ShouldReloadBrowser = true;
@@ -191,7 +185,7 @@ namespace TweetDuck.Dialogs.Settings{
break; break;
case State.Export: case State.Export:
using(SaveFileDialog dialog = new SaveFileDialog{ using (SaveFileDialog dialog = new SaveFileDialog {
AddExtension = true, AddExtension = true,
AutoUpgradeEnabled = true, AutoUpgradeEnabled = true,
OverwritePrompt = true, OverwritePrompt = true,
@@ -199,8 +193,8 @@ namespace TweetDuck.Dialogs.Settings{
FileName = "TweetDuck.tdsettings", FileName = "TweetDuck.tdsettings",
Title = "Export TweetDuck Profile", Title = "Export TweetDuck Profile",
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings" Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
}){ }) {
if (dialog.ShowDialog() != DialogResult.OK){ if (dialog.ShowDialog() != DialogResult.OK) {
return; return;
} }
@@ -215,30 +209,30 @@ namespace TweetDuck.Dialogs.Settings{
} }
} }
private void btnCancel_Click(object sender, EventArgs e){ private void btnCancel_Click(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel; DialogResult = DialogResult.Cancel;
Close(); Close();
} }
private void Config_ProgramRestartRequested(object sender, EventArgs e){ private void Config_ProgramRestartRequested(object sender, EventArgs e) {
requestedRestartFromConfig = true; requestedRestartFromConfig = true;
} }
private void SetFlag(ProfileManager.Items flag, bool enable){ private void SetFlag(ProfileManager.Items flag, bool enable) {
_selectedItems = enable ? _selectedItems | flag : _selectedItems & ~flag; _selectedItems = enable ? _selectedItems | flag : _selectedItems & ~flag;
btnContinue.Enabled = _selectedItems != ProfileManager.Items.None; btnContinue.Enabled = _selectedItems != ProfileManager.Items.None;
if (currentState == State.Import){ if (currentState == State.Import) {
btnContinue.Text = SelectedItemsForceRestart ? "Import && Restart" : "Import Profile"; btnContinue.Text = "Import Profile";
} }
else if (currentState == State.Reset){ else if (currentState == State.Reset) {
btnContinue.Text = SelectedItemsForceRestart ? "Restore && Restart" : "Restore Defaults"; btnContinue.Text = "Restore Defaults";
} }
} }
private void RestartProgram(params string[] extraArgs){ private void RestartProgram() {
IsRestarting = true; IsRestarting = true;
Program.Restart(extraArgs); Program.Restart();
} }
} }
} }

View File

@@ -1,23 +1,24 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetLib.Core.Collections; using TweetLib.Core;
using TweetLib.Utils.Collections;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsRestart : Form{ sealed partial class DialogSettingsRestart : Form {
public CommandLineArgs Args { get; private set; } public CommandLineArgs Args { get; private set; }
public DialogSettingsRestart(CommandLineArgs currentArgs){ public DialogSettingsRestart(CommandLineArgs currentArgs) {
InitializeComponent(); InitializeComponent();
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging); cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
cbLogging.CheckedChanged += control_Change; cbLogging.CheckedChanged += control_Change;
if (Program.IsPortable){ if (App.IsPortable) {
tbDataFolder.Text = "Not available in portable version"; tbDataFolder.Text = "Not available in portable version";
tbDataFolder.Enabled = false; tbDataFolder.Enabled = false;
} }
else{ else {
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder) ?? string.Empty; tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder) ?? string.Empty;
tbDataFolder.TextChanged += control_Change; tbDataFolder.TextChanged += control_Change;
} }
@@ -27,14 +28,14 @@ namespace TweetDuck.Dialogs.Settings{
Text = Program.BrandName + " Arguments"; Text = Program.BrandName + " Arguments";
} }
private void control_Change(object sender, EventArgs e){ private void control_Change(object sender, EventArgs e) {
Args = new CommandLineArgs(); Args = new CommandLineArgs();
if (cbLogging.Checked){ if (cbLogging.Checked) {
Args.AddFlag(Arguments.ArgLogging); Args.AddFlag(Arguments.ArgLogging);
} }
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){ if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled) {
Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text); Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);
} }
@@ -42,18 +43,18 @@ namespace TweetDuck.Dialogs.Settings{
tbShortcutTarget.Select(tbShortcutTarget.Text.Length, 0); tbShortcutTarget.Select(tbShortcutTarget.Text.Length, 0);
} }
private void tbShortcutTarget_Click(object sender, EventArgs e){ private void tbShortcutTarget_Click(object sender, EventArgs e) {
if (tbShortcutTarget.SelectionLength == 0){ if (tbShortcutTarget.SelectionLength == 0) {
tbShortcutTarget.SelectAll(); tbShortcutTarget.SelectAll();
} }
} }
private void btnRestart_Click(object sender, EventArgs e){ private void btnRestart_Click(object sender, EventArgs e) {
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();
} }
private void btnCancel_Click(object sender, EventArgs e){ private void btnCancel_Click(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel; DialogResult = DialogResult.Cancel;
Close(); Close();
} }

View File

@@ -1,11 +1,11 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class DialogSettingsSearchEngine : Form{ sealed partial class DialogSettingsSearchEngine : Form {
public string Url => textBoxUrl.Text; public string Url => textBoxUrl.Text;
public DialogSettingsSearchEngine(){ public DialogSettingsSearchEngine() {
InitializeComponent(); InitializeComponent();
Text = Program.BrandName + " Options - Custom Search Engine"; Text = Program.BrandName + " Options - Custom Search Engine";
@@ -14,12 +14,12 @@ namespace TweetDuck.Dialogs.Settings{
textBoxUrl.Select(textBoxUrl.Text.Length, 0); textBoxUrl.Select(textBoxUrl.Text.Length, 0);
} }
private void btnApply_Click(object sender, EventArgs e){ private void btnApply_Click(object sender, EventArgs e) {
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();
} }
private void btnCancel_Click(object sender, EventArgs e){ private void btnCancel_Click(object sender, EventArgs e) {
DialogResult = DialogResult.Cancel; DialogResult = DialogResult.Cancel;
Close(); Close();
} }

View File

@@ -40,24 +40,34 @@
this.panelClearCacheAuto = new System.Windows.Forms.Panel(); this.panelClearCacheAuto = new System.Windows.Forms.Panel();
this.panelConfiguration = new System.Windows.Forms.Panel(); this.panelConfiguration = new System.Windows.Forms.Panel();
this.labelConfiguration = new System.Windows.Forms.Label(); this.labelConfiguration = new System.Windows.Forms.Label();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel(); this.flowPanelLeft = new System.Windows.Forms.FlowLayoutPanel();
this.labelBrowserSettings = new System.Windows.Forms.Label();
this.checkHardwareAcceleration = new System.Windows.Forms.CheckBox();
this.checkAutomaticallyDetectColorProfile = new System.Windows.Forms.CheckBox();
this.checkTouchAdjustment = new System.Windows.Forms.CheckBox();
this.labelProxy = new System.Windows.Forms.Label();
this.checkUseSystemProxyForAllConnections = new System.Windows.Forms.CheckBox();
this.labelDevTools = new System.Windows.Forms.Label(); this.labelDevTools = new System.Windows.Forms.Label();
this.checkDevToolsInContextMenu = new System.Windows.Forms.CheckBox();
this.checkDevToolsWindowOnTop = new System.Windows.Forms.CheckBox(); this.checkDevToolsWindowOnTop = new System.Windows.Forms.CheckBox();
this.flowPanelRight = new System.Windows.Forms.FlowLayoutPanel();
this.panelSeparator = new System.Windows.Forms.Panel();
((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).BeginInit();
this.panelAppButtons.SuspendLayout(); this.panelAppButtons.SuspendLayout();
this.panelClearCacheAuto.SuspendLayout(); this.panelClearCacheAuto.SuspendLayout();
this.panelConfiguration.SuspendLayout(); this.panelConfiguration.SuspendLayout();
this.flowPanel.SuspendLayout(); this.flowPanelLeft.SuspendLayout();
this.flowPanelRight.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// btnClearCache // btnClearCache
// //
this.btnClearCache.Font = new System.Drawing.Font("Segoe UI", 9F); this.btnClearCache.Font = new System.Drawing.Font("Segoe UI", 9F);
this.btnClearCache.Location = new System.Drawing.Point(5, 135); this.btnClearCache.Location = new System.Drawing.Point(5, 145);
this.btnClearCache.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); this.btnClearCache.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnClearCache.Name = "btnClearCache"; this.btnClearCache.Name = "btnClearCache";
this.btnClearCache.Size = new System.Drawing.Size(143, 25); this.btnClearCache.Size = new System.Drawing.Size(143, 25);
this.btnClearCache.TabIndex = 3; this.btnClearCache.TabIndex = 5;
this.btnClearCache.Text = "Clear Cache (...)"; this.btnClearCache.Text = "Clear Cache (...)";
this.btnClearCache.UseVisualStyleBackColor = true; this.btnClearCache.UseVisualStyleBackColor = true;
// //
@@ -144,7 +154,7 @@
0, 0,
0}); 0});
this.numClearCacheThreshold.Name = "numClearCacheThreshold"; this.numClearCacheThreshold.Name = "numClearCacheThreshold";
this.numClearCacheThreshold.Size = new System.Drawing.Size(89, 23); this.numClearCacheThreshold.Size = new System.Drawing.Size(80, 23);
this.numClearCacheThreshold.TabIndex = 1; this.numClearCacheThreshold.TabIndex = 1;
this.numClearCacheThreshold.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; this.numClearCacheThreshold.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
this.numClearCacheThreshold.TextSuffix = " MB"; this.numClearCacheThreshold.TextSuffix = " MB";
@@ -193,92 +203,205 @@
// //
this.labelCache.AutoSize = true; this.labelCache.AutoSize = true;
this.labelCache.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); this.labelCache.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelCache.Location = new System.Drawing.Point(0, 112); this.labelCache.Location = new System.Drawing.Point(0, 122);
this.labelCache.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1); this.labelCache.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
this.labelCache.Name = "labelCache"; this.labelCache.Name = "labelCache";
this.labelCache.Size = new System.Drawing.Size(123, 19); this.labelCache.Size = new System.Drawing.Size(123, 19);
this.labelCache.TabIndex = 2; this.labelCache.TabIndex = 4;
this.labelCache.Text = "BROWSER CACHE"; this.labelCache.Text = "BROWSER CACHE";
// //
// panelClearCacheAuto // panelClearCacheAuto
// //
this.panelClearCacheAuto.Controls.Add(this.checkClearCacheAuto); this.panelClearCacheAuto.Controls.Add(this.checkClearCacheAuto);
this.panelClearCacheAuto.Controls.Add(this.numClearCacheThreshold); this.panelClearCacheAuto.Controls.Add(this.numClearCacheThreshold);
this.panelClearCacheAuto.Location = new System.Drawing.Point(0, 163); this.panelClearCacheAuto.Location = new System.Drawing.Point(0, 173);
this.panelClearCacheAuto.Margin = new System.Windows.Forms.Padding(0); this.panelClearCacheAuto.Margin = new System.Windows.Forms.Padding(0);
this.panelClearCacheAuto.Name = "panelClearCacheAuto"; this.panelClearCacheAuto.Name = "panelClearCacheAuto";
this.panelClearCacheAuto.Size = new System.Drawing.Size(300, 28); this.panelClearCacheAuto.Size = new System.Drawing.Size(300, 28);
this.panelClearCacheAuto.TabIndex = 4; this.panelClearCacheAuto.TabIndex = 6;
// //
// panelConfiguration // panelConfiguration
// //
this.panelConfiguration.Controls.Add(this.btnEditCSS); this.panelConfiguration.Controls.Add(this.btnEditCSS);
this.panelConfiguration.Controls.Add(this.btnEditCefArgs); this.panelConfiguration.Controls.Add(this.btnEditCefArgs);
this.panelConfiguration.Location = new System.Drawing.Point(0, 241); this.panelConfiguration.Location = new System.Drawing.Point(0, 132);
this.panelConfiguration.Margin = new System.Windows.Forms.Padding(0); this.panelConfiguration.Margin = new System.Windows.Forms.Padding(0);
this.panelConfiguration.Name = "panelConfiguration"; this.panelConfiguration.Name = "panelConfiguration";
this.panelConfiguration.Size = new System.Drawing.Size(300, 31); this.panelConfiguration.Size = new System.Drawing.Size(300, 31);
this.panelConfiguration.TabIndex = 6; this.panelConfiguration.TabIndex = 3;
// //
// labelConfiguration // labelConfiguration
// //
this.labelConfiguration.AutoSize = true; this.labelConfiguration.AutoSize = true;
this.labelConfiguration.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); this.labelConfiguration.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelConfiguration.Location = new System.Drawing.Point(0, 221); this.labelConfiguration.Location = new System.Drawing.Point(0, 112);
this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1); this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
this.labelConfiguration.Name = "labelConfiguration"; this.labelConfiguration.Name = "labelConfiguration";
this.labelConfiguration.Size = new System.Drawing.Size(123, 19); this.labelConfiguration.Size = new System.Drawing.Size(123, 19);
this.labelConfiguration.TabIndex = 5; this.labelConfiguration.TabIndex = 2;
this.labelConfiguration.Text = "CONFIGURATION"; this.labelConfiguration.Text = "CONFIGURATION";
// //
// flowPanel // flowPanelLeft
// //
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.flowPanelLeft.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left))); | System.Windows.Forms.AnchorStyles.Left)));
this.flowPanel.Controls.Add(this.labelApp); this.flowPanelLeft.Controls.Add(this.labelBrowserSettings);
this.flowPanel.Controls.Add(this.panelAppButtons); this.flowPanelLeft.Controls.Add(this.checkHardwareAcceleration);
this.flowPanel.Controls.Add(this.labelCache); this.flowPanelLeft.Controls.Add(this.checkAutomaticallyDetectColorProfile);
this.flowPanel.Controls.Add(this.btnClearCache); this.flowPanelLeft.Controls.Add(this.checkTouchAdjustment);
this.flowPanel.Controls.Add(this.panelClearCacheAuto); this.flowPanelLeft.Controls.Add(this.labelCache);
this.flowPanel.Controls.Add(this.labelConfiguration); this.flowPanelLeft.Controls.Add(this.btnClearCache);
this.flowPanel.Controls.Add(this.panelConfiguration); this.flowPanelLeft.Controls.Add(this.panelClearCacheAuto);
this.flowPanel.Controls.Add(this.labelDevTools); this.flowPanelLeft.Controls.Add(this.labelProxy);
this.flowPanel.Controls.Add(this.checkDevToolsWindowOnTop); this.flowPanelLeft.Controls.Add(this.checkUseSystemProxyForAllConnections);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; this.flowPanelLeft.Controls.Add(this.labelDevTools);
this.flowPanel.Location = new System.Drawing.Point(9, 9); this.flowPanelLeft.Controls.Add(this.checkDevToolsInContextMenu);
this.flowPanel.Name = "flowPanel"; this.flowPanelLeft.Controls.Add(this.checkDevToolsWindowOnTop);
this.flowPanel.Size = new System.Drawing.Size(300, 462); this.flowPanelLeft.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.TabIndex = 0; this.flowPanelLeft.Location = new System.Drawing.Point(9, 9);
this.flowPanel.WrapContents = false; this.flowPanelLeft.Name = "flowPanelLeft";
this.flowPanelLeft.Size = new System.Drawing.Size(300, 462);
this.flowPanelLeft.TabIndex = 0;
this.flowPanelLeft.WrapContents = false;
//
// labelBrowserSettings
//
this.labelBrowserSettings.AutoSize = true;
this.labelBrowserSettings.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelBrowserSettings.Location = new System.Drawing.Point(0, 0);
this.labelBrowserSettings.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.labelBrowserSettings.Name = "labelBrowserSettings";
this.labelBrowserSettings.Size = new System.Drawing.Size(143, 19);
this.labelBrowserSettings.TabIndex = 0;
this.labelBrowserSettings.Text = "BROWSER SETTINGS";
//
// checkHardwareAcceleration
//
this.checkHardwareAcceleration.AutoSize = true;
this.checkHardwareAcceleration.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 23);
this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkHardwareAcceleration.Name = "checkHardwareAcceleration";
this.checkHardwareAcceleration.Size = new System.Drawing.Size(146, 19);
this.checkHardwareAcceleration.TabIndex = 1;
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
//
// checkAutomaticallyDetectColorProfile
//
this.checkAutomaticallyDetectColorProfile.AutoSize = true;
this.checkAutomaticallyDetectColorProfile.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkAutomaticallyDetectColorProfile.Location = new System.Drawing.Point(6, 47);
this.checkAutomaticallyDetectColorProfile.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkAutomaticallyDetectColorProfile.Name = "checkAutomaticallyDetectColorProfile";
this.checkAutomaticallyDetectColorProfile.Size = new System.Drawing.Size(206, 19);
this.checkAutomaticallyDetectColorProfile.TabIndex = 2;
this.checkAutomaticallyDetectColorProfile.Text = "Automatically Detect Color Profile";
this.checkAutomaticallyDetectColorProfile.UseVisualStyleBackColor = true;
//
// checkTouchAdjustment
//
this.checkTouchAdjustment.AutoSize = true;
this.checkTouchAdjustment.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkTouchAdjustment.Location = new System.Drawing.Point(6, 71);
this.checkTouchAdjustment.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkTouchAdjustment.Name = "checkTouchAdjustment";
this.checkTouchAdjustment.Size = new System.Drawing.Size(163, 19);
this.checkTouchAdjustment.TabIndex = 3;
this.checkTouchAdjustment.Text = "Touch Screen Adjustment";
this.checkTouchAdjustment.UseVisualStyleBackColor = true;
//
// labelProxy
//
this.labelProxy.AutoSize = true;
this.labelProxy.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelProxy.Location = new System.Drawing.Point(0, 231);
this.labelProxy.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
this.labelProxy.Name = "labelProxy";
this.labelProxy.Size = new System.Drawing.Size(54, 19);
this.labelProxy.TabIndex = 7;
this.labelProxy.Text = "PROXY";
//
// checkUseSystemProxyForAllConnections
//
this.checkUseSystemProxyForAllConnections.AutoSize = true;
this.checkUseSystemProxyForAllConnections.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkUseSystemProxyForAllConnections.Location = new System.Drawing.Point(6, 257);
this.checkUseSystemProxyForAllConnections.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
this.checkUseSystemProxyForAllConnections.Name = "checkUseSystemProxyForAllConnections";
this.checkUseSystemProxyForAllConnections.Size = new System.Drawing.Size(223, 19);
this.checkUseSystemProxyForAllConnections.TabIndex = 8;
this.checkUseSystemProxyForAllConnections.Text = "Use System Proxy for All Connections";
this.checkUseSystemProxyForAllConnections.UseVisualStyleBackColor = true;
// //
// labelDevTools // labelDevTools
// //
this.labelDevTools.AutoSize = true; this.labelDevTools.AutoSize = true;
this.labelDevTools.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold); this.labelDevTools.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelDevTools.Location = new System.Drawing.Point(0, 302); this.labelDevTools.Location = new System.Drawing.Point(0, 308);
this.labelDevTools.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1); this.labelDevTools.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
this.labelDevTools.Name = "labelDevTools"; this.labelDevTools.Name = "labelDevTools";
this.labelDevTools.Size = new System.Drawing.Size(156, 19); this.labelDevTools.Size = new System.Drawing.Size(156, 19);
this.labelDevTools.TabIndex = 7; this.labelDevTools.TabIndex = 9;
this.labelDevTools.Text = "DEVELOPMENT TOOLS"; this.labelDevTools.Text = "DEVELOPMENT TOOLS";
// //
// checkDevToolsInContextMenu
//
this.checkDevToolsInContextMenu.AutoSize = true;
this.checkDevToolsInContextMenu.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkDevToolsInContextMenu.Location = new System.Drawing.Point(6, 334);
this.checkDevToolsInContextMenu.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
this.checkDevToolsInContextMenu.Name = "checkDevToolsInContextMenu";
this.checkDevToolsInContextMenu.Size = new System.Drawing.Size(201, 19);
this.checkDevToolsInContextMenu.TabIndex = 10;
this.checkDevToolsInContextMenu.Text = "Show Dev Tools in Context Menu";
this.checkDevToolsInContextMenu.UseVisualStyleBackColor = true;
//
// checkDevToolsWindowOnTop // checkDevToolsWindowOnTop
// //
this.checkDevToolsWindowOnTop.AutoSize = true; this.checkDevToolsWindowOnTop.AutoSize = true;
this.checkDevToolsWindowOnTop.Font = new System.Drawing.Font("Segoe UI", 9F); this.checkDevToolsWindowOnTop.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkDevToolsWindowOnTop.Location = new System.Drawing.Point(6, 328); this.checkDevToolsWindowOnTop.Location = new System.Drawing.Point(6, 358);
this.checkDevToolsWindowOnTop.Margin = new System.Windows.Forms.Padding(6, 6, 0, 2); this.checkDevToolsWindowOnTop.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkDevToolsWindowOnTop.Name = "checkDevToolsWindowOnTop"; this.checkDevToolsWindowOnTop.Name = "checkDevToolsWindowOnTop";
this.checkDevToolsWindowOnTop.Size = new System.Drawing.Size(168, 19); this.checkDevToolsWindowOnTop.Size = new System.Drawing.Size(168, 19);
this.checkDevToolsWindowOnTop.TabIndex = 8; this.checkDevToolsWindowOnTop.TabIndex = 11;
this.checkDevToolsWindowOnTop.Text = "Dev Tools Window On Top"; this.checkDevToolsWindowOnTop.Text = "Dev Tools Window On Top";
this.checkDevToolsWindowOnTop.UseVisualStyleBackColor = true; this.checkDevToolsWindowOnTop.UseVisualStyleBackColor = true;
// //
// flowPanelRight
//
this.flowPanelRight.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.flowPanelRight.Controls.Add(this.labelApp);
this.flowPanelRight.Controls.Add(this.panelAppButtons);
this.flowPanelRight.Controls.Add(this.labelConfiguration);
this.flowPanelRight.Controls.Add(this.panelConfiguration);
this.flowPanelRight.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanelRight.Location = new System.Drawing.Point(322, 9);
this.flowPanelRight.Name = "flowPanelRight";
this.flowPanelRight.Size = new System.Drawing.Size(300, 462);
this.flowPanelRight.TabIndex = 1;
this.flowPanelRight.WrapContents = false;
//
// panelSeparator
//
this.panelSeparator.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.panelSeparator.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(200)))), ((int)(((byte)(200)))), ((int)(((byte)(200)))));
this.panelSeparator.Location = new System.Drawing.Point(312, 0);
this.panelSeparator.Margin = new System.Windows.Forms.Padding(0, 0, 6, 0);
this.panelSeparator.Name = "panelSeparator";
this.panelSeparator.Size = new System.Drawing.Size(1, 480);
this.panelSeparator.TabIndex = 3;
//
// TabSettingsAdvanced // TabSettingsAdvanced
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.flowPanel); this.Controls.Add(this.panelSeparator);
this.Controls.Add(this.flowPanelRight);
this.Controls.Add(this.flowPanelLeft);
this.Name = "TabSettingsAdvanced"; this.Name = "TabSettingsAdvanced";
this.Size = new System.Drawing.Size(631, 480); this.Size = new System.Drawing.Size(631, 480);
((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).EndInit();
@@ -286,8 +409,10 @@
this.panelClearCacheAuto.ResumeLayout(false); this.panelClearCacheAuto.ResumeLayout(false);
this.panelClearCacheAuto.PerformLayout(); this.panelClearCacheAuto.PerformLayout();
this.panelConfiguration.ResumeLayout(false); this.panelConfiguration.ResumeLayout(false);
this.flowPanel.ResumeLayout(false); this.flowPanelLeft.ResumeLayout(false);
this.flowPanel.PerformLayout(); this.flowPanelLeft.PerformLayout();
this.flowPanelRight.ResumeLayout(false);
this.flowPanelRight.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
} }
@@ -310,8 +435,17 @@
private System.Windows.Forms.Label labelConfiguration; private System.Windows.Forms.Label labelConfiguration;
private Controls.NumericUpDownEx numClearCacheThreshold; private Controls.NumericUpDownEx numClearCacheThreshold;
private System.Windows.Forms.CheckBox checkClearCacheAuto; private System.Windows.Forms.CheckBox checkClearCacheAuto;
private System.Windows.Forms.FlowLayoutPanel flowPanel; private System.Windows.Forms.FlowLayoutPanel flowPanelLeft;
private System.Windows.Forms.Label labelDevTools; private System.Windows.Forms.Label labelDevTools;
private System.Windows.Forms.CheckBox checkDevToolsInContextMenu;
private System.Windows.Forms.CheckBox checkDevToolsWindowOnTop; private System.Windows.Forms.CheckBox checkDevToolsWindowOnTop;
private System.Windows.Forms.Label labelProxy;
private System.Windows.Forms.CheckBox checkUseSystemProxyForAllConnections;
private System.Windows.Forms.FlowLayoutPanel flowPanelRight;
private System.Windows.Forms.Label labelBrowserSettings;
private System.Windows.Forms.CheckBox checkTouchAdjustment;
private System.Windows.Forms.CheckBox checkAutomaticallyDetectColorProfile;
private System.Windows.Forms.CheckBox checkHardwareAcceleration;
private System.Windows.Forms.Panel panelSeparator;
} }
} }

View File

@@ -7,12 +7,12 @@ using TweetDuck.Management;
using TweetDuck.Utils; using TweetDuck.Utils;
using TweetLib.Core; using TweetLib.Core;
namespace TweetDuck.Dialogs.Settings{ namespace TweetDuck.Dialogs.Settings {
sealed partial class TabSettingsAdvanced : FormSettings.BaseTab{ sealed partial class TabSettingsAdvanced : FormSettings.BaseTab {
private readonly Action<string> reinjectBrowserCSS; private readonly Action<string> reinjectBrowserCSS;
private readonly Action openDevTools; private readonly Action openDevTools;
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, Action openDevTools){ public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, Action openDevTools) {
InitializeComponent(); InitializeComponent();
this.reinjectBrowserCSS = reinjectBrowserCSS; this.reinjectBrowserCSS = reinjectBrowserCSS;
@@ -25,6 +25,16 @@ namespace TweetDuck.Dialogs.Settings{
toolTip.SetToolTip(btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch."); toolTip.SetToolTip(btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch.");
toolTip.SetToolTip(btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments."); toolTip.SetToolTip(btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments.");
// browser settings
toolTip.SetToolTip(checkTouchAdjustment, "Toggles Chromium touch screen adjustment.\r\nDisabled by default, because it is very imprecise with TweetDeck.");
toolTip.SetToolTip(checkAutomaticallyDetectColorProfile, "Automatically detects the color profile of your system.\r\nUses the sRGB profile if disabled.");
toolTip.SetToolTip(checkHardwareAcceleration, "Uses graphics card to improve performance.\r\nDisable if you experience visual glitches, or to save a small amount of RAM.");
checkTouchAdjustment.Checked = SysConfig.EnableTouchAdjustment;
checkAutomaticallyDetectColorProfile.Checked = SysConfig.EnableColorProfileDetection;
checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration;
// browser cache // browser cache
toolTip.SetToolTip(btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources."); toolTip.SetToolTip(btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources.");
@@ -35,7 +45,7 @@ namespace TweetDuck.Dialogs.Settings{
numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold); numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold);
BrowserCache.GetCacheSize(task => { BrowserCache.GetCacheSize(task => {
string text = task.Status == TaskStatus.RanToCompletion ? (int)Math.Ceiling(task.Result / (1024.0 * 1024.0)) + " MB" : "unknown"; string text = task.Status == TaskStatus.RanToCompletion ? (int) Math.Ceiling(task.Result / (1024.0 * 1024.0)) + " MB" : "unknown";
this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})"); this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
}); });
@@ -44,76 +54,105 @@ namespace TweetDuck.Dialogs.Settings{
toolTip.SetToolTip(btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework."); toolTip.SetToolTip(btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework.");
toolTip.SetToolTip(btnEditCSS, "Set custom CSS for browser and notification windows."); toolTip.SetToolTip(btnEditCSS, "Set custom CSS for browser and notification windows.");
// proxy
toolTip.SetToolTip(checkUseSystemProxyForAllConnections, "Sets whether all connections should automatically detect and use the system proxy.\r\nBy default, only the browser component uses the system proxy, while other parts (such as update checks) ignore it.\r\nDisabled by default because Windows' proxy detection can be really slow.");
checkUseSystemProxyForAllConnections.Checked = SysConfig.UseSystemProxyForAllConnections;
// development tools // development tools
toolTip.SetToolTip(checkDevToolsInContextMenu, "Sets whether all context menus include an option to open dev tools.");
toolTip.SetToolTip(checkDevToolsWindowOnTop, "Sets whether dev tool windows appears on top of other windows."); toolTip.SetToolTip(checkDevToolsWindowOnTop, "Sets whether dev tool windows appears on top of other windows.");
checkDevToolsInContextMenu.Checked = Config.DevToolsInContextMenu;
checkDevToolsWindowOnTop.Checked = Config.DevToolsWindowOnTop; checkDevToolsWindowOnTop.Checked = Config.DevToolsWindowOnTop;
if (!BrowserUtils.HasDevTools){
checkDevToolsWindowOnTop.Enabled = false;
}
} }
public override void OnReady(){ public override void OnReady() {
btnOpenAppFolder.Click += btnOpenAppFolder_Click; btnOpenAppFolder.Click += btnOpenAppFolder_Click;
btnOpenDataFolder.Click += btnOpenDataFolder_Click; btnOpenDataFolder.Click += btnOpenDataFolder_Click;
btnRestart.Click += btnRestart_Click; btnRestart.Click += btnRestart_Click;
btnRestartArgs.Click += btnRestartArgs_Click; btnRestartArgs.Click += btnRestartArgs_Click;
checkTouchAdjustment.CheckedChanged += checkTouchAdjustment_CheckedChanged;
checkAutomaticallyDetectColorProfile.CheckedChanged += checkAutomaticallyDetectColorProfile_CheckedChanged;
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
btnClearCache.Click += btnClearCache_Click; btnClearCache.Click += btnClearCache_Click;
checkClearCacheAuto.CheckedChanged += checkClearCacheAuto_CheckedChanged; checkClearCacheAuto.CheckedChanged += checkClearCacheAuto_CheckedChanged;
btnEditCefArgs.Click += btnEditCefArgs_Click; btnEditCefArgs.Click += btnEditCefArgs_Click;
btnEditCSS.Click += btnEditCSS_Click; btnEditCSS.Click += btnEditCSS_Click;
checkUseSystemProxyForAllConnections.CheckedChanged += checkUseSystemProxyForAllConnections_CheckedChanged;
checkDevToolsWindowOnTop.CheckedChanged += checkDevToolsWindowOnTop_CheckedChanged; checkDevToolsWindowOnTop.CheckedChanged += checkDevToolsWindowOnTop_CheckedChanged;
checkDevToolsInContextMenu.CheckedChanged += checkDevToolsInContextMenuOnCheckedChanged;
} }
public override void OnClosing(){ public override void OnClosing() {
SysConfig.ClearCacheAutomatically = checkClearCacheAuto.Checked; SysConfig.ClearCacheAutomatically = checkClearCacheAuto.Checked;
SysConfig.ClearCacheThreshold = (int)numClearCacheThreshold.Value; SysConfig.ClearCacheThreshold = (int) numClearCacheThreshold.Value;
} }
#region Application #region Application
private void btnOpenAppFolder_Click(object sender, EventArgs e){ private void btnOpenAppFolder_Click(object sender, EventArgs e) {
App.SystemHandler.OpenFileExplorer(Program.ProgramPath); App.SystemHandler.OpenFileExplorer(App.ProgramPath);
} }
private void btnOpenDataFolder_Click(object sender, EventArgs e){ private void btnOpenDataFolder_Click(object sender, EventArgs e) {
App.SystemHandler.OpenFileExplorer(Program.StoragePath); App.SystemHandler.OpenFileExplorer(App.StoragePath);
} }
private void btnRestart_Click(object sender, EventArgs e){ private void btnRestart_Click(object sender, EventArgs e) {
Program.Restart(); Program.Restart();
} }
private void btnRestartArgs_Click(object sender, EventArgs e){ private void btnRestartArgs_Click(object sender, EventArgs e) {
using DialogSettingsRestart dialog = new DialogSettingsRestart(Arguments.GetCurrentClean()); using DialogSettingsRestart dialog = new DialogSettingsRestart(Arguments.GetCurrentClean());
if (dialog.ShowDialog() == DialogResult.OK){ if (dialog.ShowDialog() == DialogResult.OK) {
Program.RestartWithArgs(dialog.Args); Program.RestartWithArgs(dialog.Args);
} }
} }
#endregion #endregion
#region Browser Settings
private void checkTouchAdjustment_CheckedChanged(object sender, EventArgs e) {
SysConfig.EnableTouchAdjustment = checkTouchAdjustment.Checked;
}
private void checkAutomaticallyDetectColorProfile_CheckedChanged(object sender, EventArgs e) {
SysConfig.EnableColorProfileDetection = checkAutomaticallyDetectColorProfile.Checked;
}
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e) {
SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
}
#endregion
#region Browser Cache #region Browser Cache
private void btnClearCache_Click(object sender, EventArgs e){ private void btnClearCache_Click(object sender, EventArgs e) {
btnClearCache.Enabled = false; btnClearCache.Enabled = false;
BrowserCache.SetClearOnExit(); BrowserCache.SetClearOnExit();
FormMessage.Information("Clear Cache", "Cache will be automatically cleared when TweetDuck exits.", FormMessage.OK); FormMessage.Information("Clear Cache", "Cache will be automatically cleared when TweetDuck exits.", FormMessage.OK);
} }
private void checkClearCacheAuto_CheckedChanged(object sender, EventArgs e){ private void checkClearCacheAuto_CheckedChanged(object sender, EventArgs e) {
numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked; numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked;
} }
#endregion #endregion
#region Configuration #region Configuration
private void btnEditCefArgs_Click(object sender, EventArgs e){ private void btnEditCefArgs_Click(object sender, EventArgs e) {
DialogSettingsCefArgs form = new DialogSettingsCefArgs(Config.CustomCefArgs); DialogSettingsCefArgs form = new DialogSettingsCefArgs(Config.CustomCefArgs);
form.VisibleChanged += (sender2, args2) => { form.VisibleChanged += (sender2, args2) => {
@@ -123,7 +162,7 @@ namespace TweetDuck.Dialogs.Settings{
form.FormClosed += (sender2, args2) => { form.FormClosed += (sender2, args2) => {
RestoreParentForm(); RestoreParentForm();
if (form.DialogResult == DialogResult.OK){ if (form.DialogResult == DialogResult.OK) {
Config.CustomCefArgs = form.CefArgs; Config.CustomCefArgs = form.CefArgs;
} }
@@ -134,7 +173,7 @@ namespace TweetDuck.Dialogs.Settings{
NativeMethods.SetFormDisabled(ParentForm, true); NativeMethods.SetFormDisabled(ParentForm, true);
} }
private void btnEditCSS_Click(object sender, EventArgs e){ private void btnEditCSS_Click(object sender, EventArgs e) {
DialogSettingsCSS form = new DialogSettingsCSS(Config.CustomBrowserCSS, Config.CustomNotificationCSS, reinjectBrowserCSS, openDevTools); DialogSettingsCSS form = new DialogSettingsCSS(Config.CustomBrowserCSS, Config.CustomNotificationCSS, reinjectBrowserCSS, openDevTools);
form.VisibleChanged += (sender2, args2) => { form.VisibleChanged += (sender2, args2) => {
@@ -144,7 +183,7 @@ namespace TweetDuck.Dialogs.Settings{
form.FormClosed += (sender2, args2) => { form.FormClosed += (sender2, args2) => {
RestoreParentForm(); RestoreParentForm();
if (form.DialogResult == DialogResult.OK){ if (form.DialogResult == DialogResult.OK) {
Config.CustomBrowserCSS = form.BrowserCSS; Config.CustomBrowserCSS = form.BrowserCSS;
Config.CustomNotificationCSS = form.NotificationCSS; Config.CustomNotificationCSS = form.NotificationCSS;
} }
@@ -157,16 +196,29 @@ namespace TweetDuck.Dialogs.Settings{
NativeMethods.SetFormDisabled(ParentForm, true); NativeMethods.SetFormDisabled(ParentForm, true);
} }
private void RestoreParentForm(){ private void RestoreParentForm() {
if (ParentForm != null){ // when the parent is closed first, ParentForm is null in FormClosed event if (ParentForm != null) { // when the parent is closed first, ParentForm is null in FormClosed event
NativeMethods.SetFormDisabled(ParentForm, false); NativeMethods.SetFormDisabled(ParentForm, false);
} }
} }
#endregion #endregion
#region Proxy
private void checkUseSystemProxyForAllConnections_CheckedChanged(object sender, EventArgs e) {
SysConfig.UseSystemProxyForAllConnections = checkUseSystemProxyForAllConnections.Checked;
}
#endregion
#region Development Tools #region Development Tools
private void checkDevToolsWindowOnTop_CheckedChanged(object sender, EventArgs e){ private void checkDevToolsInContextMenuOnCheckedChanged(object sender, EventArgs e) {
Config.DevToolsInContextMenu = checkDevToolsInContextMenu.Checked;
}
private void checkDevToolsWindowOnTop_CheckedChanged(object sender, EventArgs e) {
Config.DevToolsWindowOnTop = checkDevToolsWindowOnTop.Checked; Config.DevToolsWindowOnTop = checkDevToolsWindowOnTop.Checked;
} }

View File

@@ -24,80 +24,13 @@
/// </summary> /// </summary>
private void InitializeComponent() { private void InitializeComponent() {
this.components = new System.ComponentModel.Container(); this.components = new System.ComponentModel.Container();
this.panelDataCollection = new System.Windows.Forms.Panel();
this.labelDataCollectionLink = new System.Windows.Forms.LinkLabel();
this.checkDataCollection = new System.Windows.Forms.CheckBox();
this.labelDataCollectionMessage = new System.Windows.Forms.Label();
this.btnViewReport = new System.Windows.Forms.Button();
this.btnSendFeedback = new System.Windows.Forms.Button(); this.btnSendFeedback = new System.Windows.Forms.Button();
this.labelDataCollection = new System.Windows.Forms.Label();
this.labelFeedback = new System.Windows.Forms.Label();
this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel(); this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.panelDataCollection.SuspendLayout(); this.labelFeedback = new System.Windows.Forms.Label();
this.flowPanel.SuspendLayout(); this.flowPanel.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// panelDataCollection
//
this.panelDataCollection.Controls.Add(this.labelDataCollectionLink);
this.panelDataCollection.Controls.Add(this.checkDataCollection);
this.panelDataCollection.Location = new System.Drawing.Point(0, 78);
this.panelDataCollection.Margin = new System.Windows.Forms.Padding(0);
this.panelDataCollection.Name = "panelDataCollection";
this.panelDataCollection.Size = new System.Drawing.Size(300, 28);
this.panelDataCollection.TabIndex = 3;
//
// labelDataCollectionLink
//
this.labelDataCollectionLink.AutoSize = true;
this.labelDataCollectionLink.Font = new System.Drawing.Font("Segoe UI", 9F);
this.labelDataCollectionLink.LinkArea = new System.Windows.Forms.LinkArea(1, 10);
this.labelDataCollectionLink.LinkBehavior = System.Windows.Forms.LinkBehavior.HoverUnderline;
this.labelDataCollectionLink.Location = new System.Drawing.Point(153, 4);
this.labelDataCollectionLink.Margin = new System.Windows.Forms.Padding(0);
this.labelDataCollectionLink.Name = "labelDataCollectionLink";
this.labelDataCollectionLink.Size = new System.Drawing.Size(71, 21);
this.labelDataCollectionLink.TabIndex = 1;
this.labelDataCollectionLink.TabStop = true;
this.labelDataCollectionLink.Text = "(learn more)";
this.labelDataCollectionLink.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.labelDataCollectionLink.UseCompatibleTextRendering = true;
//
// checkDataCollection
//
this.checkDataCollection.AutoSize = true;
this.checkDataCollection.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkDataCollection.Location = new System.Drawing.Point(6, 6);
this.checkDataCollection.Margin = new System.Windows.Forms.Padding(6, 6, 0, 2);
this.checkDataCollection.Name = "checkDataCollection";
this.checkDataCollection.Size = new System.Drawing.Size(147, 19);
this.checkDataCollection.TabIndex = 0;
this.checkDataCollection.Text = "Send Anonymous Data";
this.checkDataCollection.UseVisualStyleBackColor = true;
//
// labelDataCollectionMessage
//
this.labelDataCollectionMessage.Font = new System.Drawing.Font("Segoe UI", 9F);
this.labelDataCollectionMessage.Location = new System.Drawing.Point(6, 143);
this.labelDataCollectionMessage.Margin = new System.Windows.Forms.Padding(6);
this.labelDataCollectionMessage.Name = "labelDataCollectionMessage";
this.labelDataCollectionMessage.Size = new System.Drawing.Size(288, 67);
this.labelDataCollectionMessage.TabIndex = 5;
//
// btnViewReport
//
this.btnViewReport.AutoSize = true;
this.btnViewReport.Font = new System.Drawing.Font("Segoe UI", 9F);
this.btnViewReport.Location = new System.Drawing.Point(5, 109);
this.btnViewReport.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnViewReport.Name = "btnViewReport";
this.btnViewReport.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnViewReport.Size = new System.Drawing.Size(155, 25);
this.btnViewReport.TabIndex = 4;
this.btnViewReport.Text = "View My Analytics Report";
this.btnViewReport.UseVisualStyleBackColor = true;
//
// btnSendFeedback // btnSendFeedback
// //
this.btnSendFeedback.AutoSize = true; this.btnSendFeedback.AutoSize = true;
@@ -111,16 +44,17 @@
this.btnSendFeedback.Text = "Send Feedback / Bug Report"; this.btnSendFeedback.Text = "Send Feedback / Bug Report";
this.btnSendFeedback.UseVisualStyleBackColor = true; this.btnSendFeedback.UseVisualStyleBackColor = true;
// //
// labelDataCollection // flowPanel
// //
this.labelDataCollection.AutoSize = true; this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles) ((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
this.labelDataCollection.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold); this.flowPanel.Controls.Add(this.labelFeedback);
this.labelDataCollection.Location = new System.Drawing.Point(3, 63); this.flowPanel.Controls.Add(this.btnSendFeedback);
this.labelDataCollection.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.labelDataCollection.Name = "labelDataCollection"; this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.labelDataCollection.Size = new System.Drawing.Size(88, 15); this.flowPanel.Name = "flowPanel";
this.labelDataCollection.TabIndex = 2; this.flowPanel.Size = new System.Drawing.Size(300, 462);
this.labelDataCollection.Text = "Data Collection"; this.flowPanel.TabIndex = 0;
this.flowPanel.WrapContents = false;
// //
// labelFeedback // labelFeedback
// //
@@ -133,24 +67,6 @@
this.labelFeedback.TabIndex = 0; this.labelFeedback.TabIndex = 0;
this.labelFeedback.Text = "FEEDBACK"; this.labelFeedback.Text = "FEEDBACK";
// //
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelFeedback);
this.flowPanel.Controls.Add(this.btnSendFeedback);
this.flowPanel.Controls.Add(this.labelDataCollection);
this.flowPanel.Controls.Add(this.panelDataCollection);
this.flowPanel.Controls.Add(this.btnViewReport);
this.flowPanel.Controls.Add(this.labelDataCollectionMessage);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(300, 462);
this.flowPanel.TabIndex = 0;
this.flowPanel.WrapContents = false;
//
// TabSettingsFeedback // TabSettingsFeedback
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -158,25 +74,16 @@
this.Controls.Add(this.flowPanel); this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsFeedback"; this.Name = "TabSettingsFeedback";
this.Size = new System.Drawing.Size(631, 480); this.Size = new System.Drawing.Size(631, 480);
this.panelDataCollection.ResumeLayout(false);
this.panelDataCollection.PerformLayout();
this.flowPanel.ResumeLayout(false); this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout(); this.flowPanel.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
} }
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Button btnSendFeedback;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
private System.Windows.Forms.Label labelFeedback;
#endregion #endregion
private System.Windows.Forms.Panel panelDataCollection;
private System.Windows.Forms.CheckBox checkDataCollection;
private System.Windows.Forms.Label labelDataCollection;
private System.Windows.Forms.Label labelFeedback;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.LinkLabel labelDataCollectionLink;
private System.Windows.Forms.Button btnSendFeedback;
private System.Windows.Forms.Button btnViewReport;
private System.Windows.Forms.Label labelDataCollectionMessage;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
} }
} }

Some files were not shown because too many files have changed in this diff Show More