mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 10:32:10 +02:00
Compare commits
1 Commits
1.21.1
...
random_wip
Author | SHA1 | Date | |
---|---|---|---|
d6a14edcdf |
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,3 +1,5 @@
|
|||||||
# Auto detect text files and perform LF normalization
|
# Auto detect text files and perform LF normalization
|
||||||
* text=auto eof=lf
|
* text=auto
|
||||||
|
|
||||||
|
*.txt text eof=lf
|
||||||
*.cs diff=csharp
|
*.cs diff=csharp
|
||||||
|
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,3 +0,0 @@
|
|||||||
github: chylex
|
|
||||||
patreon: chylex
|
|
||||||
ko_fi: chylex
|
|
7
.gitignore
vendored
7
.gitignore
vendored
@@ -5,14 +5,8 @@
|
|||||||
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
|
||||||
@@ -146,3 +140,4 @@ _UpgradeReport_Files/
|
|||||||
Backup*/
|
Backup*/
|
||||||
UpgradeLog*.XML
|
UpgradeLog*.XML
|
||||||
UpgradeLog*.htm
|
UpgradeLog*.htm
|
||||||
|
|
||||||
|
8
.idea/.idea.TweetDuck/.idea/.gitignore
generated
vendored
8
.idea/.idea.TweetDuck/.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Rider ignored files
|
|
||||||
/projectSettingsUpdater.xml
|
|
||||||
/modules.xml
|
|
||||||
/contentModel.xml
|
|
||||||
/.idea.TweetDuck.iml
|
|
450
.idea/.idea.TweetDuck/.idea/codeStyles/Project.xml
generated
450
.idea/.idea.TweetDuck/.idea/codeStyles/Project.xml
generated
@@ -1,450 +0,0 @@
|
|||||||
<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=" " />
|
|
||||||
<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>
|
|
@@ -1,5 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<state>
|
|
||||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
|
||||||
</state>
|
|
||||||
</component>
|
|
6
.idea/.idea.TweetDuck/.idea/encodings.xml
generated
6
.idea/.idea.TweetDuck/.idea/encodings.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
|
||||||
<file url="PROJECT" charset="UTF-8" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
8
.idea/.idea.TweetDuck/.idea/indexLayout.xml
generated
8
.idea/.idea.TweetDuck/.idea/indexLayout.xml
generated
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="UserContentModel">
|
|
||||||
<attachedFolders />
|
|
||||||
<explicitIncludes />
|
|
||||||
<explicitExcludes />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@@ -1,770 +0,0 @@
|
|||||||
<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>
|
|
@@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<settings>
|
|
||||||
<option name="PROJECT_PROFILE" value="Project" />
|
|
||||||
<version value="1.0" />
|
|
||||||
</settings>
|
|
||||||
</component>
|
|
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="JavaScriptLibraryMappings">
|
|
||||||
<file url="PROJECT" libraries="{@types/jquery}" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@@ -1,20 +0,0 @@
|
|||||||
<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
11
.idea/.idea.TweetDuck/.idea/vcs.xml
generated
@@ -1,11 +0,0 @@
|
|||||||
<?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>
|
|
@@ -1,43 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,155 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
|
||||||
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.Features.Twitter;
|
|
||||||
using TweetLib.Core.Systems.Configuration;
|
|
||||||
|
|
||||||
namespace TweetDuck.Application {
|
|
||||||
sealed class SystemHandler : IAppSystemHandler {
|
|
||||||
public void OpenBrowser(string url) {
|
|
||||||
if (string.IsNullOrWhiteSpace(url)) {
|
|
||||||
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,
|
|
||||||
ErrorDialog = true
|
|
||||||
})) {}
|
|
||||||
} catch (Exception 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FormManager.RunOnUIThreadAsync(PerformSearch);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,108 +0,0 @@
|
|||||||
using System;
|
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.WinForms;
|
|
||||||
using TweetDuck.Browser.Handling;
|
|
||||||
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 {
|
|
||||||
internal abstract class CefBrowserComponent : IBrowserComponent {
|
|
||||||
public bool Ready { get; private set; }
|
|
||||||
|
|
||||||
public string Url => browser.Address;
|
|
||||||
|
|
||||||
public IFileDownloader FileDownloader => TwitterFileDownloader.Instance;
|
|
||||||
|
|
||||||
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 RunFunction(string name, params object[] args) {
|
|
||||||
browser.BrowserCore.ExecuteScriptAsync(name, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunScript(string identifier, string script) {
|
|
||||||
using IFrame frame = browser.GetMainFrame();
|
|
||||||
frame.ExecuteJavaScriptAsync(script, identifier, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,28 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using CefSharp;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
|
||||||
internal 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,80 +0,0 @@
|
|||||||
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 {
|
|
||||||
internal 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,23 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,42 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text;
|
|
||||||
using CefSharp;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
|
||||||
internal 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 _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,77 +0,0 @@
|
|||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
using System;
|
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.WinForms;
|
|
||||||
using TweetLib.Browser.Interfaces;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
|
||||||
internal 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,38 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetLib.Browser.Interfaces;
|
|
||||||
using TweetLib.Browser.Request;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Adapters {
|
|
||||||
internal 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,608 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Browser.Handling;
|
|
||||||
using TweetDuck.Browser.Notification;
|
|
||||||
using TweetDuck.Browser.Notification.Screenshot;
|
|
||||||
using TweetDuck.Configuration;
|
|
||||||
using TweetDuck.Controls;
|
|
||||||
using TweetDuck.Dialogs;
|
|
||||||
using TweetDuck.Dialogs.Settings;
|
|
||||||
using TweetDuck.Management;
|
|
||||||
using TweetDuck.Updates;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Core;
|
|
||||||
using TweetLib.Core.Features.Notifications;
|
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
using TweetLib.Core.Features.TweetDeck;
|
|
||||||
using TweetLib.Core.Resources;
|
|
||||||
using TweetLib.Core.Systems.Configuration;
|
|
||||||
using TweetLib.Core.Systems.Updates;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser {
|
|
||||||
sealed partial class FormBrowser : Form, CustomKeyboardHandler.IBrowserKeyHandler {
|
|
||||||
private static UserConfig Config => Program.Config.User;
|
|
||||||
|
|
||||||
public bool IsWaiting {
|
|
||||||
set {
|
|
||||||
if (value) {
|
|
||||||
browser.Enabled = false;
|
|
||||||
Cursor = Cursors.WaitCursor;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
browser.Enabled = true;
|
|
||||||
Cursor = Cursors.Default;
|
|
||||||
|
|
||||||
if (Focused) { // re-focus browser only if the window or a child is activated
|
|
||||||
browser.Focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UpdateInstaller UpdateInstaller { get; private set; }
|
|
||||||
|
|
||||||
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
|
||||||
private readonly TweetDeckBrowser browser;
|
|
||||||
private readonly FormNotificationTweet notification;
|
|
||||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
|
||||||
|
|
||||||
private readonly ResourceCache resourceCache;
|
|
||||||
private readonly ITweetDeckInterface tweetDeckInterface;
|
|
||||||
private readonly PluginManager plugins;
|
|
||||||
private readonly UpdateChecker updates;
|
|
||||||
private readonly ContextMenu contextMenu;
|
|
||||||
private readonly uint windowRestoreMessage;
|
|
||||||
|
|
||||||
private bool isLoaded;
|
|
||||||
private FormWindowState prevState;
|
|
||||||
|
|
||||||
private TweetScreenshotManager notificationScreenshotManager;
|
|
||||||
private VideoPlayer videoPlayer;
|
|
||||||
|
|
||||||
public FormBrowser(ResourceCache resourceCache, PluginManager pluginManager, IUpdateCheckClient updateCheckClient, uint windowRestoreMessage) {
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
Text = Program.BrandName;
|
|
||||||
|
|
||||||
this.resourceCache = resourceCache;
|
|
||||||
|
|
||||||
this.plugins = pluginManager;
|
|
||||||
|
|
||||||
this.tweetDeckInterface = new TweetDeckInterfaceImpl(this);
|
|
||||||
|
|
||||||
this.notification = new FormNotificationTweet(this, tweetDeckInterface, plugins);
|
|
||||||
this.notification.Show();
|
|
||||||
|
|
||||||
this.updates = new UpdateChecker(updateCheckClient, TaskScheduler.FromCurrentSynchronizationContext());
|
|
||||||
this.updates.InteractionManager.UpdateAccepted += updateInteractionManager_UpdateAccepted;
|
|
||||||
this.updates.InteractionManager.UpdateDismissed += updateInteractionManager_UpdateDismissed;
|
|
||||||
|
|
||||||
this.browser = new TweetDeckBrowser(this, plugins, tweetDeckInterface, updates);
|
|
||||||
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
Config.MuteToggled -= Config_MuteToggled;
|
|
||||||
Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
|
|
||||||
browser.Dispose();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
|
|
||||||
this.trayIcon.ClickClose += trayIcon_ClickClose;
|
|
||||||
|
|
||||||
UpdateTray();
|
|
||||||
|
|
||||||
if (Config.MuteNotifications) {
|
|
||||||
UpdateFormIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
RestoreWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing) {
|
|
||||||
if (disposing) {
|
|
||||||
components?.Dispose();
|
|
||||||
|
|
||||||
updates.Dispose();
|
|
||||||
contextMenu.Dispose();
|
|
||||||
|
|
||||||
notificationScreenshotManager?.Dispose();
|
|
||||||
videoPlayer?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowChildForm(Form form) {
|
|
||||||
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
|
|
||||||
form.Show(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ForceClose() {
|
|
||||||
trayIcon.Visible = false; // checked in FormClosing event
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// window setup
|
|
||||||
|
|
||||||
private void RestoreWindow() {
|
|
||||||
Config.BrowserWindow.Restore(this, true);
|
|
||||||
browser.PrepareSize(ClientSize);
|
|
||||||
|
|
||||||
prevState = WindowState;
|
|
||||||
isLoaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateFormIcon() { // TODO fix to show icon in taskbar too
|
|
||||||
Icon = Config.MuteNotifications ? Properties.Resources.icon_muted : Properties.Resources.icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateTray() {
|
|
||||||
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
// event handlers
|
|
||||||
|
|
||||||
private void timerResize_Tick(object sender, EventArgs e) {
|
|
||||||
FormBrowser_ResizeEnd(this, e); // also stops timer
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FormBrowser_Activated(object sender, EventArgs e) {
|
|
||||||
if (!isLoaded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
trayIcon.HasNotifications = false;
|
|
||||||
|
|
||||||
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
|
|
||||||
} // the window, enable the browser again
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FormBrowser_LocationChanged(object sender, EventArgs e) {
|
|
||||||
if (!isLoaded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
timerResize.Stop();
|
|
||||||
timerResize.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FormBrowser_Resize(object sender, EventArgs e) {
|
|
||||||
if (!isLoaded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WindowState != prevState) {
|
|
||||||
prevState = WindowState;
|
|
||||||
|
|
||||||
if (WindowState == FormWindowState.Minimized) {
|
|
||||||
if (Config.TrayBehavior.ShouldHideOnMinimize()) {
|
|
||||||
Hide(); // hides taskbar too?! welp that works I guess
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
FormBrowser_ResizeEnd(sender, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
timerResize.Stop();
|
|
||||||
timerResize.Start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FormBrowser_ResizeEnd(object sender, EventArgs e) { // also triggers when the window moves
|
|
||||||
if (!isLoaded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
timerResize.Stop();
|
|
||||||
browser.PrepareSize(ClientSize); // needed to pre-size browser control when launched in maximized state
|
|
||||||
|
|
||||||
if (Location != ControlExtensions.InvisibleLocation) {
|
|
||||||
Config.BrowserWindow.Save(this);
|
|
||||||
Config.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e) {
|
|
||||||
if (!isLoaded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config.TrayBehavior.ShouldHideOnClose() && trayIcon.Visible && e.CloseReason == CloseReason.UserClosing) {
|
|
||||||
Hide(); // hides taskbar too?! welp that works I guess
|
|
||||||
e.Cancel = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e) {
|
|
||||||
if (isLoaded && UpdateInstaller == null) {
|
|
||||||
updates.InteractionManager.ClearUpdate();
|
|
||||||
updates.InteractionManager.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Config_MuteToggled(object sender, EventArgs e) {
|
|
||||||
UpdateFormIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Config_TrayBehaviorChanged(object sender, EventArgs e) {
|
|
||||||
UpdateTray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void trayIcon_ClickRestore(object sender, EventArgs e) {
|
|
||||||
Show();
|
|
||||||
RestoreWindow();
|
|
||||||
Activate();
|
|
||||||
UpdateTray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void trayIcon_ClickClose(object sender, EventArgs e) {
|
|
||||||
ForceClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateInteractionManager_UpdateAccepted(object sender, UpdateInfo update) {
|
|
||||||
this.InvokeAsyncSafe(() => {
|
|
||||||
FormManager.CloseAllDialogs();
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Config.DismissedUpdate)) {
|
|
||||||
Config.DismissedUpdate = null;
|
|
||||||
Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnFinished() {
|
|
||||||
UpdateDownloadStatus status = update.DownloadStatus;
|
|
||||||
|
|
||||||
if (status == UpdateDownloadStatus.Done) {
|
|
||||||
UpdateInstaller = new UpdateInstaller(update.InstallerPath);
|
|
||||||
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)) {
|
|
||||||
App.SystemHandler.OpenBrowser(Program.Website);
|
|
||||||
ForceClose();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update.DownloadStatus.IsFinished(true)) {
|
|
||||||
OnFinished();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
FormUpdateDownload downloadForm = new FormUpdateDownload(update);
|
|
||||||
|
|
||||||
downloadForm.VisibleChanged += (sender2, args2) => {
|
|
||||||
downloadForm.MoveToCenter(this);
|
|
||||||
Hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
downloadForm.FormClosed += (sender2, args2) => {
|
|
||||||
if (downloadForm.DialogResult != DialogResult.OK) {
|
|
||||||
update.CancelDownload();
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadForm.Dispose();
|
|
||||||
OnFinished();
|
|
||||||
};
|
|
||||||
|
|
||||||
downloadForm.Show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateInteractionManager_UpdateDismissed(object sender, UpdateInfo update) {
|
|
||||||
this.InvokeAsyncSafe(() => {
|
|
||||||
Config.DismissedUpdate = update.VersionTag;
|
|
||||||
Config.Save();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void WndProc(ref Message m) {
|
|
||||||
if (isLoaded && m.Msg == windowRestoreMessage) {
|
|
||||||
using Process me = Process.GetCurrentProcess();
|
|
||||||
|
|
||||||
if (me.Id == m.WParam.ToInt32()) {
|
|
||||||
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN) {
|
|
||||||
if (videoPlayer is { Running: true }) {
|
|
||||||
videoPlayer.Close();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
browser.Functions.OnMouseClickExtra((m.WParam.ToInt32() >> 16) & 0xFFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
base.WndProc(ref m);
|
|
||||||
}
|
|
||||||
|
|
||||||
// bridge methods
|
|
||||||
|
|
||||||
public void PauseNotification() {
|
|
||||||
notification.PauseNotification();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResumeNotification() {
|
|
||||||
notification.ResumeNotification();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReloadToTweetDeck() {
|
|
||||||
#if DEBUG
|
|
||||||
Resources.ResourceHotSwap.Run();
|
|
||||||
resourceCache.ClearCache();
|
|
||||||
#else
|
|
||||||
if (ModifierKeys.HasFlag(Keys.Shift)) {
|
|
||||||
resourceCache.ClearCache();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
browser.ReloadToTweetDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenDevTools() {
|
|
||||||
browser.OpenDevTools();
|
|
||||||
}
|
|
||||||
|
|
||||||
// callback handlers
|
|
||||||
|
|
||||||
private void OnIntroductionClosed(bool showGuide) {
|
|
||||||
if (Config.FirstRun) {
|
|
||||||
Config.FirstRun = false;
|
|
||||||
Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showGuide) {
|
|
||||||
FormGuide.Show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OpenContextMenu() {
|
|
||||||
contextMenu.Show(this, PointToClient(Cursor.Position));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenSettings() {
|
|
||||||
OpenSettings(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenSettings(Type startTab) {
|
|
||||||
if (!FormManager.TryBringToFront<FormSettings>()) {
|
|
||||||
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
|
||||||
|
|
||||||
FormSettings form = new FormSettings(this, plugins, updates, browser.Functions, startTab);
|
|
||||||
|
|
||||||
form.FormClosed += (sender, args) => {
|
|
||||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck) {
|
|
||||||
Config.DismissedUpdate = null;
|
|
||||||
Config.Save();
|
|
||||||
|
|
||||||
updates.Check(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Config.EnableTrayHighlight) {
|
|
||||||
trayIcon.HasNotifications = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
BrowserCache.RefreshTimer();
|
|
||||||
|
|
||||||
if (form.ShouldReloadBrowser) {
|
|
||||||
FormManager.TryFind<FormPlugins>()?.Close();
|
|
||||||
plugins.Reload(); // also reloads the browser
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Program.Config.User.TriggerOptionsDialogClosed();
|
|
||||||
}
|
|
||||||
|
|
||||||
notification.RequiresResize = true;
|
|
||||||
form.Dispose();
|
|
||||||
};
|
|
||||||
|
|
||||||
ShowChildForm(form);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenAbout() {
|
|
||||||
if (!FormManager.TryBringToFront<FormAbout>()) {
|
|
||||||
ShowChildForm(new FormAbout());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenPlugins() {
|
|
||||||
if (!FormManager.TryBringToFront<FormPlugins>()) {
|
|
||||||
ShowChildForm(new FormPlugins(plugins));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OpenProfileImport() {
|
|
||||||
FormManager.TryFind<FormSettings>()?.Close();
|
|
||||||
|
|
||||||
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
|
|
||||||
BrowserProcessHandler.UpdatePrefs();
|
|
||||||
FormManager.TryFind<FormPlugins>()?.Close();
|
|
||||||
plugins.Reload(); // also reloads the browser
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowDesktopNotification(DesktopNotification notification) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveVideo(string url, string username) {
|
|
||||||
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://");
|
|
||||||
}
|
|
||||||
|
|
||||||
string playerPath = Config.VideoPlayerPath;
|
|
||||||
|
|
||||||
if (playerPath == null || !File.Exists(playerPath)) {
|
|
||||||
if (videoPlayer == null) {
|
|
||||||
videoPlayer = new VideoPlayer(this);
|
|
||||||
videoPlayer.ProcessExited += (sender, args) => browser.HideVideoOverlay(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
callShowOverlay.ExecuteAsync();
|
|
||||||
callShowOverlay.Dispose();
|
|
||||||
|
|
||||||
videoPlayer.Launch(videoUrl, tweetUrl, username);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
callShowOverlay.Dispose();
|
|
||||||
|
|
||||||
string quotedUrl = '"' + videoUrl + '"';
|
|
||||||
string playerArgs = Config.VideoPlayerPathArgs == null ? quotedUrl : Config.VideoPlayerPathArgs + ' ' + quotedUrl;
|
|
||||||
|
|
||||||
try {
|
|
||||||
using (Process.Start(playerPath, playerArgs)) {}
|
|
||||||
} catch (Exception e) {
|
|
||||||
App.ErrorHandler.HandleException("Error Opening Video Player", "Could not open the video player.", true, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StopVideo() {
|
|
||||||
videoPlayer?.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowTweetDetail(string columnId, string chirpId, string fallbackUrl) {
|
|
||||||
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);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class TweetDeckInterfaceImpl : ITweetDeckInterface {
|
|
||||||
private readonly FormBrowser form;
|
|
||||||
|
|
||||||
public TweetDeckInterfaceImpl(FormBrowser form) {
|
|
||||||
this.form = form;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 DisplayTooltip(string text) {
|
|
||||||
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FixClipboard() {
|
|
||||||
form.InvokeAsyncSafe(ClipboardManager.StripHtmlStyles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetIdleSeconds() {
|
|
||||||
return NativeMethods.GetIdleSeconds();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnIntroductionClosed(bool showGuide) {
|
|
||||||
form.InvokeAsyncSafe(() => form.OnIntroductionClosed(showGuide));
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,28 +0,0 @@
|
|||||||
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() {}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,109 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Browser.Adapters;
|
|
||||||
using TweetDuck.Configuration;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Browser.Contexts;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
abstract class ContextMenuBase : IContextMenuHandler {
|
|
||||||
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;
|
|
||||||
|
|
||||||
private readonly TweetLib.Browser.Interfaces.IContextMenuHandler handler;
|
|
||||||
private readonly CefContextMenuActionRegistry actionRegistry;
|
|
||||||
|
|
||||||
protected ContextMenuBase(TweetLib.Browser.Interfaces.IContextMenuHandler handler) {
|
|
||||||
this.handler = handler;
|
|
||||||
this.actionRegistry = new CefContextMenuActionRegistry();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual Context CreateContext(IContextMenuParams parameters) {
|
|
||||||
return CefContextMenuModel.CreateContext(parameters, null, Config.TwitterImageQuality);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
|
||||||
for (int i = model.Count - 1; i >= 0; i--) {
|
|
||||||
CefMenuCommand command = model.GetCommandIdAt(i);
|
|
||||||
|
|
||||||
if (!AllowedCefCommands.Contains(command) && !(command >= CefMenuCommand.CustomFirst && command <= CefMenuCommand.CustomLast)) {
|
|
||||||
model.RemoveAt(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = model.Count - 2; i >= 0; i--) {
|
|
||||||
if (model.GetTypeAt(i) == MenuItemType.Separator && model.GetTypeAt(i + 1) == MenuItemType.Separator) {
|
|
||||||
model.RemoveAt(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (model.Count > 0 && model.GetTypeAt(0) == MenuItemType.Separator) {
|
|
||||||
model.RemoveAt(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddSeparator(model);
|
|
||||||
handler.Show(new CefContextMenuModel(model, actionRegistry), CreateContext(parameters));
|
|
||||||
RemoveSeparatorIfLast(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
|
||||||
if (actionRegistry.Execute(commandId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commandId == MenuOpenDevTools) {
|
|
||||||
browserControl.OpenDevToolsCustom(new Point(parameters.XCoord, parameters.YCoord));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
|
||||||
actionRegistry.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void AddDebugMenuItems(IMenuModel model) {
|
|
||||||
if (Config.DevToolsInContextMenu) {
|
|
||||||
AddSeparator(model);
|
|
||||||
model.AddItem(MenuOpenDevTools, "Open dev tools");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void RemoveSeparatorIfLast(IMenuModel model) {
|
|
||||||
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) == MenuItemType.Separator) {
|
|
||||||
model.RemoveAt(model.Count - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
model.AddSeparator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,123 +0,0 @@
|
|||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Browser.Adapters;
|
|
||||||
using TweetDuck.Controls;
|
|
||||||
using TweetLib.Browser.Contexts;
|
|
||||||
using TweetLib.Core.Features.TweetDeck;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
using TweetLib.Core.Systems.Configuration;
|
|
||||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class ContextMenuBrowser : ContextMenuBase {
|
|
||||||
private const CefMenuCommand MenuGlobal = (CefMenuCommand) 26600;
|
|
||||||
private const CefMenuCommand MenuMute = (CefMenuCommand) 26601;
|
|
||||||
private const CefMenuCommand MenuSettings = (CefMenuCommand) 26602;
|
|
||||||
private const CefMenuCommand MenuPlugins = (CefMenuCommand) 26003;
|
|
||||||
private const CefMenuCommand MenuAbout = (CefMenuCommand) 26604;
|
|
||||||
|
|
||||||
private const string TitleReloadBrowser = "Reload browser";
|
|
||||||
private const string TitleMuteNotifications = "Mute notifications";
|
|
||||||
private const string TitleSettings = "Options";
|
|
||||||
private const string TitlePlugins = "Plugins";
|
|
||||||
private const string TitleAboutProgram = "About " + Program.BrandName;
|
|
||||||
|
|
||||||
private readonly FormBrowser form;
|
|
||||||
private readonly TweetDeckExtraContext extraContext;
|
|
||||||
|
|
||||||
public ContextMenuBrowser(FormBrowser form, IContextMenuHandler handler, TweetDeckExtraContext extraContext) : base(handler) {
|
|
||||||
this.form = form;
|
|
||||||
this.extraContext = extraContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Context CreateContext(IContextMenuParams parameters) {
|
|
||||||
return CefContextMenuModel.CreateContext(parameters, extraContext, Config.TwitterImageQuality);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection);
|
|
||||||
bool isEditing = parameters.TypeFlags.HasFlag(ContextMenuType.Editable);
|
|
||||||
|
|
||||||
if (!isSelecting && !isEditing) {
|
|
||||||
AddSeparator(model);
|
|
||||||
|
|
||||||
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
|
|
||||||
|
|
||||||
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
|
|
||||||
globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
|
|
||||||
globalMenu.SetChecked(MenuMute, Config.MuteNotifications);
|
|
||||||
globalMenu.AddSeparator();
|
|
||||||
|
|
||||||
globalMenu.AddItem(MenuSettings, TitleSettings);
|
|
||||||
globalMenu.AddItem(MenuPlugins, TitlePlugins);
|
|
||||||
globalMenu.AddItem(MenuAbout, TitleAboutProgram);
|
|
||||||
|
|
||||||
AddDebugMenuItems(globalMenu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (commandId) {
|
|
||||||
case CefMenuCommand.Reload:
|
|
||||||
form.InvokeAsyncSafe(form.ReloadToTweetDeck);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuSettings:
|
|
||||||
form.InvokeAsyncSafe(form.OpenSettings);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuAbout:
|
|
||||||
form.InvokeAsyncSafe(form.OpenAbout);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuPlugins:
|
|
||||||
form.InvokeAsyncSafe(form.OpenPlugins);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MenuMute:
|
|
||||||
form.InvokeAsyncSafe(ToggleMuteNotifications);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
menu.MenuItems.Add(TitleReloadBrowser, (sender, args) => form.ReloadToTweetDeck());
|
|
||||||
menu.MenuItems.Add(TitleMuteNotifications, (sender, args) => ToggleMuteNotifications());
|
|
||||||
menu.MenuItems.Add("-");
|
|
||||||
menu.MenuItems.Add(TitleSettings, (sender, args) => form.OpenSettings());
|
|
||||||
menu.MenuItems.Add(TitlePlugins, (sender, args) => form.OpenPlugins());
|
|
||||||
menu.MenuItems.Add(TitleAboutProgram, (sender, args) => form.OpenAbout());
|
|
||||||
|
|
||||||
menu.Popup += (sender, args) => {
|
|
||||||
menu.MenuItems[1].Checked = Config.MuteNotifications;
|
|
||||||
};
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ToggleMuteNotifications() {
|
|
||||||
Config.MuteNotifications = !Config.MuteNotifications;
|
|
||||||
Config.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class ContextMenuGuide : ContextMenuBase {
|
|
||||||
public ContextMenuGuide(IContextMenuHandler handler) : base(handler) {}
|
|
||||||
|
|
||||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
|
||||||
AddDebugMenuItems(model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,32 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using TweetDuck.Browser.Notification;
|
|
||||||
using TweetDuck.Controls;
|
|
||||||
using TweetLib.Browser.Contexts;
|
|
||||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class ContextMenuNotification : ContextMenuBase {
|
|
||||||
private readonly FormNotificationBase form;
|
|
||||||
|
|
||||||
public ContextMenuNotification(FormNotificationBase form, IContextMenuHandler handler) : base(handler) {
|
|
||||||
this.form = form;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Context CreateContext(IContextMenuParams parameters) {
|
|
||||||
Context context = base.CreateContext(parameters);
|
|
||||||
context.Notification = new TweetLib.Browser.Contexts.Notification(form.CurrentTweetUrl, form.CurrentQuoteUrl);
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
|
||||||
AddDebugMenuItems(model);
|
|
||||||
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
|
||||||
base.OnContextMenuDismissed(browserControl, browser, frame);
|
|
||||||
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,43 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,37 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,35 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.Enums;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class DragHandlerBrowser : IDragHandler {
|
|
||||||
private readonly RequestHandlerBrowser requestHandler;
|
|
||||||
|
|
||||||
public DragHandlerBrowser(RequestHandlerBrowser requestHandler) {
|
|
||||||
this.requestHandler = requestHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask) {
|
|
||||||
void TriggerDragStart(string type, string data = null) {
|
|
||||||
browserControl.BrowserCore.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
|
|
||||||
|
|
||||||
if (dragData.IsLink) {
|
|
||||||
TriggerDragStart("link", dragData.LinkUrl);
|
|
||||||
}
|
|
||||||
else if (dragData.IsFragment) {
|
|
||||||
TriggerDragStart("text", dragData.FragmentText.Trim());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
TriggerDragStart("unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IFrame frame, IList<DraggableRegion> regions) {}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,68 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,96 +0,0 @@
|
|||||||
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) {}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,22 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using CefSharp.Handler;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
class RequestHandlerBase : RequestHandler {
|
|
||||||
private readonly bool autoReload;
|
|
||||||
|
|
||||||
public RequestHandlerBase(bool autoReload) {
|
|
||||||
this.autoReload = autoReload;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) {
|
|
||||||
return CustomLifeSpanHandler.HandleLinkClick(targetDisposition, targetUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status) {
|
|
||||||
if (autoReload) {
|
|
||||||
browser.Reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,23 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class RequestHandlerBrowser : RequestHandlerBase {
|
|
||||||
public string BlockNextUserNavUrl { get; set; }
|
|
||||||
|
|
||||||
public RequestHandlerBrowser() : base(true) {}
|
|
||||||
|
|
||||||
protected override bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect) {
|
|
||||||
if (userGesture && request.TransitionType == TransitionType.LinkClicked) {
|
|
||||||
bool block = request.Url == BlockNextUserNavUrl;
|
|
||||||
BlockNextUserNavUrl = string.Empty;
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
else if (request.TransitionType.HasFlag(TransitionType.ForwardBack) && TwitterUrls.IsTweetDeck(frame.Url)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,79 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.Callback;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
|
||||||
sealed class ResourceHandlerNotification : IResourceHandler {
|
|
||||||
private readonly NameValueCollection headers = new NameValueCollection(0);
|
|
||||||
private MemoryStream dataIn;
|
|
||||||
|
|
||||||
public void SetHTML(string html) {
|
|
||||||
dataIn?.Dispose();
|
|
||||||
dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
if (dataIn != null) {
|
|
||||||
dataIn.Dispose();
|
|
||||||
dataIn = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IResourceHandler.Open(IRequest request, out bool handleRequest, ICallback callback) {
|
|
||||||
callback.Dispose();
|
|
||||||
handleRequest = true;
|
|
||||||
|
|
||||||
if (dataIn != null) {
|
|
||||||
dataIn.Position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl) {
|
|
||||||
redirectUrl = null;
|
|
||||||
|
|
||||||
response.MimeType = "text/html";
|
|
||||||
response.StatusCode = 200;
|
|
||||||
response.StatusText = "OK";
|
|
||||||
response.Headers = headers;
|
|
||||||
responseLength = dataIn?.Length ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IResourceHandler.Read(Stream dataOut, out int bytesRead, IResourceReadCallback callback) {
|
|
||||||
callback?.Dispose(); // TODO unnecessary null check once ReadResponse is removed
|
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] buffer = new byte[Math.Min(dataIn.Length - dataIn.Position, dataOut.Length)];
|
|
||||||
int length = buffer.Length;
|
|
||||||
|
|
||||||
dataIn.Read(buffer, 0, length);
|
|
||||||
dataOut.Write(buffer, 0, length);
|
|
||||||
bytesRead = length;
|
|
||||||
} catch { // catch IOException, possibly NullReferenceException if dataIn is null
|
|
||||||
bytesRead = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytesRead > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IResourceHandler.Skip(long bytesToSkip, out long bytesSkipped, IResourceSkipCallback callback) {
|
|
||||||
bytesSkipped = -2; // ERR_FAILED
|
|
||||||
callback.Dispose();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback) {
|
|
||||||
return ((IResourceHandler) this).Open(request, out bool _, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback) {
|
|
||||||
return ((IResourceHandler) this).Read(dataOut, out bytesRead, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IResourceHandler.Cancel() {}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,76 +0,0 @@
|
|||||||
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() {}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,251 +0,0 @@
|
|||||||
using System.Drawing;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp.WinForms;
|
|
||||||
using TweetDuck.Browser.Adapters;
|
|
||||||
using TweetDuck.Browser.Handling;
|
|
||||||
using TweetDuck.Configuration;
|
|
||||||
using TweetDuck.Controls;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Browser.Interfaces;
|
|
||||||
using TweetLib.Core.Features.Notifications;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
using TweetLib.Core.Systems.Configuration;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification {
|
|
||||||
abstract partial class FormNotificationBase : Form {
|
|
||||||
protected static UserConfig Config => Program.Config.User;
|
|
||||||
|
|
||||||
protected delegate NotificationBrowser CreateBrowserImplFunc(FormNotificationBase form, IBrowserComponent browserComponent);
|
|
||||||
|
|
||||||
protected virtual Point PrimaryLocation {
|
|
||||||
get {
|
|
||||||
Screen screen;
|
|
||||||
|
|
||||||
if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length) {
|
|
||||||
screen = Screen.AllScreens[Config.NotificationDisplay - 1];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
screen = Screen.FromControl(owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
int edgeDist = Config.NotificationEdgeDistance;
|
|
||||||
|
|
||||||
switch (Config.NotificationPosition) {
|
|
||||||
case DesktopNotification.Position.TopLeft:
|
|
||||||
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + edgeDist);
|
|
||||||
|
|
||||||
case DesktopNotification.Position.TopRight:
|
|
||||||
return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
|
|
||||||
|
|
||||||
case DesktopNotification.Position.BottomLeft:
|
|
||||||
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height);
|
|
||||||
|
|
||||||
case DesktopNotification.Position.BottomRight:
|
|
||||||
return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height);
|
|
||||||
|
|
||||||
case DesktopNotification.Position.Custom:
|
|
||||||
if (!Config.IsCustomNotificationPositionSet) {
|
|
||||||
Config.CustomNotificationPosition = new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
|
|
||||||
Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Config.CustomNotificationPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Location;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
|
|
||||||
protected virtual bool CanDragWindow => true;
|
|
||||||
|
|
||||||
public new Point Location {
|
|
||||||
get {
|
|
||||||
return base.Location;
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
|
|
||||||
FormBorderStyle = NotificationBorderStyle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual FormBorderStyle NotificationBorderStyle {
|
|
||||||
get {
|
|
||||||
if (WindowsUtils.ShouldAvoidToolWindow && Visible) { // Visible = workaround for alt+tab
|
|
||||||
return FormBorderStyle.FixedSingle;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return FormBorderStyle.FixedToolWindow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool ShowWithoutActivation => true;
|
|
||||||
|
|
||||||
protected float DpiScale { get; }
|
|
||||||
protected double SizeScale => DpiScale * Config.ZoomLevel / 100.0;
|
|
||||||
|
|
||||||
private readonly FormBrowser owner;
|
|
||||||
|
|
||||||
protected readonly IBrowserComponent browserComponent;
|
|
||||||
private readonly NotificationBrowser browserImpl;
|
|
||||||
|
|
||||||
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
|
||||||
protected readonly ChromiumWebBrowser browser;
|
|
||||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
|
||||||
|
|
||||||
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
|
|
||||||
|
|
||||||
private DesktopNotification currentNotification;
|
|
||||||
private int pauseCounter;
|
|
||||||
|
|
||||||
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
|
||||||
public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
|
|
||||||
|
|
||||||
protected bool IsPaused => pauseCounter > 0;
|
|
||||||
protected internal bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
|
|
||||||
|
|
||||||
public bool FreezeTimer { get; set; }
|
|
||||||
public bool ContextMenuOpen { get; set; }
|
|
||||||
|
|
||||||
protected FormNotificationBase(FormBrowser owner, CreateBrowserImplFunc createBrowserImpl) {
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
this.owner = owner;
|
|
||||||
this.owner.FormClosed += owner_FormClosed;
|
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser(NotificationBrowser.BlankURL) {
|
|
||||||
RequestHandler = new RequestHandlerBase(false)
|
|
||||||
};
|
|
||||||
|
|
||||||
this.browserComponent = new ComponentImpl(browser, this);
|
|
||||||
this.browserImpl = createBrowserImpl(this, browserComponent);
|
|
||||||
|
|
||||||
this.browser.Dock = DockStyle.None;
|
|
||||||
this.browser.ClientSize = ClientSize;
|
|
||||||
|
|
||||||
Controls.Add(browser);
|
|
||||||
|
|
||||||
Disposed += (sender, args) => {
|
|
||||||
this.owner.FormClosed -= owner_FormClosed;
|
|
||||||
this.browserImpl.Dispose();
|
|
||||||
this.browser.Dispose();
|
|
||||||
};
|
|
||||||
|
|
||||||
DpiScale = this.GetDPIScale();
|
|
||||||
|
|
||||||
// ReSharper disable once VirtualMemberCallInContructor
|
|
||||||
UpdateTitle();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected sealed class ComponentImpl : CefBrowserComponent {
|
|
||||||
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();
|
|
||||||
resourceHandler.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void WndProc(ref Message m) {
|
|
||||||
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow) { // WM_SYSCOMMAND, SC_MOVE
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
base.WndProc(ref m);
|
|
||||||
}
|
|
||||||
|
|
||||||
// event handlers
|
|
||||||
|
|
||||||
private void owner_FormClosed(object sender, FormClosedEventArgs e) {
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// notification methods
|
|
||||||
|
|
||||||
public virtual void HideNotification() {
|
|
||||||
browser.Load(NotificationBrowser.BlankURL);
|
|
||||||
DisplayTooltip(null);
|
|
||||||
|
|
||||||
Location = ControlExtensions.InvisibleLocation;
|
|
||||||
currentNotification = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void FinishCurrentNotification() {}
|
|
||||||
|
|
||||||
public virtual void PauseNotification() {
|
|
||||||
if (pauseCounter++ == 0 && IsNotificationVisible) {
|
|
||||||
Location = ControlExtensions.InvisibleLocation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void ResumeNotification() {
|
|
||||||
if (pauseCounter > 0) {
|
|
||||||
--pauseCounter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void LoadTweet(DesktopNotification tweet) {
|
|
||||||
currentNotification = tweet;
|
|
||||||
resourceHandler.SetHTML(browserImpl.GetTweetHTML(tweet));
|
|
||||||
|
|
||||||
browser.Load(TwitterUrls.TweetDeck);
|
|
||||||
DisplayTooltip(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void SetNotificationSize(int width, int height) {
|
|
||||||
browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void UpdateTitle() {
|
|
||||||
string title = currentNotification?.ColumnTitle;
|
|
||||||
Text = string.IsNullOrEmpty(title) || !Config.DisplayNotificationColumn ? Program.BrandName : $"{Program.BrandName} - {title}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowTweetDetail() {
|
|
||||||
if (currentNotification != null && owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl)) {
|
|
||||||
FinishCurrentNotification();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MoveToVisibleLocation() {
|
|
||||||
bool needsReactivating = Location == ControlExtensions.InvisibleLocation;
|
|
||||||
Location = PrimaryLocation;
|
|
||||||
|
|
||||||
if (needsReactivating) {
|
|
||||||
NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisplayTooltip(string text) {
|
|
||||||
if (string.IsNullOrEmpty(text)) {
|
|
||||||
toolTip.Hide(this);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Point position = PointToClient(Cursor.Position);
|
|
||||||
position.Offset(20, 5);
|
|
||||||
toolTip.Show(text, this, position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,62 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,318 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetDuck.Browser.Handling;
|
|
||||||
using TweetDuck.Controls;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Core.Features.Notifications;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification {
|
|
||||||
abstract partial class FormNotificationMain : FormNotificationBase, CustomKeyboardHandler.IBrowserKeyHandler {
|
|
||||||
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;
|
|
||||||
|
|
||||||
protected int timeLeft, totalTime;
|
|
||||||
protected bool pausedDuringNotification;
|
|
||||||
|
|
||||||
private readonly NativeMethods.HookProc mouseHookDelegate;
|
|
||||||
private IntPtr mouseHook;
|
|
||||||
private bool blockXButtonUp;
|
|
||||||
|
|
||||||
private int currentOpacity;
|
|
||||||
|
|
||||||
private bool? prevDisplayTimer;
|
|
||||||
private int? prevFontSize;
|
|
||||||
|
|
||||||
public virtual bool RequiresResize {
|
|
||||||
get {
|
|
||||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
if (value) {
|
|
||||||
prevDisplayTimer = null;
|
|
||||||
prevFontSize = null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
prevDisplayTimer = Config.DisplayNotificationTimer;
|
|
||||||
prevFontSize = FontSizeLevel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int BaseClientWidth {
|
|
||||||
get => Config.NotificationSize switch {
|
|
||||||
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Width,
|
|
||||||
_ => BrowserUtils.Scale(284, SizeScale * (1.0 + 0.05 * FontSizeLevel))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private int BaseClientHeight {
|
|
||||||
get => Config.NotificationSize switch {
|
|
||||||
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Height,
|
|
||||||
_ => BrowserUtils.Scale(122, SizeScale * (1.0 + 0.08 * FontSizeLevel))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height - timerBarHeight) : ClientSize;
|
|
||||||
|
|
||||||
protected FormNotificationMain(FormBrowser owner, CreateBrowserImplFunc createBrowserImpl) : base(owner, createBrowserImpl) {
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
|
|
||||||
|
|
||||||
browser.KeyboardHandler = new CustomKeyboardHandler(this);
|
|
||||||
|
|
||||||
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
|
||||||
|
|
||||||
mouseHookDelegate = MouseHookProc;
|
|
||||||
Disposed += (sender, args) => StopMouseHook(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetOpacity(int opacity) {
|
|
||||||
if (currentOpacity != opacity) {
|
|
||||||
currentOpacity = opacity;
|
|
||||||
Opacity = opacity / 100.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mouse wheel hook
|
|
||||||
|
|
||||||
private void StartMouseHook() {
|
|
||||||
if (mouseHook == IntPtr.Zero) {
|
|
||||||
mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WM_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StopMouseHook(bool force) {
|
|
||||||
if (mouseHook != IntPtr.Zero && (force || !blockXButtonUp)) {
|
|
||||||
NativeMethods.UnhookWindowsHookEx(mouseHook);
|
|
||||||
mouseHook = IntPtr.Zero;
|
|
||||||
blockXButtonUp = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam) {
|
|
||||||
if (nCode == 0) {
|
|
||||||
int eventType = wParam.ToInt32();
|
|
||||||
|
|
||||||
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser) {
|
|
||||||
int delta = BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed * 0.01);
|
|
||||||
|
|
||||||
if (Config.EnableSmoothScrolling) {
|
|
||||||
browser.BrowserCore.ExecuteScriptAsync("window.TDGF_scrollSmoothly", (int) Math.Round(-delta / 0.6));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
browser.SendMouseWheelEvent(0, 0, 0, delta, CefEventFlags.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NativeMethods.HOOK_HANDLED;
|
|
||||||
}
|
|
||||||
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)) {
|
|
||||||
int extraButton = NativeMethods.GetMouseHookData(lParam);
|
|
||||||
|
|
||||||
if (extraButton == 2) { // forward button
|
|
||||||
this.InvokeAsyncSafe(FinishCurrentNotification);
|
|
||||||
}
|
|
||||||
else if (extraButton == 1) { // back button
|
|
||||||
this.InvokeAsyncSafe(Close);
|
|
||||||
}
|
|
||||||
|
|
||||||
blockXButtonUp = true;
|
|
||||||
return NativeMethods.HOOK_HANDLED;
|
|
||||||
}
|
|
||||||
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp) {
|
|
||||||
blockXButtonUp = false;
|
|
||||||
|
|
||||||
if (!Visible) {
|
|
||||||
StopMouseHook(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NativeMethods.HOOK_HANDLED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NativeMethods.CallNextHookEx(mouseHook, nCode, wParam, lParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
// event handlers
|
|
||||||
|
|
||||||
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e) {
|
|
||||||
if (e.CloseReason == CloseReason.UserClosing) {
|
|
||||||
HideNotification();
|
|
||||||
e.Cancel = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
|
||||||
if (!e.IsLoading && browser.Address != NotificationBrowser.BlankURL) {
|
|
||||||
this.InvokeSafe(() => {
|
|
||||||
Visible = true; // ensures repaint before moving the window to a visible location
|
|
||||||
timerDisplayDelay.Start();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void timerDisplayDelay_Tick(object sender, EventArgs e) {
|
|
||||||
OnNotificationReady();
|
|
||||||
timerDisplayDelay.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void timerHideProgress_Tick(object sender, EventArgs e) {
|
|
||||||
bool isCursorInside = Bounds.Contains(Cursor.Position);
|
|
||||||
|
|
||||||
if (isCursorInside) {
|
|
||||||
StartMouseHook();
|
|
||||||
SetOpacity(100);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
StopMouseHook(false);
|
|
||||||
SetOpacity(Config.NotificationWindowOpacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCursorInside || FreezeTimer || ContextMenuOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
timeLeft -= timerProgress.Interval;
|
|
||||||
|
|
||||||
int value = BrowserUtils.Scale(progressBarTimer.Maximum + 25, (totalTime - timeLeft) / (double) totalTime);
|
|
||||||
progressBarTimer.SetValueInstant(Config.NotificationTimerCountDown ? progressBarTimer.Maximum - value : value);
|
|
||||||
|
|
||||||
if (timeLeft <= 0) {
|
|
||||||
FinishCurrentNotification();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// notification methods
|
|
||||||
|
|
||||||
public virtual void ShowNotification(DesktopNotification notification) {
|
|
||||||
LoadTweet(notification);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void HideNotification() {
|
|
||||||
base.HideNotification();
|
|
||||||
|
|
||||||
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
|
||||||
timerProgress.Stop();
|
|
||||||
totalTime = 0;
|
|
||||||
|
|
||||||
StopMouseHook(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void FinishCurrentNotification() {
|
|
||||||
timerProgress.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void PauseNotification() {
|
|
||||||
if (!IsPaused) {
|
|
||||||
pausedDuringNotification = IsNotificationVisible;
|
|
||||||
timerProgress.Stop();
|
|
||||||
StopMouseHook(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
base.PauseNotification();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ResumeNotification() {
|
|
||||||
bool wasPaused = IsPaused;
|
|
||||||
base.ResumeNotification();
|
|
||||||
|
|
||||||
if (wasPaused && !IsPaused && pausedDuringNotification) {
|
|
||||||
OnNotificationReady();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadTweet(DesktopNotification tweet) {
|
|
||||||
timerProgress.Stop();
|
|
||||||
totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue);
|
|
||||||
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
|
||||||
|
|
||||||
base.LoadTweet(tweet);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void SetNotificationSize(int width, int height) {
|
|
||||||
if (Config.DisplayNotificationTimer) {
|
|
||||||
ClientSize = new Size(width, height + timerBarHeight);
|
|
||||||
progressBarTimer.Visible = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ClientSize = new Size(width, height);
|
|
||||||
progressBarTimer.Visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
browser.ClientSize = new Size(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void PrepareAndDisplayWindow() {
|
|
||||||
if (RequiresResize) {
|
|
||||||
RequiresResize = false;
|
|
||||||
SetNotificationSize(BaseClientWidth, BaseClientHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetOpacity(IsCursorOverBrowser ? 100 : Config.NotificationWindowOpacity);
|
|
||||||
MoveToVisibleLocation();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnNotificationReady() {
|
|
||||||
PrepareAndDisplayWindow();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,171 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Browser.Interfaces;
|
|
||||||
using TweetLib.Core.Features.Notifications;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private const int NonIntrusiveIdleLimit = 30;
|
|
||||||
private const int TrimMinimum = 32;
|
|
||||||
|
|
||||||
protected override Point PrimaryLocation => hasTemporarilyMoved && IsNotificationVisible ? Location : base.PrimaryLocation;
|
|
||||||
private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position);
|
|
||||||
|
|
||||||
protected override bool CanDragWindow {
|
|
||||||
get {
|
|
||||||
if (ModifierKeys.HasFlag(Keys.Alt)) {
|
|
||||||
hasTemporarilyMoved = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Queue<DesktopNotification> tweetQueue = new Queue<DesktopNotification>(4);
|
|
||||||
private bool needsTrim;
|
|
||||||
private bool hasTemporarilyMoved;
|
|
||||||
|
|
||||||
public FormNotificationTweet(FormBrowser owner, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) : base(owner, (form, browserComponent) => CreateBrowserImpl(browserComponent, new NotificationInterfaceImpl(form), tweetDeckInterface, pluginManager)) {
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
Config.MuteToggled += Config_MuteToggled;
|
|
||||||
Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
|
|
||||||
|
|
||||||
if (Config.MuteNotifications) {
|
|
||||||
PauseNotification();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void WndProc(ref Message m) {
|
|
||||||
if (m.Msg == 0x00A7) { // WM_NCMBUTTONDOWN
|
|
||||||
int hitTest = m.WParam.ToInt32();
|
|
||||||
|
|
||||||
if (hitTest == 2 || hitTest == 20) { // HTCAPTION, HTCLOSE
|
|
||||||
hasTemporarilyMoved = false;
|
|
||||||
MoveToVisibleLocation();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base.WndProc(ref m);
|
|
||||||
}
|
|
||||||
|
|
||||||
// event handlers
|
|
||||||
|
|
||||||
private void Config_MuteToggled(object sender, EventArgs e) {
|
|
||||||
if (Config.MuteNotifications) {
|
|
||||||
PauseNotification();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ResumeNotification();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void timerCursorCheck_Tick(object sender, EventArgs e) {
|
|
||||||
if (!IsCursorOverNotificationArea) {
|
|
||||||
ResumeNotification();
|
|
||||||
timerCursorCheck.Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void timerIdlePauseCheck_Tick(object sender, EventArgs e) {
|
|
||||||
if (NativeMethods.GetIdleSeconds() < Config.NotificationIdlePauseSeconds) {
|
|
||||||
ResumeNotification();
|
|
||||||
timerIdlePauseCheck.Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// notification methods
|
|
||||||
|
|
||||||
public override void ShowNotification(DesktopNotification notification) {
|
|
||||||
tweetQueue.Enqueue(notification);
|
|
||||||
|
|
||||||
if (!IsPaused) {
|
|
||||||
UpdateTitle();
|
|
||||||
|
|
||||||
if (totalTime == 0) {
|
|
||||||
LoadNextNotification();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
needsTrim |= tweetQueue.Count >= TrimMinimum;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void HideNotification() {
|
|
||||||
base.HideNotification();
|
|
||||||
tweetQueue.Clear();
|
|
||||||
|
|
||||||
if (needsTrim) {
|
|
||||||
tweetQueue.TrimExcess();
|
|
||||||
needsTrim = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasTemporarilyMoved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void FinishCurrentNotification() {
|
|
||||||
if (tweetQueue.Count > 0) {
|
|
||||||
LoadNextNotification();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
HideNotification();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ResumeNotification() {
|
|
||||||
bool wasPaused = IsPaused;
|
|
||||||
base.ResumeNotification();
|
|
||||||
|
|
||||||
if (wasPaused && !IsPaused && !pausedDuringNotification && tweetQueue.Count > 0) {
|
|
||||||
LoadNextNotification();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadNextNotification() {
|
|
||||||
if (!IsNotificationVisible) {
|
|
||||||
if (Config.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit) {
|
|
||||||
if (!timerCursorCheck.Enabled) {
|
|
||||||
PauseNotification();
|
|
||||||
timerCursorCheck.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (Config.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Config.NotificationIdlePauseSeconds) {
|
|
||||||
if (!timerIdlePauseCheck.Enabled) {
|
|
||||||
PauseNotification();
|
|
||||||
timerIdlePauseCheck.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadTweet(tweetQueue.Dequeue());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateTitle() {
|
|
||||||
base.UpdateTitle();
|
|
||||||
|
|
||||||
if (tweetQueue.Count > 0) {
|
|
||||||
Text = Text + " (" + tweetQueue.Count + " more left)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnNotificationReady() {
|
|
||||||
UpdateTitle();
|
|
||||||
base.OnNotificationReady();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,84 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.DevTools.Page;
|
|
||||||
using TweetDuck.Controls;
|
|
||||||
using TweetDuck.Dialogs;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Browser.Interfaces;
|
|
||||||
using TweetLib.Core.Features.Notifications;
|
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
using TweetLib.Core.Resources;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification.Screenshot {
|
|
||||||
sealed class FormNotificationScreenshotable : FormNotificationBase {
|
|
||||||
private static NotificationBrowser CreateBrowserImpl( IBrowserComponent browserComponent, PluginManager pluginManager) {
|
|
||||||
return new NotificationBrowser.Screenshot(browserComponent, pluginManager.NotificationInjections);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool CanDragWindow => false;
|
|
||||||
|
|
||||||
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);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string substituted = script.Replace("{width}", realWidth.ToString()).Replace("1/*FRAMES*/", TweetScreenshotManager.WaitFrames.ToString());
|
|
||||||
browserComponent.RunScript("gen:screenshot", substituted);
|
|
||||||
};
|
|
||||||
|
|
||||||
SetNotificationSize(realWidth, 1024);
|
|
||||||
LoadTweet(new DesktopNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetScreenshotHeight(int browserHeight) {
|
|
||||||
this.height = BrowserUtils.Scale(browserHeight, SizeScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<Image> TakeScreenshot(bool ignoreHeightError = false) {
|
|
||||||
if (!ignoreHeightError) {
|
|
||||||
if (height == 0) {
|
|
||||||
FormMessage.Error("Screenshot Failed", "Could not detect screenshot size.", FormMessage.OK);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else if (height > ClientSize.Height) {
|
|
||||||
FormMessage.Error("Screenshot Failed", $"Screenshot is too large: {height}px > {ClientSize.Height}px", FormMessage.OK);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.Run(TakeScreenshotImpl);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Image> TakeScreenshotImpl() {
|
|
||||||
if (this.height == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Viewport viewport = new Viewport {
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,28 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDuck.Controls;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification.Screenshot {
|
|
||||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
|
||||||
sealed class ScreenshotBridge {
|
|
||||||
private readonly Control owner;
|
|
||||||
|
|
||||||
private readonly Action<int> safeSetHeight;
|
|
||||||
private readonly Action safeTriggerScreenshot;
|
|
||||||
|
|
||||||
public ScreenshotBridge(Control owner, Action<int> safeSetHeight, Action safeTriggerScreenshot) {
|
|
||||||
this.owner = owner;
|
|
||||||
this.safeSetHeight = safeSetHeight;
|
|
||||||
this.safeTriggerScreenshot = safeTriggerScreenshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetHeight(int tweetHeight) {
|
|
||||||
owner.InvokeSafe(() => safeSetHeight(tweetHeight));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TriggerScreenshot() {
|
|
||||||
owner.InvokeSafe(safeTriggerScreenshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,164 +0,0 @@
|
|||||||
#if DEBUG
|
|
||||||
// Uncomment to keep screenshot windows visible for debugging
|
|
||||||
// #define NO_HIDE_SCREENSHOTS
|
|
||||||
|
|
||||||
// Uncomment to generate screenshots of individual frames for at most 1 second
|
|
||||||
// #define GEN_SCREENSHOT_FRAMES
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDuck.Controls;
|
|
||||||
using TweetLib.Core;
|
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
#if GEN_SCREENSHOT_FRAMES
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.IO;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification.Screenshot {
|
|
||||||
sealed class TweetScreenshotManager : IDisposable {
|
|
||||||
private readonly FormBrowser owner;
|
|
||||||
private readonly PluginManager plugins;
|
|
||||||
private readonly Timer timeout;
|
|
||||||
private readonly Timer disposer;
|
|
||||||
|
|
||||||
#if GEN_SCREENSHOT_FRAMES
|
|
||||||
private readonly Timer debugger;
|
|
||||||
private int frameCounter;
|
|
||||||
|
|
||||||
public const int WaitFrames = 60;
|
|
||||||
#else
|
|
||||||
public const int WaitFrames = 5;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private FormNotificationScreenshotable screenshot;
|
|
||||||
|
|
||||||
public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager) {
|
|
||||||
this.owner = owner;
|
|
||||||
this.plugins = pluginManager;
|
|
||||||
|
|
||||||
this.timeout = new Timer { Interval = 8000 };
|
|
||||||
this.timeout.Tick += timeout_Tick;
|
|
||||||
|
|
||||||
this.disposer = new Timer { Interval = 1 };
|
|
||||||
this.disposer.Tick += disposer_Tick;
|
|
||||||
|
|
||||||
#if GEN_SCREENSHOT_FRAMES
|
|
||||||
this.debugger = new Timer { Interval = 16 };
|
|
||||||
this.debugger.Tick += debugger_Tick;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void timeout_Tick(object sender, EventArgs e) {
|
|
||||||
timeout.Stop();
|
|
||||||
OnFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disposer_Tick(object sender, EventArgs e) {
|
|
||||||
disposer.Stop();
|
|
||||||
screenshot.Dispose();
|
|
||||||
screenshot = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Trigger(string html, int width) {
|
|
||||||
if (screenshot != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins, html, width);
|
|
||||||
screenshot.Show();
|
|
||||||
timeout.Start();
|
|
||||||
|
|
||||||
#if GEN_SCREENSHOT_FRAMES
|
|
||||||
StartDebugger();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !NO_HIDE_SCREENSHOTS
|
|
||||||
owner.IsWaiting = true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Callback() {
|
|
||||||
if (!timeout.Enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout.Stop();
|
|
||||||
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
|
|
||||||
OnFinished();
|
|
||||||
#else
|
|
||||||
screenshot.MoveToVisibleLocation();
|
|
||||||
screenshot.FormClosed += (sender, args) => disposer.Start();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFinished() {
|
|
||||||
#if GEN_SCREENSHOT_FRAMES
|
|
||||||
debugger.Stop();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
screenshot.Location = ControlExtensions.InvisibleLocation;
|
|
||||||
owner.IsWaiting = false;
|
|
||||||
disposer.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
#if GEN_SCREENSHOT_FRAMES
|
|
||||||
debugger.Dispose();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
timeout.Dispose();
|
|
||||||
disposer.Dispose();
|
|
||||||
screenshot?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if GEN_SCREENSHOT_FRAMES
|
|
||||||
private static readonly string DebugScreenshotPath = Path.Combine(Program.StoragePath, "TD_Screenshots");
|
|
||||||
|
|
||||||
private void StartDebugger() {
|
|
||||||
frameCounter = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
Directory.Delete(DebugScreenshotPath, true);
|
|
||||||
WindowsUtils.TrySleepUntil(() => !Directory.Exists(DebugScreenshotPath), 1000, 10);
|
|
||||||
} catch (DirectoryNotFoundException) {}
|
|
||||||
|
|
||||||
Directory.CreateDirectory(DebugScreenshotPath);
|
|
||||||
debugger.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void debugger_Tick(object sender, EventArgs e) {
|
|
||||||
if (frameCounter < 63) {
|
|
||||||
int frame = ++frameCounter;
|
|
||||||
screenshot.TakeScreenshot(true).ContinueWith(task => SaveDebugFrame(task, frame), TaskScheduler.FromCurrentSynchronizationContext());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,67 +0,0 @@
|
|||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDuck.Browser.Adapters;
|
|
||||||
using TweetDuck.Controls;
|
|
||||||
using TweetDuck.Dialogs;
|
|
||||||
using TweetDuck.Dialogs.Settings;
|
|
||||||
using TweetDuck.Management;
|
|
||||||
using TweetLib.Core.Features.TweetDeck;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Notification {
|
|
||||||
sealed class SoundNotification : ISoundNotificationHandler {
|
|
||||||
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
|
||||||
|
|
||||||
private readonly CefResourceHandlerRegistry registry;
|
|
||||||
|
|
||||||
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",
|
|
||||||
".webm" => "audio/webm",
|
|
||||||
".wav" => "audio/wav",
|
|
||||||
".ogg" => "audio/ogg",
|
|
||||||
".mp3" => "audio/mp3",
|
|
||||||
".flac" => "audio/flac",
|
|
||||||
".opus" => "audio/ogg; codecs=opus",
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
return (File.ReadAllBytes(path), mimeType);
|
|
||||||
} catch {
|
|
||||||
FormBrowser browser = FormManager.TryFind<FormBrowser>();
|
|
||||||
|
|
||||||
browser?.InvokeAsyncSafe(() => {
|
|
||||||
using FormMessage form = new FormMessage("Sound Notification Error", "Could not find custom notification sound file:\n" + path, MessageBoxIcon.Error);
|
|
||||||
form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
|
|
||||||
|
|
||||||
Button btnViewOptions = form.AddButton("View Options");
|
|
||||||
btnViewOptions.Width += 16;
|
|
||||||
btnViewOptions.Location = new Point(btnViewOptions.Location.X - 16, btnViewOptions.Location.Y);
|
|
||||||
|
|
||||||
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions) {
|
|
||||||
browser.OpenSettings(typeof(TabSettingsSounds));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,129 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDuck.Configuration;
|
|
||||||
using TweetLib.Core.Systems.Configuration;
|
|
||||||
using Res = TweetDuck.Properties.Resources;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser {
|
|
||||||
sealed partial class TrayIcon : Component {
|
|
||||||
public enum Behavior { // keep order
|
|
||||||
Disabled,
|
|
||||||
DisplayOnly,
|
|
||||||
MinimizeToTray,
|
|
||||||
CloseToTray,
|
|
||||||
Combined
|
|
||||||
}
|
|
||||||
|
|
||||||
private static UserConfig Config => Program.Config.User;
|
|
||||||
|
|
||||||
public event EventHandler ClickRestore;
|
|
||||||
public event EventHandler ClickClose;
|
|
||||||
|
|
||||||
public bool Visible {
|
|
||||||
get {
|
|
||||||
return notifyIcon.Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
notifyIcon.Visible = value;
|
|
||||||
hasNotifications = false;
|
|
||||||
UpdateIcon();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasNotifications {
|
|
||||||
get {
|
|
||||||
return hasNotifications;
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
if (hasNotifications != value) {
|
|
||||||
hasNotifications = value;
|
|
||||||
UpdateIcon();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ContextMenu contextMenu;
|
|
||||||
private bool hasNotifications;
|
|
||||||
|
|
||||||
public TrayIcon() {
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
this.contextMenu = new ContextMenu();
|
|
||||||
this.contextMenu.MenuItems.Add("Restore", menuItemRestore_Click);
|
|
||||||
this.contextMenu.MenuItems.Add("Mute notifications", menuItemMuteNotifications_Click);
|
|
||||||
this.contextMenu.MenuItems.Add("Close", menuItemClose_Click);
|
|
||||||
this.contextMenu.Popup += contextMenu_Popup;
|
|
||||||
|
|
||||||
this.notifyIcon.ContextMenu = contextMenu;
|
|
||||||
this.notifyIcon.Text = Program.BrandName;
|
|
||||||
|
|
||||||
Config.MuteToggled += Config_MuteToggled;
|
|
||||||
Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrayIcon(IContainer container) : this() {
|
|
||||||
container.Add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing) {
|
|
||||||
if (disposing) {
|
|
||||||
components?.Dispose();
|
|
||||||
contextMenu.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateIcon() {
|
|
||||||
if (Visible) {
|
|
||||||
notifyIcon.Icon = HasNotifications ? Res.icon_tray_new : Config.MuteNotifications ? Res.icon_tray_muted : Res.icon_tray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// event handlers
|
|
||||||
|
|
||||||
private void Config_MuteToggled(object sender, EventArgs e) {
|
|
||||||
UpdateIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void trayIcon_MouseClick(object sender, MouseEventArgs e) {
|
|
||||||
if (e.Button == MouseButtons.Left) {
|
|
||||||
menuItemRestore_Click(sender, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void contextMenu_Popup(object sender, EventArgs e) {
|
|
||||||
contextMenu.MenuItems[1].Checked = Config.MuteNotifications;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void menuItemRestore_Click(object sender, EventArgs e) {
|
|
||||||
ClickRestore?.Invoke(this, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void menuItemMuteNotifications_Click(object sender, EventArgs e) {
|
|
||||||
Config.MuteNotifications = !contextMenu.MenuItems[1].Checked;
|
|
||||||
Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void menuItemClose_Click(object sender, EventArgs e) {
|
|
||||||
ClickClose?.Invoke(this, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class BehaviorExtensions {
|
|
||||||
public static bool ShouldDisplayIcon(this TrayIcon.Behavior behavior) {
|
|
||||||
return behavior != TrayIcon.Behavior.Disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool ShouldHideOnMinimize(this TrayIcon.Behavior behavior) {
|
|
||||||
return behavior == TrayIcon.Behavior.MinimizeToTray || behavior == TrayIcon.Behavior.Combined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool ShouldHideOnClose(this TrayIcon.Behavior behavior) {
|
|
||||||
return behavior == TrayIcon.Behavior.CloseToTray || behavior == TrayIcon.Behavior.Combined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,132 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Drawing;
|
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.WinForms;
|
|
||||||
using TweetDuck.Browser.Adapters;
|
|
||||||
using TweetDuck.Browser.Handling;
|
|
||||||
using TweetDuck.Browser.Notification;
|
|
||||||
using TweetDuck.Configuration;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
using TweetLib.Core.Features.Plugins;
|
|
||||||
using TweetLib.Core.Features.TweetDeck;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
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 {
|
|
||||||
sealed class TweetDeckBrowser : IDisposable {
|
|
||||||
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
|
||||||
|
|
||||||
public bool Ready => browserComponent.Ready;
|
|
||||||
|
|
||||||
public bool Enabled {
|
|
||||||
get => browser.Enabled;
|
|
||||||
set => browser.Enabled = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsTweetDeckWebsite {
|
|
||||||
get {
|
|
||||||
if (!Ready) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
using IFrame frame = browser.GetBrowser().MainFrame;
|
|
||||||
return TwitterUrls.IsTweetDeck(frame.Url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TweetDeckFunctions Functions => browserImpl.Functions;
|
|
||||||
|
|
||||||
private readonly CefBrowserComponent browserComponent;
|
|
||||||
private readonly TweetDeckBrowserImpl browserImpl;
|
|
||||||
private readonly ChromiumWebBrowser browser;
|
|
||||||
|
|
||||||
public TweetDeckBrowser(FormBrowser owner, PluginManager pluginManager, ITweetDeckInterface tweetDeckInterface, UpdateChecker updateChecker) {
|
|
||||||
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck) {
|
|
||||||
DialogHandler = new FileDialogHandler(),
|
|
||||||
DragHandler = new DragHandlerBrowser(requestHandler),
|
|
||||||
KeyboardHandler = new CustomKeyboardHandler(owner),
|
|
||||||
RequestHandler = requestHandler
|
|
||||||
};
|
|
||||||
|
|
||||||
// ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable
|
|
||||||
this.browser.BrowserSettings.BackgroundColor = (uint) BackgroundColor.ToArgb();
|
|
||||||
|
|
||||||
var extraContext = new TweetDeckExtraContext();
|
|
||||||
var resourceHandlerRegistry = new CefResourceHandlerRegistry();
|
|
||||||
var soundNotificationHandler = new SoundNotification(resourceHandlerRegistry);
|
|
||||||
|
|
||||||
this.browserComponent = new ComponentImpl(browser, owner, extraContext, resourceHandlerRegistry);
|
|
||||||
this.browserImpl = new TweetDeckBrowserImpl(browserComponent, tweetDeckInterface, extraContext, soundNotificationHandler, pluginManager, updateChecker);
|
|
||||||
|
|
||||||
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)) {
|
|
||||||
browserComponent.PageLoadEnd += (sender, args) => {
|
|
||||||
if (TwitterUrls.IsTweetDeck(args.Url)) {
|
|
||||||
browserComponent.RunScript("gen:gdpr", "TD.storage.Account.prototype.requiresConsent = function() { return false; }");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
owner.Controls.Add(browser);
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class ComponentImpl : CefBrowserComponent {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
browserImpl.Dispose();
|
|
||||||
browser.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Focus() {
|
|
||||||
browser.Focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenDevTools() {
|
|
||||||
browser.OpenDevToolsCustom();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReloadToTweetDeck() {
|
|
||||||
browserImpl.ReloadToTweetDeck();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveVideo(string url, string username) {
|
|
||||||
browserImpl.FileDownloadManager.SaveVideo(url, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HideVideoOverlay(bool focus) {
|
|
||||||
if (focus) {
|
|
||||||
browser.GetBrowser().GetHost().SendFocusEvent(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
browserComponent.RunScript("gen:hidevideo", "$('#td-video-player-overlay').remove()");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,5 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using TweetLib.Utils.Collections;
|
using TweetDuck.Data;
|
||||||
|
|
||||||
namespace TweetDuck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
static class Arguments{
|
static class Arguments{
|
||||||
@@ -7,7 +7,6 @@ namespace TweetDuck.Configuration {
|
|||||||
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
|
||||||
@@ -23,8 +22,8 @@ namespace TweetDuck.Configuration {
|
|||||||
return Current.HasFlag(flag);
|
return Current.HasFlag(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetValue(string key) {
|
public static string GetValue(string key, string defaultValue){
|
||||||
return Current.GetValue(key);
|
return Current.GetValue(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CommandLineArgs GetCurrentClean(){
|
public static CommandLineArgs GetCurrentClean(){
|
||||||
|
128
Configuration/ConfigManager.cs
Normal file
128
Configuration/ConfigManager.cs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using TweetDuck.Configuration.Instance;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
using TweetDuck.Data.Serialization;
|
||||||
|
|
||||||
|
namespace TweetDuck.Configuration{
|
||||||
|
sealed class ConfigManager{
|
||||||
|
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 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(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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TriggerProgramRestartRequested(){
|
||||||
|
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IConfigInstance<BaseConfig> GetInstanceInfo(BaseConfig instance){
|
||||||
|
Type instanceType = instance.GetType();
|
||||||
|
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class BaseConfig{
|
||||||
|
private readonly ConfigManager configManager;
|
||||||
|
|
||||||
|
protected BaseConfig(ConfigManager configManager){
|
||||||
|
this.configManager = configManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Management
|
||||||
|
|
||||||
|
public void Save(){
|
||||||
|
configManager.GetInstanceInfo(this).Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reload(){
|
||||||
|
configManager.GetInstanceInfo(this).Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset(){
|
||||||
|
configManager.GetInstanceInfo(this).Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construction methods
|
||||||
|
|
||||||
|
public T ConstructWithDefaults<T>() where T : BaseConfig{
|
||||||
|
return ConstructWithDefaults(configManager) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract BaseConfig ConstructWithDefaults(ConfigManager configManager);
|
||||||
|
|
||||||
|
// Utility methods
|
||||||
|
|
||||||
|
protected void UpdatePropertyWithEvent<T>(ref T field, T value, EventHandler eventHandler){
|
||||||
|
if (!EqualityComparer<T>.Default.Equals(field, value)){
|
||||||
|
field = value;
|
||||||
|
eventHandler?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdatePropertyWithRestartRequest<T>(ref T field, T value){
|
||||||
|
if (!EqualityComparer<T>.Default.Equals(field, value)){
|
||||||
|
field = value;
|
||||||
|
configManager.TriggerProgramRestartRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
Configuration/Instance/FileConfigInstance.cs
Normal file
104
Configuration/Instance/FileConfigInstance.cs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using TweetDuck.Data.Serialization;
|
||||||
|
|
||||||
|
namespace TweetDuck.Configuration.Instance{
|
||||||
|
sealed class FileConfigInstance<T> : IConfigInstance<T> where T : ConfigManager.BaseConfig{
|
||||||
|
private const string ErrorTitle = "Configuration Error";
|
||||||
|
|
||||||
|
public T Instance { get; }
|
||||||
|
public FileSerializer<T> Serializer { get; }
|
||||||
|
|
||||||
|
private readonly string filenameMain;
|
||||||
|
private readonly string filenameBackup;
|
||||||
|
private readonly string errorIdentifier;
|
||||||
|
|
||||||
|
public FileConfigInstance(string filename, T instance, string errorIdentifier){
|
||||||
|
this.filenameMain = filename;
|
||||||
|
this.filenameBackup = filename+".bak";
|
||||||
|
this.errorIdentifier = errorIdentifier;
|
||||||
|
|
||||||
|
this.Instance = instance;
|
||||||
|
this.Serializer = new FileSerializer<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadInternal(bool backup){
|
||||||
|
Serializer.Read(backup ? filenameBackup : filenameMain, Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load(){
|
||||||
|
Exception firstException = null;
|
||||||
|
|
||||||
|
for(int attempt = 0; attempt < 2; attempt++){
|
||||||
|
try{
|
||||||
|
LoadInternal(attempt > 0);
|
||||||
|
|
||||||
|
if (firstException != null){ // silently log exception that caused a backup restore
|
||||||
|
Program.Reporter.LogImportant(firstException.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}catch(FileNotFoundException){
|
||||||
|
}catch(DirectoryNotFoundException){
|
||||||
|
break;
|
||||||
|
}catch(Exception e){
|
||||||
|
if (firstException == null){
|
||||||
|
firstException = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstException is FormatException){
|
||||||
|
Program.Reporter.HandleException(ErrorTitle, "The configuration file for "+errorIdentifier+" is outdated or corrupted. If you continue, your "+errorIdentifier+" will be reset.", true, firstException);
|
||||||
|
}
|
||||||
|
else if (firstException is SerializationSoftException sse){
|
||||||
|
Program.Reporter.HandleException(ErrorTitle, $"{sse.Errors.Count} error{(sse.Errors.Count == 1 ? " was" : "s were")} encountered while loading the configuration file for "+errorIdentifier+". If you continue, some of your "+errorIdentifier+" will be reset.", true, firstException);
|
||||||
|
}
|
||||||
|
else if (firstException != null){
|
||||||
|
Program.Reporter.HandleException(ErrorTitle, "Could not open the configuration file for "+errorIdentifier+". If you continue, your "+errorIdentifier+" will be reset.", true, firstException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(){
|
||||||
|
try{
|
||||||
|
if (File.Exists(filenameMain)){
|
||||||
|
File.Delete(filenameBackup);
|
||||||
|
File.Move(filenameMain, filenameBackup);
|
||||||
|
}
|
||||||
|
|
||||||
|
Serializer.Write(filenameMain, Instance);
|
||||||
|
}catch(SerializationSoftException e){
|
||||||
|
Program.Reporter.HandleException(ErrorTitle, $"{e.Errors.Count} error{(e.Errors.Count == 1 ? " was" : "s were")} encountered while saving the configuration file for "+errorIdentifier+".", true, e);
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException(ErrorTitle, "Could not save the configuration file for "+errorIdentifier+".", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reload(){
|
||||||
|
try{
|
||||||
|
LoadInternal(false);
|
||||||
|
}catch(FileNotFoundException){
|
||||||
|
try{
|
||||||
|
Serializer.Write(filenameMain, Instance.ConstructWithDefaults<T>());
|
||||||
|
LoadInternal(false);
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException(ErrorTitle, "Could not regenerate the configuration file for "+errorIdentifier+".", true, e);
|
||||||
|
}
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException(ErrorTitle, "Could not reload the configuration file for "+errorIdentifier+".", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset(){
|
||||||
|
try{
|
||||||
|
File.Delete(filenameMain);
|
||||||
|
File.Delete(filenameBackup);
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException(ErrorTitle, "Could not delete configuration files to reset "+errorIdentifier+".", true, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
Configuration/Instance/IConfigInstance.cs
Normal file
9
Configuration/Instance/IConfigInstance.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace TweetDuck.Configuration.Instance{
|
||||||
|
interface IConfigInstance<out T>{
|
||||||
|
T Instance { get; }
|
||||||
|
|
||||||
|
void Save();
|
||||||
|
void Reload();
|
||||||
|
void Reset();
|
||||||
|
}
|
||||||
|
}
|
69
Configuration/Instance/PluginConfigInstance.cs
Normal file
69
Configuration/Instance/PluginConfigInstance.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace TweetDuck.Configuration.Instance{
|
||||||
|
class PluginConfigInstance : IConfigInstance<PluginConfig>{
|
||||||
|
public PluginConfig Instance { get; }
|
||||||
|
|
||||||
|
private readonly string filename;
|
||||||
|
|
||||||
|
public PluginConfigInstance(string filename, PluginConfig instance){
|
||||||
|
this.filename = filename;
|
||||||
|
this.Instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load(){
|
||||||
|
try{
|
||||||
|
using(StreamReader reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)){
|
||||||
|
string line = reader.ReadLine();
|
||||||
|
|
||||||
|
if (line == "#Disabled"){
|
||||||
|
HashSet<string> newDisabled = new HashSet<string>();
|
||||||
|
|
||||||
|
while((line = reader.ReadLine()) != null){
|
||||||
|
newDisabled.Add(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance.ReloadSilently(newDisabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(FileNotFoundException){
|
||||||
|
}catch(DirectoryNotFoundException){
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Plugin Configuration Error", "Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(){
|
||||||
|
try{
|
||||||
|
using(StreamWriter writer = new StreamWriter(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
|
||||||
|
writer.WriteLine("#Disabled");
|
||||||
|
|
||||||
|
foreach(string identifier in Instance.DisabledPlugins){
|
||||||
|
writer.WriteLine(identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Plugin Configuration Error", "Could not save the plugin configuration file.", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reload(){
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset(){
|
||||||
|
try{
|
||||||
|
File.Delete(filename);
|
||||||
|
Instance.ReloadSilently(Instance.ConstructWithDefaults<PluginConfig>().DisabledPlugins);
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Plugin Configuration Error", "Could not delete the plugin configuration file.", true, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
183
Configuration/LockManager.cs
Normal file
183
Configuration/LockManager.cs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Configuration{
|
||||||
|
sealed class LockManager{
|
||||||
|
private const int RetryDelay = 250;
|
||||||
|
|
||||||
|
public enum Result{
|
||||||
|
Success, HasProcess, Fail
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly string file;
|
||||||
|
private FileStream lockStream;
|
||||||
|
private Process lockingProcess;
|
||||||
|
|
||||||
|
public LockManager(string file){
|
||||||
|
this.file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock file
|
||||||
|
|
||||||
|
private bool ReleaseLockFileStream(){
|
||||||
|
if (lockStream != null){
|
||||||
|
lockStream.Dispose();
|
||||||
|
lockStream = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result TryCreateLockFile(){
|
||||||
|
void CreateLockFileStream(){
|
||||||
|
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||||
|
lockStream.Write(BitConverter.GetBytes(WindowsUtils.CurrentProcessID), 0, sizeof(int));
|
||||||
|
lockStream.Flush(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
CreateLockFileStream();
|
||||||
|
return Result.Success;
|
||||||
|
}catch(DirectoryNotFoundException){
|
||||||
|
try{
|
||||||
|
CreateLockFileStream();
|
||||||
|
return Result.Success;
|
||||||
|
}catch{
|
||||||
|
ReleaseLockFileStream();
|
||||||
|
return Result.Fail;
|
||||||
|
}
|
||||||
|
}catch(IOException){
|
||||||
|
return Result.HasProcess;
|
||||||
|
}catch{
|
||||||
|
ReleaseLockFileStream();
|
||||||
|
return Result.Fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock management
|
||||||
|
|
||||||
|
public Result Lock(){
|
||||||
|
if (lockStream != null){
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result initialResult = TryCreateLockFile();
|
||||||
|
|
||||||
|
if (initialResult == Result.HasProcess){
|
||||||
|
try{
|
||||||
|
int pid;
|
||||||
|
|
||||||
|
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
|
||||||
|
byte[] bytes = new byte[sizeof(int)];
|
||||||
|
fileStream.Read(bytes, 0, bytes.Length);
|
||||||
|
pid = BitConverter.ToInt32(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
Process foundProcess = Process.GetProcessById(pid);
|
||||||
|
|
||||||
|
using(Process currentProcess = Process.GetCurrentProcess()){
|
||||||
|
if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
|
||||||
|
lockingProcess = foundProcess;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
foundProcess.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch{
|
||||||
|
// GetProcessById throws ArgumentException if the process is missing
|
||||||
|
// Process.MainModule can throw exceptions in some cases
|
||||||
|
}
|
||||||
|
|
||||||
|
return lockingProcess == null ? Result.Fail : Result.HasProcess;
|
||||||
|
}catch{
|
||||||
|
return Result.Fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result LockWait(int timeout){
|
||||||
|
for(int elapsed = 0; elapsed < timeout; elapsed += RetryDelay){
|
||||||
|
Result result = Lock();
|
||||||
|
|
||||||
|
if (result == Result.HasProcess){
|
||||||
|
Thread.Sleep(RetryDelay);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Unlock(){
|
||||||
|
if (ReleaseLockFileStream()){
|
||||||
|
try{
|
||||||
|
File.Delete(file);
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.LogImportant(e.ToString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locking process
|
||||||
|
|
||||||
|
public bool RestoreLockingProcess(int failTimeout){
|
||||||
|
if (lockingProcess != null && lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
||||||
|
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
|
||||||
|
|
||||||
|
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CloseLockingProcess(int closeTimeout, int killTimeout){
|
||||||
|
if (lockingProcess != null){
|
||||||
|
try{
|
||||||
|
if (lockingProcess.CloseMainWindow()){
|
||||||
|
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, RetryDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lockingProcess.HasExited){
|
||||||
|
lockingProcess.Kill();
|
||||||
|
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, RetryDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lockingProcess.HasExited){
|
||||||
|
lockingProcess.Dispose();
|
||||||
|
lockingProcess = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
|
||||||
|
if (lockingProcess != null){
|
||||||
|
bool hasExited = CheckLockingProcessExited();
|
||||||
|
lockingProcess.Dispose();
|
||||||
|
return hasExited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckLockingProcessExited(){
|
||||||
|
lockingProcess.Refresh();
|
||||||
|
return lockingProcess.HasExited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
Configuration/PluginConfig.cs
Normal file
45
Configuration/PluginConfig.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetDuck.Plugins.Events;
|
||||||
|
|
||||||
|
namespace TweetDuck.Configuration{
|
||||||
|
sealed class PluginConfig : ConfigManager.BaseConfig, IPluginConfig{
|
||||||
|
private static readonly string[] DefaultDisabled = {
|
||||||
|
"official/clear-columns",
|
||||||
|
"official/reply-account"
|
||||||
|
};
|
||||||
|
|
||||||
|
// CONFIGURATION
|
||||||
|
|
||||||
|
public IEnumerable<string> DisabledPlugins => disabled;
|
||||||
|
|
||||||
|
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadSilently(IEnumerable<string> newDisabled){
|
||||||
|
disabled.Clear();
|
||||||
|
disabled.UnionWith(newDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled);
|
||||||
|
|
||||||
|
// END OF CONFIG
|
||||||
|
|
||||||
|
public PluginConfig(ConfigManager configManager) : base(configManager){}
|
||||||
|
|
||||||
|
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
|
||||||
|
return new PluginConfig(configManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,17 +1,9 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
namespace TweetDuck.Configuration{
|
||||||
using TweetLib.Core;
|
sealed class SystemConfig : ConfigManager.BaseConfig{
|
||||||
using TweetLib.Core.Application;
|
|
||||||
using TweetLib.Core.Systems.Configuration;
|
|
||||||
|
|
||||||
namespace TweetDuck.Configuration {
|
// CONFIGURATION DATA
|
||||||
sealed class SystemConfig : BaseConfig<SystemConfig>, IAppSystemConfiguration {
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
|
||||||
public int MigrationVersion { get; set; } = 0;
|
|
||||||
|
|
||||||
private bool _hardwareAcceleration = true;
|
public bool _hardwareAcceleration = true;
|
||||||
private bool _enableTouchAdjustment = false;
|
|
||||||
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;
|
||||||
@@ -20,50 +12,15 @@ namespace TweetDuck.Configuration {
|
|||||||
|
|
||||||
public bool HardwareAcceleration{
|
public bool HardwareAcceleration{
|
||||||
get => _hardwareAcceleration;
|
get => _hardwareAcceleration;
|
||||||
set => UpdatePropertyWithCallback(ref _hardwareAcceleration, value, App.ConfigManager.TriggerProgramRestartRequested);
|
set => UpdatePropertyWithRestartRequest(ref _hardwareAcceleration, value);
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
#pragma warning disable CS0618
|
public SystemConfig(ConfigManager configManager) : base(configManager){}
|
||||||
|
|
||||||
public bool Migrate() {
|
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
|
||||||
bool hasChanged = false;
|
return new SystemConfig(configManager);
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,50 +1,43 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using TweetDuck.Browser;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Core.Notification;
|
||||||
using TweetLib.Core;
|
using TweetDuck.Core.Other;
|
||||||
using TweetLib.Core.Application;
|
using TweetDuck.Core.Utils;
|
||||||
using TweetLib.Core.Features.Notifications;
|
using TweetDuck.Data;
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
using TweetLib.Core.Systems.Configuration;
|
|
||||||
using TweetLib.Utils.Data;
|
|
||||||
|
|
||||||
namespace TweetDuck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
sealed class UserConfig : BaseConfig<UserConfig>, IAppUserConfiguration {
|
sealed class UserConfig : ConfigManager.BaseConfig{
|
||||||
public bool FirstRun { get; set; } = true;
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
// CONFIGURATION DATA
|
||||||
|
|
||||||
|
public bool FirstRun { get; set; } = true;
|
||||||
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();
|
||||||
public Size PluginsWindowSize { get; set; } = Size.Empty;
|
public Size PluginsWindowSize { get; set; } = Size.Empty;
|
||||||
|
|
||||||
public bool ExpandLinksOnHover { get; set; } = true;
|
public bool ExpandLinksOnHover { get; set; } = true;
|
||||||
public bool FocusDmInput { get; set; } = true;
|
|
||||||
public bool OpenSearchInFirstColumn { get; set; } = true;
|
public bool OpenSearchInFirstColumn { get; set; } = true;
|
||||||
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;
|
||||||
|
|
||||||
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;
|
||||||
public string BrowserPathArgs { get; set; } = null;
|
|
||||||
public bool IgnoreTrackingUrlWarning { get; set; } = false;
|
public bool IgnoreTrackingUrlWarning { get; set; } = false;
|
||||||
public string SearchEngineUrl { get; set; } = null;
|
public string SearchEngineUrl { get; set; } = null;
|
||||||
private int _zoomLevel = 100;
|
private int _zoomLevel = 100;
|
||||||
|
|
||||||
public string VideoPlayerPath { get; set; } = null;
|
|
||||||
public string VideoPlayerPathArgs { get; set; } = null;
|
|
||||||
public int VideoPlayerVolume { get; set; } = 50;
|
public int VideoPlayerVolume { get; set; } = 50;
|
||||||
|
|
||||||
public bool EnableSpellCheck { get; set; } = false;
|
public bool EnableSpellCheck { get; set; } = false;
|
||||||
private string _spellCheckLanguage = "en-US";
|
private string _spellCheckLanguage = "en-US";
|
||||||
|
|
||||||
public string TranslationTarget { get; set; } = "en";
|
public string TranslationTarget { get; set; } = "en";
|
||||||
public int CalendarFirstDay { get; set; } = -1;
|
|
||||||
|
|
||||||
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
|
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
|
||||||
public bool EnableTrayHighlight { get; set; } = true;
|
public bool EnableTrayHighlight { get; set; } = true;
|
||||||
@@ -62,13 +55,12 @@ namespace TweetDuck.Configuration {
|
|||||||
public bool NotificationTimerCountDown { get; set; } = false;
|
public bool NotificationTimerCountDown { get; set; } = false;
|
||||||
public int NotificationDurationValue { get; set; } = 25;
|
public int NotificationDurationValue { get; set; } = 25;
|
||||||
|
|
||||||
public DesktopNotification.Position NotificationPosition { get; set; } = DesktopNotification.Position.TopRight;
|
public TweetNotification.Position NotificationPosition { get; set; } = TweetNotification.Position.TopRight;
|
||||||
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
|
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
|
||||||
public int NotificationDisplay { get; set; } = 0;
|
public int NotificationDisplay { get; set; } = 0;
|
||||||
public int NotificationEdgeDistance { get; set; } = 8;
|
public int NotificationEdgeDistance { get; set; } = 8;
|
||||||
public int NotificationWindowOpacity { get; set; } = 100;
|
|
||||||
|
|
||||||
public DesktopNotification.Size NotificationSize { get; set; } = DesktopNotification.Size.Auto;
|
public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.Size.Auto;
|
||||||
public Size CustomNotificationSize { get; set; } = Size.Empty;
|
public Size CustomNotificationSize { get; set; } = Size.Empty;
|
||||||
public int NotificationScrollSpeed { get; set; } = 100;
|
public int NotificationScrollSpeed { get; set; } = 100;
|
||||||
|
|
||||||
@@ -80,16 +72,13 @@ 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;
|
|
||||||
|
|
||||||
// SPECIAL PROPERTIES
|
// SPECIAL PROPERTIES
|
||||||
|
|
||||||
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
|
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
|
||||||
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
|
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
|
||||||
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
|
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
|
||||||
|
|
||||||
public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default;
|
public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default;
|
||||||
|
|
||||||
public string NotificationSoundPath{
|
public string NotificationSoundPath{
|
||||||
get => _notificationSoundPath ?? string.Empty;
|
get => _notificationSoundPath ?? string.Empty;
|
||||||
@@ -118,49 +107,37 @@ namespace TweetDuck.Configuration {
|
|||||||
|
|
||||||
public bool EnableSmoothScrolling{
|
public bool EnableSmoothScrolling{
|
||||||
get => _enableSmoothScrolling;
|
get => _enableSmoothScrolling;
|
||||||
set => UpdatePropertyWithCallback(ref _enableSmoothScrolling, value, App.ConfigManager.TriggerProgramRestartRequested);
|
set => UpdatePropertyWithRestartRequest(ref _enableSmoothScrolling, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableTouchAdjustment{
|
||||||
|
get => _enableTouchAdjustment;
|
||||||
|
set => UpdatePropertyWithRestartRequest(ref _enableTouchAdjustment, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CustomCefArgs{
|
public string CustomCefArgs{
|
||||||
get => _customCefArgs;
|
get => _customCefArgs;
|
||||||
set => UpdatePropertyWithCallback(ref _customCefArgs, value, App.ConfigManager.TriggerProgramRestartRequested);
|
set => UpdatePropertyWithRestartRequest(ref _customCefArgs, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SpellCheckLanguage{
|
public string SpellCheckLanguage{
|
||||||
get => _spellCheckLanguage;
|
get => _spellCheckLanguage;
|
||||||
set => UpdatePropertyWithCallback(ref _spellCheckLanguage, value, App.ConfigManager.TriggerProgramRestartRequested);
|
set => UpdatePropertyWithRestartRequest(ref _spellCheckLanguage, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 override UserConfig ConstructWithDefaults() {
|
public UserConfig(ConfigManager configManager) : base(configManager){}
|
||||||
return new UserConfig();
|
|
||||||
|
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
|
||||||
|
return new UserConfig(configManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,103 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetLib.Utils.Data;
|
|
||||||
|
|
||||||
namespace TweetDuck.Controls {
|
|
||||||
static class ControlExtensions {
|
|
||||||
public static readonly Point InvisibleLocation = new Point(-32000, -32000);
|
|
||||||
|
|
||||||
public static void InvokeSafe(this Control control, Action func) {
|
|
||||||
if (control.InvokeRequired) {
|
|
||||||
control.Invoke(func);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
func();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void InvokeAsyncSafe(this Control control, Action func) {
|
|
||||||
if (control.InvokeRequired) {
|
|
||||||
control.BeginInvoke(func);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
func();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float GetDPIScale(this Control control) {
|
|
||||||
using Graphics graphics = control.CreateGraphics();
|
|
||||||
return graphics.DpiY / 96F;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsFullyOutsideView(this Form form) {
|
|
||||||
return !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds));
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetValueInstant(this ProgressBar bar, int value) {
|
|
||||||
if (value == bar.Maximum) {
|
|
||||||
bar.Value = value;
|
|
||||||
bar.Value = value - 1;
|
|
||||||
bar.Value = value;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
bar.Value = value + 1;
|
|
||||||
bar.Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetValueSafe(this NumericUpDown numUpDown, int value) {
|
|
||||||
if (value >= numUpDown.Minimum && value <= numUpDown.Maximum) {
|
|
||||||
numUpDown.Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetValueSafe(this TrackBar trackBar, int value) {
|
|
||||||
if (value >= trackBar.Minimum && value <= trackBar.Maximum) {
|
|
||||||
trackBar.Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool AlignValueToTick(this TrackBar trackBar) {
|
|
||||||
if (trackBar.Value % trackBar.SmallChange != 0) {
|
|
||||||
trackBar.Value = trackBar.SmallChange * (int) Math.Floor(((double) trackBar.Value / trackBar.SmallChange) + 0.5);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void EnableMultilineShortcuts(this TextBox textBox) {
|
|
||||||
textBox.KeyDown += (sender, args) => {
|
|
||||||
if (args.Control && args.KeyCode == Keys.A) {
|
|
||||||
((TextBox) sender).SelectAll();
|
|
||||||
args.SuppressKeyPress = 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
|
|
||||||
namespace TweetDuck.Controls {
|
|
||||||
sealed class FlatButton : Button {
|
|
||||||
protected override bool ShowFocusCues => false;
|
|
||||||
|
|
||||||
public FlatButton() {
|
|
||||||
GotFocus += FlatButton_GotFocus;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FlatButton_GotFocus(object sender, EventArgs e) { // removes extra border when focused
|
|
||||||
NotifyDefault(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,38 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
|
|
||||||
namespace TweetDuck.Controls {
|
|
||||||
sealed class FlatProgressBar : ProgressBar {
|
|
||||||
private readonly SolidBrush brush;
|
|
||||||
|
|
||||||
public FlatProgressBar() {
|
|
||||||
brush = new SolidBrush(Color.White);
|
|
||||||
|
|
||||||
SetStyle(ControlStyles.UserPaint, true);
|
|
||||||
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetValueInstant(int value) {
|
|
||||||
ControlExtensions.SetValueInstant(this, Math.Max(Minimum, Math.Min(Maximum, value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e) {
|
|
||||||
if (brush.Color != ForeColor) {
|
|
||||||
brush.Color = ForeColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle rect = e.ClipRectangle;
|
|
||||||
rect.Width = (int) (rect.Width * ((double) Value / Maximum));
|
|
||||||
e.Graphics.FillRectangle(brush, rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing) {
|
|
||||||
base.Dispose(disposing);
|
|
||||||
|
|
||||||
if (disposing) {
|
|
||||||
brush.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
using System.Windows.Forms;
|
|
||||||
using TweetDuck.Utils;
|
|
||||||
|
|
||||||
namespace TweetDuck.Controls {
|
|
||||||
sealed class FlowLayoutPanelNoHScroll : FlowLayoutPanel {
|
|
||||||
protected override void WndProc(ref Message m) {
|
|
||||||
if (m.Msg == 0x85) { // WM_NCPAINT
|
|
||||||
NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much
|
|
||||||
}
|
|
||||||
|
|
||||||
base.WndProc(ref m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
|
|
||||||
namespace TweetDuck.Controls {
|
|
||||||
sealed class LabelVertical : Label {
|
|
||||||
public int LineHeight { get; set; }
|
|
||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e) {
|
|
||||||
int y = (int) Math.Floor((ClientRectangle.Height - Text.Length * LineHeight) / 2F) - 1;
|
|
||||||
using Brush brush = new SolidBrush(ForeColor);
|
|
||||||
|
|
||||||
foreach (char chr in Text) {
|
|
||||||
string str = chr.ToString();
|
|
||||||
float x = (ClientRectangle.Width - e.Graphics.MeasureString(str, Font).Width) / 2F;
|
|
||||||
|
|
||||||
e.Graphics.DrawString(str, Font, brush, x, y);
|
|
||||||
y += LineHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,18 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
|
|
||||||
namespace TweetDuck.Controls {
|
|
||||||
sealed class NumericUpDownEx : NumericUpDown {
|
|
||||||
public string TextSuffix { get; set ; }
|
|
||||||
|
|
||||||
protected override void UpdateEditText() {
|
|
||||||
base.UpdateEditText();
|
|
||||||
|
|
||||||
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime) {
|
|
||||||
ChangingText = true;
|
|
||||||
Text += TextSuffix;
|
|
||||||
ChangingText = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
34
Core/Bridge/PropertyBridge.cs
Normal file
34
Core/Bridge/PropertyBridge.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Text;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Bridge{
|
||||||
|
static class PropertyBridge{
|
||||||
|
public enum Environment{
|
||||||
|
Browser, Notification
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GenerateScript(Environment environment){
|
||||||
|
string Bool(bool value) => value ? "true;" : "false;";
|
||||||
|
string Str(string value) => '"'+value+"\";";
|
||||||
|
|
||||||
|
UserConfig config = Program.Config.User;
|
||||||
|
StringBuilder build = new StringBuilder(128).Append("(function(x){");
|
||||||
|
|
||||||
|
build.Append("x.expandLinksOnHover=").Append(Bool(config.ExpandLinksOnHover));
|
||||||
|
|
||||||
|
if (environment == Environment.Browser){
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environment == Environment.Notification){
|
||||||
|
build.Append("x.skipOnLinkClick=").Append(Bool(config.NotificationSkipOnLinkClick));
|
||||||
|
}
|
||||||
|
|
||||||
|
return build.Append("})(window.$TDX=window.$TDX||{})").ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
139
Core/Bridge/TweetDeckBridge.cs
Normal file
139
Core/Bridge/TweetDeckBridge.cs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Management;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Bridge{
|
||||||
|
class TweetDeckBridge{
|
||||||
|
public static string FontSize { get; private set; }
|
||||||
|
public static string NotificationHeadLayout { get; private set; }
|
||||||
|
public static readonly ContextInfo ContextInfo = new ContextInfo();
|
||||||
|
|
||||||
|
public static void ResetStaticProperties(){
|
||||||
|
FontSize = NotificationHeadLayout = 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(() => {
|
||||||
|
FontSize = fontSize;
|
||||||
|
NotificationHeadLayout = headLayout;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRightClickedLink(string type, string url){
|
||||||
|
ContextInfo.SetLink(type, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||||
|
ContextInfo.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 TweetNotification(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 url, string username){
|
||||||
|
form.InvokeAsyncSafe(() => form.PlayVideo(url, username));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FixClipboard(){
|
||||||
|
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenBrowser(string url){
|
||||||
|
form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetIdleSeconds(){
|
||||||
|
return NativeMethods.GetIdleSeconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Alert(string type, string contents){
|
||||||
|
MessageBoxIcon icon;
|
||||||
|
|
||||||
|
switch(type){
|
||||||
|
case "error": icon = MessageBoxIcon.Error; break;
|
||||||
|
case "warning": icon = MessageBoxIcon.Warning; break;
|
||||||
|
case "info": icon = MessageBoxIcon.Information; break;
|
||||||
|
default: icon = MessageBoxIcon.None; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
Core/Bridge/UpdateBridge.cs
Normal file
68
Core/Bridge/UpdateBridge.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Updates;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Bridge{
|
||||||
|
class UpdateBridge{
|
||||||
|
private readonly UpdateHandler updates;
|
||||||
|
private readonly Control sync;
|
||||||
|
|
||||||
|
private UpdateInfo nextUpdate = null;
|
||||||
|
|
||||||
|
public event EventHandler<UpdateInfo> UpdateAccepted;
|
||||||
|
public event EventHandler<UpdateInfo> UpdateDelayed;
|
||||||
|
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 OnUpdateDelayed(){
|
||||||
|
HandleInteractionEvent(UpdateDelayed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnUpdateDismissed(){
|
||||||
|
HandleInteractionEvent(UpdateDismissed);
|
||||||
|
|
||||||
|
nextUpdate?.DeleteInstaller();
|
||||||
|
nextUpdate = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
Core/Controls/ControlExtensions.cs
Normal file
79
Core/Controls/ControlExtensions.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Controls{
|
||||||
|
static class ControlExtensions{
|
||||||
|
public static readonly Point InvisibleLocation = new Point(-32000, -32000);
|
||||||
|
|
||||||
|
public static void InvokeSafe(this Control control, Action func){
|
||||||
|
if (control.InvokeRequired){
|
||||||
|
control.Invoke(func);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void InvokeAsyncSafe(this Control control, Action func){
|
||||||
|
control.BeginInvoke(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float GetDPIScale(this Control control){
|
||||||
|
using(Graphics graphics = control.CreateGraphics()){
|
||||||
|
return graphics.DpiY/96F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsFullyOutsideView(this Form form){
|
||||||
|
return !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetValueInstant(this ProgressBar bar, int value){
|
||||||
|
if (value == bar.Maximum){
|
||||||
|
bar.Value = value;
|
||||||
|
bar.Value = value-1;
|
||||||
|
bar.Value = value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
bar.Value = value+1;
|
||||||
|
bar.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetValueSafe(this NumericUpDown numUpDown, int value){
|
||||||
|
if (value >= numUpDown.Minimum && value <= numUpDown.Maximum){
|
||||||
|
numUpDown.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetValueSafe(this TrackBar trackBar, int value){
|
||||||
|
if (value >= trackBar.Minimum && value <= trackBar.Maximum){
|
||||||
|
trackBar.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool AlignValueToTick(this TrackBar trackBar){
|
||||||
|
if (trackBar.Value % trackBar.SmallChange != 0){
|
||||||
|
trackBar.Value = trackBar.SmallChange*(int)Math.Floor(((double)trackBar.Value/trackBar.SmallChange)+0.5);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EnableMultilineShortcuts(this TextBox textBox){
|
||||||
|
textBox.KeyDown += (sender, args) => {
|
||||||
|
if (args.Control && args.KeyCode == Keys.A){
|
||||||
|
((TextBox)sender).SelectAll();
|
||||||
|
args.SuppressKeyPress = true;
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
Core/Controls/FlatButton.cs
Normal file
16
Core/Controls/FlatButton.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Controls{
|
||||||
|
sealed class FlatButton : Button{
|
||||||
|
protected override bool ShowFocusCues => false;
|
||||||
|
|
||||||
|
public FlatButton(){
|
||||||
|
GotFocus += FlatButton_GotFocus;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlatButton_GotFocus(object sender, EventArgs e){ // removes extra border when focused
|
||||||
|
NotifyDefault(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
Core/Controls/FlatProgressBar.cs
Normal file
38
Core/Controls/FlatProgressBar.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Controls{
|
||||||
|
sealed class FlatProgressBar : ProgressBar{
|
||||||
|
private readonly SolidBrush brush;
|
||||||
|
|
||||||
|
public FlatProgressBar(){
|
||||||
|
brush = new SolidBrush(Color.White);
|
||||||
|
|
||||||
|
SetStyle(ControlStyles.UserPaint, true);
|
||||||
|
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetValueInstant(int value){
|
||||||
|
ControlExtensions.SetValueInstant(this, Math.Max(Minimum, Math.Min(Maximum, value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPaint(PaintEventArgs e){
|
||||||
|
if (brush.Color != ForeColor){
|
||||||
|
brush.Color = ForeColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle rect = e.ClipRectangle;
|
||||||
|
rect.Width = (int)(rect.Width*((double)Value/Maximum));
|
||||||
|
e.Graphics.FillRectangle(brush, rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing){
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing){
|
||||||
|
brush.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
Core/Controls/LabelVertical.cs
Normal file
23
Core/Controls/LabelVertical.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Controls{
|
||||||
|
sealed class LabelVertical : Label{
|
||||||
|
public int LineHeight { get; set; }
|
||||||
|
|
||||||
|
protected override void OnPaint(PaintEventArgs e){
|
||||||
|
int y = (int)Math.Floor((ClientRectangle.Height-Text.Length*LineHeight)/2F)-1;
|
||||||
|
|
||||||
|
using(Brush brush = new SolidBrush(ForeColor)){
|
||||||
|
foreach(char chr in Text){
|
||||||
|
string str = chr.ToString();
|
||||||
|
float x = (ClientRectangle.Width-e.Graphics.MeasureString(str, Font).Width)/2F;
|
||||||
|
|
||||||
|
e.Graphics.DrawString(str, Font, brush, x, y);
|
||||||
|
y += LineHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
Core/Controls/NumericUpDownEx.cs
Normal file
18
Core/Controls/NumericUpDownEx.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Controls{
|
||||||
|
sealed class NumericUpDownEx : NumericUpDown{
|
||||||
|
public string TextSuffix { get; set ; }
|
||||||
|
|
||||||
|
protected override void UpdateEditText(){
|
||||||
|
base.UpdateEditText();
|
||||||
|
|
||||||
|
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime){
|
||||||
|
ChangingText = true;
|
||||||
|
Text += TextSuffix;
|
||||||
|
ChangingText = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,10 +1,21 @@
|
|||||||
namespace TweetDuck.Browser {
|
namespace TweetDuck.Core {
|
||||||
sealed partial class FormBrowser {
|
sealed partial class FormBrowser {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private System.ComponentModel.IContainer components = null;
|
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
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -13,7 +24,7 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent() {
|
private void InitializeComponent() {
|
||||||
this.components = new System.ComponentModel.Container();
|
this.components = new System.ComponentModel.Container();
|
||||||
this.trayIcon = new TrayIcon(this.components);
|
this.trayIcon = new TweetDuck.Core.Other.TrayIcon(this.components);
|
||||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||||
this.timerResize = new System.Windows.Forms.Timer(this.components);
|
this.timerResize = new System.Windows.Forms.Timer(this.components);
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
@@ -27,10 +38,10 @@
|
|||||||
//
|
//
|
||||||
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.Browser.TweetDeckBrowser.BackgroundColor;
|
this.BackColor = TweetDuck.Core.Utils.TwitterUtils.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.Core.Controls.ControlExtensions.InvisibleLocation;
|
||||||
this.MinimumSize = new System.Drawing.Size(348, 424);
|
this.MinimumSize = new System.Drawing.Size(348, 424);
|
||||||
this.Name = "FormBrowser";
|
this.Name = "FormBrowser";
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
||||||
@@ -46,7 +57,7 @@
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private TrayIcon trayIcon;
|
private TweetDuck.Core.Other.TrayIcon trayIcon;
|
||||||
private System.Windows.Forms.ToolTip toolTip;
|
private System.Windows.Forms.ToolTip toolTip;
|
||||||
private System.Windows.Forms.Timer timerResize;
|
private System.Windows.Forms.Timer timerResize;
|
||||||
}
|
}
|
575
Core/FormBrowser.cs
Normal file
575
Core/FormBrowser.cs
Normal file
@@ -0,0 +1,575 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Core.Bridge;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Handling;
|
||||||
|
using TweetDuck.Core.Handling.General;
|
||||||
|
using TweetDuck.Core.Management;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
|
using TweetDuck.Core.Notification.Screenshot;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Other.Analytics;
|
||||||
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetDuck.Plugins.Events;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
using TweetDuck.Updates;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core{
|
||||||
|
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
|
||||||
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
|
public bool IsWaiting{
|
||||||
|
set{
|
||||||
|
if (value){
|
||||||
|
browser.Enabled = false;
|
||||||
|
Cursor = Cursors.WaitCursor;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
browser.Enabled = true;
|
||||||
|
Cursor = Cursors.Default;
|
||||||
|
|
||||||
|
if (Focused){ // re-focus browser only if the window or a child is activated
|
||||||
|
browser.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UpdateInstallerPath { get; private set; }
|
||||||
|
private bool ignoreUpdateCheckError;
|
||||||
|
|
||||||
|
public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
|
||||||
|
|
||||||
|
private readonly TweetDeckBrowser browser;
|
||||||
|
private readonly PluginManager plugins;
|
||||||
|
private readonly UpdateHandler updates;
|
||||||
|
private readonly FormNotificationTweet notification;
|
||||||
|
private readonly ContextMenu contextMenu;
|
||||||
|
private readonly UpdateBridge updateBridge;
|
||||||
|
|
||||||
|
private bool isLoaded;
|
||||||
|
private FormWindowState prevState;
|
||||||
|
|
||||||
|
private TweetScreenshotManager notificationScreenshotManager;
|
||||||
|
private VideoPlayer videoPlayer;
|
||||||
|
private AnalyticsManager analytics;
|
||||||
|
|
||||||
|
public FormBrowser(){
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
Text = Program.BrandName;
|
||||||
|
|
||||||
|
this.plugins = new PluginManager(Program.Config.Plugins, Program.PluginPath);
|
||||||
|
this.plugins.Reloaded += plugins_Reloaded;
|
||||||
|
this.plugins.Executed += plugins_Executed;
|
||||||
|
this.plugins.Reload();
|
||||||
|
|
||||||
|
this.notification = new FormNotificationTweet(this, plugins);
|
||||||
|
this.notification.Show();
|
||||||
|
|
||||||
|
this.updates = new UpdateHandler(Program.InstallerPath);
|
||||||
|
this.updates.CheckFinished += updates_CheckFinished;
|
||||||
|
|
||||||
|
this.updateBridge = new UpdateBridge(updates, this);
|
||||||
|
this.updateBridge.UpdateAccepted += updateBridge_UpdateAccepted;
|
||||||
|
this.updateBridge.UpdateDelayed += updateBridge_UpdateDelayed;
|
||||||
|
this.updateBridge.UpdateDismissed += updateBridge_UpdateDismissed;
|
||||||
|
|
||||||
|
this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge.Browser(this, notification), updateBridge);
|
||||||
|
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
||||||
|
|
||||||
|
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
||||||
|
|
||||||
|
Disposed += (sender, args) => {
|
||||||
|
Config.MuteToggled -= Config_MuteToggled;
|
||||||
|
Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
|
||||||
|
|
||||||
|
browser.Dispose();
|
||||||
|
updates.Dispose();
|
||||||
|
contextMenu.Dispose();
|
||||||
|
|
||||||
|
notificationScreenshotManager?.Dispose();
|
||||||
|
videoPlayer?.Dispose();
|
||||||
|
analytics?.Dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
Config.MuteToggled += Config_MuteToggled;
|
||||||
|
|
||||||
|
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
|
||||||
|
this.trayIcon.ClickClose += trayIcon_ClickClose;
|
||||||
|
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
|
||||||
|
|
||||||
|
UpdateTray();
|
||||||
|
|
||||||
|
if (Config.MuteNotifications){
|
||||||
|
UpdateFormIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.AllowDataCollection){
|
||||||
|
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
RestoreWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowChildForm(Form form){
|
||||||
|
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
|
||||||
|
form.Show(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ForceClose(){
|
||||||
|
trayIcon.Visible = false; // checked in FormClosing event
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// window setup
|
||||||
|
|
||||||
|
private void RestoreWindow(){
|
||||||
|
Config.BrowserWindow.Restore(this, true);
|
||||||
|
prevState = WindowState;
|
||||||
|
isLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateFormIcon(){ // TODO fix to show icon in taskbar too
|
||||||
|
Icon = Config.MuteNotifications ? Properties.Resources.icon_muted : Properties.Resources.icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTray(){
|
||||||
|
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
// event handlers
|
||||||
|
|
||||||
|
private void timerResize_Tick(object sender, EventArgs e){
|
||||||
|
FormBrowser_ResizeEnd(this, e); // also stops timer
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FormBrowser_Activated(object sender, EventArgs e){
|
||||||
|
if (!isLoaded)return;
|
||||||
|
|
||||||
|
trayIcon.HasNotifications = false;
|
||||||
|
|
||||||
|
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
|
||||||
|
} // the window, enable the browser again
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FormBrowser_LocationChanged(object sender, EventArgs e){
|
||||||
|
if (!isLoaded)return;
|
||||||
|
|
||||||
|
timerResize.Stop();
|
||||||
|
timerResize.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FormBrowser_Resize(object sender, EventArgs e){
|
||||||
|
if (!isLoaded)return;
|
||||||
|
|
||||||
|
if (WindowState != prevState){
|
||||||
|
prevState = WindowState;
|
||||||
|
|
||||||
|
if (WindowState == FormWindowState.Minimized){
|
||||||
|
if (Config.TrayBehavior.ShouldHideOnMinimize()){
|
||||||
|
Hide(); // hides taskbar too?! welp that works I guess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
FormBrowser_ResizeEnd(sender, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
timerResize.Stop();
|
||||||
|
timerResize.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves
|
||||||
|
if (!isLoaded)return;
|
||||||
|
|
||||||
|
timerResize.Stop();
|
||||||
|
|
||||||
|
if (Location != ControlExtensions.InvisibleLocation){
|
||||||
|
Config.BrowserWindow.Save(this);
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e){
|
||||||
|
if (!isLoaded)return;
|
||||||
|
|
||||||
|
if (Config.TrayBehavior.ShouldHideOnClose() && trayIcon.Visible && e.CloseReason == CloseReason.UserClosing){
|
||||||
|
Hide(); // hides taskbar too?! welp that works I guess
|
||||||
|
e.Cancel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e){
|
||||||
|
if (isLoaded && UpdateInstallerPath == null){
|
||||||
|
updateBridge.Cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Config_MuteToggled(object sender, EventArgs e){
|
||||||
|
UpdateFormIcon();
|
||||||
|
AnalyticsFile.NotificationMutes.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
|
||||||
|
UpdateTray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trayIcon_ClickRestore(object sender, EventArgs e){
|
||||||
|
Show();
|
||||||
|
RestoreWindow();
|
||||||
|
Activate();
|
||||||
|
UpdateTray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trayIcon_ClickClose(object sender, EventArgs e){
|
||||||
|
ForceClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
|
||||||
|
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 static void plugins_Executed(object sender, PluginErrorEventArgs e){
|
||||||
|
if (e.HasErrors){
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Config.DismissedUpdate)){
|
||||||
|
Config.DismissedUpdate = null;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnFinished(){
|
||||||
|
UpdateDownloadStatus status = update.DownloadStatus;
|
||||||
|
|
||||||
|
if (status == UpdateDownloadStatus.Done){
|
||||||
|
UpdateInstallerPath = update.InstallerPath;
|
||||||
|
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)){
|
||||||
|
BrowserUtils.OpenExternalBrowser(Program.Website);
|
||||||
|
ForceClose();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update.DownloadStatus.IsFinished(true)){
|
||||||
|
OnFinished();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
FormUpdateDownload downloadForm = new FormUpdateDownload(update);
|
||||||
|
|
||||||
|
downloadForm.VisibleChanged += (sender2, args2) => {
|
||||||
|
downloadForm.MoveToCenter(this);
|
||||||
|
Hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
downloadForm.FormClosed += (sender2, args2) => {
|
||||||
|
if (downloadForm.DialogResult != DialogResult.OK){
|
||||||
|
update.CancelDownload();
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadForm.Dispose();
|
||||||
|
OnFinished();
|
||||||
|
};
|
||||||
|
|
||||||
|
downloadForm.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBridge_UpdateDelayed(object sender, UpdateInfo update){
|
||||||
|
// stops the timer
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBridge_UpdateDismissed(object sender, UpdateInfo update){
|
||||||
|
Config.DismissedUpdate = update.VersionTag;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void WndProc(ref Message m){
|
||||||
|
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
||||||
|
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
|
||||||
|
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
|
||||||
|
if (videoPlayer != null && videoPlayer.Running){
|
||||||
|
videoPlayer.Close();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
browser.OnMouseClickExtra(m.WParam);
|
||||||
|
AnalyticsFile.BrowserExtraMouseButtons.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.WndProc(ref m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bridge methods
|
||||||
|
|
||||||
|
public void PauseNotification(){
|
||||||
|
notification.PauseNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResumeNotification(){
|
||||||
|
notification.ResumeNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReinjectCustomCSS(string css){
|
||||||
|
browser.ReinjectCustomCSS(css);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadToTweetDeck(){
|
||||||
|
#if DEBUG
|
||||||
|
ScriptLoader.HotSwap();
|
||||||
|
#else
|
||||||
|
if (ModifierKeys.HasFlag(Keys.Shift)){
|
||||||
|
ScriptLoader.ClearCache();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ignoreUpdateCheckError = false;
|
||||||
|
browser.ReloadToTweetDeck();
|
||||||
|
AnalyticsFile.BrowserReloads.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddSearchColumn(string query){
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// callback handlers
|
||||||
|
|
||||||
|
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
|
||||||
|
if (Config.FirstRun){
|
||||||
|
Config.FirstRun = false;
|
||||||
|
Config.AllowDataCollection = allowDataCollection;
|
||||||
|
Config.Save();
|
||||||
|
|
||||||
|
if (allowDataCollection && analytics == null){
|
||||||
|
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showGuide){
|
||||||
|
FormGuide.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenContextMenu(){
|
||||||
|
contextMenu.Show(this, PointToClient(Cursor.Position));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenSettings(){
|
||||||
|
OpenSettings(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenSettings(Type startTab){
|
||||||
|
if (!FormManager.TryBringToFront<FormSettings>()){
|
||||||
|
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
||||||
|
|
||||||
|
FormSettings form = new FormSettings(this, plugins, updates, analytics, startTab);
|
||||||
|
|
||||||
|
form.FormClosed += (sender, args) => {
|
||||||
|
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
|
||||||
|
Config.DismissedUpdate = null;
|
||||||
|
Config.Save();
|
||||||
|
|
||||||
|
updates.Check(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.EnableTrayHighlight){
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (form.ShouldReloadBrowser){
|
||||||
|
FormManager.TryFind<FormPlugins>()?.Close();
|
||||||
|
plugins.Reload(); // also reloads the browser
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
browser.UpdateProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.RequiresResize = true;
|
||||||
|
form.Dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
AnalyticsFile.OpenOptions.Trigger();
|
||||||
|
ShowChildForm(form);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenAbout(){
|
||||||
|
if (!FormManager.TryBringToFront<FormAbout>()){
|
||||||
|
AnalyticsFile.OpenAbout.Trigger();
|
||||||
|
ShowChildForm(new FormAbout());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenPlugins(){
|
||||||
|
if (!FormManager.TryBringToFront<FormPlugins>()){
|
||||||
|
AnalyticsFile.OpenPlugins.Trigger();
|
||||||
|
ShowChildForm(new FormPlugins(plugins));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenProfileImport(){
|
||||||
|
FormManager.TryFind<FormSettings>()?.Close();
|
||||||
|
|
||||||
|
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
|
||||||
|
BrowserProcessHandler.UpdatePrefs();
|
||||||
|
FormManager.TryFind<FormPlugins>()?.Close();
|
||||||
|
plugins.Reload(); // also reloads the browser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnTweetNotification(){ // may be called multiple times, once for each type of notification
|
||||||
|
if (Config.EnableTrayHighlight && !ContainsFocus){
|
||||||
|
trayIcon.HasNotifications = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnTweetSound(){
|
||||||
|
AnalyticsFile.SoundNotifications.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayVideo(string url, string username){
|
||||||
|
if (string.IsNullOrEmpty(url)){
|
||||||
|
videoPlayer?.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoPlayer == null){
|
||||||
|
videoPlayer = new VideoPlayer(this);
|
||||||
|
|
||||||
|
videoPlayer.ProcessExited += (sender, args) => {
|
||||||
|
browser.HideVideoOverlay(true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
videoPlayer.Launch(url, username);
|
||||||
|
AnalyticsFile.VideoPlays.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ProcessBrowserKey(Keys key){
|
||||||
|
if (videoPlayer != null && videoPlayer.Running){
|
||||||
|
videoPlayer.SendKeyEvent(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){
|
||||||
|
Activate();
|
||||||
|
|
||||||
|
if (!browser.IsTweetDeckWebsite){
|
||||||
|
FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.FinishCurrentNotification();
|
||||||
|
browser.ShowTweetDetail(columnId, chirpId, fallbackUrl);
|
||||||
|
AnalyticsFile.TweetDetails.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnTweetScreenshotReady(string html, int width){
|
||||||
|
if (notificationScreenshotManager == null){
|
||||||
|
notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationScreenshotManager.Trigger(html, width);
|
||||||
|
AnalyticsFile.TweetScreenshots.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
Core/FormManager.cs
Normal file
30
Core/FormManager.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core{
|
||||||
|
static class FormManager{
|
||||||
|
public static T TryFind<T>() where T : Form{
|
||||||
|
return Application.OpenForms.OfType<T>().FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryBringToFront<T>() where T : Form{
|
||||||
|
T form = TryFind<T>();
|
||||||
|
|
||||||
|
if (form != null){
|
||||||
|
form.BringToFront();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool HasAnyDialogs => Application.OpenForms.OfType<IAppDialog>().Any();
|
||||||
|
|
||||||
|
public static void CloseAllDialogs(){
|
||||||
|
foreach(IAppDialog dialog in Application.OpenForms.OfType<IAppDialog>().Reverse()){
|
||||||
|
((Form)dialog).Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAppDialog{}
|
||||||
|
}
|
||||||
|
}
|
229
Core/Handling/ContextMenuBase.cs
Normal file
229
Core/Handling/ContextMenuBase.cs
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using System.Linq;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Core.Bridge;
|
||||||
|
using TweetDuck.Core.Management;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Other.Analytics;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
abstract class ContextMenuBase : IContextMenuHandler{
|
||||||
|
protected static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
|
private static TwitterUtils.ImageQuality ImageQuality => Config.TwitterImageQuality;
|
||||||
|
|
||||||
|
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
|
||||||
|
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
|
||||||
|
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 MenuSaveMedia = (CefMenuCommand)26506;
|
||||||
|
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507;
|
||||||
|
private const CefMenuCommand MenuSearchInBrowser = (CefMenuCommand)26508;
|
||||||
|
private const CefMenuCommand MenuReadApplyROT13 = (CefMenuCommand)26509;
|
||||||
|
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
||||||
|
|
||||||
|
protected ContextInfo.ContextData Context { get; private set; }
|
||||||
|
|
||||||
|
private readonly AnalyticsFile.IProvider analytics;
|
||||||
|
|
||||||
|
protected ContextMenuBase(AnalyticsFile.IProvider analytics){
|
||||||
|
this.analytics = analytics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||||
|
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||||
|
Context = TweetDeckBridge.ContextInfo.Reset();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
Context = TweetDeckBridge.ContextInfo.Create(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
|
||||||
|
model.AddItem(MenuSearchInBrowser, "Search in browser");
|
||||||
|
model.AddSeparator();
|
||||||
|
model.AddItem(MenuReadApplyROT13, "Apply ROT13");
|
||||||
|
model.AddSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
|
string TextOpen(string name) => "Open "+name+" in browser";
|
||||||
|
string TextCopy(string name) => "Copy "+name+" address";
|
||||||
|
string TextSave(string name) => "Save "+name+" as...";
|
||||||
|
|
||||||
|
if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
|
||||||
|
if (TwitterUtils.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 != TweetNotification.AppLogo.Url){
|
||||||
|
model.AddItem(MenuViewImage, "View image in photo viewer");
|
||||||
|
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
|
||||||
|
model.AddItem(MenuCopyMediaUrl, TextCopy("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){
|
||||||
|
Control control = browserControl.AsControl();
|
||||||
|
|
||||||
|
switch(commandId){
|
||||||
|
case MenuOpenLinkUrl:
|
||||||
|
OpenBrowser(control, Context.LinkUrl);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MenuCopyLinkUrl:
|
||||||
|
SetClipboardText(control, Context.UnsafeLinkUrl);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MenuCopyUsername: {
|
||||||
|
string url = Context.UnsafeLinkUrl;
|
||||||
|
Match match = TwitterUtils.RegexAccount.Match(url);
|
||||||
|
|
||||||
|
SetClipboardText(control, match.Success ? match.Groups[1].Value : url);
|
||||||
|
control.InvokeAsyncSafe(analytics.AnalyticsFile.CopiedUsernames.Trigger);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MenuOpenMediaUrl:
|
||||||
|
OpenBrowser(control, TwitterUtils.GetMediaLink(Context.MediaUrl, ImageQuality));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MenuCopyMediaUrl:
|
||||||
|
SetClipboardText(control, TwitterUtils.GetMediaLink(Context.MediaUrl, ImageQuality));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MenuViewImage: {
|
||||||
|
string url = Context.MediaUrl;
|
||||||
|
|
||||||
|
control.InvokeAsyncSafe(() => {
|
||||||
|
TwitterUtils.ViewImage(url, ImageQuality);
|
||||||
|
analytics.AnalyticsFile.ViewedImages.Trigger();
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MenuSaveMedia: {
|
||||||
|
bool isVideo = Context.Types.HasFlag(ContextInfo.ContextType.Video);
|
||||||
|
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;
|
||||||
|
|
||||||
|
case MenuSearchInBrowser:
|
||||||
|
string query = parameters.SelectionText;
|
||||||
|
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalSearch(query));
|
||||||
|
DeselectAll(frame);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MenuOpenDevTools:
|
||||||
|
browserControl.ShowDevTools();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||||
|
Context = TweetDeckBridge.ContextInfo.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void DeselectAll(IFrame frame){
|
||||||
|
ScriptLoader.ExecuteScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void OpenBrowser(Control control, string url){
|
||||||
|
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void SetClipboardText(Control control, string text){
|
||||||
|
control.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(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);
|
||||||
|
model.AddItem(MenuOpenDevTools, "Open dev tools");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
||||||
|
if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){
|
||||||
|
model.RemoveAt(model.Count-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
model.AddSeparator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
179
Core/Handling/ContextMenuBrowser.cs
Normal file
179
Core/Handling/ContextMenuBrowser.cs
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Management;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
sealed class ContextMenuBrowser : ContextMenuBase{
|
||||||
|
private const CefMenuCommand MenuGlobal = (CefMenuCommand)26600;
|
||||||
|
private const CefMenuCommand MenuMute = (CefMenuCommand)26601;
|
||||||
|
private const CefMenuCommand MenuSettings = (CefMenuCommand)26602;
|
||||||
|
private const CefMenuCommand MenuPlugins = (CefMenuCommand)26003;
|
||||||
|
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 TitleMuteNotifications = "Mute notifications";
|
||||||
|
private const string TitleSettings = "Options";
|
||||||
|
private const string TitlePlugins = "Plugins";
|
||||||
|
private const string TitleAboutProgram = "About "+Program.BrandName;
|
||||||
|
|
||||||
|
private readonly FormBrowser form;
|
||||||
|
|
||||||
|
public ContextMenuBrowser(FormBrowser form) : base(form){
|
||||||
|
this.form = form;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||||
|
bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
|
||||||
|
if (isSelecting && !isEditing && TwitterUtils.IsTweetDeckWebsite(frame)){
|
||||||
|
InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Context.Types.HasFlag(ContextInfo.ContextType.Chirp) && !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);
|
||||||
|
|
||||||
|
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
|
||||||
|
|
||||||
|
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
|
||||||
|
globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
|
||||||
|
globalMenu.SetChecked(MenuMute, Config.MuteNotifications);
|
||||||
|
globalMenu.AddSeparator();
|
||||||
|
|
||||||
|
globalMenu.AddItem(MenuSettings, TitleSettings);
|
||||||
|
globalMenu.AddItem(MenuPlugins, TitlePlugins);
|
||||||
|
globalMenu.AddItem(MenuAbout, TitleAboutProgram);
|
||||||
|
|
||||||
|
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){
|
||||||
|
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(commandId){
|
||||||
|
case CefMenuCommand.Reload:
|
||||||
|
form.InvokeAsyncSafe(form.ReloadToTweetDeck);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MenuSettings:
|
||||||
|
form.InvokeAsyncSafe(form.OpenSettings);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MenuAbout:
|
||||||
|
form.InvokeAsyncSafe(form.OpenAbout);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MenuPlugins:
|
||||||
|
form.InvokeAsyncSafe(form.OpenPlugins);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MenuMute:
|
||||||
|
form.InvokeAsyncSafe(ToggleMuteNotifications);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MenuOpenTweetUrl:
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ContextMenu CreateMenu(FormBrowser form){
|
||||||
|
ContextMenu menu = new ContextMenu();
|
||||||
|
|
||||||
|
menu.MenuItems.Add(TitleReloadBrowser, (sender, args) => form.ReloadToTweetDeck());
|
||||||
|
menu.MenuItems.Add(TitleMuteNotifications, (sender, args) => ToggleMuteNotifications());
|
||||||
|
menu.MenuItems.Add("-");
|
||||||
|
menu.MenuItems.Add(TitleSettings, (sender, args) => form.OpenSettings());
|
||||||
|
menu.MenuItems.Add(TitlePlugins, (sender, args) => form.OpenPlugins());
|
||||||
|
menu.MenuItems.Add(TitleAboutProgram, (sender, args) => form.OpenAbout());
|
||||||
|
|
||||||
|
menu.Popup += (sender, args) => {
|
||||||
|
menu.MenuItems[1].Checked = Config.MuteNotifications;
|
||||||
|
form.AnalyticsFile.BrowserContextMenus.Trigger();
|
||||||
|
};
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ToggleMuteNotifications(){
|
||||||
|
Config.MuteNotifications = !Config.MuteNotifications;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
Core/Handling/ContextMenuGuide.cs
Normal file
14
Core/Handling/ContextMenuGuide.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Other.Analytics;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
sealed class ContextMenuGuide : ContextMenuBase{
|
||||||
|
public ContextMenuGuide(AnalyticsFile.IProvider analytics) : base(analytics){}
|
||||||
|
|
||||||
|
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||||
|
model.Clear();
|
||||||
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
AddDebugMenuItems(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
94
Core/Handling/ContextMenuNotification.cs
Normal file
94
Core/Handling/ContextMenuNotification.cs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
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 bool enableCustomMenu;
|
||||||
|
|
||||||
|
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){
|
||||||
|
this.form = form;
|
||||||
|
this.enableCustomMenu = enableCustomMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||||
|
model.Clear();
|
||||||
|
|
||||||
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
|
||||||
|
model.AddItem(CefMenuCommand.Copy, "Copy");
|
||||||
|
model.AddSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
RemoveSeparatorIfLast(model);
|
||||||
|
|
||||||
|
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){
|
||||||
|
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);
|
||||||
|
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
Core/Handling/DragHandlerBrowser.cs
Normal file
35
Core/Handling/DragHandlerBrowser.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using CefSharp;
|
||||||
|
using CefSharp.Enums;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
sealed class DragHandlerBrowser : IDragHandler{
|
||||||
|
private readonly RequestHandlerBrowser requestHandler;
|
||||||
|
|
||||||
|
public DragHandlerBrowser(RequestHandlerBrowser requestHandler){
|
||||||
|
this.requestHandler = requestHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask){
|
||||||
|
void TriggerDragStart(string type, string data = null){
|
||||||
|
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
|
||||||
|
|
||||||
|
if (dragData.IsLink){
|
||||||
|
TriggerDragStart("link", dragData.LinkUrl);
|
||||||
|
}
|
||||||
|
else if (dragData.IsFragment){
|
||||||
|
TriggerDragStart("text", dragData.FragmentText.Trim());
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
TriggerDragStart("unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IList<DraggableRegion> regions){}
|
||||||
|
}
|
||||||
|
}
|
72
Core/Handling/Filters/ResponseFilterBase.cs
Normal file
72
Core/Handling/Filters/ResponseFilterBase.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using CefSharp;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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();
|
||||||
|
}
|
||||||
|
}
|
20
Core/Handling/Filters/ResponseFilterVendor.cs
Normal file
20
Core/Handling/Filters/ResponseFilterVendor.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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(){}
|
||||||
|
}
|
||||||
|
}
|
29
Core/Handling/General/BrowserProcessHandler.cs
Normal file
29
Core/Handling/General/BrowserProcessHandler.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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(){}
|
||||||
|
}
|
||||||
|
}
|
40
Core/Handling/General/FileDialogHandler.cs
Normal file
40
Core/Handling/General/FileDialogHandler.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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.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);
|
||||||
|
callback.Continue(acceptFilters.FindIndex(filter => filter.Equals(ext, StringComparison.OrdinalIgnoreCase)), dialog.FileNames.ToList());
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
callback.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
callback.Dispose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
Core/Handling/General/JavaScriptDialogHandler.cs
Normal file
90
Core/Handling/General/JavaScriptDialogHandler.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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){
|
||||||
|
switch(text.Substring(0, pipe)){
|
||||||
|
case "error": icon = MessageBoxIcon.Error; break;
|
||||||
|
case "warning": icon = MessageBoxIcon.Warning; break;
|
||||||
|
case "info": icon = MessageBoxIcon.Information; break;
|
||||||
|
case "question": icon = MessageBoxIcon.Question; break;
|
||||||
|
default: return new FormMessage(caption, text, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.OnJSBeforeUnload(IWebBrowser browserControl, IBrowser browser, string message, bool isReload, IJsDialogCallback callback){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser){}
|
||||||
|
void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser){}
|
||||||
|
}
|
||||||
|
}
|
34
Core/Handling/General/LifeSpanHandler.cs
Normal file
34
Core/Handling/General/LifeSpanHandler.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling.General{
|
||||||
|
sealed class LifeSpanHandler : ILifeSpanHandler{
|
||||||
|
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
|
||||||
|
switch(targetDisposition){
|
||||||
|
case WindowOpenDisposition.NewBackgroundTab:
|
||||||
|
case WindowOpenDisposition.NewForegroundTab:
|
||||||
|
case WindowOpenDisposition.NewPopup:
|
||||||
|
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){}
|
||||||
|
}
|
||||||
|
}
|
47
Core/Handling/KeyboardHandlerBase.cs
Normal file
47
Core/Handling/KeyboardHandlerBase.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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){
|
||||||
|
browser.ShowDevTools();
|
||||||
|
}
|
||||||
|
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("chrome-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
Core/Handling/KeyboardHandlerBrowser.cs
Normal file
20
Core/Handling/KeyboardHandlerBrowser.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
Core/Handling/KeyboardHandlerNotification.cs
Normal file
44
Core/Handling/KeyboardHandlerNotification.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
89
Core/Handling/RequestHandlerBase.cs
Normal file
89
Core/Handling/RequestHandlerBase.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using CefSharp;
|
||||||
|
using CefSharp.Handler;
|
||||||
|
using TweetDuck.Core.Handling.General;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
class RequestHandlerBase : DefaultRequestHandler{
|
||||||
|
private static readonly Regex TweetDeckResourceUrl = new Regex(@"/dist/(.*?)\.(.*?)\.(css|js)$", RegexOptions.Compiled);
|
||||||
|
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(',')){
|
||||||
|
string[] split = rule.Split('=');
|
||||||
|
|
||||||
|
if (split.Length == 2){
|
||||||
|
string key = split[0];
|
||||||
|
string hash = split[1];
|
||||||
|
|
||||||
|
if (hash.All(chr => char.IsDigit(chr) || (chr >= 'a' && chr <= 'f'))){
|
||||||
|
TweetDeckHashes.Add(key, hash);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw new ArgumentException("Invalid hash characters: "+rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw new ArgumentException("A rule must have exactly one '=' character: "+rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly bool autoReload;
|
||||||
|
|
||||||
|
public RequestHandlerBase(bool autoReload){
|
||||||
|
this.autoReload = autoReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){
|
||||||
|
return LifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||||
|
if (autoReload){
|
||||||
|
browser.Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
Core/Handling/RequestHandlerBrowser.cs
Normal file
65
Core/Handling/RequestHandlerBrowser.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System.Collections.Specialized;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Handling.Filters;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
sealed class RequestHandlerBrowser : RequestHandlerBase{
|
||||||
|
private const string UrlVendorResource = "/dist/vendor";
|
||||||
|
private const string UrlLoadingSpinner = "/backgrounds/spinner_blue";
|
||||||
|
|
||||||
|
public string BlockNextUserNavUrl { get; set; }
|
||||||
|
|
||||||
|
public RequestHandlerBrowser() : base(true){}
|
||||||
|
|
||||||
|
public 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 = TwitterUtils.TweetDeckURL; // redirect plain twitter.com requests, fixes bugs with login 2FA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (request.ResourceType == ResourceType.Script){
|
||||||
|
string url = request.Url;
|
||||||
|
|
||||||
|
if (url.Contains("analytics.")){
|
||||||
|
callback.Dispose();
|
||||||
|
return CefReturnValue.Cancel;
|
||||||
|
}
|
||||||
|
else if (url.Contains(UrlVendorResource)){
|
||||||
|
NameValueCollection headers = request.Headers;
|
||||||
|
headers["Accept-Encoding"] = "identity";
|
||||||
|
request.Headers = headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect){
|
||||||
|
if (userGesture && request.TransitionType == TransitionType.LinkClicked){
|
||||||
|
bool block = request.Url == BlockNextUserNavUrl;
|
||||||
|
BlockNextUserNavUrl = string.Empty;
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||||
|
if (request.ResourceType == ResourceType.Image && request.Url.Contains(UrlLoadingSpinner)){
|
||||||
|
request.Url = TwitterUtils.LoadingSpinner.Url;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnResourceResponse(browserControl, browser, frame, request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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.ResponseHeaders["Content-Length"], out int totalBytes)){
|
||||||
|
return new ResponseFilterVendor(totalBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
Core/Handling/ResourceHandlerFactory.cs
Normal file
43
Core/Handling/ResourceHandlerFactory.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
sealed class ResourceHandlerFactory : IResourceHandlerFactory{
|
||||||
|
public bool HasHandlers => !handlers.IsEmpty;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, IResourceHandler> handlers = new ConcurrentDictionary<string, IResourceHandler>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request){
|
||||||
|
try{
|
||||||
|
return handlers.TryGetValue(request.Url, out IResourceHandler handler) ? handler : null;
|
||||||
|
}finally{
|
||||||
|
request.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// registration
|
||||||
|
|
||||||
|
public bool RegisterHandler(string url, IResourceHandler handler){
|
||||||
|
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
||||||
|
handlers.AddOrUpdate(uri.AbsoluteUri, handler, (key, prev) => handler);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RegisterHandler(ResourceLink link){
|
||||||
|
return RegisterHandler(link.Url, link.Handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UnregisterHandler(string url){
|
||||||
|
return handlers.TryRemove(url, out IResourceHandler _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UnregisterHandler(ResourceLink link){
|
||||||
|
return UnregisterHandler(link.Url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
Core/Handling/ResourceHandlerNotification.cs
Normal file
63
Core/Handling/ResourceHandlerNotification.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
sealed class ResourceHandlerNotification : IResourceHandler{
|
||||||
|
private readonly NameValueCollection headers = new NameValueCollection(0);
|
||||||
|
private MemoryStream dataIn;
|
||||||
|
|
||||||
|
public void SetHTML(string html){
|
||||||
|
dataIn?.Dispose();
|
||||||
|
dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose(){
|
||||||
|
if (dataIn != null){
|
||||||
|
dataIn.Dispose();
|
||||||
|
dataIn = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback){
|
||||||
|
callback.Continue();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl){
|
||||||
|
redirectUrl = null;
|
||||||
|
|
||||||
|
response.MimeType = "text/html";
|
||||||
|
response.StatusCode = 200;
|
||||||
|
response.StatusText = "OK";
|
||||||
|
response.ResponseHeaders = headers;
|
||||||
|
responseLength = dataIn?.Length ?? -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback){
|
||||||
|
callback.Dispose();
|
||||||
|
|
||||||
|
try{
|
||||||
|
int length = (int)dataIn.Length;
|
||||||
|
|
||||||
|
dataIn.CopyTo(dataOut, length);
|
||||||
|
bytesRead = length;
|
||||||
|
return true;
|
||||||
|
}catch{ // catch IOException, possibly NullReferenceException if dataIn is null
|
||||||
|
bytesRead = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IResourceHandler.CanGetCookie(Cookie cookie){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IResourceHandler.CanSetCookie(Cookie cookie){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IResourceHandler.Cancel(){}
|
||||||
|
}
|
||||||
|
}
|
76
Core/Management/BrowserCache.cs
Normal file
76
Core/Management/BrowserCache.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Management{
|
||||||
|
static class BrowserCache{
|
||||||
|
public static string CacheFolder => Path.Combine(Program.StoragePath, "Cache");
|
||||||
|
|
||||||
|
private static bool ClearOnExit;
|
||||||
|
private static Timer AutoClearTimer;
|
||||||
|
|
||||||
|
private static long CalculateCacheSize(){
|
||||||
|
return new DirectoryInfo(CacheFolder).EnumerateFiles().Select(file => {
|
||||||
|
try{
|
||||||
|
return file.Length;
|
||||||
|
}catch{
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
}).Sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GetCacheSize(Action<Task<long>> callbackBytes){
|
||||||
|
Task<long> task = new Task<long>(CalculateCacheSize);
|
||||||
|
task.ContinueWith(callbackBytes);
|
||||||
|
task.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RefreshTimer(){
|
||||||
|
bool shouldRun = Program.Config.System.ClearCacheAutomatically && !ClearOnExit;
|
||||||
|
|
||||||
|
if (!shouldRun && AutoClearTimer != null){
|
||||||
|
AutoClearTimer.Dispose();
|
||||||
|
AutoClearTimer = null;
|
||||||
|
}
|
||||||
|
else if (shouldRun && AutoClearTimer == null){
|
||||||
|
AutoClearTimer = new Timer(state => {
|
||||||
|
if (AutoClearTimer != null){
|
||||||
|
try{
|
||||||
|
if (CalculateCacheSize() >= Program.Config.System.ClearCacheThreshold*1024L*1024L){
|
||||||
|
SetClearOnExit();
|
||||||
|
}
|
||||||
|
}catch(Exception){
|
||||||
|
// TODO should probably log errors and report them at some point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null, TimeSpan.FromSeconds(30), TimeSpan.FromHours(4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetClearOnExit(){
|
||||||
|
ClearOnExit = true;
|
||||||
|
RefreshTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TryClearNow(){
|
||||||
|
try{
|
||||||
|
Directory.Delete(CacheFolder, true);
|
||||||
|
}catch{
|
||||||
|
// welp, too bad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Exit(){
|
||||||
|
if (AutoClearTimer != null){
|
||||||
|
AutoClearTimer.Dispose();
|
||||||
|
AutoClearTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ClearOnExit){
|
||||||
|
TryClearNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
161
Core/Management/ContextInfo.cs
Normal file
161
Core/Management/ContextInfo.cs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Management{
|
||||||
|
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(ChirpInfo);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
215
Core/Management/ProfileManager.cs
Normal file
215
Core/Management/ProfileManager.cs
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetDuck.Plugins.Enums;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Management{
|
||||||
|
sealed class ProfileManager{
|
||||||
|
private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies");
|
||||||
|
private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp");
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum Items{
|
||||||
|
None = 0,
|
||||||
|
UserConfig = 1,
|
||||||
|
SystemConfig = 2,
|
||||||
|
Session = 4,
|
||||||
|
PluginData = 8,
|
||||||
|
All = UserConfig|SystemConfig|Session|PluginData
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly string file;
|
||||||
|
private readonly PluginManager plugins;
|
||||||
|
|
||||||
|
public ProfileManager(string file, PluginManager plugins){
|
||||||
|
this.file = file;
|
||||||
|
this.plugins = plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Export(Items items){
|
||||||
|
try{
|
||||||
|
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
|
||||||
|
if (items.HasFlag(Items.UserConfig)){
|
||||||
|
stream.WriteFile("config", Program.UserConfigFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.HasFlag(Items.SystemConfig)){
|
||||||
|
stream.WriteFile("system", Program.SystemConfigFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.HasFlag(Items.PluginData)){
|
||||||
|
stream.WriteFile("plugin.config", Program.PluginConfigFilePath);
|
||||||
|
|
||||||
|
foreach(Plugin plugin in plugins.Plugins){
|
||||||
|
foreach(PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))){
|
||||||
|
try{
|
||||||
|
stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
|
||||||
|
}catch(ArgumentOutOfRangeException e){
|
||||||
|
FormMessage.Warning("Export Profile", "Could not include a plugin file in the export. "+e.Message, FormMessage.OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.HasFlag(Items.Session)){
|
||||||
|
stream.WriteFile("cookies", CookiesPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Items FindImportItems(){
|
||||||
|
Items items = Items.None;
|
||||||
|
|
||||||
|
try{
|
||||||
|
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
|
||||||
|
string key;
|
||||||
|
|
||||||
|
while((key = stream.SkipFile()) != null){
|
||||||
|
switch(key){
|
||||||
|
case "config":
|
||||||
|
items |= Items.UserConfig;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "system":
|
||||||
|
items |= Items.SystemConfig;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "plugin.config":
|
||||||
|
case "plugin.data":
|
||||||
|
items |= Items.PluginData;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "cookies":
|
||||||
|
items |= Items.Session;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(Exception){
|
||||||
|
items = Items.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Import(Items items){
|
||||||
|
try{
|
||||||
|
HashSet<string> missingPlugins = new HashSet<string>();
|
||||||
|
|
||||||
|
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
|
||||||
|
CombinedFileStream.Entry entry;
|
||||||
|
|
||||||
|
while((entry = stream.ReadFile()) != null){
|
||||||
|
switch(entry.KeyName){
|
||||||
|
case "config":
|
||||||
|
if (items.HasFlag(Items.UserConfig)){
|
||||||
|
entry.WriteToFile(Program.UserConfigFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "system":
|
||||||
|
if (items.HasFlag(Items.SystemConfig)){
|
||||||
|
entry.WriteToFile(Program.SystemConfigFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "plugin.config":
|
||||||
|
if (items.HasFlag(Items.PluginData)){
|
||||||
|
entry.WriteToFile(Program.PluginConfigFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "plugin.data":
|
||||||
|
if (items.HasFlag(Items.PluginData)){
|
||||||
|
string[] value = entry.KeyValue;
|
||||||
|
|
||||||
|
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
|
||||||
|
|
||||||
|
if (!plugins.IsPluginInstalled(value[0])){
|
||||||
|
missingPlugins.Add(value[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "cookies":
|
||||||
|
if (items.HasFlag(Items.Session)){
|
||||||
|
entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingPlugins.Count > 0){
|
||||||
|
FormMessage.Information("Profile Import", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Profile Import", "An exception happened while importing TweetDuck profile.", true, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ImportCookies(){
|
||||||
|
if (File.Exists(TempCookiesPath)){
|
||||||
|
try{
|
||||||
|
if (File.Exists(CookiesPath)){
|
||||||
|
File.Delete(CookiesPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Move(TempCookiesPath, CookiesPath);
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Profile Import Error", "Could not import the cookie file to restore login session.", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DeleteCookies(){
|
||||||
|
try{
|
||||||
|
if (File.Exists(CookiesPath)){
|
||||||
|
File.Delete(CookiesPath);
|
||||||
|
}
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Session Reset Error", "Could not remove the cookie file to reset the login session.", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
|
||||||
|
if (Directory.Exists(root)){
|
||||||
|
int rootLength = root.Length;
|
||||||
|
return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo(fullPath, rootLength));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return Enumerable.Empty<PathInfo>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class PathInfo{
|
||||||
|
public string Full { get; }
|
||||||
|
public string Relative { get; }
|
||||||
|
|
||||||
|
public PathInfo(string fullPath, int rootLength){
|
||||||
|
this.Full = fullPath;
|
||||||
|
this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
213
Core/Management/VideoPlayer.cs
Normal file
213
Core/Management/VideoPlayer.cs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetLib.Communication;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Management{
|
||||||
|
sealed class VideoPlayer : IDisposable{
|
||||||
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
|
public bool Running => currentInstance != null && currentInstance.Running;
|
||||||
|
|
||||||
|
public event EventHandler ProcessExited;
|
||||||
|
|
||||||
|
private readonly FormBrowser owner;
|
||||||
|
|
||||||
|
private Instance currentInstance;
|
||||||
|
private bool isClosing;
|
||||||
|
|
||||||
|
public VideoPlayer(FormBrowser owner){
|
||||||
|
this.owner = owner;
|
||||||
|
this.owner.FormClosing += owner_FormClosing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Launch(string url, string username){
|
||||||
|
if (Running){
|
||||||
|
Destroy();
|
||||||
|
isClosing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
DuplexPipe.Server pipe = DuplexPipe.CreateServer();
|
||||||
|
pipe.DataIn += pipe_DataIn;
|
||||||
|
|
||||||
|
Process process;
|
||||||
|
|
||||||
|
if ((process = Process.Start(new ProcessStartInfo{
|
||||||
|
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
|
||||||
|
Arguments = $"{owner.Handle} {(int)Math.Floor(100F*owner.GetDPIScale())} {Config.VideoPlayerVolume} \"{url}\" \"{pipe.GenerateToken()}\"",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true
|
||||||
|
})) != null){
|
||||||
|
currentInstance = new Instance(process, pipe, url, username);
|
||||||
|
|
||||||
|
process.EnableRaisingEvents = true;
|
||||||
|
process.Exited += process_Exited;
|
||||||
|
|
||||||
|
process.BeginOutputReadLine();
|
||||||
|
process.OutputDataReceived += process_OutputDataReceived;
|
||||||
|
|
||||||
|
pipe.DisposeToken();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
pipe.DataIn -= pipe_DataIn;
|
||||||
|
pipe.Dispose();
|
||||||
|
}
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendKeyEvent(Keys key){
|
||||||
|
currentInstance?.Pipe.Write("key", ((int)key).ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
|
||||||
|
owner.InvokeSafe(() => {
|
||||||
|
switch(e.Key){
|
||||||
|
case "vol":
|
||||||
|
if (int.TryParse(e.Data, out int volume) && volume != Config.VideoPlayerVolume){
|
||||||
|
Config.VideoPlayerVolume = volume;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "download":
|
||||||
|
if (currentInstance != null){
|
||||||
|
owner.AnalyticsFile.DownloadedVideos.Trigger();
|
||||||
|
TwitterUtils.DownloadVideo(currentInstance.Url, currentInstance.Username);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "rip":
|
||||||
|
currentInstance?.Dispose();
|
||||||
|
currentInstance = null;
|
||||||
|
|
||||||
|
isClosing = false;
|
||||||
|
TriggerProcessExitEventUnsafe();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close(){
|
||||||
|
if (currentInstance != null){
|
||||||
|
if (isClosing){
|
||||||
|
Destroy();
|
||||||
|
isClosing = false;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
isClosing = true;
|
||||||
|
currentInstance.Process.Exited -= process_Exited;
|
||||||
|
currentInstance.Pipe.Write("die");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose(){
|
||||||
|
ProcessExited = null;
|
||||||
|
|
||||||
|
isClosing = true;
|
||||||
|
Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Destroy(){
|
||||||
|
if (currentInstance != null){
|
||||||
|
currentInstance.KillAndDispose();
|
||||||
|
currentInstance = null;
|
||||||
|
|
||||||
|
TriggerProcessExitEventUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void owner_FormClosing(object sender, FormClosingEventArgs e){
|
||||||
|
if (currentInstance != null){
|
||||||
|
currentInstance.Process.Exited -= process_Exited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process_OutputDataReceived(object sender, DataReceivedEventArgs e){
|
||||||
|
if (!string.IsNullOrEmpty(e.Data)){
|
||||||
|
Program.Reporter.LogVerbose("[VideoPlayer] "+e.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process_Exited(object sender, EventArgs e){
|
||||||
|
if (currentInstance == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int exitCode = currentInstance.Process.ExitCode;
|
||||||
|
string url = currentInstance.Url;
|
||||||
|
|
||||||
|
currentInstance.Dispose();
|
||||||
|
currentInstance = null;
|
||||||
|
|
||||||
|
switch(exitCode){
|
||||||
|
case 3: // CODE_LAUNCH_FAIL
|
||||||
|
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
|
||||||
|
BrowserUtils.OpenExternalBrowser(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4: // CODE_MEDIA_ERROR
|
||||||
|
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
|
||||||
|
BrowserUtils.OpenExternalBrowser(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TriggerProcessExitEventUnsafe(){
|
||||||
|
ProcessExited?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Instance : IDisposable{
|
||||||
|
public bool Running{
|
||||||
|
get{
|
||||||
|
Process.Refresh();
|
||||||
|
return !Process.HasExited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Process Process { get; }
|
||||||
|
public DuplexPipe.Server Pipe { get; }
|
||||||
|
|
||||||
|
public string Url { get; }
|
||||||
|
public string Username { get; }
|
||||||
|
|
||||||
|
public Instance(Process process, DuplexPipe.Server pipe, string url, string username){
|
||||||
|
this.Process = process;
|
||||||
|
this.Pipe = pipe;
|
||||||
|
this.Url = url;
|
||||||
|
this.Username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void KillAndDispose(){
|
||||||
|
try{
|
||||||
|
Process.Kill();
|
||||||
|
}catch{
|
||||||
|
// kill me instead then
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose(){
|
||||||
|
Process.Dispose();
|
||||||
|
Pipe.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
Core/Notification/Example/FormNotificationExample.cs
Normal file
68
Core/Notification/Example/FormNotificationExample.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Notification.Example{
|
||||||
|
sealed class FormNotificationExample : FormNotificationMain{
|
||||||
|
public override bool RequiresResize => true;
|
||||||
|
protected override bool CanDragWindow => Config.NotificationPosition == TweetNotification.Position.Custom;
|
||||||
|
|
||||||
|
protected override FormBorderStyle NotificationBorderStyle{
|
||||||
|
get{
|
||||||
|
if (Config.NotificationSize == TweetNotification.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 TweetNotification exampleNotification;
|
||||||
|
|
||||||
|
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
|
||||||
|
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||||
|
|
||||||
|
string exampleTweetHTML = ScriptLoader.LoadResourceSilent("pages/example.html")?.Replace("{avatar}", TweetNotification.AppLogo.Url) ?? string.Empty;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
exampleNotification = new TweetNotification(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,10 +1,21 @@
|
|||||||
namespace TweetDuck.Browser.Notification {
|
namespace TweetDuck.Core.Notification {
|
||||||
partial class FormNotificationBase {
|
partial class FormNotificationBase {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private System.ComponentModel.IContainer components = null;
|
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
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -23,7 +34,7 @@
|
|||||||
this.BackColor = System.Drawing.SystemColors.Control;
|
this.BackColor = System.Drawing.SystemColors.Control;
|
||||||
this.ClientSize = new System.Drawing.Size(284, 122);
|
this.ClientSize = new System.Drawing.Size(284, 122);
|
||||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||||
this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation;
|
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
|
||||||
this.MaximizeBox = false;
|
this.MaximizeBox = false;
|
||||||
this.MinimizeBox = false;
|
this.MinimizeBox = false;
|
||||||
this.Name = "FormNotification";
|
this.Name = "FormNotification";
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user