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

Compare commits

...

247 Commits
1.4 ... 1.6.4

Author SHA1 Message Date
3a28556c7f Release 1.6.4 2017-03-08 21:33:39 +01:00
9ecc92b9a5 Fix emoji keyboard separators only working for the first case 2017-03-08 21:18:58 +01:00
ca023be98a Change default installation directory in portable installer 2017-03-08 21:16:36 +01:00
11a1423f76 Make sure the app is loaded before hooking account selectors 2017-03-08 13:06:50 +01:00
79f6df121b Swap shift key functionality in drawer and retweet account selectors 2017-03-08 13:01:48 +01:00
71eade7e86 Fix unsupported video tweaks for actual embedded video elements 2017-03-07 22:47:54 +01:00
5f81d29036 Finish basic emoji keyboard (enable/disable functionality, layout fix, screenshot pasting fix)
Closes #102
2017-03-07 20:32:30 +01:00
ec1cb5dc5f Final optimizations for emoji keyboard 2017-03-07 20:05:40 +01:00
fd969e2d55 Further cut down size of emoji-ordering.txt by wildcarding emojis with skin tones 2017-03-07 18:54:51 +01:00
37e33b77ff Cut down size of emoji-ordering.txt file 2017-03-07 18:36:07 +01:00
f7ed7703b4 Rewrite plugin cache to use tokens and local paths as multikeys 2017-03-07 18:31:58 +01:00
4bb35295ca Add a debug plugin to unit test plugin features 2017-03-07 18:11:13 +01:00
1e4f673f9e Add a TwoKeyDictionary collection with unit tests 2017-03-07 17:45:13 +01:00
7cadb1c403 Add an option (disabled by default) to revert New Tweet font size in design-revert plugin 2017-03-07 16:39:06 +01:00
37148f5093 Make design-revert plugin features configurable
Closes #107
2017-03-07 16:32:08 +01:00
f6bc26789f Rework emoji keyboard using official ordering, fix loading, add separators, tweak styles 2017-03-07 15:32:34 +01:00
b3f5a88525 Set red play button on unsupported videos instead of replacing them
Closes #104
2017-03-07 01:15:33 +01:00
1e538d2b28 Move sound notification code to a separate class 2017-03-05 14:27:47 +01:00
7d7bfb7b01 Refactor FormSettings to take initial tab index in constructor and remove public SelectTab 2017-03-05 14:27:35 +01:00
41d86ba440 Remove (hopefully) unnecessary user link target fix 2017-03-04 13:11:33 +01:00
3df474a8a5 Refactor ready state handling in code.js 2017-03-04 13:03:30 +01:00
a50d6e8f47 Disable resizing for the settings export dialog 2017-02-25 19:07:21 +01:00
6081e5b9c1 Add & use ControlExtensions.InvokeAsyncSafe for improved performance 2017-02-20 13:02:24 +01:00
66ccea920c Hide emoji keyboard on escape or click outside 2017-01-30 16:54:50 +01:00
470d63093f Add combined emoji to the emoji keyboard plugin 2017-01-30 16:21:09 +01:00
eae0507831 Add a WIP emoji keyboard plugin 2017-01-30 15:32:28 +01:00
92af85d3bb Release 1.6.3 2017-01-28 18:49:23 +01:00
7635af5730 Add an AppName suffix for portable and update installers 2017-01-28 18:49:14 +01:00
a838e89695 Fix custom sound notification textbox not setting color when the control is created 2017-01-28 18:12:30 +01:00
b22289a8b9 Work around Alt freezing the app since W10 Anniversary Update
Get fucked, Microsoft
2017-01-28 17:44:30 +01:00
45b3ff52c6 Tweak FormBrowser.ShowChildForm to use VisibleChanged instead of Shown event for reliability 2017-01-28 01:08:31 +01:00
4464991f4c Prevent automatic Settings tab selection from triggering autoclick in Notification tab 2017-01-28 01:07:57 +01:00
b0d2f77583 Merge pull request #101 from chylex/ipc
Replace WCF with native chromium IPC
2017-01-28 00:01:22 +01:00
b211a4405d Set CefSharpSettings.WcfEnabled to false 2017-01-27 23:59:01 +01:00
8823016d2c Make custom sound notification textbox font red when the file doesn't exist 2017-01-27 23:56:51 +01:00
859fdc7ec1 Rewrite custom sound notification to show an error message on failure instead of hiding it 2017-01-27 23:56:00 +01:00
028d5ed01f Improve debug script for easier extendibility, add sound notification simulation 2017-01-27 21:48:57 +01:00
5fd5a2a436 Use and test RegisterAsyncJsObject in FormBrowser 2017-01-27 18:51:14 +01:00
79a7e7470c Use and test RegisterAsyncJsObject in FormNotification 2017-01-27 17:00:09 +01:00
9ecef78aed Fix DismissedUpdate not being set after toggling updates 2017-01-27 16:21:36 +01:00
65a837a6e1 Move TweetDeckBridge properties to a separate JS object 2017-01-27 16:13:17 +01:00
6e4db4acea Rewrite custom CSS injection and automatically inject it while typing 2017-01-26 15:35:40 +01:00
26fb977d05 Remove unnecessary properties from TweetDeckBridge 2017-01-26 06:51:51 +01:00
b42cd1c048 Tweak screenshot notification script (minor edit) 2017-01-26 06:46:19 +01:00
467f7cd12f Rewrite update system to use RegisterAsyncJsObject 2017-01-26 06:41:20 +01:00
66699ce9df Change update progress form to show kB instead of MB 2017-01-26 06:39:46 +01:00
cf7d903932 Move updater event args to a separate namespace 2017-01-26 04:09:04 +01:00
a7ab67925c Allow moving the notification window when holding Alt in debug builds 2017-01-23 01:13:15 +01:00
a474ba4260 Fix incorrect cursor when hovering over quoted tweet in notification
Closes #97
2017-01-23 01:00:16 +01:00
09e5636e86 Remove unused 'using' statement 2017-01-23 00:59:19 +01:00
2295a875be Fix 'Copy' context menu item (separator in wrong place in browser, missing in notification) 2017-01-23 00:53:15 +01:00
82a2455afc Release 1.6.2 2017-01-23 00:33:06 +01:00
268de676ee Add NativeMethods.GetIdleSeconds for idle time detection 2017-01-22 16:00:54 +01:00
8fe26c07f1 Preserve plaintext when stripping HTML styles from clipboard text 2017-01-17 18:29:09 +01:00
da3921b1ca Add safeguards for clipboard update methods
Closes #91
2017-01-17 18:19:39 +01:00
4dd2e787d1 Remove unnecessary IsSystemSupported check in UpdateHandler 2017-01-17 02:48:17 +01:00
ce005ae6c2 Add unit tests for CombinedFileStream 2017-01-17 02:33:47 +01:00
1513f46a11 Add a safety net to CombinedFileStream.Entry.WriteToFile with createDirectory 2017-01-17 02:27:03 +01:00
7543eeb0f4 Add more methods to TestUtils and fix cleanup code not running 2017-01-17 01:38:55 +01:00
873242120c Add a TestUtils class for easy file manipulation and cleanup in unit tests 2017-01-17 01:20:12 +01:00
98f8095a65 Add unit tests for CommandLineArgsParser 2017-01-16 22:46:01 +01:00
785571a550 Add unit tests for BrowserUtils and CommandLineArgs 2017-01-16 22:06:04 +01:00
0c4bd4044e Add ReSharper code coverage settings and cleanup the test project 2017-01-16 20:56:36 +01:00
0319543dce Add a unit test project 2017-01-16 19:36:29 +01:00
82d70b2d7f Stealthfix a bug with CommandLineArgs.ToString causing an exception if there are no args 2017-01-10 21:58:17 +01:00
62d18e010a Release 1.6.1 2017-01-10 21:24:54 +01:00
fc77b85083 Remove HTML styles after copying selected text to clipboard 2017-01-08 16:36:49 +01:00
50a8893f4f Add an option to disable screenshot window border 2017-01-08 02:47:47 +01:00
9252b3040e Fix screenshot functionality broken by previous refactoring 2017-01-08 02:26:09 +01:00
d5141ed020 Redo OnNotificationReady call to use LoadingStateChanged with a delay 2017-01-08 02:16:40 +01:00
7ff9e23283 Remove legacy notification loading option 2017-01-08 01:33:48 +01:00
89854d527c Fix notification position config after changing namespace and remove TweetNotification.Duration 2017-01-03 18:43:36 +01:00
6ff0cad2a8 Pre-release 1.6 2017-01-03 17:45:52 +01:00
349cfbd2d5 Set FormNotification.Visible to false if the form is out of view 2017-01-03 17:08:15 +01:00
40303ef74a Move FormBrowser.CreateNotificationForm next to other notification related methods 2017-01-03 00:34:45 +01:00
6c652122c2 Replace FormBrowser notification property with methods and use pause in TweetScreenshotManager 2017-01-03 00:30:36 +01:00
3658e3a2aa Update CEF version in update installer script 2017-01-02 22:24:11 +01:00
2b20fcfcd1 Pause notifications while the Settings window is open 2017-01-02 20:17:53 +01:00
554d427fef Improve notification muting by pausing instead of clearing it 2017-01-02 20:17:35 +01:00
7cf5b23306 Add debug.js and implement notification simulation in it 2017-01-02 18:29:28 +01:00
b26a6098eb Add a HORRIBLE HACK to unfocus example notification in Settings 2017-01-02 15:44:18 +01:00
7ad927bdaf Use CreateSingleTickTimer in TweetScreenshotManager 2017-01-02 04:29:45 +01:00
4ed30b3619 Add WindowsUtils.CreateSingleTickTimer 2017-01-02 04:28:57 +01:00
edfa9264d5 Add a timeout to TweetScreenshotManager in case of failure 2017-01-01 23:36:18 +01:00
f7516b593f Add JavaScript dialog handler that uses FormMessage for alerts and confirmations 2017-01-01 21:38:46 +01:00
83ff998f9d Rename DialogHandlerBrowser to FileDialogHandler 2017-01-01 21:34:24 +01:00
47381e0df4 Fix alignment of FormMessage text with no message icon 2017-01-01 21:30:45 +01:00
ba62d57485 Fix invalid context menu items due to bridge properties not being cleared after reload 2017-01-01 00:34:52 +01:00
c014c4bc24 Refactor notifications (move namespaces, move screenshot methods to a separate class) 2016-12-29 02:50:16 +01:00
5d1a3fede2 Fix the export/import button not getting disabled if no option is selected 2016-12-29 00:47:05 +01:00
53b584fe45 Add a button to open wiki to CSS dialog 2016-12-29 00:32:46 +01:00
f53d974400 Add tooltip to export/import dialog and uncheck session export by default 2016-12-29 00:18:03 +01:00
c4b4ef19cd Add profile import file flag detection 2016-12-29 00:01:55 +01:00
3bfc360362 Add a SkipFile method to CombinedFileStream to skip through key names 2016-12-29 00:00:36 +01:00
584f16d375 Fix export dialog design and event handling 2016-12-28 23:59:41 +01:00
b3d84c3217 Update clear-columns plugin version 2016-12-28 23:00:36 +01:00
dd14ad470e Add WIP export/import selection dialog 2016-12-28 21:16:53 +01:00
85b90574b8 Fix visual issues with screenshots (reply avatars, media margins, poll cleanup) 2016-12-25 13:52:34 +01:00
ee5d172468 Fix timeline-polls plugin adding extra margin to tweets without polls 2016-12-25 13:51:15 +01:00
7ca4b94361 Cleanup unnecessary TODO in code.js 2016-12-25 13:06:27 +01:00
31f1546483 Make TweetNotification properties constants instead 2016-12-25 13:02:56 +01:00
d8a88a19af Separate notification CSS fixes (fixes badge and urls in screenshots) 2016-12-25 12:58:02 +01:00
12af79de05 Refactor tweet screenshot code to a separate class and work around window disposal issues 2016-12-24 20:35:13 +01:00
2260dd419d Update tweet screenshot to work with detail view, tweak the bottom padding 2016-12-24 17:04:56 +01:00
61a940cc82 Fix highlighted tweet context menu for tweet detail view 2016-12-24 16:57:30 +01:00
1bbc1e0d7e Include script files in the project to make Visual Studio detect changes 2016-12-24 16:35:47 +01:00
921294eeb3 Add support for custom wav notification sounds
Closes #3
2016-12-24 15:59:29 +01:00
baaa90f49d Remove widevine drm plugin 2016-12-24 12:05:19 +01:00
4e25381770 Fix retweeted retweet icon color and size mismatch 2016-12-24 01:52:01 +01:00
272877d0ed Add Open Data Folder button and fix log tooltip in Advanced Settings tab 2016-12-24 00:53:14 +01:00
555b947bf7 Add DM participants column to back mouse button handling 2016-12-24 00:48:28 +01:00
da29811b16 Fix td-hover class missing after clicking the Skip button and not moving the cursor 2016-12-24 00:44:34 +01:00
241f67fd4d Add column reset functionality when holding Shift to code.js 2016-12-23 23:52:33 +01:00
eb4ce18e31 Refactor -32000 location to a static Point object 2016-12-23 23:27:37 +01:00
ae99fee440 Fix clear-columns button style 2016-12-23 16:23:26 +01:00
d116ac5e56 Reorder notification context menu to place relevant items on top
Closes #81
2016-12-23 16:15:43 +01:00
28db1f4253 Add NotificationFlags.TopMost and disable it for Settings form 2016-12-23 16:00:59 +01:00
034312e676 Add dev tools to context menus when debugging 2016-12-23 15:43:11 +01:00
da83d73ba6 Merge pull request #85 from chylex/screenshot
Add tweet screenshot functionality & update CEF
2016-12-23 15:31:15 +01:00
02e8dc3440 Fix screenshot script modifying original elements and missing html classes 2016-12-23 15:29:33 +01:00
cac6d1f889 Swap notification windows when taking a screenshot, and make screenshot window unmovable 2016-12-23 15:13:34 +01:00
68fa3294d4 Update CefSharp version in README 2016-12-23 14:56:16 +01:00
9b983de8c9 Ensure notification window visibility when taking screenshots, refactor MoveToVisibleLocation 2016-12-23 14:52:48 +01:00
3a37ee719b Fix csproj file after previous refactor commit 2016-12-23 14:29:23 +01:00
61359c2faa Refactor NotificationFlags and inner screenshot bridge class to a separate namespace 2016-12-23 14:26:59 +01:00
7f7c5ab35b Update CefSharp to 53.0.1 2016-12-23 14:07:47 +01:00
a1b483d20a Implement printscreen simulation and a timeout to screenshot notification 2016-12-23 13:40:30 +01:00
04cd662d78 Release 1.5.1 2016-11-23 05:03:49 +01:00
da597f076f Fix quote escaping in updater arguments 2016-11-23 04:57:13 +01:00
fab3efdcf5 Fix update checker running outside of TweetDeck website 2016-11-23 04:55:58 +01:00
a55509a34d Add an old TweetDeck profile detection & warning message to the full installer 2016-11-23 03:51:58 +01:00
84fb1c5b2b Make update installer use TweetDuck's initial command line arguments 2016-11-23 03:20:38 +01:00
391a90e1df Add a -debugupdates command line argument to allow prereleases in update checker 2016-11-23 02:08:33 +01:00
e0fe39195d Add HasValue method to CommandLineArgs 2016-11-23 02:06:41 +01:00
385fead81a Fix updater calling onUpdateCheckFinished when eventID parameter is undefined 2016-11-23 02:05:44 +01:00
648d1b9aa9 Rewrite lock system to be more reliable and handle exceptions better 2016-11-19 05:57:55 +01:00
3f0028913d Move unhandled exception handler from Program to Reporter class 2016-11-19 03:11:37 +01:00
45e6ec8b0f Fix FormMessage fonts 2016-11-18 20:28:00 +01:00
a3fbaa0b34 Make program restarts as reliable as possible
Closes #80
2016-11-18 19:59:21 +01:00
7102cbfb3b Add a retry button to the warning message when TweetDuck takes too long to restart 2016-11-18 19:35:02 +01:00
c3db3ce0f2 Push WIP tweet screenshot functionality 2016-11-16 18:39:30 +01:00
7a1e7637ff Add a parameter to toggle custom CSS in TweetNotification.GenerateHtml 2016-11-16 18:33:30 +01:00
04a78a02d3 Add NotificationFlags for easier configuration of the notification window 2016-11-16 18:33:01 +01:00
cb61dc742f Push minor tweak in ExecuteScriptAsync in image pasting code 2016-11-16 18:29:35 +01:00
cd53f6e757 Disable 'Show Error Log' button if the logging failed 2016-11-16 14:51:47 +01:00
c64f7daa8d Cleanup browser subprocess path code 2016-11-16 14:25:52 +01:00
e70d792654 Fix plugin status not updating from new config after importing profile
Closes #79
2016-11-16 04:10:17 +01:00
9ae533f907 Remove TweetDick config file compatibility 2016-11-15 19:26:23 +01:00
cfe92f18e3 Remove all TweetDeck and other migration code 2016-11-15 19:20:43 +01:00
e2a34ea28e Remove original CheckFolderPermission and replace it with the lazy workaround 2016-11-15 18:10:25 +01:00
ec8000360e Windows file permissions can go to hell 2016-11-15 01:01:41 +01:00
57b0821e19 Revert "Rewrite folder write permission check to hopefully make it more reliable"
This reverts commit 1f9db3bda6.
2016-11-15 00:47:15 +01:00
09d39df15a Release 1.5 2016-11-15 00:19:24 +01:00
1f9db3bda6 Rewrite folder write permission check to hopefully make it more reliable 2016-11-14 23:32:45 +01:00
b7104c8828 Remove privilege requirement from update & portable installer, handle updater privileges within TweetDuck 2016-11-14 20:53:56 +01:00
5da02b4092 Make the portable installer fully autonomous 2016-11-14 20:52:11 +01:00
802f1e3042 Refactor Process.Start uses (missing using statement, use WindowsUtils for elevation) 2016-11-14 19:39:26 +01:00
66db0df45a Add WindowsUtils.StartProcess for easier elevated process starting 2016-11-14 19:38:36 +01:00
650c2e2eb7 Remove redundant null check from WindowsUtils 2016-11-14 18:54:58 +01:00
6ab3754129 Update write permission check to use the storage folder 2016-11-14 14:06:15 +01:00
7dca0a8cab Add plugin config migration to the new data folder 2016-11-14 14:01:22 +01:00
7cd0b4ad54 Fix highlighted tweets staying in context menu after logging out of TweetDeck 2016-11-14 10:35:58 +01:00
97acb41eee Fix console errors caused by running browser scripts even outside of TweetDeck website 2016-11-14 10:35:42 +01:00
b916b9726e Add a method to check if a frame has a TweetDeck URL to BrowserUtils 2016-11-14 10:34:52 +01:00
32d3990ace Rewrite plugin data export and combined file stream identifiers, add missing plugin warning 2016-11-14 10:15:21 +01:00
cb1fd109cc Prevent missing plugin folders from causing a crash 2016-11-14 10:12:22 +01:00
0e68dd6185 Fix Configure button in PluginControl causing issues with mixed slash types 2016-11-14 09:47:56 +01:00
fb6502bc65 Rename plugin data folder to TD_Plugins for consistency 2016-11-14 09:39:48 +01:00
c7e7403781 Update plugin config to use the data folder instead of plugin root 2016-11-14 06:14:38 +01:00
bf224408a3 Rewrite PluginBridge to accommodate the functions to the new plugin folder handling 2016-11-14 06:14:14 +01:00
84b7078873 Assign a data folder to each plugin and add new folder handling functions 2016-11-14 06:09:05 +01:00
89e5943d8f Add a PluginFolder enum and a plugin data root path to Program 2016-11-14 05:43:30 +01:00
b78c4cb8f0 Move PluginEnvironment and PluginGroup to a separate Enums package 2016-11-14 05:08:18 +01:00
976ba074a8 Add a warning for outdated config in reply-account plugin 2016-11-13 15:18:10 +01:00
5205d59b96 Rewrite custom selector in reply-account plugin to fix and futureproof TweetDeck changes 2016-11-13 15:06:51 +01:00
e8394b9c08 Add browser console logging to debug output 2016-11-13 13:45:10 +01:00
9cd00239f9 Fix update installer creating Start Menu entry in portable mode (applied to 1.4.3) 2016-10-23 00:48:23 +02:00
b6b26142f8 Release 1.4.3 2016-10-22 22:13:15 +02:00
4ee99376fd Add a portable installer that uses the full installer with a custom flag 2016-10-22 21:49:31 +02:00
b0261342ff Add portable functionality to update installer 2016-10-22 21:11:48 +02:00
87fd2a521e Fix quoted tweet link in notification window not resetting
Closes #75
2016-10-21 06:50:34 +02:00
334793c6f6 Add full installer error/abort handling to the update installer
- Will abort and cleanup if full installer fails
- Fixed uninstallation files getting deleted if the full installer could
not be started
2016-10-20 18:48:35 +02:00
3e70d991bb Fix installer unable to run TD when TweetDuck.exe requires admin privileges 2016-10-20 18:45:10 +02:00
feec96fc5c Pass installation path to the updater to allow portability
Closes #77
2016-10-20 18:23:48 +02:00
765984709e Make sure TweetDeck uninstaller runs elevated, add safety nets and shield icons to buttons 2016-10-20 04:23:48 +02:00
c7c9931f68 Add an extension method to add UAC shield to a button 2016-10-18 16:21:08 +02:00
ae64573510 Remove old debug.log and ChromeDWriteFontCache on update install 2016-10-18 16:05:34 +02:00
d675af5aa4 Rename the debug log again to TD_Console.txt for consistency 2016-10-18 16:04:31 +02:00
9480d17cfc Change CEF debug file to jsconsole.log in storage path 2016-10-18 15:55:25 +02:00
5ac1df2283 Fix LockManager not finding correct process in debug 2016-10-18 15:48:55 +02:00
20119db883 Release 1.4.2 2016-10-09 16:04:38 +02:00
a4006deb8c Rewrite extra mouse button handling and fix skipping 'Back to Tweet'
Closes #74
2016-10-09 15:49:08 +02:00
25fa3cefab Fix notification tweet footer displaying in some tweets (after removing it in a prev commit) 2016-10-09 15:13:01 +02:00
bb5161eb34 Fix notifications only displaying the last one when multiple were enqueued at the same time 2016-10-09 14:58:11 +02:00
1bfc403a98 Fix typos in installer script comments 2016-10-09 14:42:13 +02:00
720d10e543 Fix update installer version cleanup issue and move idpDownloadAfter to InitializeWizard 2016-10-09 13:33:38 +02:00
30c117672e Make full installer not automatically run TweetDuck when in silent mode 2016-10-09 13:32:28 +02:00
6d1b5c77d1 Fix program arguments for the full installer execution in update installer 2016-10-09 00:58:13 +02:00
d1cbf608e0 Add WIP full package download to update installation file if CEF needs updating 2016-10-09 00:54:37 +02:00
7e3014c52d Refactor installation files (move .NET Framework check to a function) 2016-10-09 00:53:59 +02:00
82beb1f5a7 Fix context menu state changing when moving mouse quickly
Closes #70
2016-10-08 17:43:55 +02:00
657dc81300 Include ISS installer scripts and resources 2016-10-08 17:36:20 +02:00
8e22192dd3 Update gitignore to include some files from 'bld' folder 2016-10-08 17:35:28 +02:00
dc0b7d58e3 Add an Open Program Folder button to Settings - Advanced 2016-10-08 16:20:52 +02:00
6919e5bdb0 Fix hardware acceleration only being partial 2016-10-08 16:13:50 +02:00
9728a62efa Remove unnecessary code from notification html builder 2016-09-30 23:56:27 +02:00
276e070759 Fix recent TweetDeck change breaking media in notifications 2016-09-30 23:43:46 +02:00
fadea54f8d Remove legacy update notification warnings 2016-09-30 15:12:18 +02:00
523d340ade Remove the disabled() event handler in timeline-polls plugin 2016-09-30 15:09:52 +02:00
96fa7efb66 Fix a crash in reply-account plugin on Popout 2016-09-27 21:30:34 +02:00
03591f8317 Release 1.4.1 2016-09-27 18:25:58 +02:00
28cc60d007 Tweak CommandLineArgsParser to slightly improve reliability with quoted strings 2016-09-27 18:11:50 +02:00
1fa69bdb3b Add invalid data folder error reporting and env variable expansion for absolute paths 2016-09-27 18:00:05 +02:00
11f5f9b72e Add Reporter.HandleEarlyFailure for non-recoverable errors before Reporter is setup 2016-09-27 17:58:13 +02:00
61f6543041 Change Source Code link to Tips & Tricks 2016-09-27 17:37:19 +02:00
0c9ab32f37 Add additional functionality to the back mouse button (inline and drawer composer, fix order) 2016-09-27 04:16:50 +02:00
fde984d02b Add a hover class to Notification body element and only display skip button when over 2016-09-27 03:48:14 +02:00
f23db31306 Increase default size of Edit CSS dialog 2016-09-26 17:20:25 +02:00
8dce99b8b3 Add support for ctrl+a shortcut in multiline textboxes 2016-09-26 17:20:10 +02:00
342ac51cda Tweak the skip button position in Notification window 2016-09-26 17:14:17 +02:00
ba31f7ae01 Update TweetDuck website to https 2016-09-26 16:31:06 +02:00
8d0fa030f8 Remove d3dcompiler_43.dll that used to be required for Win XP 2016-09-26 16:16:20 +02:00
d030a79c81 Fix Release build compliation error 2016-09-26 16:10:21 +02:00
6690efc4d9 Address code analysis issues 2016-09-26 15:53:08 +02:00
afa8098463 Add two restart buttons to Settings - Advanced 2016-09-26 14:28:14 +02:00
c064e579d2 Add Program.Restart and fix restarting causing loss of initial program arguments 2016-09-26 14:05:44 +02:00
01dc4e4714 Update CommandLineArgsParser to use CommandLineArgs 2016-09-26 13:58:59 +02:00
6fbc246b12 Use CommandLineArgs in Program and fix broken -locale parameter 2016-09-26 13:56:13 +02:00
1efe2a02f7 Add a CommandLineArgs class for easy arg management 2016-09-26 13:52:55 +02:00
ab2ab06f60 Reorganize utility classes and methods in TweetDeck Migration code 2016-09-26 04:19:50 +02:00
a71d889682 Replace FormMigrationQuestion with a custom FormMessage 2016-09-25 23:10:18 +02:00
2252d85b27 Add MessageBoxIcon.Question support to FormMessage 2016-09-25 23:09:36 +02:00
3f09100177 Update version of the design-revert plugin 2016-09-25 19:01:39 +02:00
6b79c89f42 Add a button to skip current notification 2016-09-25 16:53:00 +02:00
5f249d4603 Add a <body> td-example-notification attribute for example notifications 2016-09-25 16:52:22 +02:00
fa64309909 Hide the old TweetDeck column Clear button when clear-columns plugin is enabled 2016-09-25 15:35:15 +02:00
c46aafdab6 Add column resetting to clear-columns plugin 2016-09-25 15:31:18 +02:00
3e9c397494 Fix inconsistency with loadConfigurationFile's onFailure parameter 2016-09-25 03:21:44 +02:00
47935165db Update current plugins to use the new CSS functionality 2016-09-25 02:37:16 +02:00
be77f753e7 Add a global function to easily add custom styles in plugins 2016-09-25 02:34:19 +02:00
2c30613279 Fix reply-account plugin for docked replies 2016-09-23 22:09:06 +02:00
d83eaec987 Update reply-account plugin version 2016-09-23 21:14:53 +02:00
e6f199a224 Implement #last in reply-account and fix bugs in its code 2016-09-23 21:13:54 +02:00
6636a2aa9e Add an experimental timeline-polls plugin 2016-09-23 19:49:56 +02:00
221bdc58fe Tweak support for old configs in reply-account plugin 2016-09-23 16:05:53 +02:00
e48a068e9d Redesign reply-account plugin configuration and add WIP last account setting 2016-09-23 15:55:13 +02:00
3371c31e63 Release 1.4 2016-09-22 00:58:27 +02:00
113 changed files with 6250 additions and 1530 deletions

6
.gitignore vendored
View File

@@ -17,9 +17,13 @@
[Rr]eleases/ [Rr]eleases/
x64/ x64/
x86/ x86/
bld/
[Bb]in/ [Bb]in/
[Oo]bj/ [Oo]bj/
bld/*
!bld/gen_full.iss
!bld/gen_port.iss
!bld/gen_upd.iss
!bld/Resources
# Visual Studio 2015 cache/options directory # Visual Studio 2015 cache/options directory
.vs/ .vs/

View File

@@ -5,6 +5,10 @@ using System.Threading;
namespace TweetDck.Configuration{ namespace TweetDck.Configuration{
sealed class LockManager{ sealed class LockManager{
public enum Result{
Success, HasProcess, Fail
}
public Process LockingProcess { get; private set; } public Process LockingProcess { get; private set; }
private readonly string file; private readonly string file;
@@ -14,87 +18,97 @@ namespace TweetDck.Configuration{
this.file = file; this.file = file;
} }
private bool CreateLockFile(){ private void CreateLockFileStream(){
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
WriteIntToStream(lockStream, GetCurrentProcessId());
lockStream.Flush(true);
}
private bool ReleaseLockFileStream(){
if (lockStream != null){
lockStream.Dispose();
lockStream = null;
return true;
}
else{
return false;
}
}
private Result TryCreateLockFile(){
if (lockStream != null){ if (lockStream != null){
throw new InvalidOperationException("Lock file already exists."); throw new InvalidOperationException("Lock file already exists.");
} }
try{ try{
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read); CreateLockFileStream();
return Result.Success;
byte[] id = BitConverter.GetBytes(Process.GetCurrentProcess().Id); }catch(DirectoryNotFoundException){
lockStream.Write(id, 0, id.Length);
lockStream.Flush();
if (LockingProcess != null){
LockingProcess.Close();
LockingProcess = null;
}
return true;
}catch(Exception){
if (lockStream != null){
lockStream.Close();
lockStream.Dispose();
}
return false;
}
}
public bool Lock(){
if (lockStream != null)return true;
try{ try{
byte[] bytes = new byte[4]; CreateLockFileStream();
return Result.Success;
}catch{
ReleaseLockFileStream();
return Result.Fail;
}
}catch(IOException){
return Result.HasProcess;
}catch{
ReleaseLockFileStream();
return Result.Fail;
}
}
public Result Lock(){
if (lockStream != null){
return Result.Success;
}
Result initialResult = TryCreateLockFile();
if (initialResult == Result.HasProcess){
try{
int pid;
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){ using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
fileStream.Read(bytes, 0, 4); pid = ReadIntFromStream(fileStream);
} }
int pid = BitConverter.ToInt32(bytes, 0);
try{ try{
Process foundProcess = Process.GetProcessById(pid); Process foundProcess = Process.GetProcessById(pid);
using(Process currentProcess = Process.GetCurrentProcess()){ using(Process currentProcess = Process.GetCurrentProcess()){
if (foundProcess.ProcessName == currentProcess.ProcessName){ if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
LockingProcess = foundProcess; LockingProcess = foundProcess;
} }
else{
foundProcess.Close();
} }
}catch(ArgumentException){}
return LockingProcess == null && CreateLockFile();
}catch(DirectoryNotFoundException){
string dir = Path.GetDirectoryName(file);
if (dir != null){
Directory.CreateDirectory(dir);
return CreateLockFile();
} }
}catch(FileNotFoundException){ }catch{
return CreateLockFile(); // GetProcessById throws ArgumentException if the process is missing
}catch(Exception){ // Process.MainModule can throw exceptions in some cases
return false;
} }
return false; return LockingProcess == null ? Result.Fail : Result.HasProcess;
}catch{
return Result.Fail;
}
}
return initialResult;
} }
public bool Unlock(){ public bool Unlock(){
bool result = true; bool result = true;
if (lockStream != null){ if (ReleaseLockFileStream()){
lockStream.Dispose();
try{ try{
File.Delete(file); File.Delete(file);
}catch(Exception e){ }catch(Exception e){
Program.Reporter.Log(e.ToString()); Program.Reporter.Log(e.ToString());
result = false; result = false;
} }
lockStream = null;
} }
return result; return result;
@@ -104,11 +118,9 @@ namespace TweetDck.Configuration{
if (LockingProcess != null){ if (LockingProcess != null){
LockingProcess.CloseMainWindow(); LockingProcess.CloseMainWindow();
for(int waited = 0; waited < timeout && !LockingProcess.HasExited;){ for(int waited = 0; waited < timeout && !LockingProcess.HasExited; waited += 250){
LockingProcess.Refresh(); LockingProcess.Refresh();
Thread.Sleep(250);
Thread.Sleep(100);
waited += 100;
} }
if (LockingProcess.HasExited){ if (LockingProcess.HasExited){
@@ -120,5 +132,24 @@ namespace TweetDck.Configuration{
return false; return false;
} }
// Utility functions
private static void WriteIntToStream(Stream stream, int value){
byte[] id = BitConverter.GetBytes(value);
stream.Write(id, 0, id.Length);
}
private static int ReadIntFromStream(Stream stream){
byte[] bytes = new byte[4];
stream.Read(bytes, 0, 4);
return BitConverter.ToInt32(bytes, 0);
}
private static int GetCurrentProcessId(){
using(Process process = Process.GetCurrentProcess()){
return process.Id;
}
}
} }
} }

View File

@@ -1,42 +1,36 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Globalization;
using System.IO; using System.IO;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization.Formatters.Binary;
using TweetDck.Core; using TweetDck.Core;
using TweetDck.Core.Handling; using TweetDck.Core.Controls;
using TweetDck.Core.Notification;
using TweetDck.Core.Utils; using TweetDck.Core.Utils;
using TweetDck.Plugins; using TweetDck.Plugins;
namespace TweetDck.Configuration{ namespace TweetDck.Configuration{
[Serializable] [Serializable]
sealed class UserConfig{ sealed class UserConfig{
private static readonly IFormatter Formatter = new BinaryFormatter{ private static readonly IFormatter Formatter = new BinaryFormatter{ Binder = new CustomBinder() };
Binder = new SerializationCompatibilityHandler()
};
private const int CurrentFileVersion = 5; private const int CurrentFileVersion = 6;
// START OF CONFIGURATION // START OF CONFIGURATION
public bool IgnoreMigration { get; set; }
public bool IgnoreUninstallCheck { get; set; }
public WindowState BrowserWindow { get; set; } public WindowState BrowserWindow { get; set; }
public bool DisplayNotificationTimer { get; set; } public bool DisplayNotificationTimer { get; set; }
public bool NotificationTimerCountDown { get; set; } public bool NotificationTimerCountDown { get; set; }
public TweetNotification.Duration NotificationDuration { get; set; }
public TweetNotification.Position NotificationPosition { get; set; } public TweetNotification.Position NotificationPosition { get; set; }
public Point CustomNotificationPosition { get; set; } public Point CustomNotificationPosition { get; set; }
public int NotificationEdgeDistance { get; set; } public int NotificationEdgeDistance { get; set; }
public int NotificationDisplay { get; set; } public int NotificationDisplay { get; set; }
public int NotificationDurationValue { get; set; } public int NotificationDurationValue { get; set; }
public bool NotificationLegacyLoad { get; set; }
public bool EnableSpellCheck { get; set; } public bool EnableSpellCheck { get; set; }
public bool ExpandLinksOnHover { get; set; } public bool ExpandLinksOnHover { get; set; }
public bool ShowScreenshotBorder { get; set; }
public bool EnableTrayHighlight { get; set; } public bool EnableTrayHighlight { get; set; }
public bool EnableUpdateCheck { get; set; } public bool EnableUpdateCheck { get; set; }
@@ -51,7 +45,7 @@ namespace TweetDck.Configuration{
public bool IsCustomNotificationPositionSet{ public bool IsCustomNotificationPositionSet{
get{ get{
return CustomNotificationPosition.X != -32000 && CustomNotificationPosition.X != 32000; return CustomNotificationPosition != ControlExtensions.InvisibleLocation;
} }
} }
@@ -71,6 +65,16 @@ namespace TweetDck.Configuration{
} }
} }
public string NotificationSoundPath{
get{
return string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath;
}
set{
notificationSoundPath = value;
}
}
public TrayIcon.Behavior TrayBehavior{ public TrayIcon.Behavior TrayBehavior{
get{ get{
return trayBehavior; return trayBehavior;
@@ -100,6 +104,7 @@ namespace TweetDck.Configuration{
private int fileVersion; private int fileVersion;
private bool muteNotifications; private bool muteNotifications;
private string notificationSoundPath;
private TrayIcon.Behavior trayBehavior; private TrayIcon.Behavior trayBehavior;
private UserConfig(string file){ private UserConfig(string file){
@@ -107,13 +112,13 @@ namespace TweetDck.Configuration{
BrowserWindow = new WindowState(); BrowserWindow = new WindowState();
DisplayNotificationTimer = true; DisplayNotificationTimer = true;
NotificationDuration = TweetNotification.Duration.Medium;
NotificationPosition = TweetNotification.Position.TopRight; NotificationPosition = TweetNotification.Position.TopRight;
CustomNotificationPosition = new Point(-32000, -32000); CustomNotificationPosition = ControlExtensions.InvisibleLocation;
NotificationEdgeDistance = 8; NotificationEdgeDistance = 8;
NotificationDurationValue = 25; NotificationDurationValue = 25;
EnableUpdateCheck = true; EnableUpdateCheck = true;
ExpandLinksOnHover = true; ExpandLinksOnHover = true;
ShowScreenshotBorder = true;
EnableTrayHighlight = true; EnableTrayHighlight = true;
Plugins = new PluginConfig(); Plugins = new PluginConfig();
PluginsWindow = new WindowState(); PluginsWindow = new WindowState();
@@ -148,14 +153,7 @@ namespace TweetDck.Configuration{
if (fileVersion == 3){ if (fileVersion == 3){
EnableTrayHighlight = true; EnableTrayHighlight = true;
NotificationDurationValue = 25;
switch(NotificationDuration){
case TweetNotification.Duration.Short: NotificationDurationValue = 15; break;
case TweetNotification.Duration.Medium: NotificationDurationValue = 25; break;
case TweetNotification.Duration.Long: NotificationDurationValue = 35; break;
case TweetNotification.Duration.VeryLong: NotificationDurationValue = 45; break;
}
++fileVersion; ++fileVersion;
} }
@@ -165,6 +163,11 @@ namespace TweetDck.Configuration{
++fileVersion; ++fileVersion;
} }
if (fileVersion == 5){
ShowScreenshotBorder = true;
++fileVersion;
}
// update the version // update the version
fileVersion = CurrentFileVersion; fileVersion = CurrentFileVersion;
Save(); Save();
@@ -236,11 +239,13 @@ namespace TweetDck.Configuration{
return file+".bak"; return file+".bak";
} }
private class SerializationCompatibilityHandler : SerializationBinder{ private sealed class CustomBinder : SerializationBinder{
public override Type BindToType(string assemblyName, string typeName){ public override Type BindToType(string assemblyName, string typeName){
assemblyName = assemblyName.Replace("TweetDick", "TweetDuck"); if (typeName == "TweetDck.Core.Handling.TweetNotification+Position"){
typeName = typeName.Replace("TweetDick", "TweetDck"); return typeof(TweetNotification.Position);
return Type.GetType(string.Format(CultureInfo.CurrentCulture, "{0}, {1}", typeName, assemblyName)); }
return null;
} }
} }
} }

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <packages xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<package id="cef.redist.x64" version="3.2785.1478" targetFramework="net452" /> <package id="cef.redist.x64" version="3.2785.1486" targetFramework="net452" />
<package id="cef.redist.x86" version="3.2785.1478" targetFramework="net452" /> <package id="cef.redist.x86" version="3.2785.1486" targetFramework="net452" />
<package id="CefSharp.Common" version="53.0.0-pre01" targetFramework="net452" /> <package id="CefSharp.Common" version="53.0.1" targetFramework="net452" />
<package id="CefSharp.WinForms" version="53.0.0-pre01" targetFramework="net452" /> <package id="CefSharp.WinForms" version="53.0.1" targetFramework="net452" />
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" /> <package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" />
</packages> </packages>

View File

@@ -0,0 +1,19 @@
using System;
using System.Windows.Forms;
using TweetDck.Core.Controls;
namespace TweetDck.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

@@ -0,0 +1,34 @@
using System;
using System.Text;
namespace TweetDck.Core.Bridge{
static class PropertyBridge{
[Flags]
public enum Properties{
ExpandLinksOnHover = 1,
MuteNotifications = 2,
HasCustomNotificationSound = 4,
All = ExpandLinksOnHover | MuteNotifications | HasCustomNotificationSound
}
public static string GenerateScript(Properties properties = Properties.All){
StringBuilder build = new StringBuilder();
build.Append("(function(c){");
if (properties.HasFlag(Properties.ExpandLinksOnHover)){
build.Append("c.expandLinksOnHover=").Append(Program.UserConfig.ExpandLinksOnHover ? "true;" : "false;");
}
if (properties.HasFlag(Properties.MuteNotifications)){
build.Append("c.muteNotifications=").Append(Program.UserConfig.MuteNotifications ? "true;" : "false;");
}
if (properties.HasFlag(Properties.HasCustomNotificationSound)){
build.Append("c.hasCustomNotificationSound=").Append(Program.UserConfig.NotificationSoundPath.Length > 0 ? "true;" : "false;");
}
build.Append("})(window.$TDX=window.$TDX||{})");
return build.ToString();
}
}
}

View File

@@ -3,56 +3,24 @@ using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Core.Utils;
using TweetDck.Core.Controls; using TweetDck.Core.Controls;
using TweetDck.Core.Notification;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Handling{ namespace TweetDck.Core.Bridge{
class TweetDeckBridge{ sealed class TweetDeckBridge{
public static string LastRightClickedLink = string.Empty; public static string LastRightClickedLink = string.Empty;
public static string LastHighlightedTweet = string.Empty; public static string LastHighlightedTweet = string.Empty;
public static string LastHighlightedQuotedTweet = string.Empty; public static string LastHighlightedQuotedTweet = string.Empty;
public static string NotificationTweetEmbedded = string.Empty;
public static string ClipboardImagePath = string.Empty; public static string ClipboardImagePath = string.Empty;
public static void ResetStaticProperties(){
LastRightClickedLink = LastHighlightedTweet = LastHighlightedQuotedTweet = ClipboardImagePath = string.Empty;
}
private readonly FormBrowser form; private readonly FormBrowser form;
private readonly FormNotification notification; private readonly FormNotification notification;
public string BrandName{
get{
return Program.BrandName;
}
}
public string VersionTag{
get{
return Program.VersionTag;
}
}
public bool MuteNotifications{
get{
return Program.UserConfig.MuteNotifications;
}
}
public bool ExpandLinksOnHover{
get{
return Program.UserConfig.ExpandLinksOnHover;
}
}
public bool HasCustomBrowserCSS{
get{
return !string.IsNullOrEmpty(Program.UserConfig.CustomBrowserCSS);
}
}
public string CustomBrowserCSS{
get{
return Program.UserConfig.CustomBrowserCSS;
}
}
public TweetDeckBridge(FormBrowser form, FormNotification notification){ public TweetDeckBridge(FormBrowser form, FormNotification notification){
this.form = form; this.form = form;
this.notification = notification; this.notification = notification;
@@ -71,26 +39,26 @@ namespace TweetDck.Core.Handling{
} }
public void SetLastRightClickedLink(string link){ public void SetLastRightClickedLink(string link){
form.InvokeSafe(() => LastRightClickedLink = link); form.InvokeAsyncSafe(() => LastRightClickedLink = link);
} }
public void SetLastHighlightedTweet(string link, string quotedLink){ public void SetLastHighlightedTweet(string link, string quotedLink){
form.InvokeSafe(() => { form.InvokeAsyncSafe(() => {
LastHighlightedTweet = link; LastHighlightedTweet = link;
LastHighlightedQuotedTweet = quotedLink; LastHighlightedQuotedTweet = quotedLink;
}); });
} }
public void SetNotificationTweetEmbedded(string link){ public void SetNotificationQuotedTweet(string link){
form.InvokeSafe(() => NotificationTweetEmbedded = link); notification.InvokeAsyncSafe(() => notification.CurrentQuotedTweetUrl = link);
} }
public void OpenSettingsMenu(){ public void OpenSettingsMenu(){
form.InvokeSafe(form.OpenSettings); form.InvokeAsyncSafe(form.OpenSettings);
} }
public void OpenPluginsMenu(){ public void OpenPluginsMenu(){
form.InvokeSafe(form.OpenPlugins); form.InvokeAsyncSafe(form.OpenPlugins);
} }
public void OnTweetPopup(string tweetHtml, string tweetUrl, int tweetCharacters){ public void OnTweetPopup(string tweetHtml, string tweetUrl, int tweetCharacters){
@@ -101,24 +69,25 @@ namespace TweetDck.Core.Handling{
} }
public void OnTweetSound(){ public void OnTweetSound(){
form.InvokeSafe(form.OnTweetNotification); form.InvokeAsyncSafe(() => {
} form.OnTweetNotification();
form.PlayNotificationSound();
public void OnNotificationReady(){ });
if (!Program.UserConfig.NotificationLegacyLoad){
notification.InvokeSafe(notification.OnNotificationReady);
}
} }
public void DisplayTooltip(string text, bool showInNotification){ public void DisplayTooltip(string text, bool showInNotification){
if (showInNotification){ if (showInNotification){
notification.InvokeSafe(() => notification.DisplayTooltip(text)); notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
} }
else{ else{
form.InvokeSafe(() => form.DisplayTooltip(text)); form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
} }
} }
public void LoadNextNotification(){
notification.InvokeAsyncSafe(notification.FinishCurrentTweet);
}
public void TryPasteImage(){ public void TryPasteImage(){
form.InvokeSafe(() => { form.InvokeSafe(() => {
if (Clipboard.ContainsImage()){ if (Clipboard.ContainsImage()){
@@ -151,6 +120,14 @@ namespace TweetDck.Core.Handling{
}); });
} }
public void ScreenshotTweet(string html, int width, int height){
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
}
public void FixClipboard(){
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
}
public void OpenBrowser(string url){ public void OpenBrowser(string url){
BrowserUtils.OpenExternalBrowser(url); BrowserUtils.OpenExternalBrowser(url);
} }

View File

@@ -1,9 +1,12 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Controls{ namespace TweetDck.Core.Controls{
static class ControlExtensions{ static class ControlExtensions{
public static readonly Point InvisibleLocation = new Point(-32000, -32000);
public static void InvokeSafe(this Control control, Action func){ public static void InvokeSafe(this Control control, Action func){
if (control.InvokeRequired){ if (control.InvokeRequired){
control.Invoke(func); control.Invoke(func);
@@ -13,6 +16,10 @@ namespace TweetDck.Core.Controls{
} }
} }
public static void InvokeAsyncSafe(this Control control, Action func){
control.BeginInvoke(func);
}
public static void MoveToCenter(this Form targetForm, Form parentForm){ public static void MoveToCenter(this Form targetForm, Form parentForm){
targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2); targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
} }
@@ -34,5 +41,21 @@ namespace TweetDck.Core.Controls{
trackBar.Value = value; trackBar.Value = value;
} }
} }
public static void SetElevated(this Button button){
button.Text = " "+button.Text;
button.FlatStyle = FlatStyle.System;
NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, 0, new IntPtr(1));
}
public static void EnableMultilineShortcuts(this TextBox textBox){
textBox.KeyDown += (sender, args) => {
if (args.Control && args.KeyCode == Keys.A){
((TextBox)sender).SelectAll();
args.SuppressKeyPress = true;
args.Handled = true;
}
};
}
} }
} }

View File

@@ -38,7 +38,7 @@
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(324, 386); this.ClientSize = new System.Drawing.Size(324, 386);
this.Icon = Properties.Resources.icon; this.Icon = Properties.Resources.icon;
this.Location = new System.Drawing.Point(-32000, -32000); this.Location = TweetDck.Core.Controls.ControlExtensions.InvisibleLocation;
this.MinimumSize = new System.Drawing.Size(340, 424); this.MinimumSize = new System.Drawing.Size(340, 424);
this.Name = "FormBrowser"; this.Name = "FormBrowser";
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;

View File

@@ -8,9 +8,15 @@ using TweetDck.Core.Other;
using TweetDck.Resources; using TweetDck.Resources;
using TweetDck.Core.Controls; using TweetDck.Core.Controls;
using System.Drawing; using System.Drawing;
using TweetDck.Core.Utils;
using TweetDck.Updates; using TweetDck.Updates;
using TweetDck.Plugins; using TweetDck.Plugins;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events; using TweetDck.Plugins.Events;
using TweetDck.Core.Bridge;
using TweetDck.Core.Notification;
using TweetDck.Core.Notification.Screenshot;
using TweetDck.Updates.Events;
namespace TweetDck.Core{ namespace TweetDck.Core{
sealed partial class FormBrowser : Form{ sealed partial class FormBrowser : Form{
@@ -25,6 +31,7 @@ namespace TweetDck.Core{
private readonly ChromiumWebBrowser browser; private readonly ChromiumWebBrowser browser;
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly UpdateHandler updates; private readonly UpdateHandler updates;
private readonly FormNotification notification;
private FormSettings currentFormSettings; private FormSettings currentFormSettings;
private FormAbout currentFormAbout; private FormAbout currentFormAbout;
@@ -33,33 +40,57 @@ namespace TweetDck.Core{
private FormWindowState prevState; private FormWindowState prevState;
public FormBrowser(PluginManager pluginManager){ private TweetScreenshotManager notificationScreenshotManager;
private SoundNotification soundNotification;
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
InitializeComponent(); InitializeComponent();
Text = Program.BrandName; Text = Program.BrandName;
this.plugins = pluginManager; this.plugins = pluginManager;
this.plugins.Reloaded += plugins_Reloaded; this.plugins.Reloaded += plugins_Reloaded;
this.plugins.Config.PluginChangedState += plugins_PluginChangedState; this.plugins.PluginChangedState += plugins_PluginChangedState;
FormNotification notification = CreateNotificationForm(true); this.notification = CreateNotificationForm(NotificationFlags.AutoHide | NotificationFlags.TopMost);
notification.CanMoveWindow = () => false; #if DEBUG
notification.Show(); this.notification.CanMoveWindow = () => (ModifierKeys & Keys.Alt) == Keys.Alt;
#else
this.notification.CanMoveWindow = () => false;
#endif
this.notification.Show();
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){ this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
MenuHandler = new ContextMenuBrowser(this), MenuHandler = new ContextMenuBrowser(this),
DialogHandler = new DialogHandlerBrowser(this), DialogHandler = new FileDialogHandler(this),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler() LifeSpanHandler = new LifeSpanHandler()
}; };
#if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
this.browser.LoadingStateChanged += Browser_LoadingStateChanged; this.browser.LoadingStateChanged += Browser_LoadingStateChanged;
this.browser.FrameLoadEnd += Browser_FrameLoadEnd; this.browser.FrameLoadEnd += Browser_FrameLoadEnd;
this.browser.RegisterJsObject("$TD", new TweetDeckBridge(this, notification)); this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge); this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
Controls.Add(browser); Controls.Add(browser);
Disposed += (sender, args) => browser.Dispose(); Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
Disposed += (sender, args) => {
browser.Dispose();
if (notificationScreenshotManager != null){
notificationScreenshotManager.Dispose();
}
if (soundNotification != null){
soundNotification.Dispose();
}
};
this.trayIcon.ClickRestore += trayIcon_ClickRestore; this.trayIcon.ClickRestore += trayIcon_ClickRestore;
this.trayIcon.ClickClose += trayIcon_ClickClose; this.trayIcon.ClickClose += trayIcon_ClickClose;
@@ -67,24 +98,23 @@ namespace TweetDck.Core{
UpdateTrayIcon(); UpdateTrayIcon();
this.updates = new UpdateHandler(browser, this); Config.MuteToggled += Config_MuteToggled;
this.updates = new UpdateHandler(browser, this, updaterSettings);
this.updates.UpdateAccepted += updates_UpdateAccepted; this.updates.UpdateAccepted += updates_UpdateAccepted;
this.updates.UpdateDismissed += updates_UpdateDismissed;
} }
private void ShowChildForm(Form form){ private void ShowChildForm(Form form){
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
form.Show(this); form.Show(this);
form.Shown += (sender, args) => form.MoveToCenter(this);
} }
private void ForceClose(){ public void ForceClose(){
trayIcon.Visible = false; // checked in FormClosing event trayIcon.Visible = false; // checked in FormClosing event
Close(); Close();
} }
public FormNotification CreateNotificationForm(bool autoHide){
return new FormNotification(this, plugins, autoHide);
}
// window setup // window setup
private void SetupWindow(){ private void SetupWindow(){
@@ -113,14 +143,22 @@ namespace TweetDck.Core{
} }
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain){ if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
UpdateProperties();
ScriptLoader.ExecuteFile(e.Frame, "code.js"); ScriptLoader.ExecuteFile(e.Frame, "code.js");
ReinjectCustomCSS(Config.CustomBrowserCSS);
#if DEBUG
ScriptLoader.ExecuteFile(e.Frame, "debug.js");
#endif
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){ if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginBrowserScriptFile); ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginBrowserScriptFile);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile); ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser, true); plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser, true);
} }
TweetDeckBridge.ResetStaticProperties();
} }
} }
@@ -150,7 +188,7 @@ namespace TweetDck.Core{
private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves
if (!isLoaded)return; if (!isLoaded)return;
if (Location.X != -32000){ if (Location != ControlExtensions.InvisibleLocation){
Config.BrowserWindow.Save(this); Config.BrowserWindow.Save(this);
Config.Save(); Config.Save();
} }
@@ -165,6 +203,10 @@ namespace TweetDck.Core{
} }
} }
private void Config_MuteToggled(object sender, EventArgs e){
UpdateProperties(PropertyBridge.Properties.MuteNotifications);
}
private void Config_TrayBehaviorChanged(object sender, EventArgs e){ private void Config_TrayBehaviorChanged(object sender, EventArgs e){
if (!isLoaded)return; if (!isLoaded)return;
@@ -214,6 +256,11 @@ namespace TweetDck.Core{
} }
} }
private void updates_UpdateDismissed(object sender, UpdateDismissedEventArgs e){
Config.DismissedUpdate = e.VersionTag;
Config.Save();
}
protected override void WndProc(ref Message m){ protected override void WndProc(ref Message m){
if (isLoaded && m.Msg == Program.WindowRestoreMessage){ if (isLoaded && m.Msg == Program.WindowRestoreMessage){
trayIcon_ClickRestore(trayIcon, new EventArgs()); trayIcon_ClickRestore(trayIcon, new EventArgs());
@@ -228,29 +275,61 @@ namespace TweetDck.Core{
base.WndProc(ref m); base.WndProc(ref m);
} }
// notification helpers
public FormNotification CreateNotificationForm(NotificationFlags flags){
return new FormNotification(this, plugins, flags);
}
public void PauseNotification(){
notification.PauseNotification();
}
public void ResumeNotification(){
notification.ResumeNotification();
}
// javascript calls
public void ReinjectCustomCSS(string css){
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css == null ? string.Empty : css.Replace(Environment.NewLine, " "));
}
public void UpdateProperties(PropertyBridge.Properties properties = PropertyBridge.Properties.All){
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(properties));
}
// callback handlers // callback handlers
public void OpenSettings(){ public void OpenSettings(){
OpenSettings(0);
}
public void OpenSettings(int tabIndex){
if (currentFormSettings != null){ if (currentFormSettings != null){
currentFormSettings.BringToFront(); currentFormSettings.BringToFront();
} }
else{ else{
bool prevEnableUpdateCheck = Config.EnableUpdateCheck; bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
currentFormSettings = new FormSettings(this, plugins, updates); currentFormSettings = new FormSettings(this, plugins, updates, tabIndex);
currentFormSettings.FormClosed += (sender, args) => { currentFormSettings.FormClosed += (sender, args) => {
currentFormSettings = null; currentFormSettings = null;
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){ if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
updates.Settings.DismissedUpdate = string.Empty;
Config.DismissedUpdate = string.Empty; Config.DismissedUpdate = string.Empty;
Config.Save(); Config.Save();
updates.Check(false); updates.Check(false);
} }
if (!Config.EnableTrayHighlight){ if (!Config.EnableTrayHighlight){
trayIcon.HasNotifications = false; trayIcon.HasNotifications = false;
} }
UpdateProperties(PropertyBridge.Properties.ExpandLinksOnHover | PropertyBridge.Properties.HasCustomNotificationSound);
}; };
ShowChildForm(currentFormSettings); ShowChildForm(currentFormSettings);
@@ -279,12 +358,32 @@ namespace TweetDck.Core{
} }
} }
public void OnTweetNotification(){ public void OnTweetNotification(){ // may be called multiple times, once for each type of notification
if (Config.EnableTrayHighlight && !ContainsFocus){ if (Config.EnableTrayHighlight && !ContainsFocus){
trayIcon.HasNotifications = true; trayIcon.HasNotifications = true;
} }
} }
public void PlayNotificationSound(){
if (Config.NotificationSoundPath.Length == 0){
return;
}
if (soundNotification == null){
soundNotification = new SoundNotification(this);
}
soundNotification.Play(Config.NotificationSoundPath);
}
public void OnTweetScreenshotReady(string html, int width, int height){
if (notificationScreenshotManager == null){
notificationScreenshotManager = new TweetScreenshotManager(this);
}
notificationScreenshotManager.Trigger(html, width, height);
}
public void DisplayTooltip(string text){ public void DisplayTooltip(string text){
if (string.IsNullOrEmpty(text)){ if (string.IsNullOrEmpty(text)){
toolTip.Hide(this); toolTip.Hide(this);
@@ -297,11 +396,15 @@ namespace TweetDck.Core{
} }
public void OnImagePasted(){ public void OnImagePasted(){
browser.ExecuteScriptAsync("TDGF_tryPasteImage", new object[0]); browser.ExecuteScriptAsync("TDGF_tryPasteImage()");
} }
public void OnImagePastedFinish(){ public void OnImagePastedFinish(){
browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish", new object[0]); browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish()");
}
public void TriggerTweetScreenshot(){
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
} }
public void ReloadBrowser(){ public void ReloadBrowser(){

View File

@@ -1,7 +1,7 @@
using TweetDck.Core.Controls; using TweetDck.Core.Controls;
namespace TweetDck.Core { namespace TweetDck.Core {
sealed partial class FormNotification { partial class FormNotification {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.
/// </summary> /// </summary>
@@ -30,6 +30,7 @@ namespace TweetDck.Core {
this.timerProgress = new System.Windows.Forms.Timer(this.components); this.timerProgress = new System.Windows.Forms.Timer(this.components);
this.progressBarTimer = new TweetDck.Core.Controls.FlatProgressBar(); this.progressBarTimer = new TweetDck.Core.Controls.FlatProgressBar();
this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.timerDisplayDelay = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout(); this.SuspendLayout();
// //
// panelBrowser // panelBrowser
@@ -61,6 +62,11 @@ namespace TweetDck.Core {
this.progressBarTimer.Size = new System.Drawing.Size(284, 4); this.progressBarTimer.Size = new System.Drawing.Size(284, 4);
this.progressBarTimer.TabIndex = 1; this.progressBarTimer.TabIndex = 1;
// //
// timerDisplayDelay
//
this.timerDisplayDelay.Interval = 17;
this.timerDisplayDelay.Tick += new System.EventHandler(this.timerDisplayDelay_Tick);
//
// FormNotification // FormNotification
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -70,7 +76,7 @@ namespace TweetDck.Core {
this.Controls.Add(this.progressBarTimer); this.Controls.Add(this.progressBarTimer);
this.Controls.Add(this.panelBrowser); this.Controls.Add(this.panelBrowser);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Location = new System.Drawing.Point(-32000, -32000); this.Location = TweetDck.Core.Controls.ControlExtensions.InvisibleLocation;
this.MaximizeBox = false; this.MaximizeBox = false;
this.MinimizeBox = false; this.MinimizeBox = false;
this.Name = "FormNotification"; this.Name = "FormNotification";
@@ -88,5 +94,6 @@ namespace TweetDck.Core {
private Controls.FlatProgressBar progressBarTimer; private Controls.FlatProgressBar progressBarTimer;
private System.Windows.Forms.Timer timerProgress; private System.Windows.Forms.Timer timerProgress;
private System.Windows.Forms.ToolTip toolTip; private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Timer timerDisplayDelay;
} }
} }

View File

@@ -5,13 +5,17 @@ using System.Windows.Forms;
using CefSharp; using CefSharp;
using CefSharp.WinForms; using CefSharp.WinForms;
using TweetDck.Configuration; using TweetDck.Configuration;
using TweetDck.Core.Bridge;
using TweetDck.Core.Handling; using TweetDck.Core.Handling;
using TweetDck.Resources; using TweetDck.Resources;
using TweetDck.Core.Utils; using TweetDck.Core.Utils;
using TweetDck.Plugins; using TweetDck.Plugins;
using TweetDck.Plugins.Enums;
using TweetDck.Core.Controls;
using TweetDck.Core.Notification;
namespace TweetDck.Core{ namespace TweetDck.Core{
sealed partial class FormNotification : Form{ partial class FormNotification : Form{
private const string NotificationScriptFile = "notification.js"; private const string NotificationScriptFile = "notification.js";
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile); private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
@@ -19,12 +23,28 @@ namespace TweetDck.Core{
public Func<bool> CanMoveWindow = () => true; public Func<bool> CanMoveWindow = () => true;
private readonly Form owner; public bool IsNotificationVisible{
get{
return Location != ControlExtensions.InvisibleLocation;
}
}
public new Point Location{
get{
return base.Location;
}
set{
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
}
}
private readonly Control owner;
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly ChromiumWebBrowser browser; protected readonly NotificationFlags flags;
protected readonly ChromiumWebBrowser browser;
private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4); private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
private readonly bool autoHide;
private int timeLeft, totalTime; private int timeLeft, totalTime;
private readonly NativeMethods.HookProc mouseHookDelegate; private readonly NativeMethods.HookProc mouseHookDelegate;
@@ -62,9 +82,18 @@ namespace TweetDck.Core{
public bool FreezeTimer { get; set; } public bool FreezeTimer { get; set; }
public bool ContextMenuOpen { get; set; } public bool ContextMenuOpen { get; set; }
public string CurrentUrl { get; private set; } public string CurrentUrl { get; private set; }
public string CurrentQuotedTweetUrl { get; set; }
public EventHandler Initialized; public EventHandler Initialized;
private bool isInitialized;
private int pauseCounter;
private bool pausedDuringNotification;
public bool IsPaused{
get{
return pauseCounter > 0;
}
}
private static int BaseClientWidth{ private static int BaseClientWidth{
get{ get{
@@ -80,35 +109,47 @@ namespace TweetDck.Core{
} }
} }
public FormNotification(FormBrowser owner, PluginManager pluginManager, bool autoHide){ public FormNotification(FormBrowser owner, PluginManager pluginManager, NotificationFlags flags){
InitializeComponent(); InitializeComponent();
Text = Program.BrandName;
this.owner = owner; this.owner = owner;
this.plugins = pluginManager; this.plugins = pluginManager;
this.autoHide = autoHide; this.flags = flags;
owner.FormClosed += (sender, args) => Close(); owner.FormClosed += (sender, args) => Close();
notificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
pluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
browser = new ChromiumWebBrowser("about:blank"){ browser = new ChromiumWebBrowser("about:blank"){
MenuHandler = new ContextMenuNotification(this, autoHide), MenuHandler = new ContextMenuNotification(this, !flags.HasFlag(NotificationFlags.DisableContextMenu)),
LifeSpanHandler = new LifeSpanHandler() LifeSpanHandler = new LifeSpanHandler()
}; };
#if DEBUG
browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged; browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
browser.LoadingStateChanged += Browser_LoadingStateChanged;
browser.FrameLoadEnd += Browser_FrameLoadEnd; browser.FrameLoadEnd += Browser_FrameLoadEnd;
browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this));
if (!flags.HasFlag(NotificationFlags.DisableScripts)){
notificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this));
if (plugins != null){
pluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge); browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
}
}
panelBrowser.Controls.Add(browser); panelBrowser.Controls.Add(browser);
if (autoHide){ if (flags.HasFlag(NotificationFlags.AutoHide)){
Program.UserConfig.MuteToggled += Config_MuteToggled; Program.UserConfig.MuteToggled += Config_MuteToggled;
Disposed += (sender, args) => Program.UserConfig.MuteToggled -= Config_MuteToggled; Disposed += (sender, args) => Program.UserConfig.MuteToggled -= Config_MuteToggled;
if (Program.UserConfig.MuteNotifications){
PauseNotification();
}
} }
mouseHookDelegate = MouseHookProc; mouseHookDelegate = MouseHookProc;
@@ -153,6 +194,11 @@ namespace TweetDck.Core{
// event handlers // event handlers
private void timerDisplayDelay_Tick(object sender, EventArgs e){
OnNotificationReady();
timerDisplayDelay.Stop();
}
private void timerHideProgress_Tick(object sender, EventArgs e){ private void timerHideProgress_Tick(object sender, EventArgs e){
if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen)return; if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen)return;
@@ -168,10 +214,10 @@ namespace TweetDck.Core{
private void Config_MuteToggled(object sender, EventArgs e){ private void Config_MuteToggled(object sender, EventArgs e){
if (Program.UserConfig.MuteNotifications){ if (Program.UserConfig.MuteNotifications){
HideNotification(true); PauseNotification();
} }
else if (tweetQueue.Count > 0){ else{
LoadNextNotification(); ResumeNotification();
} }
} }
@@ -181,20 +227,21 @@ namespace TweetDck.Core{
} }
} }
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading && browser.Address != "about:blank" && !flags.HasFlag(NotificationFlags.ManualDisplay)){
this.InvokeSafe(() => {
Visible = true; // ensures repaint before moving the window to a visible location
timerDisplayDelay.Start();
});
}
}
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (!e.Frame.IsMain)return; if (e.Frame.IsMain && notificationJS != null && browser.Address != "about:blank" && !flags.HasFlag(NotificationFlags.DisableScripts)){
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Properties.ExpandLinksOnHover));
if (!isInitialized && !Program.UserConfig.NotificationLegacyLoad){
isInitialized = true;
if (Initialized != null){
Initialized(this, new EventArgs());
}
}
else if (notificationJS != null && browser.Address != "about:blank"){
ScriptLoader.ExecuteScript(e.Frame, notificationJS, NotificationScriptIdentifier); ScriptLoader.ExecuteScript(e.Frame, notificationJS, NotificationScriptIdentifier);
if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){ if (plugins != null && plugins.HasAnyPlugin(PluginEnvironment.Notification)){
ScriptLoader.ExecuteScript(e.Frame, pluginJS, PluginScriptIdentifier); ScriptLoader.ExecuteScript(e.Frame, pluginJS, PluginScriptIdentifier);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile); ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false); plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false);
@@ -218,14 +265,14 @@ namespace TweetDck.Core{
// notification methods // notification methods
public void ShowNotification(TweetNotification notification){ public void ShowNotification(TweetNotification notification){
if (Program.UserConfig.MuteNotifications){ if (IsPaused){
tweetQueue.Enqueue(notification); tweetQueue.Enqueue(notification);
} }
else{ else{
tweetQueue.Enqueue(notification); tweetQueue.Enqueue(notification);
UpdateTitle(); UpdateTitle();
if (!timerProgress.Enabled){ if (totalTime == 0){
LoadNextNotification(); LoadNextNotification();
} }
} }
@@ -236,33 +283,28 @@ namespace TweetDck.Core{
LoadTweet(TweetNotification.ExampleTweet); LoadTweet(TweetNotification.ExampleTweet);
} }
else{ else{
MoveToVisibleLocation(); PrepareAndDisplayWindow();
} }
} }
public void HideNotification(bool loadBlank){ public void HideNotification(bool loadBlank){
if (loadBlank || Program.UserConfig.NotificationLegacyLoad){ if (loadBlank){
browser.LoadHtml("", "about:blank"); browser.LoadHtml("", "about:blank");
} }
Location = new Point(-32000, -32000); Location = ControlExtensions.InvisibleLocation;
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0; progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
timerProgress.Stop(); timerProgress.Stop();
totalTime = 0;
StopMouseHook(); StopMouseHook();
} }
public void OnNotificationReady(){
UpdateTitle();
MoveToVisibleLocation();
timerProgress.Start();
}
public void FinishCurrentTweet(){ public void FinishCurrentTweet(){
if (tweetQueue.Count > 0){ if (tweetQueue.Count > 0){
LoadNextNotification(); LoadNextNotification();
} }
else if (autoHide){ else if (flags.HasFlag(NotificationFlags.AutoHide)){
HideNotification(true); HideNotification(true);
} }
else{ else{
@@ -270,49 +312,79 @@ namespace TweetDck.Core{
} }
} }
public void PauseNotification(){
if (pauseCounter++ == 0){
pausedDuringNotification = IsNotificationVisible;
if (IsNotificationVisible){
Location = ControlExtensions.InvisibleLocation;
timerProgress.Stop();
StopMouseHook();
}
}
}
public void ResumeNotification(){
if (pauseCounter > 0 && --pauseCounter == 0){
if (pausedDuringNotification){
OnNotificationReady();
}
else if (tweetQueue.Count > 0){
LoadNextNotification();
}
}
}
private void LoadNextNotification(){ private void LoadNextNotification(){
LoadTweet(tweetQueue.Dequeue()); LoadTweet(tweetQueue.Dequeue());
} }
private void LoadTweet(TweetNotification tweet){ private void LoadTweet(TweetNotification tweet){
CurrentUrl = tweet.Url; CurrentUrl = tweet.Url;
CurrentQuotedTweetUrl = string.Empty; // load from JS
timerProgress.Stop(); timerProgress.Stop();
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue); totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0; progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
browser.LoadHtml(tweet.GenerateHtml(), "http://tweetdeck.twitter.com/?"+DateTime.Now.Ticks); string bodyClasses = browser.Bounds.Contains(PointToClient(Cursor.Position)) ? "td-hover" : string.Empty;
if (Program.UserConfig.NotificationLegacyLoad){ browser.LoadHtml(tweet.GenerateHtml(bodyClasses), "http://tweetdeck.twitter.com/?"+DateTime.Now.Ticks);
OnNotificationReady();
}
} }
private void MoveToVisibleLocation(){ private void PrepareAndDisplayWindow(){
UserConfig config = Program.UserConfig;
if (RequiresResize){ if (RequiresResize){
RequiresResize = false; RequiresResize = false;
SetNotificationSize(BaseClientWidth, BaseClientHeight, Program.UserConfig.DisplayNotificationTimer);
}
if (config.DisplayNotificationTimer){ MoveToVisibleLocation();
ClientSize = new Size(BaseClientWidth, BaseClientHeight+4); StartMouseHook();
}
protected void SetNotificationSize(int width, int height, bool displayTimer){
if (displayTimer){
ClientSize = new Size(width, height+4);
progressBarTimer.Visible = true; progressBarTimer.Visible = true;
} }
else{ else{
ClientSize = new Size(BaseClientWidth, BaseClientHeight); ClientSize = new Size(width, height);
progressBarTimer.Visible = false; progressBarTimer.Visible = false;
} }
panelBrowser.Height = BaseClientHeight; panelBrowser.Height = height;
} }
protected void MoveToVisibleLocation(){
UserConfig config = Program.UserConfig;
Screen screen = Screen.FromControl(owner); Screen screen = Screen.FromControl(owner);
if (config.NotificationDisplay > 0 && config.NotificationDisplay <= Screen.AllScreens.Length){ if (config.NotificationDisplay > 0 && config.NotificationDisplay <= Screen.AllScreens.Length){
screen = Screen.AllScreens[config.NotificationDisplay-1]; screen = Screen.AllScreens[config.NotificationDisplay-1];
} }
bool needsReactivating = Location.X == -32000; bool needsReactivating = Location == ControlExtensions.InvisibleLocation;
int edgeDist = config.NotificationEdgeDistance; int edgeDist = config.NotificationEdgeDistance;
switch(config.NotificationPosition){ switch(config.NotificationPosition){
@@ -342,17 +414,21 @@ namespace TweetDck.Core{
break; break;
} }
if (needsReactivating){ if (needsReactivating && flags.HasFlag(NotificationFlags.TopMost)){
NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE); NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
} }
StartMouseHook();
} }
private void UpdateTitle(){ protected void UpdateTitle(){
Text = tweetQueue.Count > 0 ? Program.BrandName+" ("+tweetQueue.Count+" more left)" : Program.BrandName; Text = tweetQueue.Count > 0 ? Program.BrandName+" ("+tweetQueue.Count+" more left)" : Program.BrandName;
} }
protected void OnNotificationReady(){
UpdateTitle();
PrepareAndDisplayWindow();
timerProgress.Start();
}
public void DisplayTooltip(string text){ public void DisplayTooltip(string text){
if (string.IsNullOrEmpty(text)){ if (string.IsNullOrEmpty(text)){
toolTip.Hide(this); toolTip.Hide(this);
@@ -360,7 +436,7 @@ namespace TweetDck.Core{
else{ else{
Point position = PointToClient(Cursor.Position); Point position = PointToClient(Cursor.Position);
position.Offset(20, 5); position.Offset(20, 5);
toolTip.Show(text, this, position); toolTip.Show(text, this, position); // TODO figure out flickering when moving the mouse
} }
} }
} }

View File

@@ -2,6 +2,8 @@
using System; using System;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Core.Bridge;
using TweetDck.Core.Controls;
using TweetDck.Core.Utils; using TweetDck.Core.Utils;
namespace TweetDck.Core.Handling{ namespace TweetDck.Core.Handling{
@@ -12,6 +14,20 @@ namespace TweetDck.Core.Handling{
private const int MenuSaveImage = 26503; private const int MenuSaveImage = 26503;
private const int MenuCopyImageUrl = 26504; private const int MenuCopyImageUrl = 26504;
#if DEBUG
private const int MenuOpenDevTools = 26599;
protected void AddDebugMenuItems(IMenuModel model){
model.AddItem((CefMenuCommand)MenuOpenDevTools, "Open dev tools");
}
#endif
private readonly Form form;
protected ContextMenuBase(Form form){
this.form = form;
}
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){ if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser"); model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser");
@@ -34,7 +50,7 @@ namespace TweetDck.Core.Handling{
break; break;
case MenuCopyLinkUrl: case MenuCopyLinkUrl:
Clipboard.SetText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink, TextDataFormat.UnicodeText); SetClipboardText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink);
break; break;
case MenuOpenImage: case MenuOpenImage:
@@ -65,8 +81,14 @@ namespace TweetDck.Core.Handling{
break; break;
case MenuCopyImageUrl: case MenuCopyImageUrl:
Clipboard.SetText(parameters.SourceUrl, TextDataFormat.UnicodeText); SetClipboardText(parameters.SourceUrl);
break; break;
#if DEBUG
case MenuOpenDevTools:
browserControl.ShowDevTools();
break;
#endif
} }
return false; return false;
@@ -78,6 +100,10 @@ namespace TweetDck.Core.Handling{
return false; return false;
} }
protected void SetClipboardText(string text){
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
}
protected static void RemoveSeparatorIfLast(IMenuModel model){ protected static void RemoveSeparatorIfLast(IMenuModel model){
if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){ if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){
model.RemoveAt(model.Count-1); model.RemoveAt(model.Count-1);

View File

@@ -1,5 +1,5 @@
using CefSharp; using CefSharp;
using System.Windows.Forms; using TweetDck.Core.Bridge;
using TweetDck.Core.Controls; using TweetDck.Core.Controls;
using TweetDck.Core.Utils; using TweetDck.Core.Utils;
@@ -15,10 +15,14 @@ namespace TweetDck.Core.Handling{
private const int MenuCopyTweetUrl = 26611; private const int MenuCopyTweetUrl = 26611;
private const int MenuOpenQuotedTweetUrl = 26612; private const int MenuOpenQuotedTweetUrl = 26612;
private const int MenuCopyQuotedTweetUrl = 26613; private const int MenuCopyQuotedTweetUrl = 26613;
private const int MenuScreenshotTweet = 26614;
private readonly FormBrowser form; private readonly FormBrowser form;
public ContextMenuBrowser(FormBrowser form){ private string lastHighlightedTweet;
private string lastHighlightedQuotedTweet;
public ContextMenuBrowser(FormBrowser form) : base(form){
this.form = form; this.form = form;
} }
@@ -29,13 +33,26 @@ namespace TweetDck.Core.Handling{
model.Remove(CefMenuCommand.ViewSource); model.Remove(CefMenuCommand.ViewSource);
RemoveSeparatorIfLast(model); RemoveSeparatorIfLast(model);
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
model.AddSeparator();
}
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
if (!string.IsNullOrEmpty(TweetDeckBridge.LastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){ lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
if (!BrowserUtils.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)MenuOpenTweetUrl, "Open tweet in browser");
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address"); model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
model.AddItem((CefMenuCommand)MenuScreenshotTweet, "Screenshot tweet to clipboard");
if (!string.IsNullOrEmpty(TweetDeckBridge.LastHighlightedQuotedTweet)){ if (!string.IsNullOrEmpty(lastHighlightedQuotedTweet)){
model.AddSeparator(); model.AddSeparator();
model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser"); model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address"); model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
@@ -57,7 +74,14 @@ namespace TweetDck.Core.Handling{
globalMenu.AddItem((CefMenuCommand)MenuSettings, "Settings"); globalMenu.AddItem((CefMenuCommand)MenuSettings, "Settings");
globalMenu.AddItem((CefMenuCommand)MenuPlugins, "Plugins"); globalMenu.AddItem((CefMenuCommand)MenuPlugins, "Plugins");
globalMenu.AddItem((CefMenuCommand)MenuAbout, "About "+Program.BrandName); globalMenu.AddItem((CefMenuCommand)MenuAbout, "About "+Program.BrandName);
#if DEBUG
globalMenu.AddSeparator();
AddDebugMenuItems(globalMenu);
#endif
} }
RemoveSeparatorIfLast(model);
} }
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
@@ -71,19 +95,19 @@ namespace TweetDck.Core.Handling{
return true; return true;
case MenuSettings: case MenuSettings:
form.InvokeSafe(form.OpenSettings); form.InvokeAsyncSafe(form.OpenSettings);
return true; return true;
case MenuAbout: case MenuAbout:
form.InvokeSafe(form.OpenAbout); form.InvokeAsyncSafe(form.OpenAbout);
return true; return true;
case MenuPlugins: case MenuPlugins:
form.InvokeSafe(form.OpenPlugins); form.InvokeAsyncSafe(form.OpenPlugins);
return true; return true;
case MenuMute: case MenuMute:
form.InvokeSafe(() => { form.InvokeAsyncSafe(() => {
Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications; Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications;
Program.UserConfig.Save(); Program.UserConfig.Save();
}); });
@@ -91,19 +115,23 @@ namespace TweetDck.Core.Handling{
return true; return true;
case MenuOpenTweetUrl: case MenuOpenTweetUrl:
BrowserUtils.OpenExternalBrowser(TweetDeckBridge.LastHighlightedTweet); BrowserUtils.OpenExternalBrowser(lastHighlightedTweet);
return true; return true;
case MenuCopyTweetUrl: case MenuCopyTweetUrl:
Clipboard.SetText(TweetDeckBridge.LastHighlightedTweet, TextDataFormat.UnicodeText); SetClipboardText(lastHighlightedTweet);
return true;
case MenuScreenshotTweet:
form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
return true; return true;
case MenuOpenQuotedTweetUrl: case MenuOpenQuotedTweetUrl:
BrowserUtils.OpenExternalBrowser(TweetDeckBridge.LastHighlightedQuotedTweet); BrowserUtils.OpenExternalBrowser(lastHighlightedQuotedTweet);
return true; return true;
case MenuCopyQuotedTweetUrl: case MenuCopyQuotedTweetUrl:
Clipboard.SetText(TweetDeckBridge.LastHighlightedQuotedTweet, TextDataFormat.UnicodeText); SetClipboardText(lastHighlightedQuotedTweet);
return true; return true;
} }

View File

@@ -1,5 +1,4 @@
using System.Windows.Forms; using CefSharp;
using CefSharp;
using TweetDck.Core.Controls; using TweetDck.Core.Controls;
namespace TweetDck.Core.Handling{ namespace TweetDck.Core.Handling{
@@ -7,12 +6,12 @@ namespace TweetDck.Core.Handling{
private const int MenuSkipTweet = 26600; private const int MenuSkipTweet = 26600;
private const int MenuFreeze = 26601; private const int MenuFreeze = 26601;
private const int MenuCopyTweetUrl = 26602; private const int MenuCopyTweetUrl = 26602;
private const int MenuCopyTweetEmbeddedUrl = 26603; private const int MenuCopyQuotedTweetUrl = 26603;
private readonly FormNotification form; private readonly FormNotification form;
private readonly bool enableCustomMenu; private readonly bool enableCustomMenu;
public ContextMenuNotification(FormNotification form, bool enableCustomMenu){ public ContextMenuNotification(FormNotification form, bool enableCustomMenu) : base(form){
this.form = form; this.form = form;
this.enableCustomMenu = enableCustomMenu; this.enableCustomMenu = enableCustomMenu;
} }
@@ -20,6 +19,13 @@ namespace TweetDck.Core.Handling{
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
model.Clear(); model.Clear();
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
model.AddItem(CefMenuCommand.Copy, "Copy");
model.AddSeparator();
}
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
if (enableCustomMenu){ if (enableCustomMenu){
model.AddItem((CefMenuCommand)MenuSkipTweet, "Skip tweet"); model.AddItem((CefMenuCommand)MenuSkipTweet, "Skip tweet");
model.AddCheckItem((CefMenuCommand)MenuFreeze, "Freeze"); model.AddCheckItem((CefMenuCommand)MenuFreeze, "Freeze");
@@ -29,18 +35,21 @@ namespace TweetDck.Core.Handling{
if (!string.IsNullOrEmpty(form.CurrentUrl)){ if (!string.IsNullOrEmpty(form.CurrentUrl)){
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address"); model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
if (!string.IsNullOrEmpty(TweetDeckBridge.NotificationTweetEmbedded)){ if (!string.IsNullOrEmpty(form.CurrentQuotedTweetUrl)){
model.AddItem((CefMenuCommand)MenuCopyTweetEmbeddedUrl, "Copy quoted tweet address"); model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
} }
model.AddSeparator(); model.AddSeparator();
} }
} }
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); #if DEBUG
AddDebugMenuItems(model);
#endif
RemoveSeparatorIfLast(model); RemoveSeparatorIfLast(model);
form.InvokeSafe(() => form.ContextMenuOpen = true); form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
} }
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
@@ -50,19 +59,19 @@ namespace TweetDck.Core.Handling{
switch((int)commandId){ switch((int)commandId){
case MenuSkipTweet: case MenuSkipTweet:
form.InvokeSafe(form.FinishCurrentTweet); form.InvokeAsyncSafe(form.FinishCurrentTweet);
return true; return true;
case MenuFreeze: case MenuFreeze:
form.InvokeSafe(() => form.FreezeTimer = !form.FreezeTimer); form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
return true; return true;
case MenuCopyTweetUrl: case MenuCopyTweetUrl:
Clipboard.SetText(form.CurrentUrl, TextDataFormat.UnicodeText); SetClipboardText(form.CurrentUrl);
return true; return true;
case MenuCopyTweetEmbeddedUrl: case MenuCopyQuotedTweetUrl:
Clipboard.SetText(TweetDeckBridge.NotificationTweetEmbedded, TextDataFormat.UnicodeText); SetClipboardText(form.CurrentQuotedTweetUrl);
return true; return true;
} }
@@ -71,7 +80,7 @@ namespace TweetDck.Core.Handling{
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){ public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
base.OnContextMenuDismissed(browserControl, browser, frame); base.OnContextMenuDismissed(browserControl, browser, frame);
form.InvokeSafe(() => form.ContextMenuOpen = false); form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
} }
} }
} }

View File

@@ -1,12 +1,13 @@
using CefSharp; using CefSharp;
using System.Collections.Generic; using System.Collections.Generic;
using TweetDck.Core.Bridge;
using TweetDck.Core.Controls; using TweetDck.Core.Controls;
namespace TweetDck.Core.Handling{ namespace TweetDck.Core.Handling{
class DialogHandlerBrowser : IDialogHandler{ class FileDialogHandler : IDialogHandler{
private readonly FormBrowser form; private readonly FormBrowser form;
public DialogHandlerBrowser(FormBrowser form){ public FileDialogHandler(FormBrowser form){
this.form = form; this.form = form;
} }

View File

@@ -0,0 +1,49 @@
using CefSharp;
using CefSharp.WinForms;
using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Core.Other;
namespace TweetDck.Core.Handling {
class JavaScriptDialogHandler : IJsDialogHandler{
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
if (dialogType != CefJsDialogType.Alert && dialogType != CefJsDialogType.Confirm){
return false;
}
((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
FormMessage form = new FormMessage(Program.BrandName, messageText, MessageBoxIcon.None);
Button btnConfirm;
if (dialogType == CefJsDialogType.Alert){
btnConfirm = form.AddButton("OK");
}
else if (dialogType == CefJsDialogType.Confirm){
form.AddButton("No");
btnConfirm = form.AddButton("Yes");
}
else{
return;
}
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnConfirm){
callback.Continue(true);
}
else{
callback.Continue(false);
}
form.Dispose();
});
return true;
}
bool IJsDialogHandler.OnJSBeforeUnload(IWebBrowser browserControl, IBrowser browser, string message, bool isReload, IJsDialogCallback callback){
return false;
}
void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser){}
void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser){}
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace TweetDck.Core.Notification{
[Flags]
public enum NotificationFlags{
None = 0,
AutoHide = 1,
DisableScripts = 2,
DisableContextMenu = 4,
TopMost = 8,
ManualDisplay = 16
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Windows.Forms;
using CefSharp;
using TweetDck.Core.Bridge;
using TweetDck.Core.Controls;
using TweetDck.Resources;
namespace TweetDck.Core.Notification.Screenshot{
sealed class FormNotificationScreenshotable : FormNotification{
public FormNotificationScreenshotable(Action callback, FormBrowser owner, NotificationFlags flags) : base(owner, null, flags){
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
browser.FrameLoadEnd += (sender, args) => {
if (args.Frame.IsMain && browser.Address != "about:blank"){
ScriptLoader.ExecuteScript(args.Frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 25)", "gen:screenshot");
}
};
UpdateTitle();
}
public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){
browser.LoadHtml(tweet.GenerateHtml(enableCustomCSS: false), "http://tweetdeck.twitter.com/?"+DateTime.Now.Ticks);
Location = ControlExtensions.InvisibleLocation;
FormBorderStyle = Program.UserConfig.ShowScreenshotBorder ? FormBorderStyle.FixedToolWindow : FormBorderStyle.None;
SetNotificationSize(width, height, false);
}
public void TakeScreenshotAndHide(){
MoveToVisibleLocation();
Activate();
SendKeys.SendWait("%{PRTSC}");
Reset();
}
public void Reset(){
Location = ControlExtensions.InvisibleLocation;
browser.LoadHtml("", "about:blank");
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Windows.Forms;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Notification.Screenshot{
sealed class TweetScreenshotManager : IDisposable{
private readonly FormBrowser browser;
private readonly FormNotificationScreenshotable screenshot;
private readonly Timer timeout;
public TweetScreenshotManager(FormBrowser browser){
this.browser = browser;
this.screenshot = new FormNotificationScreenshotable(Callback, browser, NotificationFlags.DisableScripts | NotificationFlags.DisableContextMenu | NotificationFlags.TopMost | NotificationFlags.ManualDisplay){
CanMoveWindow = () => false
};
this.timeout = WindowsUtils.CreateSingleTickTimer(10000);
this.timeout.Tick += (sender, args) => screenshot.Reset();
}
public void Trigger(string html, int width, int height){
screenshot.LoadNotificationForScreenshot(new TweetNotification(html, string.Empty, 0), width, height);
screenshot.Show();
timeout.Start();
}
private void Callback(){
if (!timeout.Enabled){
return;
}
timeout.Stop();
browser.PauseNotification();
screenshot.TakeScreenshotAndHide();
browser.ResumeNotification();
}
public void Dispose(){
timeout.Dispose();
screenshot.Dispose();
}
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Drawing;
using System.IO;
using System.Media;
using System.Windows.Forms;
using TweetDck.Core.Other;
namespace TweetDck.Core.Notification{
class SoundNotification : IDisposable{
private readonly FormBrowser browserForm;
private SoundPlayer notificationSound;
private bool ignoreNotificationSoundError;
public SoundNotification(FormBrowser browserForm){
this.browserForm = browserForm;
}
public void Play(string file){
if (notificationSound == null){
notificationSound = new SoundPlayer{
LoadTimeout = 5000
};
}
if (notificationSound.SoundLocation != file){
notificationSound.SoundLocation = file;
ignoreNotificationSoundError = false;
}
try{
notificationSound.Play();
}catch(FileNotFoundException e){
OnNotificationSoundError("File not found: "+e.FileName);
}catch(InvalidOperationException){
OnNotificationSoundError("File is not a valid sound file.");
}catch(TimeoutException){
OnNotificationSoundError("File took too long to load.");
}
}
private void OnNotificationSoundError(string message){
if (!ignoreNotificationSoundError){
ignoreNotificationSoundError = true;
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound."+Environment.NewLine+message, MessageBoxIcon.Error)){
form.AddButton("Ignore");
Button btnOpenSettings = form.AddButton("Open Settings");
btnOpenSettings.Width += 16;
btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y);
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){
browserForm.OpenSettings(FormSettings.TabIndexNotification);
}
}
}
}
public void Dispose(){
if (notificationSound != null){
notificationSound.Dispose();
}
}
}
}

View File

@@ -1,28 +1,16 @@
using System; using System;
using System.Text; using System.Text;
namespace TweetDck.Core.Handling{ namespace TweetDck.Core.Notification{
sealed class TweetNotification{ sealed class TweetNotification{
private static string FontSizeClass { get; set; } private static string FontSizeClass { get; set; }
private static string HeadTag { get; set; } private static string HeadTag { get; set; }
private static string DefaultFontSizeClass{ private const string DefaultFontSizeClass = "medium";
get{ 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'>";
return "medium";
}
}
private static string DefaultHeadTag{ private const string FixedCSS = @"a[data-full-url]{word-break:break-all}.txt-base-smallest .badge-verified:before{height:13px!important}";
get{ private const string CustomCSS = @".scroll-styled-v::-webkit-scrollbar{width:8px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}";
return @"<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'>";
}
}
private static string CustomCSS{
get{
return @".scroll-styled-v::-webkit-scrollbar{width:8px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}a[data-full-url]{word-break:break-all}";
}
}
public static int FontSizeLevel{ public static int FontSizeLevel{
get{ get{
@@ -55,7 +43,7 @@ namespace TweetDck.Core.Handling{
build.Append(@"</div></div></article>"); build.Append(@"</div></div></article>");
return new TweetNotification(build.ToString(), "", 95); return new TweetNotification(build.ToString(), "", 95, true);
} }
} }
@@ -71,10 +59,6 @@ namespace TweetDck.Core.Handling{
TopLeft, TopRight, BottomLeft, BottomRight, Custom TopLeft, TopRight, BottomLeft, BottomRight, Custom
} }
public enum Duration{
Short, Medium, Long, VeryLong
}
public string Url{ public string Url{
get{ get{
return url; return url;
@@ -84,29 +68,46 @@ namespace TweetDck.Core.Handling{
private readonly string html; private readonly string html;
private readonly string url; private readonly string url;
private readonly int characters; private readonly int characters;
private readonly bool isExample;
public TweetNotification(string html, string url, int characters){ public TweetNotification(string html, string url, int characters) : this(html, url, characters, false){}
private TweetNotification(string html, string url, int characters, bool isExample){
this.html = html; this.html = html;
this.url = url; this.url = url;
this.characters = characters; this.characters = characters;
this.isExample = isExample;
} }
public int GetDisplayDuration(int value){ public int GetDisplayDuration(int value){
return 2000+Math.Max(1000, value*characters); return 2000+Math.Max(1000, value*characters);
} }
public string GenerateHtml(){ public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){
StringBuilder build = new StringBuilder(); StringBuilder build = new StringBuilder();
build.Append("<!DOCTYPE html>"); build.Append("<!DOCTYPE html>");
build.Append("<html class='os-windows txt-base-").Append(FontSizeClass ?? DefaultFontSizeClass).Append("'>"); build.Append("<html class='os-windows txt-base-").Append(FontSizeClass ?? DefaultFontSizeClass).Append("'>");
build.Append("<head>").Append(HeadTag ?? DefaultHeadTag).Append("<style type='text/css'>").Append(CustomCSS).Append("</style>"); build.Append("<head>").Append(HeadTag ?? DefaultHeadTag);
if (enableCustomCSS){
build.Append("<style type='text/css'>").Append(FixedCSS).Append(CustomCSS).Append("</style>");
if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){ if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){
build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>"); build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>");
} }
}
else{
build.Append("<style type='text/css'>").Append(FixedCSS).Append("</style>");
}
build.Append("</head>"); build.Append("</head>");
build.Append("<body class='hearty'><div class='app-columns-container'><div class='column scroll-styled-v' style='width:100%;overflow-y:auto'>"); build.Append("<body class='hearty");
if (!string.IsNullOrEmpty(bodyClasses)){
build.Append(' ').Append(bodyClasses);
}
build.Append('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='app-columns-container'><div class='column scroll-styled-v' style='width:100%;overflow-y:auto'>");
build.Append(html); build.Append(html);
build.Append("</div></div></body>"); build.Append("</div></div></body>");
build.Append("</html>"); build.Append("</html>");

View File

@@ -28,7 +28,7 @@ namespace TweetDck.Core.Other {
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormAbout)); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormAbout));
this.pictureLogo = new System.Windows.Forms.PictureBox(); this.pictureLogo = new System.Windows.Forms.PictureBox();
this.labelDescription = new System.Windows.Forms.Label(); this.labelDescription = new System.Windows.Forms.Label();
this.labelSourceCode = new System.Windows.Forms.LinkLabel(); this.labelTips = new System.Windows.Forms.LinkLabel();
this.labelWebsite = new System.Windows.Forms.LinkLabel(); this.labelWebsite = new System.Windows.Forms.LinkLabel();
this.tablePanelLinks = new System.Windows.Forms.TableLayoutPanel(); this.tablePanelLinks = new System.Windows.Forms.TableLayoutPanel();
this.labelIssues = new System.Windows.Forms.LinkLabel(); this.labelIssues = new System.Windows.Forms.LinkLabel();
@@ -59,19 +59,19 @@ namespace TweetDck.Core.Other {
this.labelDescription.Size = new System.Drawing.Size(232, 109); this.labelDescription.Size = new System.Drawing.Size(232, 109);
this.labelDescription.TabIndex = 1; this.labelDescription.TabIndex = 1;
// //
// labelSourceCode // labelTips
// //
this.labelSourceCode.Dock = System.Windows.Forms.DockStyle.Fill; this.labelTips.Dock = System.Windows.Forms.DockStyle.Fill;
this.labelSourceCode.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelTips.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelSourceCode.LinkArea = new System.Windows.Forms.LinkArea(0, 0); this.labelTips.LinkArea = new System.Windows.Forms.LinkArea(0, 0);
this.labelSourceCode.Location = new System.Drawing.Point(117, 0); this.labelTips.Location = new System.Drawing.Point(117, 0);
this.labelSourceCode.Margin = new System.Windows.Forms.Padding(0); this.labelTips.Margin = new System.Windows.Forms.Padding(0);
this.labelSourceCode.Name = "labelSourceCode"; this.labelTips.Name = "labelTips";
this.labelSourceCode.Size = new System.Drawing.Size(99, 16); this.labelTips.Size = new System.Drawing.Size(99, 16);
this.labelSourceCode.TabIndex = 3; this.labelTips.TabIndex = 3;
this.labelSourceCode.Text = "Source Code"; this.labelTips.Text = "Tips && Tricks";
this.labelSourceCode.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; this.labelTips.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.labelSourceCode.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked); this.labelTips.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
// //
// labelWebsite // labelWebsite
// //
@@ -98,7 +98,7 @@ namespace TweetDck.Core.Other {
this.tablePanelLinks.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 35.14F)); this.tablePanelLinks.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 35.14F));
this.tablePanelLinks.Controls.Add(this.labelIssues, 2, 0); this.tablePanelLinks.Controls.Add(this.labelIssues, 2, 0);
this.tablePanelLinks.Controls.Add(this.labelWebsite, 0, 0); this.tablePanelLinks.Controls.Add(this.labelWebsite, 0, 0);
this.tablePanelLinks.Controls.Add(this.labelSourceCode, 1, 0); this.tablePanelLinks.Controls.Add(this.labelTips, 1, 0);
this.tablePanelLinks.Location = new System.Drawing.Point(12, 124); this.tablePanelLinks.Location = new System.Drawing.Point(12, 124);
this.tablePanelLinks.Name = "tablePanelLinks"; this.tablePanelLinks.Name = "tablePanelLinks";
this.tablePanelLinks.RowCount = 1; this.tablePanelLinks.RowCount = 1;
@@ -146,7 +146,7 @@ namespace TweetDck.Core.Other {
private System.Windows.Forms.PictureBox pictureLogo; private System.Windows.Forms.PictureBox pictureLogo;
private System.Windows.Forms.Label labelDescription; private System.Windows.Forms.Label labelDescription;
private System.Windows.Forms.LinkLabel labelSourceCode; private System.Windows.Forms.LinkLabel labelTips;
private System.Windows.Forms.LinkLabel labelWebsite; private System.Windows.Forms.LinkLabel labelWebsite;
private System.Windows.Forms.TableLayoutPanel tablePanelLinks; private System.Windows.Forms.TableLayoutPanel tablePanelLinks;
private System.Windows.Forms.LinkLabel labelIssues; private System.Windows.Forms.LinkLabel labelIssues;

View File

@@ -3,7 +3,7 @@ using TweetDck.Core.Utils;
namespace TweetDck.Core.Other{ namespace TweetDck.Core.Other{
sealed partial class FormAbout : Form{ sealed partial class FormAbout : Form{
private const string GitHubLink = "https://github.com/chylex/TweetDuck"; private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki";
private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues"; private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues";
public FormAbout(){ public FormAbout(){
@@ -14,7 +14,7 @@ namespace TweetDck.Core.Other{
labelDescription.Text = Program.BrandName+" 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."; labelDescription.Text = Program.BrandName+" 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)); labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
labelSourceCode.Links.Add(new LinkLabel.Link(0, labelSourceCode.Text.Length, GitHubLink)); labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink)); labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
} }

View File

@@ -42,8 +42,8 @@
| System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.labelMessage.AutoSize = true; this.labelMessage.AutoSize = true;
this.labelMessage.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelMessage.Font = System.Drawing.SystemFonts.MessageBoxFont;
this.labelMessage.Location = new System.Drawing.Point(62, 33); this.labelMessage.Location = new System.Drawing.Point(62, 34);
this.labelMessage.Margin = new System.Windows.Forms.Padding(53, 24, 27, 24); this.labelMessage.Margin = new System.Windows.Forms.Padding(53, 24, 27, 24);
this.labelMessage.MaximumSize = new System.Drawing.Size(600, 0); this.labelMessage.MaximumSize = new System.Drawing.Size(600, 0);
this.labelMessage.MinimumSize = new System.Drawing.Size(0, 24); this.labelMessage.MinimumSize = new System.Drawing.Size(0, 24);

View File

@@ -34,9 +34,13 @@ namespace TweetDck.Core.Other{
icon = SystemIcons.Error; icon = SystemIcons.Error;
break; break;
case MessageBoxIcon.Question:
icon = SystemIcons.Question;
break;
default: default:
icon = null; icon = null;
labelMessage.Location = new Point(labelMessage.Location.X-32, labelMessage.Location.Y); labelMessage.Location = new Point(labelMessage.Location.X-37, labelMessage.Location.Y);
break; break;
} }
@@ -49,6 +53,7 @@ namespace TweetDck.Core.Other{
public Button AddButton(string title){ public Button AddButton(string title){
Button button = new Button{ Button button = new Button{
Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
Font = SystemFonts.MessageBoxFont,
Location = new Point(Width-112-buttonCount*96, 12), Location = new Point(Width-112-buttonCount*96, 12),
Size = new Size(88, 26), Size = new Size(88, 26),
TabIndex = buttonCount, TabIndex = buttonCount,

View File

@@ -6,6 +6,7 @@ using System.Windows.Forms;
using TweetDck.Core.Controls; using TweetDck.Core.Controls;
using TweetDck.Plugins; using TweetDck.Plugins;
using TweetDck.Plugins.Controls; using TweetDck.Plugins.Controls;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events; using TweetDck.Plugins.Events;
namespace TweetDck.Core.Other{ namespace TweetDck.Core.Other{

View File

@@ -2,25 +2,35 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Core.Notification;
using TweetDck.Core.Other.Settings; using TweetDck.Core.Other.Settings;
using TweetDck.Plugins; using TweetDck.Plugins;
using TweetDck.Updates; using TweetDck.Updates;
namespace TweetDck.Core.Other{ namespace TweetDck.Core.Other{
sealed partial class FormSettings : Form{ sealed partial class FormSettings : Form{
private readonly Dictionary<Type, BaseTabSettings> tabs = new Dictionary<Type, BaseTabSettings>(4); public const int TabIndexNotification = 1;
public FormSettings(FormBrowser browserForm, PluginManager plugins, UpdateHandler updates){ private readonly FormBrowser browser;
private readonly Dictionary<Type, BaseTabSettings> tabs = new Dictionary<Type, BaseTabSettings>(4);
private readonly bool hasFinishedLoading;
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, int startTabIndex = 0){
InitializeComponent(); InitializeComponent();
Text = Program.BrandName+" Settings"; Text = Program.BrandName+" Settings";
this.browser = browser;
this.browser.PauseNotification();
this.tabPanel.SetupTabPanel(100); this.tabPanel.SetupTabPanel(100);
this.tabPanel.AddButton("General", SelectTab<TabSettingsGeneral>); this.tabPanel.AddButton("General", SelectTab<TabSettingsGeneral>);
this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browserForm.CreateNotificationForm(false)))); this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browser.CreateNotificationForm(NotificationFlags.DisableContextMenu), !hasFinishedLoading)));
this.tabPanel.AddButton("Updates", () => SelectTab(() => new TabSettingsUpdates(updates))); this.tabPanel.AddButton("Updates", () => SelectTab(() => new TabSettingsUpdates(updates)));
this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browserForm.ReloadBrowser, plugins))); this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browser.ReinjectCustomCSS, plugins)));
this.tabPanel.SelectTab(tabPanel.Buttons.First());
this.tabPanel.SelectTab(tabPanel.Buttons.ElementAt(startTabIndex));
hasFinishedLoading = true;
} }
private void SelectTab<T>() where T : BaseTabSettings, new(){ private void SelectTab<T>() where T : BaseTabSettings, new(){
@@ -41,11 +51,17 @@ namespace TweetDck.Core.Other{
} }
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){ private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
foreach(BaseTabSettings control in tabs.Values){
control.OnClosing();
}
Program.UserConfig.Save(); Program.UserConfig.Save();
foreach(BaseTabSettings control in tabs.Values){ foreach(BaseTabSettings control in tabs.Values){
control.Dispose(); control.Dispose();
} }
browser.ResumeNotification();
} }
private void btnClose_Click(object sender, EventArgs e){ private void btnClose_Click(object sender, EventArgs e){

View File

@@ -1,5 +1,4 @@
using System.Diagnostics; using System.Windows.Forms;
using System.Windows.Forms;
using TweetDck.Configuration; using TweetDck.Configuration;
namespace TweetDck.Core.Other.Settings{ namespace TweetDck.Core.Other.Settings{
@@ -16,10 +15,11 @@ namespace TweetDck.Core.Other.Settings{
Padding = new Padding(6); Padding = new Padding(6);
} }
public virtual void OnClosing(){}
protected static void PromptRestart(){ protected static void PromptRestart(){
if (MessageBox.Show("The application must restart for the setting to take place. Do you want to restart now?", Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes){ if (MessageBox.Show("The application must restart for the setting to take place. Do you want to restart now?", Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes){
Process.Start(Application.ExecutablePath, "-restart"); Program.Restart();
Application.Exit();
} }
} }
} }

View File

@@ -23,6 +23,7 @@
/// the contents of this method with the code editor. /// the contents of this method with the code editor.
/// </summary> /// </summary>
private void InitializeComponent() { private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.textBoxBrowserCSS = new System.Windows.Forms.TextBox(); this.textBoxBrowserCSS = new System.Windows.Forms.TextBox();
this.btnCancel = new System.Windows.Forms.Button(); this.btnCancel = new System.Windows.Forms.Button();
this.btnApply = new System.Windows.Forms.Button(); this.btnApply = new System.Windows.Forms.Button();
@@ -31,6 +32,8 @@
this.labelNotification = new System.Windows.Forms.Label(); this.labelNotification = new System.Windows.Forms.Label();
this.textBoxNotificationCSS = new System.Windows.Forms.TextBox(); this.textBoxNotificationCSS = new System.Windows.Forms.TextBox();
this.labelWarning = new System.Windows.Forms.Label(); 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(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
this.splitContainer.Panel1.SuspendLayout(); this.splitContainer.Panel1.SuspendLayout();
this.splitContainer.Panel2.SuspendLayout(); this.splitContainer.Panel2.SuspendLayout();
@@ -48,14 +51,15 @@
this.textBoxBrowserCSS.Multiline = true; this.textBoxBrowserCSS.Multiline = true;
this.textBoxBrowserCSS.Name = "textBoxBrowserCSS"; this.textBoxBrowserCSS.Name = "textBoxBrowserCSS";
this.textBoxBrowserCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both; this.textBoxBrowserCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxBrowserCSS.Size = new System.Drawing.Size(226, 193); this.textBoxBrowserCSS.Size = new System.Drawing.Size(373, 253);
this.textBoxBrowserCSS.TabIndex = 0; this.textBoxBrowserCSS.TabIndex = 0;
this.textBoxBrowserCSS.WordWrap = false; this.textBoxBrowserCSS.WordWrap = false;
this.textBoxBrowserCSS.KeyUp += new System.Windows.Forms.KeyEventHandler(this.textBoxBrowserCSS_KeyUp);
// //
// btnCancel // btnCancel
// //
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.Location = new System.Drawing.Point(354, 227); this.btnCancel.Location = new System.Drawing.Point(654, 287);
this.btnCancel.Name = "btnCancel"; this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23); this.btnCancel.Size = new System.Drawing.Size(56, 23);
@@ -67,7 +71,7 @@
// btnApply // btnApply
// //
this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnApply.Location = new System.Drawing.Point(416, 227); this.btnApply.Location = new System.Drawing.Point(716, 287);
this.btnApply.Name = "btnApply"; this.btnApply.Name = "btnApply";
this.btnApply.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnApply.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnApply.Size = new System.Drawing.Size(56, 23); this.btnApply.Size = new System.Drawing.Size(56, 23);
@@ -95,8 +99,8 @@
this.splitContainer.Panel2.Controls.Add(this.labelNotification); this.splitContainer.Panel2.Controls.Add(this.labelNotification);
this.splitContainer.Panel2.Controls.Add(this.textBoxNotificationCSS); this.splitContainer.Panel2.Controls.Add(this.textBoxNotificationCSS);
this.splitContainer.Panel2MinSize = 64; this.splitContainer.Panel2MinSize = 64;
this.splitContainer.Size = new System.Drawing.Size(460, 209); this.splitContainer.Size = new System.Drawing.Size(760, 269);
this.splitContainer.SplitterDistance = 226; this.splitContainer.SplitterDistance = 373;
this.splitContainer.SplitterWidth = 5; this.splitContainer.SplitterWidth = 5;
this.splitContainer.TabIndex = 5; this.splitContainer.TabIndex = 5;
// //
@@ -131,7 +135,7 @@
this.textBoxNotificationCSS.Multiline = true; this.textBoxNotificationCSS.Multiline = true;
this.textBoxNotificationCSS.Name = "textBoxNotificationCSS"; this.textBoxNotificationCSS.Name = "textBoxNotificationCSS";
this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both; this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxNotificationCSS.Size = new System.Drawing.Size(226, 193); this.textBoxNotificationCSS.Size = new System.Drawing.Size(373, 253);
this.textBoxNotificationCSS.TabIndex = 1; this.textBoxNotificationCSS.TabIndex = 1;
this.textBoxNotificationCSS.WordWrap = false; this.textBoxNotificationCSS.WordWrap = false;
// //
@@ -139,22 +143,41 @@
// //
this.labelWarning.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.labelWarning.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.labelWarning.AutoSize = true; this.labelWarning.AutoSize = true;
this.labelWarning.Location = new System.Drawing.Point(9, 232); this.labelWarning.Location = new System.Drawing.Point(91, 292);
this.labelWarning.Name = "labelWarning"; this.labelWarning.Name = "labelWarning";
this.labelWarning.Size = new System.Drawing.Size(341, 13); this.labelWarning.Size = new System.Drawing.Size(341, 13);
this.labelWarning.TabIndex = 6; this.labelWarning.TabIndex = 6;
this.labelWarning.Text = "The code is not validated, please make sure there are no syntax errors."; 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.AutoSize = true;
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 = 7;
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 // DialogSettingsCSS
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(484, 262); this.ClientSize = new System.Drawing.Size(784, 322);
this.Controls.Add(this.btnOpenWiki);
this.Controls.Add(this.labelWarning); this.Controls.Add(this.labelWarning);
this.Controls.Add(this.splitContainer); this.Controls.Add(this.splitContainer);
this.Controls.Add(this.btnApply); this.Controls.Add(this.btnApply);
this.Controls.Add(this.btnCancel); this.Controls.Add(this.btnCancel);
this.MinimumSize = new System.Drawing.Size(500, 160); this.MinimumSize = new System.Drawing.Size(600, 160);
this.Name = "DialogSettingsCSS"; this.Name = "DialogSettingsCSS";
this.ShowIcon = false; this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
@@ -179,5 +202,7 @@
private System.Windows.Forms.Label labelBrowser; private System.Windows.Forms.Label labelBrowser;
private System.Windows.Forms.Label labelNotification; private System.Windows.Forms.Label labelNotification;
private System.Windows.Forms.Label labelWarning; private System.Windows.Forms.Label labelWarning;
private System.Windows.Forms.Button btnOpenWiki;
private System.Windows.Forms.Timer timerTestBrowser;
} }
} }

View File

@@ -1,5 +1,7 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Other.Settings.Dialogs{ namespace TweetDck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsCSS : Form{ sealed partial class DialogSettingsCSS : Form{
@@ -15,15 +17,36 @@ namespace TweetDck.Core.Other.Settings.Dialogs{
} }
} }
public DialogSettingsCSS(){ private readonly Action<string> reinjectBrowserCSS;
public DialogSettingsCSS(Action<string> reinjectBrowserCSS){
InitializeComponent(); InitializeComponent();
Text = Program.BrandName+" Settings - CSS"; Text = Program.BrandName+" Settings - CSS";
this.reinjectBrowserCSS = reinjectBrowserCSS;
textBoxBrowserCSS.EnableMultilineShortcuts();
textBoxBrowserCSS.Text = Program.UserConfig.CustomBrowserCSS ?? ""; textBoxBrowserCSS.Text = Program.UserConfig.CustomBrowserCSS ?? "";
textBoxNotificationCSS.EnableMultilineShortcuts();
textBoxNotificationCSS.Text = Program.UserConfig.CustomNotificationCSS ?? ""; 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.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki");
}
private void btnApply_Click(object sender, EventArgs e){ private void btnApply_Click(object sender, EventArgs e){
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();

View File

@@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Core.Utils; using TweetDck.Core.Utils;
namespace TweetDck.Core.Other.Settings.Dialogs{ namespace TweetDck.Core.Other.Settings.Dialogs{
@@ -16,6 +16,7 @@ namespace TweetDck.Core.Other.Settings.Dialogs{
Text = Program.BrandName+" Settings - CEF Arguments"; Text = Program.BrandName+" Settings - CEF Arguments";
textBoxArgs.EnableMultilineShortcuts();
textBoxArgs.Text = Program.UserConfig.CustomCefArgs ?? ""; textBoxArgs.Text = Program.UserConfig.CustomCefArgs ?? "";
textBoxArgs.Select(textBoxArgs.Text.Length, 0); textBoxArgs.Select(textBoxArgs.Text.Length, 0);
} }
@@ -33,7 +34,7 @@ namespace TweetDck.Core.Other.Settings.Dialogs{
return; return;
} }
int count = CommandLineArgsParser.AddToDictionary(CefArgs, new Dictionary<string, string>()); int count = CommandLineArgsParser.ReadCefArguments(CefArgs).Count;
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All arguments will be removed from the settings. Continue?" : count+(count == 1 ? " argument" : " arguments")+" will be added to the settings. Continue?"; string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All arguments will be removed from the settings. Continue?" : count+(count == 1 ? " argument" : " arguments")+" will be added to the settings. Continue?";
if (MessageBox.Show(prompt, "Confirm CEF Arguments", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.OK){ if (MessageBox.Show(prompt, "Confirm CEF Arguments", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.OK){

View File

@@ -0,0 +1,126 @@
namespace TweetDck.Core.Other.Settings.Dialogs {
partial class DialogSettingsExport {
/// <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.btnApply = new System.Windows.Forms.Button();
this.cbConfig = new System.Windows.Forms.CheckBox();
this.cbSession = new System.Windows.Forms.CheckBox();
this.cbPluginData = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.SuspendLayout();
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.AutoSize = true;
this.btnCancel.Location = new System.Drawing.Point(176, 97);
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 = 0;
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.AutoSize = true;
this.btnApply.Location = new System.Drawing.Point(117, 97);
this.btnApply.Name = "btnApply";
this.btnApply.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnApply.Size = new System.Drawing.Size(53, 23);
this.btnApply.TabIndex = 1;
this.btnApply.UseVisualStyleBackColor = true;
this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
//
// cbConfig
//
this.cbConfig.AutoSize = true;
this.cbConfig.Location = new System.Drawing.Point(13, 13);
this.cbConfig.Name = "cbConfig";
this.cbConfig.Size = new System.Drawing.Size(106, 17);
this.cbConfig.TabIndex = 2;
this.cbConfig.Text = "Program Settings";
this.toolTip.SetToolTip(this.cbConfig, "Interface, notification, and update settings.\r\nIncludes a list of disabled plugin" +
"s.");
this.cbConfig.UseVisualStyleBackColor = true;
this.cbConfig.CheckedChanged += new System.EventHandler(this.cbConfig_CheckedChanged);
//
// cbSession
//
this.cbSession.AutoSize = true;
this.cbSession.Location = new System.Drawing.Point(13, 37);
this.cbSession.Name = "cbSession";
this.cbSession.Size = new System.Drawing.Size(92, 17);
this.cbSession.TabIndex = 3;
this.cbSession.Text = "Login Session";
this.toolTip.SetToolTip(this.cbSession, "A token that allows logging into the\r\ncurrent TweetDeck account.");
this.cbSession.UseVisualStyleBackColor = true;
this.cbSession.CheckedChanged += new System.EventHandler(this.cbSession_CheckedChanged);
//
// cbPluginData
//
this.cbPluginData.AutoSize = true;
this.cbPluginData.Location = new System.Drawing.Point(13, 61);
this.cbPluginData.Name = "cbPluginData";
this.cbPluginData.Size = new System.Drawing.Size(81, 17);
this.cbPluginData.TabIndex = 4;
this.cbPluginData.Text = "Plugin Data";
this.toolTip.SetToolTip(this.cbPluginData, "Data files generated by plugins.\r\nDoes not include the plugins themselves.");
this.cbPluginData.UseVisualStyleBackColor = true;
this.cbPluginData.CheckedChanged += new System.EventHandler(this.cbPluginData_CheckedChanged);
//
// DialogSettingsExport
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(244, 132);
this.Controls.Add(this.cbPluginData);
this.Controls.Add(this.cbSession);
this.Controls.Add(this.cbConfig);
this.Controls.Add(this.btnApply);
this.Controls.Add(this.btnCancel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MinimumSize = new System.Drawing.Size(200, 170);
this.Name = "DialogSettingsExport";
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 btnApply;
private System.Windows.Forms.CheckBox cbConfig;
private System.Windows.Forms.CheckBox cbSession;
private System.Windows.Forms.CheckBox cbPluginData;
private System.Windows.Forms.ToolTip toolTip;
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Windows.Forms;
using TweetDck.Core.Other.Settings.Export;
namespace TweetDck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsExport : Form{
public static DialogSettingsExport Import(ExportFileFlags flags){
return new DialogSettingsExport(flags);
}
public static DialogSettingsExport Export(){
return new DialogSettingsExport(ExportFileFlags.None);
}
public ExportFileFlags Flags{
get{
return selectedFlags;
}
set{
selectedFlags = value;
btnApply.Enabled = selectedFlags != ExportFileFlags.None;
cbConfig.Checked = selectedFlags.HasFlag(ExportFileFlags.Config);
cbSession.Checked = selectedFlags.HasFlag(ExportFileFlags.Session);
cbPluginData.Checked = selectedFlags.HasFlag(ExportFileFlags.PluginData);
}
}
private ExportFileFlags selectedFlags = ExportFileFlags.None;
private DialogSettingsExport(ExportFileFlags importFlags){
InitializeComponent();
bool isExporting = importFlags == ExportFileFlags.None;
if (isExporting){
Text = "Export Profile";
btnApply.Text = "Export";
Flags = ExportFileFlags.All & ~ExportFileFlags.Session;
}
else{
Text = "Import Profile";
btnApply.Text = "Import";
Flags = importFlags;
cbConfig.Enabled = cbConfig.Checked;
cbSession.Enabled = cbSession.Checked;
cbPluginData.Enabled = cbPluginData.Checked;
}
}
private void SetFlag(ExportFileFlags flag, bool enable){
selectedFlags = enable ? selectedFlags | flag : selectedFlags & ~flag;
btnApply.Enabled = selectedFlags != ExportFileFlags.None;
}
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 btnApply_Click(object sender, EventArgs e){
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e){
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@@ -4,7 +4,7 @@ using System.Text;
namespace TweetDck.Core.Other.Settings.Export{ namespace TweetDck.Core.Other.Settings.Export{
class CombinedFileStream : IDisposable{ class CombinedFileStream : IDisposable{
public const char KeySeparator = '/'; public const char KeySeparator = '|';
private readonly Stream stream; private readonly Stream stream;
@@ -12,6 +12,10 @@ namespace TweetDck.Core.Other.Settings.Export{
this.stream = stream; this.stream = stream;
} }
public void WriteFile(string[] identifier, string path){
WriteFile(string.Join(KeySeparator.ToString(), identifier), path);
}
public void WriteFile(string identifier, string path){ public void WriteFile(string identifier, string path){
byte[] name = Encoding.UTF8.GetBytes(identifier); byte[] name = Encoding.UTF8.GetBytes(identifier);
@@ -59,6 +63,26 @@ namespace TweetDck.Core.Other.Settings.Export{
return new Entry(Encoding.UTF8.GetString(name), contents); return new Entry(Encoding.UTF8.GetString(name), contents);
} }
public string SkipFile(){
int nameLength = stream.ReadByte();
if (nameLength == -1){
return null;
}
byte[] name = new byte[nameLength];
stream.Read(name, 0, nameLength);
byte[] contentLength = new byte[4];
stream.Read(contentLength, 0, 4);
stream.Position += BitConverter.ToInt32(contentLength, 0);
string keyName = Encoding.UTF8.GetString(name);
int separatorIndex = keyName.IndexOf(KeySeparator);
return separatorIndex == -1 ? keyName : keyName.Substring(0, separatorIndex);
}
public void Flush(){ public void Flush(){
stream.Flush(); stream.Flush();
} }
@@ -77,6 +101,13 @@ namespace TweetDck.Core.Other.Settings.Export{
} }
} }
public string[] KeyValue{
get{
int index = Identifier.IndexOf(KeySeparator);
return index == -1 ? new string[0] : Identifier.Substring(index+1).Split(KeySeparator);
}
}
private readonly byte[] contents; private readonly byte[] contents;
public Entry(string identifier, byte[] contents){ public Entry(string identifier, byte[] contents){
@@ -90,8 +121,11 @@ namespace TweetDck.Core.Other.Settings.Export{
public void WriteToFile(string path, bool createDirectory){ public void WriteToFile(string path, bool createDirectory){
if (createDirectory){ if (createDirectory){
// ReSharper disable once AssignNullToNotNullAttribute string dir = Path.GetDirectoryName(path);
Directory.CreateDirectory(Path.GetDirectoryName(path));
if (!string.IsNullOrEmpty(dir)){
Directory.CreateDirectory(dir);
}
} }
File.WriteAllBytes(path, contents); File.WriteAllBytes(path, contents);

View File

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

View File

@@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Plugins; using TweetDck.Plugins;
using TweetDck.Plugins.Enums;
namespace TweetDck.Core.Other.Settings.Export{ namespace TweetDck.Core.Other.Settings.Export{
sealed class ExportManager{ sealed class ExportManager{
@@ -22,41 +22,26 @@ namespace TweetDck.Core.Other.Settings.Export{
this.plugins = plugins; this.plugins = plugins;
} }
public bool Export(bool includeSession){ public bool Export(ExportFileFlags flags){
try{ try{
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){ using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
if (flags.HasFlag(ExportFileFlags.Config)){
stream.WriteFile("config", Program.ConfigFilePath); stream.WriteFile("config", Program.ConfigFilePath);
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathOfficialPlugins)){
string[] split = path.Relative.Split(CombinedFileStream.KeySeparator);
if (split.Length < 3){
continue;
}
else if (split.Length == 3){
if (split[2].Equals(".meta", StringComparison.OrdinalIgnoreCase) ||
split[2].Equals("browser.js", StringComparison.OrdinalIgnoreCase) ||
split[2].Equals("notification.js", StringComparison.OrdinalIgnoreCase)){
continue;
}
} }
if (flags.HasFlag(ExportFileFlags.PluginData)){
foreach(Plugin plugin in plugins.Plugins){
foreach(PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))){
try{ try{
stream.WriteFile("plugin.off"+path.Relative, path.Full); stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
}catch(ArgumentOutOfRangeException e){ }catch(ArgumentOutOfRangeException e){
MessageBox.Show("Could not include a file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning); MessageBox.Show("Could not include a plugin file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
} }
} }
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathCustomPlugins)){ if (flags.HasFlag(ExportFileFlags.Session)){
try{
stream.WriteFile("plugin.usr"+path.Relative, path.Full);
}catch(ArgumentOutOfRangeException e){
MessageBox.Show("Could not include a file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
if (includeSession){
stream.WriteFile("cookies", CookiesPath); stream.WriteFile("cookies", CookiesPath);
} }
@@ -70,9 +55,40 @@ namespace TweetDck.Core.Other.Settings.Export{
} }
} }
public bool Import(){ public ExportFileFlags GetImportFlags(){
ExportFileFlags flags = ExportFileFlags.None;
try{ try{
bool updatedPlugins = false; using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
string key;
while((key = stream.SkipFile()) != null){
switch(key){
case "config":
flags |= ExportFileFlags.Config;
break;
case "plugin.data":
flags |= ExportFileFlags.PluginData;
break;
case "cookies":
flags |= ExportFileFlags.Session;
break;
}
}
}
}catch(Exception e){
LastException = e;
flags = ExportFileFlags.None;
}
return flags;
}
public bool Import(ExportFileFlags flags){
try{
HashSet<string> missingPlugins = new HashSet<string>();
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){ using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
CombinedFileStream.Entry entry; CombinedFileStream.Entry entry;
@@ -80,33 +96,30 @@ namespace TweetDck.Core.Other.Settings.Export{
while((entry = stream.ReadFile()) != null){ while((entry = stream.ReadFile()) != null){
switch(entry.KeyName){ switch(entry.KeyName){
case "config": case "config":
if (flags.HasFlag(ExportFileFlags.Config)){
entry.WriteToFile(Program.ConfigFilePath); entry.WriteToFile(Program.ConfigFilePath);
Program.ReloadConfig(); Program.ReloadConfig();
break;
case "plugin.off":
string root = Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Split(CombinedFileStream.KeySeparator)[1]);
if (Directory.Exists(root)){
entry.WriteToFile(Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
updatedPlugins = true;
} }
break; break;
case "plugin.usr": case "plugin.data":
entry.WriteToFile(Path.Combine(plugins.PathCustomPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true); if (flags.HasFlag(ExportFileFlags.PluginData)){
updatedPlugins = true; string[] value = entry.KeyValue;
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
if (!plugins.IsPluginInstalled(value[0])){
missingPlugins.Add(value[0]);
}
}
break; break;
case "cookies": case "cookies":
if (MessageBox.Show("Do you want to import the login session? This will restart "+Program.BrandName+".", "Importing "+Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){ if (flags.HasFlag(ExportFileFlags.Session) && MessageBox.Show("Do you want to import the login session? This will restart "+Program.BrandName+".", "Importing "+Program.BrandName+" Profile", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath)); entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
// okay to and restart, 'cookies' is always the last entry
IsRestarting = true; IsRestarting = true;
Process.Start(Application.ExecutablePath, "-restart -importcookies");
Application.Exit();
} }
break; break;
@@ -114,7 +127,14 @@ namespace TweetDck.Core.Other.Settings.Export{
} }
} }
if (updatedPlugins){ if (missingPlugins.Count > 0){
MessageBox.Show("Detected missing plugins when importing plugin data:"+Environment.NewLine+string.Join(Environment.NewLine, missingPlugins), "Importing "+Program.BrandName+" Profile", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
if (IsRestarting){
Program.Restart(new string[]{ "-importcookies" });
}
else{
plugins.Reload(); plugins.Reload();
} }
@@ -140,10 +160,10 @@ namespace TweetDck.Core.Other.Settings.Export{
} }
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){ private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{ return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
Full = fullPath, Full = fullPath,
Relative = fullPath.Substring(root.Length).Replace(Path.DirectorySeparatorChar, CombinedFileStream.KeySeparator) // includes leading separator character Relative = fullPath.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) // strip leading separator character
}); }) : Enumerable.Empty<PathInfo>();
} }
private class PathInfo{ private class PathInfo{

View File

@@ -29,13 +29,19 @@
this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.btnEditCefArgs = new System.Windows.Forms.Button(); this.btnEditCefArgs = new System.Windows.Forms.Button();
this.btnEditCSS = new System.Windows.Forms.Button(); this.btnEditCSS = new System.Windows.Forms.Button();
this.btnRestartLog = 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.btnReset = new System.Windows.Forms.Button(); this.btnReset = new System.Windows.Forms.Button();
this.btnImport = new System.Windows.Forms.Button(); this.btnImport = new System.Windows.Forms.Button();
this.btnExport = new System.Windows.Forms.Button(); this.btnExport = new System.Windows.Forms.Button();
this.groupPerformance = new System.Windows.Forms.GroupBox(); this.groupPerformance = new System.Windows.Forms.GroupBox();
this.groupConfiguration = new System.Windows.Forms.GroupBox(); this.groupConfiguration = new System.Windows.Forms.GroupBox();
this.groupApp = new System.Windows.Forms.GroupBox();
this.groupPerformance.SuspendLayout(); this.groupPerformance.SuspendLayout();
this.groupConfiguration.SuspendLayout(); this.groupConfiguration.SuspendLayout();
this.groupApp.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// btnClearCache // btnClearCache
@@ -86,6 +92,52 @@
this.btnEditCSS.UseVisualStyleBackColor = true; this.btnEditCSS.UseVisualStyleBackColor = true;
this.btnEditCSS.Click += new System.EventHandler(this.btnEditCSS_Click); this.btnEditCSS.Click += new System.EventHandler(this.btnEditCSS_Click);
// //
// btnRestartLog
//
this.btnRestartLog.Location = new System.Drawing.Point(6, 106);
this.btnRestartLog.Name = "btnRestartLog";
this.btnRestartLog.Size = new System.Drawing.Size(171, 23);
this.btnRestartLog.TabIndex = 18;
this.btnRestartLog.Text = "Restart with Logging";
this.toolTip.SetToolTip(this.btnRestartLog, "Restarts the program and enables logging\r\ninto a debug.txt file in the data folde" +
"r.");
this.btnRestartLog.UseVisualStyleBackColor = true;
this.btnRestartLog.Click += new System.EventHandler(this.btnRestartLog_Click);
//
// btnRestart
//
this.btnRestart.Location = new System.Drawing.Point(6, 77);
this.btnRestart.Name = "btnRestart";
this.btnRestart.Size = new System.Drawing.Size(171, 23);
this.btnRestart.TabIndex = 17;
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 lau" +
"nch.");
this.btnRestart.UseVisualStyleBackColor = true;
this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click);
//
// btnOpenAppFolder
//
this.btnOpenAppFolder.Location = new System.Drawing.Point(6, 19);
this.btnOpenAppFolder.Name = "btnOpenAppFolder";
this.btnOpenAppFolder.Size = new System.Drawing.Size(171, 23);
this.btnOpenAppFolder.TabIndex = 16;
this.btnOpenAppFolder.Text = "Open Program Folder";
this.toolTip.SetToolTip(this.btnOpenAppFolder, "Opens the folder where the app is located.");
this.btnOpenAppFolder.UseVisualStyleBackColor = true;
this.btnOpenAppFolder.Click += new System.EventHandler(this.btnOpenAppFolder_Click);
//
// btnOpenDataFolder
//
this.btnOpenDataFolder.Location = new System.Drawing.Point(6, 48);
this.btnOpenDataFolder.Name = "btnOpenDataFolder";
this.btnOpenDataFolder.Size = new System.Drawing.Size(171, 23);
this.btnOpenDataFolder.TabIndex = 19;
this.btnOpenDataFolder.Text = "Open Data Folder";
this.toolTip.SetToolTip(this.btnOpenDataFolder, "Opens the folder where your profile data is located.");
this.btnOpenDataFolder.UseVisualStyleBackColor = true;
this.btnOpenDataFolder.Click += new System.EventHandler(this.btnOpenDataFolder_Click);
//
// btnReset // btnReset
// //
this.btnReset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.btnReset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
@@ -148,10 +200,24 @@
this.groupConfiguration.TabStop = false; this.groupConfiguration.TabStop = false;
this.groupConfiguration.Text = "Configuration"; this.groupConfiguration.Text = "Configuration";
// //
// groupApp
//
this.groupApp.Controls.Add(this.btnOpenDataFolder);
this.groupApp.Controls.Add(this.btnOpenAppFolder);
this.groupApp.Controls.Add(this.btnRestartLog);
this.groupApp.Controls.Add(this.btnRestart);
this.groupApp.Location = new System.Drawing.Point(198, 9);
this.groupApp.Name = "groupApp";
this.groupApp.Size = new System.Drawing.Size(183, 135);
this.groupApp.TabIndex = 20;
this.groupApp.TabStop = false;
this.groupApp.Text = "App";
//
// TabSettingsAdvanced // TabSettingsAdvanced
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.groupApp);
this.Controls.Add(this.groupConfiguration); this.Controls.Add(this.groupConfiguration);
this.Controls.Add(this.groupPerformance); this.Controls.Add(this.groupPerformance);
this.Controls.Add(this.btnReset); this.Controls.Add(this.btnReset);
@@ -162,6 +228,7 @@
this.groupPerformance.ResumeLayout(false); this.groupPerformance.ResumeLayout(false);
this.groupPerformance.PerformLayout(); this.groupPerformance.PerformLayout();
this.groupConfiguration.ResumeLayout(false); this.groupConfiguration.ResumeLayout(false);
this.groupApp.ResumeLayout(false);
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout(); this.PerformLayout();
@@ -179,5 +246,10 @@
private System.Windows.Forms.GroupBox groupConfiguration; private System.Windows.Forms.GroupBox groupConfiguration;
private System.Windows.Forms.Button btnEditCefArgs; private System.Windows.Forms.Button btnEditCefArgs;
private System.Windows.Forms.Button btnEditCSS; private System.Windows.Forms.Button btnEditCSS;
private System.Windows.Forms.GroupBox groupApp;
private System.Windows.Forms.Button btnRestartLog;
private System.Windows.Forms.Button btnRestart;
private System.Windows.Forms.Button btnOpenAppFolder;
private System.Windows.Forms.Button btnOpenDataFolder;
} }
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Core.Controls; using TweetDck.Core.Controls;
using TweetDck.Core.Other.Settings.Dialogs; using TweetDck.Core.Other.Settings.Dialogs;
@@ -8,13 +9,13 @@ using TweetDck.Plugins;
namespace TweetDck.Core.Other.Settings{ namespace TweetDck.Core.Other.Settings{
partial class TabSettingsAdvanced : BaseTabSettings{ partial class TabSettingsAdvanced : BaseTabSettings{
private readonly Action browserReloadAction; private readonly Action<string> reinjectBrowserCSS;
private readonly PluginManager plugins; private readonly PluginManager plugins;
public TabSettingsAdvanced(Action browserReloadAction, PluginManager plugins){ public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, PluginManager plugins){
InitializeComponent(); InitializeComponent();
this.browserReloadAction = browserReloadAction; this.reinjectBrowserCSS = reinjectBrowserCSS;
this.plugins = plugins; this.plugins = plugins;
checkHardwareAcceleration.Checked = HardwareAcceleration.IsEnabled; checkHardwareAcceleration.Checked = HardwareAcceleration.IsEnabled;
@@ -70,30 +71,36 @@ namespace TweetDck.Core.Other.Settings{
if (form.ShowDialog(ParentForm) == DialogResult.OK){ if (form.ShowDialog(ParentForm) == DialogResult.OK){
Config.CustomCefArgs = form.CefArgs; Config.CustomCefArgs = form.CefArgs;
form.Dispose();
PromptRestart(); PromptRestart();
} }
else{
form.Dispose();
}
} }
private void btnEditCSS_Click(object sender, EventArgs e){ private void btnEditCSS_Click(object sender, EventArgs e){
DialogSettingsCSS form = new DialogSettingsCSS(); using(DialogSettingsCSS form = new DialogSettingsCSS(reinjectBrowserCSS)){
if (form.ShowDialog(ParentForm) == DialogResult.OK){ if (form.ShowDialog(ParentForm) == DialogResult.OK){
bool hasChangedBrowser = form.BrowserCSS != Config.CustomBrowserCSS;
Config.CustomBrowserCSS = form.BrowserCSS; Config.CustomBrowserCSS = form.BrowserCSS;
Config.CustomNotificationCSS = form.NotificationCSS; Config.CustomNotificationCSS = form.NotificationCSS;
if (hasChangedBrowser && MessageBox.Show("The browser CSS has changed, do you want to reload it?", "Browser CSS Changed", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
browserReloadAction();
} }
reinjectBrowserCSS(Config.CustomBrowserCSS); // reinject on cancel too, because the CSS is updated while typing
} }
} }
private void btnExport_Click(object sender, EventArgs e){ private void btnExport_Click(object sender, EventArgs e){
DialogResult resultSaveCredentials = MessageBox.Show("Do you want to include your login session? This will not save your password into the file, but it will allow anyone with the file to login into TweetDeck as you.", "Export "+Program.BrandName+" Settings", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button3); ExportFileFlags flags;
if (resultSaveCredentials == DialogResult.Cancel)return;
using(DialogSettingsExport dialog = DialogSettingsExport.Export()){
if (dialog.ShowDialog() != DialogResult.OK){
return;
}
flags = dialog.Flags;
}
bool saveCredentials = resultSaveCredentials == DialogResult.Yes;
string file; string file;
using(SaveFileDialog dialog = new SaveFileDialog{ using(SaveFileDialog dialog = new SaveFileDialog{
@@ -105,19 +112,21 @@ namespace TweetDck.Core.Other.Settings{
Title = "Export "+Program.BrandName+" Settings", Title = "Export "+Program.BrandName+" Settings",
Filter = Program.BrandName+" Settings (*.tdsettings)|*.tdsettings" Filter = Program.BrandName+" Settings (*.tdsettings)|*.tdsettings"
}){ }){
file = dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null; if (dialog.ShowDialog() != DialogResult.OK){
return;
}
file = dialog.FileName;
} }
if (file != null){
Program.UserConfig.Save(); Program.UserConfig.Save();
ExportManager manager = new ExportManager(file, plugins); ExportManager manager = new ExportManager(file, plugins);
if (!manager.Export(saveCredentials)){ if (!manager.Export(flags)){
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting "+Program.BrandName+" settings.", true, manager.LastException); Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting "+Program.BrandName+" settings.", true, manager.LastException);
} }
} }
}
private void btnImport_Click(object sender, EventArgs e){ private void btnImport_Click(object sender, EventArgs e){
string file; string file;
@@ -128,13 +137,25 @@ namespace TweetDck.Core.Other.Settings{
Title = "Import "+Program.BrandName+" Settings", Title = "Import "+Program.BrandName+" Settings",
Filter = Program.BrandName+" Settings (*.tdsettings)|*.tdsettings" Filter = Program.BrandName+" Settings (*.tdsettings)|*.tdsettings"
}){ }){
file = dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null; if (dialog.ShowDialog() != DialogResult.OK){
return;
} }
if (file != null){ file = dialog.FileName;
ExportManager manager = new ExportManager(file, plugins); }
if (manager.Import()){ ExportManager manager = new ExportManager(file, plugins);
ExportFileFlags flags;
using(DialogSettingsExport dialog = DialogSettingsExport.Import(manager.GetImportFlags())){
if (dialog.ShowDialog() != DialogResult.OK){
return;
}
flags = dialog.Flags;
}
if (manager.Import(flags)){
if (!manager.IsRestarting){ if (!manager.IsRestarting){
((FormSettings)ParentForm).ReloadUI(); ((FormSettings)ParentForm).ReloadUI();
} }
@@ -143,7 +164,6 @@ namespace TweetDck.Core.Other.Settings{
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing "+Program.BrandName+" settings.", true, manager.LastException); Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing "+Program.BrandName+" settings.", true, manager.LastException);
} }
} }
}
private void btnReset_Click(object sender, EventArgs e){ private void btnReset_Click(object sender, EventArgs e){
if (MessageBox.Show("This will reset all of your settings, including disabled plugins. Do you want to proceed?", "Reset "+Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){ if (MessageBox.Show("This will reset all of your settings, including disabled plugins. Do you want to proceed?", "Reset "+Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
@@ -151,5 +171,21 @@ namespace TweetDck.Core.Other.Settings{
((FormSettings)ParentForm).ReloadUI(); ((FormSettings)ParentForm).ReloadUI();
} }
} }
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 btnRestartLog_Click(object sender, EventArgs e){
Program.Restart(new string[]{ "-log" });
}
} }
} }

View File

@@ -29,6 +29,7 @@
this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.checkTrayHighlight = new System.Windows.Forms.CheckBox(); this.checkTrayHighlight = new System.Windows.Forms.CheckBox();
this.checkSpellCheck = new System.Windows.Forms.CheckBox(); this.checkSpellCheck = new System.Windows.Forms.CheckBox();
this.checkScreenshotBorder = new System.Windows.Forms.CheckBox();
this.groupTray = new System.Windows.Forms.GroupBox(); this.groupTray = new System.Windows.Forms.GroupBox();
this.labelTrayIcon = new System.Windows.Forms.Label(); this.labelTrayIcon = new System.Windows.Forms.Label();
this.groupInterface = new System.Windows.Forms.GroupBox(); this.groupInterface = new System.Windows.Forms.GroupBox();
@@ -87,12 +88,24 @@
this.checkSpellCheck.UseVisualStyleBackColor = true; this.checkSpellCheck.UseVisualStyleBackColor = true;
this.checkSpellCheck.CheckedChanged += new System.EventHandler(this.checkSpellCheck_CheckedChanged); this.checkSpellCheck.CheckedChanged += new System.EventHandler(this.checkSpellCheck_CheckedChanged);
// //
// checkScreenshotBorder
//
this.checkScreenshotBorder.AutoSize = true;
this.checkScreenshotBorder.Location = new System.Drawing.Point(9, 67);
this.checkScreenshotBorder.Name = "checkScreenshotBorder";
this.checkScreenshotBorder.Size = new System.Drawing.Size(169, 17);
this.checkScreenshotBorder.TabIndex = 16;
this.checkScreenshotBorder.Text = "Include Border In Screenshots";
this.toolTip.SetToolTip(this.checkScreenshotBorder, "Shows the window border in tweet screenshots.");
this.checkScreenshotBorder.UseVisualStyleBackColor = true;
this.checkScreenshotBorder.CheckedChanged += new System.EventHandler(this.checkScreenshotBorder_CheckedChanged);
//
// groupTray // groupTray
// //
this.groupTray.Controls.Add(this.checkTrayHighlight); this.groupTray.Controls.Add(this.checkTrayHighlight);
this.groupTray.Controls.Add(this.labelTrayIcon); this.groupTray.Controls.Add(this.labelTrayIcon);
this.groupTray.Controls.Add(this.comboBoxTrayType); this.groupTray.Controls.Add(this.comboBoxTrayType);
this.groupTray.Location = new System.Drawing.Point(9, 86); this.groupTray.Location = new System.Drawing.Point(9, 109);
this.groupTray.Name = "groupTray"; this.groupTray.Name = "groupTray";
this.groupTray.Size = new System.Drawing.Size(183, 93); this.groupTray.Size = new System.Drawing.Size(183, 93);
this.groupTray.TabIndex = 15; this.groupTray.TabIndex = 15;
@@ -111,11 +124,12 @@
// //
// groupInterface // groupInterface
// //
this.groupInterface.Controls.Add(this.checkScreenshotBorder);
this.groupInterface.Controls.Add(this.checkSpellCheck); this.groupInterface.Controls.Add(this.checkSpellCheck);
this.groupInterface.Controls.Add(this.checkExpandLinks); this.groupInterface.Controls.Add(this.checkExpandLinks);
this.groupInterface.Location = new System.Drawing.Point(9, 9); this.groupInterface.Location = new System.Drawing.Point(9, 9);
this.groupInterface.Name = "groupInterface"; this.groupInterface.Name = "groupInterface";
this.groupInterface.Size = new System.Drawing.Size(183, 71); this.groupInterface.Size = new System.Drawing.Size(183, 90);
this.groupInterface.TabIndex = 16; this.groupInterface.TabIndex = 16;
this.groupInterface.TabStop = false; this.groupInterface.TabStop = false;
this.groupInterface.Text = "User Interface"; this.groupInterface.Text = "User Interface";
@@ -146,5 +160,6 @@
private System.Windows.Forms.Label labelTrayIcon; private System.Windows.Forms.Label labelTrayIcon;
private System.Windows.Forms.CheckBox checkTrayHighlight; private System.Windows.Forms.CheckBox checkTrayHighlight;
private System.Windows.Forms.CheckBox checkSpellCheck; private System.Windows.Forms.CheckBox checkSpellCheck;
private System.Windows.Forms.CheckBox checkScreenshotBorder;
} }
} }

View File

@@ -14,6 +14,7 @@ namespace TweetDck.Core.Other.Settings{
checkExpandLinks.Checked = Config.ExpandLinksOnHover; checkExpandLinks.Checked = Config.ExpandLinksOnHover;
checkSpellCheck.Checked = Config.EnableSpellCheck; checkSpellCheck.Checked = Config.EnableSpellCheck;
checkScreenshotBorder.Checked = Config.ShowScreenshotBorder;
checkTrayHighlight.Checked = Config.EnableTrayHighlight; checkTrayHighlight.Checked = Config.EnableTrayHighlight;
} }
@@ -30,6 +31,12 @@ namespace TweetDck.Core.Other.Settings{
PromptRestart(); PromptRestart();
} }
private void checkScreenshotBorder_CheckedChanged(object sender, EventArgs e){
if (!Ready)return;
Config.ShowScreenshotBorder = checkScreenshotBorder.Checked;
}
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){ private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
if (!Ready)return; if (!Ready)return;

View File

@@ -44,15 +44,19 @@
this.trackBarDuration = new System.Windows.Forms.TrackBar(); this.trackBarDuration = new System.Windows.Forms.TrackBar();
this.groupUserInterface = new System.Windows.Forms.GroupBox(); this.groupUserInterface = new System.Windows.Forms.GroupBox();
this.checkTimerCountDown = new System.Windows.Forms.CheckBox(); this.checkTimerCountDown = new System.Windows.Forms.CheckBox();
this.checkLegacyLoad = new System.Windows.Forms.CheckBox();
this.checkNotificationTimer = new System.Windows.Forms.CheckBox(); this.checkNotificationTimer = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.groupCustomSound = new System.Windows.Forms.GroupBox();
this.btnResetSound = new System.Windows.Forms.Button();
this.btnBrowseSound = new System.Windows.Forms.Button();
this.tbCustomSound = new System.Windows.Forms.TextBox();
this.groupNotificationLocation.SuspendLayout(); this.groupNotificationLocation.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).BeginInit();
this.groupNotificationDuration.SuspendLayout(); this.groupNotificationDuration.SuspendLayout();
this.tableLayoutDurationButtons.SuspendLayout(); this.tableLayoutDurationButtons.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).BeginInit();
this.groupUserInterface.SuspendLayout(); this.groupUserInterface.SuspendLayout();
this.groupCustomSound.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// groupNotificationLocation // groupNotificationLocation
@@ -199,7 +203,7 @@
this.groupNotificationDuration.Controls.Add(this.tableLayoutDurationButtons); this.groupNotificationDuration.Controls.Add(this.tableLayoutDurationButtons);
this.groupNotificationDuration.Controls.Add(this.labelDurationValue); this.groupNotificationDuration.Controls.Add(this.labelDurationValue);
this.groupNotificationDuration.Controls.Add(this.trackBarDuration); this.groupNotificationDuration.Controls.Add(this.trackBarDuration);
this.groupNotificationDuration.Location = new System.Drawing.Point(9, 106); this.groupNotificationDuration.Location = new System.Drawing.Point(9, 83);
this.groupNotificationDuration.Name = "groupNotificationDuration"; this.groupNotificationDuration.Name = "groupNotificationDuration";
this.groupNotificationDuration.Size = new System.Drawing.Size(183, 89); this.groupNotificationDuration.Size = new System.Drawing.Size(183, 89);
this.groupNotificationDuration.TabIndex = 9; this.groupNotificationDuration.TabIndex = 9;
@@ -302,11 +306,10 @@
// groupUserInterface // groupUserInterface
// //
this.groupUserInterface.Controls.Add(this.checkTimerCountDown); this.groupUserInterface.Controls.Add(this.checkTimerCountDown);
this.groupUserInterface.Controls.Add(this.checkLegacyLoad);
this.groupUserInterface.Controls.Add(this.checkNotificationTimer); this.groupUserInterface.Controls.Add(this.checkNotificationTimer);
this.groupUserInterface.Location = new System.Drawing.Point(9, 9); this.groupUserInterface.Location = new System.Drawing.Point(9, 9);
this.groupUserInterface.Name = "groupUserInterface"; this.groupUserInterface.Name = "groupUserInterface";
this.groupUserInterface.Size = new System.Drawing.Size(183, 91); this.groupUserInterface.Size = new System.Drawing.Size(183, 68);
this.groupUserInterface.TabIndex = 10; this.groupUserInterface.TabIndex = 10;
this.groupUserInterface.TabStop = false; this.groupUserInterface.TabStop = false;
this.groupUserInterface.Text = "General"; this.groupUserInterface.Text = "General";
@@ -323,19 +326,6 @@
this.checkTimerCountDown.UseVisualStyleBackColor = true; this.checkTimerCountDown.UseVisualStyleBackColor = true;
this.checkTimerCountDown.CheckedChanged += new System.EventHandler(this.checkTimerCountDown_CheckedChanged); this.checkTimerCountDown.CheckedChanged += new System.EventHandler(this.checkTimerCountDown_CheckedChanged);
// //
// checkLegacyLoad
//
this.checkLegacyLoad.AutoSize = true;
this.checkLegacyLoad.Location = new System.Drawing.Point(6, 67);
this.checkLegacyLoad.Name = "checkLegacyLoad";
this.checkLegacyLoad.Size = new System.Drawing.Size(139, 17);
this.checkLegacyLoad.TabIndex = 5;
this.checkLegacyLoad.Text = "Legacy Loading System";
this.toolTip.SetToolTip(this.checkLegacyLoad, "Try enabling if notifications do not display.\r\nMight cause delays and visual arti" +
"facts.");
this.checkLegacyLoad.UseVisualStyleBackColor = true;
this.checkLegacyLoad.CheckedChanged += new System.EventHandler(this.checkLegacyLoad_CheckedChanged);
//
// checkNotificationTimer // checkNotificationTimer
// //
this.checkNotificationTimer.AutoSize = true; this.checkNotificationTimer.AutoSize = true;
@@ -349,10 +339,55 @@
this.checkNotificationTimer.UseVisualStyleBackColor = true; this.checkNotificationTimer.UseVisualStyleBackColor = true;
this.checkNotificationTimer.CheckedChanged += new System.EventHandler(this.checkNotificationTimer_CheckedChanged); this.checkNotificationTimer.CheckedChanged += new System.EventHandler(this.checkNotificationTimer_CheckedChanged);
// //
// groupCustomSound
//
this.groupCustomSound.Controls.Add(this.btnResetSound);
this.groupCustomSound.Controls.Add(this.btnBrowseSound);
this.groupCustomSound.Controls.Add(this.tbCustomSound);
this.groupCustomSound.Location = new System.Drawing.Point(9, 178);
this.groupCustomSound.Name = "groupCustomSound";
this.groupCustomSound.Size = new System.Drawing.Size(183, 72);
this.groupCustomSound.TabIndex = 11;
this.groupCustomSound.TabStop = false;
this.groupCustomSound.Text = "Custom Sound";
//
// btnResetSound
//
this.btnResetSound.AutoSize = true;
this.btnResetSound.Location = new System.Drawing.Point(126, 43);
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 = 2;
this.btnResetSound.Text = "Reset";
this.btnResetSound.UseVisualStyleBackColor = true;
this.btnResetSound.Click += new System.EventHandler(this.btnResetSound_Click);
//
// btnBrowseSound
//
this.btnBrowseSound.AutoSize = true;
this.btnBrowseSound.Location = new System.Drawing.Point(53, 43);
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;
this.btnBrowseSound.Click += new System.EventHandler(this.btnBrowseSound_Click);
//
// tbCustomSound
//
this.tbCustomSound.Location = new System.Drawing.Point(6, 19);
this.tbCustomSound.Name = "tbCustomSound";
this.tbCustomSound.Size = new System.Drawing.Size(170, 20);
this.tbCustomSound.TabIndex = 0;
this.tbCustomSound.TextChanged += new System.EventHandler(this.tbCustomSound_TextChanged);
//
// TabSettingsNotifications // TabSettingsNotifications
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.groupCustomSound);
this.Controls.Add(this.groupUserInterface); this.Controls.Add(this.groupUserInterface);
this.Controls.Add(this.groupNotificationDuration); this.Controls.Add(this.groupNotificationDuration);
this.Controls.Add(this.groupNotificationLocation); this.Controls.Add(this.groupNotificationLocation);
@@ -368,6 +403,8 @@
((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).EndInit();
this.groupUserInterface.ResumeLayout(false); this.groupUserInterface.ResumeLayout(false);
this.groupUserInterface.PerformLayout(); this.groupUserInterface.PerformLayout();
this.groupCustomSound.ResumeLayout(false);
this.groupCustomSound.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
} }
@@ -389,7 +426,6 @@
private System.Windows.Forms.CheckBox checkNotificationTimer; private System.Windows.Forms.CheckBox checkNotificationTimer;
private System.Windows.Forms.ToolTip toolTip; private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Label labelEdgeDistanceValue; private System.Windows.Forms.Label labelEdgeDistanceValue;
private System.Windows.Forms.CheckBox checkLegacyLoad;
private System.Windows.Forms.CheckBox checkTimerCountDown; private System.Windows.Forms.CheckBox checkTimerCountDown;
private System.Windows.Forms.Label labelDurationValue; private System.Windows.Forms.Label labelDurationValue;
private System.Windows.Forms.TrackBar trackBarDuration; private System.Windows.Forms.TrackBar trackBarDuration;
@@ -397,5 +433,9 @@
private TweetDck.Core.Controls.FlatButton btnDurationMedium; private TweetDck.Core.Controls.FlatButton btnDurationMedium;
private TweetDck.Core.Controls.FlatButton btnDurationLong; private TweetDck.Core.Controls.FlatButton btnDurationLong;
private TweetDck.Core.Controls.FlatButton btnDurationShort; private TweetDck.Core.Controls.FlatButton btnDurationShort;
private System.Windows.Forms.GroupBox groupCustomSound;
private System.Windows.Forms.Button btnResetSound;
private System.Windows.Forms.Button btnBrowseSound;
private System.Windows.Forms.TextBox tbCustomSound;
} }
} }

View File

@@ -1,14 +1,18 @@
using System; using System;
using System.Drawing;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Core.Handling;
using TweetDck.Core.Controls; using TweetDck.Core.Controls;
using TweetDck.Core.Notification;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Other.Settings{ namespace TweetDck.Core.Other.Settings{
partial class TabSettingsNotifications : BaseTabSettings{ partial class TabSettingsNotifications : BaseTabSettings{
private readonly FormNotification notification; private readonly FormNotification notification;
private readonly Point initCursorPosition;
public TabSettingsNotifications(FormNotification notification){ public TabSettingsNotifications(FormNotification notification, bool ignoreAutoClick){
InitializeComponent(); InitializeComponent();
this.notification = notification; this.notification = notification;
@@ -24,8 +28,11 @@ namespace TweetDck.Core.Other.Settings{
this.InvokeSafe(() => this.notification.ShowNotificationForSettings(true)); this.InvokeSafe(() => this.notification.ShowNotificationForSettings(true));
}; };
this.notification.Activated += notification_Activated;
this.notification.Show(this); this.notification.Show(this);
initCursorPosition = ignoreAutoClick ? ControlExtensions.InvisibleLocation : Cursor.Position;
switch(Config.NotificationPosition){ switch(Config.NotificationPosition){
case TweetNotification.Position.TopLeft: radioLocTL.Checked = true; break; case TweetNotification.Position.TopLeft: radioLocTL.Checked = true; break;
case TweetNotification.Position.TopRight: radioLocTR.Checked = true; break; case TweetNotification.Position.TopRight: radioLocTR.Checked = true; break;
@@ -48,14 +55,19 @@ namespace TweetDck.Core.Other.Settings{
checkNotificationTimer.Checked = Config.DisplayNotificationTimer; checkNotificationTimer.Checked = Config.DisplayNotificationTimer;
checkTimerCountDown.Enabled = checkNotificationTimer.Checked; checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
checkTimerCountDown.Checked = Config.NotificationTimerCountDown; checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
checkLegacyLoad.Checked = Config.NotificationLegacyLoad;
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance); trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px"; labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px";
tbCustomSound.Text = Config.NotificationSoundPath;
Disposed += (sender, args) => this.notification.Dispose(); Disposed += (sender, args) => this.notification.Dispose();
} }
public override void OnClosing(){
Config.NotificationSoundPath = tbCustomSound.Text;
}
private void TabSettingsNotifications_ParentChanged(object sender, EventArgs e){ private void TabSettingsNotifications_ParentChanged(object sender, EventArgs e){
if (Parent == null){ if (Parent == null){
notification.HideNotification(false); notification.HideNotification(false);
@@ -65,6 +77,21 @@ namespace TweetDck.Core.Other.Settings{
} }
} }
private void notification_Activated(object sender, EventArgs e){
if (Cursor.Position == initCursorPosition && initCursorPosition != ControlExtensions.InvisibleLocation){
Timer delay = WindowsUtils.CreateSingleTickTimer(1);
delay.Tick += (sender2, args2) => { // here you can see a disgusting hack to force the freshly opened notification window out of focus
NativeMethods.SimulateMouseClick(NativeMethods.MouseButton.Left); // because for some reason, the stupid thing keeps stealing it
delay.Dispose(); // even after using ShowWithoutActivation, the CreateParams bullshit, and about a million different combinations
}; // of trying to force the original form back into focus in various events, so you will have to fucking deal with it, alright
delay.Start();
}
notification.Activated -= notification_Activated;
}
private void radioLoc_CheckedChanged(object sender, EventArgs e){ private void radioLoc_CheckedChanged(object sender, EventArgs e){
if (!Ready)return; if (!Ready)return;
@@ -126,12 +153,6 @@ namespace TweetDck.Core.Other.Settings{
notification.ShowNotificationForSettings(true); notification.ShowNotificationForSettings(true);
} }
private void checkLegacyLoad_CheckedChanged(object sender, EventArgs e){
if (!Ready)return;
Config.NotificationLegacyLoad = checkLegacyLoad.Checked;
}
private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e){ private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e){
if (!Ready)return; if (!Ready)return;
@@ -146,5 +167,29 @@ namespace TweetDck.Core.Other.Settings{
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value; Config.NotificationEdgeDistance = trackBarEdgeDistance.Value;
notification.ShowNotificationForSettings(false); notification.ShowNotificationForSettings(false);
} }
private void tbCustomSound_TextChanged(object sender, EventArgs e){
// also runs when the control is created, i.e. when Ready is false
bool fileExists = string.IsNullOrEmpty(tbCustomSound.Text) || File.Exists(tbCustomSound.Text);
tbCustomSound.ForeColor = fileExists ? SystemColors.WindowText : Color.Maroon;
}
private void btnBrowseSound_Click(object sender, EventArgs e){
using(OpenFileDialog dialog = new OpenFileDialog{
AutoUpgradeEnabled = true,
DereferenceLinks = true,
Title = "Custom Notification Sound",
Filter = "Wave file (*.wav)|*.wav"
}){
if (dialog.ShowDialog() == DialogResult.OK){
tbCustomSound.Text = dialog.FileName;
}
}
}
private void btnResetSound_Click(object sender, EventArgs e){
tbCustomSound.Text = string.Empty;
}
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Updates; using TweetDck.Updates;
using TweetDck.Updates.Events;
namespace TweetDck.Core.Other.Settings{ namespace TweetDck.Core.Other.Settings{
partial class TabSettingsUpdates : BaseTabSettings{ partial class TabSettingsUpdates : BaseTabSettings{
@@ -27,12 +28,18 @@ namespace TweetDck.Core.Other.Settings{
private void btnCheckUpdates_Click(object sender, EventArgs e){ private void btnCheckUpdates_Click(object sender, EventArgs e){
if (!Ready)return; if (!Ready)return;
Config.DismissedUpdate = string.Empty;
Config.Save();
updateCheckEventId = updates.Check(true); updateCheckEventId = updates.Check(true);
if (updateCheckEventId == -1){
MessageBox.Show("Sorry, your system is no longer supported.", "Unsupported System", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else{
btnCheckUpdates.Enabled = false; btnCheckUpdates.Enabled = false;
updates.Settings.DismissedUpdate = string.Empty;
Config.DismissedUpdate = string.Empty;
Config.Save();
}
} }
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){ private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){

View File

@@ -4,6 +4,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp;
namespace TweetDck.Core.Utils{ namespace TweetDck.Core.Utils{
static class BrowserUtils{ static class BrowserUtils{
@@ -27,7 +28,7 @@ namespace TweetDck.Core.Utils{
} }
public static void OpenExternalBrowser(string url){ // TODO implement mailto public static void OpenExternalBrowser(string url){ // TODO implement mailto
Process.Start(url); using(Process.Start(url)){}
} }
public static string GetFileNameFromUrl(string url){ public static string GetFileNameFromUrl(string url){
@@ -47,5 +48,15 @@ namespace TweetDck.Core.Utils{
client.DownloadFileAsync(new Uri(url), target); client.DownloadFileAsync(new Uri(url), target);
} }
public static bool IsTweetDeckWebsite(IFrame frame){
return frame.Url.Contains("//tweetdeck.twitter.com/");
}
#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

@@ -0,0 +1,111 @@
using System.Collections.Generic;
using System.Text;
namespace TweetDck.Core.Utils{
class CommandLineArgs{
public static CommandLineArgs FromStringArray(char entryChar, string[] array){
CommandLineArgs args = new CommandLineArgs();
ReadStringArray(entryChar, array, args);
return args;
}
public static void ReadStringArray(char entryChar, string[] array, CommandLineArgs targetArgs){
for(int index = 0; index < array.Length; index++){
string entry = array[index];
if (entry.Length > 0 && entry[0] == entryChar){
if (index < array.Length-1){
string potentialValue = array[index+1];
if (potentialValue.Length > 0 && potentialValue[0] == entryChar){
targetArgs.AddFlag(entry);
}
else{
targetArgs.SetValue(entry, potentialValue);
++index;
}
}
else{
targetArgs.AddFlag(entry);
}
}
}
}
private readonly HashSet<string> flags = new HashSet<string>();
private readonly Dictionary<string, string> values = new Dictionary<string, string>();
public int Count{
get{
return flags.Count+values.Count;
}
}
public void AddFlag(string flag){
flags.Add(flag.ToLowerInvariant());
}
public bool HasFlag(string flag){
return flags.Contains(flag.ToLowerInvariant());
}
public void RemoveFlag(string flag){
flags.Remove(flag.ToLowerInvariant());
}
public void SetValue(string key, string value){
values[key.ToLowerInvariant()] = value;
}
public bool HasValue(string key){
return values.ContainsKey(key.ToLowerInvariant());
}
public string GetValue(string key, string defaultValue){
string val;
return values.TryGetValue(key.ToLowerInvariant(), out val) ? val : defaultValue;
}
public void RemoveValue(string key){
values.Remove(key.ToLowerInvariant());
}
public CommandLineArgs Clone(){
CommandLineArgs copy = new CommandLineArgs();
foreach(string flag in flags){
copy.AddFlag(flag);
}
foreach(KeyValuePair<string, string> kvp in values){
copy.SetValue(kvp.Key, kvp.Value);
}
return copy;
}
public void ToDictionary(IDictionary<string, string> target){
foreach(string flag in flags){
target[flag] = "1";
}
foreach(KeyValuePair<string, string> kvp in values){
target[kvp.Key] = kvp.Value;
}
}
public override string ToString(){
StringBuilder build = new StringBuilder();
foreach(string flag in flags){
build.Append(flag).Append(' ');
}
foreach(KeyValuePair<string, string> kvp in values){
build.Append(kvp.Key).Append(" \"").Append(kvp.Value).Append("\" ");
}
return build.Length == 0 ? string.Empty : build.Remove(build.Length-1, 1).ToString();
}
}
}

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
namespace TweetDck.Core.Utils{ namespace TweetDck.Core.Utils{
static class CommandLineArgsParser{ static class CommandLineArgsParser{
@@ -7,18 +6,18 @@ namespace TweetDck.Core.Utils{
private static Regex SplitRegex{ private static Regex SplitRegex{
get{ get{
return splitRegex ?? (splitRegex = new Regex(@"([^=\s]+(?:=(?:""[^""]*?""|[^ ]*))?)", RegexOptions.Compiled)); return splitRegex ?? (splitRegex = new Regex(@"([^=\s]+(?:=(?:[^ ]*""[^""]*?""[^ ]*|[^ ]*))?)", RegexOptions.Compiled));
} }
} }
public static int AddToDictionary(string args, IDictionary<string, string> dictionary){ public static CommandLineArgs ReadCefArguments(string argumentString){
if (string.IsNullOrWhiteSpace(args)){ CommandLineArgs args = new CommandLineArgs();
return 0;
if (string.IsNullOrWhiteSpace(argumentString)){
return args;
} }
int count = 0; foreach(Match match in SplitRegex.Matches(argumentString)){
foreach(Match match in SplitRegex.Matches(args)){
string matchValue = match.Value; string matchValue = match.Value;
int indexEquals = matchValue.IndexOf('='); int indexEquals = matchValue.IndexOf('=');
@@ -34,12 +33,11 @@ namespace TweetDck.Core.Utils{
} }
if (key.Length != 0){ if (key.Length != 0){
dictionary[key] = value; args.SetValue(key, value);
++count;
} }
} }
return count; return args;
} }
} }
} }

View File

@@ -15,6 +15,7 @@ namespace TweetDck.Core.Utils{
public const int MOUSEEVENTF_RIGHTUP = 0x10; public const int MOUSEEVENTF_RIGHTUP = 0x10;
public const int SB_HORZ = 0; public const int SB_HORZ = 0;
public const int BCM_SETSHIELD = 0x160C;
public const int WH_MOUSE_LL = 14; public const int WH_MOUSE_LL = 14;
public const int WH_MOUSEWHEEL = 0x020A; public const int WH_MOUSEWHEEL = 0x020A;
@@ -23,13 +24,26 @@ namespace TweetDck.Core.Utils{
Left, Right Left, Right
} }
private struct LASTINPUTINFO{
public static readonly uint Size = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO));
// ReSharper disable once NotAccessedField.Local
public uint cbSize;
#pragma warning disable 649
public uint dwTime;
#pragma warning restore 649
}
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "SetWindowPos")] [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern bool SetWindowPos(int hWnd, int hWndOrder, int x, int y, int width, int height, uint flags); private static extern bool SetWindowPos(int hWnd, int hWndOrder, int x, int y, int width, int height, uint flags);
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo); private static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
[DllImport("user32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO info);
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, IntPtr lParam); public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, IntPtr lParam);
@@ -74,5 +88,23 @@ namespace TweetDck.Core.Utils{
mouse_event(flagHold, Cursor.Position.X, Cursor.Position.Y, 0, 0); mouse_event(flagHold, Cursor.Position.X, Cursor.Position.Y, 0, 0);
mouse_event(flagRelease, Cursor.Position.X, Cursor.Position.Y, 0, 0); mouse_event(flagRelease, Cursor.Position.X, Cursor.Position.Y, 0, 0);
} }
public static int GetIdleSeconds(){
LASTINPUTINFO info = new LASTINPUTINFO();
info.cbSize = LASTINPUTINFO.Size;
if (!GetLastInputInfo(ref info)){
return 0;
}
uint ticks;
unchecked{
ticks = (uint)Environment.TickCount;
}
int seconds = (int)Math.Floor(TimeSpan.FromMilliseconds(ticks-info.dwTime).TotalSeconds);
return Math.Max(0, seconds); // ignore rollover after several weeks of uptime
}
} }
} }

View File

@@ -0,0 +1,100 @@
using System.Collections.Generic;
using System.Linq;
namespace TweetDck.Core.Utils{
class TwoKeyDictionary<K1, K2, V>{
private readonly Dictionary<K1, Dictionary<K2, V>> dict;
private readonly int innerCapacity;
public TwoKeyDictionary() : this(16, 16){}
public TwoKeyDictionary(int outerCapacity, int innerCapacity){
this.dict = new Dictionary<K1, Dictionary<K2, V>>(outerCapacity);
this.innerCapacity = innerCapacity;
}
// Properties
public V this[K1 outerKey, K2 innerKey]{
get{ // throws on missing key
return dict[outerKey][innerKey];
}
set{
Dictionary<K2, V> innerDict;
if (!dict.TryGetValue(outerKey, out innerDict)){
dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity));
}
innerDict[innerKey] = value;
}
}
// Members
public void Add(K1 outerKey, K2 innerKey, V value){ // throws on duplicate
Dictionary<K2, V> innerDict;
if (!dict.TryGetValue(outerKey, out innerDict)){
dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity));
}
innerDict.Add(innerKey, value);
}
public void Clear(){
this.dict.Clear();
}
public void Clear(K1 outerKey){ // throws on missing key, but keeps the key unlike Remove(K1)
dict[outerKey].Clear();
}
public bool Contains(K1 outerKey){
return dict.ContainsKey(outerKey);
}
public bool Contains(K1 outerKey, K2 innerKey){
Dictionary<K2, V> innerDict;
return dict.TryGetValue(outerKey, out innerDict) && innerDict.ContainsKey(innerKey);
}
public int Count(){
return dict.Values.Sum(d => d.Count);
}
public int Count(K1 outerKey){ // throws on missing key
return dict[outerKey].Count;
}
public bool Remove(K1 outerKey){
return dict.Remove(outerKey);
}
public bool Remove(K1 outerKey, K2 innerKey){
Dictionary<K2, V> innerDict;
if (dict.TryGetValue(outerKey, out innerDict) && innerDict.Remove(innerKey)){
if (innerDict.Count == 0){
dict.Remove(outerKey);
}
return true;
}
else return false;
}
public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){
Dictionary<K2, V> innerDict;
if (dict.TryGetValue(outerKey, out innerDict)){
return innerDict.TryGetValue(innerKey, out value);
}
else{
value = default(V);
return false;
}
}
}
}

View File

@@ -1,32 +1,84 @@
using System.IO; using System.Diagnostics;
using System.Linq; using System.IO;
using System.Security.AccessControl; using System.Runtime.InteropServices;
using System.Security.Principal; using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace TweetDck.Core.Utils{ namespace TweetDck.Core.Utils{
static class WindowsUtils{ static class WindowsUtils{
public static bool CheckFolderPermission(string path, FileSystemRights right){ private static readonly Regex RegexStripHtmlStyles = new Regex(@"\s?(?:style|class)="".*?""");
private static readonly Regex RegexOffsetClipboardHtml = new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)");
public static bool CheckFolderWritePermission(string path){
string testFile = Path.Combine(path, ".test");
try{ try{
AuthorizationRuleCollection rules = Directory.GetAccessControl(path).GetAccessRules(true, true, typeof(SecurityIdentifier)); Directory.CreateDirectory(path);
WindowsIdentity identity = WindowsIdentity.GetCurrent();
if (identity == null || identity.Groups == null){ using(File.Create(testFile)){}
File.Delete(testFile);
return true;
}catch{
return false; return false;
} }
bool accessAllow = false, accessDeny = false;
foreach(FileSystemAccessRule rule in rules.Cast<FileSystemAccessRule>().Where(rule => identity.Groups.Contains(rule.IdentityReference) && (right & rule.FileSystemRights) == right)){
switch(rule.AccessControlType){
case AccessControlType.Allow: accessAllow = true; break;
case AccessControlType.Deny: accessDeny = true; break;
}
} }
return accessAllow && !accessDeny; public static Process StartProcess(string file, string arguments, bool runElevated){
ProcessStartInfo processInfo = new ProcessStartInfo{
FileName = file,
Arguments = arguments
};
if (runElevated){
processInfo.Verb = "runas";
} }
catch{
return false; return Process.Start(processInfo);
}
public static Timer CreateSingleTickTimer(int timeout){
Timer timer = new Timer{
Interval = timeout
};
timer.Tick += (sender, args) => timer.Stop();
return timer;
}
public static void ClipboardStripHtmlStyles(){
if (!Clipboard.ContainsText(TextDataFormat.Html)){
return;
}
string originalText = Clipboard.GetText(TextDataFormat.UnicodeText);
string originalHtml = Clipboard.GetText(TextDataFormat.Html);
string updatedHtml = RegexStripHtmlStyles.Replace(originalHtml, string.Empty);
int removed = originalHtml.Length-updatedHtml.Length;
updatedHtml = RegexOffsetClipboardHtml.Replace(updatedHtml, match => (int.Parse(match.Value)-removed).ToString().PadLeft(match.Value.Length, '0'));
DataObject obj = new DataObject();
obj.SetText(originalText, TextDataFormat.UnicodeText);
obj.SetText(updatedHtml, TextDataFormat.Html);
SetClipboardData(obj);
}
public static void SetClipboard(string text, TextDataFormat format){
if (string.IsNullOrEmpty(text)){
return;
}
DataObject obj = new DataObject();
obj.SetText(text, format);
SetClipboardData(obj);
}
private static void SetClipboardData(DataObject obj){
try{
Clipboard.SetDataObject(obj);
}catch(ExternalException e){
Program.Reporter.HandleException("Clipboard Error", Program.BrandName+" could not access the clipboard as it is currently used by another process.", true, e);
} }
} }
} }

View File

@@ -1,77 +0,0 @@
using TweetDck.Core.Controls;
namespace TweetDck.Migration {
partial class FormBackgroundWork {
/// <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.progressBarUseless = new System.Windows.Forms.ProgressBar();
this.labelDescription = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// progressBarUseless
//
this.progressBarUseless.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.progressBarUseless.Location = new System.Drawing.Point(15, 52);
this.progressBarUseless.MarqueeAnimationSpeed = 10;
this.progressBarUseless.Name = "progressBarUseless";
this.progressBarUseless.Size = new System.Drawing.Size(477, 23);
this.progressBarUseless.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this.progressBarUseless.TabIndex = 0;
//
// labelDescription
//
this.labelDescription.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.labelDescription.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelDescription.Location = new System.Drawing.Point(12, 12);
this.labelDescription.Name = "labelDescription";
this.labelDescription.Size = new System.Drawing.Size(480, 37);
this.labelDescription.TabIndex = 1;
this.labelDescription.Text = "Please, watch this informationless progress bar showcase while some magic happens" +
" in the background...";
//
// FormBackgroundWork
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(504, 87);
this.Controls.Add(this.labelDescription);
this.Controls.Add(this.progressBarUseless);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Name = "FormBackgroundWork";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "TweetDeck Migration";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ProgressBar progressBarUseless;
private System.Windows.Forms.Label labelDescription;
}
}

View File

@@ -1,15 +0,0 @@
using System;
using System.Windows.Forms;
namespace TweetDck.Migration{
partial class FormBackgroundWork : Form{
public FormBackgroundWork(){
InitializeComponent();
}
public void ShowWorkDialog(Action onBegin){
Shown += (sender, args) => onBegin();
ShowDialog();
}
}
}

View File

@@ -1,148 +0,0 @@
using TweetDck.Core.Controls;
namespace TweetDck.Migration {
partial class FormMigrationQuestion {
/// <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.btnIgnore = new System.Windows.Forms.Button();
this.panelButtons = new System.Windows.Forms.FlowLayoutPanel();
this.btnAskLater = new System.Windows.Forms.Button();
this.btnMigrate = new System.Windows.Forms.Button();
this.btnMigrateUninstall = new System.Windows.Forms.Button();
this.labelQuestion = new System.Windows.Forms.Label();
this.panelButtons.SuspendLayout();
this.SuspendLayout();
//
// btnIgnore
//
this.btnIgnore.AutoSize = true;
this.btnIgnore.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnIgnore.Location = new System.Drawing.Point(391, 0);
this.btnIgnore.Margin = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnIgnore.Name = "btnIgnore";
this.btnIgnore.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnIgnore.Size = new System.Drawing.Size(53, 23);
this.btnIgnore.TabIndex = 1;
this.btnIgnore.Text = "Ignore";
this.btnIgnore.UseVisualStyleBackColor = true;
this.btnIgnore.Click += new System.EventHandler(this.btnIgnore_Click);
//
// panelButtons
//
this.panelButtons.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelButtons.Controls.Add(this.btnAskLater);
this.panelButtons.Controls.Add(this.btnIgnore);
this.panelButtons.Controls.Add(this.btnMigrate);
this.panelButtons.Controls.Add(this.btnMigrateUninstall);
this.panelButtons.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
this.panelButtons.Location = new System.Drawing.Point(12, 67);
this.panelButtons.Name = "panelButtons";
this.panelButtons.Size = new System.Drawing.Size(518, 23);
this.panelButtons.TabIndex = 0;
//
// btnAskLater
//
this.btnAskLater.AutoSize = true;
this.btnAskLater.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnAskLater.Location = new System.Drawing.Point(450, 0);
this.btnAskLater.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0);
this.btnAskLater.Name = "btnAskLater";
this.btnAskLater.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnAskLater.Size = new System.Drawing.Size(68, 23);
this.btnAskLater.TabIndex = 0;
this.btnAskLater.Text = "Ask Later";
this.btnAskLater.UseVisualStyleBackColor = true;
this.btnAskLater.Click += new System.EventHandler(this.btnAskLater_Click);
//
// btnMigrate
//
this.btnMigrate.AutoSize = true;
this.btnMigrate.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnMigrate.Location = new System.Drawing.Point(327, 0);
this.btnMigrate.Margin = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnMigrate.Name = "btnMigrate";
this.btnMigrate.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnMigrate.Size = new System.Drawing.Size(58, 23);
this.btnMigrate.TabIndex = 3;
this.btnMigrate.Text = "Migrate";
this.btnMigrate.UseVisualStyleBackColor = true;
this.btnMigrate.Click += new System.EventHandler(this.btnMigrate_Click);
//
// btnMigrateUninstall
//
this.btnMigrateUninstall.AutoSize = true;
this.btnMigrateUninstall.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnMigrateUninstall.Location = new System.Drawing.Point(223, 0);
this.btnMigrateUninstall.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.btnMigrateUninstall.Name = "btnMigrateUninstall";
this.btnMigrateUninstall.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnMigrateUninstall.Size = new System.Drawing.Size(98, 23);
this.btnMigrateUninstall.TabIndex = 4;
this.btnMigrateUninstall.Text = "Migrate && Purge";
this.btnMigrateUninstall.UseVisualStyleBackColor = true;
this.btnMigrateUninstall.Click += new System.EventHandler(this.btnMigrateUninstall_Click);
//
// labelQuestion
//
this.labelQuestion.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.labelQuestion.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelQuestion.Location = new System.Drawing.Point(49, 9);
this.labelQuestion.Margin = new System.Windows.Forms.Padding(40, 3, 3, 3);
this.labelQuestion.Name = "labelQuestion";
this.labelQuestion.Size = new System.Drawing.Size(481, 52);
this.labelQuestion.TabIndex = 2;
//
// FormMigrationQuestion
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(542, 102);
this.Controls.Add(this.labelQuestion);
this.Controls.Add(this.panelButtons);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormMigrationQuestion";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "TweetDeck Migration";
this.panelButtons.ResumeLayout(false);
this.panelButtons.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button btnIgnore;
private System.Windows.Forms.FlowLayoutPanel panelButtons;
private System.Windows.Forms.Button btnMigrate;
private System.Windows.Forms.Label labelQuestion;
private System.Windows.Forms.Button btnAskLater;
private System.Windows.Forms.Button btnMigrateUninstall;
}
}

View File

@@ -1,42 +0,0 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace TweetDck.Migration{
partial class FormMigrationQuestion : Form{
public MigrationDecision Decision { get; private set; }
public FormMigrationQuestion(){
InitializeComponent();
labelQuestion.Text = "Hey there, I found some TweetDeck data! Do you want to »Migrate« it and delete the old data folder, »Ignore« the request forever, or try "+Program.BrandName+" out first?\r\nYou may also »Migrate && Purge« which uninstalls TweetDeck too!";
}
protected override void OnPaint(PaintEventArgs e){
e.Graphics.DrawIcon(SystemIcons.Question, 10, 10);
base.OnPaint(e);
}
private void btnMigrateUninstall_Click(object sender, EventArgs e){
Close(MigrationDecision.MigratePurge);
}
private void btnMigrate_Click(object sender, EventArgs e){
Close(MigrationDecision.Migrate);
}
private void btnIgnore_Click(object sender, EventArgs e){
Close(MigrationDecision.Ignore);
}
private void btnAskLater_Click(object sender, EventArgs e){
Close(MigrationDecision.AskLater);
}
private void Close(MigrationDecision decision){
Decision = decision;
DialogResult = DialogResult.OK;
Close();
}
}
}

View File

@@ -1,43 +0,0 @@
using System;
using System.Linq;
using Microsoft.Win32;
namespace TweetDck.Migration.Helpers{
static class ProgramRegistrySearch{
public static string FindByDisplayName(string displayName){
Predicate<RegistryKey> predicate = key => displayName.Equals(key.GetValue("DisplayName") as string, StringComparison.OrdinalIgnoreCase);
string guid;
return FindMatchingSubKey(Registry.LocalMachine, @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall", predicate, out guid) ||
FindMatchingSubKey(Registry.LocalMachine, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", predicate, out guid) ||
FindMatchingSubKey(Registry.CurrentUser, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", predicate, out guid)
? guid : null;
}
private static bool FindMatchingSubKey(RegistryKey keyHandle, string path, Predicate<RegistryKey> predicate, out string guid){
string outputId = null;
try{
RegistryKey parentKey = keyHandle.OpenSubKey(path, false);
if (parentKey == null)throw new InvalidOperationException();
foreach(RegistryKey subKey in parentKey.GetSubKeyNames().Select(subName => parentKey.OpenSubKey(subName, false)).Where(subKey => subKey != null)){
if (predicate(subKey)){
outputId = subKey.Name.Substring(subKey.Name.LastIndexOf('\\')+1);
subKey.Close();
break;
}
subKey.Close();
}
parentKey.Close();
}catch(Exception){
guid = null;
return false;
}
return (guid = outputId) != null;
}
}
}

View File

@@ -1,23 +0,0 @@
namespace TweetDck.Migration{
enum MigrationDecision{
/// <summary>
/// Copies the important files and then deletes the TweetDeck folder.
/// </summary>
Migrate,
/// <summary>
/// Does exactly what <see cref="Migrate"/> does, but also silently uninstalls TweetDeck.
/// </summary>
MigratePurge,
/// <summary>
/// Does not copy any files and does not ask the user about data migration again.
/// </summary>
Ignore,
/// <summary>
/// Does not copy any files but asks the user again when the program is re-ran.
/// </summary>
AskLater
}
}

View File

@@ -1,186 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Win32;
using TweetDck.Migration.Helpers;
namespace TweetDck.Migration{
static class MigrationManager{
private static readonly string TweetDeckPathParent = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "twitter");
private static readonly string TweetDeckPath = Path.Combine(TweetDeckPathParent, "TweetDeck");
private static readonly string TweetDickStorage = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "TweetDick");
public static void Run(){
if (!Program.IsPortable && Directory.Exists(TweetDickStorage) && !Directory.Exists(Program.StoragePath)){
if (MessageBox.Show("Welcome to TweetDuck! Would you like to move your old TweetDick configuration and login data?", "TweetDick Migration", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes){
try{
Directory.Move(TweetDickStorage, Program.StoragePath);
MessageBox.Show("All done! You can now uninstall TweetDick.", "TweetDick Migration", MessageBoxButtons.OK, MessageBoxIcon.Information);
}catch(Exception ex){
Program.Reporter.HandleException("Migration Error", "An unexpected error occurred during the migration process.", true, ex);
}
}
return;
}
if (!Program.UserConfig.IgnoreMigration && Directory.Exists(TweetDeckPath)){
MigrationDecision decision;
using(FormMigrationQuestion formQuestion = new FormMigrationQuestion()){
decision = formQuestion.ShowDialog() == DialogResult.OK ? formQuestion.Decision : MigrationDecision.AskLater;
}
switch(decision){
case MigrationDecision.MigratePurge:
case MigrationDecision.Migrate:
FormBackgroundWork formWait = new FormBackgroundWork();
formWait.ShowWorkDialog(() => {
if (!BeginMigration(decision, ex => formWait.Invoke(new Action(() => {
formWait.Close();
if (ex != null){
Program.Reporter.HandleException("Migration Error", "An unexpected error occurred during the migration process.", true, ex);
return;
}
Program.UserConfig.IgnoreMigration = true;
Program.UserConfig.Save();
})))){
formWait.Close();
}
});
break;
case MigrationDecision.Ignore:
Program.UserConfig.IgnoreMigration = true;
Program.UserConfig.Save();
break;
}
}
else if (!Program.UserConfig.IgnoreUninstallCheck){
string guid = ProgramRegistrySearch.FindByDisplayName("TweetDeck");
if (guid != null && MessageBox.Show("TweetDeck is still installed on your computer, do you want to uninstall it?", "Uninstall TweetDeck", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
RunUninstaller(guid, 0);
CleanupTweetDeck();
}
Program.UserConfig.IgnoreUninstallCheck = true;
Program.UserConfig.Save();
}
}
private static bool BeginMigration(MigrationDecision decision, Action<Exception> onFinished){
if (decision != MigrationDecision.MigratePurge && decision != MigrationDecision.Migrate){
return false;
}
Task task = new Task(() => {
Directory.CreateDirectory(Program.StoragePath);
Directory.CreateDirectory(Path.Combine(Program.StoragePath, "localStorage"));
Directory.CreateDirectory(Path.Combine(Program.StoragePath, "Local Storage"));
CopyFile("Cookies");
CopyFile("Cookies-journal");
CopyFile("localStorage"+Path.DirectorySeparatorChar+"qrc__0.localstorage");
CopyFile("Local Storage"+Path.DirectorySeparatorChar+"https_tweetdeck.twitter.com_0.localstorage");
CopyFile("Local Storage"+Path.DirectorySeparatorChar+"https_tweetdeck.twitter.com_0.localstorage-journal");
if (decision == MigrationDecision.Migrate || decision == MigrationDecision.MigratePurge){
// kill process if running
Process runningProcess = null;
try{
runningProcess = Process.GetProcessesByName("TweetDeck").FirstOrDefault(process => process.MainWindowHandle != IntPtr.Zero);
}catch(Exception){
// process not found
}
if (runningProcess != null){
runningProcess.CloseMainWindow();
for(int wait = 0; wait < 100 && !runningProcess.HasExited; wait++){ // 10 seconds
runningProcess.Refresh();
Thread.Sleep(100);
}
runningProcess.Close();
}
// delete folders
for(int wait = 0; wait < 50; wait++){
try{
Directory.Delete(TweetDeckPath, true);
break;
}catch(Exception){
// browser subprocess not ended yet, wait
Thread.Sleep(300);
}
}
try{
Directory.Delete(TweetDeckPathParent, false);
}catch(IOException){
// most likely not empty, ignore
}
}
if (decision == MigrationDecision.MigratePurge){
// uninstall in the background
string guid = ProgramRegistrySearch.FindByDisplayName("TweetDeck");
if (guid != null){
RunUninstaller(guid, 5000);
}
// registry cleanup
CleanupTweetDeck();
// migration finished like a boss
}
});
task.ContinueWith(originalTask => onFinished(originalTask.Exception), TaskContinuationOptions.ExecuteSynchronously);
task.Start();
return true;
}
private static void CopyFile(string relativePath){
try{
File.Copy(Path.Combine(TweetDeckPath, relativePath), Path.Combine(Program.StoragePath, relativePath), true);
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
}
}
private static void RunUninstaller(string guid, int timeout){
Process uninstaller = Process.Start("msiexec.exe", "/x "+guid+" /quiet /qn");
if (uninstaller != null){
if (timeout > 0){
uninstaller.WaitForExit(timeout); // it appears that the process is restarted or something that triggers this, but it shouldn't be a problem
}
uninstaller.Close();
}
}
private static void CleanupTweetDeck(){
try{
Registry.CurrentUser.DeleteSubKeyTree(@"Software\Twitter\TweetDeck", true);
Registry.CurrentUser.DeleteSubKey(@"Software\Twitter"); // only if empty
}catch(Exception){
// not found or too bad
}
}
}
}

View File

@@ -50,7 +50,7 @@ namespace TweetDck.Plugins.Controls{
} }
private void btnOpenConfig_Click(object sender, EventArgs e){ private void btnOpenConfig_Click(object sender, EventArgs e){
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath+"\"")){} using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){}
} }
private void btnToggleState_Click(object sender, EventArgs e){ private void btnToggleState_Click(object sender, EventArgs e){

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace TweetDck.Plugins{ namespace TweetDck.Plugins.Enums{
[Flags] [Flags]
enum PluginEnvironment{ enum PluginEnvironment{
None = 0, None = 0,

View File

@@ -0,0 +1,5 @@
namespace TweetDck.Plugins.Enums{
enum PluginFolder{
Root, Data
}
}

View File

@@ -1,4 +1,4 @@
namespace TweetDck.Plugins{ namespace TweetDck.Plugins.Enums{
enum PluginGroup{ enum PluginGroup{
Official, Custom Official, Custom
} }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using TweetDck.Plugins.Enums;
namespace TweetDck.Plugins{ namespace TweetDck.Plugins{
class Plugin{ class Plugin{
@@ -24,37 +25,32 @@ namespace TweetDck.Plugins{
} }
} }
public string FolderPath{
get{
return path;
}
}
public bool HasConfig{ public bool HasConfig{
get{ get{
return ConfigFile.Length > 0 && GetFullPathIfSafe(ConfigFile).Length > 0; return ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
} }
} }
public string ConfigPath{ public string ConfigPath{
get{ get{
return HasConfig ? Path.Combine(path, ConfigFile) : string.Empty; return HasConfig ? Path.Combine(GetPluginFolder(PluginFolder.Data), ConfigFile) : string.Empty;
} }
} }
public bool HasDefaultConfig{ public bool HasDefaultConfig{
get{ get{
return ConfigDefault.Length > 0 && GetFullPathIfSafe(ConfigDefault).Length > 0; return ConfigDefault.Length > 0 && GetFullPathIfSafe(PluginFolder.Root, ConfigDefault).Length > 0;
} }
} }
public string DefaultConfigPath{ public string DefaultConfigPath{
get{ get{
return HasDefaultConfig ? Path.Combine(path, ConfigDefault) : string.Empty; return HasDefaultConfig ? Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigDefault) : string.Empty;
} }
} }
private readonly string path; private readonly string pathRoot;
private readonly string pathData;
private readonly string identifier; private readonly string identifier;
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(4){ private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(4){
{ "NAME", "" }, { "NAME", "" },
@@ -70,8 +66,13 @@ namespace TweetDck.Plugins{
private bool? canRun; private bool? canRun;
private Plugin(string path, PluginGroup group){ private Plugin(string path, PluginGroup group){
this.path = path; string name = Path.GetFileName(path);
this.identifier = group.GetIdentifierPrefix()+Path.GetFileName(path); System.Diagnostics.Debug.Assert(name != null);
this.pathRoot = path;
this.pathData = Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name);
this.identifier = group.GetIdentifierPrefix()+name;
this.Group = group; this.Group = group;
this.Environments = PluginEnvironment.None; this.Environments = PluginEnvironment.None;
} }
@@ -80,7 +81,26 @@ namespace TweetDck.Plugins{
string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath; string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath;
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){ if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
string dataFolder = GetPluginFolder(PluginFolder.Data);
if (!Directory.Exists(dataFolder)){ // config migration
string originalFile = Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigFile);
if (File.Exists(originalFile)){
try{ try{
Directory.CreateDirectory(dataFolder);
File.Copy(originalFile, configPath, false);
File.Delete(originalFile); // will fail without write perms in program folder, ignore if so
}catch{
// ignore
}
return;
}
}
try{
Directory.CreateDirectory(dataFolder);
File.Copy(defaultConfigPath, configPath, false); File.Copy(defaultConfigPath, configPath, false);
}catch(Exception e){ }catch(Exception e){
Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+identifier+"' plugin.", true, e); Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+identifier+"' plugin.", true, e);
@@ -91,18 +111,27 @@ namespace TweetDck.Plugins{
public string GetScriptPath(PluginEnvironment environment){ public string GetScriptPath(PluginEnvironment environment){
if (Environments.HasFlag(environment)){ if (Environments.HasFlag(environment)){
string file = environment.GetScriptFile(); string file = environment.GetScriptFile();
return file != null ? Path.Combine(path, file) : string.Empty; return file != null ? Path.Combine(pathRoot, file) : string.Empty;
} }
else{ else{
return string.Empty; return string.Empty;
} }
} }
public string GetFullPathIfSafe(string relativePath){ public string GetPluginFolder(PluginFolder folder){
string fullPath = Path.Combine(path, relativePath); switch(folder){
case PluginFolder.Root: return pathRoot;
case PluginFolder.Data: return pathData;
default: return string.Empty;
}
}
public string GetFullPathIfSafe(PluginFolder folder, string relativePath){
string rootFolder = GetPluginFolder(folder);
string fullPath = Path.Combine(rootFolder, relativePath);
try{ try{
string folderPathName = new DirectoryInfo(path).FullName; string folderPathName = new DirectoryInfo(rootFolder).FullName;
DirectoryInfo currentInfo = new DirectoryInfo(fullPath); DirectoryInfo currentInfo = new DirectoryInfo(fullPath);
while(currentInfo.Parent != null){ while(currentInfo.Parent != null){
@@ -130,7 +159,7 @@ namespace TweetDck.Plugins{
public override bool Equals(object obj){ public override bool Equals(object obj){
Plugin plugin = obj as Plugin; Plugin plugin = obj as Plugin;
return plugin != null && plugin.path.Equals(path); return plugin != null && plugin.identifier.Equals(identifier);
} }
public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){ public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){

View File

@@ -1,57 +1,62 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using TweetDck.Core.Utils;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events; using TweetDck.Plugins.Events;
namespace TweetDck.Plugins{ namespace TweetDck.Plugins{
class PluginBridge{ class PluginBridge{
private static string SanitizeCacheKey(string key){
return key.Replace('\\', '/').Trim();
}
private readonly PluginManager manager; private readonly PluginManager manager;
private readonly Dictionary<string, string> fileCache = new Dictionary<string, string>(2); private readonly TwoKeyDictionary<int, string, string> fileCache = new TwoKeyDictionary<int, string, string>(4, 2);
public PluginBridge(PluginManager manager){ public PluginBridge(PluginManager manager){
this.manager = manager; this.manager = manager;
this.manager.Reloaded += manager_Reloaded; this.manager.Reloaded += manager_Reloaded;
this.manager.PluginChangedState += manager_PluginChangedState;
} }
private void manager_Reloaded(object sender, PluginLoadEventArgs e){ private void manager_Reloaded(object sender, PluginLoadEventArgs e){
fileCache.Clear(); fileCache.Clear();
} }
private string GetFullPathIfSafe(int token, string path){ private void manager_PluginChangedState(object sender, PluginChangedStateEventArgs e){
if (!e.IsEnabled){
fileCache.Remove(manager.GetTokenFromPlugin(e.Plugin));
}
}
private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
Plugin plugin = manager.GetPluginFromToken(token); Plugin plugin = manager.GetPluginFromToken(token);
return plugin == null ? string.Empty : plugin.GetFullPathIfSafe(path); string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
if (fullPath.Length == 0){
switch(folder){
case PluginFolder.Data: throw new Exception("File path has to be relative to the plugin data folder.");
case PluginFolder.Root: throw new Exception("File path has to be relative to the plugin root folder.");
default: throw new Exception("Invalid folder type "+folder+", this is a "+Program.BrandName+" error.");
}
}
else{
return fullPath;
}
} }
public void WriteFile(int token, string path, string contents){ private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool readCached){
string fullPath = GetFullPathIfSafe(token, path); cacheKey = SanitizeCacheKey(cacheKey);
if (fullPath == string.Empty){
throw new Exception("File path has to be relative to the plugin folder.");
}
// ReSharper disable once AssignNullToNotNullAttribute
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
File.WriteAllText(fullPath, contents, Encoding.UTF8);
fileCache[fullPath] = contents;
}
public string ReadFile(int token, string path, bool cache){
string fullPath = GetFullPathIfSafe(token, path);
if (fullPath == string.Empty){
throw new Exception("File path has to be relative to the plugin folder.");
}
string cachedContents; string cachedContents;
if (cache && fileCache.TryGetValue(fullPath, out cachedContents)){ if (readCached && fileCache.TryGetValue(token, cacheKey, out cachedContents)){
return cachedContents; return cachedContents;
} }
try{ try{
return fileCache[fullPath] = File.ReadAllText(fullPath, Encoding.UTF8); return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8);
}catch(FileNotFoundException){ }catch(FileNotFoundException){
throw new Exception("File not found."); throw new Exception("File not found.");
}catch(DirectoryNotFoundException){ }catch(DirectoryNotFoundException){
@@ -59,25 +64,39 @@ namespace TweetDck.Plugins{
} }
} }
public void DeleteFile(int token, string path){ // Public methods
string fullPath = GetFullPathIfSafe(token, path);
if (fullPath.Length == 0){ public void WriteFile(int token, string path, string contents){
throw new Exception("File path has to be relative to the plugin folder."); string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
// ReSharper disable once AssignNullToNotNullAttribute
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
File.WriteAllText(fullPath, contents, Encoding.UTF8);
fileCache[token, SanitizeCacheKey(path)] = contents;
} }
fileCache.Remove(fullPath); public string ReadFile(int token, string path, bool cache){
return ReadFileUnsafe(token, path, GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
}
public void DeleteFile(int token, string path){
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
fileCache.Remove(token, SanitizeCacheKey(path));
File.Delete(fullPath); File.Delete(fullPath);
} }
public bool CheckFileExists(int token, string path){ public bool CheckFileExists(int token, string path){
string fullPath = GetFullPathIfSafe(token, path); return File.Exists(GetFullPathOrThrow(token, PluginFolder.Data, path));
if (fullPath.Length == 0){
throw new Exception("File path has to be relative to the plugin folder.");
} }
return File.Exists(fullPath); public string ReadFileRoot(int token, string path){
return ReadFileUnsafe(token, "root*"+path, GetFullPathOrThrow(token, PluginFolder.Root, path), true);
}
public bool CheckFileExistsRoot(int token, string path){
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Root, path));
} }
} }
} }

View File

@@ -6,7 +6,7 @@ namespace TweetDck.Plugins{
[Serializable] [Serializable]
sealed class PluginConfig{ sealed class PluginConfig{
[field:NonSerialized] [field:NonSerialized]
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState; public event EventHandler<PluginChangedStateEventArgs> InternalPluginChangedState; // should only be accessed from PluginManager
public IEnumerable<string> DisabledPlugins{ public IEnumerable<string> DisabledPlugins{
get{ get{
@@ -24,8 +24,8 @@ namespace TweetDck.Plugins{
public void SetEnabled(Plugin plugin, bool enabled){ public void SetEnabled(Plugin plugin, bool enabled){
if ((enabled && Disabled.Remove(plugin.Identifier)) || (!enabled && Disabled.Add(plugin.Identifier))){ if ((enabled && Disabled.Remove(plugin.Identifier)) || (!enabled && Disabled.Add(plugin.Identifier))){
if (PluginChangedState != null){ if (InternalPluginChangedState != null){
PluginChangedState(this, new PluginChangedStateEventArgs(plugin, enabled)); InternalPluginChangedState(this, new PluginChangedStateEventArgs(plugin, enabled));
} }
} }
} }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using CefSharp; using CefSharp;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events; using TweetDck.Plugins.Events;
using TweetDck.Resources; using TweetDck.Resources;
@@ -12,6 +13,8 @@ namespace TweetDck.Plugins{
public const string PluginNotificationScriptFile = "plugins.notification.js"; public const string PluginNotificationScriptFile = "plugins.notification.js";
public const string PluginGlobalScriptFile = "plugins.js"; public const string PluginGlobalScriptFile = "plugins.js";
private const int InvalidToken = 0;
public string PathOfficialPlugins { get { return Path.Combine(rootPath, "official"); } } public string PathOfficialPlugins { get { return Path.Combine(rootPath, "official"); } }
public string PathCustomPlugins { get { return Path.Combine(rootPath, "user"); } } public string PathCustomPlugins { get { return Path.Combine(rootPath, "user"); } }
@@ -20,6 +23,7 @@ namespace TweetDck.Plugins{
public PluginBridge Bridge { get; private set; } public PluginBridge Bridge { get; private set; }
public event EventHandler<PluginLoadEventArgs> Reloaded; public event EventHandler<PluginLoadEventArgs> Reloaded;
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
private readonly string rootPath; private readonly string rootPath;
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>(); private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
@@ -30,10 +34,25 @@ namespace TweetDck.Plugins{
public PluginManager(string path, PluginConfig config){ public PluginManager(string path, PluginConfig config){
this.rootPath = path; this.rootPath = path;
this.Config = config; this.SetConfig(config);
this.Bridge = new PluginBridge(this); this.Bridge = new PluginBridge(this);
} }
public void SetConfig(PluginConfig config){
this.Config = config;
this.Config.InternalPluginChangedState += Config_InternalPluginChangedState;
}
private void Config_InternalPluginChangedState(object sender, PluginChangedStateEventArgs e){
if (PluginChangedState != null){
PluginChangedState(this, e);
}
}
public bool IsPluginInstalled(string identifier){
return plugins.Any(plugin => plugin.Identifier.Equals(identifier));
}
public IEnumerable<Plugin> GetPluginsByGroup(PluginGroup group){ public IEnumerable<Plugin> GetPluginsByGroup(PluginGroup group){
return plugins.Where(plugin => plugin.Group == group); return plugins.Where(plugin => plugin.Group == group);
} }
@@ -46,6 +65,16 @@ namespace TweetDck.Plugins{
return plugins.Any(plugin => plugin.Environments.HasFlag(environment)); return plugins.Any(plugin => plugin.Environments.HasFlag(environment));
} }
public int GetTokenFromPlugin(Plugin plugin){
foreach(KeyValuePair<int, Plugin> kvp in tokens){
if (kvp.Value.Equals(plugin)){
return kvp.Key;
}
}
return InvalidToken;
}
public Plugin GetPluginFromToken(int token){ public Plugin GetPluginFromToken(int token){
Plugin plugin; Plugin plugin;
return tokens.TryGetValue(token, out plugin) ? plugin : null; return tokens.TryGetValue(token, out plugin) ? plugin : null;
@@ -91,7 +120,7 @@ namespace TweetDck.Plugins{
int token; int token;
if (tokens.ContainsValue(plugin)){ if (tokens.ContainsValue(plugin)){
token = tokens.First(kvp => kvp.Value.Equals(plugin)).Key; token = GetTokenFromPlugin(plugin);
} }
else{ else{
token = GenerateToken(); token = GenerateToken();
@@ -103,6 +132,10 @@ namespace TweetDck.Plugins{
} }
private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){ private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
if (!Directory.Exists(path)){
yield break;
}
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){ foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
string error; string error;
Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out error); Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out error);
@@ -120,7 +153,7 @@ namespace TweetDck.Plugins{
for(int attempt = 0; attempt < 1000; attempt++){ for(int attempt = 0; attempt < 1000; attempt++){
int token = rand.Next(); int token = rand.Next();
if (!tokens.ContainsKey(token)){ if (!tokens.ContainsKey(token) && token != InvalidToken){
return token; return token;
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Text; using System.Text;
using TweetDck.Plugins.Enums;
namespace TweetDck.Plugins{ namespace TweetDck.Plugins{
static class PluginScriptGenerator{ static class PluginScriptGenerator{

View File

@@ -5,7 +5,6 @@ using System.Windows.Forms;
using CefSharp; using CefSharp;
using TweetDck.Configuration; using TweetDck.Configuration;
using TweetDck.Core; using TweetDck.Core;
using TweetDck.Migration;
using TweetDck.Core.Utils; using TweetDck.Core.Utils;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -13,27 +12,28 @@ using TweetDck.Plugins;
using TweetDck.Plugins.Events; using TweetDck.Plugins.Events;
using TweetDck.Core.Other.Settings.Export; using TweetDck.Core.Other.Settings.Export;
using TweetDck.Core.Handling; using TweetDck.Core.Handling;
using System.Security.AccessControl; using TweetDck.Core.Other;
using TweetDck.Updates;
[assembly: CLSCompliant(true)] [assembly: CLSCompliant(true)]
namespace TweetDck{ namespace TweetDck{
static class Program{ static class Program{
public const string BrandName = "TweetDuck"; public const string BrandName = "TweetDuck";
public const string Website = "http://tweetduck.chylex.com"; public const string Website = "https://tweetduck.chylex.com";
public const string BrowserSubprocess = BrandName+".Browser.exe"; public const string VersionTag = "1.6.4";
public const string VersionFull = "1.6.4.0";
public const string VersionTag = "1.3.3";
public const string VersionFull = "1.3.3.0";
public static readonly Version Version = new Version(VersionTag); public static readonly Version Version = new Version(VersionTag);
public static readonly bool IsPortable = File.Exists("makeportable"); public static readonly bool IsPortable = File.Exists("makeportable");
private static readonly CommandLineArgs Args = CommandLineArgs.FromStringArray('-', Environment.GetCommandLineArgs());
public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory; public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory;
public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataStoragePath(); public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataStoragePath();
public static readonly string TemporaryPath = IsPortable ? Path.Combine(ProgramPath, "portable", "tmp") : Path.Combine(Path.GetTempPath(), BrandName+'_'+Path.GetRandomFileName().Substring(0, 6)); public static readonly string TemporaryPath = IsPortable ? Path.Combine(ProgramPath, "portable", "tmp") : Path.Combine(Path.GetTempPath(), BrandName+'_'+Path.GetRandomFileName().Substring(0, 6));
public static readonly string PluginDataPath = Path.Combine(StoragePath, "TD_Plugins");
public static readonly string ConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg"); public static readonly string ConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg");
private static readonly string LogFilePath = Path.Combine(StoragePath, "TD_Log.txt"); private static readonly string LogFilePath = Path.Combine(StoragePath, "TD_Log.txt");
@@ -55,50 +55,55 @@ namespace TweetDck{
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore"); WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
if (!WindowsUtils.CheckFolderPermission(ProgramPath, FileSystemRights.WriteData)){ if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
MessageBox.Show(BrandName+" does not have write permissions to the program folder. If it is installed in Program Files, please run it as Administrator.", "Administrator Required", MessageBoxButtons.OK, MessageBoxIcon.Warning); MessageBox.Show(BrandName+" does not have write permissions to the storage folder: "+StoragePath, "Permission Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return; return;
} }
Reporter = new Reporter(LogFilePath); Reporter = new Reporter(LogFilePath);
Reporter.SetupUnhandledExceptionHandler(BrandName+" Has Failed :(");
AppDomain.CurrentDomain.UnhandledException += (sender, args) => { if (Args.HasFlag("-restart")){
Exception ex = args.ExceptionObject as Exception; for(int attempt = 0; attempt < 21; attempt++){
LockManager.Result lockResult = LockManager.Lock();
if (ex != null){ if (lockResult == LockManager.Result.Success){
Reporter.HandleException(BrandName+" Has Failed :(", "An unhandled exception has occurred.", false, ex);
}
};
string[] programArguments = Environment.GetCommandLineArgs();
if (programArguments.Contains("-restart")){
for(int attempt = 0; attempt < 41; attempt++){
if (LockManager.Lock()){
break; break;
} }
else if (attempt == 40){ else if (lockResult == LockManager.Result.Fail){
MessageBox.Show(BrandName+" is taking too long to close, please wait and then start the application again manually.", BrandName+" Cannot Restart", MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show("An unknown error occurred accessing the data folder. Please, make sure "+BrandName+" is not already running. If the problem persists, try restarting your system.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
return; return;
} }
else if (attempt == 20){
using(FormMessage form = new FormMessage(BrandName+" Cannot Restart", BrandName+" is taking too long to close.", MessageBoxIcon.Warning)){
form.AddButton("Exit");
Button btnRetry = form.AddButton("Retry");
if (form.ShowDialog() == DialogResult.OK){
if (form.ClickedButton == btnRetry){
attempt /= 2;
continue;
}
}
return;
}
}
else{ else{
Thread.Sleep(500); Thread.Sleep(500);
} }
} }
ReloadConfig();
} }
else{ else{
ReloadConfig(); LockManager.Result lockResult = LockManager.Lock();
MigrationManager.Run();
if (!LockManager.Lock()){ if (lockResult == LockManager.Result.HasProcess){
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero && LockManager.LockingProcess.Responding){ // restore if the original process is in tray if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero && LockManager.LockingProcess.Responding){ // restore if the original process is in tray
NativeMethods.SendMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, 0, IntPtr.Zero); NativeMethods.SendMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, 0, IntPtr.Zero);
return; return;
} }
else if (MessageBox.Show("Another instance of "+BrandName+" is already running.\r\nDo you want to close it?", BrandName+" is Already Running", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button2) == DialogResult.Yes){ else if (MessageBox.Show("Another instance of "+BrandName+" is already running.\r\nDo you want to close it?", BrandName+" is Already Running", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
if (!LockManager.CloseLockingProcess(20000)){ if (!LockManager.CloseLockingProcess(30000)){
MessageBox.Show("Could not close the other process.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show("Could not close the other process.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
return; return;
} }
@@ -107,26 +112,40 @@ namespace TweetDck{
} }
else return; else return;
} }
else if (lockResult != LockManager.Result.Success){
MessageBox.Show("An unknown error occurred accessing the data folder. Please, make sure "+BrandName+" is not already running. If the problem persists, try restarting your system.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
} }
if (programArguments.Contains("-importcookies")){ ReloadConfig();
if (Args.HasFlag("-importcookies")){
ExportManager.ImportCookies(); ExportManager.ImportCookies();
} }
BrowserCache.ClearOldCacheFiles(); BrowserCache.ClearOldCacheFiles();
CefSharpSettings.WcfEnabled = false;
CefSettings settings = new CefSettings{ CefSettings settings = new CefSettings{
AcceptLanguageList = BrowserUtils.HeaderAcceptLanguage, AcceptLanguageList = BrowserUtils.HeaderAcceptLanguage,
UserAgent = BrowserUtils.HeaderUserAgent, UserAgent = BrowserUtils.HeaderUserAgent,
Locale = GetCmdArgumentValue("locale") ?? "en", Locale = Args.GetValue("-locale", "en"),
CachePath = StoragePath, CachePath = StoragePath,
BrowserSubprocessPath = File.Exists(BrowserSubprocess) ? BrowserSubprocess : "CefSharp.BrowserSubprocess.exe", LogFile = Path.Combine(StoragePath, "TD_Console.txt"),
#if !DEBUG #if !DEBUG
LogSeverity = programArguments.Contains("-log") ? LogSeverity.Info : LogSeverity.Disable BrowserSubprocessPath = BrandName+".Browser.exe",
LogSeverity = Args.HasFlag("-log") ? LogSeverity.Info : LogSeverity.Disable
#endif #endif
}; };
CommandLineArgsParser.AddToDictionary(UserConfig.CustomCefArgs, settings.CefCommandLineArgs); CommandLineArgsParser.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
if (!HardwareAcceleration.IsEnabled){
settings.CefCommandLineArgs["disable-gpu"] = "1";
settings.CefCommandLineArgs["disable-gpu-vsync"] = "1";
}
Cef.Initialize(settings, false, new BrowserProcessHandler()); Cef.Initialize(settings, false, new BrowserProcessHandler());
@@ -134,16 +153,23 @@ namespace TweetDck{
PluginManager plugins = new PluginManager(PluginPath, UserConfig.Plugins); PluginManager plugins = new PluginManager(PluginPath, UserConfig.Plugins);
plugins.Reloaded += plugins_Reloaded; plugins.Reloaded += plugins_Reloaded;
plugins.Config.PluginChangedState += (sender, args) => UserConfig.Save(); plugins.PluginChangedState += (sender, args) => UserConfig.Save();
plugins.Reload(); plugins.Reload();
FormBrowser mainForm = new FormBrowser(plugins); FormBrowser mainForm = new FormBrowser(plugins, new UpdaterSettings{
AllowPreReleases = Args.HasFlag("-debugupdates"),
DismissedUpdate = UserConfig.DismissedUpdate
});
Application.Run(mainForm); Application.Run(mainForm);
if (mainForm.UpdateInstallerPath != null){ if (mainForm.UpdateInstallerPath != null){
ExitCleanup(); ExitCleanup();
Process.Start(mainForm.UpdateInstallerPath, "/SP- /SILENT /NOICONS /CLOSEAPPLICATIONS"); string updaterArgs = "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+GetArgsClean().ToString().Replace("\"", "^\"")+"\""+(IsPortable ? " /PORTABLE=1" : "");
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, updaterArgs, runElevated); // ProgramPath has a trailing backslash
Application.Exit(); Application.Exit();
} }
} }
@@ -152,20 +178,22 @@ namespace TweetDck{
if (!e.Success){ if (!e.Success){
MessageBox.Show("The following plugins will not be available until the issues are resolved:\n"+string.Join("\n", e.Errors), "Error Loading Plugins", MessageBoxButtons.OK, MessageBoxIcon.Warning); MessageBox.Show("The following plugins will not be available until the issues are resolved:\n"+string.Join("\n", e.Errors), "Error Loading Plugins", MessageBoxButtons.OK, MessageBoxIcon.Warning);
} }
}
private static string GetCmdArgumentValue(string search){ ((PluginManager)sender).SetConfig(UserConfig.Plugins);
string[] args = Environment.GetCommandLineArgs();
int index = Array.FindIndex(args, arg => arg.Equals(search, StringComparison.OrdinalIgnoreCase));
return index >= 0 && index < args.Length-1 ? args[index+1] : null;
} }
private static string GetDataStoragePath(){ private static string GetDataStoragePath(){
string custom = GetCmdArgumentValue("-datafolder"); string custom = Args.GetValue("-datafolder", null);
if (custom != null && (custom.Contains(Path.DirectorySeparatorChar) || custom.Contains(Path.AltDirectorySeparatorChar))){ if (custom != null && (custom.Contains(Path.DirectorySeparatorChar) || custom.Contains(Path.AltDirectorySeparatorChar))){
return custom; if (Path.GetInvalidPathChars().Any(custom.Contains)){
Reporter.HandleEarlyFailure("Data Folder Invalid", "The data folder contains invalid characters:\n"+custom);
}
else if (!Path.IsPathRooted(custom)){
Reporter.HandleEarlyFailure("Data Folder Invalid", "The data folder has to be either a simple folder name, or a full path:\n"+custom);
}
return Environment.ExpandEnvironmentVariables(custom);
} }
else{ else{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), custom ?? BrandName); return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), custom ?? BrandName);
@@ -188,6 +216,35 @@ namespace TweetDck{
ReloadConfig(); ReloadConfig();
} }
private static CommandLineArgs GetArgsClean(){
CommandLineArgs args = Args.Clone();
args.RemoveFlag("-importcookies");
return args;
}
public static void Restart(){
Restart(new string[0]);
}
public static void Restart(string[] extraArgs){
FormBrowser browserForm = Application.OpenForms.OfType<FormBrowser>().FirstOrDefault();
if (browserForm == null){
return;
}
CommandLineArgs args = GetArgsClean();
args.AddFlag("-restart");
CommandLineArgs.ReadStringArray('-', extraArgs, args);
browserForm.ForceClose();
ExitCleanup();
Process.Start(Application.ExecutablePath, args.ToString());
Application.Exit();
}
private static void ExitCleanup(){ private static void ExitCleanup(){
if (HasCleanedUp)return; if (HasCleanedUp)return;

View File

@@ -37,3 +37,7 @@ using TweetDck;
[assembly: AssemblyFileVersion(Program.VersionFull)] [assembly: AssemblyFileVersion(Program.VersionFull)]
[assembly: NeutralResourcesLanguageAttribute("en")] [assembly: NeutralResourcesLanguageAttribute("en")]
#if DEBUG
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UnitTests")]
#endif

View File

@@ -2,7 +2,7 @@
The program was build using Visual Studio 2013. After opening the solution, make sure you have **CefSharp.WinForms** and **Microsoft.VC120.CRT.JetBrains** included - if not, download them using NuGet. For **CefSharp**, you will need version 53 or newer currently available as a pre-release. The program was build using Visual Studio 2013. After opening the solution, make sure you have **CefSharp.WinForms** and **Microsoft.VC120.CRT.JetBrains** included - if not, download them using NuGet. For **CefSharp**, you will need version 53 or newer currently available as a pre-release.
``` ```
PM> Install-Package CefSharp.WinForms -Pre -Version 53.0.0-pre01 PM> Install-Package CefSharp.WinForms -Version 53.0.1
PM> Install-Package Microsoft.VC120.CRT.JetBrains PM> Install-Package Microsoft.VC120.CRT.JetBrains
``` ```

View File

@@ -15,6 +15,16 @@ namespace TweetDck{
this.logFile = logFile; this.logFile = logFile;
} }
public void SetupUnhandledExceptionHandler(string caption){
AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
Exception ex = args.ExceptionObject as Exception;
if (ex != null){
HandleException(caption, "An unhandled exception has occurred.", false, ex);
}
};
}
public bool Log(string data){ public bool Log(string data){
StringBuilder build = new StringBuilder(); StringBuilder build = new StringBuilder();
@@ -34,7 +44,7 @@ namespace TweetDck{
} }
public void HandleException(string caption, string message, bool canIgnore, Exception e){ public void HandleException(string caption, string message, bool canIgnore, Exception e){
Log(e.ToString()); bool loggedSuccessfully = Log(e.ToString());
FormMessage form = new FormMessage(caption, message+"\r\nError: "+e.Message, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error); FormMessage form = new FormMessage(caption, message+"\r\nError: "+e.Message, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
@@ -43,6 +53,8 @@ namespace TweetDck{
Button btnOpenLog = new Button{ Button btnOpenLog = new Button{
Anchor = AnchorStyles.Bottom | AnchorStyles.Left, Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
Enabled = loggedSuccessfully,
Font = SystemFonts.MessageBoxFont,
Location = new Point(12, 12), Location = new Point(12, 12),
Margin = new Padding(0, 0, 48, 0), Margin = new Padding(0, 0, 48, 0),
Size = new Size(88, 26), Size = new Size(88, 26),
@@ -50,7 +62,9 @@ namespace TweetDck{
UseVisualStyleBackColor = true UseVisualStyleBackColor = true
}; };
btnOpenLog.Click += (sender, args) => Process.Start(logFile); btnOpenLog.Click += (sender, args) => {
using(Process.Start(logFile)){}
};
form.AddActionControl(btnOpenLog); form.AddActionControl(btnOpenLog);
@@ -70,5 +84,20 @@ namespace TweetDck{
Environment.FailFast(message, e); Environment.FailFast(message, e);
} }
} }
public static void HandleEarlyFailure(string caption, string message){
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
FormMessage form = new FormMessage(caption, message, MessageBoxIcon.Error);
form.AddButton("Exit");
form.ShowDialog();
try{
Process.GetCurrentProcess().Kill();
}catch{
Environment.FailFast(message, new Exception(message));
}
}
} }
} }

View File

@@ -0,0 +1,14 @@
[name]
Debug plugin
[description]
- Runs several tests on startup, only included in debug configuration
[author]
chylex
[version]
1.0
[website]
https://tweetduck.chylex.com

View File

@@ -0,0 +1,3 @@
enabled(){
}

View File

@@ -3,12 +3,16 @@ Clear columns
[description] [description]
- Adds buttons and keyboard shortcuts to quickly clear columns - Adds buttons and keyboard shortcuts to quickly clear columns
- Hold Shift when clicking or using a keyboard shortcut to reset the column instead
[author] [author]
chylex chylex
[version] [version]
1.0 1.1.1
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com
[requires]
1.4.1

View File

@@ -11,38 +11,75 @@ enabled(){
TD.controller.stats.columnActionClick("clear"); TD.controller.stats.columnActionClick("clear");
}; };
var clearAllColumns = () => { var resetColumn = (columnName) => {
Object.keys(TD.controller.columnManager.getAll()).forEach(key => clearColumn(key)); var col = TD.controller.columnManager.get(columnName);
col.model.setClearedTimestamp(0);
col.reloadTweets();
};
var forEachColumn = (func) => {
Object.keys(TD.controller.columnManager.getAll()).forEach(func);
}; };
var replaceMustache = (key, search, replace) => { var replaceMustache = (key, search, replace) => {
TD.mustaches[key] = TD.mustaches[key].replace(search, replace); TD.mustaches[key] = TD.mustaches[key].replace(search, replace);
}; };
var wasShiftPressed = false;
var updateShiftState = (pressed) => {
if (pressed != wasShiftPressed){
wasShiftPressed = pressed;
if (pressed){
$(document).on("mousemove", this.eventKeyUp);
}
else{
$(document).off("mousemove", this.eventKeyUp);
}
$("#clear-columns-btn-all").text(pressed ? "Reset all" : "Clear all");
}
};
// prepare event handlers // prepare event handlers
this.eventClearSingle = function(){ this.eventClickSingle = function(e){
clearColumn($(this).closest(".js-column").attr("data-column")); var name = $(this).closest(".js-column").attr("data-column");
e.shiftKey ? resetColumn(name) : clearColumn(name);
}; };
this.eventClearAll = function(){ this.eventClickAll = function(e){
clearAllColumns(); forEachColumn(e.shiftKey ? resetColumn : clearColumn);
}; };
this.eventKeys = function(e){ this.eventKeyDown = function(e){
if (e.keyCode === 46 && (document.activeElement === null || document.activeElement === document.body)){ // 46 = delete if (!(document.activeElement === null || document.activeElement === document.body)){
return;
}
updateShiftState(e.shiftKey);
if (e.keyCode === 46){ // 46 = delete
if (e.altKey){ if (e.altKey){
clearAllColumns(); forEachColumn(e.shiftKey ? resetColumn : clearColumn);
} }
else{ else{
var focusedColumn = $(".js-column.is-focused"); var focusedColumn = $(".js-column.is-focused");
if (focusedColumn.length){ if (focusedColumn.length){
clearColumn(focusedColumn.attr("data-column")); var name = focusedColumn.attr("data-column");
e.shiftKey ? resetColumn(name) : clearColumn(name);
} }
} }
} }
}; };
this.eventKeyUp = function(e){
if (!e.shiftKey){
updateShiftState(false);
}
};
// add column buttons and keyboard shortcut info to UI // add column buttons and keyboard shortcut info to UI
replaceMustache("column/column_header.mustache", "</header>", [ replaceMustache("column/column_header.mustache", "</header>", [
'{{^isTemporary}}', '{{^isTemporary}}',
@@ -62,27 +99,26 @@ enabled(){
].join("")); ].join(""));
// load custom style // load custom style
var style = document.createElement("style"); var css = window.TDPF_createCustomStyle(this);
document.head.appendChild(style); css.insert(".column-title { margin-right: 60px !important; }");
css.insert(".column-type-message .column-title { margin-right: 115px !important; }");
var sheet = style.sheet; css.insert(".mark-all-read-link { right: 59px !important; }");
sheet.insertRule(".column-title { margin-right: 60px !important; }", 0); css.insert(".open-compose-dm-link { right: 90px !important; }");
sheet.insertRule(".column-type-message .column-title { margin-right: 115px !important; }", 0); css.insert("button[data-action='clear'].btn-options-tray { display: none !important; }");
sheet.insertRule(".mark-all-read-link { right: 59px !important; }", 0);
sheet.insertRule(".open-compose-dm-link { right: 90px !important; }", 0);
} }
ready(){ ready(){
// setup events // setup events
$(document).on("click", "[data-action='td-clearcolumns-dosingle']", this.eventClearSingle); $(document).on("click", "[data-action='td-clearcolumns-dosingle']", this.eventClickSingle);
$(document).on("click", "[data-action='td-clearcolumns-doall']", this.eventClearAll); $(document).on("click", "[data-action='td-clearcolumns-doall']", this.eventClickAll);
$(document).on("keydown", this.eventKeys); $(document).on("keydown", this.eventKeyDown);
$(document).on("keyup", this.eventKeyUp);
// add clear all button // add clear all button
$("nav.app-navigator").first().append([ $("nav.app-navigator").first().append([
'<a class="link-clean cf app-nav-link padding-hl" data-title="Clear all" data-action="td-clearcolumns-doall">', '<a class="link-clean cf app-nav-link padding-h--10" data-title="Clear all" data-action="td-clearcolumns-doall">',
'<div class="obj-left"><i class="icon icon-large icon-clear-timeline"></i></div>', '<div class="obj-left margin-l--2"><i class="icon icon-medium icon-clear-timeline"></i></div>',
'<div class="nbfc padding-ts hide-condensed">Clear all</div>', '<div id="clear-columns-btn-all" class="nbfc padding-ts hide-condensed txt-size--16">Clear all</div>',
'</a></nav>' '</a></nav>'
].join("")); ].join(""));
} }

View File

@@ -2,6 +2,7 @@
Revert TweetDeck design changes Revert TweetDeck design changes
[description] [description]
- Individually configurable options to revert and tweak TweetDeck design changes
- Moves action menu to the right and hides it by default - Moves action menu to the right and hides it by default
- Reverts interactive texts around tweets (such as 'Details' or 'Conversation') - Reverts interactive texts around tweets (such as 'Details' or 'Conversation')
@@ -9,7 +10,16 @@ Revert TweetDeck design changes
chylex chylex
[version] [version]
1.0 1.2
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com
[configfile]
configuration.js
[configdefault]
configuration.default.js
[requires]
1.4.1

View File

@@ -1,24 +1,36 @@
enabled(){ enabled(){
// add a stylesheet to change tweet actions this.css = window.TDPF_createCustomStyle(this);
var style = document.createElement("style");
style.id = "design-revert";
document.head.appendChild(style);
var sheet = style.sheet;
sheet.insertRule(".tweet-actions { float: right !important; width: auto !important; }", 0);
sheet.insertRule(".tweet-action { opacity: 0; }", 0);
sheet.insertRule(".is-favorite .tweet-action, .is-retweet .tweet-action { opacity: 0.5; visibility: visible !important; }", 0);
sheet.insertRule(".tweet:hover .tweet-action, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1; visibility: visible !important; }", 0);
sheet.insertRule(".tweet-actions > li:nth-child(4) { margin-right: 2px !important; }", 0);
// revert small links around the tweet
this.prevFooterMustache = TD.mustaches["status/tweet_single_footer.mustache"]; this.prevFooterMustache = TD.mustaches["status/tweet_single_footer.mustache"];
var footerLayout = TD.mustaches["status/tweet_single_footer.mustache"]; // load configuration
footerLayout = footerLayout.replace('txt-mute txt-size--12', 'txt-mute txt-small'); window.TDPF_loadConfigurationFile(this, "configuration.js", "configuration.default.js", config => {
footerLayout = footerLayout.replace('{{#inReplyToID}}', '{{^inReplyToID}} <a class="pull-left margin-txs txt-mute txt-small is-vishidden-narrow" href="#" rel="viewDetails">{{_i}}Details{{/i}}</a> <a class="pull-left margin-txs txt-mute txt-small is-vishidden is-visshown-narrow" href="#" rel="viewDetails">{{_i}}Open{{/i}}</a> {{/inReplyToID}} {{#inReplyToID}}'); if (config.hideTweetActions){
footerLayout = footerLayout.replace('<span class="link-complex-target"> {{_i}}View Conversation{{/i}}', '<i class="icon icon-conversation icon-small-context"></i> <span class="link-complex-target"> <span class="is-vishidden-wide is-vishidden-narrow">{{_i}}View{{/i}}</span> <span class="is-vishidden is-visshown-wide">{{_i}}Conversation{{/i}}</span>'); this.css.insert(".tweet-action { opacity: 0; }");
TD.mustaches["status/tweet_single_footer.mustache"] = footerLayout; this.css.insert(".is-favorite .tweet-action, .is-retweet .tweet-action { opacity: 0.5; visibility: visible !important; }");
this.css.insert(".tweet:hover .tweet-action, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1; visibility: visible !important; }");
}
if (config.moveTweetActionsToRight){
this.css.insert(".tweet-actions { float: right !important; width: auto !important; }");
this.css.insert(".tweet-actions > li:nth-child(4) { margin-right: 2px !important; }");
}
if (config.smallComposeTextSize){
this.css.insert(".compose-text { font-size: 12px !important; height: 120px !important; }");
}
if (config.revertConversationLinks){
var footer = TD.mustaches["status/tweet_single_footer.mustache"];
footer = footer.replace('txt-mute txt-size--12', 'txt-mute txt-small');
footer = footer.replace('{{#inReplyToID}}', '{{^inReplyToID}} <a class="pull-left margin-txs txt-mute txt-small is-vishidden-narrow" href="#" rel="viewDetails">{{_i}}Details{{/i}}</a> <a class="pull-left margin-txs txt-mute txt-small is-vishidden is-visshown-narrow" href="#" rel="viewDetails">{{_i}}Open{{/i}}</a> {{/inReplyToID}} {{#inReplyToID}}');
footer = footer.replace('<span class="link-complex-target"> {{_i}}View Conversation{{/i}}', '<i class="icon icon-conversation icon-small-context"></i> <span class="link-complex-target"> <span class="is-vishidden-wide is-vishidden-narrow">{{_i}}View{{/i}}</span> <span class="is-vishidden is-visshown-wide">{{_i}}Conversation{{/i}}</span>');
TD.mustaches["status/tweet_single_footer.mustache"] = footer;
}
if (config.moveTweetActionsToRight){
$(document).on("uiShowActionsMenu", this.uiShowActionsMenuEvent);
}
});
// fix layout for right-aligned actions menu // fix layout for right-aligned actions menu
this.uiShowActionsMenuEvent = function(){ this.uiShowActionsMenuEvent = function(){
@@ -26,12 +38,9 @@ enabled(){
}; };
} }
ready(){
$(document).on("uiShowActionsMenu", this.uiShowActionsMenuEvent);
}
disabled(){ disabled(){
$("#design-revert").remove(); this.css.remove();
$(document).off("uiShowActionsMenu", this.uiShowActionsMenuEvent);
TD.mustaches["status/tweet_single_footer.mustache"] = this.prevFooterMustache; TD.mustaches["status/tweet_single_footer.mustache"] = this.prevFooterMustache;
$(document).off("uiShowActionsMenu", this.uiShowActionsMenuEvent);
} }

View File

@@ -0,0 +1,22 @@
{
/*
* Hides the action bar below each tweet.
* The action bar fully appears when the mouse is over the tweet, or at half the opacity when the tweet is liked/retweeted.
*/
hideTweetActions: true,
/*
* Moves the action bar to the right side of the tweet.
*/
moveTweetActionsToRight: true,
/*
* Reverts New Tweet font size to a smaller one.
*/
smallComposeTextSize: false,
/*
* Reverts design changes to the 'View Conversation' and 'Details' links.
*/
revertConversationLinks: true
}

View File

@@ -0,0 +1,18 @@
[name]
Emoji keyboard
[description]
- Adds an emoji keyboard when writing tweets
- Emoji list provided by http://unicode.org/emoji/charts/emoji-ordering.html
[author]
chylex
[version]
1.0
[website]
https://tweetduck.chylex.com
[requires]
1.5.3

View File

@@ -0,0 +1,178 @@
enabled(){
this.emojiHTML = "";
var me = this;
// styles
this.css = window.TDPF_createCustomStyle(this);
this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; height: 11.2em; background-color: white; overflow-y: auto; padding: 0.1em; box-sizing: border-box; border-radius: 2px; font-size: 24px; z-index: 9999 }");
this.css.insert(".emoji-keyboard .separator { height: 26px; }");
this.css.insert(".emoji-keyboard .emoji { padding: 0.1em !important; cursor: pointer }");
// layout
var buttonHTML = '<button class="needsclick btn btn-on-blue txt-left padding-v--9 emoji-keyboard-popup-btn"><i class="icon icon-heart"></i></button>';
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="cf margin-t--12 margin-b--30">', '<div class="cf margin-t--12 margin-b--30">'+buttonHTML);
var dockedComposePanel = $(".js-docked-compose");
if (dockedComposePanel.length){
dockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
}
// keyboard generation
this.currentKeyboard = null;
var hideKeyboard = () => {
$(this.currentKeyboard).remove();
this.currentKeyboard = null;
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
};
this.generateKeyboardFor = (input, left, top) => {
var created = document.createElement("div");
document.body.appendChild(created);
created.classList.add("emoji-keyboard");
created.style.left = left+"px";
created.style.top = top+"px";
created.innerHTML = this.emojiHTML;
created.addEventListener("click", function(e){
if (e.target.tagName === "IMG"){
input.val(input.val()+e.target.getAttribute("alt"));
input.trigger("change");
input.focus();
}
e.stopPropagation();
});
return created;
};
this.prevTryPasteImage = window.TDGF_tryPasteImage;
var prevTryPasteImageF = this.prevTryPasteImage;
window.TDGF_tryPasteImage = function(){
if (me.currentKeyboard){
hideKeyboard();
}
return prevTryPasteImageF.apply(this, arguments);
};
// event handlers
this.emojiKeyboardButtonClickEvent = function(e){
if (me.currentKeyboard){
hideKeyboard();
}
else{
var pos = $(this).offset();
me.currentKeyboard = me.generateKeyboardFor($(".js-compose-text").first(), pos.left, pos.top+$(this).outerHeight()+8);
$(this).addClass("is-selected");
}
$(this).blur();
e.stopPropagation();
};
this.documentClickEvent = function(e){
if (me.currentKeyboard && !e.target.classList.contains("js-compose-text")){
hideKeyboard();
}
};
this.documentKeyEvent = function(e){
if (me.currentKeyboard && e.keyCode === 27){ // escape
hideKeyboard();
e.stopPropagation();
}
};
/*
* TODO
* ----
* add emoji search if I can be bothered
* lazy emoji loading
*/
}
ready(){
$(".emoji-keyboard-popup-btn").on("click", this.emojiKeyboardButtonClickEvent);
$(document).on("click", this.documentClickEvent);
$(document).on("keydown", this.documentKeyEvent);
// HTML generation
var convUnicode = function(codePt){
if (codePt > 0xFFFF){
codePt -= 0x10000;
return String.fromCharCode(0xD800+(codePt>>10), 0xDC00+(codePt&0x3FF));
}
else{
return String.fromCharCode(codePt);
}
};
$TDP.readFileRoot(this.$token, "emoji-ordering.txt").then(contents => {
let generated = [];
let addDeclaration = decl => {
generated.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""));
};
let skinTones = [
"1F3FB", "1F3FC", "1F3FD", "1F3FE", "1F3FF"
];
for(let line of contents.split("\n")){
if (line[0] === '@'){
generated.push("___");
}
else{
let decl = line.slice(0, line.indexOf(";"));
let skinIndex = decl.indexOf('$');
if (skinIndex !== -1){
let declPre = decl.slice(0, skinIndex);
let declPost = decl.slice(skinIndex+1);
skinTones.map(skinTone => declPre+skinTone+declPost).forEach(addDeclaration);
}
else{
addDeclaration(decl);
}
}
}
let start = "<p style='font-size:13px;color:#444;margin:4px;text-align:center'>Please, note that most emoji will not show up properly in the text box above, but they will display in the tweet.</p>";
this.emojiHTML = start+TD.util.cleanWithEmoji(generated.join("")).replace(/___/g, "<div class='separator'></div>");
}).catch(err => {
$TD.alert("error", "Problem loading emoji keyboard: "+err.message);
});
}
disabled(){
this.css.remove();
if (this.currentKeyboard){
$(this.currentKeyboard).remove();
}
window.TDGF_tryPasteImage = this.prevTryPasteImage;
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
$(".emoji-keyboard-popup-btn").remove();
$(document).off("click", this.documentClickEvent);
$(document).off("keydown", this.documentKeyEvent);
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ Custom reply account
chylex chylex
[version] [version]
1.0 1.2
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

View File

@@ -1,67 +1,137 @@
enabled(){ enabled(){
var configuration = { defaultAccount: "" }; var configuration = { defaultAccount: "#preferred" };
window.TDPF_loadConfigurationFile(this, "configuration.js", "configuration.default.js", obj => configuration = obj); window.TDPF_loadConfigurationFile(this, "configuration.js", "configuration.default.js", obj => configuration = obj);
this.uiInlineComposeTweetEvent = function(e, data){ this.lastSelectedAccount = null;
var account = null;
if (configuration.useAdvancedSelector && configuration.customSelector){ this.uiComposeTweetEvent = (e, data) => {
var column = TD.controller.columnManager.get(data.element.closest("section.column").attr("data-column")); if (data.type !== "reply" || data.popFromInline || !("element" in data)){
var result = configuration.customSelector(column);
if (typeof result === "string" && result[0] === '@'){
account = result.substring(1);
}
}
if (account === null){
if (configuration.defaultAccount === false){
return; return;
} }
else if (configuration.defaultAccount !== "" && configuration.defaultAccount[0] === '@'){
account = configuration.defaultAccount.substring(1); var query;
}
if (configuration.useAdvancedSelector){
if (configuration.customSelector){
if (configuration.customSelector.toString().startsWith("function (column){")){
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector needs to be updated due to TweetDeck changes, please read the default configuration file for the updated guide");
return;
} }
var identifier; var section = data.element.closest("section.column");
if (account === null){ var column = TD.controller.columnManager.get(section.attr("data-column"));
identifier = TD.storage.clientController.client.getDefaultAccount(); var header = $("h1.column-title", section);
var columnTitle = header.children(".column-head-title").text();
var columnAccount = header.children(".attribution").text();
try{
query = configuration.customSelector(column.getColumnType(), columnTitle, columnAccount, column);
}catch(e){
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector threw an error: "+e.message);
return;
}
} }
else{ else{
var obj = TD.storage.accountController.getAccountFromUsername(account); $TD.alert("warning", "Plugin reply-account has invalid configuration: useAdvancedSelector is true, but customSelector function is missing");
return;
}
}
else{
query = configuration.defaultAccount;
if (query === ""){
query = "#preferred";
}
else if (typeof query !== "string"){
query = "#default";
}
}
if (typeof query === "undefined"){
query = "#preferred";
}
if (typeof query !== "string"){
return;
}
else if (query.length === 0){
$TD.alert("warning", "Plugin reply-account has invalid configuration: the requested account is empty");
return;
}
else if (query[0] !== '@' && query[0] !== '#'){
$TD.alert("warning", "Plugin reply-account has invalid configuration: the requested account does not begin with @ or #: "+query);
return;
}
var identifier = null;
switch(query){
case "#preferred":
identifier = TD.storage.clientController.client.getDefaultAccount();
break;
case "#last":
if (this.lastSelectedAccount === null){
return;
}
identifier = this.lastSelectedAccount;
break;
case "#default":
return;
default:
if (query[0] === '@'){
var obj = TD.storage.accountController.getAccountFromUsername(query.substring(1));
if (obj.length === 0){ if (obj.length === 0){
$TD.alert("warning", "Plugin reply-account has invalid configuration: requested account not found: "+query);
return; return;
} }
else{ else{
identifier = obj[0].privateState.key; identifier = obj[0].privateState.key;
} }
} }
else{
$TD.alert("warning", "Plugin reply-account has invalid configuration: unknown requested account query: "+query);
return;
}
}
data.singleFrom = data.from = [ identifier ]; data.singleFrom = data.from = [ identifier ];
}; };
this.onSelectedAccountChanged = () => {
var selected = $(".js-account-item.is-selected", ".js-account-list");
this.lastSelectedAccount = selected.length === 1 ? selected.attr("data-account-key") : null;
};
} }
ready(){ ready(){
var events = $._data(document, "events"); var events = $._data(document, "events");
if ("uiInlineComposeTweet" in events){ for(var event of [ "uiInlineComposeTweet", "uiDockedComposeTweet" ]){
$(document).on("uiInlineComposeTweet", this.uiInlineComposeTweetEvent); $(document).on(event, this.uiComposeTweetEvent);
var handlers = events["uiInlineComposeTweet"]; var handlers = events[event];
var oldHandler = handlers[0]; var newHandler = handlers[handlers.length-1];
var newHandler = handlers[1];
for(var index = handlers.length-1; index > 0; index--){
handlers[index] = handlers[index-1];
}
handlers[0] = newHandler; handlers[0] = newHandler;
handlers[1] = oldHandler;
}
else{
$(document).on("uiInlineComposeTweet", this.uiInlineComposeTweetEvent);
} }
$(document).on("click", ".js-account-list .js-account-item", this.onSelectedAccountChanged);
} }
disabled(){ disabled(){
$(document).off("uiInlineComposeTweet", this.uiInlineComposeTweetEvent); $(document).off("uiInlineComposeTweet", this.uiComposeTweetEvent);
$(document).off("uiDockedComposeTweet", this.uiComposeTweetEvent);
$(document).off("click", ".js-account-list .js-account-item", this.onSelectedAccountChanged);
} }

View File

@@ -13,13 +13,14 @@
* *
* Set value of 'defaultAccount' to one of the following values: * Set value of 'defaultAccount' to one of the following values:
* *
* "" to use your preferred TweetDeck account for all replies (default) * "#preferred" to use your preferred TweetDeck account (the one used to log into TweetDeck)
* "#last" to specify the account that was selected last time (only updates if a single account is selected)
* "#default" to fall back to default TweetDeck behavior; useful for advanced configuration below, otherwise disable the plugin instead
* "@myAccount" to specify an account name to use; has to be one of your registered account names * "@myAccount" to specify an account name to use; has to be one of your registered account names
* false to fall back to default TweetDeck behavior; useful for advanced configuration below, otherwise disable the plugin instead
* *
*/ */
defaultAccount: "", defaultAccount: "#preferred",
/* /*
* Advanced way of configuring the plugin * Advanced way of configuring the plugin
@@ -30,45 +31,46 @@
* 1. Set value of 'useAdvancedSelector' to true * 1. Set value of 'useAdvancedSelector' to true
* 2. Uncomment the 'customSelector' function, and replace the example code with your desired behavior * 2. Uncomment the 'customSelector' function, and replace the example code with your desired behavior
* *
* If 'customSelector' returns a string containing a full name of one of the registered accounts (including @), that account is used. * The 'customSelector' function should return a string in one of the formats supported by 'defaultAccount'.
* If it returns anything else (for example false, undefined, or an account name that is not registered), it falls back to 'defaultAccount' behavior. * If it returns anything else (for example, false or undefined), it falls back to 'defaultAccount' behavior.
* *
* The 'column' parameter is a TweetDeck column object. If you want to see all properties of the object, open your browser, nagivate to TweetDeck,
* log in, and run the following code in your browser console, which will return an object containing all of the column objects mapped to their IDs:
* TD.controller.columnManager.getAll()
*
* The example below shows how to extract the column type, title, and account from the object.
* Column type is prefixed with col_, and may be one of the following:
* *
* The 'type' parameter is TweetDeck column type. Here is the full list of column types, note that some are
* unused and have misleading names (for example, Home columns are 'col_timeline' instead of 'col_home'):
* col_timeline, col_interactions, col_mentions, col_followers, col_search, col_list, * col_timeline, col_interactions, col_mentions, col_followers, col_search, col_list,
* col_customtimeline, col_messages, col_usertweets, col_favorites, col_activity, * col_customtimeline, col_messages, col_usertweets, col_favorites, col_activity,
* col_dataminr, col_home, col_me, col_inbox, col_scheduled, col_unknown * col_dataminr, col_home, col_me, col_inbox, col_scheduled, col_unknown
* *
* Some of these appear to be unused (for example, Home columns are 'col_timeline' instead of 'col_home'). * If you want to see your current column types, run this in your browser console:
* TD.controller.columnManager.getAllOrdered().map(obj => obj.getColumnType());
* *
* If you want to see your column types, run this in your browser console:
* Object.values(TD.controller.columnManager.getAll()).forEach(obj => console.log(obj.getColumnType()));
* *
* You can also get the jQuery column object using: $("section.column[data-column='"+column.ui.state.columnKey+"']") * The 'title' parameter is the column title. Some are fixed (such as 'Home' or 'Notifications'),
* some contain specific information (for example, Search columns contain the search query).
*
*
* The 'account' parameter is the account name displayed next to the column title (including the @).
* This parameter is empty for some columns (such as Messages, or Notifications for all accounts) or can
* contain other text (for example, the Scheduled column contains the string 'All accounts').
*
*
* The 'column' parameter is a TweetDeck column object. If you want to see all properties of the object,
* run the following code in your browser console, which will return an array containing all of your
* current column objects in order:
* TD.controller.columnManager.getAllOrdered()
* *
*/ */
useAdvancedSelector: false, useAdvancedSelector: false,
/*customSelector: function(column){ /*customSelector: function(type, title, account, column){
var titleObj = $(column.getTitleHTML()); if (type === "col_search" && title === "TweetDuck"){
var columnType = column.getColumnType(); // col_timeline
var columnTitle = titleObj.siblings(".column-head-title").text(); // Home
var columnAccount = titleObj.siblings(".attribution").text(); // @chylexmc
if (columnType === "col_search" && columnTitle === "TweetDuck"){
// This is a search column that looks for 'TweetDuck' in the tweets, // This is a search column that looks for 'TweetDuck' in the tweets,
// search columns are normally linked to the preferred account // search columns are normally linked to the preferred account
// so this forces the @TryTweetDuck account to be used instead. // so this forces the @TryTweetDuck account to be used instead.
return "@TryTweetDuck"; return "@TryTweetDuck";
} }
else if (columnType === "col_timeline" && columnAccount === "@chylexcz"){ else if (type === "col_timeline" && account === "@chylexcz"){
// This is a Home column of my test account @chylexcz, // This is a Home column of my test account @chylexcz,
// but I want to reply to tweets from my official account. // but I want to reply to tweets from my official account.
return "@chylexmc"; return "@chylexmc";

View File

@@ -0,0 +1,18 @@
[name]
Polls in timelines
[description]
- Adds poll result display directly into timelines
- Experimental, may be buggy or break when TweetDeck updates
[author]
chylex
[version]
1.0
[website]
https://tweetduck.chylex.com
[requires]
1.4.1

View File

@@ -0,0 +1,25 @@
constructor(){
super({
requiresPageReload: true
});
}
enabled(){
// add a stylesheet
window.TDPF_createCustomStyle(this).insert(".column-detail .timeline-poll-container { display: none }");
// setup layout injecting
var injectLayout = (mustache, onlyIfNotFound, search, replace) => {
if (TD.mustaches[mustache].indexOf(onlyIfNotFound) === -1){
TD.mustaches[mustache] = TD.mustaches[mustache].replace(search, replace);
}
};
// add poll rendering to tweets
injectLayout("status/tweet_single.mustache", "status/poll", "{{/quotedTweetMissing}} {{#translation}}", "{{/quotedTweetMissing}} <div class='timeline-poll-container'>{{#poll}}{{>duck/tweet_single/poll}}{{/poll}}</div> {{#translation}}");
TD.mustaches["duck/tweet_single/poll.mustache"] = '<div class="js-poll margin-tl"> {{#poll}} <ul class="margin-b--12"> {{#choices}} <li class="position-rel margin-b--8 height-3"> <div class="poll-bar pin-top height-p--100 br-1 {{#isWinner}}poll-bar--winner{{/isWinner}} {{#hasTimeLeft}}br-left{{/hasTimeLeft}} width-p--{{percentage}}"/> <div class="poll-label position-rel padding-a--4"> <span class="txt-bold txt-right inline-block width-5 padding-r--4">{{percentage}}%</span> {{{label}}} {{#isSelectedChoice}} <i class="icon icon-check txt-size-variable--11"></i> {{/isSelectedChoice}} </div> </li> {{/choices}} </ul> <span class="inline-block txt-small padding-ls txt-seamful-deep-gray"> {{{prettyCount}}} &middot; {{#hasTimeLeft}} {{{prettyTimeLeft}}} {{/hasTimeLeft}} {{^hasTimeLeft}} {{_i}}Final results{{/i}} {{/hasTimeLeft}} </span> {{/poll}} </div>';
}
disabled(){
// not needed, plugin reloads the page when enabled or disabled
}

View File

@@ -1,4 +1,4 @@
(function($, $TD, TD){ (function($, $TD, $TDX, TD){
// //
// Variable: Current highlighted column jQuery object. // Variable: Current highlighted column jQuery object.
// //
@@ -10,71 +10,14 @@
var highlightedTweetEle; var highlightedTweetEle;
// //
// Function: Initializes TweetD*ck events. Called after the website app is loaded. // Variable: Array of functions called after the website app is loaded.
// //
var initializeTweetDck = function(){ var onAppReady = [];
// Settings button hook
$("[data-action='settings-menu']").click(function(){
setTimeout(function(){
var menu = $(".js-dropdown-content").children("ul").first();
if (menu.length === 0)return;
menu.children(".drp-h-divider").last().after([ //
'<li class="is-selectable" data-std><a href="#" data-action="td-settings">'+$TD.brandName+' settings</a></li>', // Variable: DOM object containing the main app element.
'<li class="is-selectable" data-std><a href="#" data-action="td-plugins">'+$TD.brandName+' plugins</a></li>', //
'<li class="drp-h-divider"></li>' var app = $(document.body).children(".js-app");
].join(""));
var buttons = menu.children("[data-std]");
buttons.on("click", "a", function(){
var action = $(this).attr("data-action");
if (action === "td-settings"){
$TD.openSettingsMenu();
}
else if (action === "td-plugins"){
$TD.openPluginsMenu();
}
});
buttons.hover(function(){
$(this).addClass("is-selected");
}, function(){
$(this).removeClass("is-selected");
});
}, 0);
});
// Notification handling
$.subscribe("/notifications/new", function(obj){
for(let index = obj.items.length-1; index >= 0; index--){
onNewTweet(obj.column, obj.items[index]);
}
});
// Setup video element replacement and fix missing target in user links
new MutationObserver(function(){
$("video").each(function(){
$(this).parent().replaceWith("<a href='"+$(this).attr("src")+"' rel='url' target='_blank' style='display:block; border:1px solid #555; padding:3px 6px'>&#9658; Open video in browser</a>");
});
$("a[rel='user']").attr("target", "_blank");
}).observe($(".js-app-columns")[0], {
childList: true,
subtree: true
});
// Finish init and load plugins
$TD.loadFontSizeClass(TD.settings.getFontSize());
$TD.loadNotificationHeadContents(getNotificationHeadContents());
window.TD_APP_READY = true;
if (window.TD_PLUGINS){
window.TD_PLUGINS.onReady();
}
};
// //
// Function: Prepends code at the beginning of a function. If the prepended function returns true, execution of the original function is cancelled. // Function: Prepends code at the beginning of a function. If the prepended function returns true, execution of the original function is cancelled.
@@ -104,18 +47,23 @@
var html = $(tweet.render({ var html = $(tweet.render({
withFooter: false, withFooter: false,
withTweetActions: false, withTweetActions: false,
withMediaPreview: false withMediaPreview: true,
isMediaPreviewOff: true,
isMediaPreviewSmall: false,
isMediaPreviewLarge: false
})); }));
html.css("border", "0"); html.css("border", "0");
html.find(".tweet-body").first().children("footer").remove(); html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
html.find(".js-quote-detail").removeClass("is-actionable");
var url = html.find("time").first().children("a").first().attr("href") || ""; var url = html.find("time").first().children("a").first().attr("href") || "";
$TD.onTweetPopup(html.html(), url, tweet.text.length); // TODO column $TD.onTweetPopup(html.html(), url, tweet.text.length); // TODO column
} }
else if (column.model.getHasSound()){
$TD.onTweetSound(); // TODO disable original if (column.model.getHasSound()){
$TD.onTweetSound();
} }
}; };
@@ -132,23 +80,6 @@
return tags.join(""); return tags.join("");
}; };
//
// Block: Observe the app <div> element and initialize TweetD*ck whenever possible.
//
var app = $("body").children(".js-app");
new MutationObserver(function(){
if (window.TD_APP_READY && app.hasClass("is-hidden")){
window.TD_APP_READY = false;
}
else if (!window.TD_APP_READY && !app.hasClass("is-hidden")){
initializeTweetDck();
}
}).observe(app[0], {
attributes: true,
attributeFilter: [ "class" ]
});
// //
// Block: Hook into settings object to detect when the settings change. // Block: Hook into settings object to detect when the settings change.
// //
@@ -163,7 +94,7 @@
}); });
// //
// Block: Force popup notification settings. // Block: Enable popup notifications.
// //
TD.controller.notifications.hasNotifications = function(){ TD.controller.notifications.hasNotifications = function(){
return true; return true;
@@ -173,6 +104,49 @@
return true; return true;
}; };
$.subscribe("/notifications/new", function(obj){
for(let index = obj.items.length-1; index >= 0; index--){
onNewTweet(obj.column, obj.items[index]);
}
});
//
// Block: Add TweetDuck buttons to the settings menu.
//
onAppReady.push(function(){
$("[data-action='settings-menu']").click(function(){
setTimeout(function(){
var menu = $(".js-dropdown-content").children("ul").first();
if (menu.length === 0)return;
menu.children(".drp-h-divider").last().after([
'<li class="is-selectable" data-std><a href="#" data-action="td-settings">TweetDuck settings</a></li>',
'<li class="is-selectable" data-std><a href="#" data-action="td-plugins">TweetDuck plugins</a></li>',
'<li class="drp-h-divider"></li>'
].join(""));
var buttons = menu.children("[data-std]");
buttons.on("click", "a", function(){
var action = $(this).attr("data-action");
if (action === "td-settings"){
$TD.openSettingsMenu();
}
else if (action === "td-plugins"){
$TD.openPluginsMenu();
}
});
buttons.hover(function(){
$(this).addClass("is-selected");
}, function(){
$(this).removeClass("is-selected");
});
}, 0);
});
});
// //
// Block: Expand shortened links on hover or display tooltip. // Block: Expand shortened links on hover or display tooltip.
// //
@@ -194,7 +168,7 @@
return; return;
} }
if ($TD.expandLinksOnHover){ if ($TDX.expandLinksOnHover){
tooltipTimer = window.setTimeout(function(){ tooltipTimer = window.setTimeout(function(){
var expanded = me.attr("data-full-url"); var expanded = me.attr("data-full-url");
expanded = cutStart(expanded, "https://"); expanded = cutStart(expanded, "https://");
@@ -213,7 +187,7 @@
} }
} }
else if (e.type === "mouseleave"){ else if (e.type === "mouseleave"){
if ($TD.expandLinksOnHover){ if ($TDX.expandLinksOnHover){
var prevText = me.attr("td-prev-text"); var prevText = me.attr("td-prev-text");
if (prevText){ if (prevText){
@@ -252,14 +226,10 @@
var soundEle = document.getElementById("update-sound"); var soundEle = document.getElementById("update-sound");
soundEle.play = prependToFunction(soundEle.play, function(){ soundEle.play = prependToFunction(soundEle.play, function(){
return $TD.muteNotifications; return $TDX.muteNotifications || $TDX.hasCustomNotificationSound;
}); });
})(); })();
/* TODO document.getElementById("update-sound").play = function(){
$TD.onTweetSound();
};*/
// //
// Block: Update highlighted column. // Block: Update highlighted column.
// //
@@ -289,7 +259,7 @@
if (e.type === "mouseenter"){ if (e.type === "mouseenter"){
highlightedTweetEle = $(this); highlightedTweetEle = $(this);
var link = $(this).find("time").first().children("a").first(); var link = $(this).parent().hasClass("js-tweet-detail") ? $(this).find("a[rel='url']").first() : $(this).find("time").first().children("a").first();
var embedded = $(this).find(".quoted-tweet[data-tweet-id]").first(); var embedded = $(this).find(".quoted-tweet[data-tweet-id]").first();
updateHighlightedTweet(link.length > 0 ? link.attr("href") : "", embedded.length > 0 ? embedded.find(".account-link").first().attr("href")+"/status/"+embedded.attr("data-tweet-id") : ""); updateHighlightedTweet(link.length > 0 ? link.attr("href") : "", embedded.length > 0 ? embedded.find(".account-link").first().attr("href")+"/status/"+embedded.attr("data-tweet-id") : "");
@@ -301,6 +271,67 @@
}); });
})(); })();
//
// Block: Screenshot tweet to clipboard.
//
(function(){
var selectedTweet;
var setImportantProperty = function(obj, property, value){
if (obj.length === 1){
obj[0].style.setProperty(property, value, "important");
}
};
app.delegate("article.js-stream-item", "contextmenu", function(){
selectedTweet = $(this);
});
window.TDGF_triggerScreenshot = function(){
if (selectedTweet){
var tweetWidth = selectedTweet.width();
var parent = selectedTweet.parent();
var isDetail = parent.hasClass("js-tweet-detail");
var isReply = !isDetail && (parent.hasClass("js-replies-to") || parent.hasClass("js-replies-before"));
selectedTweet = selectedTweet.clone();
selectedTweet.children().first().addClass($(document.documentElement).attr("class")).css("padding-bottom", "12px");
setImportantProperty(selectedTweet.find(".js-quote-detail"), "margin-bottom", "0");
setImportantProperty(selectedTweet.find(".js-media-preview-container"), "margin-bottom", "0");
if (isDetail){
setImportantProperty(selectedTweet.find(".js-tweet-media"), "margin-bottom", "0");
selectedTweet.find(".js-translate-call-to-action").first().remove();
selectedTweet.find(".js-cards-container").first().nextAll().remove();
selectedTweet.find(".js-detail-view-inline").first().remove();
}
else{
selectedTweet.find("footer").last().remove();
}
if (isReply){
selectedTweet.find(".is-conversation").removeClass("is-conversation");
selectedTweet.find(".timeline-poll-container").first().remove(); // fix for timeline polls plugin
}
selectedTweet.find(".js-poll-link").remove();
var testTweet = selectedTweet.clone().css({
position: "absolute",
left: "-999px",
width: tweetWidth+"px"
}).appendTo(document.body);
var realHeight = testTweet.height();
testTweet.remove();
$TD.screenshotTweet(selectedTweet.html(), tweetWidth, realHeight);
}
};
})();
// //
// Block: Paste images when tweeting. // Block: Paste images when tweeting.
// //
@@ -325,7 +356,7 @@
$TD.clickUploadImage(Math.floor(buttonPos.left), Math.floor(buttonPos.top)); $TD.clickUploadImage(Math.floor(buttonPos.left), Math.floor(buttonPos.top));
}; };
$(".js-app").delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(){ app.delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(){
lastPasteElement = $(this); lastPasteElement = $(this);
$TD.tryPasteImage(); $TD.tryPasteImage();
}); });
@@ -376,26 +407,39 @@
// //
// Block: Support for extra mouse buttons. // Block: Support for extra mouse buttons.
// //
(function(){
var tryClickSelector = function(selector, parent){
return $(selector, parent).click().length;
};
var tryCloseModal = function(){
var modal = $("#open-modal");
return modal.is(":visible") && tryClickSelector("a[rel=dismiss]", modal);
};
var tryCloseHighlightedColumn = function(){
if (highlightedColumnEle){
var column = highlightedColumnEle.closest(".js-column");
return (column.is(".is-shifted-2") && tryClickSelector(".js-tweet-social-proof-back", column)) || (column.is(".is-shifted-1") && tryClickSelector(".js-column-back", column));
}
};
window.TDGF_onMouseClickExtra = function(button){ window.TDGF_onMouseClickExtra = function(button){
if (button === 1){ // back button if (button === 1){ // back button
var modal = $("#open-modal"); tryCloseModal() ||
tryClickSelector(".js-inline-compose-close") ||
if (highlightedColumnEle && highlightedColumnEle.closest(".js-column").is(".is-shifted-1")){ tryCloseHighlightedColumn() ||
highlightedColumnEle.find(".js-column-back").first().click(); tryClickSelector(".js-app-content.is-open .js-drawer-close:visible") ||
} tryClickSelector(".is-shifted-2 .js-tweet-social-proof-back, .is-shifted-2 .js-dm-participants-back") ||
else if (modal.is(":visible")){
modal.find("a[rel=dismiss]").click();
}
else{
$(".js-column-back").click(); $(".js-column-back").click();
} }
}
else if (button === 2){ // forward button else if (button === 2){ // forward button
if (highlightedTweetEle){ if (highlightedTweetEle){
highlightedTweetEle.children().first().click(); highlightedTweetEle.children().first().click();
} }
} }
}; };
})();
// //
// Block: Fix scheduled tweets not showing up sometimes. // Block: Fix scheduled tweets not showing up sometimes.
@@ -412,6 +456,70 @@
} }
}); });
//
// Block: Hold Shift to reset cleared column.
//
(function(){
var holdingShift = false;
var updateShiftState = (pressed) => {
if (pressed != holdingShift){
holdingShift = pressed;
$("button[data-action='clear']").children("span").text(holdingShift ? "Reset" : "Clear");
}
};
var resetActiveFocus = () => {
document.activeElement.blur();
};
$(document).keydown(function(e){
if (e.shiftKey && (document.activeElement === null || !("value" in document.activeElement))){
updateShiftState(true);
}
}).keyup(function(e){
if (!e.shiftKey){
updateShiftState(false);
}
});
TD.vo.Column.prototype.clear = prependToFunction(TD.vo.Column.prototype.clear, function(){
window.setTimeout(resetActiveFocus, 0); // unfocuses the Clear button, otherwise it steals keyboard input
if (holdingShift){
this.model.setClearedTimestamp(0);
this.reloadTweets();
return true;
}
});
})();
//
// Block: Swap shift key functionality for selecting accounts.
//
onAppReady.push(function(){
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", function(e){
e.shiftKey = !e.shiftKey;
});
TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){
if (!this.$node.attr("td-account-selector-hook")){
this.$node.attr("td-account-selector-hook", "1");
this.$node.delegate(".js-account-item", "click", function(e){
e.shiftKey = !e.shiftKey;
});
}
});
});
//
// Block: Work around clipboard HTML formatting.
//
$(document).on("copy", function(e){
window.setTimeout($TD.fixClipboard, 0);
});
// //
// Block: Inject custom CSS and layout into the page. // Block: Inject custom CSS and layout into the page.
// //
@@ -424,10 +532,79 @@
styleOfficial.sheet.insertRule(".txt-base-smallest .badge-verified:before { height: 13px !important; }", 0); // fix cut off badge icon styleOfficial.sheet.insertRule(".txt-base-smallest .badge-verified:before { height: 13px !important; }", 0); // fix cut off badge icon
styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment
if ($TD.hasCustomBrowserCSS){ styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']), .is-gif .js-media-gif-container { cursor: alias; }", 0); // change cursor on unsupported videos
var styleCustom = document.createElement("style"); styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot { color: #bd3d37; }", 0); // change play icon color on unsupported videos
styleCustom.innerHTML = $TD.customBrowserCSS;
document.head.appendChild(styleCustom); TD.services.TwitterActionRetweetedRetweet.prototype.iconClass = "icon-retweet icon-retweet-color txt-base-medium"; // fix retweet icon mismatch
window.TDGF_reinjectCustomCSS = function(styles){
$("#tweetduck-custom-css").remove();
if (styles && styles.length){
$(document.head).append("<style type='text/css' id='tweetduck-custom-css'>"+styles+"</style>");
} }
};
})(); })();
})($, $TD, TD);
//
// Block: Setup unsupported video element hook.
//
(function(){
var cancelModal = false;
TD.components.MediaGallery.prototype._loadTweet = appendToFunction(TD.components.MediaGallery.prototype._loadTweet, function(){
var media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
if (media && media.isVideo && media.service !== "youtube"){
$TD.openBrowser(this.clickedLink);
cancelModal = true;
}
});
TD.components.BaseModal.prototype.setAndShowContainer = prependToFunction(TD.components.BaseModal.prototype.setAndShowContainer, function(){
if (cancelModal){
cancelModal = false;
return true;
}
});
TD.ui.Column.prototype.playGifIfNotManuallyPaused = function(){};
TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
app.delegate(".js-gif-play", "click", function(e){
var parent = $(e.target).closest(".js-tweet").first();
var link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
$TD.openBrowser(link.attr("href"));
e.stopPropagation();
});
})();
//
// Block: Finish initialization and load plugins.
//
onAppReady.push(function(){
$TD.loadFontSizeClass(TD.settings.getFontSize());
$TD.loadNotificationHeadContents(getNotificationHeadContents());
if (window.TD_PLUGINS){
window.TD_PLUGINS.onReady();
}
});
//
// Block: Observe the main app element and call the ready event whenever the contents are loaded.
//
new MutationObserver(function(){
if (window.TD_APP_READY && app.hasClass("is-hidden")){
window.TD_APP_READY = false;
}
else if (!window.TD_APP_READY && !app.hasClass("is-hidden")){
onAppReady.forEach(func => func());
window.TD_APP_READY = true;
}
}).observe(app[0], {
attributes: true,
attributeFilter: [ "class" ]
});
})($, $TD, $TDX, TD);

View File

@@ -0,0 +1,49 @@
(function($, $TD, $TDX, TD){
var isDebugging = false;
$(document).keydown(function(e){
// ==========================
// F4 key - toggle debug mode
// ==========================
if (e.keyCode === 115){
isDebugging = !isDebugging;
$(".app-title").first().css("background-color", isDebugging ? "#5A6B75" : "#292F33");
}
// Debug mode handling
else if (isDebugging){
e.preventDefault();
// ===================================
// N key - simulate popup notification
// ===================================
if (e.keyCode === 78){
var col = TD.controller.columnManager.getAllOrdered()[0];
$.publish("/notifications/new",[{
column: col,
items: [
col.updateArray[Math.floor(Math.random()*col.updateArray.length)]
]
}]);
}
// ===================================
// S key - simulate sound notification
// ===================================
else if (e.keyCode === 83){
if ($TDX.hasCustomNotificationSound){
$TD.onTweetSound();
}
else{
document.getElementById("update-sound").play();
}
}
}
});
})($, $TD, $TDX, TD);

View File

@@ -1,4 +1,4 @@
(function($TD){ (function($TD, $TDX){
// //
// Variable: Collection of all <a> tags. // Variable: Collection of all <a> tags.
// //
@@ -51,7 +51,7 @@
return; return;
} }
if ($TD.expandLinksOnHover){ if ($TDX.expandLinksOnHover){
tooltipTimer = window.setTimeout(function(){ tooltipTimer = window.setTimeout(function(){
var expanded = url; var expanded = url;
expanded = cutStart(expanded, "https://"); expanded = cutStart(expanded, "https://");
@@ -73,7 +73,7 @@
addEventListener(links, "mouseleave", function(e){ addEventListener(links, "mouseleave", function(e){
if (!e.currentTarget.hasAttribute("data-full-url"))return; if (!e.currentTarget.hasAttribute("data-full-url"))return;
if ($TD.expandLinksOnHover){ if ($TDX.expandLinksOnHover){
var prevText = e.currentTarget.getAttribute("td-prev-text"); var prevText = e.currentTarget.getAttribute("td-prev-text");
if (prevText){ if (prevText){
@@ -114,11 +114,36 @@
var account = embedded[0].getElementsByClassName("account-link"); var account = embedded[0].getElementsByClassName("account-link");
if (account.length === 0)return; if (account.length === 0)return;
$TD.setNotificationTweetEmbedded(account[0].getAttribute("href")+"/status/"+tweetId); $TD.setNotificationQuotedTweet(account[0].getAttribute("href")+"/status/"+tweetId);
})(); })();
// //
// Block: Page fully loaded. // Block: Setup a skip button.
// //
$TD.onNotificationReady(); (function(){
})($TD); if (document.body.hasAttribute("td-example-notification")){
return;
}
document.body.insertAdjacentHTML("afterbegin", [
'<svg id="td-skip" xmlns="http://www.w3.org/2000/svg" width="10" height="17" viewBox="0 0 350 600" style="position:fixed;left:30px;bottom:10px;z-index:1000">',
'<path fill="#888" d="M0,151.656l102.208-102.22l247.777,247.775L102.208,544.986L0,442.758l145.546-145.547">',
'</svg>'
].join(""));
document.getElementById("td-skip").addEventListener("click", function(){
$TD.loadNextNotification();
});
})();
//
// Block: Setup a hover class on body.
//
document.body.addEventListener("mouseenter", function(){
document.body.classList.add("td-hover");
});
document.body.addEventListener("mouseleave", function(){
document.body.classList.remove("td-hover");
});
})($TD, $TDX);

View File

@@ -9,13 +9,13 @@
$TDP.checkFileExists(token, fileNameUser).then(exists => { $TDP.checkFileExists(token, fileNameUser).then(exists => {
var fileName = exists ? fileNameUser : fileNameDefault; var fileName = exists ? fileNameUser : fileNameDefault;
$TDP.readFile(token, fileName, true).then(contents => { (exists ? $TDP.readFile(token, fileName, true) : $TDP.readFileRoot(token, fileName)).then(contents => {
var obj; var obj;
try{ try{
obj = eval("("+contents+")"); obj = eval("("+contents+")");
}catch(err){ }catch(err){
if (!(onFailure && onFailure(err.message))){ if (!(onFailure && onFailure(err))){
$TD.alert("warning", "Problem loading '"+fileName+"' file for '"+identifier+"' plugin, the JavaScript syntax is invalid: "+err.message); $TD.alert("warning", "Problem loading '"+fileName+"' file for '"+identifier+"' plugin, the JavaScript syntax is invalid: "+err.message);
} }
@@ -34,4 +34,21 @@
} }
}); });
}; };
//
// Block: Setup a function to add/remove custom CSS.
//
window.TDPF_createCustomStyle = function(pluginObject){
var element = document.createElement("style");
element.id = "plugin-"+pluginObject.$id+"-"+Math.random().toString(36).substring(2, 7);
document.head.appendChild(element);
var obj = {
insert: (rule) => element.sheet.insertRule(rule, 0),
remove: () => $(element).remove()
};
obj.element = element;
return obj;
};
})($TDP); })($TDP);

View File

@@ -7,21 +7,25 @@
// //
// Constant: Update exe file name. // Constant: Update exe file name.
// //
const updateFileName = $TDU.brandName+".Update.exe"; const updateFileName = "TweetDuck.Update.exe";
// //
// Constant: Url that returns JSON data about latest version. // Constant: Url that returns JSON data about latest version.
// //
const updateCheckUrl = "https://api.github.com/repos/chylex/"+$TDU.brandName+"/releases/latest"; const updateCheckUrlLatest = "https://api.github.com/repos/chylex/TweetDuck/releases/latest";
//
// Constant: Url that returns JSON data about all versions, including prereleases.
//
const updateCheckUrlAll = "https://api.github.com/repos/chylex/TweetDuck/releases";
// //
// Function: Creates the update notification element. Removes the old one if already exists. // Function: Creates the update notification element. Removes the old one if already exists.
// //
var createUpdateNotificationElement = function(version, download){ var displayNotification = function(version, download){
var outdated = version === "unsupported"; var outdated = version === "unsupported";
var tweetdick = version === "tweetdick";
var ele = $("#tweetdck-update"); var ele = $("#tweetduck-update");
var existed = ele.length > 0; var existed = ele.length > 0;
if (existed > 0){ if (existed > 0){
@@ -29,7 +33,7 @@
} }
var html = outdated ? [ var html = outdated ? [
"<div id='tweetdck-update'>", "<div id='tweetduck-update'>",
"<p class='tdu-title'>Unsupported System</p>", "<p class='tdu-title'>Unsupported System</p>",
"<p class='tdu-info'>You will not receive updates.</p>", "<p class='tdu-info'>You will not receive updates.</p>",
"<div class='tdu-buttons'>", "<div class='tdu-buttons'>",
@@ -37,18 +41,9 @@
"<button class='btn btn-negative tdu-btn-dismiss'><span class='label'>Dismiss</span></button>", "<button class='btn btn-negative tdu-btn-dismiss'><span class='label'>Dismiss</span></button>",
"</div>", "</div>",
"</div>" "</div>"
] : tweetdick ? [
"<div id='tweetdck-update'>",
"<p class='tdu-title'>TweetDick Ending</p>",
"<p class='tdu-info'>Please, move to TweetDuck.</p>",
"<div class='tdu-buttons'>",
"<button class='btn btn-positive tdu-btn-unsupported'><span class='label'>Read More</span></button>",
"<button class='btn btn-negative tdu-btn-dismiss'><span class='label'>Dismiss</span></button>",
"</div>",
"</div>"
] : [ ] : [
"<div id='tweetdck-update'>", "<div id='tweetduck-update'>",
"<p class='tdu-title'>"+$TDU.brandName+" Update</p>", "<p class='tdu-title'>TweetDuck Update</p>",
"<p class='tdu-info'>Version "+version+" is now available.</p>", "<p class='tdu-info'>Version "+version+" is now available.</p>",
"<div class='tdu-buttons'>", "<div class='tdu-buttons'>",
"<button class='btn btn-positive tdu-btn-download'><span class='label'>Download</span></button>", "<button class='btn btn-positive tdu-btn-download'><span class='label'>Download</span></button>",
@@ -59,7 +54,7 @@
$(document.body).append(html.join("")); $(document.body).append(html.join(""));
ele = $("#tweetdck-update"); ele = $("#tweetduck-update");
var buttonDiv = ele.children("div.tdu-buttons").first(); var buttonDiv = ele.children("div.tdu-buttons").first();
@@ -118,10 +113,6 @@
$TDU.openBrowser("https://github.com/chylex/TweetDuck/wiki/Supported-Systems"); $TDU.openBrowser("https://github.com/chylex/TweetDuck/wiki/Supported-Systems");
}); });
buttonDiv.children(".tdu-btn-tweetdick").click(function(){
$TDU.openBrowser("https://github.com/chylex/TweetDick/wiki/Future-of-TweetDick");
});
buttonDiv.children(".tdu-btn-dismiss,.tdu-btn-unsupported").click(function(){ buttonDiv.children(".tdu-btn-dismiss,.tdu-btn-unsupported").click(function(){
$TDU.onUpdateDismissed(version); $TDU.onUpdateDismissed(version);
ele.slideUp(function(){ ele.remove(); }); ele.slideUp(function(){ ele.remove(); });
@@ -137,37 +128,22 @@
// //
// Function: Runs an update check and updates all DOM elements appropriately. // Function: Runs an update check and updates all DOM elements appropriately.
// //
var runUpdateCheck = function(force, eventID){ var runUpdateCheck = function(eventID, versionTag, dismissedVersionTag, allowPre){
if (!$TDU.isSystemSupported){
if ($TDU.dismissedVersionTag !== "unsupported"){
createUpdateNotificationElement("unsupported");
}
return;
}
else if ($TDU.brandName === "TweetDick"){
if ($TDU.dismissedVersionTag !== "tweetdick"){
createUpdateNotificationElement("tweetdick");
}
return;
}
clearTimeout(updateCheckTimeoutID); clearTimeout(updateCheckTimeoutID);
updateCheckTimeoutID = setTimeout(runUpdateCheck, 1000*60*60); // 1 hour updateCheckTimeoutID = setTimeout($TDU.triggerUpdateCheck, 1000*60*60); // 1 hour
if (!$TDU.updateCheckEnabled && !force)return; $.getJSON(allowPre ? updateCheckUrlAll : updateCheckUrlLatest, function(response){
var release = allowPre ? response[0] : response;
$.getJSON(updateCheckUrl, function(response){ var tagName = release.tag_name;
var tagName = response.tag_name; var hasUpdate = tagName !== versionTag && tagName !== dismissedVersionTag && release.assets.length > 0;
var hasUpdate = tagName !== $TDU.versionTag && tagName !== $TDU.dismissedVersionTag && response.assets.length > 0;
if (hasUpdate){ if (hasUpdate){
var obj = response.assets.find(asset => asset.name === updateFileName) || response.assets[0]; var obj = release.assets.find(asset => asset.name === updateFileName) || release.assets[0];
createUpdateNotificationElement(tagName, obj.browser_download_url); displayNotification(tagName, obj.browser_download_url);
} }
if (eventID !== 0){ if (eventID){ // ignore undefined and 0
$TDU.onUpdateCheckFinished(eventID, hasUpdate, tagName); $TDU.onUpdateCheckFinished(eventID, hasUpdate, tagName);
} }
}); });
@@ -176,6 +152,6 @@
// //
// Block: Setup global functions. // Block: Setup global functions.
// //
window.TDUF_displayNotification = displayNotification;
window.TDUF_runUpdateCheck = runUpdateCheck; window.TDUF_runUpdateCheck = runUpdateCheck;
runUpdateCheck();
})($, $TDU); })($, $TDU);

View File

@@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.props')" /> <Import Project="packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.props')" />
<Import Project="packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.props')" /> <Import Project="packages\CefSharp.Common.53.0.1\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.53.0.1\build\CefSharp.Common.props')" />
<Import Project="packages\CefSharp.WinForms.49.0.0-pre02\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.49.0.0-pre02\build\CefSharp.WinForms.props')" />
<Import Project="packages\CefSharp.Common.49.0.0-pre02\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.49.0.0-pre02\build\CefSharp.Common.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -16,7 +14,7 @@
<AssemblyName Condition=" '$(Configuration)' == 'Release' ">TweetDuck</AssemblyName> <AssemblyName Condition=" '$(Configuration)' == 'Release' ">TweetDuck</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<NuGetPackageImportStamp>e83161d1</NuGetPackageImportStamp> <NuGetPackageImportStamp>783c0e30</NuGetPackageImportStamp>
<TargetFrameworkProfile> <TargetFrameworkProfile>
</TargetFrameworkProfile> </TargetFrameworkProfile>
<PublishUrl>publish\</PublishUrl> <PublishUrl>publish\</PublishUrl>
@@ -73,6 +71,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="Configuration\LockManager.cs" /> <Compile Include="Configuration\LockManager.cs" />
<Compile Include="Configuration\UserConfig.cs" /> <Compile Include="Configuration\UserConfig.cs" />
<Compile Include="Core\Bridge\PropertyBridge.cs" />
<Compile Include="Core\Controls\ControlExtensions.cs" /> <Compile Include="Core\Controls\ControlExtensions.cs" />
<Compile Include="Core\Controls\FlatButton.cs"> <Compile Include="Core\Controls\FlatButton.cs">
<SubType>Component</SubType> <SubType>Component</SubType>
@@ -111,9 +110,11 @@
<DependentUpon>FormNotification.cs</DependentUpon> <DependentUpon>FormNotification.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\Handling\ContextMenuNotification.cs" /> <Compile Include="Core\Handling\ContextMenuNotification.cs" />
<Compile Include="Core\Handling\DialogHandlerBrowser.cs" /> <Compile Include="Core\Handling\FileDialogHandler.cs" />
<Compile Include="Core\Handling\JavaScriptDialogHandler.cs" />
<Compile Include="Core\Handling\LifeSpanHandler.cs" /> <Compile Include="Core\Handling\LifeSpanHandler.cs" />
<Compile Include="Core\Handling\TweetNotification.cs" /> <Compile Include="Core\Notification\SoundNotification.cs" />
<Compile Include="Core\Notification\TweetNotification.cs" />
<Compile Include="Core\Other\FormAbout.cs"> <Compile Include="Core\Other\FormAbout.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -144,7 +145,14 @@
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsCefArgs.Designer.cs"> <Compile Include="Core\Other\Settings\Dialogs\DialogSettingsCefArgs.Designer.cs">
<DependentUpon>DialogSettingsCefArgs.cs</DependentUpon> <DependentUpon>DialogSettingsCefArgs.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsExport.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsExport.Designer.cs">
<DependentUpon>DialogSettingsExport.cs</DependentUpon>
</Compile>
<Compile Include="Core\Other\Settings\Export\CombinedFileStream.cs" /> <Compile Include="Core\Other\Settings\Export\CombinedFileStream.cs" />
<Compile Include="Core\Other\Settings\Export\ExportFileFlags.cs" />
<Compile Include="Core\Other\Settings\Export\ExportManager.cs" /> <Compile Include="Core\Other\Settings\Export\ExportManager.cs" />
<Compile Include="Core\Other\Settings\TabSettingsAdvanced.cs"> <Compile Include="Core\Other\Settings\TabSettingsAdvanced.cs">
<SubType>UserControl</SubType> <SubType>UserControl</SubType>
@@ -176,16 +184,18 @@
<Compile Include="Core\Other\Settings\TabSettingsUpdates.Designer.cs"> <Compile Include="Core\Other\Settings\TabSettingsUpdates.Designer.cs">
<DependentUpon>TabSettingsUpdates.cs</DependentUpon> <DependentUpon>TabSettingsUpdates.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\Bridge\CallbackBridge.cs" />
<Compile Include="Core\Utils\CommandLineArgs.cs" />
<Compile Include="Core\Utils\CommandLineArgsParser.cs" /> <Compile Include="Core\Utils\CommandLineArgsParser.cs" />
<Compile Include="Core\Utils\WindowState.cs" /> <Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs">
<Compile Include="Core\Utils\WindowsUtils.cs" />
<Compile Include="Migration\FormBackgroundWork.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
<Compile Include="Migration\FormBackgroundWork.Designer.cs"> <Compile Include="Core\Notification\NotificationFlags.cs" />
<DependentUpon>FormBackgroundWork.cs</DependentUpon> <Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
</Compile> <Compile Include="Core\Utils\TwoKeyDictionary.cs" />
<Compile Include="Core\Handling\TweetDeckBridge.cs" /> <Compile Include="Core\Utils\WindowState.cs" />
<Compile Include="Core\Utils\WindowsUtils.cs" />
<Compile Include="Core\Bridge\TweetDeckBridge.cs" />
<Compile Include="Core\Other\FormSettings.cs"> <Compile Include="Core\Other\FormSettings.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -204,16 +214,18 @@
<Compile Include="Plugins\Controls\PluginListFlowLayout.Designer.cs"> <Compile Include="Plugins\Controls\PluginListFlowLayout.Designer.cs">
<DependentUpon>PluginListFlowLayout.cs</DependentUpon> <DependentUpon>PluginListFlowLayout.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Plugins\Enums\PluginFolder.cs" />
<Compile Include="Plugins\Plugin.cs" /> <Compile Include="Plugins\Plugin.cs" />
<Compile Include="Plugins\Events\PluginChangedStateEventArgs.cs" /> <Compile Include="Plugins\Events\PluginChangedStateEventArgs.cs" />
<Compile Include="Plugins\PluginBridge.cs" /> <Compile Include="Plugins\PluginBridge.cs" />
<Compile Include="Plugins\PluginConfig.cs" /> <Compile Include="Plugins\PluginConfig.cs" />
<Compile Include="Plugins\PluginEnvironment.cs" /> <Compile Include="Plugins\Enums\PluginEnvironment.cs" />
<Compile Include="Plugins\PluginGroup.cs" /> <Compile Include="Plugins\Enums\PluginGroup.cs" />
<Compile Include="Plugins\Events\PluginLoadEventArgs.cs" /> <Compile Include="Plugins\Events\PluginLoadEventArgs.cs" />
<Compile Include="Plugins\PluginManager.cs" /> <Compile Include="Plugins\PluginManager.cs" />
<Compile Include="Plugins\PluginScriptGenerator.cs" /> <Compile Include="Plugins\PluginScriptGenerator.cs" />
<Compile Include="Reporter.cs" /> <Compile Include="Reporter.cs" />
<Compile Include="Updates\Events\UpdateDismissedEventArgs.cs" />
<Compile Include="Updates\FormUpdateDownload.cs"> <Compile Include="Updates\FormUpdateDownload.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -230,19 +242,10 @@
<Compile Include="Core\Utils\BrowserUtils.cs" /> <Compile Include="Core\Utils\BrowserUtils.cs" />
<Compile Include="Core\Utils\HardwareAcceleration.cs" /> <Compile Include="Core\Utils\HardwareAcceleration.cs" />
<Compile Include="Core\Utils\NativeMethods.cs" /> <Compile Include="Core\Utils\NativeMethods.cs" />
<Compile Include="Updates\UpdateAcceptedEventArgs.cs" /> <Compile Include="Updates\Events\UpdateAcceptedEventArgs.cs" />
<Compile Include="Updates\UpdateCheckEventArgs.cs" /> <Compile Include="Updates\Events\UpdateCheckEventArgs.cs" />
<Compile Include="Updates\UpdateHandler.cs" /> <Compile Include="Updates\UpdateHandler.cs" />
<Compile Include="Updates\UpdateInfo.cs" /> <Compile Include="Updates\UpdateInfo.cs" />
<Compile Include="Migration\FormMigrationQuestion.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Migration\FormMigrationQuestion.Designer.cs">
<DependentUpon>FormMigrationQuestion.cs</DependentUpon>
</Compile>
<Compile Include="Migration\MigrationDecision.cs" />
<Compile Include="Migration\MigrationManager.cs" />
<Compile Include="Migration\Helpers\ProgramRegistrySearch.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs"> <Compile Include="Properties\Resources.Designer.cs">
@@ -251,6 +254,7 @@
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
</Compile> </Compile>
<Compile Include="Resources\ScriptLoader.cs" /> <Compile Include="Resources\ScriptLoader.cs" />
<Compile Include="Updates\UpdaterSettings.cs" />
<None Include="Configuration\app.config" /> <None Include="Configuration\app.config" />
<None Include="Configuration\packages.config" /> <None Include="Configuration\packages.config" />
</ItemGroup> </ItemGroup>
@@ -310,19 +314,27 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Resources\Plugins\" /> <Folder Include="Resources\Plugins\" />
<Folder Include="Resources\Scripts\" /> </ItemGroup>
<ItemGroup>
<Content Include="Resources\Scripts\code.js" />
<Content Include="Resources\Scripts\debug.js" />
<Content Include="Resources\Scripts\notification.js" />
<Content Include="Resources\Scripts\plugins.browser.js" />
<Content Include="Resources\Scripts\plugins.js" />
<Content Include="Resources\Scripts\plugins.notification.js" />
<Content Include="Resources\Scripts\update.js" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup> </PropertyGroup>
<Error Condition="!Exists('packages\cef.redist.x86.3.2785.1478\build\cef.redist.x86.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.2785.1478\build\cef.redist.x86.targets'))" /> <Error Condition="!Exists('packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets'))" />
<Error Condition="!Exists('packages\cef.redist.x64.3.2785.1478\build\cef.redist.x64.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.2785.1478\build\cef.redist.x64.targets'))" /> <Error Condition="!Exists('packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets'))" />
<Error Condition="!Exists('packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.props'))" /> <Error Condition="!Exists('packages\CefSharp.Common.53.0.1\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.53.0.1\build\CefSharp.Common.props'))" />
<Error Condition="!Exists('packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.targets'))" /> <Error Condition="!Exists('packages\CefSharp.Common.53.0.1\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.53.0.1\build\CefSharp.Common.targets'))" />
<Error Condition="!Exists('packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.props'))" /> <Error Condition="!Exists('packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.props'))" />
<Error Condition="!Exists('packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.targets'))" /> <Error Condition="!Exists('packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.targets'))" />
</Target> </Target>
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>del "$(TargetPath).config" <PostBuildEvent>del "$(TargetPath).config"
@@ -341,12 +353,20 @@ mkdir "$(TargetDir)plugins\official"
mkdir "$(TargetDir)plugins\user" mkdir "$(TargetDir)plugins\user"
xcopy "$(ProjectDir)Resources\Plugins\*" "$(TargetDir)plugins\official\" /E /Y xcopy "$(ProjectDir)Resources\Plugins\*" "$(TargetDir)plugins\official\" /E /Y
rmdir "$(ProjectDir)\bin\Debug" rmdir "$(ProjectDir)\bin\Debug"
rmdir "$(ProjectDir)\bin\Release"</PostBuildEvent> rmdir "$(ProjectDir)\bin\Release"
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
if $(ConfigurationName) == Debug (
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
mkdir "$(TargetDir)plugins\user\.debug"
xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y
)</PostBuildEvent>
</PropertyGroup> </PropertyGroup>
<Import Project="packages\cef.redist.x86.3.2785.1478\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2785.1478\build\cef.redist.x86.targets')" /> <Import Project="packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets')" />
<Import Project="packages\cef.redist.x64.3.2785.1478\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2785.1478\build\cef.redist.x64.targets')" /> <Import Project="packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets')" />
<Import Project="packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.53.0.0-pre01\build\CefSharp.Common.targets')" /> <Import Project="packages\CefSharp.Common.53.0.1\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.53.0.1\build\CefSharp.Common.targets')" />
<Import Project="packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.53.0.0-pre01\build\CefSharp.WinForms.targets')" /> <Import Project="packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">

View File

@@ -1,21 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013 # Visual Studio 2013
VisualStudioVersion = 12.0.40629.0 VisualStudioVersion = 12.0.40629.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDck", "TweetDck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDck", "TweetDck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "tests\UnitTests.csproj", "{A958FA7A-4A2C-42A7-BFA0-159343483F4E}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x86 = Debug|x86 Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x86 = Release|x86 Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|Any CPU.ActiveCfg = Debug|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.ActiveCfg = Debug|x86 {2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.ActiveCfg = Debug|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.Build.0 = Debug|x86 {2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.Build.0 = Debug|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.Deploy.0 = Debug|x86 {2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.Deploy.0 = Debug|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|Any CPU.ActiveCfg = Release|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|x86.ActiveCfg = Release|x86 {2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|x86.ActiveCfg = Release|x86
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|x86.Build.0 = Release|x86 {2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|x86.Build.0 = Release|x86
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Debug|Any CPU.ActiveCfg = Debug|x86
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Debug|x86.ActiveCfg = Debug|x86
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Debug|x86.Build.0 = Debug|x86
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Release|Any CPU.ActiveCfg = Release|x86
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Release|x86.ActiveCfg = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

2
TweetDck.sln.DotSettings Normal file
View File

@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters&gt;&lt;Filter ModuleMask="UnitTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;/ExcludeFilters&gt;&lt;/data&gt;</s:String></wpf:ResourceDictionary>

View File

@@ -1,6 +1,6 @@
using System; using System;
namespace TweetDck.Updates{ namespace TweetDck.Updates.Events{
class UpdateAcceptedEventArgs : EventArgs{ class UpdateAcceptedEventArgs : EventArgs{
public readonly UpdateInfo UpdateInfo; public readonly UpdateInfo UpdateInfo;

View File

@@ -1,6 +1,6 @@
using System; using System;
namespace TweetDck.Updates{ namespace TweetDck.Updates.Events{
class UpdateCheckEventArgs : EventArgs{ class UpdateCheckEventArgs : EventArgs{
public int EventId { get; private set; } public int EventId { get; private set; }
public bool UpdateAvailable { get; private set; } public bool UpdateAvailable { get; private set; }

View File

@@ -0,0 +1,11 @@
using System;
namespace TweetDck.Updates.Events{
class UpdateDismissedEventArgs : EventArgs{
public readonly string VersionTag;
public UpdateDismissedEventArgs(string versionTag){
this.VersionTag = versionTag;
}
}
}

View File

@@ -9,7 +9,7 @@ using TweetDck.Core.Utils;
namespace TweetDck.Updates{ namespace TweetDck.Updates{
sealed partial class FormUpdateDownload : Form{ sealed partial class FormUpdateDownload : Form{
private const double BytesToMB = 1024.0*1024.0; private const double BytesToKB = 1024.0;
public string InstallerPath{ public string InstallerPath{
get{ get{
@@ -69,7 +69,7 @@ namespace TweetDck.Updates{
progressDownload.SetValueInstant(1000); progressDownload.SetValueInstant(1000);
} }
labelStatus.Text = (e.BytesReceived/BytesToMB).ToString("0.0", CultureInfo.CurrentCulture)+" MB"; labelStatus.Text = (e.BytesReceived/BytesToKB).ToString("0.0", CultureInfo.CurrentCulture)+" kB";
} }
else{ else{
if (progressDownload.Style != ProgressBarStyle.Continuous){ if (progressDownload.Style != ProgressBarStyle.Continuous){
@@ -77,7 +77,7 @@ namespace TweetDck.Updates{
} }
progressDownload.SetValueInstant(e.ProgressPercentage*10); progressDownload.SetValueInstant(e.ProgressPercentage*10);
labelStatus.Text = (e.BytesReceived/BytesToMB).ToString("0.0", CultureInfo.CurrentCulture)+" / "+(e.TotalBytesToReceive/BytesToMB).ToString("0.0", CultureInfo.CurrentCulture)+" MB"; labelStatus.Text = (e.BytesReceived/BytesToKB).ToString("0.0", CultureInfo.CurrentCulture)+" / "+(e.TotalBytesToReceive/BytesToKB).ToString("0.0", CultureInfo.CurrentCulture)+" kB";
} }
}); });
} }

View File

@@ -5,41 +5,82 @@ using TweetDck.Core;
using TweetDck.Core.Controls; using TweetDck.Core.Controls;
using TweetDck.Core.Utils; using TweetDck.Core.Utils;
using TweetDck.Resources; using TweetDck.Resources;
using TweetDck.Updates.Events;
namespace TweetDck.Updates{ namespace TweetDck.Updates{
class UpdateHandler{ class UpdateHandler{
private static bool IsSystemSupported{
get{
return true; // Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
}
}
public UpdaterSettings Settings{
get{
return settings;
}
}
private readonly ChromiumWebBrowser browser; private readonly ChromiumWebBrowser browser;
private readonly FormBrowser form; private readonly FormBrowser form;
private readonly UpdaterSettings settings;
public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted; public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted;
public event EventHandler<UpdateDismissedEventArgs> UpdateDismissed;
public event EventHandler<UpdateCheckEventArgs> CheckFinished; public event EventHandler<UpdateCheckEventArgs> CheckFinished;
private int lastEventId; private int lastEventId;
public UpdateHandler(ChromiumWebBrowser browser, FormBrowser form){ public UpdateHandler(ChromiumWebBrowser browser, FormBrowser form, UpdaterSettings settings){
this.browser = browser; this.browser = browser;
this.form = form; this.form = form;
this.settings = settings;
browser.FrameLoadEnd += browser_FrameLoadEnd; browser.FrameLoadEnd += browser_FrameLoadEnd;
browser.RegisterJsObject("$TDU", new Bridge(this)); browser.RegisterAsyncJsObject("$TDU", new Bridge(this));
} }
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain){ if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
ScriptLoader.ExecuteFile(e.Frame, "update.js"); ScriptLoader.ExecuteFile(e.Frame, "update.js");
Check(false);
} }
} }
public int Check(bool force){ public int Check(bool force){
browser.ExecuteScriptAsync("TDUF_runUpdateCheck", force, ++lastEventId); if (IsSystemSupported){
if (Program.UserConfig.EnableUpdateCheck || force){
string dismissedUpdate = force || settings.DismissedUpdate == null ? string.Empty : settings.DismissedUpdate;
browser.ExecuteScriptAsync("TDUF_runUpdateCheck", ++lastEventId, Program.VersionTag, dismissedUpdate, settings.AllowPreReleases);
return lastEventId; return lastEventId;
} }
return 0;
}
else if (settings.DismissedUpdate != "unsupported"){
browser.ExecuteScriptAsync("TDUF_displayNotification", "unsupported");
}
return -1;
}
private void TriggerUpdateAcceptedEvent(UpdateAcceptedEventArgs args){ private void TriggerUpdateAcceptedEvent(UpdateAcceptedEventArgs args){
if (UpdateAccepted != null){ if (UpdateAccepted != null){
form.InvokeSafe(() => UpdateAccepted(this, args)); form.InvokeSafe(() => UpdateAccepted(this, args));
} }
} }
private void TriggerUpdateDismissedEvent(UpdateDismissedEventArgs args){
form.InvokeSafe(() => {
settings.DismissedUpdate = args.VersionTag;
if (UpdateDismissed != null){
UpdateDismissed(this, args);
}
});
}
private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){ private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){
if (CheckFinished != null){ if (CheckFinished != null){
form.InvokeSafe(() => CheckFinished(this, args)); form.InvokeSafe(() => CheckFinished(this, args));
@@ -47,42 +88,16 @@ namespace TweetDck.Updates{
} }
public class Bridge{ public class Bridge{
public string BrandName{
get{
return Program.BrandName;
}
}
public string VersionTag{
get{
return Program.VersionTag;
}
}
public bool UpdateCheckEnabled{
get{
return Program.UserConfig.EnableUpdateCheck;
}
}
public string DismissedVersionTag{
get{
return Program.UserConfig.DismissedUpdate ?? string.Empty;
}
}
public bool IsSystemSupported{
get{
return Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
}
}
private readonly UpdateHandler owner; private readonly UpdateHandler owner;
public Bridge(UpdateHandler owner){ public Bridge(UpdateHandler owner){
this.owner = owner; this.owner = owner;
} }
public void TriggerUpdateCheck(){
owner.Check(false);
}
public void OnUpdateCheckFinished(int eventId, bool isUpdateAvailable, string latestVersion){ public void OnUpdateCheckFinished(int eventId, bool isUpdateAvailable, string latestVersion){
owner.TriggerCheckFinishedEvent(new UpdateCheckEventArgs(eventId, isUpdateAvailable, latestVersion)); owner.TriggerCheckFinishedEvent(new UpdateCheckEventArgs(eventId, isUpdateAvailable, latestVersion));
} }
@@ -92,10 +107,7 @@ namespace TweetDck.Updates{
} }
public void OnUpdateDismissed(string versionTag){ public void OnUpdateDismissed(string versionTag){
owner.form.InvokeSafe(() => { owner.TriggerUpdateDismissedEvent(new UpdateDismissedEventArgs(versionTag));
Program.UserConfig.DismissedUpdate = versionTag;
Program.UserConfig.Save();
});
} }
public void OpenBrowser(string url){ public void OpenBrowser(string url){

View File

@@ -0,0 +1,6 @@
namespace TweetDck.Updates{
class UpdaterSettings{
public bool AllowPreReleases { get; set; }
public string DismissedUpdate { get; set; }
}
}

View File

@@ -1,5 +1,8 @@
del "bin\x86\Release\*.xml" del "bin\x86\Release\*.xml"
del "bin\x86\Release\devtools_resources.pak" del "bin\x86\Release\devtools_resources.pak"
del "bin\x86\Release\d3dcompiler_43.dll"
del "bin\x86\Release\widevinecdmadapter.dll"
del "bin\x86\Release\Scripts\debug.js"
del "bin\x86\Release\TweetDuck.Browser.exe" del "bin\x86\Release\TweetDuck.Browser.exe"
ren "bin\x86\Release\CefSharp.BrowserSubprocess.exe" "TweetDuck.Browser.exe" ren "bin\x86\Release\CefSharp.BrowserSubprocess.exe" "TweetDuck.Browser.exe"

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