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

Compare commits

..

934 Commits

Author SHA1 Message Date
1c16187346 Release 1.19 2020-06-08 16:58:58 +02:00
2fe058d9cb Fix crash if reloading plugins reports errors before the main window appears 2020-06-08 09:03:22 +02:00
cefdadd53a Update installer to remove native_blob.bin 2020-06-07 18:01:40 +02:00
c21c10df63 Add a way to exclude <link> tags from being auto-added to notifications 2020-06-07 15:45:55 +02:00
b4d359d30c Add TDPF_createStorage as a plugin replacement for localStorage 2020-06-07 11:33:58 +02:00
1e8c62ac25 Add plugin object validation to TDPF functions 2020-06-07 10:28:55 +02:00
c578f36644 Block CSP reports 2020-06-06 10:00:25 +02:00
c973d0cff4 Fix missing resources from csproj & reorganize it 2020-06-06 09:10:54 +02:00
0c3d9ae46a Refactor main JS code & split code.js into multiple files 2020-06-06 09:01:50 +02:00
a834e8b6a2 Fix broken post-build script import handling 2020-06-06 07:36:03 +02:00
9f5580d983 Eliminate a few post-launch frames of misaligned TweetDeck load icon 2020-06-06 07:36:03 +02:00
e94e3cecf8 Let JS continue even if jQuery or bridge objects are missing 2020-06-06 07:36:02 +02:00
1991f7f50f Bypass 'tdp://' CORS without AddCrossOriginWhitelistEntry 2020-06-06 07:36:02 +02:00
9eb4e623e7 Work around broken smooth scrolling in notifications 2020-06-06 07:36:02 +02:00
ad28d2279f Fix runtime errors & minor tweaks after updating CefSharp 2020-06-05 06:20:19 +02:00
1e3de31fc3 Fix compile errors after updating CefSharp 2020-06-05 06:20:19 +02:00
f85bd41b96 Update CefSharp to 81 2020-06-05 06:20:19 +02:00
563124b68c Fix ResourceHandlerNotification buffer being unnecessarily large 2020-06-05 06:19:50 +02:00
63de08c635 Fix plugin code running in blank notifications from previous commit 2020-06-04 04:33:50 +02:00
8a0a215443 Reduce notification code hackery 2020-06-03 00:57:24 +02:00
f1b7cd633e Refactor WindowsUtils.OpenAssociatedProgram & update installer code 2020-06-02 23:56:03 +02:00
458eeeccda Add tdp:// scheme for plugins (with 'root/' to access root files) 2020-06-02 12:31:34 +02:00
464e758b94 Ensure window.jQuery is available alongside window.$ 2020-06-02 06:45:51 +02:00
4c61047e9b Fix large notification HTML overflowing CEF buffer and silently crashing 2020-06-02 04:05:29 +02:00
1bf9e7fb56 Release 1.18.6 2020-05-16 19:15:07 +02:00
ca69554f37 Fix docked composer not re-focusing after image upload 2020-05-16 18:08:51 +02:00
cf7029037e Fix twitter login page after it was migrated and broke detection 2020-05-16 18:02:37 +02:00
418388b0ab Merge branch 'master' of https://github.com/chylex/TweetDuck 2020-05-16 17:54:00 +02:00
a0f8689d4f Fix TweetDeck bug where docked composer loses focus after Alt+Tab
Closes #297
2020-05-16 17:53:50 +02:00
95007bdd26 Update FUNDING.yml 2020-05-13 23:23:34 +02:00
ab91540deb Move some TweetLib.Core files into different namespaces 2020-05-04 17:13:20 +02:00
b2ebb984f8 Release 1.18.5 2020-05-04 16:20:44 +02:00
f7e9ad74d1 Fix stuck processes after closing the app
Closes #294
2020-05-04 13:45:56 +02:00
d48da3d51c Add option for notification window opacity 2020-04-27 10:20:07 +02:00
76d22554c5 Make trackbars in settings wider 2020-04-27 10:03:51 +02:00
6eaafd883b Release 1.18.4 2020-04-25 06:35:43 +02:00
5961a80b23 Fix blank notifications on certain hardware configurations w/ disabled acceleration
Closes #274
2020-04-25 05:47:17 +02:00
f41c6fe533 Unify all exe & dll versions 2020-04-25 05:05:05 +02:00
65b8efe13c Fix non-quoted tweet links opening in browser despite also opening in the column
Closes #273
2020-04-25 03:48:20 +02:00
89529f9c96 Add $TD.makeGetRequest and fix template plugin AJAX
Closes #272
2020-04-25 03:15:52 +02:00
e90f6ebc63 Add 'Copy image' to context menu
Closes #287
2020-04-25 02:43:12 +02:00
5888d540a6 Move clipboard utils into ClipboardManager & add SetImage 2020-04-25 02:33:39 +02:00
ae8b740600 Reorganize namespaces in main project 2020-04-25 02:16:57 +02:00
ab4e2f5bda Add error message to template plugin when AJAX request fails 2020-04-25 00:19:29 +02:00
1091b6d232 Fix IDE warnings (dispose, lang features) & nullability settings 2020-04-24 23:14:22 +02:00
fc89744238 Apparently video duration minus 0.05 causes complete hangs in short videos... 2020-03-07 01:40:07 +01:00
34e049a002 Work around video player black screen when looping & reduce player polling 2020-03-05 13:26:08 +01:00
980bf2c307 Release 1.18.3 2020-02-16 18:04:21 +01:00
762aea1e20 Add option to set first day of week in date picker
Closes #276
2020-02-16 15:47:26 +01:00
c1aefc7163 Add a way to register callbacks when $TDX property object gets updated 2020-02-16 14:39:15 +01:00
9480ba26e0 Move system tray options to a separate tab & reorganize General tab 2020-02-16 14:39:15 +01:00
c2c9160ed9 Allow dragging Twitter account links onto the app to view their profile
Closes #288
2020-02-16 11:35:20 +01:00
175b067a17 Add missing tooltip on custom video player selection 2020-02-16 11:27:46 +01:00
9d8656ca20 Add option for whether dev tools window should stay on top 2020-02-16 11:15:25 +01:00
0863001c80 Add option for custom video player executable & arguments 2020-02-15 21:17:44 +01:00
0ee22a30ad Add option for custom browser args w/ new dialog & disallow quotes in URLs 2020-02-15 20:26:07 +01:00
447697ba45 Add StringUtils.NullIfEmpty & update existing code to use it 2020-02-15 17:25:00 +01:00
aea77ff909 Make dev tools dialog a proper window that appears in taskbar 2020-02-15 15:18:16 +01:00
af5da76f72 Make video player open tweet URL instead of video URL if playback fails 2020-02-15 14:34:19 +01:00
a369c65451 Release 1.18.2 2019-10-23 02:26:53 +02:00
318f65f187 Merge branch 'master' of https://github.com/chylex/TweetDuck 2019-10-17 13:04:19 +02:00
1cd60e831c Add a few missing translation languages 2019-10-12 18:30:12 +02:00
b988959eaa Only activate mouse hook while cursor is over the notification window 2019-10-07 04:49:39 +02:00
1eb1f9848a Prepare login/logout page scripts and styles for Twitter redesign & minor fixes 2019-10-07 03:01:17 +02:00
7f6cc0da01 Fix mouse back/forward button triggering navigation if history wasn't empty
Closes #286
2019-10-06 14:47:49 +02:00
19fcb69525 Fix prebuild event not killing hung browser processes reliably 2019-09-05 01:27:36 +02:00
22cef0a44c Fix C# version in secondary projects 2019-09-05 00:48:18 +02:00
2459d31bff Remove RegexOptions.Compiled where not needed 2019-09-05 00:16:25 +02:00
19f104239a Fix missing spaces in C#/F# code 2019-08-23 01:56:31 +02:00
bd0be65038 Minor refactoring & removal of unnecessary code 2019-08-23 01:56:18 +02:00
bbb7907e54 Move LockManager to TweetLib.Core & remove WindowsUtils.CurrentProcessID 2019-08-22 06:29:32 +02:00
a6963a18d4 Move debug resource hot swap into a separate class 2019-08-21 10:31:30 +02:00
92716ea3e0 Move URL-related code from UrlUtils & TwitterUtils to TwitterUrls 2019-08-21 10:12:19 +02:00
aec4c1feea Move TweetNotification to TweetLib.Core as DesktopNotification 2019-08-21 10:12:19 +02:00
d505b3305b Initial refactoring of ScriptLoader & making it accessible in TweetLib.Core 2019-08-21 10:12:19 +02:00
a34a02e14d Generalize PluginListFlowLayout and move it 2019-07-15 00:49:28 +02:00
26d2d7a51e Move PluginManager to Core lib & refactor plugin enums 2019-07-14 20:44:25 +02:00
c2f7e52d13 Add IAppSystemHandler w/ OpenFileExplorer and update existing code to use it 2019-07-14 20:44:25 +02:00
de68d8934d Add IScriptExecutor w/ implementation for CefSharp browser 2019-07-14 17:15:14 +02:00
4fdf7fc958 Release 1.18.1 2019-07-13 19:50:49 +02:00
42a5e72f19 Revert README change & lock Inno Setup to version 5.6.1 2019-07-13 19:39:08 +02:00
f7359ebc8a Update README with instructions for fixing Inno Download Plugin 2019-07-13 18:44:02 +02:00
f395ac53dc Fix wrong colors in dropdown menus w/ black theme 2019-07-13 18:22:38 +02:00
1113e0b559 Fix new image url parser not checking if an extension already exists 2019-07-13 18:16:11 +02:00
5e3bd31862 Delete corrupted downloads after an error 2019-07-13 18:10:16 +02:00
11d978dad1 Fix GIF thumbnails not loading after Twitter changed image urls
Closes #271
2019-07-13 17:51:16 +02:00
f7961024d7 Enable popup for linking another account
Closes #269
2019-07-13 06:17:30 +02:00
72973a8707 Restore smooth scrolling in columns
Fixes #251
2019-07-13 06:07:01 +02:00
68254f48d5 Fix TweetDeck bug with broken DM image previews
References #271
2019-07-13 00:48:53 +02:00
eac4f30c50 Support new image urls & fix missing filename features w/o Best Image Quality
Fixes #270
2019-07-13 00:40:27 +02:00
25680fa980 Add StringUtils.SplitInTwo & use it in RequestHandlerBase 2019-07-12 22:17:29 +02:00
ff5e1da14d Fix wrong 'X columns on screen' width calculation after a TweetDeck update 2019-06-03 11:30:14 +02:00
95afff7879 Update F# compiler location 2019-06-03 10:33:57 +02:00
50bd526025 Continue refactoring and moving plugin code 2019-05-27 19:46:39 +02:00
108a0fefc3 Fix PluginManager crashing after error(s) during plugin execution 2019-05-27 19:38:53 +02:00
dd8c5d27be Update code to use C# 8 switch expression 2019-05-27 16:04:08 +02:00
b2937bc776 Fix broken image upload dialog in new composer 2019-05-27 12:37:30 +02:00
4d8e764211 Release 1.18 2019-05-26 21:29:46 +02:00
544b8664fd Add edit-design plugin option to set composer/drawer size 2019-05-26 18:41:23 +02:00
d0610865bd Fix wrong background color in screenshots 2019-05-26 18:12:40 +02:00
ebc0b51590 Merge branch 'master' of https://github.com/chylex/TweetDuck 2019-05-26 18:03:17 +02:00
4487f1169e Fix composer input refocus & emoji keyboard broken after switching composers 2019-05-26 18:02:11 +02:00
85559b6083 Fix and refactor 'Stay open' pin, that was broken after composer update 2019-05-26 18:01:47 +02:00
1056273c57 Add a custom JS event when the old composer is activated 2019-05-26 17:58:58 +02:00
61af2ebc8b Fix template panel not hiding when switching to different drawer/new composer 2019-05-26 17:57:23 +02:00
9121c86656 Update README (drop VS 2017, update support section) 2019-05-26 15:03:16 +02:00
1ccefe853a Update .NET & begin refactoring code into a core lib (#264)
* Switch to .NET Framework 4.7.2 & C# 8.0, update libraries

* Add TweetLib.Core project targeting .NET Standard 2.0

* Enable reference nullability checks for TweetLib.Core

* Move a bunch of utility classes into TweetLib.Core & refactor

* Partially move TweetDuck plugin & update system to TweetLib.Core

* Move some constants and CultureInfo setup to TweetLib.Core

* Move some configuration classes to TweetLib.Core

* Minor refactoring and warning suppression

* Add App to TweetLib.Core

* Add IAppErrorHandler w/ implementation

* Continue moving config, plugin, and update classes to TweetLib.Core

* Fix a few nullability checks

* Update installers to check for .NET Framework 4.7.2
2019-05-26 14:55:12 +02:00
aca438b837 Create FUNDING.yml 2019-05-25 10:17:51 +02:00
7210c29cd8 Update readme (VS 2019, CefSharp version, remove MyGet reference) 2019-05-08 13:13:09 +02:00
26d90c0c9b Work around missing culture codes on Wine 2019-05-08 12:44:16 +02:00
a03b222a95 Fix emoji keyboard button not working after re-enabling w/ compose drawer open
Closes #256
2019-04-04 20:05:52 +02:00
7944a24d3c Release 1.17.4 2019-03-08 19:29:26 +01:00
cc8459c759 Fix clear-columns plugin nav button to match new TweetDeck style 2019-03-08 19:25:39 +01:00
10074ff92c Fix various alignment issues with the verified badge 2019-03-08 18:57:00 +01:00
173f25bebc Add option to disable automatic DM input focus
Closes #253
2019-03-08 18:16:37 +01:00
31680fc4ae Fix colors in retweet dialog w/ black theme 2019-03-07 19:12:35 +01:00
e937d43614 Fix broken compose drawer hooks after a recent TweetDeck update 2019-03-07 19:03:30 +01:00
20e29a7975 Release 1.17.3 2019-01-28 23:59:32 +01:00
ef815dabce Add verbose logging controlled by command line flag & update existing calls 2019-01-28 23:43:52 +01:00
1fb133e6b8 Make TweetDeck resource freezing a command line argument 2019-01-28 23:17:33 +01:00
50b58cd6a6 Add keyboard shortcut to open dev tools (Ctrl+Shift+I) 2019-01-28 18:43:55 +01:00
01485d7ef9 Add a base class for browser keyboard handling 2019-01-28 18:42:27 +01:00
b17c6a5ac7 Safeguard video player to avoid showing overlay if the URL is null 2019-01-28 17:43:53 +01:00
d2ed2b4a00 Force video player UI layout update to work around an edge case 2019-01-28 17:18:05 +01:00
710a7524a1 Kill subprocess if it doesn't exit after the app is closed 2019-01-23 15:28:05 +01:00
2be46464d6 Release 1.17.2 2018-11-23 06:19:43 +01:00
8d536a6734 Fix video player seek bar resizing when clipped & adjust min window size 2018-11-23 04:13:39 +01:00
250d502238 Add a compact layout for video player controls if the window is small
Closes #245
2018-11-23 03:03:58 +01:00
e8de7266d0 Fix cursor staying on 'resize' when moved over minimum size video player 2018-11-23 01:31:01 +01:00
9414f372d7 Fix video player size with a small window on high DPI 2018-11-23 00:45:02 +01:00
b0f9de67cf Release 1.7.1 2018-11-21 04:41:18 +01:00
9b082e114e Redirect plain twitter.com requests to TD to fix 2FA bug 2018-11-20 20:58:04 +01:00
816a5334ac Make the fix to visible scrollbar when loading TweetDeck more reliable 2018-11-20 20:56:52 +01:00
15a4e10da9 Hide the Manage Options dialog when cancelling profile import from login page 2018-11-20 20:44:44 +01:00
01b9302b0c Fix colors in introduction dialog 2018-11-20 20:13:18 +01:00
442126a11a Rewrite login/logout page CSS handling to fix broken 2FA styles
Closes #218
2018-11-20 20:07:42 +01:00
a9c140c0fc Fix video player seek bar tooltip not disappearing when cursor moves outside 2018-11-20 18:30:06 +01:00
97ad7a3e68 Fix video player bug where playback freezes for ~3s on non-primary screen 2018-11-20 18:04:34 +01:00
7d737eefb6 Fix video player's minimum size 2018-11-20 17:33:09 +01:00
4ac05b38d3 Fix column loading spinner color when using black theme 2018-11-20 15:25:26 +01:00
651bbbb672 Fix crash when showing a browser error message
Closes #244
2018-11-20 14:55:40 +01:00
52da4d8687 Release 1.17 2018-11-16 22:06:47 +01:00
36063ae76a Fix Visual Studio being stupid 2018-11-15 10:16:27 +01:00
2fcec2d2cd Update CefSharp to 67 (release) 2018-11-14 22:53:55 +01:00
762a7fdfb7 Disable compression on vendor.js as the x-ton-expected-size header isn't sometimes sent
Closes #241
2018-11-14 19:22:49 +01:00
cd7aeaeed2 Create and use a custom resource handler factory 2018-11-14 18:47:19 +01:00
6f414d312c Clear cache after each update 2018-11-10 06:20:22 +01:00
1b5304efb7 Release 1.16.3 2018-11-07 11:55:37 +01:00
d59375308f Work around clear-columns plugin reappearing after being disabled
Closes #240
2018-11-07 11:16:44 +01:00
8c9509a906 Fix broken colors of plugin elements with dark theme 2018-11-07 10:27:08 +01:00
fb86d8f3a8 Fix broken black theme colors 2018-11-07 10:18:24 +01:00
50e909cb3d Move debug TweetDeck resource freezing and update it to support CSS 2018-11-07 05:45:20 +01:00
2f54edf7e7 Fix missing <body> margin reset 2018-11-07 00:26:43 +01:00
c251603e1e Fix broken Arial font override 2018-11-07 00:26:22 +01:00
4476edb6c3 Release 1.16.2 2018-10-18 22:31:28 +02:00
28fc67660f Fix border color of large timeline cards 2018-10-18 22:30:11 +02:00
6e8b5a5ce5 Bypass t.co in new timeline cards 2018-10-18 21:26:03 +02:00
e53681416f Fix TweetDeck update breaking theme setting in edit-design plugin 2018-10-18 20:44:09 +02:00
acb5e184e8 Update styles for new timeline cards (rounded borders, black theme colors) 2018-10-18 20:36:42 +02:00
bdbafb3e5c Fix rounded borders in media badges and sensitive media overlay 2018-10-02 01:18:43 +02:00
ac70cf87c6 Fix slight misalignments in tweet composer elements when using old icons 2018-09-27 22:03:00 +02:00
93de835ab4 Fix rounded borders in composer (media elements & media description dialog) 2018-09-27 22:01:34 +02:00
2ea38b88ce Release 1.16.1 2018-08-28 21:11:31 +02:00
2c4f2be57d Support system font in notifications, but revert to Arial everywhere by default 2018-08-28 21:06:53 +02:00
fa4beea425 Reset script cache when holding Shift during browser reload 2018-08-24 16:34:55 +02:00
7a976edc82 Ignore cached files for updates and viewed images if their contents are empty 2018-08-23 20:08:34 +02:00
bb22c35221 Fix broken context menu options for images in DMs
Closes #238
2018-08-23 19:51:43 +02:00
ff3dc59016 Move 'View image in photo viewer' handling to TwitterUtils 2018-08-23 19:32:20 +02:00
2e4dd3df3e Fix column icon alignment & clear icon not being hidden for certain columns 2018-08-18 14:55:08 +02:00
b82e5d33f9 Add .column to data-td-icon attribute selectors (very minor optimization) 2018-08-18 14:41:30 +02:00
65d56b336b Release 1.16 2018-08-17 20:09:15 +02:00
7836d61173 Rewrite hovered column/tweet detection again to improve consistency & performance 2018-08-17 16:13:09 +02:00
898437720b Refactor and optimize the plugin configuration file 2018-08-17 11:09:07 +02:00
d9a80d1085 Make it easier to modify custom values for column width and font size 2018-08-17 08:09:28 +02:00
ab3d8b0ae2 Load edit-design plugin configuration dialog lazily 2018-08-17 07:53:43 +02:00
b865074c32 Mark DMs as read when replying to them 2018-08-17 07:02:22 +02:00
2b8ca77c0d Add a safeguard and a TODO to DM notification fix 2018-08-16 20:36:12 +02:00
fa8b4e1e7f Remove broken feature that showed missed notifications after reload 2018-08-16 20:33:31 +02:00
a310c5bcc1 Fix a broken workaround for DM notifications not showing if the convo is open 2018-08-16 19:53:48 +02:00
b5dccd6b91 Merge branch 'master' of https://github.com/chylex/TweetDuck 2018-08-15 15:55:23 +02:00
c2bcb39b38 Fix broken hover color on column filter icons 2018-08-15 15:55:02 +02:00
4515add0a0 Update README.md 2018-08-12 16:06:38 +02:00
5b6aaec48c Reorganize installer batch files 2018-08-12 15:56:14 +02:00
810e56ca31 Update CefSharp to 67 (pre01) 2018-08-12 15:36:48 +02:00
5bcc8ac2e0 Remove ITweetDeckBrowser 2018-08-10 03:18:26 +02:00
f5bfb35867 Add a notification script to the debug plugin for testing 2018-08-10 03:14:39 +02:00
9088b8cd07 Finish refactoring context menu structures & fix bugs from previous commits 2018-08-10 00:58:44 +02:00
a7d90dc708 Allow RT & Add to List/Collection dialogs to be smaller
Closes #236
2018-08-09 20:20:08 +02:00
5968b57a01 Add account context menu items to follow notifications 2018-08-09 20:07:51 +02:00
94946a9ed6 Refactor context menu link handling 2018-08-09 19:55:53 +02:00
561aec5ef0 Refactor highlighted column/tweet variables 2018-08-09 18:13:45 +02:00
e31696d843 Update README.md 2018-07-31 14:30:05 +02:00
8b33fd2002 Update README.md 2018-07-31 14:23:25 +02:00
064e43750f Rewrite PostBuild into F# with optional compilation for performance 2018-07-31 12:48:07 +02:00
29d2f4f681 Why the fuck is Copy-Item file exclusion not working 2018-07-31 03:01:15 +02:00
c11b40b94a Tweak appearance of list-related notifications 2018-07-31 02:33:51 +02:00
e874e1d798 Refactor StringBuilder usage 2018-07-30 01:58:23 +02:00
01244ec632 Replace td-example-notification attribute with .td-example body class 2018-07-30 01:49:38 +02:00
671657e2b0 Remove user & system config properties from Program class & fix field visibility 2018-07-29 10:16:29 +02:00
dff7278e2e Refactor zoom config events & fix notification zoom not updating when outside TweetDeck 2018-07-29 09:41:52 +02:00
0881328636 Release 1.15.2 2018-07-27 22:13:16 +02:00
a951c3a7c9 Tweak Options visuals (labels, spacing, colors) & fix off-by-pixel bug on high DPI 2018-07-27 19:29:21 +02:00
b25fae54fe Update PostCefUpdate script to remove x64/AnyCPU from .targets file 2018-07-27 05:59:27 +02:00
a87bc4609e Add a cue text to Sounds tab in Options 2018-07-27 05:32:17 +02:00
3e68026949 Redesign Options to reduce amount of tabs and organize code 2018-07-27 05:12:17 +02:00
78d6d285cd Remove GDI charset from fonts in designer files 2018-07-26 20:17:53 +02:00
72e3596a3c Tweak heading font in Options and fix minor issues (tab order, overflowing text) 2018-07-26 19:37:07 +02:00
643ebcaab4 Prompt restart after profile import/reset only if an updated option requires it 2018-07-25 22:01:37 +02:00
52ef6cd95a Unify configs (backups, behavior, error handling) & make future development easier 2018-07-25 17:25:20 +02:00
4aec2f3260 Refactor FileSerializer to attempt error recovery & tweak StreamWriter usage 2018-07-25 03:38:44 +02:00
73549515eb Remove old user configuration fix 2018-07-24 06:16:52 +02:00
d83d2660cf Initial Options dialog refactoring to use an event for restart requests 2018-07-24 04:04:44 +02:00
8de913172c Minor visual fixes (old column icon alignment, TweetDeck reply triangle bug) 2018-07-23 20:14:54 +02:00
4acfd64cff Update xUnit 2018-07-23 20:13:04 +02:00
b56f90ed52 Add README instructions to fix broken MSBuild task 2018-07-23 14:20:07 +02:00
1375630727 Safeguard ResponseFilterBase in case input stream is null
Closes #234 (hopefully)
2018-07-23 12:37:57 +02:00
668825bc01 Add option to toggle touch adjustment & disable it by default
Closes #230
2018-07-23 01:36:49 +02:00
90414ae579 Release 1.15.1 2018-07-13 08:28:44 +02:00
41c8caa2a4 Make avatars in DM recipient list square 2018-07-12 15:23:01 +02:00
15305ecabe Revert TweetDeck style changes to the 'Add column' dialog
Closes #233
2018-07-12 08:37:44 +02:00
50090effd4 Refactor image/video download code and fix video download error message 2018-07-10 18:31:39 +02:00
6dffdcd1ed Add update notifications outside TweetDeck, tweak changelog styling 2018-07-10 08:50:09 +02:00
db9daf2714 Refactor update notification into a bridge object & tweak installer pre-downloading 2018-07-10 08:17:42 +02:00
502ac4ebc1 Enable high DPI awareness for video player process
Closes #232
2018-07-09 18:14:58 +02:00
3f44f3bab4 Fix notification scrolling issue by disabling threaded scrolling & latching
Closes #221
2018-07-09 17:47:20 +02:00
746eba185b Release 1.15 2018-07-09 14:06:13 +02:00
82dec92510 Fix wrong screenshot width on high DPI 2018-07-09 14:06:07 +02:00
2edc93df47 Update CefSharp to the newest build 2018-07-09 13:23:13 +02:00
c5a6738199 Fix a missing 'Clear columns' button after re-enabling the clear-columns plugin 2018-07-09 13:20:58 +02:00
dfd578165b Revert custom throttling, it is not ready due to issues w/ notifications 2018-07-09 13:04:55 +02:00
285d400f69 Refactor ScriptLoader to cache files on its own & change method signatures 2018-07-09 12:22:29 +02:00
47a3444ace Remove unnecessary styles and tweak formatting in black theme CSS 2018-07-09 10:21:44 +02:00
0ec227da52 Redesign Edit CSS dialog, add 'Open Dev Tools' button and better editor 2018-07-08 12:57:22 +02:00
f9cf582306 ... 2018-07-08 10:09:44 +02:00
b0883229bf Move HasDevTools to BrowserUtils 2018-07-08 10:06:40 +02:00
31b5d9a4c0 Protect code.js from uncaught errors & add custom 'throw if' statement 2018-07-08 09:53:42 +02:00
6841a78556 Rewrite clear-columns plugin to work w/o reload & fix broken Clear icon 2018-07-08 08:09:29 +02:00
9d2089a8ce Extract markup from introduction.js into an imported file & fix import regex 2018-07-08 06:14:33 +02:00
6d6bb79199 Massive JS formatting refactoring 2018-07-08 06:09:51 +02:00
937c8e22c4 Force UTF-8 in VS Code workspace settings 2018-07-08 05:16:31 +02:00
34c8d44dfe Project tweaks (make update installer on build, stopwatch resource swap, formatting) 2018-07-08 05:03:30 +02:00
2dbf778e56 Change the new DM limit behavior b/c older DMs don't load when scrolled to bottom 2018-07-06 22:09:09 +02:00
873fe2b393 Remove legacy HW acceleration support check & update tooltip 2018-07-06 09:37:25 +02:00
0c53bc6f32 Move icons and images to a separate folder 2018-07-06 07:26:22 +02:00
fa407e150b Add a quick way to import profile from the login page
Closes #228
2018-07-06 07:03:39 +02:00
4e52102c5c Add option for browser throttling w/ default when covered by larger window 2018-07-06 06:26:50 +02:00
0355a5c646 Add VS Code workspace file & fix some warnings 2018-07-06 06:04:10 +02:00
377d9c3554 Make an interface for dialogs to avoid hard-coding them in FormManager 2018-07-06 05:30:12 +02:00
20b1b3c895 Add more safety checks to code.js 2018-07-05 09:05:20 +02:00
41bbe7c51b Decrease amount of loaded DMs to lower performance hit from re-opening DMs 2018-07-05 07:39:26 +02:00
27fa6aefd3 Improve CSS minification 2018-07-05 06:32:45 +02:00
a63c3232da Update CefSharp to 67 (early) 2018-07-05 06:30:41 +02:00
146908a115 Include files for importing in the project to let VS detect changes 2018-07-04 17:27:49 +02:00
48b0f35fee Move template plugin HTML and CSS into a separate file 2018-07-04 17:24:32 +02:00
6c435ebe26 Extract styles from update.js into an imported CSS file 2018-07-04 07:03:48 +02:00
b8d0b721a2 Extract styles from introduction.js into an imported CSS file 2018-07-04 07:00:47 +02:00
a6d5957f46 Extract styles from twitter.js into imported CSS files 2018-07-04 06:55:10 +02:00
9e5f676e23 Move imported files into structured folder for easier handling & tweak syntax 2018-07-04 06:53:31 +02:00
414f3a1f9d Import shared plugin setup code into individual environment files 2018-07-04 06:26:41 +02:00
fedf9c60ee Hack up a simple import system in the build process 2018-07-04 06:25:22 +02:00
b6385d9622 Change unit test namespaces 2018-07-04 03:07:45 +02:00
76d25a712d Move old IO test project in preparation for future refactoring 2018-07-04 03:07:15 +02:00
2c6d935273 Rewrite unit tests for CommandLineArgs into xUnit 2018-07-04 02:45:11 +02:00
830d98a101 Rewrite unit tests for TwoKeyDictionary into xUnit 2018-07-03 21:23:34 +02:00
d7378bd75a Add leading slashes to URL strings in RequestHandlerBrowser 2018-07-02 20:32:23 +02:00
0f41cb9dbc Add response filter to restore global jQuery & unfreeze TweetDeck resources 2018-07-02 20:27:54 +02:00
77bc922d93 Add base class for response filters to safely modify intercepted files 2018-07-02 19:57:24 +02:00
c91b635132 Fix TDPF_createCustomStyle's remove function depending on jQuery 2018-07-01 16:51:57 +02:00
e5521de34a Split icon design files & update 256x256 icon 2018-07-01 15:45:33 +02:00
7c0f8d0f24 Remove temporarily forced update checking 2018-07-01 03:10:47 +02:00
f60d5f650f Add new program and tray icons to show when notifications are muted 2018-06-30 21:28:41 +02:00
ca67f2fe0a Redo app icon background as vector & update all icons 2018-06-30 19:39:16 +02:00
16cce8be1d Remove unnecessary code for disabling TweetDeck update notification 2018-06-30 15:02:46 +02:00
770619d948 Make update notification work without jQuery & optimize slide animation 2018-06-30 14:30:45 +02:00
cfedb7d6b1 Improve logged error message when update checking fails 2018-06-30 13:22:37 +02:00
738557b3a2 Add exception type that adds (potentially sensitive) details to error log 2018-06-30 13:20:25 +02:00
38b01deec1 Make it easier to freeze TweetDeck resources in the future 2018-06-30 11:49:46 +02:00
1a31e69ec9 Release 1.14.4.1 2018-06-29 19:56:00 +02:00
e065983c95 Delay the apocalypse (freeze TweetDeck JS resources, force update checking) 2018-06-29 19:41:02 +02:00
30a169171a Apparently deserializing @TryMyAwesomeApp sometimes causes a hidden crash 2018-06-29 17:10:11 +02:00
8d1900362e Update PostCefUpdate script to remove invalid build configurations (x64, AnyCPU) 2018-06-29 17:05:53 +02:00
e154189de1 Cleanup TweetDuck.csproj and fix names in TestResult unit test 2018-06-28 14:06:03 +02:00
b0f147de24 Fix misaligned 'Add column' icons when using old icon design 2018-06-28 08:10:48 +02:00
979b3548db Release 1.14.4 2018-06-28 07:54:38 +02:00
05d6c578b3 Move InjectedHTML unit tests to xUnit and rename Inject method 2018-06-26 11:19:44 +02:00
a117559063 Minor formatting tweaks 2018-06-26 10:05:53 +02:00
f87c649b09 Fix Twitter experiment causing crash in notifications and subsequent render corruption 2018-06-26 09:49:37 +02:00
6504dc9184 Add unit tests for Result and a few utility methods & fix edge case in StringUtils 2018-06-24 21:41:02 +02:00
25a8ddffd4 Rewrite and tweak existing Core namespace unit tests into xUnit 2018-06-24 19:29:24 +02:00
fa0f9b89cf Remove 'Release' configuration from UnitTests project 2018-06-24 16:16:11 +02:00
4d00a67891 Add a new F# xUnit test project 2018-06-24 16:09:21 +02:00
bd2c43e1f4 Fix analytics not counting applying ROT13 on non-editable text 2018-06-19 21:35:35 +02:00
c7279eaa34 Fix bug with falsely detecting symlinks in plugins if a file/folder doesn't exist 2018-06-19 21:32:21 +02:00
fd523e552c Symlinks/junctions in plugin folders can go to hell 2018-06-13 22:20:10 +02:00
cb877b8b23 Fix broken desktop notifications for retweets with sensitive media 2018-06-10 23:53:01 +02:00
ed1bee8b89 Release 1.14.3 2018-06-10 20:13:05 +02:00
a8e1492056 Push pin icon to the repository 2018-06-08 10:16:58 +02:00
5587216c01 Fix one more case of breaking overlays (account dialog) 2018-06-07 17:48:52 +02:00
86569261ad Add a visual response when hovering filter icons under column header 2018-06-07 14:52:39 +02:00
4a9049c7aa Fix CSS change in dialog overlays breaking some cases 2018-06-07 14:09:35 +02:00
75d60a8182 Work around browser redirection when dragging links into a scrolling column 2018-06-07 13:23:19 +02:00
14d4dc2ed9 Fix more instances of cut off badges 2018-06-06 16:04:15 +02:00
fd0e1740a5 Rename SetLastRightClickInfo bridge method and make it browser-only 2018-06-06 15:11:07 +02:00
70ca890bef Fix not stripping t.co in notifications when dragging & sometimes when copying 2018-06-06 15:10:52 +02:00
b9318dfd8e Minor visual fixes (Edit List modal, set minimum column width) 2018-06-05 04:25:05 +02:00
995642a719 Add support for 1 or 2 columns on screen in edit-design plugin 2018-06-05 03:48:40 +02:00
d14de4ac9e Lower minimum width of browser window & fix modals breaking in small windows 2018-06-05 02:51:21 +02:00
b7f325a241 Minor RequestHandlerBase refactoring 2018-06-05 02:05:04 +02:00
27c55718c2 Release 1.14.2.1 2018-06-04 13:01:44 +02:00
421ff0654b Temporarily work around buggy notification scrolling 2018-06-04 12:35:34 +02:00
ed947458f9 Update fallback HTML in desktop notifications 2018-06-04 12:09:02 +02:00
9cdb20ba84 Fix several broken column types in 'Add column' modal 2018-06-04 11:02:52 +02:00
d8774b735f Release 1.14.2 2018-06-04 08:59:32 +02:00
adcb42695f Update CEF so I don't have to workaround wrong dev tools version
Closes #220
2018-06-01 22:32:44 +02:00
dd77b5bcbb Revert smooth scrolling fix and rewrite horizontal scrolling fix to avoid column jumping 2018-06-01 21:48:26 +02:00
d2445be155 Fix missing GIF previews in tweet screenshots 2018-05-31 03:07:51 +02:00
10254c8af7 Fix tweet screenshots with Aero disabled by making the window visible
Closes #223
2018-05-31 02:58:25 +02:00
d7e830badf Slightly increase default notification height for larger font sizes 2018-05-31 01:11:17 +02:00
b445a3a9b8 Fix broken reply-account advanced selector arguments after a TweetDeck update 2018-05-31 00:52:08 +02:00
97f42ead66 Make it easier to debug screenshots 2018-05-30 19:50:11 +02:00
03730fafb9 ...because I can't be bothered 2018-05-29 15:34:02 +02:00
0be9465dca Fix column icons being hidden by title with clear-columns or edit-design features on 2018-05-29 15:23:41 +02:00
d7f1df4995 Release 1.14.1 2018-05-16 13:07:47 +02:00
3cb0f90706 Prevent an unlikely crash when showing an error message in context menu 2018-05-16 13:05:59 +02:00
a3e9b15a8a Add 'Apply ROT13' to non-editable selections to allow decoding tweets 2018-05-16 12:40:49 +02:00
00bfa68a57 Fix UI issues after recent TweetDeck updates 2018-05-16 07:48:47 +02:00
c311e24f08 Make it easier to update devtools file after updating CEF 2018-05-15 12:42:57 +02:00
1cdd4e4455 Update CefSharp to 66 (early) to fix memory leak 2018-05-15 10:10:03 +02:00
8078c0081a Add a script to fix browser project references after updating CEF 2018-05-15 10:04:08 +02:00
a867e1fc40 Optimize speed and memory usage of build process for app & installers 2018-05-08 20:20:02 +02:00
61da36ac1c Update README.md 2018-05-08 19:37:06 +02:00
720ca2a018 Update README.md 2018-05-08 19:36:08 +02:00
b39c593552 Update README.md 2018-05-08 06:28:07 +02:00
c808952a45 Update README.md 2018-05-08 00:03:08 +02:00
b468d7a766 Update README.md 2018-05-07 20:03:50 +02:00
28578f60be Include plugin files in the project & update README 2018-05-07 19:35:08 +02:00
92a39e2527 Push debug configuration start arguments (-datafolder TweetDuckDebug) 2018-05-07 18:11:56 +02:00
1bce5e4342 Release 1.14 2018-05-07 17:28:34 +02:00
68f586e104 Fix wrong info in the analytics dialog 2018-05-07 15:42:41 +02:00
d27a66202e Remove "Show this thread" in quoted tweets from notifications and screenshots 2018-05-07 03:01:24 +02:00
07de2f450c Fix broken notification scrolling in CEF 65 properly 2018-05-07 02:43:40 +02:00
3c03726634 Remove old and no longer necessary code from update installer 2018-05-06 22:47:58 +02:00
6fb2643063 Slightly increase installer compression level 2018-05-06 22:24:55 +02:00
5eef6c8196 Kill stubborn app processes when installing updates 2018-05-06 21:18:23 +02:00
829c332e13 Add a global function for mustache injection & fix broken clear-columns mustache 2018-05-05 11:47:19 +02:00
47eec14bca Fix freshly broken GIF previews in notifications 2018-05-04 21:07:33 +02:00
e7ee1d6be7 Revert "Fix broken notification scrolling in CEF 65"
This reverts commit 1029ea5840.
2018-05-04 14:00:41 +02:00
e41b5e5ff7 Replace generated license files with just one and include CEF license 2018-05-04 13:30:54 +02:00
ba1bacd08c Fix minor formatting and method modifier issues 2018-05-04 13:04:23 +02:00
1029ea5840 Fix broken notification scrolling in CEF 65 2018-05-04 11:41:20 +02:00
339eaf0195 Fix button appearance in introduction dialog and some plugins 2018-05-03 16:02:44 +02:00
aa1e1549d8 Fix Twitter's broken Cancel button when logging out 2018-05-03 15:04:09 +02:00
1f8ae9ef80 Update CefSharp to 65 (pre01) and fix blank example notification
* Update CefSharp to 65 (pre01)

* Fix blank example notification on first load
2018-05-03 14:05:56 +02:00
65165de060 Release 1.13.6 2018-05-02 16:27:23 +02:00
485836d2ce Replace about:blank in FormGuide with a dummy page 2018-05-02 15:21:23 +02:00
64c07c14d9 Revert "Update to CefSharp 65 (early) (#215)"
This reverts commit b6a599f8a6.
2018-05-02 15:19:01 +02:00
b6313c2b72 Update CefSharp to 65 (pre01) 2018-05-02 00:05:08 +02:00
58124b5821 Force Chrome UA on TweetDeck and remove -chromeagent argument 2018-05-01 19:31:39 +02:00
b6a599f8a6 Update to CefSharp 65 (early) (#215) 2018-05-01 19:20:13 +02:00
19a6bc0dbd Improve performance of PostBuild.ps1 2018-05-01 16:37:06 +02:00
8cb81d44ee Fix update installer changing uninstaller name and remove /MERGETASKS parameter 2018-05-01 14:36:51 +02:00
22d0a372d8 Add dev tools to the installer as an optional component 2018-05-01 14:30:56 +02:00
988fae75c3 Add a command line argument to use Chrome user agent 2018-04-30 21:02:15 +02:00
a82b0e3622 Release 1.13.5 2018-04-29 20:55:33 +02:00
bc6cacacf9 Fix portable install not recognizing itself after importing login session 2018-04-29 19:00:17 +02:00
03ad1b3cbc Update instructions for reply-account plugin and TDPF_getColumnName 2018-04-29 14:29:33 +02:00
eac300627f Fix broken column names again and make getColumnName accessible to plugins 2018-04-29 13:17:00 +02:00
12525ac386 Fix screenshots with zoom & try to fix rendering issues 2018-04-28 20:36:49 +02:00
7558551859 Add a debug flag to generate individual screenshot frames 2018-04-28 18:15:25 +02:00
a9cce13eef Fix visual inconsistencies with new icons 2018-04-28 15:34:15 +02:00
5bb2c43dd0 Fix edit-design plugin not loading when enabled after a restart 2018-04-28 15:22:55 +02:00
5b1dcc88cc Make Plugins form always show Configure button when configurable 2018-04-28 15:19:58 +02:00
5c8fc1d136 Fix clear-columns plugin button disappearing when adding/removing columns 2018-04-27 21:13:38 +02:00
82c2ab3448 Fix broken smooth and horizontal scrolling after a TweetDeck update 2018-04-27 19:33:32 +02:00
b05c8d180f Remove UpdaterSettings and fix not restarting the timer after a dismissed update 2018-04-27 19:21:42 +02:00
87109e5d01 Fix a few visual issues with high DPI 2018-04-27 18:29:02 +02:00
be1a809098 Update all forms and dialogs to use the 'Segoe UI' font 2018-04-27 18:06:45 +02:00
ba0e3f1bd4 Continue redesign of Plugins form (tweak visuals, position, and size limits) 2018-04-27 14:59:36 +02:00
27d41e6164 Begin redesign of the Plugins form (layout reorganization, fixes, optimization) 2018-04-27 13:47:29 +02:00
1ce5ddfd98 Rewrite names and descriptions of plugins & update debug plugin 2018-04-26 21:54:40 +02:00
0096a1a4ef Move debug configuration build events to PostBuild script 2018-04-26 15:37:02 +02:00
d2a6560a90 Measure PostBuild script duration and fix formatting 2018-04-25 19:35:57 +02:00
4d7c048139 Remove versions from official plugins and make them only work on one app version 2018-04-25 19:35:04 +02:00
1d78bd2655 Release 1.13.4.1 2018-04-24 18:16:13 +02:00
9250f1907c Quick semi-temporary fix for removed column.isOfType 2018-04-24 18:10:22 +02:00
a63e210b88 Release 1.13.4 2018-04-15 19:59:35 +02:00
06bd65b7f8 Fix wrong behavior when an update is canceled during download & multiple check errors in some cases 2018-04-15 19:01:39 +02:00
b6c17eb05e Remove unused selectors and classes from styles & add a related TODO note 2018-04-15 18:08:37 +02:00
a3d40fdc2b Push a quick utility to detect unused selectors and classes 2018-04-15 18:08:03 +02:00
c064ef7a30 Improve screenshot reliability 2018-04-15 16:24:26 +02:00
762717da1e Move clear-columns plugin nav button next to 'Add column' button & add isClearable safeguard 2018-04-15 14:39:59 +02:00
b7d3758bea Add error handling when checking updates, and remove unnecessary TODO 2018-04-15 14:03:11 +02:00
d20541fd24 Fix clear-columns plugin to hide the Clear button on scheduled & collection columns 2018-04-14 20:28:48 +02:00
2c2f860f26 Fix issues caused by recent TweetDeck update (notifications, column styles, reply account)
Closes #211
2018-04-14 19:40:51 +02:00
d1db3aa673 Remove command line argument for pre-releases & reorganize restart dialog 2018-04-11 10:39:17 +02:00
cedc52cdf5 Move update notification trigger code to TweetDeckBrowser 2018-04-11 10:01:55 +02:00
33f8eafbcf Remove unused VC120 NuGet package 2018-04-11 09:59:10 +02:00
ad45cf8c72 Begin rewriting update checker to run within C# 2018-04-11 09:59:00 +02:00
f99d035621 Add a Result class that acts as an Either monad for a value or exception 2018-04-10 19:45:41 +02:00
f3072caea8 Fix broken element resizing in the Edit CSS dialog 2018-04-07 13:42:36 +02:00
1410974292 Release 1.13.3 2018-04-07 11:56:36 +02:00
44413fa96c Swap order of 'Search in' items in selection context menu 2018-04-07 11:15:14 +02:00
342a4b4067 Minor code formatting tweaks 2018-04-07 03:46:10 +02:00
4356dde92d Fix wrong c# language version setting for Release builds 2018-04-06 16:36:27 +02:00
21e64a18d8 Fix screenshots to work properly with combinations of DPI and zoom settings 2018-04-06 07:28:57 +02:00
5a305a6740 Fix wrong screenshot size when browser zoom is not 100% 2018-04-06 03:05:06 +02:00
44595bad40 Refactor plugin loading and validation 2018-04-05 21:34:35 +02:00
7fc9edc9cb Fix wrong namespace in update event classes 2018-04-05 09:58:52 +02:00
93e191f522 Reorganize hot swap code & add support for hot swapping plugins 2018-04-05 03:34:11 +02:00
8d8355e792 Rewrite PluginManager setup scripts to use a custom array-based dictionary 2018-04-05 02:09:51 +02:00
a5379d290c Add resource hotswap for easier debugging 2018-04-04 23:13:44 +02:00
caea8d4315 Move most of post build event (copying, cleanup) to PostBuild.ps1 2018-04-04 20:05:59 +02:00
24224ab4c6 Increase height of Options form to avoid scrollbars in General tab 2018-04-04 07:43:00 +02:00
4dbc02360c Add context menu options to search selected text in a column or browser
Closes #209
2018-04-04 05:22:41 +02:00
aa7a29af0c Fix combo box issues (closing while opening, minor browser selection bug) 2018-04-04 03:33:14 +02:00
296d0c6199 Fix ScriptLoader showing multiple errors at once sometimes & change error title 2018-04-03 23:46:00 +02:00
812a034e8d Include version header in ScriptLoader files to detect failed installs 2018-04-03 23:44:43 +02:00
e9de789b79 Minor refactoring, including removal of unnecessary enableCustomCSS parameter 2018-04-03 20:49:21 +02:00
cfbc1b9575 Enable custom CSS in screenshots and move styles from code.js to notification.css 2018-04-03 20:48:33 +02:00
e39e85e4dd Add 'td-notification' body class to desktop notifications & update notification.css 2018-04-03 20:26:54 +02:00
3f0b161cd0 Move screenshot height calculation to the screenshot window 2018-04-03 18:26:33 +02:00
ebe3868720 Fix ScriptLoader crash when showing error message from another thread 2018-04-03 18:19:39 +02:00
ffd0f5e986 Rewrite screenshot rendering to fix current and future visual issues 2018-04-03 02:05:22 +02:00
217535a3ba Make td-notification-padded styles available in screenshots 2018-04-03 02:02:15 +02:00
7abfbea2da Fix "Replying to" user link not using black theme in screenshots
Closes #208
2018-04-03 01:10:53 +02:00
86ffeaac9a Remove no longer supported keycap emoji from the emoji keyboard
Closes #207
2018-04-03 00:04:18 +02:00
ab915b7115 Move accounts above hashtags in search results 2018-04-02 23:26:51 +02:00
705b5d38cf Add design files for logo and video player buttons 2018-04-01 19:35:12 +02:00
fc2acb00b3 Add a batch file to build update installer only 2018-03-16 18:50:37 +01:00
5add8a1d0e Move ITweetDeckBrowser and refactor some things 2018-03-16 18:48:41 +01:00
063d3a2637 Remove unnecessary null fallback in SetClipboardText 2018-03-09 14:43:59 +01:00
f1f90a2ee3 Refactor code to avoid nulls (#206)
* Ensure potential nulls have a fallback value & add/remove null checks

* Refactor update check code to avoid nulls

* Refactor ProfileManager exception handling to avoid nulls

* Refactor a few more various classes and fix nulls in ContextInfo

* Force c#7 everywhere and revert usage of newer features from cherry-picked commits

* Remove unused #pragma declaration
2018-03-07 14:37:03 +01:00
ed317a4e46 Refactor VideoPlayer 2018-03-06 21:17:22 +01:00
cca16f3bb1 Release 1.13.2 2018-03-06 18:38:24 +01:00
aba156cb3b Fix typo from refactoring breaking context menu for some links 2018-03-02 06:00:53 +01:00
cd4e4d7095 Fix hashtags and search links being recognized as account links 2018-03-02 05:59:20 +01:00
8fbb639430 Refactor & optimize context menu, send last tweet info only on right-click 2018-03-02 05:24:45 +01:00
d5bf8ec558 Fix missing image/video context menu for tweets that have both media and a quote 2018-03-02 00:15:28 +01:00
b6cff40f1e Warn when checking updates outside TweetDeck & fix visual and unlikely issues 2018-02-28 11:03:55 +01:00
833e42f455 Add a IsTweetDeckWebsite bool to ITweetDeckBrowser 2018-02-28 07:24:40 +01:00
8134843dad Fix background color & twitter.com hooks not applying quickly enough sometimes 2018-02-28 02:34:29 +01:00
1f92d5e633 Remove 'Shift Selects Multiple Accounts' option & fix refocusing after switching account 2018-02-28 01:43:44 +01:00
dc51c0ae85 Remove unnecessary console logging in debug builds 2018-02-26 17:45:56 +01:00
45c79643d6 Why the fuck is TLS 1.2 disabled by default in .NET on some computers 2018-02-25 23:15:00 +01:00
9041bfc627 Tweak search input font size and icon position 2018-02-21 22:17:51 +01:00
0b3b3dd0be Fix a crash when downloading tweet images with no username
Happens when someone accidentally or through dev tools gets to
twitter.com and tries downloading an image.
2018-02-21 19:48:00 +01:00
89e92dab59 Fix middle-clicking on links in desktop notifications not skipping them or stripping t.co 2018-02-19 17:31:28 +01:00
8c168c9ad7 Fix emoji keyboard button size & tweak composer button layout 2018-02-17 12:21:26 +01:00
9f63357a92 Pre-check desktop icon option in installer when not updating 2018-02-17 10:52:19 +01:00
d91b4bd1f3 Release 1.13.1 2018-02-14 18:43:02 +01:00
c0c64f6d62 Remove old TweetDeck installation check from the installer & tweak formatting 2018-02-14 17:45:19 +01:00
1a5d2af779 Decrease post-update analytics report to 8h and increase startup delay to 2m 2018-02-14 17:10:24 +01:00
f40a33192b Revert and fix various changes from recent TweetDeck update 2018-02-14 15:39:27 +01:00
ca4900aff0 Fix 'Manage templates' button after a recent TweetDeck update 2018-02-14 15:17:58 +01:00
56fc9e2d40 Fix black theme issues (mismatched detection & rare bug with wrong notification background color) 2018-02-13 16:51:40 +01:00
d2174c0b69 Fix misaligned avatars in activity columns 2018-02-13 15:59:21 +01:00
9f76754ad3 Force full install from 1.13 to 1.13.0.1 2018-02-13 13:37:30 +01:00
118ceaec35 Release 1.13.0.1 2018-02-13 13:27:52 +01:00
5a57d28a7d Fix crash by checking and downloading for VC++ 2015 in the installer
Closes #205
2018-02-13 13:17:40 +01:00
07af99f862 Fix wrong background color in tweet screenshots when using black theme 2018-02-13 12:42:25 +01:00
59fba7fba0 Fix a hidden crash that prevented desktop notifications from showing 2018-02-13 12:37:11 +01:00
dd4edc4249 Update CefSharp to latest 64 and remove VC++ 2012 2018-02-13 11:28:48 +01:00
856226473a Update README.md 2018-02-13 05:52:03 +01:00
8d1c07d6b2 Release 1.13 2018-02-12 18:48:42 +01:00
c32462cc9e Update TweetDeck color selectors in CSS for black theme 2018-02-12 18:23:16 +01:00
ec94ea3273 Refactor PluginManager to use ITweetDeckBrowser & do some cleanup 2018-02-12 15:26:21 +01:00
41acd8c15b Refactor UpdateHandler to reference ITweetDeckBrowser 2018-02-12 11:35:39 +01:00
155a79f2ec Add ITweetDeckBrowser for refactoring 2018-02-12 11:34:23 +01:00
9197cb9be6 Add support for 'Configure' button to edit-design plugin 2018-02-12 11:26:50 +01:00
03d50c847b Add 'Configure' button to plugins with a configure() method & close dialog afterwards 2018-02-12 10:40:00 +01:00
bf45c40365 Make analytics debugging easier & tweak Counter serialization 2018-02-12 06:13:08 +01:00
679e126194 Reset all analytics counters 2018-02-12 05:41:03 +01:00
50e39164bd Update and add analytics data points & increase report interval to 14 days 2018-02-11 20:01:57 +01:00
cb9f75e968 Refactor AnalyticsFile events and usage 2018-02-11 16:59:02 +01:00
aa7f6cc3b1 Fix loading spinner sometimes being visible before getting replaced 2018-02-10 23:20:34 +01:00
fe601aed41 Redesign favorite/retweet notifications to be more compact and show full text 2018-02-10 13:09:09 +01:00
2282a9df28 Move 'Show this thread' in desktop notifications above media/quotes & fix hover color w/ black theme 2018-02-10 08:29:59 +01:00
2b54627750 Tweak media size and margins in desktop notifications 2018-02-10 07:54:50 +01:00
16051a0d25 Forgot this 2018-02-10 07:13:56 +01:00
66d5f0d790 Refactor IResourceHandler usage 2018-02-10 07:07:11 +01:00
07d29207f0 Restore loading background color and spinner from before the TweetDeck update 2018-02-10 06:50:52 +01:00
a60be2afcc More Visual Studio shit 2018-02-07 21:58:21 +01:00
027f3ee253 Remove recently added follow notification 2018-02-07 03:22:50 +01:00
04774815e4 Fix bad padding in introduction modal 2018-02-07 03:20:58 +01:00
61a73c055b Fix weird alignment of stuff in notification columns 2018-02-07 00:39:05 +01:00
7731534ffc Save some space in edit-design plugin 2018-02-07 00:21:57 +01:00
ed7bf99610 Prevent dev tools from leaking info in all request headers 2018-02-06 21:10:29 +01:00
cbe4272556 Hide unused items in TweetDeck Settings modal (startup notifications, gif autoplay) 2018-02-06 20:43:18 +01:00
8f5e3dfdcc Merge pull request #203 from chylex/cefsharp64
Update CefSharp to 64 & re-enable mp3s in sound notifications
2018-02-06 18:40:23 +01:00
35500c51f1 Allow export/import/restoring system options & refactor Manage Options dialog 2018-02-06 18:35:36 +01:00
629f873bb2 Add a debugger trigger shortcut to debug plugin 2018-02-06 18:25:11 +01:00
a44cb884c4 Fix a crash when restarting after importing/resetting profile & refactor 2018-02-06 17:04:32 +01:00
d5ad1d0daa Fix loading spinners, and links in notifications when using black theme
Closes #202
2018-02-06 04:38:57 +01:00
61ae7e3b6a Fix 'Show this thread' being too close to media thumbnails in notifications 2018-02-06 04:37:45 +01:00
01583e424f Re-add mp3 support in sound notifications 2018-02-06 04:10:03 +01:00
5c0aa1b3da Update CefSharp to 64 (early build) 2018-02-06 04:06:18 +01:00
07391efa70 Fix more visual issues (remove DM reply button background w/ black theme) 2018-02-02 23:13:43 +01:00
b80f1bfc7c Fix more visual issues (remove disabled button border w/ black theme) 2018-02-02 22:31:01 +01:00
ad310db86c Fix more visual issues (profile modal w/ black theme, timeline input shadow) 2018-02-02 21:57:28 +01:00
4ce0122a29 Fix hover/click effects on buttons under reply input box 2018-02-02 19:15:44 +01:00
a8894f7054 Fix visual issues with search input and buttons 2018-02-02 18:01:11 +01:00
1d1515351b Release 1.12.5.1 2018-02-02 16:57:28 +01:00
2a9ddd4468 Fix edit-design modal, black theme quote border, and dark theme scrollbar color 2018-02-02 16:56:06 +01:00
0f9a944775 Square-ify border of reply box & fix notification background 2018-02-02 15:54:22 +01:00
34ee9ebd66 Release 1.12.5 2018-02-02 15:24:19 +01:00
43f632b555 Allow detecting custom edit-design themes in analytics 2018-02-02 15:19:25 +01:00
7cf3f1d32c Add option for the old dark theme in edit-design plugin 2018-02-02 14:59:33 +01:00
e51e87647e Remove unknown property error in FileSerializer & refactor reading 2018-02-02 13:49:10 +01:00
b8aae88b11 Fix broken Shift swap when selecting accounts after a recent TweetDeck update 2018-01-31 00:28:19 +01:00
d06e29db15 Get rid of string.Split in FileSerializer
string.Split is not suitable for potentially very large strings, so this
decently improves memory usage
2018-01-30 15:45:19 +01:00
62449450f3 Release 1.12.4 2018-01-29 08:40:38 +01:00
b290c94635 Fix a video player crash caused by not handling remote commands on UI thread 2018-01-29 08:20:33 +01:00
f909b887d9 Fix screenshot issues (wrong media size and margin, hide "Show this thread") 2018-01-28 21:48:57 +01:00
5cf4843212 Fix broken screenshots of tweets with just one line of text 2018-01-28 21:40:18 +01:00
b3d1e1bfac Fix example notification timer breaking on skip (forward mouse button or Enter) 2018-01-28 20:54:49 +01:00
df47499a28 Update example notification (username, avatar, improve text) 2018-01-28 20:50:18 +01:00
421475ec87 Add a notification and way to follow the new TweetDuck account 2018-01-28 20:22:13 +01:00
29d999b8eb Remove old data collection notification that was shown after updating 2018-01-28 20:05:50 +01:00
acacd9a5e5 Fix reversed button tab order in FormMessage 2018-01-28 19:39:12 +01:00
b81c26f93f Add an option to ignore tracking URL warnings (t.co) 2018-01-28 19:38:40 +01:00
00b212944c Implement top tier account bamboozle scheme 2018-01-28 19:12:32 +01:00
70ba006e4d Reorganize logo file resources and remove about.png 2018-01-26 17:44:41 +01:00
118e0cae62 Compress the logo in the About form 2018-01-26 17:36:15 +01:00
c003bb4e71 Add a way to display the TweetDuck logo in the browser 2018-01-26 17:32:54 +01:00
e9b2fa7603 Release 1.12.3.1 2018-01-26 15:58:17 +01:00
35afaa105d Fix text alignment in the Feedback tab in Options 2018-01-26 15:49:44 +01:00
2e300a7b8f Fix broken stylesheets in notifications after a recent TweetDeck update
Closes #199
2018-01-26 15:43:52 +01:00
f3f5b88550 Refactor resource handler related extension methods 2018-01-22 14:53:38 +01:00
22f491d98a Release 1.12.3 2018-01-22 07:13:50 +01:00
7908c8ebd9 Goddammit VS 2018-01-22 06:54:58 +01:00
e114a93714 Refactor and move BrowserCache, VideoPlayer, and ExportManager 2018-01-22 06:41:20 +01:00
931761600f Move and refactor browser list options a bit more again 2018-01-22 05:17:50 +01:00
e5b4b03e1a Meh 2018-01-21 09:11:40 +01:00
f1e8b3fbf0 Move option for custom program for opening links at the end for better accessibility 2018-01-21 06:33:50 +01:00
4d64243a07 Turn WindowsUtils.Browser fields into get-only properties 2018-01-21 04:25:06 +01:00
3422b4d4d6 Fix height, tab order, and recently broken scroll focus handling in Options 2018-01-21 03:29:11 +01:00
b170d529fd Add an option to disable smooth scrolling 2018-01-21 03:11:12 +01:00
83741db5aa Fix broken smooth & horizontal scrolling with cursor above columns
Closes #192
2018-01-21 01:18:59 +01:00
c4b2b3ab25 Add verbose error logging to video player & tweak Reporter.Log 2018-01-19 23:37:45 +01:00
676df44985 Fix dialog title inconsistencies 2018-01-19 22:29:53 +01:00
037adc6b5c Add a way to select a custom program for opening links
References #185
2018-01-19 20:08:48 +01:00
186d17dd98 Add an option to select an installed browser to open links in
Closes #185
2018-01-19 19:19:40 +01:00
ab9ff980ef Fix dragging twitter links over columns from some sources or w/ url parameters not working 2018-01-19 06:05:46 +01:00
f297cb2623 Add line escaping to FileSerializer for easier manual file editing 2018-01-18 20:37:29 +01:00
b53c672768 Refactor Program.ResetConfig & Program.RestartWithArgs 2018-01-18 10:58:58 +01:00
1a2b967749 Move Chromium data from LocalAppData/CEF to TweetDuck storage folder 2018-01-18 10:47:16 +01:00
6ba30c48cf Remove BrowserUtils.HeaderAcceptLanguage and use default value instead 2018-01-18 10:37:43 +01:00
1af9ee9ced Release 1.12.2 2018-01-17 16:19:28 +01:00
e50480aa35 Fix edit-design plugin modal labels changing margins with different themes 2018-01-16 22:55:21 +01:00
6943c7813f Fix hovering scrollbars not changing their color with edit-design plugin enabled 2018-01-16 22:54:53 +01:00
7c9b4382ca Fix Follow dialog closing when clicking any but the first Follow button 2018-01-16 19:20:14 +01:00
3187f97592 Rewrite 'Keep Like/Follow dialogs open' code after TD removed the old way 2018-01-15 21:25:43 +01:00
b71a367052 Merge pull request #196 from chylex/delet_audio_lib
Remove audio library
2018-01-14 11:13:31 +01:00
2d4bbf2a6f Fix sound notification extension detection and add warning to mp3 files 2018-01-14 00:08:43 +01:00
6e59dfddcc Remove audio library 2018-01-13 23:38:30 +01:00
bd92fc6ee0 Use <audio> for custom sound notifications & allow volume control for default one
Closes #195
2018-01-13 22:59:34 +01:00
2f61de7025 Add GetHandlerFactory extension method to BrowserUtils 2018-01-13 22:37:24 +01:00
8fcec7ec7c Merge remote-tracking branch 'refs/remotes/origin/master' into delet_audio_lib 2018-01-13 19:50:13 +01:00
33d9ba3871 Refactor UserConfig event invocations into a generic method 2018-01-13 19:49:16 +01:00
4f8c778ba0 Ignore errors in automatic cache clearing
Closes #194
2018-01-13 15:35:20 +01:00
804c739038 Fix broken element dragging (timeline tweets and maybe more) 2018-01-13 15:27:07 +01:00
a0445fbb12 Release 1.12.1.1 2018-01-12 12:48:14 +01:00
7ab5d7b796 Release 1.12.1 2018-01-12 12:40:09 +01:00
7f83a7773b Fix 'Show this thread' not working >_> 2018-01-12 12:39:46 +01:00
fc9e8a808f Fix emoji inline tweak not working in DMs 2018-01-11 21:48:00 +01:00
5ab8976bc7 Fix broken links in guide because Chromium is an idiot 2018-01-11 21:06:31 +01:00
e2a28f2811 Allow typing unambiguous :emoji: in inline replies 2018-01-11 20:15:56 +01:00
137a20ed0e Fix notification tooltip not disappearing when skipping/closing without moving mouse 2018-01-11 00:06:18 +01:00
f956f696f4 Limit some $TD functions to browser/notification, change displayTooltip params 2018-01-10 23:53:37 +01:00
bb7cbde38f Make 'Show this thread' in notification open tweet detail
Closes #191
2018-01-10 22:47:50 +01:00
8c452d3fa2 Fix clipboard html styles when copying text from notifications 2018-01-10 22:47:06 +01:00
f65c33c432 Add newly added option to analytics report 2018-01-10 22:17:30 +01:00
da2758ccb1 Add option to keep Like/Follow dialogs open
Closes #193
2018-01-10 14:22:47 +01:00
de10112b7f Remove non-english locale files from the installation 2018-01-03 16:23:56 +01:00
301d4fb171 Replace app locale option with spell check language & use correct lang list 2018-01-03 16:04:11 +01:00
f0a79add14 Fix broken example notification after closing it and then changing options 2018-01-03 14:52:47 +01:00
d33bc9fe25 Release 1.12 2018-01-01 01:48:48 +01:00
a2a5dfd435 Reset the official plugins folder in the update installer 2018-01-01 01:27:58 +01:00
f3d7c8d4c3 Set language header to match app locale 2017-12-31 14:21:21 +01:00
67f60dd787 Merge pull request #190 from chylex/locale_magic
Add options for app locale (remove argument) & target translation language
2017-12-31 14:03:15 +01:00
62310ce4a4 Add an option to set target language for tweet translations 2017-12-31 13:59:19 +01:00
3a27089364 Add an option to set app locale in a new Options tab & remove locale argument 2017-12-31 13:29:38 +01:00
a05460f562 Make BrowserCache.CacheFolder a property 2017-12-31 12:14:45 +01:00
390872c305 Merge pull request #189 from chylex/settings_ui_plz 2017-12-31 10:26:23 +01:00
594d12df79 Reorganize all Options tabs into FlowLayoutPanels 2017-12-31 10:20:42 +01:00
c42c12c72b Move Options tooltips outside designer files & reorder options in code 2017-12-31 08:10:24 +01:00
c37f4fe365 Merge branch 'master' of https://github.com/chylex/TweetDuck 2017-12-31 07:02:20 +01:00
8d37d68770 Fix dragging links not stripping t.co shortener
Closes #183
2017-12-31 06:59:17 +01:00
1b3d1fb36a Tweak readme wording 2017-12-31 05:36:32 +01:00
2f352ef9bb Update CefSharp to 63-pre01 and update documentation 2017-12-31 05:34:36 +01:00
527f3cab4c Fix reinstantiating AnalyticsManager when restoring from tray & test stuff 2017-12-31 05:01:23 +01:00
f67bf27db2 Add an option to automatically clear cache after exceeding a set size
Closes #182
2017-12-30 02:06:36 +01:00
a8bb3ba349 Improve performance and safety of cache size checking & clearing 2017-12-28 04:52:53 +01:00
321ab12213 WTF is AnyCPU doing in the project files 2017-12-28 00:50:51 +01:00
4cab18e557 Merge pull request #187 from chylex/cefsharp63
Update CefSharp to 63 (early build) & delete GC reload
2017-12-28 00:39:07 +01:00
c15ea97a36 Update CEF again 2017-12-28 00:32:26 +01:00
a0cc4109df Delete GC reload in CEF 63 (#186) 2017-12-28 00:03:24 +01:00
f66ff1000a Replace AbstractRequestHandler with CefSharp's implementation 2017-12-21 22:45:29 +01:00
51a9bb6d3c Update CefSharp to 63 pre-release 2017-12-21 20:25:36 +01:00
07017bd29b Fix cut off usernames in Messages column 2017-12-21 20:15:02 +01:00
45b6f49a08 Fix middle-click and ctrl-click handling in CEF 62 2017-12-07 20:07:15 +01:00
103ad72788 Update CefSharp to 62 (early build) 2017-12-07 19:50:43 +01:00
543259f29f Release 1.11.2 2017-11-21 07:07:24 +01:00
98799734c5 Add a box shadow to main menu in the guide 2017-11-21 06:59:54 +01:00
96f491a666 Redo the introduction dialog (add main menu image & follow link, rewrite text) 2017-11-21 06:11:49 +01:00
29e541dbef Fix comment formatting in notification.css 2017-11-20 18:01:17 +01:00
1343b9c113 Fix username alignment (follow notifications & all types in desktop notifications) 2017-11-20 18:01:02 +01:00
94920fd459 Ensure only one guide window is open and fix webkit element outline 2017-11-20 17:44:28 +01:00
b2f3b245b7 Open TweetDuck guide links directly in the app 2017-11-20 17:25:25 +01:00
15bc6c1d73 Fix idiot chromium being unable to figure out window size while loading guide 2017-11-20 17:21:46 +01:00
2c175b8d3a Fix incorrect default config value for notification scroll speed 2017-11-18 15:31:36 +01:00
a48c17a769 Update analytics (fix system edition, add dev tools and other feature collection) 2017-11-18 07:15:18 +01:00
03465c4ab0 Remove dismissed update config entry after accepting an update 2017-11-18 02:53:25 +01:00
b4e936c530 Minor refactoring of notification classes & remove no longer needed CSS 2017-11-14 19:06:05 +01:00
fb1482370a Fix issues from TweetDeck updates (long usernames, badge in detail view, notification media previews) 2017-11-14 18:42:34 +01:00
e831bc2bea Fix broken compose text size in edit-design plugin after a TweetDeck update 2017-11-11 17:32:20 +01:00
c74c168c96 Release 1.11.1 2017-11-10 14:59:48 +01:00
40b53fa40c Fix broken update notification CSS after a TweetDeck update 2017-11-10 14:52:32 +01:00
3481cc0349 Fix broken CSS in browser and plugins after a TweetDeck update 2017-11-10 14:44:47 +01:00
09abd889e9 Add ID to <html> for selector priority 2017-11-10 14:14:50 +01:00
330bbfbb31 Fix broken CSS in notifications after a TweetDeck update 2017-11-10 13:35:29 +01:00
6b7b690476 Remove Log method from the bridge object 2017-11-09 21:43:22 +01:00
cea72801a7 Fix video player process sometimes taking too long to close 2017-11-09 19:29:01 +01:00
04369e22a7 Add option to disable animated avatars (general GIF toggle)
Note: this breaks when disable-extensions is used, so it was changed to
disable-pdf-extension instead
2017-11-09 18:18:22 +01:00
f1b16eab9a Add a global function (including one for plugins) to reload columns 2017-11-09 18:15:58 +01:00
13646d9c90 Increase quality of media previews in desktop notifications 2017-11-09 17:08:39 +01:00
17d762ce91 Make spell check option not require a restart 2017-11-09 16:51:40 +01:00
edb40adaa1 Release 1.11 2017-11-09 14:40:09 +01:00
bc0809994a Fix import & restart overwriting some imported settings such as zoom, add a TODO for system config import/export 2017-11-08 12:23:58 +01:00
a3e3d517b0 Fix high DPI for dialogs displayed before the main window 2017-11-08 11:39:32 +01:00
d8b63a54ca Update the guide 2017-11-08 10:56:05 +01:00
b81e7583eb Implement feature usage analytics 2017-11-08 08:15:45 +01:00
51f9ba3642 Add option to always show character count to edit-design plugin 2017-11-08 06:40:36 +01:00
296626f7c7 Implement a notification about previously enabled anonymous data sending 2017-11-07 21:37:41 +01:00
5b2daf9746 Refactor method order and return types in config file classes 2017-11-07 18:36:52 +01:00
9a6b615174 Cleanup FileSerializer Write/Read calls & change exception for empty files 2017-11-07 18:10:38 +01:00
18f8d5b269 Tweak key format in analytics request 2017-11-04 14:07:24 +01:00
2867a875c9 "Fix" an uncommon video player crash when closing short videos
Closes #177
2017-11-04 14:03:28 +01:00
ee2f5ae8cb Disable default TweetDeck update notification 2017-11-04 03:30:50 +01:00
bd5c301fb9 Refactor old icon style declaration in edit-design and add !important 2017-11-02 16:45:26 +01:00
6df68629f7 Implement system config export/import/reset (without UI) 2017-11-02 13:34:51 +01:00
be08fd4445 Remove UserConfig.ZoomMultiplier and old migration code 2017-11-02 13:20:19 +01:00
6f1afb94fb Add a global function to reprioritize events and refactor stuff 2017-11-02 11:02:00 +01:00
7401b8a52d Fix search input not being unfocused after opening search externally 2017-11-02 10:10:04 +01:00
c83b62ebaa Make searching while holding Ctrl or middle-clicking search icon open search externally 2017-11-02 09:46:33 +01:00
108cf8923e Implement analytics (#176)
* Implement analytics report generation w/ user config and basic system info

* Add HW and plugin info to analytics report generation

* Add a way of displaying the full analytics report

* Fix issues in analytics report and include design theme

* Ensure tab config is saved when switching tabs

* Fix compilation error in TabSettingsFeedback and safeguard nulls

* Add locale to analytics report

* Work on analytics (utils, last collection label, dependency refactoring)

* Add analytics state file and implement sending reports every week

* Send an analytics report after each update
2017-11-02 03:08:43 +01:00
4e26fd9d56 Random refactoring 2017-11-01 04:02:44 +01:00
8c9168a4bf Temporarily move notification with Alt and restore by middle-clicking title bar
Closes #175
2017-10-31 23:40:56 +01:00
97da0b79e4 Fix compile error after removing TimerBarHeight constant 2017-10-31 23:34:00 +01:00
d7e5f6876b Increase notification timer height with higher DPI 2017-10-31 23:10:08 +01:00
1b92b112e2 Refactor FormNotificationBase.GetBorderStyle 2017-10-31 12:50:34 +01:00
ca55119531 Rewrite CanMoveWindow and CanResizeWindow in notification classes 2017-10-31 12:36:46 +01:00
d9da14b5dc Make the example notification its own class 2017-10-31 12:01:53 +01:00
512b5666ac Fix 'Escape' key not clearing notification tweet queue & refactor HideNotification 2017-10-31 11:31:01 +01:00
64977964e8 Bump project versions 2017-10-31 10:53:42 +01:00
2bc13e0de6 Remove subprocess dependency on communication lib & remove Comms class 2017-10-31 10:51:33 +01:00
b90c5f17cf Fix misaligned Plugins form controls when reloading plugins after scrolling down 2017-10-31 07:18:38 +01:00
7d8d0bd43b Refactor awful plugin loading and management code 2017-10-31 07:13:17 +01:00
Alexander
54c1137927 Fix warning about possible null reference in audio playback error event (#174)
* Fix V3083 warnings from PVS-Studio Static Analyzer
2017-10-29 19:32:18 +01:00
e6655219ee Add a context menu item to apply ROT13 to text selection in inputs 2017-10-29 04:39:28 +01:00
5896f8e35a Allow only px values in custom items in edit-design plugin 2017-10-25 22:12:28 +02:00
934cba7251 Fix link expansion & tooltips not working with long domains (ellipsis in front)
Closes #172
2017-10-25 19:23:02 +02:00
9cc1a11bef Fix a rare issue where GIFs open in browser due to a missing source URL 2017-10-23 00:25:23 +02:00
c1bc956d6d Move TrayIcon to a different namespace 2017-10-21 15:46:52 +02:00
351b174b86 Refactor TweetDeck browser into a separate class 2017-10-21 15:35:46 +02:00
0b4aaf80dc Remove unnecessary usings and code 2017-10-21 14:53:50 +02:00
c10c185817 Release 1.10.3 2017-10-19 00:43:06 +02:00
327ef1cbee Restart TweetDuck if user declines UAC when updating and improve error handling 2017-10-19 00:30:37 +02:00
15eb823c7f Replace OpenExternalBrowserUnsafe with the new OpenAssociatedProgram 2017-10-19 00:24:40 +02:00
54613e5242 Handle errors when opening images in associated viewer 2017-10-19 00:21:53 +02:00
df1352cbe3 Update README.md 2017-10-18 23:50:23 +02:00
0559afd972 Make middle-click tweet actions work in detail view 2017-10-18 16:50:34 +02:00
afffca020e Add context menu item to view images in photo viewer 2017-10-18 13:50:20 +02:00
d663cc3f64 Add username to default video download filename & tweak playback error message 2017-10-17 19:39:35 +02:00
110d41e393 Add function to trigger video playback for plugins 2017-10-17 13:16:19 +02:00
1a8823f592 Fix clipboard html stripping crashing with no text data
Closes #171
2017-10-17 11:22:38 +02:00
6374a852b0 Merge branch 'master' of https://github.com/chylex/TweetDuck 2017-10-16 16:37:24 +02:00
a10c7dd7c3 Fix README heading links 2017-10-16 16:36:30 +02:00
547c7ea417 Update README heading links (still bork on GitHub) 2017-10-16 16:33:41 +02:00
760607995a Update README and post build log message 2017-10-16 16:30:55 +02:00
4704197c09 Add option for larger quote font size to edit-design & update modal layout 2017-10-16 15:38:19 +02:00
093ac1ac40 Make middle-click instant quote work with reply-account plugin 2017-10-16 09:32:52 +02:00
9ed8b0d904 Fix used account in updated middle-click actions 2017-10-15 23:48:56 +02:00
7346ce370d Add middle-click actions (reply popout, RT quote, fav modal) & fix popout in temp columns 2017-10-15 22:59:56 +02:00
adefdadc19 Fix border-radius when quoting a tweet 2017-10-15 22:29:44 +02:00
703bce2d00 Fix PostBuild.ps1 errors not causing failed build & refactor 2017-10-13 13:09:50 +02:00
97928ecd84 Check emoji-ordering.txt for carriage return on build 2017-10-13 12:54:15 +02:00
be9ea7f64a Fuck everything about gitattributes 2017-10-13 12:39:03 +02:00
ec2aaa8789 Add exact emoji name match detection to emoji keyboard 2017-10-13 12:20:54 +02:00
ab14b72526 Force LF in emoji-ordering.txt and other txt/js files 2017-10-13 12:16:39 +02:00
d8e304f3c1 Release 1.10.2 2017-10-12 13:01:47 +02:00
ea53ce361f Bump edit-design plugin version 2017-10-12 10:58:12 +02:00
2fce80b347 Fix custom font size in edit-design & time font in example notification 2017-10-11 20:42:42 +02:00
373c0b1cc3 Fix separator before 'Open dev tools' in example notification context menu 2017-10-11 20:34:50 +02:00
e5e1b7e608 Fix TweetDeck being unable to detect OS name 2017-10-11 20:33:39 +02:00
7e9221c9e0 Merge branch 'master' of https://github.com/chylex/TweetDuck 2017-10-11 20:24:09 +02:00
6b849f854e Rewrite font size & theme handling, add <html> attributes to notifications 2017-10-11 20:22:32 +02:00
831f6bc744 Add support section to readme 2017-10-10 12:25:52 +02:00
d282a7a537 Fix border-radius in media upload previews 2017-10-08 05:43:54 +02:00
fb2f1e3031 Release 1.10.1 2017-10-08 05:27:38 +02:00
00a0da3df3 Fix detail view screenshots & tweak screenshot timings for iframes separately 2017-10-08 01:42:53 +02:00
8c447b1ffb Maybe fix dragged tweet links not being recognized from certain drag sources 2017-09-29 18:04:44 +02:00
a4841175e8 Fix more border-radius, "Ready to Tweet" alignment, and docked reply close icon position 2017-09-29 16:52:39 +02:00
9b139132a1 Disable reply middle-click in temporary columns & fix random reloads from middle-clicks 2017-09-29 14:58:58 +02:00
4a404ecabc Fix weird button styles in inline replies in modal dialogs 2017-09-29 12:48:00 +02:00
aee758b559 Update reply-account docs & fix error with temporary columns and advanced selector 2017-09-29 12:47:13 +02:00
be060d0386 Add new info about emoji usage to the guide 2017-09-28 17:29:34 +02:00
0195378c10 Release 1.10 2017-09-28 17:09:46 +02:00
bc804c6a53 Allow linking to items in the guide 2017-09-28 13:33:54 +02:00
76b15f1971 Fix wrong emoji names & issues with emoji keyboard on while using :emoji_name: 2017-09-28 01:00:03 +02:00
c4d43c9d5b Fix bugs in :emoji_name: update (character count & duplicate keyboard modal) 2017-09-27 23:56:12 +02:00
e8d3e530de Update guide with a screenshot of the main menu 2017-09-27 17:08:51 +02:00
e145adec58 Bump emoji keyboard plugin version 2017-09-27 14:55:08 +02:00
e2dad3e477 Automatically convert :emoji_name: in emoji keyboard plugin
Closes #160
2017-09-27 13:25:05 +02:00
27bdbde171 Optimize images and scripts in the guide 2017-09-25 21:39:04 +02:00
e9ec27169c Improve guide for small width screens and screen readers 2017-09-25 21:12:47 +02:00
2e24cb634c Add favicon and meta tags to the guide 2017-09-25 19:47:00 +02:00
beb9046055 Add a button to send feedback / bug report to Feedback tab in Options 2017-09-25 17:23:16 +02:00
e57301952c Rewrite introduction text and update styles 2017-09-25 15:59:19 +02:00
7411279e48 Update guide URL 2017-09-25 14:22:19 +02:00
16acfa85b5 Bump project and plugin versions 2017-09-25 14:21:51 +02:00
41ef37f3f0 Fix GIFs in detail view not having the pointer cursor 2017-09-24 23:41:39 +02:00
00d8538726 Fix middle-clicking GIFs not opening the tweet externally
Closes #169
2017-09-24 23:35:31 +02:00
6eeb3f9895 Fix error when hovering a quoted tweet with a video 2017-09-24 23:22:20 +02:00
d19dca6ea5 Add a (currently unimplemented) anonymous data collection option 2017-09-24 15:31:44 +02:00
2008ccdaa4 Add an introduction modal & guide 2017-09-23 23:33:06 +02:00
ba2e62de3a Remove a warning in the reply-account plugin's default config 2017-09-23 10:17:55 +02:00
2b62eb254d Fix quoted tweet and media border radius in notifications & screenshots 2017-09-21 13:39:51 +02:00
31f72b7957 Move browser CSS to a file & optimize CSS injection 2017-09-21 13:19:21 +02:00
fdc4616875 Minify CSS files after build 2017-09-21 10:50:13 +02:00
b7de261d25 Move notification CSS to a file, refactor FontSizeLevel & TweetDeckBridge props 2017-09-21 10:34:57 +02:00
ae78a5a026 Fix screenshots of reply threads 2017-09-21 09:50:48 +02:00
fd2cf5d4d7 Make memory cleanup detect recent activity and fix modals 2017-09-21 09:06:55 +02:00
9f0997be1a Refactor some JS 2017-09-21 08:51:41 +02:00
dbade7f854 Fix crash when clicking video overlay after a playback error 2017-09-17 19:06:12 +02:00
3cdc1e190a Fix configuration.default.js files being minified on build 2017-09-14 19:40:41 +02:00
36bede7211 Tweak build instructions (wording and formatting) 2017-09-12 23:06:40 +02:00
46689bb700 Rewrite build and setup instructions 2017-09-12 22:51:59 +02:00
13e1a6543c Release 1.9.2 2017-09-12 20:29:40 +02:00
820ce9e845 Fix uploading files with uppercase extensions 2017-09-11 18:27:39 +02:00
f17806f4e8 Allow dragging tweet links over columns to open them in detail view
Closes #167
2017-09-10 23:03:03 +02:00
3f5ffc9e10 Fix t.co bypass when middle-clicking links in tweets 2017-09-09 19:16:08 +02:00
aeb0842ab4 Move system tray options to a separate tab 2017-09-08 11:36:34 +02:00
38837ae84c Add option to open search columns before the first column 2017-09-07 15:22:14 +02:00
a4eb6935af Fix 'Theme color tweaks' changing color of some scrollbars it shouldn't change 2017-09-06 12:57:43 +02:00
52f1f4c4eb Release 1.9.1 2017-09-05 22:55:32 +02:00
6c1782a038 Fix some twitter links (/signup, /tos, /privacy) having context menu for accounts 2017-09-05 22:43:07 +02:00
8b8f5f5473 Fix login page links opening in the app instead of external browser 2017-09-05 22:32:11 +02:00
61d3ed891a Update t.co bypass to work for media and bio urls 2017-09-05 21:47:22 +02:00
b1abf87320 Revert TweetDeck scrollbar color & fix notification scrollbar with 'Theme color tweaks' on 2017-09-05 14:55:29 +02:00
9aedfc2799 Fix scrollbar in Options not disappearing when switching tabs while animating 2017-09-04 17:52:01 +02:00
ad6240a067 Refactor delegating multiple events at once in code.js 2017-09-04 03:02:30 +02:00
9539eb076a Fix heart icon size and animation 2017-09-03 01:45:59 +02:00
c808e7bd83 Fix calling OpenExternalBrowser from non-UI threads, causing crashes or errors 2017-09-02 21:49:45 +02:00
13ea388f5e Fix upload dialog to include 'All Supported Formats' instead of separating them 2017-09-02 20:51:58 +02:00
c46dc0f1a3 Fix 'Open link in browser' not bypassing t.co 2017-09-02 20:30:54 +02:00
2ae311007d Make https scheme check first because https rocks 2017-09-02 13:54:54 +02:00
9344e02bff Add a privacy warning when opening a t.co link in case the bypass fails 2017-09-02 13:47:43 +02:00
40ad836fc3 Bypass t.co on click & fix right-clicking t.co links in notifications 2017-09-02 13:07:40 +02:00
e8604a261d Replace 'new EventArgs()' with 'EventArgs.Empty' 2017-09-01 14:34:23 +02:00
2a41d21a29 Add unit tests for UserConfig 2017-08-31 21:38:26 +02:00
4c62aa067b Update unit test generated file names 2017-08-31 19:45:56 +02:00
49db3074c6 Rewrite IO handling in unit tests 2017-08-31 19:33:24 +02:00
f5e3b34f30 Tweak border radius on inputs in column settings and custom timelines 2017-08-31 16:03:38 +02:00
f0affa4aec Implement 'Save all images as...' for images in quoted tweets 2017-08-31 15:34:05 +02:00
4f5075ac54 Fix 'Save image as...' usernames in quoted tweets & more refactoring 2017-08-31 15:26:03 +02:00
20f0445b10 Replace FormNotificationBase.ChirpId with CanViewDetail that checks ColumnId too 2017-08-31 14:52:38 +02:00
c77c974455 Rename more parameters and fields in TweetDeckBridge 2017-08-31 14:40:10 +02:00
44397b2d45 Fix parameter name in TweetDeckBridge 2017-08-31 14:07:46 +02:00
943d4d4d72 Release 1.9 2017-08-30 21:40:11 +02:00
6468c03465 Fix 'Restore Defaults' not resetting plugin status and import/reset not closing Plugins form 2017-08-30 21:34:12 +02:00
8141a5a5c5 Fix TrackBar labels being above focus cues 2017-08-30 21:14:58 +02:00
26a1779310 Fix 'Restart with Arguments' including disabled data folder message in shortcut 2017-08-30 21:00:59 +02:00
45d18ffafe Set volume slider SmallChange to 1 and increase width of video player volume slider 2017-08-30 21:00:11 +02:00
5f1c30609c Fix typo in error message in FileSerializer 2017-08-30 20:34:02 +02:00
7266d705d3 Fix video player UI for small videos & increase FormBrowser min size 2017-08-30 19:15:45 +02:00
ee6bb782d6 Tweak the download icon in video player 2017-08-30 16:59:01 +02:00
8ae6e2c886 Bump project and plugin versions 2017-08-30 16:51:53 +02:00
dd3a0d3890 Tweak emoji warning message 2017-08-30 14:57:36 +02:00
8d8e2da57e Make Enter key in emoji search insert the first available emoji 2017-08-30 14:53:43 +02:00
e60d204302 Refocus tweet input after closing emoji keyboard via icon & remove unused code 2017-08-30 14:50:39 +02:00
3d642d8ad2 Tweak emoji search to only select query on click and refocus it after clicking emoji 2017-08-30 14:48:43 +02:00
8db6e8a090 Remove unused custom emoji keyboard code 2017-08-30 14:09:32 +02:00
8153fcde85 Minor refactoring 2017-08-30 13:35:47 +02:00
96469cfca5 Rewrite config reload & fix some options breaking after import or reset 2017-08-30 12:53:10 +02:00
7601645c12 Fix some config options not being committed before opening Manage Options 2017-08-30 12:41:54 +02:00
c28615d548 Add options to reset session and plugin data when restoring defaults 2017-08-29 14:28:33 +02:00
b515add94e Rewrite browser/plugin reload handling when importing a profile 2017-08-29 14:26:42 +02:00
9fd5e9443d Make 'Manage Options' dialog close options after a successful operation 2017-08-29 14:22:20 +02:00
b2ddb1fab2 Disable 'Tray Highlight' option when the icon is disabled 2017-08-29 00:02:41 +02:00
fdac42947c Only activate parent form in video player if the player window itself is active 2017-08-28 23:41:46 +02:00
eeaf6949c5 Delay 'View detail' if the website is reloading 2017-08-28 22:46:06 +02:00
d7ad62d476 Make TweetNotification use persistent column ID 2017-08-28 22:38:11 +02:00
cd87a329fc Add a network error notification if the device goes offline
Closes #145
2017-08-28 22:14:07 +02:00
8c0d306823 Rewrite sound notification hook to be hopefully more reliable 2017-08-28 20:05:49 +02:00
d5c3ea0862 'View detail' errors now ask user if they want to open the tweet in a browser 2017-08-28 19:55:10 +02:00
83c962a7a4 Add support for icons in alert/confirm/prompt JS functions 2017-08-28 19:40:32 +02:00
40ef9a42dd Fix unsealed classes 2017-08-28 18:46:14 +02:00
868af5ac6a Goodbye, sweet rant 2017-08-28 18:19:32 +02:00
625227d0ce Rewrite audio library & add notification volume option for WMP impl 2017-08-28 18:16:13 +02:00
064627961e Fix zoom option label overlapping the slider 2017-08-28 17:16:04 +02:00
de0321cb2d Tweak video player label rendering & add label to volume slider 2017-08-28 15:31:27 +02:00
0d71a33b28 Add close/download/fullscreen buttons to video player 2017-08-28 13:31:19 +02:00
6d779f17b3 Fix video player tooltip going outside Form bounds 2017-08-28 10:37:18 +02:00
05510d7bc1 Add tooltip to seek bar in video player 2017-08-28 10:36:53 +02:00
8e162fe031 Add a custom tooltip to be used for video player controls 2017-08-27 23:13:39 +02:00
7ea7366a43 Change default CultureInfo in video player 2017-08-27 23:12:52 +02:00
445e6fcec0 Make Escape key in video player exit fullscreen or close the player 2017-08-27 21:03:57 +02:00
42f4d97d5d Rewrite key handling in video player 2017-08-27 20:46:10 +02:00
6357708533 Finish implementing 'View detail' context menu option in notifications
Closes #152
2017-08-27 20:11:56 +02:00
59c9801437 Address code analysis and remove unused code 2017-08-27 18:48:54 +02:00
d691bef1fb Add video context menu items and update video service check 2017-08-27 18:23:50 +02:00
442d74d0cb Refactor context menu handling and make adding new types of context easier 2017-08-27 18:18:30 +02:00
588bb9a093 Refactor FormNotificationBase to store TweetNotification instead of copying data 2017-08-27 13:40:49 +02:00
380e580d65 Fix cut off badge icon in notifications in notifications 2017-08-27 13:35:02 +02:00
4e306661f8 Fix cut off badge icon in notifications 2017-08-24 14:45:20 +02:00
9f3f33da93 More power! 2017-08-23 07:28:08 +02:00
69cd96a37c Add 'View detail' context menu item in notifications (currently loaded tweets only) 2017-08-22 11:59:34 +02:00
1293a2a533 Harness the incredible power of return-if statements 2017-08-22 10:10:46 +02:00
d24b7bbcb9 Implement return-if transpiler for JS files 2017-08-22 09:51:27 +02:00
b55b47b689 Refactor postbuild js/html processing script 2017-08-22 09:48:03 +02:00
c4c032b4d5 Bump TweetDuck.Video project version 2017-08-22 08:16:28 +02:00
970cd21964 Move TweetDuck.Video project folder 2017-08-22 08:13:49 +02:00
8ca9d242b2 Fix tab order in restart dialog 2017-08-22 07:30:17 +02:00
6f0518edcc Disable text input in locale drop-down in restart dialog 2017-08-22 07:22:09 +02:00
e2d15dd7e3 Add a shortcut target field to restart dialog 2017-08-22 07:20:40 +02:00
5c310e8647 Disable data folder in restart dialog for portable installs, and fix up tooltips 2017-08-22 06:24:02 +02:00
01dca0bc66 Fix sensitive media preference being ignored in notification previews 2017-08-22 04:59:55 +02:00
8b54fbdb2f Remove GC reload & threshold option migration code 2017-08-22 03:53:59 +02:00
663d0a633e Remove redundant Config.Save() call in TabSettingsGeneral 2017-08-22 03:44:13 +02:00
ccd5edb0e4 Remove legacy config file upgrade code 2017-08-22 03:23:53 +02:00
c6190db918 Rewrite update event args and update dismissal handling 2017-08-22 03:22:44 +02:00
3d4cec3b22 Remove update code that handles unsupported system check 2017-08-22 02:45:51 +02:00
5ed970b5a0 Remove resx file on FormUpdateDownload 2017-08-21 19:18:12 +02:00
c22934336b Remove Program.VersionFull and refactor plugin version checks 2017-08-21 18:47:26 +02:00
a3a52e0a1c Release 1.8.7 2017-08-21 14:32:15 +02:00
68dca6e3d9 Fix spacebar not toggling video pause when the main app was focused 2017-08-21 14:14:38 +02:00
017f883e0b Disable custom emoji input, fix selection handling and support twemoji font if installed 2017-08-21 13:37:21 +02:00
77b5c95f75 Add basic js minification (trim whitespace and remove single line comments) 2017-08-21 09:41:15 +02:00
9d052c8339 Update close button fix to only affect New Tweet drawer 2017-08-21 02:17:48 +02:00
d67623a657 Tweak follow notification padding in the browser 2017-08-21 01:52:19 +02:00
c740b3dd46 What the fuck are you doing Twitter 2017-08-21 01:35:53 +02:00
2ef5f7f96f Fix border radius on media previews in tweet detail 2017-08-16 18:27:44 +02:00
404568d795 Fix pre-build powershell command causing build error 2017-08-16 18:23:38 +02:00
b5a6337a0c Update custom CSS to work better with recent TweetDeck changes 2017-08-14 17:15:18 +02:00
82170c3fbd Fix sensitive media in notification previews and tweak follow notification padding 2017-08-14 16:12:34 +02:00
e6d6275fcc Work on emoji keyboard contenteditable fixes (selection, focus, editor migration) 2017-08-14 15:37:55 +02:00
97c865a127 Make emoji editor only show after adding emoji, fix minor UI issues 2017-08-14 04:22:13 +02:00
1ff21f0ee0 Make emoji keyboard replace tweet input with one that displays emoji
Closes #146
2017-08-14 00:47:08 +02:00
2a3dca4467 Rewrite video player to use duplex pipe for process communication 2017-08-13 17:52:46 +02:00
d4ecfcceec Tweak DuplexPipe to set key instead of data when separator is missing 2017-08-13 17:31:58 +02:00
ec5d503e4d Make DuplexPipe data serialized as key/value pairs 2017-08-13 17:23:23 +02:00
346391ca2d Remove unused 'using' statements for the billionth time 2017-08-13 16:55:08 +02:00
9074cdf340 Add a hover effect to video player seek bar 2017-08-13 16:46:33 +02:00
2fcf3604a8 Move video player form controls to a different namespace 2017-08-13 16:14:46 +02:00
34e5185fa1 Fuck localized .NET exceptions 2017-08-13 15:53:39 +02:00
e09e0e69ca Fuck browser process when building the project 2017-08-13 15:50:43 +02:00
963c98e588 Move interprocess comms to a separate project & implement duplex pipe 2017-08-13 15:20:04 +02:00
92acb823a4 Implement a duplex anonymous pipe in TweetLib.Communication 2017-08-13 15:14:17 +02:00
b967b1288f Move process communication to a separate project 2017-08-13 13:54:34 +02:00
1db271ce90 Fix spacebar triggering fullscreen in video player 2017-08-13 00:23:08 +02:00
58c64025e3 Fix level 2 lists and links in update changelog modal 2017-08-12 23:52:38 +02:00
643a7a87aa Release 1.8.6 2017-08-12 23:39:41 +02:00
5e9ed5d713 Improve video player startup and ensure it's always closed with the main app 2017-08-12 23:36:14 +02:00
78e492c764 Tweak 'stay open' pin position and tooltip 2017-08-12 20:33:52 +02:00
59c2a3642b Bump version of subprocess exe (should have been done a long time ago) 2017-08-12 19:10:38 +02:00
40ca923745 Cleanup FormPlayer code and set sync timer interval to 15 instead of 10 2017-08-12 19:08:17 +02:00
03af6cecaa Replace 'Stay open' checkbox with a pin icon
Closes #154
2017-08-12 17:49:27 +02:00
3992e447f4 Change tooltip border radius to be almost square 2017-08-12 17:47:55 +02:00
14a9edeb73 Fix various focus issues with video player and fix double-clicking control panel 2017-08-12 15:12:54 +02:00
92f1e9f7ec Make video player progress bar seek on mouse down instead of up 2017-08-12 14:31:46 +02:00
19c294c53e Terminate video player when pressing back mouse button over it 2017-08-12 13:43:53 +02:00
fe88ea5c05 Fix ctrl key not opening gifs externally 2017-08-12 03:37:24 +02:00
c9d551213a Remove license screen from installers 2017-08-12 03:15:51 +02:00
1e86a33ceb Hide video player overlay when video process exits gracelessly 2017-08-12 03:12:50 +02:00
551dd229f5 Make back mouse button hide video player and overlay 2017-08-12 03:04:24 +02:00
5ecf3c4147 Fix video player going past the end of a video when paused near the end 2017-08-12 02:26:52 +02:00
91bb2f4df0 Fix video player control panel not disappearing & improve error handling 2017-08-12 01:02:09 +02:00
ae3a0ae83d Fix crash when trying to update with 'Edit CSS' or 'Edit CEF Arguments' open 2017-08-12 00:05:56 +02:00
63ce7523de Fix oversight from previous commit 2017-08-12 00:01:13 +02:00
9e3b92bfc1 Move PluginManager initialization and move Form manipulation to FormManager 2017-08-11 23:57:44 +02:00
bc1767fb84 Change namespace of BrowserProcesses, MemoryUsageTracker, VideoPlayer 2017-08-11 23:50:16 +02:00
f917096cc7 Refactor plugin execution code 2017-08-11 23:32:47 +02:00
308926a2ae Add video player volume sync with user config 2017-08-11 20:58:37 +02:00
76f2b1a454 Make video player volume slider constant width 2017-08-11 20:20:07 +02:00
d899e4b38b Refactor video player control outside designer for dev convenience 2017-08-11 20:14:45 +02:00
e1422e35cc Add seeking + current time and duration to video player 2017-08-11 16:49:23 +02:00
2c00c6bb81 Expand the video player control panel and add progress bar 2017-08-11 16:21:31 +02:00
7e56ba6408 Make custom video player triggerable in tweet detail 2017-08-11 15:52:20 +02:00
8ceb70e67d Fix back button and context menu handling with a video playing 2017-08-11 15:22:45 +02:00
37d5efef1d Add an icon to TweetDuck.Video.exe 2017-08-11 15:06:38 +02:00
924065c26e Change video play icon color and handle playback errors 2017-08-11 13:59:05 +02:00
58cc7ea10d Add WIP video player for MP4s 2017-08-11 13:27:15 +02:00
f93e275ddf Add a volume slider to video player 2017-08-11 13:22:12 +02:00
06d2a5f715 Make video player pause/unpause when pressing space 2017-08-11 13:20:50 +02:00
3a7455eafe Fix video player cursor & pause/unpause on click 2017-08-11 12:33:34 +02:00
8b676fe6ce Implement video player in TweetDeck 2017-08-11 11:56:19 +02:00
54d12686af Tweak video player UI handling 2017-08-11 11:32:20 +02:00
f231256402 Improve player UI handling (cursor, position, setting owner handle) 2017-08-11 10:31:23 +02:00
410ead66f8 Add video player args and adjust location and size to owner window 2017-08-11 09:36:29 +02:00
c833a810af Add TweetDuck.Video project for video playback 2017-08-11 08:22:12 +02:00
50f1336b1d Tweak headings in update changelog renderer 2017-08-10 16:33:45 +02:00
60ed0b8cde Release 1.8.5.1 2017-08-10 16:25:55 +02:00
cc55a81c1b Remove emoji-instructions.txt during an update 2017-08-10 16:25:49 +02:00
f832e04e9e Remove unnecessary resx files and cleanup csproj 2017-08-10 15:09:15 +02:00
fc760b9a0c Fix another case in duplicate DM notifications 2017-08-10 13:46:13 +02:00
9addff0521 Exclude emoji-instructions.txt from build 2017-08-10 13:45:32 +02:00
dcaa3aab19 Work around duplicate DM notifications and rewrite recent tweet check 2017-08-10 00:51:38 +02:00
628785c68c Move _postbuild.bat to an MSBuild target directly in the project file 2017-08-10 00:20:58 +02:00
a5aa396fda Fix image quality setting not working in columns with large previews 2017-08-09 18:48:36 +02:00
f53a9f05e3 Fix image download filename for avatars and add more unit tests 2017-08-07 14:48:20 +02:00
7749b14156 Increment emoji keyboard plugin version 2017-08-06 20:32:13 +02:00
c15f339718 Fix emoji keyboard not disappearing after pressing ctrl+enter to tweet 2017-08-06 20:31:44 +02:00
775f590bfa Release 1.8.5 2017-08-06 15:58:28 +02:00
76408ea56f Increment verison of edit-design and emoji-keyboard plugins 2017-08-06 15:58:23 +02:00
a391d8ee83 Fix image pasting allowing more than 1 image in DMs 2017-08-05 21:52:38 +02:00
48c38f6e1d Include tweet author and quality in image download filename 2017-08-05 21:32:07 +02:00
37c5fba162 Change text color of sound notification file option for invalid paths 2017-08-05 19:50:30 +02:00
23e99b1d44 Update GC memory threshold defaults, also GC reload is enabled by default 2017-08-05 19:42:10 +02:00
8432240a47 Update HW acceleration & GC reload tooltips to note they won't be exported 2017-08-05 19:37:14 +02:00
a4bab743d6 Remove notification warning in GC reload option tooltip 2017-08-05 19:34:20 +02:00
60766789ab Move GC reload options to SystemConfig 2017-08-05 19:27:20 +02:00
ca014f881c Rewrite unknown property handling in FileSerializer 2017-08-05 19:23:42 +02:00
886eabe26c Show notifications that were missed during a browser reload 2017-08-05 18:43:57 +02:00
65b7167b5f Rewrite browser reload to save column notification state in session data 2017-08-05 18:36:17 +02:00
abbdde851e Make quoted tweets and RT account selectors square, fix RT account selector heading 2017-08-05 18:30:42 +02:00
54ac54aba6 Add session data that persists across browser reloads 2017-08-05 18:08:22 +02:00
184340f400 Increase delay for clearing recent notifications to prevent duplicates 2017-08-05 17:06:02 +02:00
93dd6813e8 Fix old icon alignment in 'Add column' dialog 2017-08-05 14:44:49 +02:00
b689b08711 Make follow notification button less visible when not hovered 2017-08-05 12:56:18 +02:00
1479a097d6 Fix alignment of old icons on buttons after TweetDeck update 2017-08-05 02:39:23 +02:00
b2be530f6b Remove legacy config file upgrade code 2017-08-01 19:29:01 +02:00
e4967ea46d Add paragraphs and level 1-2 headings to update notification markdown renderer 2017-08-01 17:33:47 +02:00
3f28f18fb4 Release 1.8.4.1 2017-08-01 17:11:34 +02:00
389 changed files with 23199 additions and 11292 deletions

5
.gitattributes vendored Normal file
View File

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

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
github: chylex
patreon: chylex
ko_fi: chylex

View File

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

View File

@@ -0,0 +1,28 @@
using System;
using System.Diagnostics;
using System.IO;
using TweetLib.Core.Application;
namespace TweetDuck.Application{
class SystemHandler : IAppSystemHandler{
void IAppSystemHandler.OpenAssociatedProgram(string path){
try{
using(Process.Start(new ProcessStartInfo{
FileName = path,
ErrorDialog = true
})){}
}catch(Exception e){
Program.Reporter.HandleException("Error Opening Program", "Could not open the associated program for " + path, true, e);
}
}
void IAppSystemHandler.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('/', '\\') + '"')){}
}
}
}
}

View File

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

View File

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

View File

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

View File

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

161
Browser/Data/ContextInfo.cs Normal file
View File

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

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Concurrent;
using CefSharp;
namespace TweetDuck.Browser.Data{
sealed class ResourceHandlers{
private readonly ConcurrentDictionary<string, IResourceHandler> handlers = new ConcurrentDictionary<string, IResourceHandler>(StringComparer.OrdinalIgnoreCase);
public bool HasHandler(IRequest request){
return handlers.ContainsKey(request.Url);
}
public IResourceHandler GetHandler(IRequest request){
return handlers.TryGetValue(request.Url, out var handler) ? handler : null;
}
public bool Register(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 Register(ResourceLink link){
return Register(link.Url, link.Handler);
}
public bool Unregister(string url){
return handlers.TryRemove(url, out IResourceHandler _);
}
public bool Unregister(ResourceLink link){
return Unregister(link.Url);
}
}
}

View File

@@ -0,0 +1,13 @@
using CefSharp;
namespace TweetDuck.Browser.Data{
sealed class ResourceLink{
public string Url { get; }
public IResourceHandler Handler { get; }
public ResourceLink(string url, IResourceHandler handler){
this.Url = url;
this.Handler = handler;
}
}
}

View File

@@ -1,12 +1,10 @@
using System;
using System.Drawing;
using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetDuck.Data.Serialization;
using TweetDuck.Controls;
using TweetLib.Core.Serialization.Converters;
using TweetLib.Core.Utils;
namespace TweetDuck.Data{
[Serializable] // TODO remove attribute with UserConfigLegacy
namespace TweetDuck.Browser.Data{
sealed class WindowState{
private Rectangle rect;
private bool isMaximized;

View File

@@ -1,21 +1,10 @@
namespace TweetDuck.Core {
namespace TweetDuck.Browser {
sealed partial class FormBrowser {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
@@ -24,7 +13,7 @@
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.trayIcon = new TweetDuck.Core.TrayIcon(this.components);
this.trayIcon = new TrayIcon(this.components);
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.timerResize = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout();
@@ -38,18 +27,18 @@
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = TweetDuck.Core.Utils.TwitterUtils.BackgroundColor;
this.ClientSize = new System.Drawing.Size(324, 386);
this.BackColor = TweetDuck.Utils.TwitterUtils.BackgroundColor;
this.ClientSize = new System.Drawing.Size(1008, 730);
this.Icon = Properties.Resources.icon;
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
this.MinimumSize = new System.Drawing.Size(340, 424);
this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation;
this.MinimumSize = new System.Drawing.Size(348, 424);
this.Name = "FormBrowser";
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
this.Activated += new System.EventHandler(this.FormBrowser_Activated);
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormBrowser_FormClosing);
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FormBrowser_FormClosed);
this.LocationChanged += new System.EventHandler(this.FormBrowser_LocationChanged);
this.ResizeEnd += new System.EventHandler(this.FormBrowser_ResizeEnd);
this.LocationChanged += new System.EventHandler(this.FormBrowser_LocationChanged);
this.Resize += new System.EventHandler(this.FormBrowser_Resize);
this.ResumeLayout(false);

613
Browser/FormBrowser.cs Normal file
View File

@@ -0,0 +1,613 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Browser.Bridge;
using TweetDuck.Browser.Handling;
using TweetDuck.Browser.Handling.General;
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.Management.Analytics;
using TweetDuck.Plugins;
using TweetDuck.Updates;
using TweetDuck.Utils;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Events;
using TweetLib.Core.Systems.Updates;
namespace TweetDuck.Browser{
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 UpdateInstaller UpdateInstaller { get; private set; }
private bool ignoreUpdateCheckError;
public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
#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 PluginManager plugins;
private readonly UpdateHandler updates;
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(PluginSchemeFactory pluginScheme){
InitializeComponent();
Text = Program.BrandName;
this.plugins = new PluginManager(Program.Config.Plugins, Program.PluginPath, Program.PluginDataPath);
this.plugins.Reloaded += plugins_Reloaded;
this.plugins.Executed += plugins_Executed;
this.plugins.Reload();
pluginScheme.Setup(plugins);
this.notification = new FormNotificationTweet(this, plugins);
this.notification.Show();
this.updates = new UpdateHandler(new UpdateCheckClient(Program.InstallerPath), TaskScheduler.FromCurrentSynchronizationContext());
this.updates.CheckFinished += updates_CheckFinished;
this.updateBridge = new UpdateBridge(updates, this);
this.updateBridge.UpdateAccepted += updateBridge_UpdateAccepted;
this.updateBridge.UpdateDismissed += updateBridge_UpdateDismissed;
this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge.Browser(this, notification), updateBridge);
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
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();
};
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();
}
protected override void Dispose(bool disposing){
if (disposing){
components?.Dispose();
updates.Dispose();
contextMenu.Dispose();
notificationScreenshotManager?.Dispose();
videoPlayer?.Dispose();
analytics?.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){
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 void plugins_Executed(object sender, PluginErrorEventArgs e){
if (e.HasErrors){
this.InvokeAsyncSafe(() => {
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n" + string.Join("\n\n", e.Errors), FormMessage.OK);
});
}
}
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
e.Result.Handle(update => {
string tag = update.VersionTag;
if (tag != Program.VersionTag && tag != Config.DismissedUpdate){
update.BeginSilentDownload();
browser.ShowUpdateNotification(tag, update.ReleaseNotes);
}
else{
updates.StartTimer();
}
}, ex => {
if (!ignoreUpdateCheckError){
Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
updates.StartTimer();
}
});
ignoreUpdateCheckError = true;
}
private void updateBridge_UpdateAccepted(object sender, UpdateInfo update){
FormManager.CloseAllDialogs();
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)){
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_UpdateDismissed(object sender, UpdateInfo update){
Config.DismissedUpdate = update.VersionTag;
Config.Save();
}
protected override void WndProc(ref Message m){
if (isLoaded && m.Msg == Program.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 != 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(){
Program.Resources.OnReloadTriggered();
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 videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay){
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){
Program.Reporter.HandleException("Error Opening Video Player", "Could not open the video player.", true, e);
}
}
AnalyticsFile.VideoPlays.Trigger();
}
public void StopVideo(){
videoPlayer?.Close();
}
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);
}
}
}
}

View File

@@ -0,0 +1,244 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Browser.Adapters;
using TweetDuck.Browser.Data;
using TweetDuck.Browser.Notification;
using TweetDuck.Configuration;
using TweetDuck.Controls;
using TweetDuck.Dialogs;
using TweetDuck.Management;
using TweetDuck.Management.Analytics;
using TweetDuck.Utils;
using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Utils;
namespace TweetDuck.Browser.Handling{
abstract class ContextMenuBase : IContextMenuHandler{
public static ContextInfo CurrentInfo { get; } = new ContextInfo();
protected static UserConfig Config => Program.Config.User;
private static 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 MenuCopyImage = (CefMenuCommand)26506;
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26507;
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26508;
private const CefMenuCommand MenuSearchInBrowser = (CefMenuCommand)26509;
private const CefMenuCommand MenuReadApplyROT13 = (CefMenuCommand)26510;
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
protected ContextInfo.ContextData Context { get; private set; }
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 (!TwitterUrls.IsTweetDeck(frame.Url) || browser.IsLoading){
Context = CurrentInfo.Reset();
}
else{
Context = CurrentInfo.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();
}
static string TextOpen(string name) => "Open " + name + " in browser";
static string TextCopy(string name) => "Copy " + name + " address";
static string TextSave(string name) => "Save " + name + " as...";
if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
if (TwitterUrls.RegexAccount.IsMatch(Context.UnsafeLinkUrl)){
model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
model.AddItem(MenuCopyUsername, "Copy account username");
}
else{
model.AddItem(MenuOpenLinkUrl, TextOpen("link"));
model.AddItem(MenuCopyLinkUrl, TextCopy("link"));
}
model.AddSeparator();
}
if (Context.Types.HasFlag(ContextInfo.ContextType.Video)){
model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
model.AddItem(MenuCopyMediaUrl, TextCopy("video"));
model.AddItem(MenuSaveMedia, TextSave("video"));
model.AddSeparator();
}
else if (Context.Types.HasFlag(ContextInfo.ContextType.Image) && Context.MediaUrl != FormNotificationBase.AppLogo.Url){
model.AddItem(MenuViewImage, "View image in photo viewer");
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
model.AddItem(MenuCopyImage, "Copy image");
model.AddItem(MenuSaveMedia, TextSave("image"));
if (Context.Chirp.Images.Length > 1){
model.AddItem(MenuSaveTweetImages, TextSave("all images"));
}
model.AddSeparator();
}
}
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
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 = TwitterUrls.RegexAccount.Match(url);
SetClipboardText(control, match.Success ? match.Groups[1].Value : url);
control.InvokeAsyncSafe(analytics.AnalyticsFile.CopiedUsernames.Trigger);
break;
}
case MenuOpenMediaUrl:
OpenBrowser(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality));
break;
case MenuCopyMediaUrl:
SetClipboardText(control, TwitterUrls.GetMediaLink(Context.MediaUrl, ImageQuality));
break;
case MenuCopyImage: {
string url = Context.MediaUrl;
control.InvokeAsyncSafe(() => {
TwitterUtils.CopyImage(url, ImageQuality);
});
break;
}
case MenuViewImage: {
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.OpenDevToolsCustom();
break;
}
return false;
}
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
Context = CurrentInfo.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){
CefScriptExecutor.RunScript(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(() => ClipboardManager.SetText(text, TextDataFormat.UnicodeText));
}
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel){
model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser) + 1, insertCommand, insertLabel);
}
protected static void AddDebugMenuItems(IMenuModel model){
if (BrowserUtils.HasDevTools){
AddSeparator(model);
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();
}
}
}
}

View File

@@ -0,0 +1,179 @@
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Browser.Data;
using TweetDuck.Controls;
using TweetLib.Core.Features.Twitter;
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 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 && TwitterUrls.IsTweetDeck(frame.Url)){
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();
}
}
}

View File

@@ -0,0 +1,14 @@
using CefSharp;
using TweetDuck.Management.Analytics;
namespace TweetDuck.Browser.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);
}
}
}

View File

@@ -1,13 +1,14 @@
using CefSharp;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
using TweetDuck.Browser.Notification;
using TweetDuck.Controls;
namespace TweetDuck.Core.Handling{
class ContextMenuNotification : ContextMenuBase{
private const int MenuSkipTweet = 26600;
private const int MenuFreeze = 26601;
private const int MenuCopyTweetUrl = 26602;
private const int MenuCopyQuotedTweetUrl = 26603;
namespace TweetDuck.Browser.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;
@@ -28,29 +29,31 @@ namespace TweetDuck.Core.Handling{
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
if (enableCustomMenu){
model.AddItem((CefMenuCommand)MenuSkipTweet, "Skip tweet");
model.AddCheckItem((CefMenuCommand)MenuFreeze, "Freeze");
model.SetChecked((CefMenuCommand)MenuFreeze, form.FreezeTimer);
model.AddSeparator();
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.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
model.AddSeparator();
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
}
model.AddSeparator();
}
}
if (HasDevTools){
AddDebugMenuItems(model);
}
RemoveSeparatorIfLast(model);
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
form.InvokeAsyncSafe(() => {
form.ContextMenuOpen = true;
form.AnalyticsFile.NotificationContextMenus.Trigger();
});
}
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
@@ -58,7 +61,7 @@ namespace TweetDuck.Core.Handling{
return true;
}
switch((int)commandId){
switch(commandId){
case MenuSkipTweet:
form.InvokeAsyncSafe(form.FinishCurrentNotification);
return true;
@@ -67,12 +70,16 @@ namespace TweetDuck.Core.Handling{
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
return true;
case MenuViewDetail:
form.InvokeSafe(form.ShowTweetDetail);
return true;
case MenuCopyTweetUrl:
SetClipboardText(form.CurrentTweetUrl);
SetClipboardText(form, form.CurrentTweetUrl);
return true;
case MenuCopyQuotedTweetUrl:
SetClipboardText(form.CurrentQuoteUrl);
SetClipboardText(form, form.CurrentQuoteUrl);
return true;
}

View File

@@ -0,0 +1,35 @@
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.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){}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,43 +1,65 @@
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
using TweetDuck.Controls;
using TweetDuck.Dialogs;
using TweetDuck.Utils;
namespace TweetDuck.Browser.Handling.General{
sealed class JavaScriptDialogHandler : IJsDialogHandler{
private static FormMessage CreateMessageForm(string caption, string text){
MessageBoxIcon icon = MessageBoxIcon.None;
int pipe = text.IndexOf('|');
if (pipe != -1){
icon = text.Substring(0, pipe) switch{
"error" => MessageBoxIcon.Error,
"warning" => MessageBoxIcon.Warning,
"info" => MessageBoxIcon.Information,
"question" => MessageBoxIcon.Question,
_ => MessageBoxIcon.None
};
if (icon != MessageBoxIcon.None){
text = text.Substring(pipe + 1);
}
}
return new FormMessage(caption, text, icon);
}
namespace TweetDuck.Core.Handling.General{
class JavaScriptDialogHandler : IJsDialogHandler{
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
browserControl.AsControl().InvokeSafe(() => {
FormMessage form;
TextBox input = null;
if (dialogType == CefJsDialogType.Alert){
form = new FormMessage("Browser Message", messageText, MessageBoxIcon.None);
form = CreateMessageForm("Browser Message", messageText);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
}
else if (dialogType == CefJsDialogType.Confirm){
form = new FormMessage("Browser Confirmation", messageText, MessageBoxIcon.None);
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 = new FormMessage("Browser Prompt", messageText, MessageBoxIcon.None);
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,
Location = new Point(BrowserUtils.Scale(22, dpiScale), form.ActionPanelY-BrowserUtils.Scale(46, dpiScale)),
Size = new Size(form.ClientSize.Width-BrowserUtils.Scale(44, dpiScale), 20)
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;
form.Height += input.Size.Height + input.Margin.Vertical;
}
else{
callback.Continue(false);
@@ -60,7 +82,8 @@ namespace TweetDuck.Core.Handling.General{
return true;
}
bool IJsDialogHandler.OnJSBeforeUnload(IWebBrowser browserControl, IBrowser browser, string message, bool isReload, IJsDialogCallback callback){
bool IJsDialogHandler.OnBeforeUnloadDialog(IWebBrowser browserControl, IBrowser browser, string messageText, bool isReload, IJsDialogCallback callback){
callback.Dispose();
return false;
}

View File

@@ -1,17 +1,20 @@
using CefSharp;
using TweetDuck.Core.Utils;
using TweetDuck.Controls;
using TweetDuck.Utils;
namespace TweetDuck.Core.Handling.General{
class LifeSpanHandler : ILifeSpanHandler{
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;
namespace TweetDuck.Browser.Handling.General{
sealed class LifeSpanHandler : ILifeSpanHandler{
private static bool IsPopupAllowed(string url){
return url.StartsWith("https://twitter.com/teams/authorize?");
}
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
switch(targetDisposition){
case WindowOpenDisposition.NewBackgroundTab:
case WindowOpenDisposition.NewForegroundTab:
case WindowOpenDisposition.NewPopup:
case WindowOpenDisposition.NewPopup when !IsPopupAllowed(targetUrl):
case WindowOpenDisposition.NewWindow:
BrowserUtils.OpenExternalBrowser(targetUrl);
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
return true;
default:
@@ -19,6 +22,11 @@ namespace TweetDuck.Core.Handling.General{
}
}
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){

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
using CefSharp;
using CefSharp.Handler;
using TweetDuck.Browser.Handling.General;
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 LifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl);
}
protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
if (autoReload){
browser.Reload();
}
}
}
}

View File

@@ -0,0 +1,23 @@
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);
}
}
}

View File

@@ -0,0 +1,79 @@
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(){}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,21 +1,10 @@
namespace TweetDuck.Core.Notification {
namespace TweetDuck.Browser.Notification {
partial class FormNotificationBase {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
@@ -34,7 +23,7 @@
this.BackColor = System.Drawing.SystemColors.Control;
this.ClientSize = new System.Drawing.Size(284, 122);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormNotification";

View File

@@ -0,0 +1,259 @@
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Browser.Data;
using TweetDuck.Browser.Handling;
using TweetDuck.Browser.Handling.General;
using TweetDuck.Configuration;
using TweetDuck.Controls;
using TweetDuck.Management.Analytics;
using TweetDuck.Utils;
using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Twitter;
namespace TweetDuck.Browser.Notification{
abstract partial class FormNotificationBase : Form, AnalyticsFile.IProvider{
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png"));
protected const string BlankURL = TwitterUrls.TweetDeck + "/?blank";
public static string FontSize = null;
public static string HeadLayout = null;
protected static UserConfig Config => Program.Config.User;
protected static int FontSizeLevel{
get => FontSize switch{
"largest" => 4,
"large" => 3,
"small" => 1,
"smallest" => 0,
_ => 2
};
}
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;
}
}
}
public AnalyticsFile AnalyticsFile => owner.AnalyticsFile;
protected override bool ShowWithoutActivation => true;
protected float DpiScale { get; }
protected double SizeScale => DpiScale * Config.ZoomLevel / 100.0;
protected readonly FormBrowser owner;
#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;
public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId);
protected bool IsPaused => pauseCounter > 0;
protected bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
public bool FreezeTimer { get; set; }
public bool ContextMenuOpen { get; set; }
protected FormNotificationBase(FormBrowser owner, bool enableContextMenu){
InitializeComponent();
this.owner = owner;
this.owner.FormClosed += owner_FormClosed;
var resourceRequestHandler = new ResourceRequestHandlerBase();
var resourceHandlers = resourceRequestHandler.ResourceHandlers;
resourceHandlers.Register(BlankURL, ResourceHandler.FromString(string.Empty));
resourceHandlers.Register(TwitterUrls.TweetDeck, this.resourceHandler);
resourceHandlers.Register(AppLogo);
this.browser = new ChromiumWebBrowser(BlankURL){
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBase(false),
ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory
};
this.browser.Dock = DockStyle.None;
this.browser.ClientSize = ClientSize;
this.browser.SetupZoomEvents();
Controls.Add(browser);
Disposed += (sender, args) => {
this.owner.FormClosed -= owner_FormClosed;
this.browser.Dispose();
};
DpiScale = this.GetDPIScale();
// ReSharper disable once VirtualMemberCallInContructor
UpdateTitle();
}
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(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 abstract string GetTweetHTML(DesktopNotification tweet);
protected virtual void LoadTweet(DesktopNotification tweet){
currentNotification = tweet;
resourceHandler.SetHTML(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);
}
}
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);
}
}
}
}

View File

@@ -1,4 +1,4 @@
namespace TweetDuck.Core.Notification {
namespace TweetDuck.Browser.Notification {
partial class FormNotificationMain {
/// <summary>
/// Required designer variable.
@@ -26,7 +26,7 @@
this.components = new System.ComponentModel.Container();
this.timerDisplayDelay = new System.Windows.Forms.Timer(this.components);
this.timerProgress = new System.Windows.Forms.Timer(this.components);
this.progressBarTimer = new TweetDuck.Core.Controls.FlatProgressBar();
this.progressBarTimer = new TweetDuck.Controls.FlatProgressBar();
this.SuspendLayout();
//
// timerDisplayDelay
@@ -49,7 +49,7 @@
this.progressBarTimer.Margin = new System.Windows.Forms.Padding(0);
this.progressBarTimer.Maximum = 1000;
this.progressBarTimer.Name = "progressBarTimer";
this.progressBarTimer.Size = new System.Drawing.Size(284, TimerBarHeight);
this.progressBarTimer.Size = new System.Drawing.Size(284, 4);
this.progressBarTimer.TabIndex = 1;
//
// FormNotification

View File

@@ -1,28 +1,23 @@
using CefSharp;
using System;
using System;
using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using CefSharp;
using TweetDuck.Browser.Adapters;
using TweetDuck.Browser.Bridge;
using TweetDuck.Browser.Handling;
using TweetDuck.Controls;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Resources;
namespace TweetDuck.Core.Notification{
partial class FormNotificationMain : FormNotificationBase{
private const string NotificationScriptFile = "notification.js";
private const int TimerBarHeight = 4;
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
private static readonly string PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile);
private static readonly string NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
private static readonly string PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
using TweetDuck.Utils;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums;
using TweetLib.Core.Features.Twitter;
namespace TweetDuck.Browser.Notification{
abstract partial class FormNotificationMain : FormNotificationBase{
private readonly PluginManager plugins;
private readonly int timerBarHeight;
protected int timeLeft, totalTime;
protected bool pausedDuringNotification;
@@ -31,12 +26,14 @@ namespace TweetDuck.Core.Notification{
private IntPtr mouseHook;
private bool blockXButtonUp;
private int currentOpacity;
private bool? prevDisplayTimer;
private int? prevFontSize;
public bool RequiresResize{
public virtual bool RequiresResize{
get{
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel || CanResizeWindow;
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
}
set{
@@ -45,57 +42,57 @@ namespace TweetDuck.Core.Notification{
prevFontSize = null;
}
else{
prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer;
prevFontSize = TweetNotification.FontSizeLevel;
prevDisplayTimer = Config.DisplayNotificationTimer;
prevFontSize = FontSizeLevel;
}
}
}
private int BaseClientWidth{
get{
switch(Program.UserConfig.NotificationSize){
default:
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*TweetNotification.FontSizeLevel));
case TweetNotification.Size.Custom:
return Program.UserConfig.CustomNotificationSize.Width;
}
}
get => Config.NotificationSize switch{
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Width,
_ => BrowserUtils.Scale(284, SizeScale * (1.0 + 0.05 * FontSizeLevel))
};
}
private int BaseClientHeight{
get{
switch(Program.UserConfig.NotificationSize){
default:
return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel));
case TweetNotification.Size.Custom:
return Program.UserConfig.CustomNotificationSize.Height;
}
}
get => Config.NotificationSize switch{
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Height,
_ => BrowserUtils.Scale(122, SizeScale * (1.0 + 0.08 * FontSizeLevel))
};
}
public Size BrowserSize{
get => Program.UserConfig.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-TimerBarHeight) : ClientSize;
}
protected virtual string BodyClasses => IsCursorOverBrowser ? "td-notification td-hover" : "td-notification";
public FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height - timerBarHeight) : ClientSize;
protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
InitializeComponent();
this.plugins = pluginManager;
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
browser.KeyboardHandler = new KeyboardHandlerNotification(this);
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this));
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
browser.RegisterJsBridge("$TD", new TweetDeckBridge.Notification(owner, this));
browser.LoadingStateChanged += Browser_LoadingStateChanged;
browser.FrameLoadEnd += Browser_FrameLoadEnd;
plugins.Register(PluginEnvironment.Notification, new PluginDispatcher(browser, url => TwitterUrls.IsTweetDeck(url) && url != BlankURL));
mouseHookDelegate = MouseHookProc;
Disposed += (sender, args) => StopMouseHook(true);
}
// helpers
private void SetOpacity(int opacity){
if (currentOpacity != opacity){
currentOpacity = opacity;
Opacity = opacity / 100.0;
}
}
// mouse wheel hook
private void StartMouseHook(){
@@ -116,8 +113,16 @@ namespace TweetDuck.Core.Notification{
if (nCode == 0){
int eventType = wParam.ToInt32();
if (eventType == NativeMethods.WM_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed/100.0), CefEventFlags.None);
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){
int delta = BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed * 0.01);
if (Config.EnableSmoothScrolling){
browser.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)){
@@ -131,6 +136,7 @@ namespace TweetDuck.Core.Notification{
}
blockXButtonUp = true;
this.InvokeAsyncSafe(AnalyticsFile.NotificationExtraMouseButtons.Trigger);
return NativeMethods.HOOK_HANDLED;
}
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){
@@ -151,13 +157,13 @@ namespace TweetDuck.Core.Notification{
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){
if (e.CloseReason == CloseReason.UserClosing){
HideNotification(true);
HideNotification();
e.Cancel = true;
}
}
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading && browser.Address != "about:blank"){
if (!e.IsLoading && browser.Address != BlankURL){
this.InvokeSafe(() => {
Visible = true; // ensures repaint before moving the window to a visible location
timerDisplayDelay.Start();
@@ -166,15 +172,11 @@ namespace TweetDuck.Core.Notification{
}
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier);
IFrame frame = e.Frame;
if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){
ScriptLoader.ExecuteScript(e.Frame, PluginJS, PluginScriptIdentifier);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false);
}
if (frame.IsMain && browser.Address != BlankURL){
frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
CefScriptExecutor.RunFile(frame, "notification.js");
}
}
@@ -184,12 +186,25 @@ namespace TweetDuck.Core.Notification{
}
private void timerHideProgress_Tick(object sender, EventArgs e){
if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen)return;
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(1025, (totalTime-timeLeft)/(double)totalTime);
progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value)));
int value = BrowserUtils.Scale(progressBarTimer.Maximum + 25, (totalTime - timeLeft) / (double)totalTime);
progressBarTimer.SetValueInstant(Config.NotificationTimerCountDown ? progressBarTimer.Maximum - value : value);
if (timeLeft <= 0){
FinishCurrentNotification();
@@ -198,25 +213,14 @@ namespace TweetDuck.Core.Notification{
// notification methods
public virtual void ShowNotification(TweetNotification notification){
public virtual void ShowNotification(DesktopNotification notification){
LoadTweet(notification);
}
public void ShowNotificationForSettings(bool reset){
if (reset){
LoadTweet(TweetNotification.ExampleTweet);
}
else{
PrepareAndDisplayWindow();
}
public override void HideNotification(){
base.HideNotification();
UpdateTitle();
}
public override void HideNotification(bool loadBlank){
base.HideNotification(loadBlank);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
timerProgress.Stop();
totalTime = 0;
@@ -246,27 +250,27 @@ namespace TweetDuck.Core.Notification{
}
}
protected override string GetTweetHTML(TweetNotification tweet){
string html = base.GetTweetHTML(tweet);
protected override string GetTweetHTML(DesktopNotification tweet){
string html = tweet.GenerateHtml(BodyClasses, HeadLayout, Config.CustomNotificationCSS);
foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){
html = injection.Inject(html);
foreach(InjectedHTML injection in plugins.NotificationInjections){
html = injection.InjectInto(html);
}
return html;
}
protected override void LoadTweet(TweetNotification tweet){
protected override void LoadTweet(DesktopNotification tweet){
timerProgress.Stop();
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
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 (Program.UserConfig.DisplayNotificationTimer){
ClientSize = new Size(width, height+TimerBarHeight);
if (Config.DisplayNotificationTimer){
ClientSize = new Size(width, height + timerBarHeight);
progressBarTimer.Visible = true;
}
else{
@@ -277,17 +281,17 @@ namespace TweetDuck.Core.Notification{
browser.ClientSize = new Size(width, height);
}
private void PrepareAndDisplayWindow(){
protected void PrepareAndDisplayWindow(){
if (RequiresResize){
RequiresResize = false;
SetNotificationSize(BaseClientWidth, BaseClientHeight);
}
SetOpacity(IsCursorOverBrowser ? 100 : Config.NotificationWindowOpacity);
MoveToVisibleLocation();
StartMouseHook();
}
protected override void OnNotificationReady(){
protected virtual void OnNotificationReady(){
PrepareAndDisplayWindow();
timerProgress.Start();
}

View File

@@ -1,4 +1,4 @@
namespace TweetDuck.Core.Notification {
namespace TweetDuck.Browser.Notification {
partial class FormNotificationTweet {
/// <summary>
/// Required designer variable.
@@ -40,7 +40,6 @@ namespace TweetDuck.Core.Notification {
//
// FormNotificationTweet
//
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormNotificationTweet_FormClosing);
this.ResumeLayout(true);
}

View File

@@ -1,49 +1,64 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using TweetDuck.Plugins;
using System.Windows.Forms;
using TweetDuck.Core.Utils;
using TweetDuck.Utils;
using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Plugins;
namespace TweetDuck.Core.Notification{
namespace TweetDuck.Browser.Notification{
sealed partial class FormNotificationTweet : FormNotificationMain{
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);
private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
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, PluginManager pluginManager) : base(owner, pluginManager, true){
InitializeComponent();
Program.UserConfig.MuteToggled += Config_MuteToggled;
Disposed += (sender, args) => Program.UserConfig.MuteToggled -= Config_MuteToggled;
Config.MuteToggled += Config_MuteToggled;
Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
if (Program.UserConfig.MuteNotifications){
if (Config.MuteNotifications){
PauseNotification();
}
}
private void FormNotificationTweet_FormClosing(object sender, FormClosingEventArgs e){
if (e.CloseReason == CloseReason.UserClosing){
tweetQueue.Clear(); // already canceled
TrimQueue();
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;
}
}
private void TrimQueue(){
if (needsTrim){
tweetQueue.TrimExcess();
needsTrim = false;
}
base.WndProc(ref m);
}
// event handlers
private void Config_MuteToggled(object sender, EventArgs e){
if (Program.UserConfig.MuteNotifications){
if (Config.MuteNotifications){
PauseNotification();
}
else{
@@ -59,7 +74,7 @@ namespace TweetDuck.Core.Notification{
}
private void timerIdlePauseCheck_Tick(object sender, EventArgs e){
if (NativeMethods.GetIdleSeconds() < Program.UserConfig.NotificationIdlePauseSeconds){
if (NativeMethods.GetIdleSeconds() < Config.NotificationIdlePauseSeconds){
ResumeNotification();
timerIdlePauseCheck.Stop();
}
@@ -67,12 +82,10 @@ namespace TweetDuck.Core.Notification{
// notification methods
public override void ShowNotification(TweetNotification notification){
if (IsPaused){
tweetQueue.Enqueue(notification);
}
else{
public override void ShowNotification(DesktopNotification notification){
tweetQueue.Enqueue(notification);
if (!IsPaused){
UpdateTitle();
if (totalTime == 0){
@@ -81,6 +94,19 @@ namespace TweetDuck.Core.Notification{
}
needsTrim |= tweetQueue.Count >= TrimMinimum;
AnalyticsFile.DesktopNotifications.Trigger();
}
public override void HideNotification(){
base.HideNotification();
tweetQueue.Clear();
if (needsTrim){
tweetQueue.TrimExcess();
needsTrim = false;
}
hasTemporarilyMoved = false;
}
public override void FinishCurrentNotification(){
@@ -88,8 +114,7 @@ namespace TweetDuck.Core.Notification{
LoadNextNotification();
}
else{
HideNotification(true);
TrimQueue();
HideNotification();
}
}
@@ -104,7 +129,7 @@ namespace TweetDuck.Core.Notification{
private void LoadNextNotification(){
if (!IsNotificationVisible){
if (Program.UserConfig.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit){
if (Config.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit){
if (!timerCursorCheck.Enabled){
PauseNotification();
timerCursorCheck.Start();
@@ -112,7 +137,7 @@ namespace TweetDuck.Core.Notification{
return;
}
else if (Program.UserConfig.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Program.UserConfig.NotificationIdlePauseSeconds){
else if (Config.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Config.NotificationIdlePauseSeconds){
if (!timerIdlePauseCheck.Enabled){
PauseNotification();
timerIdlePauseCheck.Start();
@@ -129,7 +154,7 @@ namespace TweetDuck.Core.Notification{
base.UpdateTitle();
if (tweetQueue.Count > 0){
Text = Text+" ("+tweetQueue.Count+" more left)";
Text = Text + " (" + tweetQueue.Count + " more left)";
}
}

View File

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

View File

@@ -0,0 +1,28 @@
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);
}
}
}

View File

@@ -0,0 +1,148 @@
#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.Windows.Forms;
using TweetDuck.Controls;
using TweetLib.Core.Features.Plugins;
#if GEN_SCREENSHOT_FRAMES
using System.Drawing.Imaging;
using System.IO;
using TweetDuck.Core.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();
#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 && screenshot.TakeScreenshot(true)){
try{
Clipboard.GetImage()?.Save(Path.Combine(DebugScreenshotPath, "frame_" + (++frameCounter) + ".png"), ImageFormat.Png);
}catch{
System.Diagnostics.Debug.WriteLine("Failed generating frame " + frameCounter);
}
}
else{
debugger.Stop();
}
}
#endif
}
}

View File

@@ -0,0 +1,48 @@
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Controls;
using TweetDuck.Dialogs;
using TweetDuck.Dialogs.Settings;
using TweetDuck.Management;
namespace TweetDuck.Browser.Notification{
static class SoundNotification{
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
public static IResourceHandler 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 ResourceHandler.FromFilePath(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;
}
}
}
}

View File

@@ -1,21 +1,10 @@
namespace TweetDuck.Core {
namespace TweetDuck.Browser {
partial class TrayIcon {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>

View File

@@ -1,26 +1,29 @@
using System;
using System.ComponentModel;
using System.Windows.Forms;
using TweetDuck.Configuration;
using Res = TweetDuck.Properties.Resources;
namespace TweetDuck.Core{
partial class TrayIcon : Component{
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 => notifyIcon.Visible;
set{
if (value){
notifyIcon.Icon = Properties.Resources.icon_tray;
get{
return notifyIcon.Visible;
}
set{
notifyIcon.Visible = value;
hasNotifications = false;
UpdateIcon();
}
}
@@ -30,9 +33,9 @@ namespace TweetDuck.Core{
}
set{
if (hasNotifications != value && Visible){
notifyIcon.Icon = value ? Properties.Resources.icon_tray_new : Properties.Resources.icon_tray;
if (hasNotifications != value){
hasNotifications = value;
UpdateIcon();
}
}
}
@@ -51,14 +54,36 @@ namespace TweetDuck.Core{
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);
@@ -66,7 +91,7 @@ namespace TweetDuck.Core{
}
private void contextMenu_Popup(object sender, EventArgs e){
contextMenu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications;
contextMenu.MenuItems[1].Checked = Config.MuteNotifications;
}
private void menuItemRestore_Click(object sender, EventArgs e){
@@ -74,8 +99,8 @@ namespace TweetDuck.Core{
}
private void menuItemMuteNotifications_Click(object sender, EventArgs e){
Program.UserConfig.MuteNotifications = !contextMenu.MenuItems[1].Checked;
Program.UserConfig.Save();
Config.MuteNotifications = !contextMenu.MenuItems[1].Checked;
Config.Save();
}
private void menuItemClose_Click(object sender, EventArgs e){

289
Browser/TweetDeckBrowser.cs Normal file
View File

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

View File

@@ -1,17 +1,18 @@
using System;
using TweetDuck.Data;
using TweetLib.Core.Collections;
namespace TweetDuck.Configuration{
static class Arguments{
// public args
public const string ArgDataFolder = "-datafolder";
public const string ArgLocale = "-locale";
public const string ArgLogging = "-log";
public const string ArgDebugUpdates = "-debugupdates";
public const string ArgIgnoreGDPR = "-nogdpr";
public const string ArgFreeze = "-freeze";
// internal args
public const string ArgRestart = "-restart";
public const string ArgImportCookies = "-importcookies";
public const string ArgDeleteCookies = "-deletecookies";
public const string ArgUpdated = "-updated";
// class data and methods
@@ -21,14 +22,15 @@ namespace TweetDuck.Configuration{
return Current.HasFlag(flag);
}
public static string GetValue(string key, string defaultValue){
return Current.GetValue(key, defaultValue);
public static string GetValue(string key){
return Current.GetValue(key);
}
public static CommandLineArgs GetCurrentClean(){
CommandLineArgs args = Current.Clone();
args.RemoveFlag(ArgRestart);
args.RemoveFlag(ArgImportCookies);
args.RemoveFlag(ArgDeleteCookies);
args.RemoveFlag(ArgUpdated);
return args;
}

View File

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

View File

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

View File

@@ -1,70 +1,28 @@
using System;
using System.IO;
using TweetDuck.Core.Utils;
using TweetDuck.Data.Serialization;
using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Configuration{
sealed class SystemConfig{
private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>{
OnReadUnknownProperty = (obj, property, value) => false
};
public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) &&
File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll"));
sealed class SystemConfig : BaseConfig{
// CONFIGURATION DATA
private bool _hardwareAcceleration = true;
public bool _hardwareAcceleration = true;
public bool ClearCacheAutomatically { get; set; } = true;
public int ClearCacheThreshold { get; set; } = 250;
// SPECIAL PROPERTIES
public bool HardwareAcceleration{
get => _hardwareAcceleration && IsHardwareAccelerationSupported;
set => _hardwareAcceleration = value;
get => _hardwareAcceleration;
set => UpdatePropertyWithRestartRequest(ref _hardwareAcceleration, value);
}
// END OF CONFIG
private readonly string file;
public SystemConfig(IConfigManager configManager) : base(configManager){}
private SystemConfig(string file){
this.file = file;
}
public bool Save(){
try{
WindowsUtils.CreateDirectoryForFile(file);
Serializer.Write(file, this);
return true;
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not save the system configuration file.", true, e);
return false;
}
}
public static SystemConfig Load(string file){
SystemConfig config = new SystemConfig(file);
try{
Serializer.Read(file, config);
return config;
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
}catch(FormatException){
try{
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
config.HardwareAcceleration = stream.ReadByte() > 0;
}
config.Save();
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not update the system configuration file.", true, e);
}
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not open the system configuration file. If you continue, you will lose system specific configuration such as Hardware Acceleration.", true, e);
}
return config;
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
return new SystemConfig(configManager);
}
}
}

View File

@@ -1,50 +1,49 @@
using System;
using System.Drawing;
using System.IO;
using TweetDuck.Core;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Data.Serialization;
using TweetDuck.Browser;
using TweetDuck.Browser.Data;
using TweetDuck.Controls;
using TweetLib.Core.Features.Notifications;
using TweetLib.Core.Features.Twitter;
using TweetLib.Core.Systems.Configuration;
namespace TweetDuck.Configuration{
sealed class UserConfig{
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{
OnReadUnknownProperty = (obj, property, value) => false
};
static UserConfig(){
Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
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]);
}
});
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]);
}
});
}
sealed class UserConfig : BaseConfig{
// CONFIGURATION DATA
public bool FirstRun { get; set; } = true;
public bool AllowDataCollection { get; set; } = false;
public WindowState BrowserWindow { get; set; } = new WindowState();
public WindowState PluginsWindow { get; set; } = new WindowState();
public Size PluginsWindowSize { get; set; } = Size.Empty;
public bool ExpandLinksOnHover { get; set; } = true;
public bool SwitchAccountSelectors { get; set; } = true;
public bool FocusDmInput { get; set; } = true;
public bool OpenSearchInFirstColumn { get; set; } = true;
public bool KeepLikeFollowDialogsOpen { get; set; } = true;
public bool BestImageQuality { get; set; } = true;
public bool EnableSpellCheck { get; set; } = false;
public bool EnableAnimatedImages { get; set; } = true;
private bool _enableSmoothScrolling = true;
private bool _enableTouchAdjustment = false;
private string _customCefArgs = null;
public string BrowserPath { get; set; } = null;
public string BrowserPathArgs { get; set; } = null;
public bool IgnoreTrackingUrlWarning { get; set; } = false;
public string SearchEngineUrl { get; set; } = null;
private int _zoomLevel = 100;
private bool _muteNotifications;
public string VideoPlayerPath { get; set; } = null;
public string VideoPlayerPathArgs { get; set; } = null;
public int VideoPlayerVolume { get; set; } = 50;
public bool EnableSpellCheck { get; set; } = false;
private string _spellCheckLanguage = "en-US";
public string TranslationTarget { get; set; } = "en";
public int CalendarFirstDay { get; set; } = -1;
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
public bool EnableTrayHighlight { get; set; } = true;
@@ -62,69 +61,77 @@ namespace TweetDuck.Configuration{
public bool NotificationTimerCountDown { get; set; } = false;
public int NotificationDurationValue { get; set; } = 25;
public TweetNotification.Position NotificationPosition { get; set; } = TweetNotification.Position.TopRight;
public DesktopNotification.Position NotificationPosition { get; set; } = DesktopNotification.Position.TopRight;
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
public int NotificationDisplay { get; set; } = 0;
public int NotificationEdgeDistance { get; set; } = 8;
public int NotificationWindowOpacity { get; set; } = 100;
public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.Size.Auto;
public DesktopNotification.Size NotificationSize { get; set; } = DesktopNotification.Size.Auto;
public Size CustomNotificationSize { get; set; } = Size.Empty;
public int NotificationScrollSpeed { get; set; } = 10;
public int NotificationScrollSpeed { get; set; } = 100;
private string _notificationSoundPath;
private int _notificationSoundVolume = 100;
private bool _muteNotifications;
public string CustomCefArgs { get; set; } = null;
public string CustomBrowserCSS { get; set; } = null;
public string CustomNotificationCSS { get; set; } = null;
public bool EnableBrowserGCReload { get; set; } = false;
public int BrowserMemoryThreshold { get; set; } = 350;
public bool DevToolsWindowOnTop { get; set; } = true;
// SPECIAL PROPERTIES
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default;
public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default;
public string NotificationSoundPath{
get => string.IsNullOrEmpty(_notificationSoundPath) ? string.Empty : _notificationSoundPath;
set => _notificationSoundPath = value;
get => _notificationSoundPath ?? string.Empty;
set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged);
}
public int NotificationSoundVolume{
get => _notificationSoundVolume;
set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged);
}
public bool MuteNotifications{
get => _muteNotifications;
set{
if (_muteNotifications != value){
_muteNotifications = value;
MuteToggled?.Invoke(this, new EventArgs());
}
}
set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled);
}
public int ZoomLevel{
get => _zoomLevel;
set{
if (_zoomLevel != value){
_zoomLevel = value;
ZoomLevelChanged?.Invoke(this, new EventArgs());
set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged);
}
}
}
public double ZoomMultiplier => _zoomLevel/100.0;
public TrayIcon.Behavior TrayBehavior{
get => _trayBehavior;
set => UpdatePropertyWithEvent(ref _trayBehavior, value, TrayBehaviorChanged);
}
set{
if (_trayBehavior != value){
_trayBehavior = value;
TrayBehaviorChanged?.Invoke(this, new EventArgs());
public bool EnableSmoothScrolling{
get => _enableSmoothScrolling;
set => UpdatePropertyWithRestartRequest(ref _enableSmoothScrolling, value);
}
public bool EnableTouchAdjustment{
get => _enableTouchAdjustment;
set => UpdatePropertyWithRestartRequest(ref _enableTouchAdjustment, value);
}
public string CustomCefArgs{
get => _customCefArgs;
set => UpdatePropertyWithRestartRequest(ref _customCefArgs, value);
}
public string SpellCheckLanguage{
get => _spellCheckLanguage;
set => UpdatePropertyWithRestartRequest(ref _spellCheckLanguage, value);
}
// EVENTS
@@ -132,68 +139,14 @@ namespace TweetDuck.Configuration{
public event EventHandler MuteToggled;
public event EventHandler ZoomLevelChanged;
public event EventHandler TrayBehaviorChanged;
public event EventHandler SoundNotificationChanged;
// END OF CONFIG
private readonly string file;
public UserConfig(IConfigManager configManager) : base(configManager){}
public UserConfig(string file){ // TODO make private after removing UserConfigLegacy
this.file = file;
}
public bool Save(){
try{
WindowsUtils.CreateDirectoryForFile(file);
if (File.Exists(file)){
string backupFile = GetBackupFile(file);
File.Delete(backupFile);
File.Move(file, backupFile);
}
Serializer.Write(file, this);
return true;
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e);
return false;
}
}
public static UserConfig Load(string file){
Exception firstException = null;
for(int attempt = 0; attempt < 2; attempt++){
try{
UserConfig config = new UserConfig(file);
Serializer.Read(attempt == 0 ? file : GetBackupFile(file), config);
return config;
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
break;
}catch(FormatException){
UserConfig config = UserConfigLegacy.Load(file);
config.Save();
return config;
}catch(Exception e){
if (attempt == 0){
firstException = e;
Program.Reporter.Log(e.ToString());
}
else if (firstException != null){
Program.Reporter.HandleException("Configuration Error", "Could not open the backup configuration file. If you continue, your program options will be reset.", true, e);
}
}
}
if (firstException != null){
Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException);
}
return new UserConfig(file);
}
public static string GetBackupFile(string file){
return file+".bak";
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
return new UserConfig(configManager);
}
}
}

View File

@@ -1,210 +0,0 @@
using System;
using System.Drawing;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using TweetDuck.Core;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
using TweetDuck.Data;
namespace TweetDuck.Configuration{
[Serializable]
sealed class UserConfigLegacy{ // TODO remove eventually
private static readonly IFormatter Formatter = new BinaryFormatter{ Binder = new LegacyBinder() };
private class LegacyBinder : SerializationBinder{
public override Type BindToType(string assemblyName, string typeName){
return Type.GetType(string.Format("{0}, {1}", typeName.Replace("TweetDck", "TweetDuck").Replace(".UserConfig", ".UserConfigLegacy").Replace("Core.Utils.WindowState", "Data.WindowState"), assemblyName.Replace("TweetDck", "TweetDuck")));
}
}
private const int CurrentFileVersion = 11;
// START OF CONFIGURATION
public WindowState BrowserWindow { get; set; }
public WindowState PluginsWindow { get; set; }
public bool DisplayNotificationColumn { get; set; }
public bool DisplayNotificationTimer { get; set; }
public bool NotificationTimerCountDown { get; set; }
public bool NotificationSkipOnLinkClick { get; set; }
public bool NotificationNonIntrusiveMode { get; set; }
public int NotificationIdlePauseSeconds { get; set; }
public int NotificationDurationValue { get; set; }
public int NotificationScrollSpeed { get; set; }
public TweetNotification.Position NotificationPosition { get; set; }
public Point CustomNotificationPosition { get; set; }
public int NotificationEdgeDistance { get; set; }
public int NotificationDisplay { get; set; }
public TweetNotification.Size NotificationSize { get; set; }
public Size CustomNotificationSize { get; set; }
public bool EnableSpellCheck { get; set; }
public bool ExpandLinksOnHover { get; set; }
public bool SwitchAccountSelectors { get; set; }
public bool EnableTrayHighlight { get; set; }
public bool EnableUpdateCheck { get; set; }
public string DismissedUpdate { get; set; }
public string CustomCefArgs { get; set; }
public string CustomBrowserCSS { get; set; }
public string CustomNotificationCSS { get; set; }
public string NotificationSoundPath{
get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath;
set => notificationSoundPath = value;
}
public bool MuteNotifications{
get => muteNotifications;
set => muteNotifications = value;
}
public int ZoomLevel{
get => zoomLevel;
set => zoomLevel = value;
}
public TrayIcon.Behavior TrayBehavior{
get => trayBehavior;
set => trayBehavior = value;
}
// END OF CONFIGURATION
[NonSerialized]
private string file;
private int fileVersion;
private bool muteNotifications;
private int zoomLevel;
private string notificationSoundPath;
private TrayIcon.Behavior trayBehavior;
private UserConfigLegacy(string file){
this.file = file;
BrowserWindow = new WindowState();
ZoomLevel = 100;
DisplayNotificationTimer = true;
NotificationNonIntrusiveMode = true;
NotificationPosition = TweetNotification.Position.TopRight;
CustomNotificationPosition = ControlExtensions.InvisibleLocation;
NotificationSize = TweetNotification.Size.Auto;
NotificationEdgeDistance = 8;
NotificationDurationValue = 25;
NotificationScrollSpeed = 100;
EnableUpdateCheck = true;
ExpandLinksOnHover = true;
SwitchAccountSelectors = true;
EnableTrayHighlight = true;
PluginsWindow = new WindowState();
}
private void UpgradeFile(){
if (fileVersion == CurrentFileVersion){
return;
}
// if outdated, cycle through all versions
if (fileVersion <= 5){
DisplayNotificationTimer = true;
EnableUpdateCheck = true;
ExpandLinksOnHover = true;
BrowserWindow = new WindowState();
PluginsWindow = new WindowState();
EnableTrayHighlight = true;
NotificationDurationValue = 25;
fileVersion = 6;
}
if (fileVersion == 6){
NotificationNonIntrusiveMode = true;
++fileVersion;
}
if (fileVersion == 7){
ZoomLevel = 100;
++fileVersion;
}
if (fileVersion == 8){
SwitchAccountSelectors = true;
++fileVersion;
}
if (fileVersion == 9){
NotificationScrollSpeed = 100;
++fileVersion;
}
if (fileVersion == 10){
NotificationSize = TweetNotification.Size.Auto;
++fileVersion;
}
// update the version
fileVersion = CurrentFileVersion;
}
public UserConfig ConvertLegacy(){
return new UserConfig(file){
BrowserWindow = BrowserWindow,
PluginsWindow = PluginsWindow,
DisplayNotificationColumn = DisplayNotificationColumn,
DisplayNotificationTimer = DisplayNotificationTimer,
NotificationTimerCountDown = NotificationTimerCountDown,
NotificationSkipOnLinkClick = NotificationSkipOnLinkClick,
NotificationNonIntrusiveMode = NotificationNonIntrusiveMode,
NotificationIdlePauseSeconds = NotificationIdlePauseSeconds,
NotificationDurationValue = NotificationDurationValue,
NotificationScrollSpeed = NotificationScrollSpeed,
NotificationPosition = NotificationPosition,
CustomNotificationPosition = CustomNotificationPosition,
NotificationEdgeDistance = NotificationEdgeDistance,
NotificationDisplay = NotificationDisplay,
NotificationSize = NotificationSize,
CustomNotificationSize = CustomNotificationSize,
EnableSpellCheck = EnableSpellCheck,
ExpandLinksOnHover = ExpandLinksOnHover,
SwitchAccountSelectors = SwitchAccountSelectors,
EnableTrayHighlight = EnableTrayHighlight,
EnableUpdateCheck = EnableUpdateCheck,
DismissedUpdate = DismissedUpdate,
CustomCefArgs = CustomCefArgs,
CustomBrowserCSS = CustomBrowserCSS,
CustomNotificationCSS = CustomNotificationCSS,
NotificationSoundPath = NotificationSoundPath,
MuteNotifications = MuteNotifications,
ZoomLevel = ZoomLevel,
TrayBehavior = TrayBehavior
};
}
public static UserConfig Load(string file){
UserConfigLegacy config = null;
try{
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
if ((config = Formatter.Deserialize(stream) as UserConfigLegacy) != null){
config.file = file;
}
}
config?.UpgradeFile();
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, e);
}
return (config ?? new UserConfigLegacy(file)).ConvertLegacy();
}
}
}

View File

@@ -1,10 +1,9 @@
using System;
using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Controls{
namespace TweetDuck.Controls{
static class ControlExtensions{
public static readonly Point InvisibleLocation = new Point(-32000, -32000);
@@ -22,9 +21,8 @@ namespace TweetDuck.Core.Controls{
}
public static float GetDPIScale(this Control control){
using(Graphics graphics = control.CreateGraphics()){
return graphics.DpiY/96F;
}
using Graphics graphics = control.CreateGraphics();
return graphics.DpiY / 96F;
}
public static bool IsFullyOutsideView(this Form form){
@@ -32,17 +30,17 @@ namespace TweetDuck.Core.Controls{
}
public static void MoveToCenter(this Form targetForm, Form parentForm){
targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
targetForm.Location = new Point(parentForm.Location.X + (parentForm.Width / 2) - (targetForm.Width / 2), parentForm.Location.Y + (parentForm.Height / 2) - (targetForm.Height / 2));
}
public static void SetValueInstant(this ProgressBar bar, int value){
if (value == bar.Maximum){
bar.Value = value;
bar.Value = value-1;
bar.Value = value - 1;
bar.Value = value;
}
else{
bar.Value = value+1;
bar.Value = value + 1;
bar.Value = value;
}
}
@@ -61,16 +59,11 @@ namespace TweetDuck.Core.Controls{
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);
trackBar.Value = trackBar.SmallChange * (int)Math.Floor(((double)trackBar.Value / trackBar.SmallChange) + 0.5);
return false;
}
else return true;
}
public static void SetElevated(this Button button){
button.Text = " "+button.Text;
button.FlatStyle = FlatStyle.System;
NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, new UIntPtr(0), new IntPtr(1));
return true;
}
public static void EnableMultilineShortcuts(this TextBox textBox){

View File

@@ -1,8 +1,8 @@
using System;
using System.Windows.Forms;
namespace TweetDuck.Core.Controls{
class FlatButton : Button{
namespace TweetDuck.Controls{
sealed class FlatButton : Button{
protected override bool ShowFocusCues => false;
public FlatButton(){

View File

@@ -1,7 +1,8 @@
using System.Drawing;
using System;
using System.Drawing;
using System.Windows.Forms;
namespace TweetDuck.Core.Controls{
namespace TweetDuck.Controls{
sealed class FlatProgressBar : ProgressBar{
private readonly SolidBrush brush;
@@ -13,7 +14,7 @@ namespace TweetDuck.Core.Controls{
}
public void SetValueInstant(int value){
ControlExtensions.SetValueInstant(this, value);
ControlExtensions.SetValueInstant(this, Math.Max(Minimum, Math.Min(Maximum, value)));
}
protected override void OnPaint(PaintEventArgs e){
@@ -22,8 +23,8 @@ namespace TweetDuck.Core.Controls{
}
Rectangle rect = e.ClipRectangle;
rect.Width = (int)(rect.Width*((double)Value/Maximum));
e.Graphics.FillRectangle(brush,rect);
rect.Width = (int)(rect.Width * ((double)Value / Maximum));
e.Graphics.FillRectangle(brush, rect);
}
protected override void Dispose(bool disposing){

View File

@@ -0,0 +1,14 @@
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);
}
}
}

22
Controls/LabelVertical.cs Normal file
View File

@@ -0,0 +1,22 @@
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;
}
}
}
}

View File

@@ -1,7 +1,7 @@
using System.ComponentModel;
using System.Windows.Forms;
namespace TweetDuck.Core.Controls{
namespace TweetDuck.Controls{
sealed class NumericUpDownEx : NumericUpDown{
public string TextSuffix { get; set ; }

View File

@@ -1,19 +0,0 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
namespace TweetDuck.Core.Bridge{
sealed class CallbackBridge{
private readonly Control owner;
private readonly Action safeCallback;
public CallbackBridge(Control owner, Action safeCallback){
this.owner = owner;
this.safeCallback = safeCallback;
}
public void Trigger(){
owner.InvokeSafe(safeCallback);
}
}
}

View File

@@ -1,32 +0,0 @@
using System.Text;
namespace TweetDuck.Core.Bridge{
static class PropertyBridge{
public enum Environment{
Browser, Notification
}
public static string GenerateScript(Environment environment){
string Bool(bool value){
return value ? "true;" : "false;";
}
StringBuilder build = new StringBuilder().Append("(function(x){");
build.Append("x.expandLinksOnHover=").Append(Bool(Program.UserConfig.ExpandLinksOnHover));
if (environment == Environment.Browser){
build.Append("x.switchAccountSelectors=").Append(Bool(Program.UserConfig.SwitchAccountSelectors));
build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications));
build.Append("x.hasCustomNotificationSound=").Append(Bool(Program.UserConfig.NotificationSoundPath.Length > 0));
build.Append("x.notificationMediaPreviews=").Append(Bool(Program.UserConfig.NotificationMediaPreviews));
}
if (environment == Environment.Notification){
build.Append("x.skipOnLinkClick=").Append(Bool(Program.UserConfig.NotificationSkipOnLinkClick));
}
return build.Append("})(window.$TDX=window.$TDX||{})").ToString();
}
}
}

View File

@@ -1,127 +0,0 @@
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Bridge{
sealed class TweetDeckBridge{
public static string LastRightClickedLink = string.Empty;
public static string LastRightClickedImage = string.Empty;
public static string LastHighlightedTweet = string.Empty;
public static string LastHighlightedQuotedTweet = string.Empty;
public static string[] LastHighlightedTweetImages = StringUtils.EmptyArray;
public static void ResetStaticProperties(){
LastRightClickedLink = LastRightClickedImage = LastHighlightedTweet = LastHighlightedQuotedTweet = string.Empty;
LastHighlightedTweetImages = StringUtils.EmptyArray;
}
private readonly FormBrowser form;
private readonly FormNotificationMain notification;
public TweetDeckBridge(FormBrowser form, FormNotificationMain notification){
this.form = form;
this.notification = notification;
}
public void LoadFontSizeClass(string fsClass){
form.InvokeAsyncSafe(() => {
TweetNotification.SetFontSizeClass(fsClass);
});
}
public void LoadNotificationHeadContents(string headContents){
form.InvokeAsyncSafe(() => {
TweetNotification.SetHeadTag(headContents);
});
}
public void SetLastRightClickedLink(string link){
form.InvokeAsyncSafe(() => LastRightClickedLink = link);
}
public void SetLastRightClickedImage(string link){
form.InvokeAsyncSafe(() => LastRightClickedImage = link);
}
public void SetLastHighlightedTweet(string link, string quotedLink, string imageList){
form.InvokeAsyncSafe(() => {
LastHighlightedTweet = link;
LastHighlightedQuotedTweet = quotedLink;
LastHighlightedTweetImages = imageList.Split(';');
});
}
public void OpenContextMenu(){
form.InvokeAsyncSafe(form.OpenContextMenu);
}
public void OnTweetPopup(string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
notification.InvokeAsyncSafe(() => {
form.OnTweetNotification();
notification.ShowNotification(new TweetNotification(columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
});
}
public void OnTweetSound(){
form.InvokeAsyncSafe(() => {
form.OnTweetNotification();
form.PlayNotificationSound();
});
}
public void DisplayTooltip(string text, bool showInNotification){
if (showInNotification){
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
}
else{
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
}
}
public void LoadNextNotification(){
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
}
public void ScreenshotTweet(string html, int width, int height){
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
}
public void FixClipboard(){
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
}
public int GetIdleSeconds(){
return NativeMethods.GetIdleSeconds();
}
public void OpenBrowser(string url){
BrowserUtils.OpenExternalBrowser(url);
}
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
Log(message);
System.Diagnostics.Debugger.Break();
#endif
}
public void Log(string data){
System.Diagnostics.Debug.WriteLine(data);
}
}
}

View File

@@ -1,23 +0,0 @@
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)-2; // 2 = random
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;
}
}
}
}
}

View File

@@ -1,531 +0,0 @@
using CefSharp;
using CefSharp.WinForms;
using System;
using System.Drawing;
using System.Linq;
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.Notification;
using TweetDuck.Core.Notification.Screenshot;
using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Settings;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events;
using TweetDuck.Resources;
using TweetDuck.Updates;
using TweetDuck.Updates.Events;
using TweetLib.Audio;
namespace TweetDuck.Core{
sealed partial class FormBrowser : Form{
private static UserConfig Config => Program.UserConfig;
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 readonly ChromiumWebBrowser browser;
private readonly PluginManager plugins;
private readonly UpdateHandler updates;
private readonly FormNotificationTweet notification;
private readonly ContextMenu contextMenu;
private readonly MemoryUsageTracker memoryUsageTracker;
private bool isLoaded;
private bool isBrowserReady;
private FormWindowState prevState;
private TweetScreenshotManager notificationScreenshotManager;
private SoundNotification soundNotification;
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
InitializeComponent();
Text = Program.BrandName;
this.plugins = pluginManager;
this.plugins.Reloaded += plugins_Reloaded;
this.plugins.PluginChangedState += plugins_PluginChangedState;
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
this.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup");
this.notification = new FormNotificationTweet(this, plugins){
#if DEBUG
CanMoveWindow = () => (ModifierKeys & Keys.Alt) == Keys.Alt
#else
CanMoveWindow = () => false
#endif
};
this.notification.Show();
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
MenuHandler = new ContextMenuBrowser(this),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBrowser()
};
#if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
this.browser.FrameLoadStart += browser_FrameLoadStart;
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
this.browser.LoadError += browser_LoadError;
this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
browser.Dock = DockStyle.None;
browser.Location = ControlExtensions.InvisibleLocation;
Controls.Add(browser);
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
Disposed += (sender, args) => {
memoryUsageTracker.Dispose();
browser.Dispose();
contextMenu.Dispose();
notificationScreenshotManager?.Dispose();
soundNotification?.Dispose();
};
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
this.trayIcon.ClickClose += trayIcon_ClickClose;
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
UpdateTrayIcon();
Config.MuteToggled += Config_MuteToggled;
Config.ZoomLevelChanged += Config_ZoomLevelChanged;
this.updates = new UpdateHandler(browser, updaterSettings);
this.updates.UpdateAccepted += updates_UpdateAccepted;
this.updates.UpdateDismissed += updates_UpdateDismissed;
RestoreWindow();
}
private bool TryBringToFront<T>() where T : Form{
T form = Application.OpenForms.OfType<T>().FirstOrDefault();
if (form != null){
form.BringToFront();
return true;
}
else return false;
}
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 OnBrowserReady(){
if (!isBrowserReady){
browser.Location = Point.Empty;
browser.Dock = DockStyle.Fill;
isBrowserReady = true;
}
}
private void UpdateTrayIcon(){
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
}
// active event handlers
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){
foreach(string word in TwitterUtils.DictionaryWords){
browser.AddWordToDictionary(word);
}
BeginInvoke(new Action(OnBrowserReady));
browser.LoadingStateChanged -= browser_LoadingStateChanged;
}
}
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
if (e.Frame.IsMain){
memoryUsageTracker.Stop();
if (Config.ZoomLevel != 100){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
}
if (TwitterUtils.IsTwitterWebsite(e.Frame)){
ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
}
}
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){
e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix);
UpdateProperties(PropertyBridge.Environment.Browser);
ScriptLoader.ExecuteFile(e.Frame, "code.js");
ReinjectCustomCSS(Config.CustomBrowserCSS);
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginBrowserScriptFile);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser, true);
}
TweetDeckBridge.ResetStaticProperties();
if (Config.EnableBrowserGCReload){
memoryUsageTracker.Start(this, e.Browser, Config.BrowserMemoryThreshold);
}
}
}
private void browser_LoadError(object sender, LoadErrorEventArgs e){
if (e.ErrorCode == CefErrorCode.Aborted){
return;
}
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
string errorPage = ScriptLoader.LoadResource("pages/error.html", true);
if (errorPage != null){
browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error");
}
}
}
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){
updates.CleanupDownload();
}
}
private void Config_MuteToggled(object sender, EventArgs e){
UpdateProperties(PropertyBridge.Environment.Browser);
}
private void Config_ZoomLevelChanged(object sender, EventArgs e){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
}
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
UpdateTrayIcon();
}
private void trayIcon_ClickRestore(object sender, EventArgs e){
Show();
RestoreWindow();
Activate();
UpdateTrayIcon();
}
private void trayIcon_ClickClose(object sender, EventArgs e){
ForceClose();
}
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
browser.GetBrowser().Reload();
}
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
browser.ExecuteScriptAsync("window.TDPF_setPluginState", e.Plugin, e.IsEnabled);
}
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){
this.InvokeAsyncSafe(() => {
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){
if (form is FormSettings || form is FormPlugins || form is FormAbout){
form.Close();
}
}
updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
if (update.DownloadStatus == UpdateDownloadStatus.Done){
UpdateInstallerPath = update.InstallerPath;
}
ForceClose();
});
});
}
private void updates_UpdateDismissed(object sender, UpdateDismissedEventArgs e){
this.InvokeAsyncSafe(() => {
Config.DismissedUpdate = e.VersionTag;
Config.Save();
});
}
private void soundNotification_PlaybackError(object sender, PlaybackErrorEventArgs e){
e.Ignore = true;
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound.\n"+e.Message, MessageBoxIcon.Error)){
form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
Button btnOpenSettings = form.AddButton("View Options");
btnOpenSettings.Width += 16;
btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y);
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){
OpenSettings(typeof(TabSettingsSounds));
}
}
}
protected override void WndProc(ref Message m){
if (isLoaded){
if (m.Msg == Program.WindowRestoreMessage){
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
trayIcon_ClickRestore(trayIcon, new EventArgs());
}
return;
}
else if (m.Msg == Program.SubProcessMessage){
int processId = m.WParam.ToInt32();
if (WindowsUtils.IsChildProcess(processId)){ // child process is checked in two places for safety
BrowserProcesses.Link(m.LParam.ToInt32(), processId);
}
return;
}
}
if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF);
return;
}
base.WndProc(ref m);
}
// notification helpers
public FormNotificationMain CreateNotificationForm(bool enableContextMenu){
return new FormNotificationMain(this, plugins, enableContextMenu);
}
public void PauseNotification(){
notification.PauseNotification();
}
public void ResumeNotification(){
notification.ResumeNotification();
}
// javascript calls
public void ReinjectCustomCSS(string css){
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
}
public void UpdateProperties(PropertyBridge.Environment environment){
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(environment));
}
public void ReloadToTweetDeck(){
browser.ExecuteScriptAsync($"gc&&gc();window.location.href='{TwitterUtils.TweetDeckURL}'");
}
// callback handlers
public void OpenContextMenu(){
contextMenu.Show(this, PointToClient(Cursor.Position));
}
public void OpenSettings(){
OpenSettings(null);
}
public void OpenSettings(Type startTab){
if (!TryBringToFront<FormSettings>()){
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
FormSettings form = new FormSettings(this, plugins, updates, startTab);
form.FormClosed += (sender, args) => {
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
updates.DismissUpdate(string.Empty);
updates.Check(false);
}
if (!Config.EnableTrayHighlight){
trayIcon.HasNotifications = false;
}
if (Config.EnableBrowserGCReload){
memoryUsageTracker.Start(this, browser.GetBrowser(), Config.BrowserMemoryThreshold);
}
else{
memoryUsageTracker.Stop();
}
UpdateProperties(PropertyBridge.Environment.Browser);
notification.RequiresResize = true;
form.Dispose();
};
ShowChildForm(form);
}
}
public void OpenAbout(){
if (!TryBringToFront<FormAbout>()){
ShowChildForm(new FormAbout());
}
}
public void OpenPlugins(){
if (!TryBringToFront<FormPlugins>()){
ShowChildForm(new FormPlugins(plugins));
}
}
public void OnTweetNotification(){ // may be called multiple times, once for each type of notification
if (Config.EnableTrayHighlight && !ContainsFocus){
trayIcon.HasNotifications = true;
}
}
public void PlayNotificationSound(){
if (Config.NotificationSoundPath.Length == 0){
return;
}
if (soundNotification == null){
soundNotification = new SoundNotification();
soundNotification.PlaybackError += soundNotification_PlaybackError;
}
soundNotification.Play(Config.NotificationSoundPath);
}
public void OnTweetScreenshotReady(string html, int width, int height){
if (notificationScreenshotManager == null){
notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
}
notificationScreenshotManager.Trigger(html, width, height);
}
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);
}
}
public void TriggerTweetScreenshot(){
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
}
}
}

View File

@@ -1,144 +0,0 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{
protected static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality;
private static string GetLink(IContextMenuParams parameters){
return string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink;
}
private static string GetImage(IContextMenuParams parameters){
return string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage) ? parameters.SourceUrl : TweetDeckBridge.LastRightClickedImage;
}
private const int MenuOpenLinkUrl = 26500;
private const int MenuCopyLinkUrl = 26501;
private const int MenuCopyUsername = 26502;
private const int MenuOpenImageUrl = 26503;
private const int MenuCopyImageUrl = 26504;
private const int MenuSaveImage = 26505;
private const int MenuSaveAllImages = 26506;
private const int MenuOpenDevTools = 26599;
private readonly Form form;
private string[] lastHighlightedTweetImageList;
protected ContextMenuBase(Form form){
this.form = form;
}
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
bool hasTweetImage = !string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage);
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
lastHighlightedTweetImageList = StringUtils.EmptyArray;
}
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal) && !hasTweetImage){
if (TwitterUtils.RegexAccount.IsMatch(parameters.UnfilteredLinkUrl)){
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open account in browser");
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy account address");
model.AddItem((CefMenuCommand)MenuCopyUsername, "Copy account username");
}
else{
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser");
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy link address");
}
model.AddSeparator();
}
if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
model.AddItem((CefMenuCommand)MenuOpenImageUrl, "Open image in browser");
model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address");
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
if (lastHighlightedTweetImageList.Length > 1){
model.AddItem((CefMenuCommand)MenuSaveAllImages, "Save all images as...");
}
model.AddSeparator();
}
}
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
switch((int)commandId){
case MenuOpenLinkUrl:
BrowserUtils.OpenExternalBrowser(parameters.LinkUrl);
break;
case MenuCopyLinkUrl:
SetClipboardText(GetLink(parameters));
break;
case MenuOpenImageUrl:
BrowserUtils.OpenExternalBrowser(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
break;
case MenuSaveImage:
TwitterUtils.DownloadImage(GetImage(parameters), ImageQuality);
break;
case MenuSaveAllImages:
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, ImageQuality);
break;
case MenuCopyImageUrl:
SetClipboardText(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
break;
case MenuCopyUsername:
Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl);
SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
break;
case MenuOpenDevTools:
browserControl.ShowDevTools();
break;
}
return false;
}
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
TweetDeckBridge.LastRightClickedLink = string.Empty;
TweetDeckBridge.LastRightClickedImage = string.Empty;
}
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
return false;
}
protected void SetClipboardText(string text){
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
}
protected static void AddDebugMenuItems(IMenuModel model){
model.AddItem((CefMenuCommand)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();
}
}
}
}

View File

@@ -1,166 +0,0 @@
using CefSharp;
using System.Windows.Forms;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{
class ContextMenuBrowser : ContextMenuBase{
private const int MenuGlobal = 26600;
private const int MenuMute = 26601;
private const int MenuSettings = 26602;
private const int MenuPlugins = 26003;
private const int MenuAbout = 26604;
private const int MenuOpenTweetUrl = 26610;
private const int MenuCopyTweetUrl = 26611;
private const int MenuOpenQuotedTweetUrl = 26612;
private const int MenuCopyQuotedTweetUrl = 26613;
private const int MenuScreenshotTweet = 26614;
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 string lastHighlightedTweet;
private string lastHighlightedQuotedTweet;
public ContextMenuBrowser(FormBrowser form) : base(form){
this.form = form;
}
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
model.Remove(CefMenuCommand.Back);
model.Remove(CefMenuCommand.Forward);
model.Remove(CefMenuCommand.Print);
model.Remove(CefMenuCommand.ViewSource);
RemoveSeparatorIfLast(model);
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
model.AddSeparator();
}
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
lastHighlightedTweet = string.Empty;
lastHighlightedQuotedTweet = string.Empty;
}
if (!string.IsNullOrEmpty(lastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser");
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
model.AddItem((CefMenuCommand)MenuScreenshotTweet, "Screenshot tweet to clipboard");
if (!string.IsNullOrEmpty(lastHighlightedQuotedTweet)){
model.AddSeparator();
model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
}
model.AddSeparator();
}
if ((parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
AddSeparator(model);
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu((CefMenuCommand)MenuGlobal, Program.BrandName);
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
globalMenu.AddCheckItem((CefMenuCommand)MenuMute, TitleMuteNotifications);
globalMenu.SetChecked((CefMenuCommand)MenuMute, Program.UserConfig.MuteNotifications);
globalMenu.AddSeparator();
globalMenu.AddItem((CefMenuCommand)MenuSettings, TitleSettings);
globalMenu.AddItem((CefMenuCommand)MenuPlugins, TitlePlugins);
globalMenu.AddItem((CefMenuCommand)MenuAbout, TitleAboutProgram);
if (HasDevTools){
globalMenu.AddSeparator();
AddDebugMenuItems(globalMenu);
}
}
RemoveSeparatorIfLast(model);
}
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((int)commandId){
case (int)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:
BrowserUtils.OpenExternalBrowser(lastHighlightedTweet);
return true;
case MenuCopyTweetUrl:
SetClipboardText(lastHighlightedTweet);
return true;
case MenuScreenshotTweet:
form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
return true;
case MenuOpenQuotedTweetUrl:
BrowserUtils.OpenExternalBrowser(lastHighlightedQuotedTweet);
return true;
case MenuCopyQuotedTweetUrl:
SetClipboardText(lastHighlightedQuotedTweet);
return true;
}
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 = Program.UserConfig.MuteNotifications;
};
return menu;
}
private static void ToggleMuteNotifications(){
Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications;
Program.UserConfig.Save();
}
}
}

View File

@@ -1,15 +0,0 @@
using System;
using CefSharp;
namespace TweetDuck.Core.Handling.General{
class BrowserProcessHandler : IBrowserProcessHandler{
void IBrowserProcessHandler.OnContextInitialized(){
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
ctx.SetPreference("browser.enable_spellchecking", Program.UserConfig.EnableSpellCheck, out string _);
}
}
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay){}
void IDisposable.Dispose(){}
}
}

View File

@@ -1,68 +0,0 @@
using System.Security.Cryptography.X509Certificates;
using CefSharp;
namespace TweetDuck.Core.Handling.General{
abstract class RequestHandlerBase : IRequestHandler{
// Browser
public virtual void OnRenderViewReady(IWebBrowser browserControl, IBrowser browser){}
public virtual void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){}
public virtual bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool isRedirect){
return false;
}
public virtual bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){
return false;
}
public virtual bool OnProtocolExecution(IWebBrowser browserControl, IBrowser browser, string url){
return false;
}
// Resources
public virtual CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
return CefReturnValue.Continue;
}
public virtual void OnResourceRedirect(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl){}
public virtual bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
return false;
}
public virtual IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
return null;
}
public virtual void OnResourceLoadComplete(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength){}
// JavaScript & Plugins
public virtual bool OnQuotaRequest(IWebBrowser browserControl, IBrowser browser, string originUrl, long newSize, IRequestCallback callback){
callback.Dispose();
return false;
}
public virtual void OnPluginCrashed(IWebBrowser browserControl, IBrowser browser, string pluginPath){}
// Auth
public virtual bool GetAuthCredentials(IWebBrowser browserControl, IBrowser browser, IFrame frame, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback){
callback.Dispose();
return false;
}
public virtual bool OnSelectClientCertificate(IWebBrowser browserControl, IBrowser browser, bool isProxy, string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback){
callback.Dispose();
return false;
}
public virtual bool OnCertificateError(IWebBrowser browserControl, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback){
callback.Dispose();
return false;
}
}
}

View File

@@ -1,38 +0,0 @@
using CefSharp;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
namespace TweetDuck.Core.Handling {
sealed class KeyboardHandlerNotification : IKeyboardHandler{
private readonly FormNotificationBase notification;
public KeyboardHandlerNotification(FormNotificationBase notification){
this.notification = notification;
}
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://")){
switch((Keys)windowsKeyCode){
case Keys.Enter:
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
return true;
case Keys.Escape:
notification.InvokeAsyncSafe(() => notification.HideNotification(true));
return true;
case Keys.Space:
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
return true;
}
}
return false;
}
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
return false;
}
}
}

View File

@@ -1,18 +0,0 @@
using CefSharp;
using TweetDuck.Core.Handling.General;
namespace TweetDuck.Core.Handling{
class RequestHandlerBrowser : RequestHandlerBase{
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
browser.Reload();
}
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
if (request.ResourceType == ResourceType.Script && request.Url.Contains("analytics.")){
return CefReturnValue.Cancel;
}
return CefReturnValue.Continue;
}
}
}

View File

@@ -1,63 +0,0 @@
using CefSharp;
using System.Collections.Specialized;
using System.IO;
using System.Text;
namespace TweetDuck.Core.Handling{
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(){}
}
}

View File

@@ -1,232 +0,0 @@
using CefSharp;
using CefSharp.WinForms;
using System;
using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Notification{
partial class FormNotificationBase : Form{
protected Point PrimaryLocation{
get{
UserConfig config = Program.UserConfig;
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 TweetNotification.Position.TopLeft:
return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+edgeDist);
case TweetNotification.Position.TopRight:
return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
case TweetNotification.Position.BottomLeft:
return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
case TweetNotification.Position.BottomRight:
return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
case TweetNotification.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;
}
}
public bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
public new Point Location{
get => base.Location;
set{
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
FormBorderStyle = GetBorderStyle(CanResizeWindow);
}
}
public bool CanResizeWindow{
get => FormBorderStyle == FormBorderStyle.Sizable || FormBorderStyle == FormBorderStyle.SizableToolWindow;
set => FormBorderStyle = GetBorderStyle(value);
}
public Func<bool> CanMoveWindow { get; set; } = () => true;
protected override bool ShowWithoutActivation => true;
protected double SizeScale => dpiScale*Program.UserConfig.ZoomMultiplier;
protected readonly Form owner;
protected readonly ChromiumWebBrowser browser;
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
private readonly float dpiScale;
private string currentColumn;
private int pauseCounter;
public bool IsPaused => pauseCounter > 0;
public bool FreezeTimer { get; set; }
public bool ContextMenuOpen { get; set; }
public string CurrentTweetUrl { get; private set; }
public string CurrentQuoteUrl { get; private set; }
public event EventHandler Initialized;
public FormNotificationBase(Form owner, bool enableContextMenu){
InitializeComponent();
this.owner = owner;
this.owner.FormClosed += owner_FormClosed;
this.browser = new ChromiumWebBrowser("about:blank"){
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler()
};
this.browser.Dock = DockStyle.None;
this.browser.ClientSize = ClientSize;
this.browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
#if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
this.dpiScale = this.GetDPIScale();
DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
handlerFactory.RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
Controls.Add(browser);
Disposed += (sender, args) => {
this.browser.Dispose();
this.owner.FormClosed -= owner_FormClosed;
};
// ReSharper disable once VirtualMemberCallInContructor
UpdateTitle();
}
protected override void WndProc(ref Message m){
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanMoveWindow()){ // WM_SYSCOMMAND, SC_MOVE
return;
}
base.WndProc(ref m);
}
// event handlers
private void owner_FormClosed(object sender, FormClosedEventArgs e){
Close();
}
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
if (e.IsBrowserInitialized){
Initialized?.Invoke(this, new EventArgs());
int identifier = browser.GetBrowser().Identifier;
Disposed += (sender2, args2) => BrowserProcesses.Forget(identifier);
}
}
// notification methods
public virtual void HideNotification(bool loadBlank){
if (loadBlank){
browser.Load("about:blank");
}
Location = ControlExtensions.InvisibleLocation;
currentColumn = 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 string GetTweetHTML(TweetNotification tweet){
string bodyClasses = browser.Bounds.Contains(PointToClient(Cursor.Position)) ? "td-hover" : string.Empty;
return tweet.GenerateHtml(bodyClasses);
}
protected virtual void LoadTweet(TweetNotification tweet){
CurrentTweetUrl = tweet.TweetUrl;
CurrentQuoteUrl = tweet.QuoteUrl;
currentColumn = tweet.Column;
resourceHandler.SetHTML(GetTweetHTML(tweet));
browser.Load(TwitterUtils.TweetDeckURL);
}
protected virtual void SetNotificationSize(int width, int height){
browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale));
}
protected virtual void OnNotificationReady(){
MoveToVisibleLocation();
}
protected virtual void UpdateTitle(){
Text = string.IsNullOrEmpty(currentColumn) || !Program.UserConfig.DisplayNotificationColumn ? Program.BrandName : Program.BrandName+" - "+currentColumn;
}
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);
}
}
private FormBorderStyle GetBorderStyle(bool sizable){
if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab
return sizable ? FormBorderStyle.Sizable : FormBorderStyle.FixedSingle;
}
else{
return sizable ? FormBorderStyle.SizableToolWindow : FormBorderStyle.FixedToolWindow;
}
}
}
}

View File

@@ -1,65 +0,0 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins;
using TweetDuck.Resources;
namespace TweetDuck.Core.Notification.Screenshot{
sealed class FormNotificationScreenshotable : FormNotificationBase{
private readonly PluginManager plugins;
public FormNotificationScreenshotable(Action callback, Form owner, PluginManager pluginManager) : base(owner, false){
this.plugins = pluginManager;
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
browser.LoadingStateChanged += (sender, args) => {
if (!args.IsLoading){
using(IFrame frame = args.Browser.MainFrame){
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 129)", "gen:screenshot");
}
}
};
}
protected override string GetTweetHTML(TweetNotification tweet){
string html = tweet.GenerateHtml("td-screenshot", false);
foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){
html = injection.Inject(html);
}
return html;
}
public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){
LoadTweet(tweet);
SetNotificationSize(width, height);
}
public void TakeScreenshot(){
IntPtr context = NativeMethods.GetDC(this.Handle);
if (context == IntPtr.Zero){
FormMessage.Error("Screenshot Failed", "Could not retrieve a graphics context handle for the notification window to take the screenshot.", FormMessage.OK);
}
else{
using(Bitmap bmp = new Bitmap(ClientSize.Width, ClientSize.Height, PixelFormat.Format32bppRgb)){
try{
NativeMethods.RenderSourceIntoBitmap(context, bmp);
}finally{
NativeMethods.ReleaseDC(this.Handle, context);
}
Clipboard.SetImage(bmp);
}
}
}
}
}

View File

@@ -1,86 +0,0 @@
// Uncomment to keep screenshot windows visible for debugging
// #define NO_HIDE_SCREENSHOTS
using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Plugins;
namespace TweetDuck.Core.Notification.Screenshot{
sealed class TweetScreenshotManager : IDisposable{
private readonly FormBrowser owner;
private readonly PluginManager plugins;
private readonly Timer timeout;
private readonly Timer disposer;
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;
}
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, int height){
if (screenshot != null){
return;
}
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins){
CanMoveWindow = () => false
};
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height);
screenshot.Show();
timeout.Start();
#if !(DEBUG && NO_HIDE_SCREENSHOTS)
owner.IsWaiting = true;
#endif
}
private void Callback(){
if (!timeout.Enabled){
return;
}
timeout.Stop();
screenshot.TakeScreenshot();
#if !(DEBUG && NO_HIDE_SCREENSHOTS)
OnFinished();
#else
screenshot.MoveToVisibleLocation();
screenshot.FormClosed += (sender, args) => disposer.Start();
#endif
}
private void OnFinished(){
screenshot.Location = ControlExtensions.InvisibleLocation;
owner.IsWaiting = false;
disposer.Start();
}
public void Dispose(){
timeout.Dispose();
disposer.Dispose();
screenshot?.Dispose();
}
}
}

View File

@@ -1,28 +0,0 @@
using System;
using TweetLib.Audio;
namespace TweetDuck.Core.Notification{
sealed class SoundNotification : IDisposable{
public string SupportedFormats => player.SupportedFormats;
public event EventHandler<PlaybackErrorEventArgs> PlaybackError;
private readonly AudioPlayer player;
public SoundNotification(){
this.player = AudioPlayer.New();
this.player.PlaybackError += Player_PlaybackError;
}
public void Play(string file){
player.Play(file);
}
private void Player_PlaybackError(object sender, PlaybackErrorEventArgs e){
PlaybackError?.Invoke(this, e);
}
public void Dispose(){
player.Dispose();
}
}
}

View File

@@ -1,110 +0,0 @@
using System;
using System.Text;
using TweetDuck.Resources;
namespace TweetDuck.Core.Notification{
sealed class TweetNotification{
private static string FontSizeClass { get; set; }
private static string HeadTag { get; set; }
private const string DefaultFontSizeClass = "medium";
private const string DefaultHeadTag = @"<meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
private const string CustomCSS = @"body:before{content:none}body{overflow-y:auto}.scroll-styled-v::-webkit-scrollbar{width:7px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}.scroll-styled-v::-webkit-scrollbar-track{border-left:0}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}.media-size-medium{height:calc(100vh - 16px)!important;max-height:240px;border-radius:1px!important}.js-quote-detail .media-size-medium{height:calc(100vh - 28px)!important;}.js-media.margin-vm, .js-media-preview-container.margin-vm{margin-bottom:0!important}";
public static int FontSizeLevel{
get{
switch(FontSizeClass){
case "largest": return 4;
case "large": return 3;
case "medium": return 2;
case "small": return 1;
default: return 0;
}
}
}
private static string ExampleTweetHTML;
public static TweetNotification ExampleTweet{
get{
if (ExampleTweetHTML == null){
ExampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true);
#if DEBUG
ExampleTweetHTML = ExampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
#endif
}
return new TweetNotification("Home", ExampleTweetHTML, 95, string.Empty, string.Empty, true);
}
}
public static void SetFontSizeClass(string newFSClass){
FontSizeClass = newFSClass;
}
public static void SetHeadTag(string headContents){
HeadTag = headContents;
}
public enum Position{
TopLeft, TopRight, BottomLeft, BottomRight, Custom
}
public enum Size{
Auto, Custom
}
public string Column { get; }
public string TweetUrl { get; }
public string QuoteUrl { get; }
private readonly string html;
private readonly int characters;
private readonly bool isExample;
public TweetNotification(string column, string html, int characters, string tweetUrl, string quoteUrl) : this(column, html, characters, tweetUrl, quoteUrl, false){}
private TweetNotification(string column, string html, int characters, string tweetUrl, string quoteUrl, bool isExample){
this.Column = column;
this.TweetUrl = tweetUrl;
this.QuoteUrl = quoteUrl;
this.html = html;
this.characters = characters;
this.isExample = isExample;
}
public int GetDisplayDuration(int value){
return 2000+Math.Max(1000, value*characters);
}
public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){
StringBuilder build = new StringBuilder();
build.Append("<!DOCTYPE html>");
build.Append("<html class='os-windows txt-base-").Append(FontSizeClass ?? DefaultFontSizeClass).Append("'>");
build.Append("<head>").Append(HeadTag ?? DefaultHeadTag);
if (enableCustomCSS){
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){
build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>");
}
}
build.Append("</head>");
build.Append("<body class='scroll-styled-v");
if (!string.IsNullOrEmpty(bodyClasses)){
build.Append(' ').Append(bodyClasses);
}
build.Append('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='column' style='width:100%;height:auto;overflow:initial;'>");
build.Append(html);
build.Append("</div></body>");
build.Append("</html>");
return build.ToString();
}
}
}

View File

@@ -1,25 +0,0 @@
using System.Windows.Forms;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other{
sealed partial class FormAbout : Form{
private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki";
private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues";
public FormAbout(){
InitializeComponent();
Text = "About "+Program.BrandName+" "+Program.VersionTag;
labelDescription.Text = "TweetDuck was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
}
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
BrowserUtils.OpenExternalBrowserUnsafe(e.Link.LinkData as string);
}
}
}

View File

@@ -1,246 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="pictureLogo.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAABGdBTUEAALGOfPtRkwAAACBjSFJNAACH
DwAAjA8AAP1SAACBQAAAfXkAAOmLAAA85QAAGcxzPIV3AAAKOWlDQ1BQaG90b3Nob3AgSUNDIHByb2Zp
bGUAAEjHnZZ3VFTXFofPvXd6oc0wAlKG3rvAANJ7k15FYZgZYCgDDjM0sSGiAhFFRJoiSFDEgNFQJFZE
sRAUVLAHJAgoMRhFVCxvRtaLrqy89/Ly++Osb+2z97n77L3PWhcAkqcvl5cGSwGQyhPwgzyc6RGRUXTs
AIABHmCAKQBMVka6X7B7CBDJy82FniFyAl8EAfB6WLwCcNPQM4BOB/+fpFnpfIHomAARm7M5GSwRF4g4
JUuQLrbPipgalyxmGCVmvihBEcuJOWGRDT77LLKjmNmpPLaIxTmns1PZYu4V8bZMIUfEiK+ICzO5nCwR
3xKxRoowlSviN+LYVA4zAwAUSWwXcFiJIjYRMYkfEuQi4uUA4EgJX3HcVyzgZAvEl3JJS8/hcxMSBXQd
li7d1NqaQffkZKVwBALDACYrmcln013SUtOZvBwAFu/8WTLi2tJFRbY0tba0NDQzMv2qUP91829K3NtF
ehn4uWcQrf+L7a/80hoAYMyJarPziy2uCoDOLQDI3fti0zgAgKSobx3Xv7oPTTwviQJBuo2xcVZWlhGX
wzISF/QP/U+Hv6GvvmckPu6P8tBdOfFMYYqALq4bKy0lTcinZ6QzWRy64Z+H+B8H/nUeBkGceA6fwxNF
hImmjMtLELWbx+YKuGk8Opf3n5r4D8P+pMW5FonS+BFQY4yA1HUqQH7tBygKESDR+8Vd/6NvvvgwIH55
4SqTi3P/7zf9Z8Gl4iWDm/A5ziUohM4S8jMX98TPEqABAUgCKpAHykAd6ABDYAasgC1wBG7AG/iDEBAJ
VgMWSASpgA+yQB7YBApBMdgJ9oBqUAcaQTNoBcdBJzgFzoNL4Bq4AW6D+2AUTIBnYBa8BgsQBGEhMkSB
5CEVSBPSh8wgBmQPuUG+UBAUCcVCCRAPEkJ50GaoGCqDqqF6qBn6HjoJnYeuQIPQXWgMmoZ+h97BCEyC
qbASrAUbwwzYCfaBQ+BVcAK8Bs6FC+AdcCXcAB+FO+Dz8DX4NjwKP4PnEIAQERqiihgiDMQF8UeikHiE
j6xHipAKpAFpRbqRPuQmMorMIG9RGBQFRUcZomxRnqhQFAu1BrUeVYKqRh1GdaB6UTdRY6hZ1Ec0Ga2I
1kfboL3QEegEdBa6EF2BbkK3oy+ib6Mn0K8xGAwNo42xwnhiIjFJmLWYEsw+TBvmHGYQM46Zw2Kx8lh9
rB3WH8vECrCF2CrsUexZ7BB2AvsGR8Sp4Mxw7rgoHA+Xj6vAHcGdwQ3hJnELeCm8Jt4G749n43PwpfhG
fDf+On4Cv0CQJmgT7AghhCTCJkIloZVwkfCA8JJIJKoRrYmBRC5xI7GSeIx4mThGfEuSIemRXEjRJCFp
B+kQ6RzpLuklmUzWIjuSo8gC8g5yM/kC+RH5jQRFwkjCS4ItsUGiRqJDYkjiuSReUlPSSXK1ZK5kheQJ
yeuSM1J4KS0pFymm1HqpGqmTUiNSc9IUaVNpf+lU6RLpI9JXpKdksDJaMm4ybJkCmYMyF2TGKQhFneJC
YVE2UxopFykTVAxVm+pFTaIWU7+jDlBnZWVkl8mGyWbL1sielh2lITQtmhcthVZKO04bpr1borTEaQln
yfYlrUuGlszLLZVzlOPIFcm1yd2WeydPl3eTT5bfJd8p/1ABpaCnEKiQpbBf4aLCzFLqUtulrKVFS48v
vacIK+opBimuVTyo2K84p6Ss5KGUrlSldEFpRpmm7KicpFyufEZ5WoWiYq/CVSlXOavylC5Ld6Kn0Cvp
vfRZVUVVT1Whar3qgOqCmrZaqFq+WpvaQ3WCOkM9Xr1cvUd9VkNFw08jT6NF454mXpOhmai5V7NPc15L
Wytca6tWp9aUtpy2l3audov2Ax2yjoPOGp0GnVu6GF2GbrLuPt0berCehV6iXo3edX1Y31Kfq79Pf9AA
bWBtwDNoMBgxJBk6GWYathiOGdGMfI3yjTqNnhtrGEcZ7zLuM/5oYmGSYtJoct9UxtTbNN+02/R3Mz0z
llmN2S1zsrm7+QbzLvMXy/SXcZbtX3bHgmLhZ7HVosfig6WVJd+y1XLaSsMq1qrWaoRBZQQwShiXrdHW
ztYbrE9Zv7WxtBHYHLf5zdbQNtn2iO3Ucu3lnOWNy8ft1OyYdvV2o/Z0+1j7A/ajDqoOTIcGh8eO6o5s
xybHSSddpySno07PnU2c+c7tzvMuNi7rXM65Iq4erkWuA24ybqFu1W6P3NXcE9xb3Gc9LDzWepzzRHv6
eO7yHPFS8mJ5NXvNelt5r/Pu9SH5BPtU+zz21fPl+3b7wX7efrv9HqzQXMFb0ekP/L38d/s/DNAOWBPw
YyAmMCCwJvBJkGlQXlBfMCU4JvhI8OsQ55DSkPuhOqHC0J4wybDosOaw+XDX8LLw0QjjiHUR1yIVIrmR
XVHYqLCopqi5lW4r96yciLaILoweXqW9KnvVldUKq1NWn46RjGHGnIhFx4bHHol9z/RnNjDn4rziauNm
WS6svaxnbEd2OXuaY8cp40zG28WXxU8l2CXsTphOdEisSJzhunCruS+SPJPqkuaT/ZMPJX9KCU9pS8Wl
xqae5Mnwknm9acpp2WmD6frphemja2zW7Fkzy/fhN2VAGasyugRU0c9Uv1BHuEU4lmmfWZP5Jiss60S2
dDYvuz9HL2d7zmSue+63a1FrWWt78lTzNuWNrXNaV78eWh+3vmeD+oaCDRMbPTYe3kTYlLzpp3yT/LL8
V5vDN3cXKBVsLBjf4rGlpVCikF84stV2a9021DbutoHt5turtn8sYhddLTYprih+X8IqufqN6TeV33za
Eb9joNSydP9OzE7ezuFdDrsOl0mX5ZaN7/bb3VFOLy8qf7UnZs+VimUVdXsJe4V7Ryt9K7uqNKp2Vr2v
Tqy+XeNc01arWLu9dn4fe9/Qfsf9rXVKdcV17w5wD9yp96jvaNBqqDiIOZh58EljWGPft4xvm5sUmoqb
PhziHRo9HHS4t9mqufmI4pHSFrhF2DJ9NProje9cv+tqNWytb6O1FR8Dx4THnn4f+/3wcZ/jPScYJ1p/
0Pyhtp3SXtQBdeR0zHYmdo52RXYNnvQ+2dNt293+o9GPh06pnqo5LXu69AzhTMGZT2dzz86dSz83cz7h
/HhPTM/9CxEXbvUG9g5c9Ll4+ZL7pQt9Tn1nL9tdPnXF5srJq4yrndcsr3X0W/S3/2TxU/uA5UDHdavr
XTesb3QPLh88M+QwdP6m681Lt7xuXbu94vbgcOjwnZHokdE77DtTd1PuvriXeW/h/sYH6AdFD6UeVjxS
fNTws+7PbaOWo6fHXMf6Hwc/vj/OGn/2S8Yv7ycKnpCfVEyqTDZPmU2dmnafvvF05dOJZ+nPFmYKf5X+
tfa5zvMffnP8rX82YnbiBf/Fp99LXsq/PPRq2aueuYC5R69TXy/MF72Rf3P4LeNt37vwd5MLWe+x7ys/
6H7o/ujz8cGn1E+f/gUDmPP8usTo0wAAAAlwSFlzAAAuIgAALiIBquLdkgAAEVpJREFUeF7tXQl0VNd5
Thq3Tk7tc+qmiZMm7YmdJj1OT3Gd0kTMG3mJnZQEJzaSjXETx0lqNwt2jWscQGixBAJLSMhgA8YYbIyQ
hHYJI4lFGMwioQWxyewgMdoXtKFd4u/337lPjDRv9pk3g+A75zsMWv577/fd+9/l3Rl9johu0Y/U/OIt
6sfrLwIYT2de+SJ4F/j34LfB+8Ap4L9J8uvvgfy9b4DfBO8EPy9DBBwC3gCI93fgDHAuuA7cDVaDzWAX
OAAOgcNgP9gN8vc+A4vB9eBr4M/Be8GAMiPgDIBAXwVDwbXgEbAPJC+RDToGvgfOBr8mi/UbAsIACMFp
ZRaYC7aBWuL5gh3gdvBZ8C5ZHV3hVwPQaM7hS0ETqCWQnqwDE8Apsnq6wC8GoJEPgFtAzt9aYviTPJ9k
gkGyuj6FrgagUd8CeVIcBLUaH0gcATeD35XV9wl0MQCN4OXjn8EroFZjA5m8qooE/1o2x6vwuQGouAEs
A7UadyPxOPgj2SyvwacGoMILwRsh3ThLTksx4BdkEz2GTwxABb8C8pJSqxGTgTvAb8jmegSvG4CK/QvI
O1Wtik8mXgSnyma7Da8agAo9Auq5kfI3+Shkhmy+W/CaAajI42CvrNjNRJ7jZksZXIZXDEAFWPxA3FTp
xVHQLRM8NgAFPwTejD1/InkH/biUxWl4ZAAK5LP3mynnO+JV0KWJ2W0DUNCXwVOy4Fu8zhrQ6SWqJwbk
WxQa2Mxop6fSWyk0rYlCUhspZEs9zdxSJ8iv+WuhW5vpqYw27d93nXvB26RUduGWAQg+36KwwCULv7VF
iDx3Wx1tPNxCBy92UU17P7VdHaLWniE619JLu8900Nv7m+jFLBgCM9gs/l3NmCqlqU+nt+D/msYtk3LZ
hcsGIPB/gPaPF7jyKrW+rwNZnJCUBoraUU9H63po9No1NM8++odGac/ZDno5r45C2QitESGF59gv5dVT
clUHzc5gEyb8XOaVa6DDsyMU67wBCHg7WCULsEkezi9/jEptbfKLCdzrn0s30b5znWiS6xgeuUaby1so
NKVubDSwGZzCOG3NzTdRQXW7+NnM4+b2atUDPAPeIeXTBEK4ZMA8i+Ca5Ir+LrOOOvtHKGkfcit6kp4m
sBh/zDGRqaMfzfEMh5CuZiXXUOgWE72Ua6L1Jc10qqmXRkbNo4nT2PMw2sHcESvl0wTCOGcAAv0DyFtv
rULGyAKsO9SMUERD6EnRO+t1M4F76++zTNTUNSjK9wYutvVhzujTTGGrMG/wqNCqiwX5IsA/SxmtgDBO
G7DBIqhN8oR3suEqQpnRh7watcOcU31pAvfCWUgZ1Y3Xy/YlCk+Z28rlqrTTvkwpoxUQyrEBCPCvoONz
fVTg1xkm6hkYQajr4Mktdpd5uecrE9jg5IpWWaJvwRN1SHKtaA/PCfMLmyiq2DxPaNUN5KMKg5RzHBDO
KQNSLYLZJFfgVSz3tMBDeM0BrMNlr9H6fXfJ8V7AErK7f1iW5hsMjYxSSnkjPbnxLP1vbq2YE04399KV
vmGak9/kqF0FUs5xQFj7BuAXvwM6ddDGOfiNnQ0IYxvbTrYjVWDiwkrFW6OB83DaEd/3/obOfiq71Ekt
3YOkzgg8NUTxPOdgLpiV1TE6K7vzASnrGBDCoQF8b0cz6ESyqDG77BvAOI2VxJycy7bX2q4QJoYgDVxo
7ZPR9QVv4OzNb7OyOll8mpnWTNPXnlwnZR0DQtg2AAHuAPnCkmbwieQREFZQjzCOwfPChlJs/3mt7cFo
YAPn5NTRsFwa6on3kII05zUIjt4uXv9icx099u5pCn7rKBkSynoMC1K/LuUVQBi7BswcF9gBWYzfZ5vE
RsZZnG/powjsVsXc4IYR/DtLix2POm+C9wGrPjWfK1nWl3v6MxA+FB1xxgeX6OF3TpKSdASsIiMMMK48
TkpM4RwprwDC2TUgTw3uFFEZXgo2dA4glPNgu8pruyms0HxAJnaWThrBP7v2oHnfoQd48xWBelqmHe7t
Is2kNNJ/rj9HD648RsoKC+FVwgBj/MGDUl4BhNQ2AIH/FuTLq1aNtkfuFYWfXUEo18FGHKvrofg9DfRs
mknEcjQq2ABejeiFyEKTWXyUbU4z7fSLj0z06NpTELnKLLyl6BOZVDVgDM+8R8ps14AQy4Y6S1fmAXvg
npZ/oo0iiuro2VSYkdIgVhocX0zc0hQ2YDWWt3phIdrGp5+hW1vpZxsv0SOcZrR6uy1iFAQvLvqDlNmu
Ae9aCus0IQzn8/LL3QjnHbAZpZe6RE9fsL2O/jvTJFIdlzMz2URRhZflT/oer+fX0E/ePUXBttKMI3Ia
enNfhpRZ2wAI+XnQ4amnLXLamPdxHQ1i4+IL8AqqHvMMp6vdZ65Q8en2sXW5r/HbjZVYzVRoi+sMYZwx
saxWWbj1Swhn04B/BPkQSVNgu+TUAHKefB/LzMkE3s3PXgfxsbLRFNd5XlMWZT6AkDYN+JmVsE5QLEPz
WujXGY0iZ3N60GOHqheuDgzT4++U80SqJarz5DQUXfAcQlobACE5/fAb4jRFtkdOPatLWqmrf4RSKlvo
1fzL9MSGM7TuQJ04R7nR0dDRSw8mlGqL6gpXniDjkp2xyoKULyCslQH81MutCZgNWLz7+qaI83Jz9yAm
0A5q6/HeGb2/cPxyBxnivWEAT8T7c4Mjc+5EWCsD/gbkt4JqimyP6rGA+sRosqHgWD0Zlh/WFtUVsgHL
S44aI3O/hrBWBvCboN273cwTMJaHvEKZjEgsOkNKogcrIJViJVTeaIzefi/CWhnA7zJvGiesC+TVT85x
8wPryYYXNpl3upqiukQYsKKyR1my63sIa2UAv3WU3xelKbAj8k71lXz/nE76Em09A/Tjt5B+PF0BqUw6
MogN2RSEtjKAHz+6twdg8k6Yz4NOuXceFKgorm7yTv5XmXRkVIk78H2EtjLgfpBv+moL7AR5Mv5NRp1X
byf4GzH5n3kn/6vkkbS8dCpCWxnAnzrikQFMPiRbWFAnbkXc6OAN2BNreAPmjfwvyQbEH9I0gD/yxfM3
W/BxRFqTuBrYOzj+lsSNht0nG72bfphJVddgwL8jvJUBHk3C4yhNeCXfRJfaPL+p5i+8ln4Cq59KbSHd
ZVLloLJs7/0Ib2UAL0NbxgnpCWEC75Bnp5rog7IW6ujz7dURb+NsYxdvmswpQ0tItyiWod1KTJHmMpQ3
YvxhR9qCukiekGenN4tHjU9+VENPbzpLcbtq6GS9954X+BLLtp/y7uTLNG/EmozRBd9GEVYG8Gf37LEU
0RNy7/+o8gqdae6jQxc76eCFTqqo7aTGzsBPSRdbeuihRG/3fpANiC85qkTkaB5F8Adr8CeaaArqKsUj
yiJ9by14C2/keXnpqXIV5pTYPblB8zZ9BcVYGcDH0f9nKaJH5I1ZSgMduNCF8DcOjtS0k+L13C/JBize
EWeYn3wbihpvgHwgw+/71RbUDfIo+FNuvdWl3UAFn+a+KM59vLzyUWl+IPMbFHVd97EXZgPuAb3yKSfq
ZSUeBbFFNSgi8PHRwRrzut8Xvd/Ma8FR+XYfSf4FyJ+PoymqM1TvRIakNtP098/TQ5h4psWXUnzBaRQT
uDhZ1yknXi/uei0JHZTlpbVBr6z7IoqzNkAlRHR9Ira4E/lEcv31O5Hq9Q2Qe9bCrJPU3T+EYgILPajT
r96v8F3qYSL/G5bszuKOjiLHeP2FBETkj5G0FlmDapp5amsrzfiwhh5ZPeFOpCXxNSWxnGauKaed2OI7
8eZF3RCejXqjbj5MPWYDInJfZo1RpF0D+EOX7L4nbOzqdWqTSDN8J3Kst2sVbkF+sMGj4fmNlZR2uFas
uYflg3t/mLJy11lf530Q6/+3qoaw+vkOa4xibRvAgMj8oaYTRLe+eu3UnUgt8mhgIxLKKBhLvsdXltDc
zRV0qUXfXfKHBy7pID4ongUfKpfyOmXAf00U3ubVa0+IOCzAM+vKqOxCG6qhHzbziodvOvhq0rUk0k9Q
WPpLUl4BVMG2AbOyu+4CW1j8Jx1dvXaHLDx6/09XHabU0loaHNb32cG7n1zQT3w+fkgs75r6Pwl3S3kF
UA3bBjB+vvH8CnH1WqYLzeCukEcNVhnc459YXSaGf0evvk/O2OjovGqZdnQQn/l2NQVF5G2Qso4B1bFv
gCEs/X6kmhHNoCp5NHBDBPm1ShYbTKwQPZ1723T09tfTT4hnrL2D+h9N17Zdpd9uNI88UUet9viCGAFB
r29WpKxjQJXsG8AwLtv3sbhOpxUYjZi+5hg9s+E4zVpfRT9ZeZh+nFRKP11ZRqFry+mFD6soKreakg/V
iPzuz/V/TqVJ1E2s83UVX9yE2294baPV3y5AtRwboIRnT0OgUavAko++fZR++cFxKj7VQgPDI9Q3OCKE
HhgKjLOfc03d9ErqcX1TjiVhADLJdCnnOKB6jg1gGOP2pwsntQpAbxLzw/JSCsuqprNocCCgoaOPEgrP
0MOJJeZjZT17vUo++Vz6yU4poxVQTScNWJT5XfSefvNmQqMgJhuBhirxJTQ/4wRVXGqnUT9c0Drf3E3L
IfyPON2Ina0fer0gVoxJVcNBr31g9QZtFaiucwYwjEt2xdqcCywpRgRWOph0f7m+QuT/y+1XfbrD5Ztr
24/V05wtR8Wmzr/CS7JWscWrpHyaQNWdN0AJS7vDmFh2zmYqmkiZmnjF8WBCibhbuWbPeTqMyZiXnp4Y
wgdnfF2cl7Gc3x9Db+ccr/sEa4u87l9R3hAcnvllKZ8m0BTnDWAo4VmPGldyIXZSkRalGdwzeWTwaonP
gRZlV9O6vRcoq8JEu7E0LTnXSidMHSKNHK29Qofwf76Xw99/Dz8XiRXV7z48QjPeLkOqg+gwN2BEtyRv
WCNyQqRsNuGyAQxlya44p1KRPaqGQDxhCnrvGFlYlRZf558TcwxP+IEmuCXFI8eda6RcduGWAcaFaX9p
jD/0qdOp6GYia7K8pCp4UfqXpFx24ZYBDGVB6jeNKypMt0ywoPm8p9UYkW3zI8omwm0DGIbXN//AuKKy
VxSsVaGbiWYNhrBpdenPnHhkAAMTzUwUPnxTm6C2PWrbc1IWp+GxAQxDePavxlXkpiLazO2OKfqjlMMl
eMUABruPCt1kI4HbiuVm9PY/SRlchtcMYBjCMkJQMcwJN8HEzB0tqWrIGF3wvGy+W/CqAQwlLH2aknDY
xGthzYpPBnIHW1HRYozMeUw222143QDGtD8n3yP2CcKESZaSeAOaUFpuDM+8TzbXI/jEAIZxYepfGaIL
48yVngQpiVMO2qHEFq81Lkr32p819JkBKgyLMmeYD/Bu4NHAdU8sr1Ui85+SzfIafG4Aw7go4y5jbPEy
8TzhRpobxMitGjYu3fOOMWzrV2VzvApdDFDxw3mbpyhL92aL09RATkuibhitb35agIn2h7L6PoGuBqgw
hOc8Zow7UCQayyMiIPYOnONlmow7sEeJzPPoL+Q5C78YoAI76KBpMTtXK4nlLcZV6HVqz9MUyEfkMpkr
KnuMSz/ZpETkPCyrpwv8aoCKH8zdcPe0yG0vYum6X8wTLIivRoZczQgmVQ2izBIluvBVZeHWb8nq6IqA
MMASWDX9k+GN7X8wxO5JU5aXnjeLxoKxIVI4YYzKCQKrX7cUWpiJf/n7CWW1yrJ9WcaYwpeU8Cynj419
hYAzwBJB8zbdHhyVd78xpuA5ZfHOWEzgWeixFVgSNiBldGG0DICj4uE7PyEzvx4Q6YQ/ECm+5Cgm0jxl
8Y5lyhsfPw9zpyrzU3zyp8ndRUAbwFDrZViQdlvQ/NQ7kKPvNkYX3GuIKbpPWfrJFP7IFyXu4FQYM1V5
c//3lWV7p/A70JWobfcq4dlfN4Zn3WmYn3y7GseSgYCxulhW7Bb1p+YXb1Ev0uf+H9A3E1Z4VJUaAAAA
AElFTkSuQmCC
</value>
</data>
</root>

View File

@@ -1,161 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other.Settings;
using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetDuck.Updates;
namespace TweetDuck.Core.Other{
sealed partial class FormSettings : Form{
private readonly FormBrowser browser;
private readonly PluginManager plugins;
private readonly int buttonHeight;
private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(4);
private SettingsTab currentTab;
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, Type startTab){
InitializeComponent();
Text = Program.BrandName+" Options";
this.browser = browser;
this.browser.PauseNotification();
this.plugins = plugins;
this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
AddButton("General", () => new TabSettingsGeneral(updates));
AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false)));
AddButton("Sounds", () => new TabSettingsSounds());
AddButton("Advanced", () => new TabSettingsAdvanced(browser.ReinjectCustomCSS));
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
}
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
foreach(SettingsTab tab in tabs.Values){
if (tab.IsInitialized){
tab.Control.OnClosing();
tab.Control.Dispose();
}
}
Program.UserConfig.Save();
browser.ResumeNotification();
}
private void btnManageOptions_Click(object sender, EventArgs e){
using(DialogSettingsManage dialog = new DialogSettingsManage(plugins)){
if (dialog.ShowDialog() == DialogResult.OK && dialog.ShouldReloadUI){
foreach(SettingsTab tab in tabs.Values){
tab.Control = null;
}
SelectTab(currentTab);
}
}
}
private void btnClose_Click(object sender, EventArgs e){
Close();
}
private void AddButton<T>(string title, Func<T> constructor) where T : BaseTabSettings{
FlatButton btn = new FlatButton{
BackColor = SystemColors.Control,
FlatStyle = FlatStyle.Flat,
Location = new Point(0, (buttonHeight+1)*(panelButtons.Controls.Count/2)),
Margin = new Padding(0),
Size = new Size(panelButtons.Width, buttonHeight),
Text = title,
UseVisualStyleBackColor = true
};
btn.FlatAppearance.BorderSize = 0;
btn.FlatAppearance.MouseDownBackColor = Color.FromArgb(179, 213, 232);
btn.FlatAppearance.MouseOverBackColor = Color.FromArgb(216, 230, 237);
panelButtons.Controls.Add(btn);
panelButtons.Controls.Add(new Panel{
BackColor = Color.DimGray,
Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count-1].Location.Y+buttonHeight),
Margin = new Padding(0),
Size = new Size(panelButtons.Width, 1)
});
tabs.Add(typeof(T), new SettingsTab(btn, constructor));
btn.Click += (sender, args) => SelectTab<T>();
}
private void SelectTab<T>() where T : BaseTabSettings{
SelectTab(tabs[typeof(T)]);
}
private void SelectTab(SettingsTab tab){
if (currentTab != null){
currentTab.Button.BackColor = SystemColors.Control;
}
tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor;
if (!tab.IsInitialized){
foreach(Control control in tab.Control.InteractiveControls){
if (control is ComboBox){
control.MouseLeave += control_MouseLeave;
}
else if (control is TrackBar){
control.MouseWheel += control_MouseWheel;
}
}
tab.Control.OnReady();
}
panelContents.SuspendLayout();
panelContents.VerticalScroll.Value = 0; // https://gfycat.com/GrotesqueTastyAstarte
panelContents.Controls.Clear();
panelContents.Controls.Add(tab.Control);
panelContents.ResumeLayout(true);
panelContents.Focus();
currentTab = tab;
}
private void control_MouseLeave(object sender, EventArgs e){
panelContents.Focus();
}
private void control_MouseWheel(object sender, MouseEventArgs e){
((HandledMouseEventArgs)e).Handled = true;
panelContents.Focus();
}
private class SettingsTab{
public Button Button { get; }
public BaseTabSettings Control{
get => control ?? (control = constructor());
set => control = value;
}
public bool IsInitialized => control != null;
private readonly Func<BaseTabSettings> constructor;
private BaseTabSettings control;
public SettingsTab(Button button, Func<BaseTabSettings> constructor){
this.Button = button;
this.constructor = constructor;
}
}
}
}

View File

@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -1,33 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using TweetDuck.Configuration;
namespace TweetDuck.Core.Other.Settings{
class BaseTabSettings : UserControl{
protected static UserConfig Config => Program.UserConfig;
public IEnumerable<Control> InteractiveControls{
get{
foreach(Panel panel in Controls.OfType<Panel>()){
foreach(Control control in panel.Controls){
yield return control;
}
}
}
}
public BaseTabSettings(){
Padding = new Padding(6);
}
public virtual void OnReady(){}
public virtual void OnClosing(){}
protected static void PromptRestart(){
if (FormMessage.Information("TweetDuck Options", "The application must restart for the option to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
Program.Restart();
}
}
}
}

View File

@@ -1,203 +0,0 @@
namespace TweetDuck.Core.Other.Settings.Dialogs {
partial class DialogSettingsCSS {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.textBoxBrowserCSS = new System.Windows.Forms.TextBox();
this.btnCancel = new System.Windows.Forms.Button();
this.btnApply = new System.Windows.Forms.Button();
this.splitContainer = new System.Windows.Forms.SplitContainer();
this.labelBrowser = new System.Windows.Forms.Label();
this.labelNotification = new System.Windows.Forms.Label();
this.textBoxNotificationCSS = new System.Windows.Forms.TextBox();
this.labelWarning = new System.Windows.Forms.Label();
this.btnOpenWiki = new System.Windows.Forms.Button();
this.timerTestBrowser = new System.Windows.Forms.Timer(this.components);
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
this.splitContainer.Panel1.SuspendLayout();
this.splitContainer.Panel2.SuspendLayout();
this.splitContainer.SuspendLayout();
this.SuspendLayout();
//
// textBoxBrowserCSS
//
this.textBoxBrowserCSS.Dock = System.Windows.Forms.DockStyle.Bottom;
this.textBoxBrowserCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.textBoxBrowserCSS.Location = new System.Drawing.Point(0, 16);
this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
this.textBoxBrowserCSS.Multiline = true;
this.textBoxBrowserCSS.Name = "textBoxBrowserCSS";
this.textBoxBrowserCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxBrowserCSS.Size = new System.Drawing.Size(378, 253);
this.textBoxBrowserCSS.TabIndex = 1;
this.textBoxBrowserCSS.WordWrap = false;
this.textBoxBrowserCSS.KeyUp += new System.Windows.Forms.KeyEventHandler(this.textBoxBrowserCSS_KeyUp);
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.Location = new System.Drawing.Point(654, 287);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23);
this.btnCancel.TabIndex = 2;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// btnApply
//
this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnApply.Location = new System.Drawing.Point(716, 287);
this.btnApply.Name = "btnApply";
this.btnApply.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnApply.Size = new System.Drawing.Size(56, 23);
this.btnApply.TabIndex = 1;
this.btnApply.Text = "Apply";
this.btnApply.UseVisualStyleBackColor = true;
this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
//
// splitContainer
//
this.splitContainer.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.splitContainer.Location = new System.Drawing.Point(12, 12);
this.splitContainer.Name = "splitContainer";
//
// splitContainer.Panel1
//
this.splitContainer.Panel1.Controls.Add(this.labelBrowser);
this.splitContainer.Panel1.Controls.Add(this.textBoxBrowserCSS);
this.splitContainer.Panel1MinSize = 64;
//
// splitContainer.Panel2
//
this.splitContainer.Panel2.Controls.Add(this.labelNotification);
this.splitContainer.Panel2.Controls.Add(this.textBoxNotificationCSS);
this.splitContainer.Panel2MinSize = 64;
this.splitContainer.Size = new System.Drawing.Size(760, 269);
this.splitContainer.SplitterDistance = 378;
this.splitContainer.SplitterWidth = 5;
this.splitContainer.TabIndex = 0;
//
// labelBrowser
//
this.labelBrowser.AutoSize = true;
this.labelBrowser.Location = new System.Drawing.Point(-3, 0);
this.labelBrowser.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelBrowser.Name = "labelBrowser";
this.labelBrowser.Size = new System.Drawing.Size(45, 13);
this.labelBrowser.TabIndex = 0;
this.labelBrowser.Text = "Browser";
//
// labelNotification
//
this.labelNotification.AutoSize = true;
this.labelNotification.Location = new System.Drawing.Point(-3, 0);
this.labelNotification.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelNotification.Name = "labelNotification";
this.labelNotification.Size = new System.Drawing.Size(60, 13);
this.labelNotification.TabIndex = 0;
this.labelNotification.Text = "Notification";
//
// textBoxNotificationCSS
//
this.textBoxNotificationCSS.Dock = System.Windows.Forms.DockStyle.Bottom;
this.textBoxNotificationCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.textBoxNotificationCSS.Location = new System.Drawing.Point(0, 16);
this.textBoxNotificationCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
this.textBoxNotificationCSS.Multiline = true;
this.textBoxNotificationCSS.Name = "textBoxNotificationCSS";
this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxNotificationCSS.Size = new System.Drawing.Size(377, 253);
this.textBoxNotificationCSS.TabIndex = 1;
this.textBoxNotificationCSS.WordWrap = false;
//
// labelWarning
//
this.labelWarning.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.labelWarning.AutoSize = true;
this.labelWarning.Location = new System.Drawing.Point(91, 292);
this.labelWarning.Name = "labelWarning";
this.labelWarning.Size = new System.Drawing.Size(341, 13);
this.labelWarning.TabIndex = 3;
this.labelWarning.Text = "The code is not validated, please make sure there are no syntax errors.";
//
// btnOpenWiki
//
this.btnOpenWiki.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnOpenWiki.Location = new System.Drawing.Point(12, 287);
this.btnOpenWiki.Name = "btnOpenWiki";
this.btnOpenWiki.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnOpenWiki.Size = new System.Drawing.Size(73, 23);
this.btnOpenWiki.TabIndex = 4;
this.btnOpenWiki.Text = "Open Wiki";
this.btnOpenWiki.UseVisualStyleBackColor = true;
this.btnOpenWiki.Click += new System.EventHandler(this.btnOpenWiki_Click);
//
// timerTestBrowser
//
this.timerTestBrowser.Interval = 500;
this.timerTestBrowser.Tick += new System.EventHandler(this.timerTestBrowser_Tick);
//
// DialogSettingsCSS
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(784, 322);
this.Controls.Add(this.btnOpenWiki);
this.Controls.Add(this.labelWarning);
this.Controls.Add(this.splitContainer);
this.Controls.Add(this.btnApply);
this.Controls.Add(this.btnCancel);
this.MinimumSize = new System.Drawing.Size(600, 160);
this.Name = "DialogSettingsCSS";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.splitContainer.Panel1.ResumeLayout(false);
this.splitContainer.Panel1.PerformLayout();
this.splitContainer.Panel2.ResumeLayout(false);
this.splitContainer.Panel2.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit();
this.splitContainer.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox textBoxBrowserCSS;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Button btnApply;
private System.Windows.Forms.SplitContainer splitContainer;
private System.Windows.Forms.TextBox textBoxNotificationCSS;
private System.Windows.Forms.Label labelBrowser;
private System.Windows.Forms.Label labelNotification;
private System.Windows.Forms.Label labelWarning;
private System.Windows.Forms.Button btnOpenWiki;
private System.Windows.Forms.Timer timerTestBrowser;
}
}

View File

@@ -1,51 +0,0 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsCSS : Form{
public string BrowserCSS => textBoxBrowserCSS.Text;
public string NotificationCSS => textBoxNotificationCSS.Text;
private readonly Action<string> reinjectBrowserCSS;
public DialogSettingsCSS(Action<string> reinjectBrowserCSS){
InitializeComponent();
Text = Program.BrandName+" Options - CSS";
this.reinjectBrowserCSS = reinjectBrowserCSS;
textBoxBrowserCSS.EnableMultilineShortcuts();
textBoxBrowserCSS.Text = Program.UserConfig.CustomBrowserCSS ?? "";
textBoxNotificationCSS.EnableMultilineShortcuts();
textBoxNotificationCSS.Text = Program.UserConfig.CustomNotificationCSS ?? "";
}
private void textBoxBrowserCSS_KeyUp(object sender, KeyEventArgs e){
timerTestBrowser.Stop();
timerTestBrowser.Start();
}
private void timerTestBrowser_Tick(object sender, EventArgs e){
reinjectBrowserCSS(textBoxBrowserCSS.Text);
timerTestBrowser.Stop();
}
private void btnOpenWiki_Click(object sender, EventArgs e){
BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/wiki");
}
private void btnApply_Click(object sender, EventArgs e){
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e){
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@@ -1,170 +0,0 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Other.Settings.Export;
using TweetDuck.Plugins;
namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsManage : Form{
private enum State{
Deciding, Import, Export
}
public ExportFileFlags Flags{
get => selectedFlags;
set{
// this will call events and SetFlag, which also updates the UI
cbConfig.Checked = value.HasFlag(ExportFileFlags.Config);
cbSession.Checked = value.HasFlag(ExportFileFlags.Session);
cbPluginData.Checked = value.HasFlag(ExportFileFlags.PluginData);
}
}
public bool ShouldReloadUI { get; private set; }
private readonly PluginManager plugins;
private State currentState;
private ExportManager importManager;
private ExportFileFlags selectedFlags = ExportFileFlags.None;
public DialogSettingsManage(PluginManager plugins){
InitializeComponent();
this.plugins = plugins;
this.currentState = State.Deciding;
}
private void radioDecision_CheckedChanged(object sender, EventArgs e){
btnContinue.Enabled = true;
}
private void cbConfig_CheckedChanged(object sender, EventArgs e){
SetFlag(ExportFileFlags.Config, cbConfig.Checked);
}
private void cbSession_CheckedChanged(object sender, EventArgs e){
SetFlag(ExportFileFlags.Session, cbSession.Checked);
}
private void cbPluginData_CheckedChanged(object sender, EventArgs e){
SetFlag(ExportFileFlags.PluginData, cbPluginData.Checked);
}
private void btnContinue_Click(object sender, EventArgs e){
string file;
switch(currentState){
case State.Deciding:
// Reset
if (radioReset.Checked){
if (FormMessage.Warning("Reset TweetDuck Options", "This will reset all of your program options. Plugins will not be affected. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){
Program.ResetConfig();
ShouldReloadUI = true;
DialogResult = DialogResult.OK;
Close();
}
return;
}
// Import
else if (radioImport.Checked){
using(OpenFileDialog dialog = new OpenFileDialog{
AutoUpgradeEnabled = true,
DereferenceLinks = true,
Title = "Import TweetDuck Profile",
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
}){
if (dialog.ShowDialog() != DialogResult.OK){
return;
}
file = dialog.FileName;
}
importManager = new ExportManager(file, plugins);
currentState = State.Import;
Text = "Import Profile";
Flags = importManager.GetImportFlags();
cbConfig.Enabled = cbConfig.Checked;
cbSession.Enabled = cbSession.Checked;
cbPluginData.Enabled = cbPluginData.Checked;
}
// Export
else if (radioExport.Checked){
currentState = State.Export;
Text = "Export Profile";
btnContinue.Text = "Export Profile";
Flags = ExportFileFlags.All & ~ExportFileFlags.Session;
}
// Continue...
panelDecision.Visible = false;
panelExport.Visible = true;
break;
case State.Import:
if (importManager.Import(Flags)){
if (!importManager.IsRestarting){
ShouldReloadUI = true;
}
}
else{
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing TweetDuck profile.", true, importManager.LastException);
}
DialogResult = DialogResult.OK;
Close();
break;
case State.Export:
using(SaveFileDialog dialog = new SaveFileDialog{
AddExtension = true,
AutoUpgradeEnabled = true,
OverwritePrompt = true,
DefaultExt = "tdsettings",
FileName = "TweetDuck.tdsettings",
Title = "Export TweetDuck Profile",
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
}){
if (dialog.ShowDialog() != DialogResult.OK){
return;
}
file = dialog.FileName;
}
Program.UserConfig.Save();
ExportManager manager = new ExportManager(file, plugins);
if (!manager.Export(Flags)){
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, manager.LastException);
}
DialogResult = DialogResult.OK;
Close();
break;
}
}
private void btnCancel_Click(object sender, EventArgs e){
DialogResult = DialogResult.Cancel;
Close();
}
private void SetFlag(ExportFileFlags flag, bool enable){
selectedFlags = enable ? selectedFlags | flag : selectedFlags & ~flag;
btnContinue.Enabled = selectedFlags != ExportFileFlags.None;
if (currentState == State.Import){
btnContinue.Text = selectedFlags.HasFlag(ExportFileFlags.Session) ? "Import && Restart" : "Import Profile";
}
}
}
}

View File

@@ -1,159 +0,0 @@
namespace TweetDuck.Core.Other.Settings.Dialogs {
partial class DialogSettingsRestart {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.btnCancel = new System.Windows.Forms.Button();
this.btnRestart = new System.Windows.Forms.Button();
this.cbLogging = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.cbDebugUpdates = new System.Windows.Forms.CheckBox();
this.labelLocale = new System.Windows.Forms.Label();
this.comboLocale = new System.Windows.Forms.ComboBox();
this.labelDataFolder = new System.Windows.Forms.Label();
this.tbDataFolder = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.Location = new System.Drawing.Point(160, 171);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23);
this.btnCancel.TabIndex = 7;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// btnRestart
//
this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnRestart.Location = new System.Drawing.Point(97, 171);
this.btnRestart.Name = "btnRestart";
this.btnRestart.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnRestart.Size = new System.Drawing.Size(57, 23);
this.btnRestart.TabIndex = 6;
this.btnRestart.Text = "Restart";
this.btnRestart.UseVisualStyleBackColor = true;
this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click);
//
// cbLogging
//
this.cbLogging.AutoSize = true;
this.cbLogging.Location = new System.Drawing.Point(12, 12);
this.cbLogging.Name = "cbLogging";
this.cbLogging.Size = new System.Drawing.Size(64, 17);
this.cbLogging.TabIndex = 0;
this.cbLogging.Text = "Logging";
this.toolTip.SetToolTip(this.cbLogging, "Logging JavaScript output into a\r\ndebug.txt file in the data folder.");
this.cbLogging.UseVisualStyleBackColor = true;
//
// cbDebugUpdates
//
this.cbDebugUpdates.AutoSize = true;
this.cbDebugUpdates.Location = new System.Drawing.Point(12, 35);
this.cbDebugUpdates.Name = "cbDebugUpdates";
this.cbDebugUpdates.Size = new System.Drawing.Size(127, 17);
this.cbDebugUpdates.TabIndex = 1;
this.cbDebugUpdates.Text = "Pre-Release Updates";
this.toolTip.SetToolTip(this.cbDebugUpdates, "Allows updating to pre-releases.");
this.cbDebugUpdates.UseVisualStyleBackColor = true;
//
// labelLocale
//
this.labelLocale.AutoSize = true;
this.labelLocale.Location = new System.Drawing.Point(12, 67);
this.labelLocale.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelLocale.Name = "labelLocale";
this.labelLocale.Size = new System.Drawing.Size(39, 13);
this.labelLocale.TabIndex = 2;
this.labelLocale.Text = "Locale";
//
// comboLocale
//
this.comboLocale.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.comboLocale.FormattingEnabled = true;
this.comboLocale.Location = new System.Drawing.Point(15, 83);
this.comboLocale.Name = "comboLocale";
this.comboLocale.Size = new System.Drawing.Size(201, 21);
this.comboLocale.TabIndex = 3;
//
// labelDataFolder
//
this.labelDataFolder.AutoSize = true;
this.labelDataFolder.Location = new System.Drawing.Point(12, 119);
this.labelDataFolder.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDataFolder.Name = "labelDataFolder";
this.labelDataFolder.Size = new System.Drawing.Size(62, 13);
this.labelDataFolder.TabIndex = 4;
this.labelDataFolder.Text = "Data Folder";
//
// tbDataFolder
//
this.tbDataFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tbDataFolder.Location = new System.Drawing.Point(15, 135);
this.tbDataFolder.Name = "tbDataFolder";
this.tbDataFolder.Size = new System.Drawing.Size(201, 20);
this.tbDataFolder.TabIndex = 5;
//
// DialogSettingsRestart
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(228, 206);
this.Controls.Add(this.tbDataFolder);
this.Controls.Add(this.labelDataFolder);
this.Controls.Add(this.comboLocale);
this.Controls.Add(this.labelLocale);
this.Controls.Add(this.cbDebugUpdates);
this.Controls.Add(this.cbLogging);
this.Controls.Add(this.btnRestart);
this.Controls.Add(this.btnCancel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "DialogSettingsRestart";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Button btnRestart;
private System.Windows.Forms.CheckBox cbLogging;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.CheckBox cbDebugUpdates;
private System.Windows.Forms.Label labelLocale;
private System.Windows.Forms.ComboBox comboLocale;
private System.Windows.Forms.Label labelDataFolder;
private System.Windows.Forms.TextBox tbDataFolder;
}
}

View File

@@ -1,62 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Data;
namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsRestart : Form{
private const string DefaultLocale = "en-US";
public CommandLineArgs Args { get; private set; }
public DialogSettingsRestart(CommandLineArgs currentArgs){
InitializeComponent();
try{
object[] locales = Directory.EnumerateFiles(Path.Combine(Program.ProgramPath, "locales"), "*.pak", SearchOption.TopDirectoryOnly).Select(Path.GetFileNameWithoutExtension).ToArray<object>();
comboLocale.Items.AddRange(locales);
}catch{
comboLocale.Items.Add(DefaultLocale);
}
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
cbDebugUpdates.Checked = currentArgs.HasFlag(Arguments.ArgDebugUpdates);
comboLocale.SelectedItem = currentArgs.GetValue(Arguments.ArgLocale, DefaultLocale);
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty);
Text = Program.BrandName+" Arguments";
}
private void btnRestart_Click(object sender, EventArgs e){
Args = new CommandLineArgs();
if (cbLogging.Checked){
Args.AddFlag(Arguments.ArgLogging);
}
if (cbDebugUpdates.Checked){
Args.AddFlag(Arguments.ArgDebugUpdates);
}
string locale = comboLocale.SelectedItem as string;
if (!string.IsNullOrWhiteSpace(locale) && locale != DefaultLocale){
Args.SetValue(Arguments.ArgLocale, locale);
}
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text)){
Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);
}
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e){
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@@ -1,12 +0,0 @@
using System;
namespace TweetDuck.Core.Other.Settings.Export{
[Flags]
enum ExportFileFlags{
None = 0,
Config = 1,
Session = 2,
PluginData = 4,
All = Config|Session|PluginData
}
}

View File

@@ -1,302 +0,0 @@
namespace TweetDuck.Core.Other.Settings {
partial class TabSettingsAdvanced {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.btnClearCache = new System.Windows.Forms.Button();
this.checkHardwareAcceleration = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.btnEditCefArgs = new System.Windows.Forms.Button();
this.btnEditCSS = new System.Windows.Forms.Button();
this.btnRestartArgs = new System.Windows.Forms.Button();
this.btnRestart = new System.Windows.Forms.Button();
this.btnOpenAppFolder = new System.Windows.Forms.Button();
this.btnOpenDataFolder = new System.Windows.Forms.Button();
this.numMemoryThreshold = new TweetDuck.Core.Controls.NumericUpDownEx();
this.checkBrowserGCReload = new System.Windows.Forms.CheckBox();
this.labelApp = new System.Windows.Forms.Label();
this.panelApp = new System.Windows.Forms.Panel();
this.labelPerformance = new System.Windows.Forms.Label();
this.panelPerformance = new System.Windows.Forms.Panel();
this.labelMemoryUsage = new System.Windows.Forms.Label();
this.panelConfiguration = new System.Windows.Forms.Panel();
this.labelConfiguration = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.numMemoryThreshold)).BeginInit();
this.panelApp.SuspendLayout();
this.panelPerformance.SuspendLayout();
this.panelConfiguration.SuspendLayout();
this.SuspendLayout();
//
// btnClearCache
//
this.btnClearCache.Location = new System.Drawing.Point(5, 28);
this.btnClearCache.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnClearCache.Name = "btnClearCache";
this.btnClearCache.Size = new System.Drawing.Size(144, 23);
this.btnClearCache.TabIndex = 1;
this.btnClearCache.Text = "Clear Cache (calculating)";
this.toolTip.SetToolTip(this.btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources.");
this.btnClearCache.UseVisualStyleBackColor = true;
//
// checkHardwareAcceleration
//
this.checkHardwareAcceleration.AutoSize = true;
this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 5);
this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkHardwareAcceleration.Name = "checkHardwareAcceleration";
this.checkHardwareAcceleration.Size = new System.Drawing.Size(134, 17);
this.checkHardwareAcceleration.TabIndex = 0;
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
this.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses your graphics card to improve performance.\r\nDisable if you experience issues with rendering.");
this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
//
// btnEditCefArgs
//
this.btnEditCefArgs.Location = new System.Drawing.Point(5, 3);
this.btnEditCefArgs.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnEditCefArgs.Name = "btnEditCefArgs";
this.btnEditCefArgs.Size = new System.Drawing.Size(144, 23);
this.btnEditCefArgs.TabIndex = 0;
this.btnEditCefArgs.Text = "Edit CEF Arguments";
this.toolTip.SetToolTip(this.btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework.");
this.btnEditCefArgs.UseVisualStyleBackColor = true;
//
// btnEditCSS
//
this.btnEditCSS.Location = new System.Drawing.Point(155, 3);
this.btnEditCSS.Name = "btnEditCSS";
this.btnEditCSS.Size = new System.Drawing.Size(144, 23);
this.btnEditCSS.TabIndex = 1;
this.btnEditCSS.Text = "Edit CSS";
this.toolTip.SetToolTip(this.btnEditCSS, "Set custom CSS for browser and notification windows.");
this.btnEditCSS.UseVisualStyleBackColor = true;
//
// btnRestartArgs
//
this.btnRestartArgs.Location = new System.Drawing.Point(155, 32);
this.btnRestartArgs.Name = "btnRestartArgs";
this.btnRestartArgs.Size = new System.Drawing.Size(144, 23);
this.btnRestartArgs.TabIndex = 3;
this.btnRestartArgs.Text = "Restart with Arguments";
this.toolTip.SetToolTip(this.btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments.");
this.btnRestartArgs.UseVisualStyleBackColor = true;
//
// btnRestart
//
this.btnRestart.Location = new System.Drawing.Point(155, 3);
this.btnRestart.Name = "btnRestart";
this.btnRestart.Size = new System.Drawing.Size(144, 23);
this.btnRestart.TabIndex = 2;
this.btnRestart.Text = "Restart the Program";
this.toolTip.SetToolTip(this.btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch.");
this.btnRestart.UseVisualStyleBackColor = true;
//
// btnOpenAppFolder
//
this.btnOpenAppFolder.Location = new System.Drawing.Point(5, 3);
this.btnOpenAppFolder.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnOpenAppFolder.Name = "btnOpenAppFolder";
this.btnOpenAppFolder.Size = new System.Drawing.Size(144, 23);
this.btnOpenAppFolder.TabIndex = 0;
this.btnOpenAppFolder.Text = "Open Program Folder";
this.toolTip.SetToolTip(this.btnOpenAppFolder, "Opens the folder where the app is located.");
this.btnOpenAppFolder.UseVisualStyleBackColor = true;
//
// btnOpenDataFolder
//
this.btnOpenDataFolder.Location = new System.Drawing.Point(5, 32);
this.btnOpenDataFolder.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnOpenDataFolder.Name = "btnOpenDataFolder";
this.btnOpenDataFolder.Size = new System.Drawing.Size(144, 23);
this.btnOpenDataFolder.TabIndex = 1;
this.btnOpenDataFolder.Text = "Open Data Folder";
this.toolTip.SetToolTip(this.btnOpenDataFolder, "Opens the folder where your profile data is located.");
this.btnOpenDataFolder.UseVisualStyleBackColor = true;
//
// numMemoryThreshold
//
this.numMemoryThreshold.Increment = new decimal(new int[] {
50,
0,
0,
0});
this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82);
this.numMemoryThreshold.Maximum = new decimal(new int[] {
3000,
0,
0,
0});
this.numMemoryThreshold.Minimum = new decimal(new int[] {
200,
0,
0,
0});
this.numMemoryThreshold.Name = "numMemoryThreshold";
this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20);
this.numMemoryThreshold.TabIndex = 4;
this.numMemoryThreshold.TextSuffix = " MB";
this.toolTip.SetToolTip(this.numMemoryThreshold, "Minimum amount of memory usage by the browser process to trigger the cleanup.\r\nThis is not a limit, the usage is allowed to exceed this value.");
this.numMemoryThreshold.Value = new decimal(new int[] {
350,
0,
0,
0});
//
// checkBrowserGCReload
//
this.checkBrowserGCReload.AutoSize = true;
this.checkBrowserGCReload.Location = new System.Drawing.Point(6, 84);
this.checkBrowserGCReload.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkBrowserGCReload.Name = "checkBrowserGCReload";
this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17);
this.checkBrowserGCReload.TabIndex = 3;
this.checkBrowserGCReload.Text = "Enable Browser Memory Threshold";
this.toolTip.SetToolTip(this.checkBrowserGCReload, "Automatically reloads TweetDeck to save memory. This option only works\r\nif the browser is in a \'default state\', i.e. all modals and drawers are closed,\r\nand all columns are scrolled to top. Some notifications may be lost.");
this.checkBrowserGCReload.UseVisualStyleBackColor = true;
//
// labelApp
//
this.labelApp.AutoSize = true;
this.labelApp.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelApp.Location = new System.Drawing.Point(6, 8);
this.labelApp.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
this.labelApp.Name = "labelApp";
this.labelApp.Size = new System.Drawing.Size(38, 20);
this.labelApp.TabIndex = 0;
this.labelApp.Text = "App";
//
// panelApp
//
this.panelApp.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelApp.Controls.Add(this.btnOpenDataFolder);
this.panelApp.Controls.Add(this.btnOpenAppFolder);
this.panelApp.Controls.Add(this.btnRestart);
this.panelApp.Controls.Add(this.btnRestartArgs);
this.panelApp.Location = new System.Drawing.Point(9, 31);
this.panelApp.Name = "panelApp";
this.panelApp.Size = new System.Drawing.Size(322, 59);
this.panelApp.TabIndex = 1;
//
// labelPerformance
//
this.labelPerformance.AutoSize = true;
this.labelPerformance.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelPerformance.Location = new System.Drawing.Point(6, 114);
this.labelPerformance.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelPerformance.Name = "labelPerformance";
this.labelPerformance.Size = new System.Drawing.Size(100, 20);
this.labelPerformance.TabIndex = 2;
this.labelPerformance.Text = "Performance";
//
// panelPerformance
//
this.panelPerformance.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelPerformance.Controls.Add(this.checkBrowserGCReload);
this.panelPerformance.Controls.Add(this.numMemoryThreshold);
this.panelPerformance.Controls.Add(this.labelMemoryUsage);
this.panelPerformance.Controls.Add(this.checkHardwareAcceleration);
this.panelPerformance.Controls.Add(this.btnClearCache);
this.panelPerformance.Location = new System.Drawing.Point(9, 137);
this.panelPerformance.Name = "panelPerformance";
this.panelPerformance.Size = new System.Drawing.Size(322, 105);
this.panelPerformance.TabIndex = 3;
//
// labelMemoryUsage
//
this.labelMemoryUsage.AutoSize = true;
this.labelMemoryUsage.Location = new System.Drawing.Point(3, 66);
this.labelMemoryUsage.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelMemoryUsage.Name = "labelMemoryUsage";
this.labelMemoryUsage.Size = new System.Drawing.Size(78, 13);
this.labelMemoryUsage.TabIndex = 2;
this.labelMemoryUsage.Text = "Memory Usage";
//
// panelConfiguration
//
this.panelConfiguration.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelConfiguration.Controls.Add(this.btnEditCSS);
this.panelConfiguration.Controls.Add(this.btnEditCefArgs);
this.panelConfiguration.Location = new System.Drawing.Point(9, 289);
this.panelConfiguration.Name = "panelConfiguration";
this.panelConfiguration.Size = new System.Drawing.Size(322, 29);
this.panelConfiguration.TabIndex = 5;
//
// labelConfiguration
//
this.labelConfiguration.AutoSize = true;
this.labelConfiguration.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelConfiguration.Location = new System.Drawing.Point(6, 266);
this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelConfiguration.Name = "labelConfiguration";
this.labelConfiguration.Size = new System.Drawing.Size(104, 20);
this.labelConfiguration.TabIndex = 4;
this.labelConfiguration.Text = "Configuration";
//
// TabSettingsAdvanced
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.labelConfiguration);
this.Controls.Add(this.panelConfiguration);
this.Controls.Add(this.panelPerformance);
this.Controls.Add(this.labelPerformance);
this.Controls.Add(this.panelApp);
this.Controls.Add(this.labelApp);
this.Name = "TabSettingsAdvanced";
this.Size = new System.Drawing.Size(340, 328);
((System.ComponentModel.ISupportInitialize)(this.numMemoryThreshold)).EndInit();
this.panelApp.ResumeLayout(false);
this.panelPerformance.ResumeLayout(false);
this.panelPerformance.PerformLayout();
this.panelConfiguration.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button btnClearCache;
private System.Windows.Forms.CheckBox checkHardwareAcceleration;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Button btnEditCefArgs;
private System.Windows.Forms.Button btnEditCSS;
private System.Windows.Forms.Button btnRestartArgs;
private System.Windows.Forms.Button btnRestart;
private System.Windows.Forms.Button btnOpenAppFolder;
private System.Windows.Forms.Button btnOpenDataFolder;
private System.Windows.Forms.Label labelApp;
private System.Windows.Forms.Panel panelApp;
private System.Windows.Forms.Label labelPerformance;
private System.Windows.Forms.Panel panelPerformance;
private System.Windows.Forms.Panel panelConfiguration;
private System.Windows.Forms.Label labelConfiguration;
private System.Windows.Forms.Label labelMemoryUsage;
private Controls.NumericUpDownEx numMemoryThreshold;
private System.Windows.Forms.CheckBox checkBrowserGCReload;
}
}

View File

@@ -1,142 +0,0 @@
using System;
using System.Diagnostics;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Settings{
partial class TabSettingsAdvanced : BaseTabSettings{
private readonly Action<string> reinjectBrowserCSS;
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS){
InitializeComponent();
this.reinjectBrowserCSS = reinjectBrowserCSS;
if (SystemConfig.IsHardwareAccelerationSupported){
checkHardwareAcceleration.Checked = Program.SystemConfig.HardwareAcceleration;
}
else{
checkHardwareAcceleration.Enabled = false;
checkHardwareAcceleration.Checked = false;
}
checkBrowserGCReload.Checked = Config.EnableBrowserGCReload;
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
numMemoryThreshold.SetValueSafe(Config.BrowserMemoryThreshold);
BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => {
if (bytes == -1L){
btnClearCache.Text = "Clear Cache (unknown size)";
}
else{
btnClearCache.Text = "Clear Cache ("+(int)Math.Ceiling(bytes/(1024.0*1024.0))+" MB)";
}
}));
}
public override void OnReady(){
btnClearCache.Click += btnClearCache_Click;
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
checkBrowserGCReload.CheckedChanged += checkBrowserGCReload_CheckedChanged;
numMemoryThreshold.ValueChanged += numMemoryThreshold_ValueChanged;
btnEditCefArgs.Click += btnEditCefArgs_Click;
btnEditCSS.Click += btnEditCSS_Click;
btnOpenAppFolder.Click += btnOpenAppFolder_Click;
btnOpenDataFolder.Click += btnOpenDataFolder_Click;
btnRestart.Click += btnRestart_Click;
btnRestartArgs.Click += btnRestartArgs_Click;
}
private void btnClearCache_Click(object sender, EventArgs e){
btnClearCache.Enabled = false;
BrowserCache.SetClearOnExit();
FormMessage.Information("Clear Cache", "Cache will be automatically cleared when TweetDuck exits.", FormMessage.OK);
}
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
Program.SystemConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
Program.SystemConfig.Save();
PromptRestart();
}
private void checkBrowserGCReload_CheckedChanged(object sender, EventArgs e){
Config.EnableBrowserGCReload = checkBrowserGCReload.Checked;
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
}
private void numMemoryThreshold_ValueChanged(object sender, EventArgs e){
Config.BrowserMemoryThreshold = (int)numMemoryThreshold.Value;
}
private void btnEditCefArgs_Click(object sender, EventArgs e){
DialogSettingsCefArgs form = new DialogSettingsCefArgs();
form.VisibleChanged += (sender2, args2) => {
form.MoveToCenter(ParentForm);
};
form.FormClosed += (sender2, args2) => {
NativeMethods.SetFormDisabled(ParentForm, false);
if (form.DialogResult == DialogResult.OK){
Config.CustomCefArgs = form.CefArgs;
PromptRestart();
form.Dispose();
}
else form.Dispose();
};
form.Show(ParentForm);
NativeMethods.SetFormDisabled(ParentForm, true);
}
private void btnEditCSS_Click(object sender, EventArgs e){
DialogSettingsCSS form = new DialogSettingsCSS(reinjectBrowserCSS);
form.VisibleChanged += (sender2, args2) => {
form.MoveToCenter(ParentForm);
};
form.FormClosed += (sender2, args2) => {
NativeMethods.SetFormDisabled(ParentForm, false);
if (form.DialogResult == DialogResult.OK){
Config.CustomBrowserCSS = form.BrowserCSS;
Config.CustomNotificationCSS = form.NotificationCSS;
}
reinjectBrowserCSS(Config.CustomBrowserCSS); // reinject on cancel too, because the CSS is updated while typing
form.Dispose();
};
form.Show(ParentForm);
NativeMethods.SetFormDisabled(ParentForm, true);
}
private void btnOpenAppFolder_Click(object sender, EventArgs e){
using(Process.Start("explorer.exe", "\""+Program.ProgramPath+"\"")){}
}
private void btnOpenDataFolder_Click(object sender, EventArgs e){
using(Process.Start("explorer.exe", "\""+Program.StoragePath+"\"")){}
}
private void btnRestart_Click(object sender, EventArgs e){
Program.Restart();
}
private void btnRestartArgs_Click(object sender, EventArgs e){
using(DialogSettingsRestart dialog = new DialogSettingsRestart(Arguments.GetCurrentClean())){
if (dialog.ShowDialog() == DialogResult.OK){
Program.RestartWithArgs(dialog.Args);
}
}
}
}
}

View File

@@ -1,318 +0,0 @@
namespace TweetDuck.Core.Other.Settings {
partial class TabSettingsGeneral {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.checkExpandLinks = new System.Windows.Forms.CheckBox();
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.checkTrayHighlight = new System.Windows.Forms.CheckBox();
this.checkSpellCheck = new System.Windows.Forms.CheckBox();
this.checkUpdateNotifications = new System.Windows.Forms.CheckBox();
this.btnCheckUpdates = new System.Windows.Forms.Button();
this.labelZoomValue = new System.Windows.Forms.Label();
this.checkSwitchAccountSelectors = new System.Windows.Forms.CheckBox();
this.labelTrayIcon = new System.Windows.Forms.Label();
this.trackBarZoom = new System.Windows.Forms.TrackBar();
this.labelZoom = new System.Windows.Forms.Label();
this.zoomUpdateTimer = new System.Windows.Forms.Timer(this.components);
this.labelUI = new System.Windows.Forms.Label();
this.panelUI = new System.Windows.Forms.Panel();
this.labelTray = new System.Windows.Forms.Label();
this.panelUpdates = new System.Windows.Forms.Panel();
this.panelTray = new System.Windows.Forms.Panel();
this.labelUpdates = new System.Windows.Forms.Label();
this.checkBestImageQuality = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
this.panelUI.SuspendLayout();
this.panelUpdates.SuspendLayout();
this.panelTray.SuspendLayout();
this.SuspendLayout();
//
// checkExpandLinks
//
this.checkExpandLinks.AutoSize = true;
this.checkExpandLinks.Location = new System.Drawing.Point(6, 5);
this.checkExpandLinks.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkExpandLinks.Name = "checkExpandLinks";
this.checkExpandLinks.Size = new System.Drawing.Size(166, 17);
this.checkExpandLinks.TabIndex = 0;
this.checkExpandLinks.Text = "Expand Links When Hovered";
this.toolTip.SetToolTip(this.checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
this.checkExpandLinks.UseVisualStyleBackColor = true;
//
// comboBoxTrayType
//
this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxTrayType.FormattingEnabled = true;
this.comboBoxTrayType.Location = new System.Drawing.Point(5, 5);
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 5, 3, 3);
this.comboBoxTrayType.Name = "comboBoxTrayType";
this.comboBoxTrayType.Size = new System.Drawing.Size(144, 21);
this.comboBoxTrayType.TabIndex = 0;
this.toolTip.SetToolTip(this.comboBoxTrayType, "Changes behavior of the Tray icon.\r\nRight-click the icon for an action menu.");
//
// checkTrayHighlight
//
this.checkTrayHighlight.AutoSize = true;
this.checkTrayHighlight.Location = new System.Drawing.Point(6, 56);
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkTrayHighlight.Name = "checkTrayHighlight";
this.checkTrayHighlight.Size = new System.Drawing.Size(103, 17);
this.checkTrayHighlight.TabIndex = 2;
this.checkTrayHighlight.Text = "Enable Highlight";
this.toolTip.SetToolTip(this.checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with popup or audio notifications.\r\nThe icon resets when the main window is restored.");
this.checkTrayHighlight.UseVisualStyleBackColor = true;
//
// checkSpellCheck
//
this.checkSpellCheck.AutoSize = true;
this.checkSpellCheck.Location = new System.Drawing.Point(6, 74);
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSpellCheck.Name = "checkSpellCheck";
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
this.checkSpellCheck.TabIndex = 3;
this.checkSpellCheck.Text = "Enable Spell Check";
this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly.");
this.checkSpellCheck.UseVisualStyleBackColor = true;
//
// checkUpdateNotifications
//
this.checkUpdateNotifications.AutoSize = true;
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 5);
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkUpdateNotifications.Name = "checkUpdateNotifications";
this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17);
this.checkUpdateNotifications.TabIndex = 0;
this.checkUpdateNotifications.Text = "Check Updates Automatically";
this.toolTip.SetToolTip(this.checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear again.");
this.checkUpdateNotifications.UseVisualStyleBackColor = true;
//
// btnCheckUpdates
//
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 28);
this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnCheckUpdates.Name = "btnCheckUpdates";
this.btnCheckUpdates.Size = new System.Drawing.Size(144, 23);
this.btnCheckUpdates.TabIndex = 1;
this.btnCheckUpdates.Text = "Check Updates Now";
this.toolTip.SetToolTip(this.btnCheckUpdates, "Forces an update check, even for updates that had been dismissed.");
this.btnCheckUpdates.UseVisualStyleBackColor = true;
//
// labelZoomValue
//
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
this.labelZoomValue.Location = new System.Drawing.Point(141, 123);
this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelZoomValue.Name = "labelZoomValue";
this.labelZoomValue.Size = new System.Drawing.Size(38, 13);
this.labelZoomValue.TabIndex = 6;
this.labelZoomValue.Text = "100%";
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
//
// checkSwitchAccountSelectors
//
this.checkSwitchAccountSelectors.AutoSize = true;
this.checkSwitchAccountSelectors.Location = new System.Drawing.Point(6, 28);
this.checkSwitchAccountSelectors.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSwitchAccountSelectors.Name = "checkSwitchAccountSelectors";
this.checkSwitchAccountSelectors.Size = new System.Drawing.Size(172, 17);
this.checkSwitchAccountSelectors.TabIndex = 1;
this.checkSwitchAccountSelectors.Text = "Shift Selects Multiple Accounts";
this.toolTip.SetToolTip(this.checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect multiple accounts, instead of TweetDeck\'s default behavior.");
this.checkSwitchAccountSelectors.UseVisualStyleBackColor = true;
//
// labelTrayIcon
//
this.labelTrayIcon.AutoSize = true;
this.labelTrayIcon.Location = new System.Drawing.Point(3, 38);
this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
this.labelTrayIcon.Name = "labelTrayIcon";
this.labelTrayIcon.Size = new System.Drawing.Size(52, 13);
this.labelTrayIcon.TabIndex = 1;
this.labelTrayIcon.Text = "Tray Icon";
//
// trackBarZoom
//
this.trackBarZoom.AutoSize = false;
this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control;
this.trackBarZoom.LargeChange = 25;
this.trackBarZoom.Location = new System.Drawing.Point(3, 122);
this.trackBarZoom.Maximum = 200;
this.trackBarZoom.Minimum = 50;
this.trackBarZoom.Name = "trackBarZoom";
this.trackBarZoom.Size = new System.Drawing.Size(148, 30);
this.trackBarZoom.SmallChange = 5;
this.trackBarZoom.TabIndex = 5;
this.trackBarZoom.TickFrequency = 25;
this.trackBarZoom.Value = 100;
//
// labelZoom
//
this.labelZoom.AutoSize = true;
this.labelZoom.Location = new System.Drawing.Point(3, 106);
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelZoom.Name = "labelZoom";
this.labelZoom.Size = new System.Drawing.Size(34, 13);
this.labelZoom.TabIndex = 4;
this.labelZoom.Text = "Zoom";
//
// zoomUpdateTimer
//
this.zoomUpdateTimer.Interval = 250;
this.zoomUpdateTimer.Tick += new System.EventHandler(this.zoomUpdateTimer_Tick);
//
// labelUI
//
this.labelUI.AutoSize = true;
this.labelUI.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelUI.Location = new System.Drawing.Point(6, 8);
this.labelUI.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
this.labelUI.Name = "labelUI";
this.labelUI.Size = new System.Drawing.Size(111, 20);
this.labelUI.TabIndex = 0;
this.labelUI.Text = "User Interface";
//
// panelUI
//
this.panelUI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelUI.Controls.Add(this.checkBestImageQuality);
this.panelUI.Controls.Add(this.checkExpandLinks);
this.panelUI.Controls.Add(this.checkSwitchAccountSelectors);
this.panelUI.Controls.Add(this.checkSpellCheck);
this.panelUI.Controls.Add(this.labelZoom);
this.panelUI.Controls.Add(this.labelZoomValue);
this.panelUI.Controls.Add(this.trackBarZoom);
this.panelUI.Location = new System.Drawing.Point(9, 31);
this.panelUI.Name = "panelUI";
this.panelUI.Size = new System.Drawing.Size(322, 157);
this.panelUI.TabIndex = 1;
//
// labelTray
//
this.labelTray.AutoSize = true;
this.labelTray.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelTray.Location = new System.Drawing.Point(6, 212);
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelTray.Name = "labelTray";
this.labelTray.Size = new System.Drawing.Size(96, 20);
this.labelTray.TabIndex = 2;
this.labelTray.Text = "System Tray";
//
// panelUpdates
//
this.panelUpdates.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelUpdates.Controls.Add(this.checkUpdateNotifications);
this.panelUpdates.Controls.Add(this.btnCheckUpdates);
this.panelUpdates.Location = new System.Drawing.Point(9, 358);
this.panelUpdates.Name = "panelUpdates";
this.panelUpdates.Size = new System.Drawing.Size(322, 55);
this.panelUpdates.TabIndex = 5;
//
// panelTray
//
this.panelTray.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelTray.Controls.Add(this.checkTrayHighlight);
this.panelTray.Controls.Add(this.comboBoxTrayType);
this.panelTray.Controls.Add(this.labelTrayIcon);
this.panelTray.Location = new System.Drawing.Point(9, 235);
this.panelTray.Name = "panelTray";
this.panelTray.Size = new System.Drawing.Size(322, 76);
this.panelTray.TabIndex = 3;
//
// labelUpdates
//
this.labelUpdates.AutoSize = true;
this.labelUpdates.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelUpdates.Location = new System.Drawing.Point(6, 335);
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelUpdates.Name = "labelUpdates";
this.labelUpdates.Size = new System.Drawing.Size(70, 20);
this.labelUpdates.TabIndex = 4;
this.labelUpdates.Text = "Updates";
//
// checkBestImageQuality
//
this.checkBestImageQuality.AutoSize = true;
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 51);
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkBestImageQuality.Name = "checkBestImageQuality";
this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17);
this.checkBestImageQuality.TabIndex = 2;
this.checkBestImageQuality.Text = "Best Image Quality";
this.toolTip.SetToolTip(this.checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
this.checkBestImageQuality.UseVisualStyleBackColor = true;
//
// TabSettingsGeneral
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.labelUpdates);
this.Controls.Add(this.panelTray);
this.Controls.Add(this.panelUpdates);
this.Controls.Add(this.labelTray);
this.Controls.Add(this.panelUI);
this.Controls.Add(this.labelUI);
this.Name = "TabSettingsGeneral";
this.Size = new System.Drawing.Size(340, 422);
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
this.panelUI.ResumeLayout(false);
this.panelUI.PerformLayout();
this.panelUpdates.ResumeLayout(false);
this.panelUpdates.PerformLayout();
this.panelTray.ResumeLayout(false);
this.panelTray.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.CheckBox checkExpandLinks;
private System.Windows.Forms.ComboBox comboBoxTrayType;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Label labelTrayIcon;
private System.Windows.Forms.CheckBox checkTrayHighlight;
private System.Windows.Forms.CheckBox checkSpellCheck;
private System.Windows.Forms.CheckBox checkUpdateNotifications;
private System.Windows.Forms.Button btnCheckUpdates;
private System.Windows.Forms.Label labelZoom;
private System.Windows.Forms.Label labelZoomValue;
private System.Windows.Forms.TrackBar trackBarZoom;
private System.Windows.Forms.Timer zoomUpdateTimer;
private System.Windows.Forms.CheckBox checkSwitchAccountSelectors;
private System.Windows.Forms.Label labelUI;
private System.Windows.Forms.Panel panelUI;
private System.Windows.Forms.Label labelTray;
private System.Windows.Forms.Panel panelUpdates;
private System.Windows.Forms.Panel panelTray;
private System.Windows.Forms.Label labelUpdates;
private System.Windows.Forms.CheckBox checkBestImageQuality;
}
}

View File

@@ -1,122 +0,0 @@
using System;
using TweetDuck.Core.Controls;
using TweetDuck.Updates;
using TweetDuck.Updates.Events;
namespace TweetDuck.Core.Other.Settings{
partial class TabSettingsGeneral : BaseTabSettings{
private readonly UpdateHandler updates;
private int updateCheckEventId = -1;
public TabSettingsGeneral(UpdateHandler updates){
InitializeComponent();
this.updates = updates;
this.updates.CheckFinished += updates_CheckFinished;
Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished;
comboBoxTrayType.Items.Add("Disabled");
comboBoxTrayType.Items.Add("Display Icon Only");
comboBoxTrayType.Items.Add("Minimize to Tray");
comboBoxTrayType.Items.Add("Close to Tray");
comboBoxTrayType.Items.Add("Combined");
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count-1);
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
trackBarZoom.SetValueSafe(Config.ZoomLevel);
labelZoomValue.Text = trackBarZoom.Value+"%";
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
checkBestImageQuality.Checked = Config.BestImageQuality;
checkSpellCheck.Checked = Config.EnableSpellCheck;
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
checkUpdateNotifications.Checked = Config.EnableUpdateCheck;
}
public override void OnReady(){
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged;
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged;
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
comboBoxTrayType.SelectedIndexChanged += comboBoxTrayType_SelectedIndexChanged;
checkTrayHighlight.CheckedChanged += checkTrayHighlight_CheckedChanged;
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged;
btnCheckUpdates.Click += btnCheckUpdates_Click;
}
public override void OnClosing(){
Config.ZoomLevel = trackBarZoom.Value;
}
private void checkExpandLinks_CheckedChanged(object sender, EventArgs e){
Config.ExpandLinksOnHover = checkExpandLinks.Checked;
}
private void checkSwitchAccountSelectors_CheckedChanged(object sender, EventArgs e){
Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked;
}
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){
Config.BestImageQuality = checkBestImageQuality.Checked;
}
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){
Config.EnableSpellCheck = checkSpellCheck.Checked;
PromptRestart();
}
private void trackBarZoom_ValueChanged(object sender, EventArgs e){
if (trackBarZoom.AlignValueToTick()){
zoomUpdateTimer.Stop();
zoomUpdateTimer.Start();
labelZoomValue.Text = trackBarZoom.Value+"%";
}
}
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
Config.TrayBehavior = (TrayIcon.Behavior)comboBoxTrayType.SelectedIndex;
}
private void checkTrayHighlight_CheckedChanged(object sender, EventArgs e){
Config.EnableTrayHighlight = checkTrayHighlight.Checked;
}
private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e){
Config.EnableUpdateCheck = checkUpdateNotifications.Checked;
}
private void btnCheckUpdates_Click(object sender, EventArgs e){
updateCheckEventId = updates.Check(true);
if (updateCheckEventId == -1){
FormMessage.Warning("Unsupported System", "Sorry, your system is no longer supported.", FormMessage.OK);
}
else{
btnCheckUpdates.Enabled = false;
updates.DismissUpdate(string.Empty);
}
}
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
this.InvokeAsyncSafe(() => {
if (e.EventId == updateCheckEventId){
btnCheckUpdates.Enabled = true;
if (!e.UpdateAvailable){
FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
}
}
});
}
private void zoomUpdateTimer_Tick(object sender, EventArgs e){
Config.ZoomLevel = trackBarZoom.Value;
zoomUpdateTimer.Stop();
}
}
}

View File

@@ -1,131 +0,0 @@
namespace TweetDuck.Core.Other.Settings {
partial class TabSettingsSounds {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.btnPlaySound = new System.Windows.Forms.Button();
this.btnResetSound = new System.Windows.Forms.Button();
this.btnBrowseSound = new System.Windows.Forms.Button();
this.tbCustomSound = new System.Windows.Forms.TextBox();
this.labelSoundNotification = new System.Windows.Forms.Label();
this.panelSoundNotification = new System.Windows.Forms.Panel();
this.panelSoundNotification.SuspendLayout();
this.SuspendLayout();
//
// btnPlaySound
//
this.btnPlaySound.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnPlaySound.AutoSize = true;
this.btnPlaySound.Location = new System.Drawing.Point(203, 29);
this.btnPlaySound.Name = "btnPlaySound";
this.btnPlaySound.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnPlaySound.Size = new System.Drawing.Size(43, 23);
this.btnPlaySound.TabIndex = 2;
this.btnPlaySound.Text = "Play";
this.btnPlaySound.UseVisualStyleBackColor = true;
//
// btnResetSound
//
this.btnResetSound.AutoSize = true;
this.btnResetSound.Location = new System.Drawing.Point(3, 29);
this.btnResetSound.Name = "btnResetSound";
this.btnResetSound.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnResetSound.Size = new System.Drawing.Size(51, 23);
this.btnResetSound.TabIndex = 3;
this.btnResetSound.Text = "Reset";
this.btnResetSound.UseVisualStyleBackColor = true;
//
// btnBrowseSound
//
this.btnBrowseSound.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnBrowseSound.AutoSize = true;
this.btnBrowseSound.Location = new System.Drawing.Point(252, 29);
this.btnBrowseSound.Name = "btnBrowseSound";
this.btnBrowseSound.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnBrowseSound.Size = new System.Drawing.Size(67, 23);
this.btnBrowseSound.TabIndex = 1;
this.btnBrowseSound.Text = "Browse...";
this.btnBrowseSound.UseVisualStyleBackColor = true;
//
// tbCustomSound
//
this.tbCustomSound.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tbCustomSound.Location = new System.Drawing.Point(3, 3);
this.tbCustomSound.Name = "tbCustomSound";
this.tbCustomSound.Size = new System.Drawing.Size(316, 20);
this.tbCustomSound.TabIndex = 0;
this.toolTip.SetToolTip(this.tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
//
// labelSoundNotification
//
this.labelSoundNotification.AutoSize = true;
this.labelSoundNotification.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelSoundNotification.Location = new System.Drawing.Point(6, 8);
this.labelSoundNotification.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
this.labelSoundNotification.Name = "labelSoundNotification";
this.labelSoundNotification.Size = new System.Drawing.Size(198, 20);
this.labelSoundNotification.TabIndex = 1;
this.labelSoundNotification.Text = "Custom Sound Notification";
//
// panelSoundNotification
//
this.panelSoundNotification.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelSoundNotification.Controls.Add(this.btnPlaySound);
this.panelSoundNotification.Controls.Add(this.tbCustomSound);
this.panelSoundNotification.Controls.Add(this.btnResetSound);
this.panelSoundNotification.Controls.Add(this.btnBrowseSound);
this.panelSoundNotification.Location = new System.Drawing.Point(9, 31);
this.panelSoundNotification.Name = "panelSoundNotification";
this.panelSoundNotification.Size = new System.Drawing.Size(322, 56);
this.panelSoundNotification.TabIndex = 2;
//
// TabSettingsSounds
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelSoundNotification);
this.Controls.Add(this.labelSoundNotification);
this.Name = "TabSettingsSounds";
this.Size = new System.Drawing.Size(340, 97);
this.panelSoundNotification.ResumeLayout(false);
this.panelSoundNotification.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Button btnResetSound;
private System.Windows.Forms.Button btnBrowseSound;
private System.Windows.Forms.TextBox tbCustomSound;
private System.Windows.Forms.Button btnPlaySound;
private System.Windows.Forms.Label labelSoundNotification;
private System.Windows.Forms.Panel panelSoundNotification;
}
}

View File

@@ -1,67 +0,0 @@
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using TweetDuck.Core.Notification;
using TweetLib.Audio;
namespace TweetDuck.Core.Other.Settings{
partial class TabSettingsSounds : BaseTabSettings{
private readonly SoundNotification soundNotification;
public TabSettingsSounds(){
InitializeComponent();
soundNotification = new SoundNotification();
soundNotification.PlaybackError += sound_PlaybackError;
tbCustomSound.Text = Config.NotificationSoundPath;
tbCustomSound_TextChanged(tbCustomSound, new EventArgs());
Disposed += (sender, args) => soundNotification.Dispose();
}
public override void OnReady(){
tbCustomSound.TextChanged += tbCustomSound_TextChanged;
btnPlaySound.Click += btnPlaySound_Click;
btnBrowseSound.Click += btnBrowseSound_Click;
btnResetSound.Click += btnResetSound_Click;
}
public override void OnClosing(){
Config.NotificationSoundPath = tbCustomSound.Text;
}
private void tbCustomSound_TextChanged(object sender, EventArgs e){
bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text);
tbCustomSound.ForeColor = isEmpty || File.Exists(tbCustomSound.Text) ? SystemColors.WindowText : Color.Maroon;
btnPlaySound.Enabled = !isEmpty;
btnResetSound.Enabled = !isEmpty;
}
private void btnPlaySound_Click(object sender, EventArgs e){
soundNotification.Play(tbCustomSound.Text);
}
private void sound_PlaybackError(object sender, PlaybackErrorEventArgs e){
FormMessage.Error("Notification Sound Error", "Could not play custom notification sound.\n"+e.Message, FormMessage.OK);
}
private void btnBrowseSound_Click(object sender, EventArgs e){
using(OpenFileDialog dialog = new OpenFileDialog{
AutoUpgradeEnabled = true,
DereferenceLinks = true,
Title = "Custom Notification Sound",
Filter = "Sound file ("+soundNotification.SupportedFormats+")|"+soundNotification.SupportedFormats+"|All files (*.*)|*.*"
}){
if (dialog.ShowDialog() == DialogResult.OK){
tbCustomSound.Text = dialog.FileName;
}
}
}
private void btnResetSound_Click(object sender, EventArgs e){
tbCustomSound.Text = string.Empty;
}
}
}

View File

@@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Linq;
namespace TweetDuck.Core.Utils{
static class BrowserCache{
private static bool ClearOnExit { get; set; }
private static readonly string CacheFolder = Path.Combine(Program.StoragePath, "Cache");
private static IEnumerable<string> CacheFiles{
get{
return Directory.EnumerateFiles(CacheFolder);
}
}
public static void CalculateCacheSize(Action<long> callbackBytes){
Task<long> task = new Task<long>(() => {
return CacheFiles.Select(file => {
try{
return new FileInfo(file).Length;
}catch{
return 0L;
}
}).Sum();
});
task.ContinueWith(originalTask => callbackBytes(originalTask.Exception == null ? originalTask.Result : -1L), TaskContinuationOptions.ExecuteSynchronously);
task.Start();
}
public static void SetClearOnExit(){
ClearOnExit = true;
}
public static void Exit(){
if (ClearOnExit){
foreach(string file in CacheFiles){
try{
File.Delete(file);
}catch{
// welp, too bad
}
}
}
}
}
}

View File

@@ -1,26 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics;
using CefSharp;
namespace TweetDuck.Core.Utils{
static class BrowserProcesses{
private static readonly Dictionary<int, int> PIDs = new Dictionary<int, int>();
public static void Link(int identifier, int pid){
PIDs[identifier] = pid;
}
public static void Forget(int identifier){
PIDs.Remove(identifier);
}
public static Process FindProcess(IBrowser browser){
if (PIDs.TryGetValue(browser.Identifier, out int pid) && WindowsUtils.IsChildProcess(pid)){ // child process is checked in two places for safety
return Process.GetProcessById(pid);
}
else{
return null;
}
}
}
}

View File

@@ -1,116 +0,0 @@
using CefSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Windows.Forms;
using TweetDuck.Core.Other;
namespace TweetDuck.Core.Utils{
static class BrowserUtils{
public static string HeaderAcceptLanguage{
get{
string culture = Program.Culture.Name;
if (culture == "en"){
return "en-us,en";
}
else{
return culture.ToLower()+",en;q=0.9";
}
}
}
public static string HeaderUserAgent => Program.BrandName+" "+Application.ProductVersion;
public static void SetupCefArgs(IDictionary<string, string> args){
if (!Program.SystemConfig.HardwareAcceleration){
args["disable-gpu"] = "1";
args["disable-gpu-vsync"] = "1";
}
args["disable-extensions"] = "1";
args["disable-plugins-discovery"] = "1";
args["enable-system-flash"] = "0";
if (args.TryGetValue("js-flags", out string jsFlags)){
args["js-flags"] = "--expose-gc "+jsFlags;
}
else{
args["js-flags"] = "--expose-gc";
}
}
public static bool IsValidUrl(string url){
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
string scheme = uri.Scheme;
return scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto;
}
return false;
}
public static void OpenExternalBrowser(string url){
if (string.IsNullOrWhiteSpace(url))return;
if (IsValidUrl(url)){
OpenExternalBrowserUnsafe(url);
}
else{
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
}
}
public static void OpenExternalBrowserUnsafe(string url){
using(Process.Start(url)){}
}
public static string GetFileNameFromUrl(string url){
string file = Path.GetFileName(new Uri(url).AbsolutePath);
return string.IsNullOrEmpty(file) ? null : file;
}
public static string GetErrorName(CefErrorCode code){
return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
}
public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){
WebClient client = new WebClient{ Proxy = null };
client.Headers[HttpRequestHeader.UserAgent] = HeaderUserAgent;
client.DownloadFileCompleted += (sender, args) => {
if (args.Cancelled){
try{
File.Delete(target);
}catch{
// didn't want it deleted anyways
}
}
else if (args.Error != null){
onFailure?.Invoke(args.Error);
}
else{
onSuccess?.Invoke();
}
};
client.DownloadFileAsync(new Uri(url), target);
return client;
}
public static int Scale(int baseValue, double scaleFactor){
return (int)Math.Round(baseValue*scaleFactor);
}
public static void SetZoomLevel(IBrowser browser, int percentage){
browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2));
}
#if DEBUG
public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){
Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line);
}
#endif
}
}

View File

@@ -1,91 +0,0 @@
using System;
using System.Diagnostics;
using System.Timers;
using System.Windows.Forms;
using CefSharp;
using Timer = System.Timers.Timer;
namespace TweetDuck.Core.Utils{
sealed class MemoryUsageTracker : IDisposable{
private const int IntervalMemoryCheck = 60000*30; // 30 minutes
private const int IntervalCleanupAttempt = 60000*5; // 5 minutes
private readonly string script;
private readonly Timer timer;
private Form owner;
private IBrowser browser;
private long threshold;
private bool needsCleanup;
public MemoryUsageTracker(string cleanupFunctionName){
this.script = $"window.{cleanupFunctionName} && window.{cleanupFunctionName}()";
this.timer = new Timer{ Interval = IntervalMemoryCheck };
this.timer.Elapsed += timer_Elapsed;
}
public void Start(Form owner, IBrowser browser, int thresholdMB){
Stop();
this.owner = owner;
this.browser = browser;
this.threshold = thresholdMB*1024L*1024L;
this.timer.SynchronizingObject = owner;
this.timer.Start();
}
public void Stop(){
timer.Stop();
timer.SynchronizingObject = null;
owner = null;
browser = null;
SetNeedsCleanup(false);
}
public void Dispose(){
timer.SynchronizingObject = null;
timer.Dispose();
owner = null;
browser = null;
}
private void SetNeedsCleanup(bool value){
if (needsCleanup != value){
needsCleanup = value;
timer.Interval = value ? IntervalCleanupAttempt : IntervalMemoryCheck; // restarts timer
}
}
private void timer_Elapsed(object sender, ElapsedEventArgs e){
if (owner == null || browser == null){
return;
}
if (needsCleanup){
if (!owner.ContainsFocus){
using(IFrame frame = browser.MainFrame){
frame.EvaluateScriptAsync(script).ContinueWith(task => {
JavascriptResponse response = task.Result;
if (response.Success && (response.Result as bool? ?? false)){
SetNeedsCleanup(false);
}
});
}
}
}
else{
try{
using(Process process = BrowserProcesses.FindProcess(browser)){
if (process?.PrivateMemorySize64 > threshold){
SetNeedsCleanup(true);
}
}
}catch{
// ignore I guess?
}
}
}
}
}

View File

@@ -1,22 +0,0 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace TweetDuck.Core.Utils{
static class StringUtils{
public static readonly string[] EmptyArray = new string[0];
public static string ExtractBefore(string str, char search, int startIndex = 0){
int index = str.IndexOf(search, startIndex);
return index == -1 ? str : str.Substring(0, index);
}
public static int[] ParseInts(string str, char separator){
return str.Split(new char[]{ separator }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();
}
public static string ConvertPascalCaseToScreamingSnakeCase(string str){
return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpper();
}
}
}

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