mirror of
				https://github.com/chylex/TweetDuck.git
				synced 2025-10-31 18:17:15 +01:00 
			
		
		
		
	Compare commits
	
		
			849 Commits
		
	
	
		
			1.10
			...
			da54af221c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| da54af221c | |||
| 6c8d518e0d | |||
| 697f4f1569 | |||
| 15d4ec3228 | |||
| c303346bc3 | |||
| b9af966849 | |||
| 0a7459b72e | |||
| 9953f06ab1 | |||
| 0c8159aa79 | |||
| c785a7ed8c | |||
| b1328e5b1f | |||
| cb94f0c81e | |||
| 8de2989f12 | |||
| 1cf7d13873 | |||
| 35c2ee3673 | |||
| a1b4c31450 | |||
| ea95e5cbac | |||
| 2927097e8e | |||
| b5bffdb95b | |||
| bee894bfbb | |||
| 96d2e7cc7c | |||
| b58c8f65fe | |||
| 2c69289785 | |||
| dc0fc06673 | |||
| 3114b489b6 | |||
| 8e5934bd84 | |||
| a2129b957e | |||
| 61cd632df6 | |||
| 712bcd5a6f | |||
| dd47201d7b | |||
| 2af864f337 | |||
| acafbc3706 | |||
| b815ae4b11 | |||
| 45a3a7499f | |||
| 09fac63ffc | |||
| dd6776fef4 | |||
| cd02a03e8a | |||
| 933e0e54df | |||
| c4aa62fc3a | |||
| ad30021d6d | |||
| 7c8b43adfe | |||
| 3aace0b399 | |||
| 0a9c84feec | |||
| d5ae698855 | |||
| 26e6a09d5a | |||
| 57fcff3824 | |||
| 2a7aec199f | |||
| 1b01c38fda | |||
| c9fd4634ab | |||
| 51d2ec92ca | |||
| 12ec8baf5c | |||
| 6040337bb4 | |||
| 1ced72388b | |||
| 4751a948e7 | |||
| 3939c2263a | |||
| b0ba4595ae | |||
| 38b1057a4c | |||
| af5d785ff2 | |||
| 655d334714 | |||
| eee72959e6 | |||
| 89b8977f7d | |||
| 9ede2e1ccc | |||
| 03d1bc0f4c | |||
| cde9f66111 | |||
| 8149ed50e1 | |||
| 24f5075116 | |||
| 2a636245b4 | |||
| 3f4844f6f6 | |||
| 29308de3ee | |||
| 0d3d744d94 | |||
| d38e525fed | |||
| e2ac38ed0b | |||
| fa534f9eb3 | |||
| ec7827df24 | |||
| b915488651 | |||
| bf9a0226be | |||
| 68582f6973 | |||
| 03f3d4d450 | |||
| 115428ec50 | |||
| 5f60852fbb | |||
| a7a5723c4b | |||
| 17e42df42d | |||
| 7e692460d8 | |||
| f41a5946e4 | |||
| 29fee155d7 | |||
| 32728fc20a | |||
| 394cb80022 | |||
| 21e49505d1 | |||
| e0025e02d1 | |||
| 4a9590c1d9 | |||
| 7de0c50387 | |||
| ccb87351c1 | |||
| 273e7266eb | |||
| 427975e5ce | |||
| adb304b6a2 | |||
| 3a89a28f8b | |||
| cff93dcc97 | |||
| dfde38ea3b | |||
| a8e7f065cf | |||
| 5ebfc67e48 | |||
| 57b03baad9 | |||
| c91f1d0e5e | |||
| 6a421292b3 | |||
| ceae748503 | |||
| 008de87e55 | |||
| e7479ef9e3 | |||
| 13f8f12ac8 | |||
| fd634379d7 | |||
| e854315a81 | |||
| 86136d7692 | |||
| db251bfdfd | |||
| 9d39f26d54 | |||
| 9cd813c02c | |||
| 8e1f87e062 | |||
| dda954285c | |||
| ed4f7b6b72 | |||
| 7239dcf4d2 | |||
| 901cca268e | |||
| b909341988 | |||
| 3e607ae0fe | |||
| 587060f73c | |||
| 3ea6f6ac18 | |||
| c162761464 | |||
| 994f9635ef | |||
| ab8752845d | |||
| a94ee2fe4b | |||
| 763c999b09 | |||
| b18cd2658c | |||
| d9782f554f | |||
| f1d9663709 | |||
| bcf77052a5 | |||
| 85d15b32e9 | |||
| 3d3b695c85 | |||
| 88b7cac298 | |||
| c7efa631e1 | |||
| aeb90cbb1a | |||
| ae1c59847f | |||
| 651d9be57c | |||
| eeb32db6fb | |||
| daa0780644 | |||
| 8502f9105f | |||
| 16ced3d827 | |||
| 1c16187346 | |||
| 2fe058d9cb | |||
| cefdadd53a | |||
| c21c10df63 | |||
| b4d359d30c | |||
| 1e8c62ac25 | |||
| c578f36644 | |||
| c973d0cff4 | |||
| 0c3d9ae46a | |||
| a834e8b6a2 | |||
| 9f5580d983 | |||
| e94e3cecf8 | |||
| 1991f7f50f | |||
| 9eb4e623e7 | |||
| ad28d2279f | |||
| 1e3de31fc3 | |||
| f85bd41b96 | |||
| 563124b68c | |||
| 63de08c635 | |||
| 8a0a215443 | |||
| f1b7cd633e | |||
| 458eeeccda | |||
| 464e758b94 | |||
| 4c61047e9b | |||
| 1bf9e7fb56 | |||
| ca69554f37 | |||
| cf7029037e | |||
| 418388b0ab | |||
| a0f8689d4f | |||
| 95007bdd26 | |||
| ab91540deb | |||
| b2ebb984f8 | |||
| f7e9ad74d1 | |||
| d48da3d51c | |||
| 76d22554c5 | |||
| 6eaafd883b | |||
| 5961a80b23 | |||
| f41c6fe533 | |||
| 65b8efe13c | |||
| 89529f9c96 | |||
| e90f6ebc63 | |||
| 5888d540a6 | |||
| ae8b740600 | |||
| ab4e2f5bda | |||
| 1091b6d232 | |||
| fc89744238 | |||
| 34e049a002 | |||
| 980bf2c307 | |||
| 762aea1e20 | |||
| c1aefc7163 | |||
| 9480ba26e0 | |||
| c2c9160ed9 | |||
| 175b067a17 | |||
| 9d8656ca20 | |||
| 0863001c80 | |||
| 0ee22a30ad | |||
| 447697ba45 | |||
| aea77ff909 | |||
| af5da76f72 | |||
| a369c65451 | |||
| 318f65f187 | |||
| 1cd60e831c | |||
| b988959eaa | |||
| 1eb1f9848a | |||
| 7f6cc0da01 | |||
| 19fcb69525 | |||
| 22cef0a44c | |||
| 2459d31bff | |||
| 19f104239a | |||
| bd0be65038 | |||
| bbb7907e54 | |||
| a6963a18d4 | |||
| 92716ea3e0 | |||
| aec4c1feea | |||
| d505b3305b | |||
| a34a02e14d | |||
| 26d2d7a51e | |||
| c2f7e52d13 | |||
| de68d8934d | |||
| 4fdf7fc958 | |||
| 42a5e72f19 | |||
| f7359ebc8a | |||
| f395ac53dc | |||
| 1113e0b559 | |||
| 5e3bd31862 | |||
| 11d978dad1 | |||
| f7961024d7 | |||
| 72973a8707 | |||
| 68254f48d5 | |||
| eac4f30c50 | |||
| 25680fa980 | |||
| ff5e1da14d | |||
| 95afff7879 | |||
| 50bd526025 | |||
| 108a0fefc3 | |||
| dd8c5d27be | |||
| b2937bc776 | |||
| 4d8e764211 | |||
| 544b8664fd | |||
| d0610865bd | |||
| ebc0b51590 | |||
| 4487f1169e | |||
| 85559b6083 | |||
| 1056273c57 | |||
| 61af2ebc8b | |||
| 9121c86656 | |||
| 1ccefe853a | |||
| aca438b837 | |||
| 7210c29cd8 | |||
| 26d90c0c9b | |||
| a03b222a95 | |||
| 7944a24d3c | |||
| cc8459c759 | |||
| 10074ff92c | |||
| 173f25bebc | |||
| 31680fc4ae | |||
| e937d43614 | |||
| 20e29a7975 | |||
| ef815dabce | |||
| 1fb133e6b8 | |||
| 50b58cd6a6 | |||
| 01485d7ef9 | |||
| b17c6a5ac7 | |||
| d2ed2b4a00 | |||
| 710a7524a1 | |||
| 2be46464d6 | |||
| 8d536a6734 | |||
| 250d502238 | |||
| e8de7266d0 | |||
| 9414f372d7 | |||
| b0f9de67cf | |||
| 9b082e114e | |||
| 816a5334ac | |||
| 15a4e10da9 | |||
| 01b9302b0c | |||
| 442126a11a | |||
| a9c140c0fc | |||
| 97ad7a3e68 | |||
| 7d737eefb6 | |||
| 4ac05b38d3 | |||
| 651bbbb672 | |||
| 52da4d8687 | |||
| 36063ae76a | |||
| 2fcec2d2cd | |||
| 762a7fdfb7 | |||
| cd7aeaeed2 | |||
| 6f414d312c | |||
| 1b5304efb7 | |||
| d59375308f | |||
| 8c9509a906 | |||
| fb86d8f3a8 | |||
| 50e909cb3d | |||
| 2f54edf7e7 | |||
| c251603e1e | |||
| 4476edb6c3 | |||
| 28fc67660f | |||
| 6e8b5a5ce5 | |||
| e53681416f | |||
| acb5e184e8 | |||
| bdbafb3e5c | |||
| ac70cf87c6 | |||
| 93de835ab4 | |||
| 2ea38b88ce | |||
| 2c4f2be57d | |||
| fa4beea425 | |||
| 7a976edc82 | |||
| bb22c35221 | |||
| ff3dc59016 | |||
| 2e4dd3df3e | |||
| b82e5d33f9 | |||
| 65d56b336b | |||
| 7836d61173 | |||
| 898437720b | |||
| d9a80d1085 | |||
| ab3d8b0ae2 | |||
| b865074c32 | |||
| 2b8ca77c0d | |||
| fa8b4e1e7f | |||
| a310c5bcc1 | |||
| b5dccd6b91 | |||
| c2bcb39b38 | |||
| 4515add0a0 | |||
| 5b6aaec48c | |||
| 810e56ca31 | |||
| 5bcc8ac2e0 | |||
| f5bfb35867 | |||
| 9088b8cd07 | |||
| a7d90dc708 | |||
| 5968b57a01 | |||
| 94946a9ed6 | |||
| 561aec5ef0 | |||
| e31696d843 | |||
| 8b33fd2002 | |||
| 064e43750f | |||
| 29d2f4f681 | |||
| c11b40b94a | |||
| e874e1d798 | |||
| 01244ec632 | |||
| 671657e2b0 | |||
| dff7278e2e | |||
| 0881328636 | |||
| a951c3a7c9 | |||
| b25fae54fe | |||
| a87bc4609e | |||
| 3e68026949 | |||
| 78d6d285cd | |||
| 72e3596a3c | |||
| 643ebcaab4 | |||
| 52ef6cd95a | |||
| 4aec2f3260 | |||
| 73549515eb | |||
| d83d2660cf | |||
| 8de913172c | |||
| 4acfd64cff | |||
| b56f90ed52 | |||
| 1375630727 | |||
| 668825bc01 | |||
| 90414ae579 | |||
| 41c8caa2a4 | |||
| 15305ecabe | |||
| 50090effd4 | |||
| 6dffdcd1ed | |||
| db9daf2714 | |||
| 502ac4ebc1 | |||
| 3f44f3bab4 | |||
| 746eba185b | |||
| 82dec92510 | |||
| 2edc93df47 | |||
| c5a6738199 | |||
| dfd578165b | |||
| 285d400f69 | |||
| 47a3444ace | |||
| 0ec227da52 | |||
| f9cf582306 | |||
| b0883229bf | |||
| 31b5d9a4c0 | |||
| 6841a78556 | |||
| 9d2089a8ce | |||
| 6d6bb79199 | |||
| 937c8e22c4 | |||
| 34c8d44dfe | |||
| 2dbf778e56 | |||
| 873fe2b393 | |||
| 0c53bc6f32 | |||
| fa407e150b | |||
| 4e52102c5c | |||
| 0355a5c646 | |||
| 377d9c3554 | |||
| 20b1b3c895 | |||
| 41bbe7c51b | |||
| 27fa6aefd3 | |||
| a63c3232da | |||
| 146908a115 | |||
| 48b0f35fee | |||
| 6c435ebe26 | |||
| b8d0b721a2 | |||
| a6d5957f46 | |||
| 9e5f676e23 | |||
| 414f3a1f9d | |||
| fedf9c60ee | |||
| b6385d9622 | |||
| 76d25a712d | |||
| 2c6d935273 | |||
| 830d98a101 | |||
| d7378bd75a | |||
| 0f41cb9dbc | |||
| 77bc922d93 | |||
| c91b635132 | |||
| e5521de34a | |||
| 7c0f8d0f24 | |||
| f60d5f650f | |||
| ca67f2fe0a | |||
| 16cce8be1d | |||
| 770619d948 | |||
| cfedb7d6b1 | |||
| 738557b3a2 | |||
| 38b01deec1 | |||
| 1a31e69ec9 | |||
| e065983c95 | |||
| 30a169171a | |||
| 8d1900362e | |||
| e154189de1 | |||
| b0f147de24 | |||
| 979b3548db | |||
| 05d6c578b3 | |||
| a117559063 | |||
| f87c649b09 | |||
| 6504dc9184 | |||
| 25a8ddffd4 | |||
| fa0f9b89cf | |||
| 4d00a67891 | |||
| bd2c43e1f4 | |||
| c7279eaa34 | |||
| fd523e552c | |||
| cb877b8b23 | |||
| ed1bee8b89 | |||
| a8e1492056 | |||
| 5587216c01 | |||
| 86569261ad | |||
| 4a9049c7aa | |||
| 75d60a8182 | |||
| 14d4dc2ed9 | |||
| fd0e1740a5 | |||
| 70ca890bef | |||
| b9318dfd8e | |||
| 995642a719 | |||
| d14de4ac9e | |||
| b7f325a241 | |||
| 27c55718c2 | |||
| 421ff0654b | |||
| ed947458f9 | |||
| 9cdb20ba84 | |||
| d8774b735f | |||
| adcb42695f | |||
| dd77b5bcbb | |||
| d2445be155 | |||
| 10254c8af7 | |||
| d7e830badf | |||
| b445a3a9b8 | |||
| 97f42ead66 | |||
| 03730fafb9 | |||
| 0be9465dca | |||
| d7f1df4995 | |||
| 3cb0f90706 | |||
| a3e9b15a8a | |||
| 00bfa68a57 | |||
| c311e24f08 | |||
| 1cdd4e4455 | |||
| 8078c0081a | |||
| a867e1fc40 | |||
| 61da36ac1c | |||
| 720ca2a018 | |||
| b39c593552 | |||
| c808952a45 | |||
| b468d7a766 | |||
| 28578f60be | |||
| 92a39e2527 | |||
| 1bce5e4342 | |||
| 68f586e104 | |||
| d27a66202e | |||
| 07de2f450c | |||
| 3c03726634 | |||
| 6fb2643063 | |||
| 5eef6c8196 | |||
| 829c332e13 | |||
| 47eec14bca | |||
| e7ee1d6be7 | |||
| e41b5e5ff7 | |||
| ba1bacd08c | |||
| 1029ea5840 | |||
| 339eaf0195 | |||
| aa1e1549d8 | |||
| 1f8ae9ef80 | |||
| 65165de060 | |||
| 485836d2ce | |||
| 64c07c14d9 | |||
| b6313c2b72 | |||
| 58124b5821 | |||
| b6a599f8a6 | |||
| 19a6bc0dbd | |||
| 8cb81d44ee | |||
| 22d0a372d8 | |||
| 988fae75c3 | |||
| a82b0e3622 | |||
| bc6cacacf9 | |||
| 03ad1b3cbc | |||
| eac300627f | |||
| 12525ac386 | |||
| 7558551859 | |||
| a9cce13eef | |||
| 5bb2c43dd0 | |||
| 5b1dcc88cc | |||
| 5c8fc1d136 | |||
| 82c2ab3448 | |||
| b05c8d180f | |||
| 87109e5d01 | |||
| be1a809098 | |||
| ba0e3f1bd4 | |||
| 27d41e6164 | |||
| 1ce5ddfd98 | |||
| 0096a1a4ef | |||
| d2a6560a90 | |||
| 4d7c048139 | |||
| 1d78bd2655 | |||
| 9250f1907c | |||
| a63e210b88 | |||
| 06bd65b7f8 | |||
| b6c17eb05e | |||
| a3d40fdc2b | |||
| c064ef7a30 | |||
| 762717da1e | |||
| b7d3758bea | |||
| d20541fd24 | |||
| 2c2f860f26 | |||
| d1db3aa673 | |||
| cedc52cdf5 | |||
| 33f8eafbcf | |||
| ad45cf8c72 | |||
| f99d035621 | |||
| f3072caea8 | |||
| 1410974292 | |||
| 44413fa96c | |||
| 342a4b4067 | |||
| 4356dde92d | |||
| 21e64a18d8 | |||
| 5a305a6740 | |||
| 44595bad40 | |||
| 7fc9edc9cb | |||
| 93e191f522 | |||
| 8d8355e792 | |||
| a5379d290c | |||
| caea8d4315 | |||
| 24224ab4c6 | |||
| 4dbc02360c | |||
| aa7a29af0c | |||
| 296d0c6199 | |||
| 812a034e8d | |||
| e9de789b79 | |||
| cfbc1b9575 | |||
| e39e85e4dd | |||
| 3f0b161cd0 | |||
| ebe3868720 | |||
| ffd0f5e986 | |||
| 217535a3ba | |||
| 7abfbea2da | |||
| 86ffeaac9a | |||
| ab915b7115 | |||
| 705b5d38cf | |||
| fc2acb00b3 | |||
| 5add8a1d0e | |||
| 063d3a2637 | |||
| f1f90a2ee3 | |||
| ed317a4e46 | |||
| cca16f3bb1 | |||
| aba156cb3b | |||
| cd4e4d7095 | |||
| 8fbb639430 | |||
| d5bf8ec558 | |||
| b6cff40f1e | |||
| 833e42f455 | |||
| 8134843dad | |||
| 1f92d5e633 | |||
| dc51c0ae85 | |||
| 45c79643d6 | |||
| 9041bfc627 | |||
| 0b3b3dd0be | |||
| 89e92dab59 | |||
| 8c168c9ad7 | |||
| 9f63357a92 | |||
| d91b4bd1f3 | |||
| c0c64f6d62 | |||
| 1a5d2af779 | |||
| f40a33192b | |||
| ca4900aff0 | |||
| 56fc9e2d40 | |||
| d2174c0b69 | |||
| 9f76754ad3 | |||
| 118ceaec35 | |||
| 5a57d28a7d | |||
| 07af99f862 | |||
| 59fba7fba0 | |||
| dd4edc4249 | |||
| 856226473a | |||
| 8d1c07d6b2 | |||
| c32462cc9e | |||
| ec94ea3273 | |||
| 41acd8c15b | |||
| 155a79f2ec | |||
| 9197cb9be6 | |||
| 03d50c847b | |||
| bf45c40365 | |||
| 679e126194 | |||
| 50e39164bd | |||
| cb9f75e968 | |||
| aa7f6cc3b1 | |||
| fe601aed41 | |||
| 2282a9df28 | |||
| 2b54627750 | |||
| 16051a0d25 | |||
| 66d5f0d790 | |||
| 07d29207f0 | |||
| a60be2afcc | |||
| 027f3ee253 | |||
| 04774815e4 | |||
| 61a73c055b | |||
| 7731534ffc | |||
| ed7bf99610 | |||
| cbe4272556 | |||
| 8f5e3dfdcc | |||
| 35500c51f1 | |||
| 629f873bb2 | |||
| a44cb884c4 | |||
| d5ad1d0daa | |||
| 61ae7e3b6a | |||
| 01583e424f | |||
| 5c0aa1b3da | |||
| 07391efa70 | |||
| b80f1bfc7c | |||
| ad310db86c | |||
| 4ce0122a29 | |||
| a8894f7054 | |||
| 1d1515351b | |||
| 2a9ddd4468 | |||
| 0f9a944775 | |||
| 34ee9ebd66 | |||
| 43f632b555 | |||
| 7cf3f1d32c | |||
| e51e87647e | |||
| b8aae88b11 | |||
| d06e29db15 | |||
| 62449450f3 | |||
| b290c94635 | |||
| f909b887d9 | |||
| 5cf4843212 | |||
| b3d1e1bfac | |||
| df47499a28 | |||
| 421475ec87 | |||
| 29d999b8eb | |||
| acacd9a5e5 | |||
| b81c26f93f | |||
| 00b212944c | |||
| 70ba006e4d | |||
| 118e0cae62 | |||
| c003bb4e71 | |||
| e9b2fa7603 | |||
| 35afaa105d | |||
| 2e300a7b8f | |||
| f3f5b88550 | |||
| 22f491d98a | |||
| 7908c8ebd9 | |||
| e114a93714 | |||
| 931761600f | |||
| e5b4b03e1a | |||
| f1e8b3fbf0 | |||
| 4d64243a07 | |||
| 3422b4d4d6 | |||
| b170d529fd | |||
| 83741db5aa | |||
| c4b2b3ab25 | |||
| 676df44985 | |||
| 037adc6b5c | |||
| 186d17dd98 | |||
| ab9ff980ef | |||
| f297cb2623 | |||
| b53c672768 | |||
| 1a2b967749 | |||
| 6ba30c48cf | |||
| 1af9ee9ced | |||
| e50480aa35 | |||
| 6943c7813f | |||
| 7c9b4382ca | |||
| 3187f97592 | |||
| b71a367052 | |||
| 2d4bbf2a6f | |||
| 6e59dfddcc | |||
| bd92fc6ee0 | |||
| 2f61de7025 | |||
| 8fcec7ec7c | |||
| 33d9ba3871 | |||
| 4f8c778ba0 | |||
| 804c739038 | |||
| a0445fbb12 | |||
| 7ab5d7b796 | |||
| 7f83a7773b | |||
| fc9e8a808f | |||
| 5ab8976bc7 | |||
| e2a28f2811 | |||
| 137a20ed0e | |||
| f956f696f4 | |||
| bb7cbde38f | |||
| 8c452d3fa2 | |||
| f65c33c432 | |||
| da2758ccb1 | |||
| de10112b7f | |||
| 301d4fb171 | |||
| f0a79add14 | |||
| d33bc9fe25 | |||
| a2a5dfd435 | |||
| f3d7c8d4c3 | |||
| 67f60dd787 | |||
| 62310ce4a4 | |||
| 3a27089364 | |||
| a05460f562 | |||
| 390872c305 | |||
| 594d12df79 | |||
| c42c12c72b | |||
| c37f4fe365 | |||
| 8d37d68770 | |||
| 1b3d1fb36a | |||
| 2f352ef9bb | |||
| 527f3cab4c | |||
| f67bf27db2 | |||
| a8bb3ba349 | |||
| 321ab12213 | |||
| 4cab18e557 | |||
| c15ea97a36 | |||
| a0cc4109df | |||
| f66ff1000a | |||
| 51a9bb6d3c | |||
| 07017bd29b | |||
| 45b6f49a08 | |||
| 103ad72788 | |||
| 543259f29f | |||
| 98799734c5 | |||
| 96f491a666 | |||
| 29e541dbef | |||
| 1343b9c113 | |||
| 94920fd459 | |||
| b2f3b245b7 | |||
| 15bc6c1d73 | |||
| 2c175b8d3a | |||
| a48c17a769 | |||
| 03465c4ab0 | |||
| b4e936c530 | |||
| fb1482370a | |||
| e831bc2bea | |||
| c74c168c96 | |||
| 40b53fa40c | |||
| 3481cc0349 | |||
| 09abd889e9 | |||
| 330bbfbb31 | |||
| 6b7b690476 | |||
| cea72801a7 | |||
| 04369e22a7 | |||
| f1b16eab9a | |||
| 13646d9c90 | |||
| 17d762ce91 | |||
| edb40adaa1 | |||
| bc0809994a | |||
| a3e3d517b0 | |||
| d8b63a54ca | |||
| b81e7583eb | |||
| 51f9ba3642 | |||
| 296626f7c7 | |||
| 5b2daf9746 | |||
| 9a6b615174 | |||
| 18f8d5b269 | |||
| 2867a875c9 | |||
| ee2f5ae8cb | |||
| bd5c301fb9 | |||
| 6df68629f7 | |||
| be08fd4445 | |||
| 6f1afb94fb | |||
| 7401b8a52d | |||
| c83b62ebaa | |||
| 108cf8923e | |||
| 4e26fd9d56 | |||
| 8c9168a4bf | |||
| 97da0b79e4 | |||
| d7e5f6876b | |||
| 1b92b112e2 | |||
| ca55119531 | |||
| d9da14b5dc | |||
| 512b5666ac | |||
| 64977964e8 | |||
| 2bc13e0de6 | |||
| b90c5f17cf | |||
| 7d8d0bd43b | |||
|   | 54c1137927 | ||
| e6655219ee | |||
| 5896f8e35a | |||
| 934cba7251 | |||
| 9cc1a11bef | |||
| c1bc956d6d | |||
| 351b174b86 | |||
| 0b4aaf80dc | |||
| c10c185817 | |||
| 327ef1cbee | |||
| 15eb823c7f | |||
| 54613e5242 | |||
| df1352cbe3 | |||
| 0559afd972 | |||
| afffca020e | |||
| d663cc3f64 | |||
| 110d41e393 | |||
| 1a8823f592 | |||
| 6374a852b0 | |||
| a10c7dd7c3 | |||
| 547c7ea417 | |||
| 760607995a | |||
| 4704197c09 | |||
| 093ac1ac40 | |||
| 9ed8b0d904 | |||
| 7346ce370d | |||
| adefdadc19 | |||
| 703bce2d00 | |||
| 97928ecd84 | |||
| be9ea7f64a | |||
| ec2aaa8789 | |||
| ab14b72526 | |||
| d8e304f3c1 | |||
| ea53ce361f | |||
| 2fce80b347 | |||
| 373c0b1cc3 | |||
| e5e1b7e608 | |||
| 7e9221c9e0 | |||
| 6b849f854e | |||
| 831f6bc744 | |||
| d282a7a537 | |||
| fb2f1e3031 | |||
| 00a0da3df3 | |||
| 8c447b1ffb | |||
| a4841175e8 | |||
| 9b139132a1 | |||
| 4a404ecabc | |||
| aee758b559 | |||
| be060d0386 | 
							
								
								
									
										3
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # Auto detect text files and perform LF normalization | ||||
| * text=auto eof=lf | ||||
| *.cs  diff=csharp | ||||
							
								
								
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| github: chylex | ||||
| patreon: chylex | ||||
| ko_fi: chylex | ||||
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -5,8 +5,15 @@ | ||||
| bld/* | ||||
| !bld/*.iss | ||||
| !bld/*.bat | ||||
| !bld/*.ps1 | ||||
| !bld/Redist | ||||
| !bld/Resources | ||||
|  | ||||
| # Rider | ||||
| **/.idea/dictionaries | ||||
| **/.idea/misc.xml | ||||
| **/.idea/riderMarkupCache.xml | ||||
|  | ||||
| # User-specific files | ||||
| *.suo | ||||
| *.user | ||||
| @@ -140,4 +147,3 @@ _UpgradeReport_Files/ | ||||
| Backup*/ | ||||
| UpgradeLog*.XML | ||||
| UpgradeLog*.htm | ||||
|  | ||||
|   | ||||
							
								
								
									
										8
									
								
								.idea/.idea.TweetDuck/.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/.idea.TweetDuck/.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| # Default ignored files | ||||
| /shelf/ | ||||
| /workspace.xml | ||||
| # Rider ignored files | ||||
| /projectSettingsUpdater.xml | ||||
| /modules.xml | ||||
| /contentModel.xml | ||||
| /.idea.TweetDuck.iml | ||||
							
								
								
									
										450
									
								
								.idea/.idea.TweetDuck/.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										450
									
								
								.idea/.idea.TweetDuck/.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,450 @@ | ||||
| <component name="ProjectCodeStyleConfiguration"> | ||||
|   <code_scheme name="Project" version="173"> | ||||
|     <option name="AUTODETECT_INDENTS" value="false" /> | ||||
|     <option name="OTHER_INDENT_OPTIONS"> | ||||
|       <value> | ||||
|         <option name="INDENT_SIZE" value="2" /> | ||||
|         <option name="TAB_SIZE" value="2" /> | ||||
|       </value> | ||||
|     </option> | ||||
|     <option name="LINE_SEPARATOR" value="
" /> | ||||
|     <option name="RIGHT_MARGIN" value="999" /> | ||||
|     <option name="FORMATTER_TAGS_ENABLED" value="true" /> | ||||
|     <CssCodeStyleSettings> | ||||
|       <option name="HEX_COLOR_LOWER_CASE" value="true" /> | ||||
|     </CssCodeStyleSettings> | ||||
|     <DB2CodeStyleSettings version="6"> | ||||
|       <option name="USE_GENERIC_STYLE" value="true" /> | ||||
|     </DB2CodeStyleSettings> | ||||
|     <DerbyCodeStyleSettings version="6"> | ||||
|       <option name="USE_GENERIC_STYLE" value="true" /> | ||||
|     </DerbyCodeStyleSettings> | ||||
|     <GoCodeStyleSettings> | ||||
|       <option name="MOVE_ALL_STDLIB_IMPORTS_IN_ONE_GROUP" value="true" /> | ||||
|       <option name="GROUP_STDLIB_IMPORTS" value="true" /> | ||||
|       <option name="WRAP_COMP_LIT" value="5" /> | ||||
|       <option name="WRAP_FUNC_PARAMS" value="5" /> | ||||
|       <option name="WRAP_FUNC_RESULT" value="5" /> | ||||
|     </GoCodeStyleSettings> | ||||
|     <H2CodeStyleSettings version="6"> | ||||
|       <option name="USE_GENERIC_STYLE" value="true" /> | ||||
|     </H2CodeStyleSettings> | ||||
|     <H2CodeStyleSettings version="6"> | ||||
|       <option name="USE_GENERIC_STYLE" value="true" /> | ||||
|     </H2CodeStyleSettings> | ||||
|     <HSQLCodeStyleSettings version="6"> | ||||
|       <option name="USE_GENERIC_STYLE" value="true" /> | ||||
|     </HSQLCodeStyleSettings> | ||||
|     <HTMLCodeStyleSettings> | ||||
|       <option name="HTML_ALIGN_TEXT" value="true" /> | ||||
|       <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" /> | ||||
|       <option name="HTML_ELEMENTS_TO_REMOVE_NEW_LINE_BEFORE" value="" /> | ||||
|       <option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="" /> | ||||
|     </HTMLCodeStyleSettings> | ||||
|     <JSCodeStyleSettings version="0"> | ||||
|       <option name="FORCE_SEMICOLON_STYLE" value="true" /> | ||||
|       <option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACKETS" value="true" /> | ||||
|       <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" /> | ||||
|       <option name="FORCE_QUOTE_STYlE" value="true" /> | ||||
|       <option name="USE_EXPLICIT_JS_EXTENSION" value="TRUE" /> | ||||
|       <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" /> | ||||
|       <option name="SPACES_WITHIN_IMPORTS" value="true" /> | ||||
|       <option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" /> | ||||
|       <option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" /> | ||||
|       <option name="IMPORT_SORT_MODULE_NAME" value="true" /> | ||||
|     </JSCodeStyleSettings> | ||||
|     <JSON> | ||||
|       <option name="OBJECT_WRAPPING" value="5" /> | ||||
|       <option name="ARRAY_WRAPPING" value="5" /> | ||||
|     </JSON> | ||||
|     <JavaCodeStyleSettings> | ||||
|       <option name="INSERT_INNER_CLASS_IMPORTS" value="true" /> | ||||
|       <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" /> | ||||
|       <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" /> | ||||
|       <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND"> | ||||
|         <value /> | ||||
|       </option> | ||||
|       <option name="IMPORT_LAYOUT_TABLE"> | ||||
|         <value> | ||||
|           <package name="" withSubpackages="true" static="false" /> | ||||
|           <package name="javax" withSubpackages="true" static="false" /> | ||||
|           <package name="java" withSubpackages="true" static="false" /> | ||||
|           <package name="" withSubpackages="true" static="true" /> | ||||
|         </value> | ||||
|       </option> | ||||
|     </JavaCodeStyleSettings> | ||||
|     <JetCodeStyleSettings> | ||||
|       <option name="PACKAGES_TO_USE_STAR_IMPORTS"> | ||||
|         <value> | ||||
|           <package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" /> | ||||
|         </value> | ||||
|       </option> | ||||
|       <option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" /> | ||||
|       <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" /> | ||||
|       <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" /> | ||||
|       <option name="IMPORT_NESTED_CLASSES" value="true" /> | ||||
|       <option name="WRAP_ELVIS_EXPRESSIONS" value="0" /> | ||||
|       <option name="ALLOW_TRAILING_COMMA" value="true" /> | ||||
|       <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | ||||
|     </JetCodeStyleSettings> | ||||
|     <LessCodeStyleSettings> | ||||
|       <option name="HEX_COLOR_LOWER_CASE" value="true" /> | ||||
|     </LessCodeStyleSettings> | ||||
|     <MSSQLCodeStyleSettings version="6"> | ||||
|       <option name="USE_GENERIC_STYLE" value="true" /> | ||||
|     </MSSQLCodeStyleSettings> | ||||
|     <MySQLCodeStyleSettings version="6"> | ||||
|       <option name="USE_GENERIC_STYLE" value="true" /> | ||||
|     </MySQLCodeStyleSettings> | ||||
|     <Objective-C> | ||||
|       <option name="INDENT_DIRECTIVE_AS_CODE" value="true" /> | ||||
|       <option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" /> | ||||
|       <option name="KEEP_CASE_EXPRESSIONS_IN_ONE_LINE" value="true" /> | ||||
|       <option name="SPACE_BEFORE_INIT_LIST" value="true" /> | ||||
|       <option name="SPACE_AFTER_DICTIONARY_LITERAL_COLON" value="false" /> | ||||
|     </Objective-C> | ||||
|     <OracleCodeStyleSettings version="6"> | ||||
|       <option name="USE_GENERIC_STYLE" value="true" /> | ||||
|     </OracleCodeStyleSettings> | ||||
|     <PHPCodeStyleSettings> | ||||
|       <option name="ALIGN_KEY_VALUE_PAIRS" value="true" /> | ||||
|       <option name="CONCAT_SPACES" value="false" /> | ||||
|       <option name="COMMA_AFTER_LAST_ARRAY_ELEMENT" value="true" /> | ||||
|       <option name="PHPDOC_BLANK_LINE_BEFORE_TAGS" value="true" /> | ||||
|       <option name="LOWER_CASE_BOOLEAN_CONST" value="true" /> | ||||
|       <option name="LOWER_CASE_NULL_CONST" value="true" /> | ||||
|       <option name="ELSE_IF_STYLE" value="COMBINE" /> | ||||
|       <option name="VARIABLE_NAMING_STYLE" value="SNAKE_CASE" /> | ||||
|       <option name="KEEP_BLANK_LINES_AFTER_LBRACE" value="0" /> | ||||
|       <option name="SPACE_BEFORE_CLOSURE_LEFT_PARENTHESIS" value="false" /> | ||||
|       <option name="FORCE_SHORT_DECLARATION_ARRAY_STYLE" value="true" /> | ||||
|       <option name="NEW_LINE_AFTER_PHP_OPENING_TAG" value="true" /> | ||||
|       <option name="SPACE_AROUND_ASSIGNMENT_IN_DECLARE" value="true" /> | ||||
|     </PHPCodeStyleSettings> | ||||
|     <PostgresCodeStyleSettings version="6"> | ||||
|       <option name="USE_GENERIC_STYLE" value="true" /> | ||||
|     </PostgresCodeStyleSettings> | ||||
|     <Properties> | ||||
|       <option name="KEEP_BLANK_LINES" value="true" /> | ||||
|     </Properties> | ||||
|     <Python> | ||||
|       <option name="SPACE_AROUND_EQ_IN_NAMED_PARAMETER" value="true" /> | ||||
|       <option name="SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT" value="true" /> | ||||
|       <option name="NEW_LINE_AFTER_COLON" value="true" /> | ||||
|       <option name="DICT_WRAPPING" value="5" /> | ||||
|       <option name="DICT_NEW_LINE_AFTER_LEFT_BRACE" value="true" /> | ||||
|       <option name="DICT_NEW_LINE_BEFORE_RIGHT_BRACE" value="true" /> | ||||
|     </Python> | ||||
|     <RsCodeStyleSettings> | ||||
|       <option name="ALIGN_RET_TYPE" value="false" /> | ||||
|       <option name="ALIGN_TYPE_PARAMS" value="true" /> | ||||
|       <option name="ALLOW_ONE_LINE_MATCH" value="true" /> | ||||
|       <option name="SPACE_AROUND_ASSOC_TYPE_BINDING" value="true" /> | ||||
|     </RsCodeStyleSettings> | ||||
|     <Ruby> | ||||
|       <option name="INDENT_PRIVATE_METHODS" value="true" /> | ||||
|       <option name="INDENT_PROTECTED_METHODS" value="true" /> | ||||
|       <option name="INDENT_PUBLIC_METHODS" value="true" /> | ||||
|       <option name="INDENT_WHEN_CASES" value="true" /> | ||||
|       <option name="CHAIN_CALLS_ALIGNMENT" value="2" /> | ||||
|     </Ruby> | ||||
|     <SQLiteCodeStyleSettings version="6"> | ||||
|       <option name="USE_GENERIC_STYLE" value="true" /> | ||||
|     </SQLiteCodeStyleSettings> | ||||
|     <ScssCodeStyleSettings> | ||||
|       <option name="HEX_COLOR_LOWER_CASE" value="true" /> | ||||
|     </ScssCodeStyleSettings> | ||||
|     <SqlCodeStyleSettings version="6"> | ||||
|       <option name="KEYWORD_CASE" value="2" /> | ||||
|       <option name="TYPE_CASE" value="2" /> | ||||
|       <option name="CUSTOM_TYPE_CASE" value="2" /> | ||||
|       <option name="SUBQUERY_CONTENT" value="1" /> | ||||
|       <option name="SUBQUERY_CLOSING" value="1" /> | ||||
|       <option name="INSERT_TABLE_EL_LINE" value="0" /> | ||||
|       <option name="INSERT_EL_WRAP" value="2" /> | ||||
|       <option name="SET_EL_WRAP" value="2" /> | ||||
|       <option name="SET_ALIGN_EQUAL_SIGN" value="false" /> | ||||
|       <option name="FROM_EL_WRAP" value="2" /> | ||||
|       <option name="FROM_ALIGN_JOIN_TABLES" value="true" /> | ||||
|       <option name="FROM_INDENT_JOIN" value="false" /> | ||||
|       <option name="FROM_ONLY_JOIN_INDENT" value="2" /> | ||||
|       <option name="WHERE_EL_WRAP" value="2" /> | ||||
|       <option name="TABLE_OPENING" value="1" /> | ||||
|       <option name="TABLE_CONTENT" value="2" /> | ||||
|       <option name="TABLE_CLOSING" value="3" /> | ||||
|       <option name="TABLE_DEFAULTS_ALIGN" value="false" /> | ||||
|       <option name="TABLE_NULLABILITIES_ALIGN" value="false" /> | ||||
|       <option name="CONSTRAINT_WRAP_1" value="false" /> | ||||
|       <option name="CONSTRAINT_WRAP_3" value="true" /> | ||||
|       <option name="CONSTRAINT_WRAP_4" value="true" /> | ||||
|       <option name="VIEW_INDENT_QUERY" value="true" /> | ||||
|       <option name="EXPR_CASE_WHEN_WRAP" value="false" /> | ||||
|     </SqlCodeStyleSettings> | ||||
|     <SybaseCodeStyleSettings version="6"> | ||||
|       <option name="USE_GENERIC_STYLE" value="true" /> | ||||
|     </SybaseCodeStyleSettings> | ||||
|     <TypeScriptCodeStyleSettings version="0"> | ||||
|       <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" /> | ||||
|       <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" /> | ||||
|       <option name="SPACES_WITHIN_IMPORTS" value="true" /> | ||||
|       <option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" /> | ||||
|       <option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" /> | ||||
|     </TypeScriptCodeStyleSettings> | ||||
|     <XML> | ||||
|       <option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" /> | ||||
|     </XML> | ||||
|     <codeStyleSettings language="CMake"> | ||||
|       <indentOptions> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="4" /> | ||||
|         <option name="USE_TAB_CHARACTER" value="true" /> | ||||
|         <option name="SMART_TABS" value="true" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="CSS"> | ||||
|       <indentOptions> | ||||
|         <option name="INDENT_SIZE" value="2" /> | ||||
|         <option name="TAB_SIZE" value="2" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="Groovy"> | ||||
|       <option name="ELSE_ON_NEW_LINE" value="true" /> | ||||
|       <option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" /> | ||||
|       <option name="IF_BRACE_FORCE" value="3" /> | ||||
|       <option name="DOWHILE_BRACE_FORCE" value="3" /> | ||||
|       <option name="WHILE_BRACE_FORCE" value="3" /> | ||||
|       <option name="FOR_BRACE_FORCE" value="3" /> | ||||
|       <indentOptions> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="4" /> | ||||
|         <option name="USE_TAB_CHARACTER" value="true" /> | ||||
|         <option name="SMART_TABS" value="true" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="HTML"> | ||||
|       <option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" /> | ||||
|       <option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" /> | ||||
|       <indentOptions> | ||||
|         <option name="INDENT_SIZE" value="2" /> | ||||
|         <option name="TAB_SIZE" value="2" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="JAVA"> | ||||
|       <option name="RIGHT_MARGIN" value="999" /> | ||||
|       <option name="BLANK_LINES_AFTER_PACKAGE" value="0" /> | ||||
|       <option name="BLANK_LINES_BEFORE_IMPORTS" value="0" /> | ||||
|       <option name="ELSE_ON_NEW_LINE" value="true" /> | ||||
|       <option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" /> | ||||
|       <option name="SPACE_AFTER_TYPE_CAST" value="false" /> | ||||
|       <option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" /> | ||||
|       <option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" /> | ||||
|       <option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" /> | ||||
|       <option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" /> | ||||
|       <option name="IF_BRACE_FORCE" value="3" /> | ||||
|       <option name="DOWHILE_BRACE_FORCE" value="3" /> | ||||
|       <option name="WHILE_BRACE_FORCE" value="3" /> | ||||
|       <option name="FOR_BRACE_FORCE" value="3" /> | ||||
|       <indentOptions> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="4" /> | ||||
|         <option name="USE_TAB_CHARACTER" value="true" /> | ||||
|         <option name="SMART_TABS" value="true" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="JSON"> | ||||
|       <option name="KEEP_BLANK_LINES_IN_CODE" value="1" /> | ||||
|       <option name="SPACE_WITHIN_BRACKETS" value="true" /> | ||||
|       <option name="SPACE_WITHIN_BRACES" value="true" /> | ||||
|       <indentOptions> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="JavaScript"> | ||||
|       <option name="ELSE_ON_NEW_LINE" value="true" /> | ||||
|       <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" /> | ||||
|       <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" /> | ||||
|       <option name="IF_BRACE_FORCE" value="3" /> | ||||
|       <option name="DOWHILE_BRACE_FORCE" value="3" /> | ||||
|       <option name="WHILE_BRACE_FORCE" value="3" /> | ||||
|       <option name="FOR_BRACE_FORCE" value="3" /> | ||||
|       <indentOptions> | ||||
|         <option name="USE_TAB_CHARACTER" value="true" /> | ||||
|         <option name="SMART_TABS" value="true" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="LESS"> | ||||
|       <indentOptions> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="Lua"> | ||||
|       <indentOptions> | ||||
|         <option name="INDENT_SIZE" value="2" /> | ||||
|         <option name="TAB_SIZE" value="2" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="ObjectiveC"> | ||||
|       <option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" /> | ||||
|       <option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" /> | ||||
|       <option name="LINE_COMMENT_ADD_SPACE" value="true" /> | ||||
|       <option name="ELSE_ON_NEW_LINE" value="true" /> | ||||
|       <option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" /> | ||||
|       <option name="IF_BRACE_FORCE" value="3" /> | ||||
|       <option name="DOWHILE_BRACE_FORCE" value="3" /> | ||||
|       <option name="WHILE_BRACE_FORCE" value="3" /> | ||||
|       <option name="FOR_BRACE_FORCE" value="3" /> | ||||
|       <indentOptions> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="4" /> | ||||
|         <option name="USE_TAB_CHARACTER" value="true" /> | ||||
|         <option name="SMART_TABS" value="true" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="PHP"> | ||||
|       <option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" /> | ||||
|       <option name="KEEP_FIRST_COLUMN_COMMENT" value="false" /> | ||||
|       <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" /> | ||||
|       <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" /> | ||||
|       <option name="KEEP_BLANK_LINES_IN_CODE" value="1" /> | ||||
|       <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" /> | ||||
|       <option name="CLASS_BRACE_STYLE" value="1" /> | ||||
|       <option name="METHOD_BRACE_STYLE" value="1" /> | ||||
|       <option name="ELSE_ON_NEW_LINE" value="true" /> | ||||
|       <option name="SPECIAL_ELSE_IF_TREATMENT" value="true" /> | ||||
|       <option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" /> | ||||
|       <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" /> | ||||
|       <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" /> | ||||
|       <option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" /> | ||||
|       <option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" /> | ||||
|       <option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" /> | ||||
|       <option name="IF_BRACE_FORCE" value="3" /> | ||||
|       <option name="DOWHILE_BRACE_FORCE" value="3" /> | ||||
|       <option name="WHILE_BRACE_FORCE" value="3" /> | ||||
|       <option name="FOR_BRACE_FORCE" value="3" /> | ||||
|       <indentOptions> | ||||
|         <option name="INDENT_SIZE" value="2" /> | ||||
|         <option name="TAB_SIZE" value="2" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="Puppet"> | ||||
|       <indentOptions> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="Python"> | ||||
|       <indentOptions> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="RHTML"> | ||||
|       <indentOptions> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="Rust"> | ||||
|       <option name="RIGHT_MARGIN" value="140" /> | ||||
|       <option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" /> | ||||
|       <indentOptions> | ||||
|         <option name="USE_TAB_CHARACTER" value="true" /> | ||||
|         <option name="SMART_TABS" value="true" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="SASS"> | ||||
|       <indentOptions> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="SCSS"> | ||||
|       <indentOptions> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="SQL"> | ||||
|       <indentOptions> | ||||
|         <option name="USE_TAB_CHARACTER" value="true" /> | ||||
|         <option name="SMART_TABS" value="true" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="Shell Script"> | ||||
|       <indentOptions> | ||||
|         <option name="INDENT_SIZE" value="4" /> | ||||
|         <option name="TAB_SIZE" value="4" /> | ||||
|         <option name="USE_TAB_CHARACTER" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="TOML"> | ||||
|       <indentOptions> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="4" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="TypeScript"> | ||||
|       <option name="ELSE_ON_NEW_LINE" value="true" /> | ||||
|       <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" /> | ||||
|       <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" /> | ||||
|       <option name="IF_BRACE_FORCE" value="3" /> | ||||
|       <option name="DOWHILE_BRACE_FORCE" value="3" /> | ||||
|       <option name="WHILE_BRACE_FORCE" value="3" /> | ||||
|       <option name="FOR_BRACE_FORCE" value="3" /> | ||||
|       <indentOptions> | ||||
|         <option name="USE_TAB_CHARACTER" value="true" /> | ||||
|         <option name="SMART_TABS" value="true" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="XML"> | ||||
|       <indentOptions> | ||||
|         <option name="INDENT_SIZE" value="2" /> | ||||
|         <option name="TAB_SIZE" value="2" /> | ||||
|         <option name="SMART_TABS" value="true" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="go"> | ||||
|       <option name="CALL_PARAMETERS_WRAP" value="5" /> | ||||
|       <indentOptions> | ||||
|         <option name="SMART_TABS" value="true" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="kotlin"> | ||||
|       <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | ||||
|       <option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" /> | ||||
|       <option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" /> | ||||
|       <option name="LINE_COMMENT_ADD_SPACE" value="true" /> | ||||
|       <option name="ELSE_ON_NEW_LINE" value="true" /> | ||||
|       <option name="METHOD_ANNOTATION_WRAP" value="0" /> | ||||
|       <option name="CLASS_ANNOTATION_WRAP" value="0" /> | ||||
|       <option name="FIELD_ANNOTATION_WRAP" value="0" /> | ||||
|       <indentOptions> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="4" /> | ||||
|         <option name="USE_TAB_CHARACTER" value="true" /> | ||||
|         <option name="SMART_TABS" value="true" /> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="liquid"> | ||||
|       <indentOptions> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="ruby"> | ||||
|       <option name="SPACE_WITHIN_BRACES" value="true" /> | ||||
|       <indentOptions> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="yaml"> | ||||
|       <indentOptions> | ||||
|         <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|   </code_scheme> | ||||
| </component> | ||||
							
								
								
									
										5
									
								
								.idea/.idea.TweetDuck/.idea/codeStyles/codeStyleConfig.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.idea/.idea.TweetDuck/.idea/codeStyles/codeStyleConfig.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| <component name="ProjectCodeStyleConfiguration"> | ||||
|   <state> | ||||
|     <option name="USE_PER_PROJECT_SETTINGS" value="true" /> | ||||
|   </state> | ||||
| </component> | ||||
							
								
								
									
										6
									
								
								.idea/.idea.TweetDuck/.idea/encodings.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/.idea.TweetDuck/.idea/encodings.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8"> | ||||
|     <file url="PROJECT" charset="UTF-8" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										8
									
								
								.idea/.idea.TweetDuck/.idea/indexLayout.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/.idea.TweetDuck/.idea/indexLayout.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="UserContentModel"> | ||||
|     <attachedFolders /> | ||||
|     <explicitIncludes /> | ||||
|     <explicitExcludes /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										770
									
								
								.idea/.idea.TweetDuck/.idea/inspectionProfiles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										770
									
								
								.idea/.idea.TweetDuck/.idea/inspectionProfiles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,770 @@ | ||||
| <component name="InspectionProjectProfileManager"> | ||||
|   <profile version="1.0"> | ||||
|     <option name="myName" value="Project" /> | ||||
|     <inspection_tool class="AccessToNonThreadSafeStaticFieldFromInstance" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="nonThreadSafeClasses"> | ||||
|         <value /> | ||||
|       </option> | ||||
|       <option name="nonThreadSafeTypes" value="" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="AccessToStaticFieldLockedOnInstance" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AddOperatorModifier" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AmbiguousFieldAccess" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AmbiguousMethodCall" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AnonymousInnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ArrayEquality" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AssertEqualsCalledOnArray" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AssertsWithoutMessagesTestNG" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AssignmentOrReturnOfFieldWithMutableType" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AssignmentToCatchBlockParameter" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AssignmentToLambdaParameter" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AssignmentToMethodParameter" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreTransformationOfOriginalParameter" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="AssignmentToStaticFieldFromInstanceMethod" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AutoBoxing" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreAddedToCollection" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoredTypes" value="java.util.stream.Stream,java.util.stream.IntStream,java.util.stream.LongStream,java.util.stream.DoubleStream,net.minecraft.client.Minecraft,net.minecraft.client.MainWindow" /> | ||||
|       <option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,net.minecraft.client.MinecraftClient,getInstance|getWindow" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="AutoUnboxing" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AwaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="AwaitWithoutCorrespondingSignal" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="BadOddness" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="BigDecimalEquals" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="BigDecimalLegacyMethod" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="BooleanExpressionMayBeConditional" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CallToNativeMethodWhileLocked" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CallableParameterUseCaseInTypeContextInspection" enabled="false" level="WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="CascadeStringReplacementInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="USE_SHORT_ARRAYS_SYNTAX" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CastToIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ChainedEquality" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ChannelResource" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="insideTryAllowed" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="ClassIndependentOfModule" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ClassLoaderInstantiation" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ClassMayBeInterface" enabled="true" level="WEAK WARNING" enabled_by_default="true"> | ||||
|       <option name="reportClassesWithNonAbstractMethods" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="ClassMethodNameMatchesFieldNameInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ClassNewInstance" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ClassOnlyUsedInOneModule" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ClassOnlyUsedInOnePackage" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ClassOverridesFieldOfSuperClassInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="REPORT_PRIVATE_REDEFINITION" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="ClassUnconnectedToPackage" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ClassWithOnlyPrivateConstructors" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CloneableClassInSecureContext" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CollectionContainsUrl" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CollectionsFieldAccessReplaceableByMethodCall" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ComparableImplementedButEqualsNotOverridden" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ComparatorNotSerializable" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CompareToUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ComposeMissingKeys" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ConditionSignal" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ConditionalExpression" enabled="true" level="INFORMATION" enabled_by_default="true"> | ||||
|       <option name="ignoreSimpleAssignmentsAndReturns" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ConfusingElse" enabled="false" level="WEAK WARNING" enabled_by_default="false"> | ||||
|       <option name="reportWhenNoStatementFollow" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="ConfusingMainMethod" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ConfusingOctalEscape" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ConfusingPlusesOrMinusesJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ConstantJUnitAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ConstantMathCall" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ConstantTestNGAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ConvertJavadoc" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ConvertLambdaToReference" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ConvertOldAnnotations" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CssConvertColorToHexInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CssConvertColorToRgbInspection" enabled="true" level="INFORMATION" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CssMissingSemicolon" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CssReplaceWithShorthandUnsafely" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CustomClassloader" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CustomSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CyclicClassDependency" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="CyclicPackageDependency" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="DateToString" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="DisallowWritingIntoStaticPropertiesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="DisconnectedForeachInstructionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="DisjointPackage" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="DivideByZeroJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="DocumentWriteJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="DoubleBraceInitialization" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="DoubleCheckedLocking" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreOnVolatileVariables" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="DriverManagerGetConnection" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="DuplicateBooleanBranch" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="DuplicateConditionJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="DynamicallyGeneratedCodeJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ES6ConvertIndexedForToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ES6ConvertLetToConst" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ES6ConvertToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ES6TopLevelAwaitExpression" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|     <inspection_tool class="EmptyDirectory" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="EmptyStatementBody" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_reportEmptyBlocks" value="true" /> | ||||
|       <option name="commentsAreContent" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="EmptySynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="EnumSwitchStatementWhichMissesCases" enabled="true" level="WEAK WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreSwitchStatementsWithDefault" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="EqualsCalledOnEnumConstant" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="EqualsHashCodeCalledOnUrl" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="EqualsUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ErrorRethrown" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ExceptionNameDoesntEndWithException" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ExceptionPackage" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ExpectedExceptionNeverThrownTestNG" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ExplicitArgumentCanBeLambda" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ExtendsThread" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ExtendsThrowable" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ExternalizableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="FallthruInSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="FieldAccessedSynchronizedAndUnsynchronized" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="countGettersAndSetters" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="FieldDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|     <inspection_tool class="FieldHidesSuperclassField" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_ignoreInvisibleFields" value="false" /> | ||||
|       <option name="ignoreStaticFields" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="FieldMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="Finalize" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreTrivialFinalizers" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="FinalizeNotProtected" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="FixedTimeStartWithInspection" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="FloatingPointEquality" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="FoldInitializerAndIfToElvis" enabled="false" level="INFO" enabled_by_default="false" /> | ||||
|     <inspection_tool class="ForLoopThatDoesntUseLoopVariableJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ForgottenDebugOutputInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true"> | ||||
|       <option name="configuration"> | ||||
|         <list> | ||||
|           <option value="\Codeception\Util\Debug::debug" /> | ||||
|           <option value="\Codeception\Util\Debug::pause" /> | ||||
|           <option value="\Doctrine\Common\Util\Debug::dump" /> | ||||
|           <option value="\Doctrine\Common\Util\Debug::export" /> | ||||
|           <option value="\Illuminate\Support\Debug\Dumper::dump" /> | ||||
|           <option value="\Symfony\Component\Debug\Debug::enable" /> | ||||
|           <option value="\Symfony\Component\Debug\DebugClassLoader::enable" /> | ||||
|           <option value="\Symfony\Component\Debug\ErrorHandler::register" /> | ||||
|           <option value="\Symfony\Component\Debug\ExceptionHandler::register" /> | ||||
|           <option value="\TYPO3\CMS\Core\Utility\DebugUtility::debug" /> | ||||
|           <option value="\Zend\Debug\Debug::dump" /> | ||||
|           <option value="\Zend\Di\Display\Console::export" /> | ||||
|           <option value="dd" /> | ||||
|           <option value="debug_print_backtrace" /> | ||||
|           <option value="debug_zval_dump" /> | ||||
|           <option value="dpm" /> | ||||
|           <option value="dpq" /> | ||||
|           <option value="dsm" /> | ||||
|           <option value="dump" /> | ||||
|           <option value="dvm" /> | ||||
|           <option value="error_log" /> | ||||
|           <option value="kpr" /> | ||||
|           <option value="phpinfo" /> | ||||
|           <option value="print_r" /> | ||||
|           <option value="var_dump" /> | ||||
|           <option value="var_export" /> | ||||
|           <option value="wp_die" /> | ||||
|           <option value="xdebug_break" /> | ||||
|           <option value="xdebug_call_class" /> | ||||
|           <option value="xdebug_call_file" /> | ||||
|           <option value="xdebug_call_function" /> | ||||
|           <option value="xdebug_call_line" /> | ||||
|           <option value="xdebug_code_coverage_started" /> | ||||
|           <option value="xdebug_debug_zval" /> | ||||
|           <option value="xdebug_debug_zval_stdout" /> | ||||
|           <option value="xdebug_dump_superglobals" /> | ||||
|           <option value="xdebug_enable" /> | ||||
|           <option value="xdebug_get_code_coverage" /> | ||||
|           <option value="xdebug_get_collected_errors" /> | ||||
|           <option value="xdebug_get_declared_vars" /> | ||||
|           <option value="xdebug_get_function_stack" /> | ||||
|           <option value="xdebug_get_headers" /> | ||||
|           <option value="xdebug_get_monitored_functions" /> | ||||
|           <option value="xdebug_get_profiler_filename" /> | ||||
|           <option value="xdebug_get_stack_depth" /> | ||||
|           <option value="xdebug_get_tracefile_name" /> | ||||
|           <option value="xdebug_is_enabled" /> | ||||
|           <option value="xdebug_memory_usage" /> | ||||
|           <option value="xdebug_peak_memory_usage" /> | ||||
|           <option value="xdebug_print_function_stack" /> | ||||
|           <option value="xdebug_start_code_coverage" /> | ||||
|           <option value="xdebug_start_error_collection" /> | ||||
|           <option value="xdebug_start_function_monitor" /> | ||||
|           <option value="xdebug_start_trace" /> | ||||
|           <option value="xdebug_stop_code_coverage" /> | ||||
|           <option value="xdebug_stop_error_collection" /> | ||||
|           <option value="xdebug_stop_function_monitor" /> | ||||
|           <option value="xdebug_stop_trace" /> | ||||
|           <option value="xdebug_time_index" /> | ||||
|           <option value="xdebug_var_dump" /> | ||||
|         </list> | ||||
|       </option> | ||||
|       <option name="migratedIntoUserSpace" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="FunctionNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_regex" value="[a-z][A-Za-z]*" /> | ||||
|       <option name="m_minLength" value="0" /> | ||||
|       <option name="m_maxLength" value="99" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="FunctionWithInconsistentReturnsJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="HashCodeUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="HibernateResource" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="insideTryAllowed" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="HtmlMissingClosingTag" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="HtmlPresentationalElement" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="HtmlRequiredTitleAttribute" enabled="true" level="INFORMATION" enabled_by_default="true" /> | ||||
|     <inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" /> | ||||
|       <option name="insideTryAllowed" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="IfStatementWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ImplicitDefaultCharsetUsage" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="InconsistentLanguageLevel" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="IncrementDecrementOperationEquivalentInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="InnerClassOnInterface" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_ignoreInnerInterfaces" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="InnerClassReferencedViaSubclass" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="InnerClassVariableHidesOuterClassVariable" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_ignoreInvisibleFields" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="InsertLiteralUnderscores" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="InstanceofCatchParameter" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="InstanceofIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="InstanceofThis" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="InterfaceMayBeAnnotatedFunctional" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="IsEmptyFunctionUsageInspection" enabled="false" level="WARNING" enabled_by_default="false"> | ||||
|       <option name="SUGGEST_TO_USE_COUNT_CHECK" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="IsNullFunctionUsageInspection" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="IteratorNextDoesNotThrowNoSuchElementException" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="JDBCExecuteWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="JDBCPrepareStatementWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="JDBCResource" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="insideTryAllowed" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="JNDIResource" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="insideTryAllowed" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="JSClassNamingConvention" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_minLength" value="0" /> | ||||
|       <option name="m_maxLength" value="99" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="JSConstructorReturnsPrimitive" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="JSEqualityComparisonWithCoercion.TS" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="mySeverity" value="Always" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="JSJoinVariableDeclarationAndAssignment" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="JSMissingSwitchBranches" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="JSNonASCIINames" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="myAllowOnlyAscii" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="JSNonStrictModeUsed" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="JSOctalInteger" enabled="true" level="ERROR" enabled_by_default="true"> | ||||
|       <option name="myReportNonStrictEs5" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="JSUndeclaredVariable" enabled="true" level="WEAK WARNING" enabled_by_default="true"> | ||||
|       <option name="myCheckGlobalDefinitions" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="JSUnusedGlobalSymbols" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="myReportUnusedDefinitions" value="true" /> | ||||
|       <option name="myReportUnusedProperties" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="JUnitDatapoint" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="JUnitRule" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="JUnitTestNG" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="JavadocHtmlLint" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|     <inspection_tool class="JoinDeclarationAndAssignmentJava" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="LambdaCanBeMethodCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="LengthOneStringInIndexOf" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="LengthOneStringsInConcatenation" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="LoadLibraryWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="REPORT_VARIABLES" value="true" /> | ||||
|       <option name="REPORT_PARAMETERS" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="LocalVariableDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|     <inspection_tool class="LocalVariableNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_regex" value="[a-z][A-Za-z]*" /> | ||||
|       <option name="m_minLength" value="0" /> | ||||
|       <option name="m_maxLength" value="99" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="MalformedSetUpTearDown" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="MapReplaceableByEnumMap" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="MethodCallSideOnly" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|     <inspection_tool class="MethodMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_onlyPrivateOrFinal" value="false" /> | ||||
|       <option name="m_ignoreEmptyMethods" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="MethodMayBeSynchronized" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="MethodOverloadsParentMethod" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="reportIncompatibleParameters" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="MethodOverridesInaccessibleMethodOfSuper" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="MethodOverridesStaticMethod" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="MethodSideOnly" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|     <inspection_tool class="MisorderedAssertEqualsArgumentsTestNG" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="MisorderedAssertEqualsParameters" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="MisorderedModifiersInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreObjectMethods" value="false" /> | ||||
|       <option name="ignoreAnonymousClassMethods" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_requireAnnotationsFirst" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="MultipleTopLevelClassesInFile" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NakedNotify" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NegatedConditional" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_ignoreNegatedNullComparison" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="NestedAssignment" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NestedClassSideOnly" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NestedConditionalExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NestedSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NestedSynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NestedTernaryOperatorInspection" enabled="false" level="WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="NewExpressionSideOnly" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonBlockStatementBodyJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonExceptionNameEndsWithException" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonFinalClone" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonFinalFieldInEnum" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonFinalFieldOfException" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonFinalUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonReproducibleMathCall" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonSerializableFieldInSerializableClass" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignorableAnnotations"> | ||||
|         <value /> | ||||
|       </option> | ||||
|       <option name="ignoreAnonymousInnerClasses" value="false" /> | ||||
|       <option name="superClassString" value="java.awt.Component" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="NonSerializableObjectBoundToHttpSession" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonSerializableObjectPassedToObjectStream" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonSerializableWithSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonSerializableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonShortCircuitBoolean" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonSynchronizedMethodOverridesSynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NonThreadSafeLazyInitialization" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NotOptimalIfConditionsInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="REPORT_DUPLICATE_CONDITIONS" value="false" /> | ||||
|       <option name="SUGGEST_OPTIMIZING_CONDITIONS" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="NotifyCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NotifyWithoutCorrespondingWait" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NullThrown" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="NumericToString" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="OCInconsistentNaming" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ObjectAllocationIgnoredJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ObjectInstantiationInEqualsHashCode" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ObjectNotify" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ObjectToString" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ObsoleteCollection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreRequiredObsoleteCollectionTypes" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="OctalAndDecimalIntegersMixed" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="OffsetOperationsInspection" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|     <inspection_tool class="OneTimeUseVariablesInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ALLOW_LONG_STATEMENTS" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="OverloadedVarargsMethod" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PackageInMultipleModules" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ParameterHidingMemberVariable" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_ignoreInvisibleFields" value="true" /> | ||||
|       <option name="m_ignoreStaticMethodParametersHidingInstanceFields" value="true" /> | ||||
|       <option name="m_ignoreForConstructors" value="true" /> | ||||
|       <option name="m_ignoreForPropertySetters" value="true" /> | ||||
|       <option name="m_ignoreForAbstractMethods" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="ParameterNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_regex" value="[a-z][A-Za-z]*" /> | ||||
|       <option name="m_minLength" value="0" /> | ||||
|       <option name="m_maxLength" value="99" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="ParameterizedParametersStaticCollection" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpAssignmentInConditionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpAssignmentReplaceableWithOperatorAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpAssignmentReplaceableWithPrefixExpressionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpCSValidationInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false"> | ||||
|       <option name="EXTENSIONS" value="php,js,css,inc" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpClassNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_minLength" value="0" /> | ||||
|       <option name="m_maxLength" value="0" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpClosureCanBeConvertedToShortArrowFunctionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpCompoundNamespaceDepthInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpConstantNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_minLength" value="0" /> | ||||
|       <option name="m_maxLength" value="0" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpConstantReassignmentInspection" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpDivisionByZeroInspection" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpDocMissingThrowsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true"> | ||||
|       <option name="SKIP_ON_EMPTY_PHPDOC" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpFunctionNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_minLength" value="0" /> | ||||
|       <option name="m_maxLength" value="0" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpInconsistentReturnPointsInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ALLOW_RETURN_NULL_IN_VOID" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpLongTypeFormInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpLoopCanBeConvertedToArrayFillInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpLoopCanBeConvertedToArrayFilterInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpLoopCanBeConvertedToArrayMapInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpMethodNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_minLength" value="0" /> | ||||
|       <option name="m_maxLength" value="0" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpMethodOrClassCallIsNotCaseSensitiveInspection" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpMissingParentCallMagicInspection" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpMissingReturnTypeInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpMissingStrictTypesDeclarationInspection" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpMissingVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpNewClassMissingParameterListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpNonCanonicalElementsOrderInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpOverridingMethodVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpPropertyNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_regex" value="[a-z][_a-z\d]*" /> | ||||
|       <option name="m_minLength" value="0" /> | ||||
|       <option name="m_maxLength" value="0" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpRedundantClosingTagInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="PhpSeparateElseIfInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpShortOpenTagInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpSingleStatementWithBracesInspection" enabled="false" level="INFORMATION" enabled_by_default="false" /> | ||||
|     <inspection_tool class="PhpStatementHasEmptyBodyInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="myCommentsCountAsContent" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpStatementWithoutBracesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpStaticAsDynamicMethodCallInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true"> | ||||
|       <option name="SHOW_FOR_MAGIC" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpTraditionalSyntaxArrayLiteralInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpTraitsUseListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpUndefinedCallbackInspection" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpUndefinedClassConstantInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="DOWNGRADE_SEVERITY" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpUndefinedMethodInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="DOWNGRADE_SEVERITY" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpUnused" enabled="true" level="WEAK WARNING" enabled_by_default="true"> | ||||
|       <option name="SHOW_UNUSED_BY_ENTRIES" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpUnusedParameterInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="DONT_REPORT_ANONYMOUS" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpVarUsageInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PhpVariableNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_minLength" value="0" /> | ||||
|       <option name="m_maxLength" value="0" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="PhpVariableVariableInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PointlessBitwiseExpressionJS" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_ignoreExpressionsContainingConstants" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="ProblematicVarargsMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ProblematicWhitespace" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PropertyCanBeStaticInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ProtectedMemberInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PublicFieldAccessedInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PublicStaticArrayField" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PublicStaticCollectionField" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PyAugmentAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PyClassicStyleClassInspection" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="PyMissingTypeHintsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true"> | ||||
|       <option name="m_onlyWhenTypesAreKnown" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="RawTypeCanBeGeneric" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReadObjectAndWriteObjectPrivate" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReadObjectInitialization" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReadResolveAndWriteReplaceProtected" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="RedundantElseClauseInspection" enabled="false" level="WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="RedundantImplements" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreSerializable" value="true" /> | ||||
|       <option name="ignoreCloneable" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="RedundantObjectTypeCheck" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="RedundantSuppression" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="IGNORE_ALL" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="ReferencingObjectsInspection" enabled="false" level="WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="RegExpOctalEscape" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReplaceAssignmentWithOperatorAssignment" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreLazyOperators" value="true" /> | ||||
|       <option name="ignoreObscureOperators" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="ReplaceAssignmentWithOperatorAssignmentJS" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReplaceCollectionCountWithSize" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReplaceGuardClauseWithFunctionCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReplaceStringFormatWithLiteral" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReplaceSubstringWithDropLast" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReplaceSubstringWithIndexingOperation" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReplaceSubstringWithSubstringAfter" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReplaceSubstringWithSubstringBefore" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReplaceSubstringWithTake" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ResultOfObjectAllocationIgnored" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ResultSetIndexZero" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ReturnOfInnerClass" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="RsSimplifyBooleanExpression" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="RuntimeExec" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="RuntimeExecWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SafeLock" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="optionConfiguration"> | ||||
|         <list> | ||||
|           <option value="barryvdh/laravel-debugbar" /> | ||||
|           <option value="behat/behat" /> | ||||
|           <option value="brianium/paratest" /> | ||||
|           <option value="codeception/codeception" /> | ||||
|           <option value="codedungeon/phpunit-result-printer" /> | ||||
|           <option value="composer/composer" /> | ||||
|           <option value="doctrine/coding-standard" /> | ||||
|           <option value="filp/whoops" /> | ||||
|           <option value="friendsofphp/php-cs-fixer" /> | ||||
|           <option value="humbug/humbug" /> | ||||
|           <option value="infection/infection" /> | ||||
|           <option value="jakub-onderka/php-parallel-lint" /> | ||||
|           <option value="johnkary/phpunit-speedtrap" /> | ||||
|           <option value="kalessil/production-dependencies-guard" /> | ||||
|           <option value="mikey179/vfsStream" /> | ||||
|           <option value="mockery/mockery" /> | ||||
|           <option value="mybuilder/phpunit-accelerator" /> | ||||
|           <option value="orchestra/testbench" /> | ||||
|           <option value="pdepend/pdepend" /> | ||||
|           <option value="phan/phan" /> | ||||
|           <option value="phing/phing" /> | ||||
|           <option value="phpcompatibility/php-compatibility" /> | ||||
|           <option value="phpmd/phpmd" /> | ||||
|           <option value="phpro/grumphp" /> | ||||
|           <option value="phpspec/phpspec" /> | ||||
|           <option value="phpspec/prophecy" /> | ||||
|           <option value="phpstan/phpstan" /> | ||||
|           <option value="phpunit/phpunit" /> | ||||
|           <option value="povils/phpmnd" /> | ||||
|           <option value="roave/security-advisories" /> | ||||
|           <option value="satooshi/php-coveralls" /> | ||||
|           <option value="sebastian/phpcpd" /> | ||||
|           <option value="slevomat/coding-standard" /> | ||||
|           <option value="spatie/phpunit-watcher" /> | ||||
|           <option value="squizlabs/php_codesniffer" /> | ||||
|           <option value="sstalle/php7cc" /> | ||||
|           <option value="symfony/debug" /> | ||||
|           <option value="symfony/maker-bundle" /> | ||||
|           <option value="symfony/phpunit-bridge" /> | ||||
|           <option value="symfony/var-dumper" /> | ||||
|           <option value="vimeo/psalm" /> | ||||
|           <option value="wimg/php-compatibility" /> | ||||
|           <option value="wp-coding-standards/wpcs" /> | ||||
|           <option value="yiisoft/yii2-coding-standards" /> | ||||
|           <option value="yiisoft/yii2-debug" /> | ||||
|           <option value="yiisoft/yii2-gii" /> | ||||
|           <option value="zendframework/zend-coding-standard" /> | ||||
|           <option value="zendframework/zend-debug" /> | ||||
|           <option value="zendframework/zend-test" /> | ||||
|         </list> | ||||
|       </option> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="SerialPersistentFieldsWithWrongSignature" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SerialVersionUIDNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SerializableHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreAnonymousInnerClasses" value="false" /> | ||||
|       <option name="superClassString" value="java.awt.Component" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="SerializableHasSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreAnonymousInnerClasses" value="false" /> | ||||
|       <option name="superClassString" value="java.awt.Component" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="SerializableInnerClassHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreAnonymousInnerClasses" value="false" /> | ||||
|       <option name="superClassString" value="java.awt.Component" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="SerializableInnerClassWithNonSerializableOuterClass" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreAnonymousInnerClasses" value="false" /> | ||||
|       <option name="superClassString" value="java.awt.Component" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="SerializableStoresNonSerializable" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SerializableWithUnconstructableAncestor" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SetReplaceableByEnumSet" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SeveralTargetsMessage" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SharedThreadLocalRandom" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ShortEchoTagCanBeUsedInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SignalWithoutCorrespondingAwait" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SimpleDateFormatWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SimplifiableAnnotation" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SimplifiableIfStatement" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SingleStatementInBlock" enabled="false" level="INFORMATION" enabled_by_default="false" /> | ||||
|     <inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SleepWhileHoldingLock" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SocketResource" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="insideTryAllowed" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false"> | ||||
|       <option name="processCode" value="true" /> | ||||
|       <option name="processLiterals" value="true" /> | ||||
|       <option name="processComments" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="SqlGotoInspection" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SqlRedundantOrderingDirectionInspection" enabled="false" level="WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="SqlWithoutWhereInspection" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="myDontWarnForLimit" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="StrTrUsageAsStrReplaceInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="StringBufferToStringInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="StringConcatenationInFormatCall" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="StringConcatenationInMessageFormatCall" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="StringConcatenationMissingWhitespace" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="StringEqualsEmptyString" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="SUPPRESS_FOR_VALUES_WHICH_COULD_BE_NULL" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="StringReplaceableByStringBuffer" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="onlyWarnOnLoop" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="StringToUpperWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SubStrUsedAsStrPosInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="SubtractionInCompareTo" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SuspiciousArrayCast" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SuspiciousLiteralUnderscore" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SwitchStatementWithConfusingDeclaration" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SynchronizationOnStaticField" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SynchronizeOnLock" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SynchronizeOnThis" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_includeNativeMethods" value="true" /> | ||||
|       <option name="ignoreSynchronizedSuperMethods" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="SynchronizedOnLiteralObject" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SystemGC" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SystemGetenv" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SystemProperties" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SystemRunFinalizersOnExit" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="SystemSetSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="TestCaseInProductCode" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="TestCaseWithConstructor" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="TestCaseWithNoTestMethods" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreSupers" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="TestMethodInProductCode" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="TestMethodWithoutAssertion" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="TextLabelInSwitchStatementJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ThreadLocalNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ThreadPriority" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ThreadStartInConstruction" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ThreadStopSuspendResume" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ThreadYield" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="ThrowCaughtLocally" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreRethrownExceptions" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="ThrowRawExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="TimeToString" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="TooBroadScope" enabled="true" level="WEAK WARNING" enabled_by_default="true"> | ||||
|       <option name="m_allowConstructorAsInitializer" value="false" /> | ||||
|       <option name="m_onlyLookAtBlocks" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="TransientFieldInNonSerializableClass" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="TransientFieldNotInitialized" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="TrivialStringConcatenation" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UnNecessaryDoubleQuotesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UnaryPlus" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UnclearBinaryExpression" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="UnconditionalWait" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UnconstrainedVariableType" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UndeclaredTests" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UnknownInspectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="UnnecessarilyQualifiedStaticUsage" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_ignoreStaticFieldAccesses" value="false" /> | ||||
|       <option name="m_ignoreStaticMethodCalls" value="false" /> | ||||
|       <option name="m_ignoreStaticAccessFromStaticContext" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="UnnecessarilyQualifiedStaticallyImportedElement" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UnnecessaryBoxing" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="onlyReportSuperfluouslyBoxed" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="UnnecessaryConstantArrayCreationExpression" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreAnnotations" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="UnnecessaryLocalVariable" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_ignoreImmediatelyReturnedVariables" value="true" /> | ||||
|       <option name="m_ignoreAnnotatedVariables" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="UnnecessaryLocalVariableJS" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_ignoreImmediatelyReturnedVariables" value="true" /> | ||||
|       <option name="m_ignoreAnnotatedVariables" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="UnnecessaryParentheses" enabled="true" level="INFORMATION" enabled_by_default="true"> | ||||
|       <option name="ignoreClarifyingParentheses" value="true" /> | ||||
|       <option name="ignoreParenthesesOnConditionals" value="false" /> | ||||
|       <option name="ignoreParenthesesOnLambdaParameter" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="UnnecessaryToStringCall" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UnnecessaryUnaryMinus" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UnnecessaryUnboxing" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="onlyReportSuperfluouslyUnboxed" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="UnsetConstructsCanBeMergedInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreSemicolonAtEndOfBlock" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="UnusedCatchParameterJS" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="m_ignoreCatchBlocksWithComments" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="UnusedProperty" enabled="false" level="WARNING" enabled_by_default="false" /> | ||||
|     <inspection_tool class="UseOfAWTPeerClass" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UseOfJDBCDriverClass" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UseOfObsoleteAssert" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UseOfObsoleteDateTimeApi" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UseOfProcessBuilder" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UseOfPropertiesAsHashtable" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UseOfSunClasses" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UsingInclusionReturnValueInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UtilityClassWithPublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="UtilityClassWithoutPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignorableAnnotations"> | ||||
|         <value /> | ||||
|       </option> | ||||
|       <option name="ignoreClassesWithOnlyMain" value="true" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="VariableNotUsedInsideIf" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="VariableUseSideOnly" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|     <inspection_tool class="VoidExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="VolatileArrayField" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="WaitCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="WaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="WaitNotifyNotInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="WaitOrAwaitWithoutTimeout" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="WaitWhileHoldingTwoLocks" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="WaitWithoutCorrespondingNotify" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     <inspection_tool class="WhileLoopSpinsOnField" enabled="true" level="WARNING" enabled_by_default="true"> | ||||
|       <option name="ignoreNonEmtpyLoops" value="false" /> | ||||
|     </inspection_tool> | ||||
|     <inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|   </profile> | ||||
| </component> | ||||
							
								
								
									
										6
									
								
								.idea/.idea.TweetDuck/.idea/inspectionProfiles/profiles_settings.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/.idea.TweetDuck/.idea/inspectionProfiles/profiles_settings.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <component name="InspectionProjectProfileManager"> | ||||
|   <settings> | ||||
|     <option name="PROJECT_PROFILE" value="Project" /> | ||||
|     <version value="1.0" /> | ||||
|   </settings> | ||||
| </component> | ||||
							
								
								
									
										6
									
								
								.idea/.idea.TweetDuck/.idea/jsLibraryMappings.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/.idea.TweetDuck/.idea/jsLibraryMappings.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="JavaScriptLibraryMappings"> | ||||
|     <file url="PROJECT" libraries="{@types/jquery}" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										20
									
								
								.idea/.idea.TweetDuck/.idea/runConfigurations/TweetDuck.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								.idea/.idea.TweetDuck/.idea/runConfigurations/TweetDuck.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <component name="ProjectRunConfigurationManager"> | ||||
|   <configuration default="false" name="TweetDuck" type="DotNetProject" factoryName=".NET Project"> | ||||
|     <option name="EXE_PATH" value="$PROJECT_DIR$/windows/TweetDuck/bin/x86/Debug/TweetDuck.exe" /> | ||||
|     <option name="PROGRAM_PARAMETERS" value="-datafolder TweetDuckDebug -nogdpr" /> | ||||
|     <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/windows/TweetDuck/bin/x86/Debug" /> | ||||
|     <option name="PASS_PARENT_ENVS" value="1" /> | ||||
|     <option name="USE_EXTERNAL_CONSOLE" value="0" /> | ||||
|     <option name="USE_MONO" value="0" /> | ||||
|     <option name="RUNTIME_ARGUMENTS" value="" /> | ||||
|     <option name="PROJECT_PATH" value="$PROJECT_DIR$/windows/TweetDuck/TweetDuck.csproj" /> | ||||
|     <option name="PROJECT_EXE_PATH_TRACKING" value="1" /> | ||||
|     <option name="PROJECT_ARGUMENTS_TRACKING" value="1" /> | ||||
|     <option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" /> | ||||
|     <option name="PROJECT_KIND" value="DotNetCore" /> | ||||
|     <option name="PROJECT_TFM" value="net7.0-windows" /> | ||||
|     <method v="2"> | ||||
|       <option name="Build" /> | ||||
|     </method> | ||||
|   </configuration> | ||||
| </component> | ||||
							
								
								
									
										11
									
								
								.idea/.idea.TweetDuck/.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.idea/.idea.TweetDuck/.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="GitSharedSettings"> | ||||
|     <option name="FORCE_PUSH_PROHIBITED_PATTERNS"> | ||||
|       <list /> | ||||
|     </option> | ||||
|   </component> | ||||
|   <component name="VcsDirectoryMappings"> | ||||
|     <mapping directory="$PROJECT_DIR$" vcs="Git" /> | ||||
|   </component> | ||||
| </project> | ||||
| @@ -1,48 +0,0 @@ | ||||
| using System; | ||||
| using TweetDuck.Data; | ||||
| 
 | ||||
| namespace TweetDuck.Configuration{ | ||||
|     static class Arguments{ | ||||
|         // public args | ||||
|         public const string ArgDataFolder = "-datafolder"; | ||||
|         public const string ArgLocale = "-locale"; | ||||
|         public const string ArgLogging = "-log"; | ||||
|         public const string ArgDebugUpdates = "-debugupdates"; | ||||
| 
 | ||||
|         // internal args | ||||
|         public const string ArgRestart = "-restart"; | ||||
|         public const string ArgImportCookies = "-importcookies"; | ||||
|         public const string ArgDeleteCookies = "-deletecookies"; | ||||
|         public const string ArgUpdated = "-updated"; | ||||
| 
 | ||||
|         // class data and methods | ||||
|         private static readonly CommandLineArgs Current = CommandLineArgs.FromStringArray('-', Environment.GetCommandLineArgs()); | ||||
| 
 | ||||
|         public static bool HasFlag(string flag){ | ||||
|             return Current.HasFlag(flag); | ||||
|         } | ||||
| 
 | ||||
|         public static string GetValue(string key, string defaultValue){ | ||||
|             return Current.GetValue(key, defaultValue); | ||||
|         } | ||||
| 
 | ||||
|         public static CommandLineArgs GetCurrentClean(){ | ||||
|             CommandLineArgs args = Current.Clone(); | ||||
|             args.RemoveFlag(ArgRestart); | ||||
|             args.RemoveFlag(ArgImportCookies); | ||||
|             args.RemoveFlag(ArgDeleteCookies); | ||||
|             args.RemoveFlag(ArgUpdated); | ||||
|             return args; | ||||
|         } | ||||
| 
 | ||||
|         public static CommandLineArgs GetCurrentForInstaller(){ | ||||
|             CommandLineArgs args = GetCurrentClean(); | ||||
|             args.AddFlag(ArgUpdated); | ||||
|             return args; | ||||
|         } | ||||
| 
 | ||||
|         public static string GetCurrentForInstallerCmd(){ | ||||
|             return GetCurrentForInstaller().ToString().Replace("\"", "::"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,189 +0,0 @@ | ||||
| using System; | ||||
| using System.ComponentModel; | ||||
| using System.Diagnostics; | ||||
| using System.IO; | ||||
| using System.Threading; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetLib.Communication; | ||||
| 
 | ||||
| namespace TweetDuck.Configuration{ | ||||
|     sealed class LockManager{ | ||||
|         private const int RetryDelay = 250; | ||||
| 
 | ||||
|         public enum Result{ | ||||
|             Success, HasProcess, Fail | ||||
|         } | ||||
| 
 | ||||
|         private readonly string file; | ||||
|         private FileStream lockStream; | ||||
|         private Process lockingProcess; | ||||
| 
 | ||||
|         public LockManager(string file){ | ||||
|             this.file = file; | ||||
|         } | ||||
| 
 | ||||
|         // Lock file | ||||
| 
 | ||||
|         private bool ReleaseLockFileStream(){ | ||||
|             if (lockStream != null){ | ||||
|                 lockStream.Dispose(); | ||||
|                 lockStream = null; | ||||
|                 return true; | ||||
|             } | ||||
|             else{ | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private Result TryCreateLockFile(){ | ||||
|             void CreateLockFileStream(){ | ||||
|                 lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read); | ||||
|                 lockStream.Write(BitConverter.GetBytes(WindowsUtils.CurrentProcessID), 0, sizeof(int)); | ||||
|                 lockStream.Flush(true); | ||||
|             } | ||||
| 
 | ||||
|             try{ | ||||
|                 CreateLockFileStream(); | ||||
|                 return Result.Success; | ||||
|             }catch(DirectoryNotFoundException){ | ||||
|                 try{ | ||||
|                     CreateLockFileStream(); | ||||
|                     return Result.Success; | ||||
|                 }catch{ | ||||
|                     ReleaseLockFileStream(); | ||||
|                     return Result.Fail; | ||||
|                 } | ||||
|             }catch(IOException){ | ||||
|                 return Result.HasProcess; | ||||
|             }catch{ | ||||
|                 ReleaseLockFileStream(); | ||||
|                 return Result.Fail; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Lock management | ||||
| 
 | ||||
|         public Result Lock(){ | ||||
|             if (lockStream != null){ | ||||
|                 return Result.Success; | ||||
|             } | ||||
| 
 | ||||
|             Result initialResult = TryCreateLockFile(); | ||||
| 
 | ||||
|             if (initialResult == Result.HasProcess){ | ||||
|                 try{ | ||||
|                     int pid; | ||||
| 
 | ||||
|                     using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){ | ||||
|                         byte[] bytes = new byte[sizeof(int)]; | ||||
|                         fileStream.Read(bytes, 0, bytes.Length); | ||||
|                         pid = BitConverter.ToInt32(bytes, 0); | ||||
|                     } | ||||
| 
 | ||||
|                     try{ | ||||
|                         Process foundProcess = Process.GetProcessById(pid); | ||||
| 
 | ||||
|                         using(Process currentProcess = Process.GetCurrentProcess()){ | ||||
|                             if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){ | ||||
|                                 lockingProcess = foundProcess; | ||||
|                             } | ||||
|                             else{ | ||||
|                                 foundProcess.Close(); | ||||
|                             } | ||||
|                         } | ||||
|                     }catch{ | ||||
|                         // GetProcessById throws ArgumentException if the process is missing | ||||
|                         // Process.MainModule can throw exceptions in some cases | ||||
|                     } | ||||
| 
 | ||||
|                     return lockingProcess == null ? Result.Fail : Result.HasProcess; | ||||
|                 }catch{ | ||||
|                     return Result.Fail; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return initialResult; | ||||
|         } | ||||
| 
 | ||||
|         public Result LockWait(int timeout){ | ||||
|             for(int elapsed = 0; elapsed < timeout; elapsed += RetryDelay){ | ||||
|                 Result result = Lock(); | ||||
| 
 | ||||
|                 if (result == Result.HasProcess){ | ||||
|                     Thread.Sleep(RetryDelay); | ||||
|                 } | ||||
|                 else{ | ||||
|                     return result; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return Lock(); | ||||
|         } | ||||
| 
 | ||||
|         public bool Unlock(){ | ||||
|             if (ReleaseLockFileStream()){ | ||||
|                 try{ | ||||
|                     File.Delete(file); | ||||
|                 }catch(Exception e){ | ||||
|                     Program.Reporter.Log(e.ToString()); | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         // Locking process | ||||
| 
 | ||||
|         public bool RestoreLockingProcess(int failTimeout){ | ||||
|             if (lockingProcess != null){ | ||||
|                 if (lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray | ||||
|                     Comms.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0); | ||||
| 
 | ||||
|                     if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){ | ||||
|                         return true; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public bool CloseLockingProcess(int closeTimeout, int killTimeout){ | ||||
|             if (lockingProcess != null){ | ||||
|                 try{ | ||||
|                     if (lockingProcess.CloseMainWindow()){ | ||||
|                         WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, RetryDelay); | ||||
|                     } | ||||
| 
 | ||||
|                     if (!lockingProcess.HasExited){ | ||||
|                         lockingProcess.Kill(); | ||||
|                         WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, RetryDelay); | ||||
|                     } | ||||
| 
 | ||||
|                     if (lockingProcess.HasExited){ | ||||
|                         lockingProcess.Dispose(); | ||||
|                         lockingProcess = null; | ||||
|                         return true; | ||||
|                     } | ||||
|                 }catch(Exception ex){ | ||||
|                     if (ex is InvalidOperationException || ex is Win32Exception){ | ||||
|                         if (lockingProcess != null){ | ||||
|                             bool hasExited = CheckLockingProcessExited(); | ||||
|                             lockingProcess.Dispose(); | ||||
|                             return hasExited; | ||||
|                         } | ||||
|                     } | ||||
|                     else throw; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         private bool CheckLockingProcessExited(){ | ||||
|             lockingProcess.Refresh(); | ||||
|             return lockingProcess.HasExited; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,60 +0,0 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetDuck.Data.Serialization; | ||||
| 
 | ||||
| namespace TweetDuck.Configuration{ | ||||
|     sealed class SystemConfig{ | ||||
|         private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>(); | ||||
| 
 | ||||
|         public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) && | ||||
|                                                                       File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll")); | ||||
| 
 | ||||
|         // CONFIGURATION DATA | ||||
| 
 | ||||
|         private bool _hardwareAcceleration = true; | ||||
| 
 | ||||
|         public bool EnableBrowserGCReload { get; set; } = true; | ||||
|         public int BrowserMemoryThreshold { get; set; } = 400; | ||||
| 
 | ||||
|         // SPECIAL PROPERTIES | ||||
|          | ||||
|         public bool HardwareAcceleration{ | ||||
|             get => _hardwareAcceleration && IsHardwareAccelerationSupported; | ||||
|             set => _hardwareAcceleration = value; | ||||
|         } | ||||
| 
 | ||||
|         // END OF CONFIG | ||||
| 
 | ||||
|         private readonly string file; | ||||
|          | ||||
|         private SystemConfig(string file){ | ||||
|             this.file = file; | ||||
|         } | ||||
| 
 | ||||
|         public bool Save(){ | ||||
|             try{ | ||||
|                 WindowsUtils.CreateDirectoryForFile(file); | ||||
|                 Serializer.Write(file, this); | ||||
|                 return true; | ||||
|             }catch(Exception e){ | ||||
|                 Program.Reporter.HandleException("Configuration Error", "Could not save the system configuration file.", true, e); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         public static SystemConfig Load(string file){ | ||||
|             SystemConfig config = new SystemConfig(file); | ||||
|              | ||||
|             try{ | ||||
|                 Serializer.Read(file, config); | ||||
|             }catch(FileNotFoundException){ | ||||
|             }catch(DirectoryNotFoundException){ | ||||
|             }catch(Exception e){ | ||||
|                 Program.Reporter.HandleException("Configuration Error", "Could not open the system configuration file. If you continue, you will lose system specific configuration such as Hardware Acceleration.", true, e); | ||||
|             } | ||||
| 
 | ||||
|             return config; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,234 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Drawing; | ||||
| using System.IO; | ||||
| using TweetDuck.Core; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Notification; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetDuck.Data; | ||||
| using TweetDuck.Data.Serialization; | ||||
| 
 | ||||
| namespace TweetDuck.Configuration{ | ||||
|     sealed class UserConfig{ | ||||
|         private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{ HandleUnknownProperties = HandleUnknownProperties }; | ||||
| 
 | ||||
|         private static void HandleUnknownProperties(UserConfig obj, Dictionary<string, string> data){ | ||||
|             data.Remove("EnableBrowserGCReload"); | ||||
|             data.Remove("BrowserMemoryThreshold"); | ||||
| 
 | ||||
|             if (data.Count == 0){ | ||||
|                 obj.Save(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         static UserConfig(){ | ||||
|             Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter); | ||||
| 
 | ||||
|             Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point>{ | ||||
|                 ConvertToString = value => $"{value.X} {value.Y}", | ||||
|                 ConvertToObject = value => { | ||||
|                     int[] elements = StringUtils.ParseInts(value, ' '); | ||||
|                     return new Point(elements[0], elements[1]); | ||||
|                 } | ||||
|             }); | ||||
|              | ||||
|             Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{ | ||||
|                 ConvertToString = value => $"{value.Width} {value.Height}", | ||||
|                 ConvertToObject = value => { | ||||
|                     int[] elements = StringUtils.ParseInts(value, ' '); | ||||
|                     return new Size(elements[0], elements[1]); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|          | ||||
|         // CONFIGURATION DATA | ||||
| 
 | ||||
|         public bool FirstRun            { get; set; } = true; | ||||
|         public bool AllowDataCollection { get; set; } = false; | ||||
| 
 | ||||
|         public WindowState BrowserWindow { get; set; } = new WindowState(); | ||||
|         public WindowState PluginsWindow { get; set; } = new WindowState(); | ||||
| 
 | ||||
|         public bool ExpandLinksOnHover      { get; set; } = true; | ||||
|         public bool SwitchAccountSelectors  { get; set; } = true; | ||||
|         public bool OpenSearchInFirstColumn { get; set; } = true; | ||||
|         public bool BestImageQuality        { get; set; } = true; | ||||
|         public bool EnableSpellCheck        { get; set; } = false; | ||||
|         public int VideoPlayerVolume        { get; set; } = 50; | ||||
|         private int _zoomLevel                           = 100; | ||||
|         private bool _muteNotifications; | ||||
|          | ||||
|         private TrayIcon.Behavior _trayBehavior       = TrayIcon.Behavior.Disabled; | ||||
|         public bool EnableTrayHighlight { get; set; } = true; | ||||
| 
 | ||||
|         public bool EnableUpdateCheck { get; set; } = true; | ||||
|         public string DismissedUpdate { get; set; } = null; | ||||
| 
 | ||||
|         public bool DisplayNotificationColumn    { get; set; } = false; | ||||
|         public bool NotificationMediaPreviews    { get; set; } = true; | ||||
|         public bool NotificationSkipOnLinkClick  { get; set; } = false; | ||||
|         public bool NotificationNonIntrusiveMode { get; set; } = true; | ||||
|         public int NotificationIdlePauseSeconds  { get; set; } = 0; | ||||
| 
 | ||||
|         public bool DisplayNotificationTimer     { get; set; } = true; | ||||
|         public bool NotificationTimerCountDown   { get; set; } = false; | ||||
|         public int NotificationDurationValue     { get; set; } = 25; | ||||
| 
 | ||||
|         public TweetNotification.Position NotificationPosition { get; set; } = TweetNotification.Position.TopRight; | ||||
|         public Point CustomNotificationPosition                { get; set; } = ControlExtensions.InvisibleLocation; | ||||
|         public int NotificationDisplay                         { get; set; } = 0; | ||||
|         public int NotificationEdgeDistance                    { get; set; } = 8; | ||||
| 
 | ||||
|         public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.Size.Auto; | ||||
|         public Size CustomNotificationSize             { get; set; } = Size.Empty; | ||||
|         public int NotificationScrollSpeed             { get; set; } = 10; | ||||
| 
 | ||||
|         public int NotificationSoundVolume { get; set; } = 100; | ||||
|         private string _notificationSoundPath; | ||||
| 
 | ||||
|         public string CustomCefArgs         { get; set; } = null; | ||||
|         public string CustomBrowserCSS      { get; set; } = null; | ||||
|         public string CustomNotificationCSS { get; set; } = null; | ||||
|          | ||||
|         // SPECIAL PROPERTIES | ||||
| 
 | ||||
|         public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation; | ||||
|         public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty; | ||||
| 
 | ||||
|         public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default; | ||||
| 
 | ||||
|         public string NotificationSoundPath{ | ||||
|             get => string.IsNullOrEmpty(_notificationSoundPath) ? string.Empty : _notificationSoundPath; | ||||
|             set => _notificationSoundPath = value; | ||||
|         } | ||||
| 
 | ||||
|         public bool MuteNotifications{ | ||||
|             get => _muteNotifications; | ||||
| 
 | ||||
|             set{ | ||||
|                 if (_muteNotifications != value){ | ||||
|                     _muteNotifications = value; | ||||
|                     MuteToggled?.Invoke(this, EventArgs.Empty); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public int ZoomLevel{ | ||||
|             get => _zoomLevel; | ||||
| 
 | ||||
|             set{ | ||||
|                 if (_zoomLevel != value){ | ||||
|                     _zoomLevel = value; | ||||
|                     ZoomLevelChanged?.Invoke(this, EventArgs.Empty); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public double ZoomMultiplier => _zoomLevel/100.0; | ||||
| 
 | ||||
|         public TrayIcon.Behavior TrayBehavior{ | ||||
|             get => _trayBehavior; | ||||
| 
 | ||||
|             set{ | ||||
|                 if (_trayBehavior != value){ | ||||
|                     _trayBehavior = value; | ||||
|                     TrayBehaviorChanged?.Invoke(this, EventArgs.Empty); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // EVENTS | ||||
|          | ||||
|         public event EventHandler MuteToggled; | ||||
|         public event EventHandler ZoomLevelChanged; | ||||
|         public event EventHandler TrayBehaviorChanged; | ||||
| 
 | ||||
|         // END OF CONFIG | ||||
|          | ||||
|         private readonly string file; | ||||
| 
 | ||||
|         private UserConfig(string file){ | ||||
|             this.file = file; | ||||
|         } | ||||
| 
 | ||||
|         public bool Save(){ | ||||
|             try{ | ||||
|                 WindowsUtils.CreateDirectoryForFile(file); | ||||
| 
 | ||||
|                 if (File.Exists(file)){ | ||||
|                     string backupFile = GetBackupFile(file); | ||||
|                     File.Delete(backupFile); | ||||
|                     File.Move(file, backupFile); | ||||
|                 } | ||||
| 
 | ||||
|                 Serializer.Write(file, this); | ||||
|                 return true; | ||||
|             }catch(Exception e){ | ||||
|                 Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public bool Reload(){ | ||||
|             try{ | ||||
|                 LoadInternal(false); | ||||
|                 return true; | ||||
|             }catch(FileNotFoundException){ | ||||
|                 try{ | ||||
|                     Serializer.Write(file, new UserConfig(file)); | ||||
|                     LoadInternal(false); | ||||
|                     return true; | ||||
|                 }catch(Exception e){ | ||||
|                     Program.Reporter.HandleException("Configuration Error", "Could not regenerate configuration file.", true, e); | ||||
|                     return false; | ||||
|                 } | ||||
|             }catch(Exception e){ | ||||
|                 Program.Reporter.HandleException("Configuration Error", "Could not reload configuration file.", true, e); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void LoadInternal(bool backup){ | ||||
|             Serializer.Read(backup ? GetBackupFile(file) : file, this); | ||||
|         } | ||||
|          | ||||
|         public static UserConfig Load(string file){ | ||||
|             Exception firstException = null; | ||||
| 
 | ||||
|             for(int attempt = 0; attempt < 2; attempt++){ | ||||
|                 try{ | ||||
|                     UserConfig config = new UserConfig(file); | ||||
|                     config.LoadInternal(attempt > 0); | ||||
|                     return config; | ||||
|                 }catch(FileNotFoundException){ | ||||
|                 }catch(DirectoryNotFoundException){ | ||||
|                     break; | ||||
|                 }catch(Exception e){ | ||||
|                     if (attempt == 0){ | ||||
|                         firstException = e; | ||||
|                         Program.Reporter.Log(e.ToString()); | ||||
|                     } | ||||
|                     else if (firstException is FormatException){ | ||||
|                         Program.Reporter.HandleException("Configuration Error", "The configuration file is outdated or corrupted. If you continue, your program options will be reset.", true, e); | ||||
|                         return new UserConfig(file); | ||||
|                     } | ||||
|                     else if (firstException != null){ | ||||
|                         Program.Reporter.HandleException("Configuration Error", "Could not open the backup configuration file. If you continue, your program options will be reset.", true, e); | ||||
|                         return new UserConfig(file); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (firstException != null){ | ||||
|                 Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException); | ||||
|             } | ||||
| 
 | ||||
|             return new UserConfig(file); | ||||
|         } | ||||
| 
 | ||||
|         public static string GetBackupFile(string file){ | ||||
|             return file+".bak"; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,19 +0,0 @@ | ||||
| using System; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Bridge{ | ||||
|     sealed class CallbackBridge{ | ||||
|         private readonly Control owner; | ||||
|         private readonly Action safeCallback; | ||||
| 
 | ||||
|         public CallbackBridge(Control owner, Action safeCallback){ | ||||
|             this.owner = owner; | ||||
|             this.safeCallback = safeCallback; | ||||
|         } | ||||
| 
 | ||||
|         public void Trigger(){ | ||||
|             owner.InvokeSafe(safeCallback); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,33 +0,0 @@ | ||||
| using System.Text; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Bridge{ | ||||
|     static class PropertyBridge{ | ||||
|         public enum Environment{ | ||||
|             Browser, Notification | ||||
|         } | ||||
| 
 | ||||
|         public static string GenerateScript(Environment environment){ | ||||
|             string Bool(bool value){ | ||||
|                 return value ? "true;" : "false;"; | ||||
|             } | ||||
| 
 | ||||
|             StringBuilder build = new StringBuilder().Append("(function(x){"); | ||||
| 
 | ||||
|             build.Append("x.expandLinksOnHover=").Append(Bool(Program.UserConfig.ExpandLinksOnHover)); | ||||
|              | ||||
|             if (environment == Environment.Browser){ | ||||
|                 build.Append("x.switchAccountSelectors=").Append(Bool(Program.UserConfig.SwitchAccountSelectors)); | ||||
|                 build.Append("x.openSearchInFirstColumn=").Append(Bool(Program.UserConfig.OpenSearchInFirstColumn)); | ||||
|                 build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications)); | ||||
|                 build.Append("x.hasCustomNotificationSound=").Append(Bool(Program.UserConfig.NotificationSoundPath.Length > 0)); | ||||
|                 build.Append("x.notificationMediaPreviews=").Append(Bool(Program.UserConfig.NotificationMediaPreviews)); | ||||
|             } | ||||
| 
 | ||||
|             if (environment == Environment.Notification){ | ||||
|                 build.Append("x.skipOnLinkClick=").Append(Bool(Program.UserConfig.NotificationSkipOnLinkClick)); | ||||
|             } | ||||
|              | ||||
|             return build.Append("})(window.$TDX=window.$TDX||{})").ToString(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,161 +0,0 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using System.Windows.Forms; | ||||
| using CefSharp; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Handling; | ||||
| using TweetDuck.Core.Notification; | ||||
| using TweetDuck.Core.Other; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetDuck.Resources; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Bridge{ | ||||
|     sealed class TweetDeckBridge{ | ||||
|         public static string FontSizeClass; | ||||
|         public static string NotificationHeadContents; | ||||
| 
 | ||||
|         public static string LastHighlightedTweetUrl = string.Empty; | ||||
|         public static string LastHighlightedQuoteUrl = string.Empty; | ||||
|         private static string LastHighlightedTweetAuthors = string.Empty; | ||||
|         private static string LastHighlightedTweetImages = string.Empty; | ||||
| 
 | ||||
|         public static string[] LastHighlightedTweetAuthorsArray => LastHighlightedTweetAuthors.Split(';'); | ||||
|         public static string[] LastHighlightedTweetImagesArray => LastHighlightedTweetImages.Split(';'); | ||||
| 
 | ||||
|         private static readonly Dictionary<string, string> SessionData = new Dictionary<string, string>(2); | ||||
| 
 | ||||
|         public static void ResetStaticProperties(){ | ||||
|             FontSizeClass = NotificationHeadContents = null; | ||||
|             LastHighlightedTweetUrl = LastHighlightedQuoteUrl = LastHighlightedTweetAuthors = LastHighlightedTweetImages = string.Empty; | ||||
|         } | ||||
| 
 | ||||
|         public static void RestoreSessionData(IFrame frame){ | ||||
|             if (SessionData.Count > 0){ | ||||
|                 StringBuilder build = new StringBuilder().Append("window.TD_SESSION={"); | ||||
|                  | ||||
|                 foreach(KeyValuePair<string, string> kvp in SessionData){ | ||||
|                     build.Append(kvp.Key).Append(":'").Append(kvp.Value.Replace("'", "\\'")).Append("',"); | ||||
|                 } | ||||
| 
 | ||||
|                 ScriptLoader.ExecuteScript(frame, build.Append("}").ToString(), "gen:session"); | ||||
|                 SessionData.Clear(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private readonly FormBrowser form; | ||||
|         private readonly FormNotificationMain notification; | ||||
| 
 | ||||
|         public TweetDeckBridge(FormBrowser form, FormNotificationMain notification){ | ||||
|             this.form = form; | ||||
|             this.notification = notification; | ||||
|         } | ||||
| 
 | ||||
|         public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){ | ||||
|             form.InvokeAsyncSafe(() => { | ||||
|                 form.OnIntroductionClosed(showGuide, allowDataCollection); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         public void LoadFontSizeClass(string fsClass){ | ||||
|             form.InvokeAsyncSafe(() => FontSizeClass = fsClass); | ||||
|         } | ||||
| 
 | ||||
|         public void LoadNotificationHeadContents(string headContents){ | ||||
|             form.InvokeAsyncSafe(() => NotificationHeadContents = headContents); | ||||
|         } | ||||
| 
 | ||||
|         public void SetLastRightClickInfo(string type, string link){ | ||||
|             form.InvokeAsyncSafe(() => ContextMenuBase.SetContextInfo(type, link)); | ||||
|         } | ||||
| 
 | ||||
|         public void SetLastHighlightedTweet(string tweetUrl, string quoteUrl, string authors, string imageList){ | ||||
|             form.InvokeAsyncSafe(() => { | ||||
|                 LastHighlightedTweetUrl = tweetUrl; | ||||
|                 LastHighlightedQuoteUrl = quoteUrl; | ||||
|                 LastHighlightedTweetAuthors = authors; | ||||
|                 LastHighlightedTweetImages = imageList; | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         public void OpenContextMenu(){ | ||||
|             form.InvokeAsyncSafe(form.OpenContextMenu); | ||||
|         } | ||||
| 
 | ||||
|         public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){ | ||||
|             notification.InvokeAsyncSafe(() => { | ||||
|                 form.OnTweetNotification(); | ||||
|                 notification.ShowNotification(new TweetNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl)); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         public void OnTweetSound(){ | ||||
|             form.InvokeAsyncSafe(() => { | ||||
|                 form.OnTweetNotification(); | ||||
|                 form.PlayNotificationSound(); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         public void DisplayTooltip(string text, bool showInNotification){ | ||||
|             if (showInNotification){ | ||||
|                 notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text)); | ||||
|             } | ||||
|             else{ | ||||
|                 form.InvokeAsyncSafe(() => form.DisplayTooltip(text)); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void SetSessionData(string key, string value){ | ||||
|             form.InvokeSafe(() => { // do not use InvokeAsyncSafe, return only after invocation | ||||
|                 SessionData.Add(key, value); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         public void LoadNextNotification(){ | ||||
|             notification.InvokeAsyncSafe(notification.FinishCurrentNotification); | ||||
|         } | ||||
| 
 | ||||
|         public void ScreenshotTweet(string html, int width, int height){ | ||||
|             form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height)); | ||||
|         } | ||||
| 
 | ||||
|         public void PlayVideo(string url){ | ||||
|             form.InvokeAsyncSafe(() => form.PlayVideo(url)); | ||||
|         } | ||||
| 
 | ||||
|         public void FixClipboard(){ | ||||
|             form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles); | ||||
|         } | ||||
| 
 | ||||
|         public void OpenBrowser(string url){ | ||||
|             form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url)); | ||||
|         } | ||||
| 
 | ||||
|         public int GetIdleSeconds(){ | ||||
|             return NativeMethods.GetIdleSeconds(); | ||||
|         } | ||||
| 
 | ||||
|         public void Alert(string type, string contents){ | ||||
|             MessageBoxIcon icon; | ||||
| 
 | ||||
|             switch(type){ | ||||
|                 case "error": icon = MessageBoxIcon.Error; break; | ||||
|                 case "warning": icon = MessageBoxIcon.Warning; break; | ||||
|                 case "info": icon = MessageBoxIcon.Information; break; | ||||
|                 default: icon = MessageBoxIcon.None; break; | ||||
|             } | ||||
| 
 | ||||
|             FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK); | ||||
|         } | ||||
| 
 | ||||
|         public void CrashDebug(string message){ | ||||
|             #if DEBUG | ||||
|             Log(message); | ||||
|             System.Diagnostics.Debugger.Break(); | ||||
|             #endif | ||||
|         } | ||||
| 
 | ||||
|         public void Log(string data){ | ||||
|             System.Diagnostics.Debug.WriteLine(data); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,79 +0,0 @@ | ||||
| using System; | ||||
| using System.Drawing; | ||||
| using System.Linq; | ||||
| using System.Windows.Forms; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Controls{ | ||||
|     static class ControlExtensions{ | ||||
|         public static readonly Point InvisibleLocation = new Point(-32000, -32000); | ||||
| 
 | ||||
|         public static void InvokeSafe(this Control control, Action func){ | ||||
|             if (control.InvokeRequired){ | ||||
|                 control.Invoke(func); | ||||
|             } | ||||
|             else{ | ||||
|                 func(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static void InvokeAsyncSafe(this Control control, Action func){ | ||||
|             control.BeginInvoke(func); | ||||
|         } | ||||
| 
 | ||||
|         public static float GetDPIScale(this Control control){ | ||||
|             using(Graphics graphics = control.CreateGraphics()){ | ||||
|                 return graphics.DpiY/96F; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static bool IsFullyOutsideView(this Form form){ | ||||
|             return !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds)); | ||||
|         } | ||||
| 
 | ||||
|         public static void MoveToCenter(this Form targetForm, Form parentForm){ | ||||
|             targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2); | ||||
|         } | ||||
| 
 | ||||
|         public static void SetValueInstant(this ProgressBar bar, int value){ | ||||
|             if (value == bar.Maximum){ | ||||
|                 bar.Value = value; | ||||
|                 bar.Value = value-1; | ||||
|                 bar.Value = value; | ||||
|             } | ||||
|             else{ | ||||
|                 bar.Value = value+1; | ||||
|                 bar.Value = value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static void SetValueSafe(this NumericUpDown numUpDown, int value){ | ||||
|             if (value >= numUpDown.Minimum && value <= numUpDown.Maximum){ | ||||
|                 numUpDown.Value = value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static void SetValueSafe(this TrackBar trackBar, int value){ | ||||
|             if (value >= trackBar.Minimum && value <= trackBar.Maximum){ | ||||
|                 trackBar.Value = value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static bool AlignValueToTick(this TrackBar trackBar){ | ||||
|             if (trackBar.Value % trackBar.SmallChange != 0){ | ||||
|                 trackBar.Value = trackBar.SmallChange*(int)Math.Floor(((double)trackBar.Value/trackBar.SmallChange)+0.5); | ||||
|                 return false; | ||||
|             } | ||||
|             else return true; | ||||
|         } | ||||
| 
 | ||||
|         public static void EnableMultilineShortcuts(this TextBox textBox){ | ||||
|             textBox.KeyDown += (sender, args) => { | ||||
|                 if (args.Control && args.KeyCode == Keys.A){ | ||||
|                     ((TextBox)sender).SelectAll(); | ||||
|                     args.SuppressKeyPress = true; | ||||
|                     args.Handled = true; | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| using System; | ||||
| using System.Windows.Forms; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Controls{ | ||||
|     sealed class FlatButton : Button{ | ||||
|         protected override bool ShowFocusCues => false; | ||||
| 
 | ||||
|         public FlatButton(){ | ||||
|             GotFocus += FlatButton_GotFocus; | ||||
|         } | ||||
| 
 | ||||
|         private void FlatButton_GotFocus(object sender, EventArgs e){ // removes extra border when focused | ||||
|             NotifyDefault(false); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,37 +0,0 @@ | ||||
| using System.Drawing; | ||||
| using System.Windows.Forms; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Controls{ | ||||
|     sealed class FlatProgressBar : ProgressBar{ | ||||
|         private readonly SolidBrush brush; | ||||
| 
 | ||||
|         public FlatProgressBar(){ | ||||
|             brush = new SolidBrush(Color.White); | ||||
| 
 | ||||
|             SetStyle(ControlStyles.UserPaint, true); | ||||
|             SetStyle(ControlStyles.OptimizedDoubleBuffer, true); | ||||
|         } | ||||
| 
 | ||||
|         public void SetValueInstant(int value){ | ||||
|             ControlExtensions.SetValueInstant(this, value); | ||||
|         } | ||||
| 
 | ||||
|         protected override void OnPaint(PaintEventArgs e){ | ||||
|             if (brush.Color != ForeColor){ | ||||
|                 brush.Color = ForeColor; | ||||
|             } | ||||
| 
 | ||||
|             Rectangle rect = e.ClipRectangle; | ||||
|             rect.Width = (int)(rect.Width*((double)Value/Maximum)); | ||||
|             e.Graphics.FillRectangle(brush, rect); | ||||
|         } | ||||
| 
 | ||||
|         protected override void Dispose(bool disposing){ | ||||
|             base.Dispose(disposing); | ||||
| 
 | ||||
|             if (disposing){ | ||||
|                 brush.Dispose(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,23 +0,0 @@ | ||||
| using System; | ||||
| using System.Drawing; | ||||
| using System.Windows.Forms; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Controls{ | ||||
|     sealed class LabelVertical : Label{ | ||||
|         public int LineHeight { get; set; } | ||||
| 
 | ||||
|         protected override void OnPaint(PaintEventArgs e){ | ||||
|             int y = (int)Math.Floor((ClientRectangle.Height-Text.Length*LineHeight)/2F)-2; // 2 = random | ||||
| 
 | ||||
|             using(Brush brush = new SolidBrush(ForeColor)){ | ||||
|                 foreach(char chr in Text){ | ||||
|                     string str = chr.ToString(); | ||||
|                     float x = (ClientRectangle.Width-e.Graphics.MeasureString(str, Font).Width)/2F; | ||||
| 
 | ||||
|                     e.Graphics.DrawString(str, Font, brush, x, y); | ||||
|                     y += LineHeight; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| using System.ComponentModel; | ||||
| using System.Windows.Forms; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Controls{ | ||||
|     sealed class NumericUpDownEx : NumericUpDown{ | ||||
|         public string TextSuffix { get; set ; } | ||||
| 
 | ||||
|         protected override void UpdateEditText(){ | ||||
|             base.UpdateEditText(); | ||||
| 
 | ||||
|             if (LicenseManager.UsageMode != LicenseUsageMode.Designtime){ | ||||
|                 ChangingText = true; | ||||
|                 Text += TextSuffix; | ||||
|                 ChangingText = false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,612 +0,0 @@ | ||||
| using CefSharp; | ||||
| using CefSharp.WinForms; | ||||
| using System; | ||||
| using System.Drawing; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Configuration; | ||||
| using TweetDuck.Core.Bridge; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Handling; | ||||
| using TweetDuck.Core.Handling.General; | ||||
| using TweetDuck.Core.Notification; | ||||
| using TweetDuck.Core.Notification.Screenshot; | ||||
| using TweetDuck.Core.Other; | ||||
| using TweetDuck.Core.Other.Management; | ||||
| using TweetDuck.Core.Other.Settings; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetDuck.Plugins; | ||||
| using TweetDuck.Plugins.Enums; | ||||
| using TweetDuck.Plugins.Events; | ||||
| using TweetDuck.Resources; | ||||
| using TweetDuck.Updates; | ||||
| using TweetLib.Audio; | ||||
| 
 | ||||
| namespace TweetDuck.Core{ | ||||
|     sealed partial class FormBrowser : Form{ | ||||
|         private static UserConfig Config => Program.UserConfig; | ||||
| 
 | ||||
|         public bool IsWaiting{ | ||||
|             set{ | ||||
|                 if (value){ | ||||
|                     browser.Enabled = false; | ||||
|                     Cursor = Cursors.WaitCursor; | ||||
|                 } | ||||
|                 else{ | ||||
|                     browser.Enabled = true; | ||||
|                     Cursor = Cursors.Default; | ||||
| 
 | ||||
|                     if (Focused){ // re-focus browser only if the window or a child is activated | ||||
|                         browser.Focus(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public string UpdateInstallerPath { get; private set; } | ||||
| 
 | ||||
|         private readonly ChromiumWebBrowser browser; | ||||
|         private readonly PluginManager plugins; | ||||
|         private readonly UpdateHandler updates; | ||||
|         private readonly FormNotificationTweet notification; | ||||
|         private readonly ContextMenu contextMenu; | ||||
|         private readonly MemoryUsageTracker memoryUsageTracker; | ||||
| 
 | ||||
|         private bool isLoaded; | ||||
|         private bool isBrowserReady; | ||||
|         private FormWindowState prevState; | ||||
| 
 | ||||
|         private TweetScreenshotManager notificationScreenshotManager; | ||||
|         private SoundNotification soundNotification; | ||||
|         private VideoPlayer videoPlayer; | ||||
| 
 | ||||
|         public FormBrowser(UpdaterSettings updaterSettings){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             Text = Program.BrandName; | ||||
| 
 | ||||
|             this.plugins = new PluginManager(Program.PluginPath, Program.PluginConfigFilePath); | ||||
|             this.plugins.Reloaded += plugins_Reloaded; | ||||
|             this.plugins.Executed += plugins_Executed; | ||||
|             this.plugins.PluginChangedState += plugins_PluginChangedState; | ||||
|             this.plugins.Reload(); | ||||
| 
 | ||||
|             this.contextMenu = ContextMenuBrowser.CreateMenu(this); | ||||
|             this.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup"); | ||||
| 
 | ||||
|             this.notification = new FormNotificationTweet(this, plugins){ | ||||
|                 #if DEBUG | ||||
|                 CanMoveWindow = () => (ModifierKeys & Keys.Alt) == Keys.Alt | ||||
|                 #else | ||||
|                 CanMoveWindow = () => false | ||||
|                 #endif | ||||
|             }; | ||||
| 
 | ||||
|             this.notification.Show(); | ||||
| 
 | ||||
|             this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){ | ||||
|                 DialogHandler = new FileDialogHandler(), | ||||
|                 DragHandler = new DragHandlerBrowser(), | ||||
|                 MenuHandler = new ContextMenuBrowser(this), | ||||
|                 JsDialogHandler = new JavaScriptDialogHandler(), | ||||
|                 KeyboardHandler = new KeyboardHandlerBrowser(this), | ||||
|                 LifeSpanHandler = new LifeSpanHandler(), | ||||
|                 RequestHandler = new RequestHandlerBrowser() | ||||
|             }; | ||||
| 
 | ||||
|             #if DEBUG | ||||
|             this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage; | ||||
|             #endif | ||||
| 
 | ||||
|             this.browser.LoadingStateChanged += browser_LoadingStateChanged; | ||||
|             this.browser.FrameLoadStart += browser_FrameLoadStart; | ||||
|             this.browser.FrameLoadEnd += browser_FrameLoadEnd; | ||||
|             this.browser.LoadError += browser_LoadError; | ||||
|             this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification)); | ||||
|             this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge); | ||||
|              | ||||
|             browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb(); | ||||
|             browser.Dock = DockStyle.None; | ||||
|             browser.Location = ControlExtensions.InvisibleLocation; | ||||
|             Controls.Add(browser); | ||||
| 
 | ||||
|             Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update | ||||
| 
 | ||||
|             Disposed += (sender, args) => { | ||||
|                 memoryUsageTracker.Dispose(); | ||||
| 
 | ||||
|                 browser.Dispose(); | ||||
|                 contextMenu.Dispose(); | ||||
| 
 | ||||
|                 notificationScreenshotManager?.Dispose(); | ||||
|                 soundNotification?.Dispose(); | ||||
|                 videoPlayer?.Dispose(); | ||||
|             }; | ||||
| 
 | ||||
|             this.trayIcon.ClickRestore += trayIcon_ClickRestore; | ||||
|             this.trayIcon.ClickClose += trayIcon_ClickClose; | ||||
|             Config.TrayBehaviorChanged += Config_TrayBehaviorChanged; | ||||
| 
 | ||||
|             UpdateTrayIcon(); | ||||
| 
 | ||||
|             Config.MuteToggled += Config_MuteToggled; | ||||
|             Config.ZoomLevelChanged += Config_ZoomLevelChanged; | ||||
| 
 | ||||
|             this.updates = new UpdateHandler(browser, updaterSettings); | ||||
|             this.updates.UpdateAccepted += updates_UpdateAccepted; | ||||
|             this.updates.UpdateDismissed += updates_UpdateDismissed; | ||||
| 
 | ||||
|             RestoreWindow(); | ||||
|         } | ||||
| 
 | ||||
|         private void ShowChildForm(Form form){ | ||||
|             form.VisibleChanged += (sender, args) => form.MoveToCenter(this); | ||||
|             form.Show(this); | ||||
|         } | ||||
| 
 | ||||
|         public void ForceClose(){ | ||||
|             trayIcon.Visible = false; // checked in FormClosing event | ||||
|             Close(); | ||||
|         } | ||||
| 
 | ||||
|         // window setup | ||||
| 
 | ||||
|         private void RestoreWindow(){ | ||||
|             Config.BrowserWindow.Restore(this, true); | ||||
|             prevState = WindowState; | ||||
|             isLoaded = true; | ||||
|         } | ||||
| 
 | ||||
|         private void OnBrowserReady(){ | ||||
|             if (!isBrowserReady){ | ||||
|                 browser.Location = Point.Empty; | ||||
|                 browser.Dock = DockStyle.Fill; | ||||
|                 isBrowserReady = true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void UpdateTrayIcon(){ | ||||
|             trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon(); | ||||
|         } | ||||
| 
 | ||||
|         // active event handlers | ||||
| 
 | ||||
|         private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){ | ||||
|             if (!e.IsLoading){ | ||||
|                 foreach(string word in TwitterUtils.DictionaryWords){ | ||||
|                     browser.AddWordToDictionary(word); | ||||
|                 } | ||||
| 
 | ||||
|                 BeginInvoke(new Action(OnBrowserReady)); | ||||
|                 browser.LoadingStateChanged -= browser_LoadingStateChanged; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){ | ||||
|             if (e.Frame.IsMain){ | ||||
|                 memoryUsageTracker.Stop(); | ||||
| 
 | ||||
|                 if (Config.ZoomLevel != 100){ | ||||
|                     BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel); | ||||
|                 } | ||||
| 
 | ||||
|                 if (TwitterUtils.IsTwitterWebsite(e.Frame)){ | ||||
|                     ScriptLoader.ExecuteFile(e.Frame, "twitter.js"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ | ||||
|             if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){ | ||||
|                 e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix); | ||||
| 
 | ||||
|                 UpdateProperties(PropertyBridge.Environment.Browser); | ||||
|                 TweetDeckBridge.RestoreSessionData(e.Frame); | ||||
|                 ScriptLoader.ExecuteFile(e.Frame, "code.js"); | ||||
|                 InjectBrowserCSS(); | ||||
|                 ReinjectCustomCSS(Config.CustomBrowserCSS); | ||||
|                 plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser); | ||||
| 
 | ||||
|                 TweetDeckBridge.ResetStaticProperties(); | ||||
| 
 | ||||
|                 if (Program.SystemConfig.EnableBrowserGCReload){ | ||||
|                     memoryUsageTracker.Start(this, e.Browser, Program.SystemConfig.BrowserMemoryThreshold); | ||||
|                 } | ||||
| 
 | ||||
|                 if (Config.FirstRun){ | ||||
|                     ScriptLoader.ExecuteFile(e.Frame, "introduction.js"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void browser_LoadError(object sender, LoadErrorEventArgs e){ | ||||
|             if (e.ErrorCode == CefErrorCode.Aborted){ | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){ | ||||
|                 string errorPage = ScriptLoader.LoadResource("pages/error.html", true); | ||||
| 
 | ||||
|                 if (errorPage != null){ | ||||
|                     browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void timerResize_Tick(object sender, EventArgs e){ | ||||
|             FormBrowser_ResizeEnd(this, e); // also stops timer | ||||
|         } | ||||
| 
 | ||||
|         private void FormBrowser_Activated(object sender, EventArgs e){ | ||||
|             if (!isLoaded)return; | ||||
| 
 | ||||
|             trayIcon.HasNotifications = false; | ||||
| 
 | ||||
|             if (!browser.Enabled){      // when taking a screenshot, the window is unfocused and | ||||
|                 browser.Enabled = true; // the browser is disabled; if the user clicks back into | ||||
|             }                           // the window, enable the browser again | ||||
|         } | ||||
| 
 | ||||
|         private void FormBrowser_LocationChanged(object sender, EventArgs e){ | ||||
|             if (!isLoaded)return; | ||||
|              | ||||
|             timerResize.Stop(); | ||||
|             timerResize.Start(); | ||||
|         } | ||||
| 
 | ||||
|         private void FormBrowser_Resize(object sender, EventArgs e){ | ||||
|             if (!isLoaded)return; | ||||
| 
 | ||||
|             if (WindowState != prevState){ | ||||
|                 prevState = WindowState; | ||||
| 
 | ||||
|                 if (WindowState == FormWindowState.Minimized){ | ||||
|                     if (Config.TrayBehavior.ShouldHideOnMinimize()){ | ||||
|                         Hide(); // hides taskbar too?! welp that works I guess | ||||
|                     } | ||||
|                 } | ||||
|                 else{ | ||||
|                     FormBrowser_ResizeEnd(sender, e); | ||||
|                 } | ||||
|             } | ||||
|             else{ | ||||
|                 timerResize.Stop(); | ||||
|                 timerResize.Start(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves | ||||
|             if (!isLoaded)return; | ||||
| 
 | ||||
|             timerResize.Stop(); | ||||
|              | ||||
|             if (Location != ControlExtensions.InvisibleLocation){ | ||||
|                 Config.BrowserWindow.Save(this); | ||||
|                 Config.Save(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e){ | ||||
|             if (!isLoaded)return; | ||||
| 
 | ||||
|             if (Config.TrayBehavior.ShouldHideOnClose() && trayIcon.Visible && e.CloseReason == CloseReason.UserClosing){ | ||||
|                 Hide(); // hides taskbar too?! welp that works I guess | ||||
|                 e.Cancel = true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e){ | ||||
|             if (isLoaded && UpdateInstallerPath == null){ | ||||
|                 updates.CleanupDownload(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void Config_MuteToggled(object sender, EventArgs e){ | ||||
|             UpdateProperties(PropertyBridge.Environment.Browser); | ||||
|         } | ||||
| 
 | ||||
|         private void Config_ZoomLevelChanged(object sender, EventArgs e){ | ||||
|             BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel); | ||||
|         } | ||||
| 
 | ||||
|         private void Config_TrayBehaviorChanged(object sender, EventArgs e){ | ||||
|             UpdateTrayIcon(); | ||||
|         } | ||||
| 
 | ||||
|         private void trayIcon_ClickRestore(object sender, EventArgs e){ | ||||
|             Show(); | ||||
|             RestoreWindow(); | ||||
|             Activate(); | ||||
|             UpdateTrayIcon(); | ||||
|         } | ||||
| 
 | ||||
|         private void trayIcon_ClickClose(object sender, EventArgs e){ | ||||
|             ForceClose(); | ||||
|         } | ||||
|          | ||||
|         private void plugins_Reloaded(object sender, PluginErrorEventArgs e){ | ||||
|             if (e.HasErrors){ | ||||
|                 FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK); | ||||
|             } | ||||
| 
 | ||||
|             if (isLoaded){ | ||||
|                 ReloadToTweetDeck(); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         private static void plugins_Executed(object sender, PluginErrorEventArgs e){ | ||||
|             if (e.HasErrors){ | ||||
|                 FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){ | ||||
|             browser.ExecuteScriptAsync("window.TDPF_setPluginState", e.Plugin, e.IsEnabled); | ||||
|         } | ||||
| 
 | ||||
|         private void updates_UpdateAccepted(object sender, UpdateEventArgs e){ | ||||
|             this.InvokeAsyncSafe(() => { | ||||
|                 FormManager.CloseAllDialogs(); | ||||
|              | ||||
|                 updates.BeginUpdateDownload(this, e.UpdateInfo, update => { | ||||
|                     if (update.DownloadStatus == UpdateDownloadStatus.Done){ | ||||
|                         UpdateInstallerPath = update.InstallerPath; | ||||
|                     } | ||||
| 
 | ||||
|                     ForceClose(); | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         private void updates_UpdateDismissed(object sender, UpdateEventArgs e){ | ||||
|             this.InvokeAsyncSafe(() => { | ||||
|                 Config.DismissedUpdate = e.UpdateInfo.VersionTag; | ||||
|                 Config.Save(); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         private void soundNotification_PlaybackError(object sender, PlaybackErrorEventArgs e){ | ||||
|             e.Ignore = true; | ||||
| 
 | ||||
|             using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound.\n"+e.Message, MessageBoxIcon.Error)){ | ||||
|                 form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused); | ||||
| 
 | ||||
|                 Button btnOpenSettings = form.AddButton("View Options"); | ||||
|                 btnOpenSettings.Width += 16; | ||||
|                 btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y); | ||||
| 
 | ||||
|                 if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){ | ||||
|                     OpenSettings(typeof(TabSettingsSounds)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected override void WndProc(ref Message m){ | ||||
|             if (isLoaded){ | ||||
|                 if (m.Msg == Program.WindowRestoreMessage){ | ||||
|                     if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){ | ||||
|                         trayIcon_ClickRestore(trayIcon, EventArgs.Empty); | ||||
|                     } | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 else if (m.Msg == Program.SubProcessMessage){ | ||||
|                     int processId = m.WParam.ToInt32(); | ||||
| 
 | ||||
|                     if (WindowsUtils.IsChildProcess(processId)){ // child process is checked in two places for safety | ||||
|                         BrowserProcesses.Link(m.LParam.ToInt32(), processId); | ||||
|                     } | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){ | ||||
|                 if (videoPlayer != null && videoPlayer.Running){ | ||||
|                     videoPlayer.Close(); | ||||
|                 } | ||||
|                 else{ | ||||
|                     browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF); | ||||
|                 } | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             base.WndProc(ref m); | ||||
|         } | ||||
| 
 | ||||
|         // notification helpers | ||||
| 
 | ||||
|         public FormNotificationMain CreateNotificationForm(bool enableContextMenu){ | ||||
|             return new FormNotificationMain(this, plugins, enableContextMenu); | ||||
|         } | ||||
| 
 | ||||
|         public void PauseNotification(){ | ||||
|             notification.PauseNotification(); | ||||
|         } | ||||
| 
 | ||||
|         public void ResumeNotification(){ | ||||
|             notification.ResumeNotification(); | ||||
|         } | ||||
| 
 | ||||
|         // javascript calls | ||||
| 
 | ||||
|         public void InjectBrowserCSS(){ | ||||
|             browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css").TrimEnd()); | ||||
|         } | ||||
| 
 | ||||
|         public void ReinjectCustomCSS(string css){ | ||||
|             browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty); | ||||
|         } | ||||
| 
 | ||||
|         public void UpdateProperties(PropertyBridge.Environment environment){ | ||||
|             browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(environment)); | ||||
|         } | ||||
| 
 | ||||
|         public void ReloadToTweetDeck(){ | ||||
|             browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'"); | ||||
|         } | ||||
| 
 | ||||
|         // callback handlers | ||||
|          | ||||
|         public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){ | ||||
|             if (Config.FirstRun){ | ||||
|                 Config.FirstRun = false; | ||||
|                 Config.AllowDataCollection = allowDataCollection; | ||||
|                 Config.Save(); | ||||
|             } | ||||
| 
 | ||||
|             if (showGuide){ | ||||
|                 ShowChildForm(new FormGuide()); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void OpenContextMenu(){ | ||||
|             contextMenu.Show(this, PointToClient(Cursor.Position)); | ||||
|         } | ||||
| 
 | ||||
|         public void OpenSettings(){ | ||||
|             OpenSettings(null); | ||||
|         } | ||||
| 
 | ||||
|         public void OpenSettings(Type startTab){ | ||||
|             if (!FormManager.TryBringToFront<FormSettings>()){ | ||||
|                 bool prevEnableUpdateCheck = Config.EnableUpdateCheck; | ||||
| 
 | ||||
|                 FormSettings form = new FormSettings(this, plugins, updates, startTab); | ||||
| 
 | ||||
|                 form.FormClosed += (sender, args) => { | ||||
|                     if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){ | ||||
|                         Config.DismissedUpdate = null; | ||||
|                         Config.Save(); | ||||
|                          | ||||
|                         updates.Check(true); | ||||
|                     } | ||||
| 
 | ||||
|                     if (!Config.EnableTrayHighlight){ | ||||
|                         trayIcon.HasNotifications = false; | ||||
|                     } | ||||
| 
 | ||||
|                     if (Program.SystemConfig.EnableBrowserGCReload){ | ||||
|                         memoryUsageTracker.Start(this, browser.GetBrowser(), Program.SystemConfig.BrowserMemoryThreshold); | ||||
|                     } | ||||
|                     else{ | ||||
|                         memoryUsageTracker.Stop(); | ||||
|                     } | ||||
|                      | ||||
|                     if (form.ShouldReloadBrowser){ | ||||
|                         FormManager.TryFind<FormPlugins>()?.Close(); | ||||
|                         plugins.Reload(); // also reloads the browser | ||||
|                     } | ||||
|                     else{ | ||||
|                         UpdateProperties(PropertyBridge.Environment.Browser); | ||||
|                     } | ||||
| 
 | ||||
|                     notification.RequiresResize = true; | ||||
|                     form.Dispose(); | ||||
|                 }; | ||||
| 
 | ||||
|                 ShowChildForm(form); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void OpenAbout(){ | ||||
|             if (!FormManager.TryBringToFront<FormAbout>()){ | ||||
|                 ShowChildForm(new FormAbout()); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void OpenPlugins(){ | ||||
|             if (!FormManager.TryBringToFront<FormPlugins>()){ | ||||
|                 ShowChildForm(new FormPlugins(plugins)); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void OnTweetNotification(){ // may be called multiple times, once for each type of notification | ||||
|             if (Config.EnableTrayHighlight && !ContainsFocus){ | ||||
|                 trayIcon.HasNotifications = true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void PlayNotificationSound(){ | ||||
|             if (Config.NotificationSoundPath.Length == 0){ | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (soundNotification == null){ | ||||
|                 soundNotification = new SoundNotification(); | ||||
|                 soundNotification.PlaybackError += soundNotification_PlaybackError; | ||||
|             } | ||||
| 
 | ||||
|             soundNotification.SetVolume(Config.NotificationSoundVolume); | ||||
|             soundNotification.Play(Config.NotificationSoundPath); | ||||
|         } | ||||
| 
 | ||||
|         public void PlayVideo(string url){ | ||||
|             if (string.IsNullOrEmpty(url)){ | ||||
|                 videoPlayer?.Close(); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (videoPlayer == null){ | ||||
|                 videoPlayer = new VideoPlayer(this); | ||||
| 
 | ||||
|                 videoPlayer.ProcessExited += (sender, args) => { | ||||
|                     browser.GetBrowser().GetHost().SendFocusEvent(true); | ||||
|                     HideVideoOverlay(); | ||||
|                 }; | ||||
|             } | ||||
|              | ||||
|             videoPlayer.Launch(url); | ||||
|         } | ||||
| 
 | ||||
|         public void HideVideoOverlay(){ | ||||
|             browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()"); | ||||
|         } | ||||
| 
 | ||||
|         public bool ProcessBrowserKey(Keys key){ | ||||
|             if (videoPlayer != null && videoPlayer.Running){ | ||||
|                 videoPlayer.SendKeyEvent(key); | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){ | ||||
|             Activate(); | ||||
| 
 | ||||
|             using(IFrame frame = browser.GetBrowser().MainFrame){ | ||||
|                 if (!TwitterUtils.IsTweetDeckWebsite(frame)){ | ||||
|                     FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             notification.FinishCurrentNotification(); | ||||
|             browser.ExecuteScriptAsync("window.TDGF_showTweetDetail", columnId, chirpId, fallbackUrl); | ||||
|         } | ||||
| 
 | ||||
|         public void OnTweetScreenshotReady(string html, int width, int height){ | ||||
|             if (notificationScreenshotManager == null){ | ||||
|                 notificationScreenshotManager = new TweetScreenshotManager(this, plugins); | ||||
|             } | ||||
| 
 | ||||
|             notificationScreenshotManager.Trigger(html, width, height); | ||||
|         } | ||||
| 
 | ||||
|         public void DisplayTooltip(string text){ | ||||
|             if (string.IsNullOrEmpty(text)){ | ||||
|                 toolTip.Hide(this); | ||||
|             } | ||||
|             else{ | ||||
|                 Point position = PointToClient(Cursor.Position); | ||||
|                 position.Offset(20, 10); | ||||
|                 toolTip.Show(text, this, position); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void TriggerTweetScreenshot(){ | ||||
|             browser.ExecuteScriptAsync("TDGF_triggerScreenshot()"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| using System.Linq; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Other; | ||||
| 
 | ||||
| namespace TweetDuck.Core{ | ||||
|     static class FormManager{ | ||||
|         public static T TryFind<T>() where T : Form{ | ||||
|             return Application.OpenForms.OfType<T>().FirstOrDefault(); | ||||
|         } | ||||
| 
 | ||||
|         public static bool TryBringToFront<T>() where T : Form{ | ||||
|             T form = TryFind<T>(); | ||||
| 
 | ||||
|             if (form != null){ | ||||
|                 form.BringToFront(); | ||||
|                 return true; | ||||
|             } | ||||
|             else return false; | ||||
|         } | ||||
| 
 | ||||
|         public static void CloseAllDialogs(){ | ||||
|             foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){ | ||||
|                 if (form is FormSettings || form is FormPlugins || form is FormAbout || form is FormGuide){ | ||||
|                     form.Close(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,171 +0,0 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Text.RegularExpressions; | ||||
| using System.Windows.Forms; | ||||
| using CefSharp; | ||||
| using TweetDuck.Core.Bridge; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Utils; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling{ | ||||
|     abstract class ContextMenuBase : IContextMenuHandler{ | ||||
|         protected static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak")); | ||||
| 
 | ||||
|         private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality; | ||||
|          | ||||
|         private static KeyValuePair<string, string> ContextInfo; | ||||
|         private static bool IsLink => ContextInfo.Key == "link"; | ||||
|         private static bool IsImage => ContextInfo.Key == "image"; | ||||
|         private static bool IsVideo => ContextInfo.Key == "video"; | ||||
| 
 | ||||
|         public static void SetContextInfo(string type, string link){ | ||||
|             ContextInfo = new KeyValuePair<string, string>(string.IsNullOrEmpty(link) ? null : type, link); | ||||
|         } | ||||
| 
 | ||||
|         private static string GetMediaLink(IContextMenuParams parameters){ | ||||
|             return IsImage || IsVideo ? ContextInfo.Value : parameters.SourceUrl; | ||||
|         } | ||||
| 
 | ||||
|         private const CefMenuCommand MenuOpenLinkUrl     = (CefMenuCommand)26500; | ||||
|         private const CefMenuCommand MenuCopyLinkUrl     = (CefMenuCommand)26501; | ||||
|         private const CefMenuCommand MenuCopyUsername    = (CefMenuCommand)26502; | ||||
|         private const CefMenuCommand MenuOpenMediaUrl    = (CefMenuCommand)26503; | ||||
|         private const CefMenuCommand MenuCopyMediaUrl    = (CefMenuCommand)26504; | ||||
|         private const CefMenuCommand MenuSaveMedia       = (CefMenuCommand)26505; | ||||
|         private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26506; | ||||
|         private const CefMenuCommand MenuOpenDevTools    = (CefMenuCommand)26599; | ||||
|          | ||||
|         private string[] lastHighlightedTweetAuthors; | ||||
|         private string[] lastHighlightedTweetImageList; | ||||
| 
 | ||||
|         public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ | ||||
|             if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){ | ||||
|                 lastHighlightedTweetAuthors = StringUtils.EmptyArray; | ||||
|                 lastHighlightedTweetImageList = StringUtils.EmptyArray; | ||||
|                 ContextInfo = default(KeyValuePair<string, string>); | ||||
|             } | ||||
|             else{ | ||||
|                 lastHighlightedTweetAuthors = TweetDeckBridge.LastHighlightedTweetAuthorsArray; | ||||
|                 lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImagesArray; | ||||
|             } | ||||
| 
 | ||||
|             bool hasTweetImage = IsImage; | ||||
|             bool hasTweetVideo = IsVideo; | ||||
| 
 | ||||
|             string TextOpen(string name) => "Open "+name+" in browser"; | ||||
|             string TextCopy(string name) => "Copy "+name+" address"; | ||||
|             string TextSave(string name) => "Save "+name+" as..."; | ||||
|              | ||||
|             if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal) && !hasTweetImage && !hasTweetVideo){ | ||||
|                 if (TwitterUtils.RegexAccount.IsMatch(parameters.UnfilteredLinkUrl)){ | ||||
|                     model.AddItem(MenuOpenLinkUrl, TextOpen("account")); | ||||
|                     model.AddItem(MenuCopyLinkUrl, TextCopy("account")); | ||||
|                     model.AddItem(MenuCopyUsername, "Copy account username"); | ||||
|                 } | ||||
|                 else{ | ||||
|                     model.AddItem(MenuOpenLinkUrl, TextOpen("link")); | ||||
|                     model.AddItem(MenuCopyLinkUrl, TextCopy("link")); | ||||
|                 } | ||||
| 
 | ||||
|                 model.AddSeparator(); | ||||
|             } | ||||
| 
 | ||||
|             if (hasTweetVideo){ | ||||
|                 model.AddItem(MenuOpenMediaUrl, TextOpen("video")); | ||||
|                 model.AddItem(MenuCopyMediaUrl, TextCopy("video")); | ||||
|                 model.AddItem(MenuSaveMedia, TextSave("video")); | ||||
|                 model.AddSeparator(); | ||||
|             } | ||||
|             else if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){ | ||||
|                 model.AddItem(MenuOpenMediaUrl, TextOpen("image")); | ||||
|                 model.AddItem(MenuCopyMediaUrl, TextCopy("image")); | ||||
|                 model.AddItem(MenuSaveMedia, TextSave("image")); | ||||
| 
 | ||||
|                 if (lastHighlightedTweetImageList.Length > 1){ | ||||
|                     model.AddItem(MenuSaveTweetImages, TextSave("all images")); | ||||
|                 } | ||||
| 
 | ||||
|                 model.AddSeparator(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ | ||||
|             switch(commandId){ | ||||
|                 case MenuOpenLinkUrl: | ||||
|                     OpenBrowser(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.LinkUrl); | ||||
|                     break; | ||||
| 
 | ||||
|                 case MenuCopyLinkUrl: | ||||
|                     SetClipboardText(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl); | ||||
|                     break; | ||||
| 
 | ||||
|                 case MenuCopyUsername: | ||||
|                     Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl); | ||||
|                     SetClipboardText(browserControl.AsControl(), match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl); | ||||
|                     break; | ||||
| 
 | ||||
|                 case MenuOpenMediaUrl: | ||||
|                     OpenBrowser(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality)); | ||||
|                     break; | ||||
| 
 | ||||
|                 case MenuCopyMediaUrl: | ||||
|                     SetClipboardText(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality)); | ||||
|                     break; | ||||
| 
 | ||||
|                 case MenuSaveMedia: | ||||
|                     if (IsVideo){ | ||||
|                         TwitterUtils.DownloadVideo(GetMediaLink(parameters)); | ||||
|                     } | ||||
|                     else{ | ||||
|                         TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality); | ||||
|                     } | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case MenuSaveTweetImages: | ||||
|                     TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality); | ||||
|                     break; | ||||
|                      | ||||
|                 case MenuOpenDevTools: | ||||
|                     browserControl.ShowDevTools(); | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){ | ||||
|             ContextInfo = default(KeyValuePair<string, string>); | ||||
|         } | ||||
| 
 | ||||
|         public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){ | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         protected void OpenBrowser(Control control, string url){ | ||||
|             control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url)); | ||||
|         } | ||||
| 
 | ||||
|         protected void SetClipboardText(Control control, string text){ | ||||
|             control.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText)); | ||||
|         } | ||||
|          | ||||
|         protected static void AddDebugMenuItems(IMenuModel model){ | ||||
|             model.AddItem(MenuOpenDevTools, "Open dev tools"); | ||||
|         } | ||||
| 
 | ||||
|         protected static void RemoveSeparatorIfLast(IMenuModel model){ | ||||
|             if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){ | ||||
|                 model.RemoveAt(model.Count-1); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected static void AddSeparator(IMenuModel model){ | ||||
|             if (model.Count > 0 && model.GetTypeAt(model.Count-1) != MenuItemType.Separator){ // do not add separators if there is nothing to separate | ||||
|                 model.AddSeparator(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,166 +0,0 @@ | ||||
| using CefSharp; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Bridge; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling{ | ||||
|     sealed class ContextMenuBrowser : ContextMenuBase{ | ||||
|         private const CefMenuCommand MenuGlobal   = (CefMenuCommand)26600; | ||||
|         private const CefMenuCommand MenuMute     = (CefMenuCommand)26601; | ||||
|         private const CefMenuCommand MenuSettings = (CefMenuCommand)26602; | ||||
|         private const CefMenuCommand MenuPlugins  = (CefMenuCommand)26003; | ||||
|         private const CefMenuCommand MenuAbout    = (CefMenuCommand)26604; | ||||
|          | ||||
|         private const CefMenuCommand MenuOpenTweetUrl       = (CefMenuCommand)26610; | ||||
|         private const CefMenuCommand MenuCopyTweetUrl       = (CefMenuCommand)26611; | ||||
|         private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand)26612; | ||||
|         private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613; | ||||
|         private const CefMenuCommand MenuScreenshotTweet    = (CefMenuCommand)26614; | ||||
| 
 | ||||
|         private const string TitleReloadBrowser = "Reload browser"; | ||||
|         private const string TitleMuteNotifications = "Mute notifications"; | ||||
|         private const string TitleSettings = "Options"; | ||||
|         private const string TitlePlugins = "Plugins"; | ||||
|         private const string TitleAboutProgram = "About "+Program.BrandName; | ||||
| 
 | ||||
|         private readonly FormBrowser form; | ||||
| 
 | ||||
|         private string lastHighlightedTweetUrl; | ||||
|         private string lastHighlightedQuoteUrl; | ||||
| 
 | ||||
|         public ContextMenuBrowser(FormBrowser form){ | ||||
|             this.form = form; | ||||
|         } | ||||
| 
 | ||||
|         public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ | ||||
|             model.Remove(CefMenuCommand.Back); | ||||
|             model.Remove(CefMenuCommand.Forward); | ||||
|             model.Remove(CefMenuCommand.Print); | ||||
|             model.Remove(CefMenuCommand.ViewSource); | ||||
|             RemoveSeparatorIfLast(model); | ||||
| 
 | ||||
|             if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){ | ||||
|                 model.AddSeparator(); | ||||
|             } | ||||
| 
 | ||||
|             base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); | ||||
| 
 | ||||
|             lastHighlightedTweetUrl = TweetDeckBridge.LastHighlightedTweetUrl; | ||||
|             lastHighlightedQuoteUrl = TweetDeckBridge.LastHighlightedQuoteUrl; | ||||
| 
 | ||||
|             if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){ | ||||
|                 lastHighlightedTweetUrl = string.Empty; | ||||
|                 lastHighlightedQuoteUrl = string.Empty; | ||||
|             } | ||||
| 
 | ||||
|             if (!string.IsNullOrEmpty(lastHighlightedTweetUrl) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){ | ||||
|                 model.AddItem(MenuOpenTweetUrl, "Open tweet in browser"); | ||||
|                 model.AddItem(MenuCopyTweetUrl, "Copy tweet address"); | ||||
|                 model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard"); | ||||
| 
 | ||||
|                 if (!string.IsNullOrEmpty(lastHighlightedQuoteUrl)){ | ||||
|                     model.AddSeparator(); | ||||
|                     model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser"); | ||||
|                     model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address"); | ||||
|                 } | ||||
| 
 | ||||
|                 model.AddSeparator(); | ||||
|             } | ||||
| 
 | ||||
|             if ((parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){ | ||||
|                 AddSeparator(model); | ||||
| 
 | ||||
|                 IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName); | ||||
|              | ||||
|                 globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser); | ||||
|                 globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications); | ||||
|                 globalMenu.SetChecked(MenuMute, Program.UserConfig.MuteNotifications); | ||||
|                 globalMenu.AddSeparator(); | ||||
| 
 | ||||
|                 globalMenu.AddItem(MenuSettings, TitleSettings); | ||||
|                 globalMenu.AddItem(MenuPlugins, TitlePlugins); | ||||
|                 globalMenu.AddItem(MenuAbout, TitleAboutProgram); | ||||
| 
 | ||||
|                 if (HasDevTools){ | ||||
|                     globalMenu.AddSeparator(); | ||||
|                     AddDebugMenuItems(globalMenu); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             RemoveSeparatorIfLast(model); | ||||
|         } | ||||
| 
 | ||||
|         public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ | ||||
|             if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)){ | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|             switch(commandId){ | ||||
|                 case CefMenuCommand.Reload: | ||||
|                     form.InvokeAsyncSafe(form.ReloadToTweetDeck); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuSettings: | ||||
|                     form.InvokeAsyncSafe(form.OpenSettings); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuAbout: | ||||
|                     form.InvokeAsyncSafe(form.OpenAbout); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuPlugins: | ||||
|                     form.InvokeAsyncSafe(form.OpenPlugins); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuMute: | ||||
|                     form.InvokeAsyncSafe(ToggleMuteNotifications); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuOpenTweetUrl: | ||||
|                     OpenBrowser(form, lastHighlightedTweetUrl); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuCopyTweetUrl: | ||||
|                     SetClipboardText(form, lastHighlightedTweetUrl); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuScreenshotTweet: | ||||
|                     form.InvokeAsyncSafe(form.TriggerTweetScreenshot); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuOpenQuotedTweetUrl: | ||||
|                     OpenBrowser(form, lastHighlightedQuoteUrl); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuCopyQuotedTweetUrl: | ||||
|                     SetClipboardText(form, lastHighlightedQuoteUrl); | ||||
|                     return true; | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public static ContextMenu CreateMenu(FormBrowser form){ | ||||
|             ContextMenu menu = new ContextMenu(); | ||||
| 
 | ||||
|             menu.MenuItems.Add(TitleReloadBrowser, (sender, args) => form.ReloadToTweetDeck()); | ||||
|             menu.MenuItems.Add(TitleMuteNotifications, (sender, args) => ToggleMuteNotifications()); | ||||
|             menu.MenuItems.Add("-"); | ||||
|             menu.MenuItems.Add(TitleSettings, (sender, args) => form.OpenSettings()); | ||||
|             menu.MenuItems.Add(TitlePlugins, (sender, args) => form.OpenPlugins()); | ||||
|             menu.MenuItems.Add(TitleAboutProgram,  (sender, args) => form.OpenAbout()); | ||||
| 
 | ||||
|             menu.Popup += (sender, args) => { | ||||
|                 menu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications; | ||||
|             }; | ||||
| 
 | ||||
|             return menu; | ||||
|         } | ||||
| 
 | ||||
|         private static void ToggleMuteNotifications(){ | ||||
|             Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications; | ||||
|             Program.UserConfig.Save(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,15 +0,0 @@ | ||||
| using CefSharp; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling{ | ||||
|     sealed class ContextMenuGuide : ContextMenuBase{ | ||||
|         public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ | ||||
|             model.Clear(); | ||||
|             base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); | ||||
| 
 | ||||
|             if (HasDevTools){ | ||||
|                 AddSeparator(model); | ||||
|                 AddDebugMenuItems(model); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,95 +0,0 @@ | ||||
| using CefSharp; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Notification; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling{ | ||||
|     sealed class ContextMenuNotification : ContextMenuBase{ | ||||
|         private const CefMenuCommand MenuViewDetail         = (CefMenuCommand)26600; | ||||
|         private const CefMenuCommand MenuSkipTweet          = (CefMenuCommand)26601; | ||||
|         private const CefMenuCommand MenuFreeze             = (CefMenuCommand)26602; | ||||
|         private const CefMenuCommand MenuCopyTweetUrl       = (CefMenuCommand)26603; | ||||
|         private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26604; | ||||
| 
 | ||||
|         private readonly FormNotificationBase form; | ||||
|         private readonly bool enableCustomMenu; | ||||
| 
 | ||||
|         public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu){ | ||||
|             this.form = form; | ||||
|             this.enableCustomMenu = enableCustomMenu; | ||||
|         } | ||||
| 
 | ||||
|         public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ | ||||
|             model.Clear(); | ||||
| 
 | ||||
|             if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){ | ||||
|                 model.AddItem(CefMenuCommand.Copy, "Copy"); | ||||
|                 model.AddSeparator(); | ||||
|             } | ||||
| 
 | ||||
|             base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); | ||||
| 
 | ||||
|             if (enableCustomMenu){ | ||||
|                 if (form.CanViewDetail){ | ||||
|                     model.AddItem(MenuViewDetail, "View detail"); | ||||
|                 } | ||||
| 
 | ||||
|                 model.AddItem(MenuSkipTweet, "Skip tweet"); | ||||
|                 model.AddCheckItem(MenuFreeze, "Freeze"); | ||||
|                 model.SetChecked(MenuFreeze, form.FreezeTimer); | ||||
| 
 | ||||
|                 if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){ | ||||
|                     model.AddSeparator(); | ||||
|                     model.AddItem(MenuCopyTweetUrl, "Copy tweet address"); | ||||
| 
 | ||||
|                     if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){ | ||||
|                         model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             if (HasDevTools){ | ||||
|                 model.AddSeparator(); | ||||
|                 AddDebugMenuItems(model); | ||||
|             } | ||||
| 
 | ||||
|             RemoveSeparatorIfLast(model); | ||||
| 
 | ||||
|             form.InvokeAsyncSafe(() => form.ContextMenuOpen = true); | ||||
|         } | ||||
| 
 | ||||
|         public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ | ||||
|             if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)){ | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|             switch(commandId){ | ||||
|                 case MenuSkipTweet: | ||||
|                     form.InvokeAsyncSafe(form.FinishCurrentNotification); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuFreeze: | ||||
|                     form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuViewDetail: | ||||
|                     form.InvokeSafe(form.ShowTweetDetail); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuCopyTweetUrl: | ||||
|                     SetClipboardText(form, form.CurrentTweetUrl); | ||||
|                     return true; | ||||
| 
 | ||||
|                 case MenuCopyQuotedTweetUrl: | ||||
|                     SetClipboardText(form, form.CurrentQuoteUrl); | ||||
|                     return true; | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){ | ||||
|             base.OnContextMenuDismissed(browserControl, browser, frame); | ||||
|             form.InvokeAsyncSafe(() => form.ContextMenuOpen = false); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,19 +0,0 @@ | ||||
| using System.Collections.Generic; | ||||
| using CefSharp; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling{ | ||||
|     sealed class DragHandlerBrowser : IDragHandler{ | ||||
|         public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask){ | ||||
|             if (dragData.IsLink){ | ||||
|                 browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", "link", dragData.LinkUrl); | ||||
|             } | ||||
|             else{ | ||||
|                 browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", "unknown"); | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IList<DraggableRegion> regions){} | ||||
|     } | ||||
| } | ||||
| @@ -1,15 +0,0 @@ | ||||
| using System; | ||||
| using CefSharp; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling.General{ | ||||
|     sealed class BrowserProcessHandler : IBrowserProcessHandler{ | ||||
|         void IBrowserProcessHandler.OnContextInitialized(){ | ||||
|             using(IRequestContext ctx = Cef.GetGlobalRequestContext()){ | ||||
|                 ctx.SetPreference("browser.enable_spellchecking", Program.UserConfig.EnableSpellCheck, out string _); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay){} | ||||
|         void IDisposable.Dispose(){} | ||||
|     } | ||||
| } | ||||
| @@ -1,42 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Windows.Forms; | ||||
| using CefSharp; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling.General{ | ||||
|     sealed class FileDialogHandler : IDialogHandler{ | ||||
|         public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback){ | ||||
|             CefFileDialogMode dialogType = mode & CefFileDialogMode.TypeMask; | ||||
| 
 | ||||
|             if (dialogType == CefFileDialogMode.Open || dialogType == CefFileDialogMode.OpenMultiple){ | ||||
|                 string allFilters = string.Join(";", acceptFilters.Select(filter => "*"+filter)); | ||||
| 
 | ||||
|                 using(OpenFileDialog dialog = new OpenFileDialog{ | ||||
|                     AutoUpgradeEnabled = true, | ||||
|                     DereferenceLinks = true, | ||||
|                     Multiselect = dialogType == CefFileDialogMode.OpenMultiple, | ||||
|                     Title = "Open Files", | ||||
|                     Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*" | ||||
|                 }){ | ||||
|                     if (dialog.ShowDialog() == DialogResult.OK){ | ||||
|                         string ext = Path.GetExtension(dialog.FileName); | ||||
|                         callback.Continue(acceptFilters.FindIndex(filter => filter.Equals(ext, StringComparison.OrdinalIgnoreCase)), dialog.FileNames.ToList()); | ||||
|                     } | ||||
|                     else{ | ||||
|                         callback.Cancel(); | ||||
|                     } | ||||
| 
 | ||||
|                     callback.Dispose(); | ||||
|                 } | ||||
| 
 | ||||
|                 return true; | ||||
|             } | ||||
|             else{ | ||||
|                 callback.Dispose(); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,90 +0,0 @@ | ||||
| using System.Drawing; | ||||
| using System.Windows.Forms; | ||||
| using CefSharp; | ||||
| using CefSharp.WinForms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Other; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling.General{ | ||||
|     sealed class JavaScriptDialogHandler : IJsDialogHandler{ | ||||
|         private static FormMessage CreateMessageForm(string caption, string text){ | ||||
|             MessageBoxIcon icon = MessageBoxIcon.None; | ||||
|             int pipe = text.IndexOf('|'); | ||||
| 
 | ||||
|             if (pipe != -1){ | ||||
|                 switch(text.Substring(0, pipe)){ | ||||
|                     case "error": icon = MessageBoxIcon.Error; break; | ||||
|                     case "warning": icon = MessageBoxIcon.Warning; break; | ||||
|                     case "info": icon = MessageBoxIcon.Information; break; | ||||
|                     case "question": icon = MessageBoxIcon.Question; break; | ||||
|                     default: return new FormMessage(caption, text, icon); | ||||
|                 } | ||||
| 
 | ||||
|                 text = text.Substring(pipe+1); | ||||
|             } | ||||
| 
 | ||||
|             return new FormMessage(caption, text, icon); | ||||
|         } | ||||
| 
 | ||||
|         bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){ | ||||
|             ((ChromiumWebBrowser)browserControl).InvokeSafe(() => { | ||||
|                 FormMessage form; | ||||
|                 TextBox input = null; | ||||
| 
 | ||||
|                 if (dialogType == CefJsDialogType.Alert){ | ||||
|                     form = CreateMessageForm("Browser Message", messageText); | ||||
|                     form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused); | ||||
|                 } | ||||
|                 else if (dialogType == CefJsDialogType.Confirm){ | ||||
|                     form = CreateMessageForm("Browser Confirmation", messageText); | ||||
|                     form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel); | ||||
|                     form.AddButton(FormMessage.Yes, ControlType.Focused); | ||||
|                 } | ||||
|                 else if (dialogType == CefJsDialogType.Prompt){ | ||||
|                     form = CreateMessageForm("Browser Prompt", messageText); | ||||
|                     form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel); | ||||
|                     form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused); | ||||
| 
 | ||||
|                     float dpiScale = form.GetDPIScale(); | ||||
|                     int inputPad = form.HasIcon ? 43 : 0; | ||||
| 
 | ||||
|                     input = new TextBox{ | ||||
|                         Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom, | ||||
|                         Location = new Point(BrowserUtils.Scale(22+inputPad, dpiScale), form.ActionPanelY-BrowserUtils.Scale(46, dpiScale)), | ||||
|                         Size = new Size(form.ClientSize.Width-BrowserUtils.Scale(44+inputPad, dpiScale), 20) | ||||
|                     }; | ||||
| 
 | ||||
|                     form.Controls.Add(input); | ||||
|                     form.ActiveControl = input; | ||||
|                     form.Height += input.Size.Height+input.Margin.Vertical; | ||||
|                 } | ||||
|                 else{ | ||||
|                     callback.Continue(false); | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 bool success = form.ShowDialog() == DialogResult.OK; | ||||
| 
 | ||||
|                 if (input == null){ | ||||
|                     callback.Continue(success); | ||||
|                 } | ||||
|                 else{ | ||||
|                     callback.Continue(success, input.Text); | ||||
|                     input.Dispose(); | ||||
|                 } | ||||
| 
 | ||||
|                 form.Dispose(); | ||||
|             }); | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         bool IJsDialogHandler.OnJSBeforeUnload(IWebBrowser browserControl, IBrowser browser, string message, bool isReload, IJsDialogCallback callback){ | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser){} | ||||
|         void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser){} | ||||
|     } | ||||
| } | ||||
| @@ -1,31 +0,0 @@ | ||||
| using CefSharp; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling.General{ | ||||
|     sealed class LifeSpanHandler : ILifeSpanHandler{ | ||||
|         public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser){ | ||||
|             newBrowser = null; | ||||
| 
 | ||||
|             switch(targetDisposition){ | ||||
|                 case WindowOpenDisposition.NewBackgroundTab: | ||||
|                 case WindowOpenDisposition.NewForegroundTab: | ||||
|                 case WindowOpenDisposition.NewPopup: | ||||
|                 case WindowOpenDisposition.NewWindow: | ||||
|                     browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl)); | ||||
|                     return true; | ||||
| 
 | ||||
|                 default: | ||||
|                     return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void OnAfterCreated(IWebBrowser browserControl, IBrowser browser){} | ||||
| 
 | ||||
|         public bool DoClose(IWebBrowser browserControl, IBrowser browser){ | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public void OnBeforeClose(IWebBrowser browserControl, IBrowser browser){} | ||||
|     } | ||||
| } | ||||
| @@ -1,68 +0,0 @@ | ||||
| using System.Security.Cryptography.X509Certificates; | ||||
| using CefSharp; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling.General{ | ||||
|     abstract class RequestHandlerBase : IRequestHandler{ | ||||
|         // Browser | ||||
| 
 | ||||
|         public virtual void OnRenderViewReady(IWebBrowser browserControl, IBrowser browser){} | ||||
| 
 | ||||
|         public virtual void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){} | ||||
| 
 | ||||
|         public virtual bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool isRedirect){ | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public virtual bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){ | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public virtual bool OnProtocolExecution(IWebBrowser browserControl, IBrowser browser, string url){ | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // Resources | ||||
| 
 | ||||
|         public virtual CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){ | ||||
|             return CefReturnValue.Continue; | ||||
|         } | ||||
| 
 | ||||
|         public virtual void OnResourceRedirect(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl){} | ||||
| 
 | ||||
|         public virtual bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){ | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public virtual IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){ | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         public virtual void OnResourceLoadComplete(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength){} | ||||
| 
 | ||||
|         // JavaScript & Plugins | ||||
| 
 | ||||
|         public virtual bool OnQuotaRequest(IWebBrowser browserControl, IBrowser browser, string originUrl, long newSize, IRequestCallback callback){ | ||||
|             callback.Dispose(); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public virtual void OnPluginCrashed(IWebBrowser browserControl, IBrowser browser, string pluginPath){} | ||||
| 
 | ||||
|         // Auth | ||||
| 
 | ||||
|         public virtual bool GetAuthCredentials(IWebBrowser browserControl, IBrowser browser, IFrame frame, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback){ | ||||
|             callback.Dispose(); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public virtual bool OnSelectClientCertificate(IWebBrowser browserControl, IBrowser browser, bool isProxy, string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback){ | ||||
|             callback.Dispose(); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public virtual bool OnCertificateError(IWebBrowser browserControl, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback){ | ||||
|             callback.Dispose(); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,20 +0,0 @@ | ||||
| using System.Windows.Forms; | ||||
| using CefSharp; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling{ | ||||
|     sealed class KeyboardHandlerBrowser : IKeyboardHandler{ | ||||
|         private readonly FormBrowser form; | ||||
| 
 | ||||
|         public KeyboardHandlerBrowser(FormBrowser form){ | ||||
|             this.form = form; | ||||
|         } | ||||
| 
 | ||||
|         bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){ | ||||
|             return type == KeyType.RawKeyDown && form.ProcessBrowserKey((Keys)windowsKeyCode); | ||||
|         } | ||||
| 
 | ||||
|         bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){ | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,38 +0,0 @@ | ||||
| using CefSharp; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Notification; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling { | ||||
|     sealed class KeyboardHandlerNotification : IKeyboardHandler{ | ||||
|         private readonly FormNotificationBase notification; | ||||
| 
 | ||||
|         public KeyboardHandlerNotification(FormNotificationBase notification){ | ||||
|             this.notification = notification; | ||||
|         } | ||||
| 
 | ||||
|         bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){ | ||||
|             if (type == KeyType.RawKeyDown && !browser.FocusedFrame.Url.StartsWith("chrome-devtools://")){ | ||||
|                 switch((Keys)windowsKeyCode){ | ||||
|                     case Keys.Enter: | ||||
|                         notification.InvokeAsyncSafe(notification.FinishCurrentNotification); | ||||
|                         return true; | ||||
| 
 | ||||
|                     case Keys.Escape: | ||||
|                         notification.InvokeAsyncSafe(() => notification.HideNotification(true)); | ||||
|                         return true; | ||||
| 
 | ||||
|                     case Keys.Space: | ||||
|                         notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer); | ||||
|                         return true; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){ | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| using CefSharp; | ||||
| using TweetDuck.Core.Handling.General; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling{ | ||||
|     sealed class RequestHandlerBrowser : RequestHandlerBase{ | ||||
|         public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){ | ||||
|             browser.Reload(); | ||||
|         } | ||||
| 
 | ||||
|         public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){ | ||||
|             if (request.ResourceType == ResourceType.Script && request.Url.Contains("analytics.")){ | ||||
|                 return CefReturnValue.Cancel; | ||||
|             } | ||||
| 
 | ||||
|             return CefReturnValue.Continue; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,63 +0,0 @@ | ||||
| using CefSharp; | ||||
| using System.Collections.Specialized; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Handling{ | ||||
|     sealed class ResourceHandlerNotification : IResourceHandler{ | ||||
|         private readonly NameValueCollection headers = new NameValueCollection(0); | ||||
|         private MemoryStream dataIn; | ||||
| 
 | ||||
|         public void SetHTML(string html){ | ||||
|             dataIn?.Dispose(); | ||||
|             dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8); | ||||
|         } | ||||
| 
 | ||||
|         public void Dispose(){ | ||||
|             if (dataIn != null){ | ||||
|                 dataIn.Dispose(); | ||||
|                 dataIn = null; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback){ | ||||
|             callback.Continue(); | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl){ | ||||
|             redirectUrl = null; | ||||
| 
 | ||||
|             response.MimeType = "text/html"; | ||||
|             response.StatusCode = 200; | ||||
|             response.StatusText = "OK"; | ||||
|             response.ResponseHeaders = headers; | ||||
|             responseLength = dataIn?.Length ?? -1; | ||||
|         } | ||||
| 
 | ||||
|         bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback){ | ||||
|             callback.Dispose(); | ||||
| 
 | ||||
|             try{ | ||||
|                 int length = (int)dataIn.Length; | ||||
| 
 | ||||
|                 dataIn.CopyTo(dataOut, length); | ||||
|                 bytesRead = length; | ||||
|                 return true; | ||||
|             }catch{ // catch IOException, possibly NullReferenceException if dataIn is null | ||||
|                 bytesRead = 0; | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         bool IResourceHandler.CanGetCookie(Cookie cookie){ | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         bool IResourceHandler.CanSetCookie(Cookie cookie){ | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         void IResourceHandler.Cancel(){} | ||||
|     } | ||||
| } | ||||
| @@ -1,246 +0,0 @@ | ||||
| using CefSharp; | ||||
| using CefSharp.WinForms; | ||||
| using System; | ||||
| using System.Drawing; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Configuration; | ||||
| using TweetDuck.Core.Bridge; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Handling; | ||||
| using TweetDuck.Core.Handling.General; | ||||
| using TweetDuck.Core.Other.Management; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Notification{ | ||||
|     partial class FormNotificationBase : Form{ | ||||
|         protected static int FontSizeLevel{ | ||||
|             get{ | ||||
|                 switch(TweetDeckBridge.FontSizeClass){ | ||||
|                     case "largest": return 4; | ||||
|                     case "large": return 3; | ||||
|                     case "small": return 1; | ||||
|                     case "smallest": return 0; | ||||
|                     default: return 2; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected Point PrimaryLocation{ | ||||
|             get{ | ||||
|                 UserConfig config = Program.UserConfig; | ||||
|                 Screen screen; | ||||
| 
 | ||||
|                 if (config.NotificationDisplay > 0 && config.NotificationDisplay <= Screen.AllScreens.Length){ | ||||
|                     screen = Screen.AllScreens[config.NotificationDisplay-1]; | ||||
|                 } | ||||
|                 else{ | ||||
|                     screen = Screen.FromControl(owner); | ||||
|                 } | ||||
|              | ||||
|                 int edgeDist = config.NotificationEdgeDistance; | ||||
| 
 | ||||
|                 switch(config.NotificationPosition){ | ||||
|                     case TweetNotification.Position.TopLeft: | ||||
|                         return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+edgeDist); | ||||
| 
 | ||||
|                     case TweetNotification.Position.TopRight: | ||||
|                         return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist); | ||||
| 
 | ||||
|                     case TweetNotification.Position.BottomLeft: | ||||
|                         return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height); | ||||
| 
 | ||||
|                     case TweetNotification.Position.BottomRight: | ||||
|                         return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height); | ||||
| 
 | ||||
|                     case TweetNotification.Position.Custom: | ||||
|                         if (!config.IsCustomNotificationPositionSet){ | ||||
|                             config.CustomNotificationPosition = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist); | ||||
|                             config.Save(); | ||||
|                         } | ||||
| 
 | ||||
|                         return config.CustomNotificationPosition; | ||||
|                 } | ||||
| 
 | ||||
|                 return Location; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation; | ||||
| 
 | ||||
|         public new Point Location{ | ||||
|             get => base.Location; | ||||
| 
 | ||||
|             set{ | ||||
|                 Visible = (base.Location = value) != ControlExtensions.InvisibleLocation; | ||||
|                 FormBorderStyle = GetBorderStyle(CanResizeWindow); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public bool CanResizeWindow{ | ||||
|             get => FormBorderStyle == FormBorderStyle.Sizable || FormBorderStyle == FormBorderStyle.SizableToolWindow; | ||||
|             set => FormBorderStyle = GetBorderStyle(value); | ||||
|         } | ||||
|          | ||||
|         public Func<bool> CanMoveWindow { get; set; } = () => true; | ||||
|         protected override bool ShowWithoutActivation => true; | ||||
| 
 | ||||
|         protected double SizeScale => dpiScale*Program.UserConfig.ZoomMultiplier; | ||||
| 
 | ||||
|         protected readonly FormBrowser owner; | ||||
|         protected readonly ChromiumWebBrowser browser; | ||||
|          | ||||
|         private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification(); | ||||
|         private readonly float dpiScale; | ||||
| 
 | ||||
|         private TweetNotification currentNotification; | ||||
|         private int pauseCounter; | ||||
|          | ||||
|         public string CurrentTweetUrl => currentNotification?.TweetUrl; | ||||
|         public string CurrentQuoteUrl => currentNotification?.QuoteUrl; | ||||
| 
 | ||||
|         public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId); | ||||
|         public bool IsPaused => pauseCounter > 0; | ||||
|          | ||||
|         public bool FreezeTimer { get; set; } | ||||
|         public bool ContextMenuOpen { get; set; } | ||||
| 
 | ||||
|         public event EventHandler Initialized; | ||||
| 
 | ||||
|         protected FormNotificationBase(FormBrowser owner, bool enableContextMenu){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             this.owner = owner; | ||||
|             this.owner.FormClosed += owner_FormClosed; | ||||
| 
 | ||||
|             this.browser = new ChromiumWebBrowser("about:blank"){ | ||||
|                 MenuHandler = new ContextMenuNotification(this, enableContextMenu), | ||||
|                 JsDialogHandler = new JavaScriptDialogHandler(), | ||||
|                 LifeSpanHandler = new LifeSpanHandler() | ||||
|             }; | ||||
| 
 | ||||
|             this.browser.Dock = DockStyle.None; | ||||
|             this.browser.ClientSize = ClientSize; | ||||
|             this.browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged; | ||||
| 
 | ||||
|             #if DEBUG | ||||
|             this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage; | ||||
|             #endif | ||||
| 
 | ||||
|             this.dpiScale = this.GetDPIScale(); | ||||
| 
 | ||||
|             DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory; | ||||
|             handlerFactory.RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler); | ||||
| 
 | ||||
|             Controls.Add(browser); | ||||
| 
 | ||||
|             Disposed += (sender, args) => { | ||||
|                 this.browser.Dispose(); | ||||
|                 this.owner.FormClosed -= owner_FormClosed; | ||||
|             }; | ||||
| 
 | ||||
|             // ReSharper disable once VirtualMemberCallInContructor | ||||
|             UpdateTitle(); | ||||
|         } | ||||
| 
 | ||||
|         protected override void WndProc(ref Message m){ | ||||
|             if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanMoveWindow()){ // WM_SYSCOMMAND, SC_MOVE | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             base.WndProc(ref m); | ||||
|         } | ||||
| 
 | ||||
|         // event handlers | ||||
| 
 | ||||
|         private void owner_FormClosed(object sender, FormClosedEventArgs e){ | ||||
|             Close(); | ||||
|         } | ||||
| 
 | ||||
|         private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){ | ||||
|             if (e.IsBrowserInitialized){ | ||||
|                 Initialized?.Invoke(this, EventArgs.Empty); | ||||
| 
 | ||||
|                 int identifier = browser.GetBrowser().Identifier; | ||||
|                 Disposed += (sender2, args2) => BrowserProcesses.Forget(identifier); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // notification methods | ||||
| 
 | ||||
|         public virtual void HideNotification(bool loadBlank){ | ||||
|             if (loadBlank){ | ||||
|                 browser.Load("about:blank"); | ||||
|             } | ||||
| 
 | ||||
|             Location = ControlExtensions.InvisibleLocation; | ||||
|             currentNotification = null; | ||||
|         } | ||||
| 
 | ||||
|         public virtual void FinishCurrentNotification(){} | ||||
| 
 | ||||
|         public virtual void PauseNotification(){ | ||||
|             if (pauseCounter++ == 0 && IsNotificationVisible){ | ||||
|                 Location = ControlExtensions.InvisibleLocation; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public virtual void ResumeNotification(){ | ||||
|             if (pauseCounter > 0){ | ||||
|                 --pauseCounter; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected virtual string GetTweetHTML(TweetNotification tweet){ | ||||
|             string bodyClasses = browser.Bounds.Contains(PointToClient(Cursor.Position)) ? "td-hover" : string.Empty; | ||||
|             return tweet.GenerateHtml(bodyClasses); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void LoadTweet(TweetNotification tweet){ | ||||
|             currentNotification = tweet; | ||||
|             resourceHandler.SetHTML(GetTweetHTML(tweet)); | ||||
|             browser.Load(TwitterUtils.TweetDeckURL); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void SetNotificationSize(int width, int height){ | ||||
|             browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale)); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void UpdateTitle(){ | ||||
|             string title = currentNotification?.ColumnTitle; | ||||
|             Text = string.IsNullOrEmpty(title) || !Program.UserConfig.DisplayNotificationColumn ? Program.BrandName : Program.BrandName+" - "+title; | ||||
|         } | ||||
| 
 | ||||
|         public void ShowTweetDetail(){ | ||||
|             owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl); | ||||
|         } | ||||
| 
 | ||||
|         public void MoveToVisibleLocation(){ | ||||
|             bool needsReactivating = Location == ControlExtensions.InvisibleLocation; | ||||
|             Location = PrimaryLocation; | ||||
| 
 | ||||
|             if (needsReactivating){ | ||||
|                 NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void DisplayTooltip(string text){ | ||||
|             if (string.IsNullOrEmpty(text)){ | ||||
|                 toolTip.Hide(this); | ||||
|             } | ||||
|             else{ | ||||
|                 Point position = PointToClient(Cursor.Position); | ||||
|                 position.Offset(20, 5); | ||||
|                 toolTip.Show(text, this, position); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private FormBorderStyle GetBorderStyle(bool sizable){ | ||||
|             if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab | ||||
|                 return sizable ? FormBorderStyle.Sizable : FormBorderStyle.FixedSingle; | ||||
|             } | ||||
|             else{ | ||||
|                 return sizable ? FormBorderStyle.SizableToolWindow : FormBorderStyle.FixedToolWindow; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,287 +0,0 @@ | ||||
| using CefSharp; | ||||
| using System; | ||||
| using System.Drawing; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Bridge; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Handling; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetDuck.Data; | ||||
| using TweetDuck.Plugins; | ||||
| using TweetDuck.Plugins.Enums; | ||||
| using TweetDuck.Resources; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Notification{ | ||||
|     partial class FormNotificationMain : FormNotificationBase{ | ||||
|         private const string NotificationScriptFile = "notification.js"; | ||||
|         private const int TimerBarHeight = 4; | ||||
| 
 | ||||
|         private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile); | ||||
|         private static readonly string NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile); | ||||
|          | ||||
|         private readonly PluginManager plugins; | ||||
| 
 | ||||
|         protected int timeLeft, totalTime; | ||||
|         protected bool pausedDuringNotification; | ||||
|          | ||||
|         private readonly NativeMethods.HookProc mouseHookDelegate; | ||||
|         private IntPtr mouseHook; | ||||
|         private bool blockXButtonUp; | ||||
| 
 | ||||
|         private bool? prevDisplayTimer; | ||||
|         private int? prevFontSize; | ||||
| 
 | ||||
|         public bool RequiresResize{ | ||||
|             get{ | ||||
|                 return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != FontSizeLevel || CanResizeWindow; | ||||
|             } | ||||
| 
 | ||||
|             set{ | ||||
|                 if (value){ | ||||
|                     prevDisplayTimer = null; | ||||
|                     prevFontSize = null; | ||||
|                 } | ||||
|                 else{ | ||||
|                     prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer; | ||||
|                     prevFontSize = FontSizeLevel; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private int BaseClientWidth{ | ||||
|             get{ | ||||
|                 switch(Program.UserConfig.NotificationSize){ | ||||
|                     default: | ||||
|                         return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*FontSizeLevel)); | ||||
| 
 | ||||
|                     case TweetNotification.Size.Custom: | ||||
|                         return Program.UserConfig.CustomNotificationSize.Width; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private int BaseClientHeight{ | ||||
|             get{ | ||||
|                 switch(Program.UserConfig.NotificationSize){ | ||||
|                     default: | ||||
|                         return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*FontSizeLevel)); | ||||
| 
 | ||||
|                     case TweetNotification.Size.Custom: | ||||
|                         return Program.UserConfig.CustomNotificationSize.Height; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public Size BrowserSize{ | ||||
|             get => Program.UserConfig.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-TimerBarHeight) : ClientSize; | ||||
|         } | ||||
| 
 | ||||
|         public FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             this.plugins = pluginManager; | ||||
|              | ||||
|             browser.KeyboardHandler = new KeyboardHandlerNotification(this); | ||||
|              | ||||
|             browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this)); | ||||
|             browser.RegisterAsyncJsObject("$TDP", plugins.Bridge); | ||||
| 
 | ||||
|             browser.LoadingStateChanged += Browser_LoadingStateChanged; | ||||
|             browser.FrameLoadEnd += Browser_FrameLoadEnd; | ||||
| 
 | ||||
|             mouseHookDelegate = MouseHookProc; | ||||
|             Disposed += (sender, args) => StopMouseHook(true); | ||||
|         } | ||||
| 
 | ||||
|         // mouse wheel hook | ||||
| 
 | ||||
|         private void StartMouseHook(){ | ||||
|             if (mouseHook == IntPtr.Zero){ | ||||
|                 mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WM_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void StopMouseHook(bool force){ | ||||
|             if (mouseHook != IntPtr.Zero && (force || !blockXButtonUp)){ | ||||
|                 NativeMethods.UnhookWindowsHookEx(mouseHook); | ||||
|                 mouseHook = IntPtr.Zero; | ||||
|                 blockXButtonUp = false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam){ | ||||
|             if (nCode == 0){ | ||||
|                 int eventType = wParam.ToInt32(); | ||||
| 
 | ||||
|                 if (eventType == NativeMethods.WM_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){ | ||||
|                     browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed/100.0), CefEventFlags.None); | ||||
|                     return NativeMethods.HOOK_HANDLED; | ||||
|                 } | ||||
|                 else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){ | ||||
|                     int extraButton = NativeMethods.GetMouseHookData(lParam); | ||||
| 
 | ||||
|                     if (extraButton == 2){ // forward button | ||||
|                         this.InvokeAsyncSafe(FinishCurrentNotification); | ||||
|                     } | ||||
|                     else if (extraButton == 1){ // back button | ||||
|                         this.InvokeAsyncSafe(Close); | ||||
|                     } | ||||
|                      | ||||
|                     blockXButtonUp = true; | ||||
|                     return NativeMethods.HOOK_HANDLED; | ||||
|                 } | ||||
|                 else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){ | ||||
|                     blockXButtonUp = false; | ||||
| 
 | ||||
|                     if (!Visible){ | ||||
|                         StopMouseHook(false); | ||||
|                     } | ||||
| 
 | ||||
|                     return NativeMethods.HOOK_HANDLED; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return NativeMethods.CallNextHookEx(mouseHook, nCode, wParam, lParam); | ||||
|         } | ||||
| 
 | ||||
|         // event handlers | ||||
| 
 | ||||
|         private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){ | ||||
|             if (e.CloseReason == CloseReason.UserClosing){ | ||||
|                 HideNotification(true); | ||||
|                 e.Cancel = true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){ | ||||
|             if (!e.IsLoading && browser.Address != "about:blank"){ | ||||
|                 this.InvokeSafe(() => { | ||||
|                     Visible = true; // ensures repaint before moving the window to a visible location | ||||
|                     timerDisplayDelay.Start(); | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ | ||||
|             if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){ | ||||
|                 e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification)); | ||||
|                 ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier); | ||||
|                 plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void timerDisplayDelay_Tick(object sender, EventArgs e){ | ||||
|             OnNotificationReady(); | ||||
|             timerDisplayDelay.Stop(); | ||||
|         } | ||||
| 
 | ||||
|         private void timerHideProgress_Tick(object sender, EventArgs e){ | ||||
|             if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen)return; | ||||
| 
 | ||||
|             timeLeft -= timerProgress.Interval; | ||||
| 
 | ||||
|             int value = BrowserUtils.Scale(1025, (totalTime-timeLeft)/(double)totalTime); | ||||
|             progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value))); | ||||
| 
 | ||||
|             if (timeLeft <= 0){ | ||||
|                 FinishCurrentNotification(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // notification methods | ||||
| 
 | ||||
|         public virtual void ShowNotification(TweetNotification notification){ | ||||
|             LoadTweet(notification); | ||||
|         } | ||||
| 
 | ||||
|         public void ShowNotificationForSettings(bool reset){ | ||||
|             if (reset){ | ||||
|                 LoadTweet(TweetNotification.ExampleTweet); | ||||
|             } | ||||
|             else{ | ||||
|                 PrepareAndDisplayWindow(); | ||||
|             } | ||||
| 
 | ||||
|             UpdateTitle(); | ||||
|         } | ||||
| 
 | ||||
|         public override void HideNotification(bool loadBlank){ | ||||
|             base.HideNotification(loadBlank); | ||||
|              | ||||
|             progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0; | ||||
|             timerProgress.Stop(); | ||||
|             totalTime = 0; | ||||
| 
 | ||||
|             StopMouseHook(false); | ||||
|         } | ||||
| 
 | ||||
|         public override void FinishCurrentNotification(){ | ||||
|             timerProgress.Stop(); | ||||
|         } | ||||
| 
 | ||||
|         public override void PauseNotification(){ | ||||
|             if (!IsPaused){ | ||||
|                 pausedDuringNotification = IsNotificationVisible; | ||||
|                 timerProgress.Stop(); | ||||
|                 StopMouseHook(true); | ||||
|             } | ||||
| 
 | ||||
|             base.PauseNotification(); | ||||
|         } | ||||
| 
 | ||||
|         public override void ResumeNotification(){ | ||||
|             bool wasPaused = IsPaused; | ||||
|             base.ResumeNotification(); | ||||
| 
 | ||||
|             if (wasPaused && !IsPaused && pausedDuringNotification){ | ||||
|                 OnNotificationReady(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected override string GetTweetHTML(TweetNotification tweet){ | ||||
|             string html = base.GetTweetHTML(tweet); | ||||
| 
 | ||||
|             foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){ | ||||
|                 html = injection.Inject(html); | ||||
|             } | ||||
| 
 | ||||
|             return html; | ||||
|         } | ||||
| 
 | ||||
|         protected override void LoadTweet(TweetNotification tweet){ | ||||
|             timerProgress.Stop(); | ||||
|             totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue); | ||||
|             progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0; | ||||
| 
 | ||||
|             base.LoadTweet(tweet); | ||||
|         } | ||||
| 
 | ||||
|         protected override void SetNotificationSize(int width, int height){ | ||||
|             if (Program.UserConfig.DisplayNotificationTimer){ | ||||
|                 ClientSize = new Size(width, height+TimerBarHeight); | ||||
|                 progressBarTimer.Visible = true; | ||||
|             } | ||||
|             else{ | ||||
|                 ClientSize = new Size(width, height); | ||||
|                 progressBarTimer.Visible = false; | ||||
|             } | ||||
| 
 | ||||
|             browser.ClientSize = new Size(width, height); | ||||
|         } | ||||
|          | ||||
|         private void PrepareAndDisplayWindow(){ | ||||
|             if (RequiresResize){ | ||||
|                 RequiresResize = false; | ||||
|                 SetNotificationSize(BaseClientWidth, BaseClientHeight); | ||||
|             } | ||||
|              | ||||
|             MoveToVisibleLocation(); | ||||
|             StartMouseHook(); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void OnNotificationReady(){ | ||||
|             PrepareAndDisplayWindow(); | ||||
|             timerProgress.Start(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,141 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Drawing; | ||||
| using TweetDuck.Plugins; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Notification{ | ||||
|     sealed partial class FormNotificationTweet : FormNotificationMain{ | ||||
|         private const int NonIntrusiveIdleLimit = 30; | ||||
|         private const int TrimMinimum = 32; | ||||
| 
 | ||||
|         private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position); | ||||
| 
 | ||||
|         private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4); | ||||
|         private bool needsTrim; | ||||
| 
 | ||||
|         public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             Program.UserConfig.MuteToggled += Config_MuteToggled; | ||||
|             Disposed += (sender, args) => Program.UserConfig.MuteToggled -= Config_MuteToggled; | ||||
| 
 | ||||
|             if (Program.UserConfig.MuteNotifications){ | ||||
|                 PauseNotification(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void FormNotificationTweet_FormClosing(object sender, FormClosingEventArgs e){ | ||||
|             if (e.CloseReason == CloseReason.UserClosing){ | ||||
|                 tweetQueue.Clear(); // already canceled | ||||
|                 TrimQueue(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void TrimQueue(){ | ||||
|             if (needsTrim){ | ||||
|                 tweetQueue.TrimExcess(); | ||||
|                 needsTrim = false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // event handlers | ||||
| 
 | ||||
|         private void Config_MuteToggled(object sender, EventArgs e){ | ||||
|             if (Program.UserConfig.MuteNotifications){ | ||||
|                 PauseNotification(); | ||||
|             } | ||||
|             else{ | ||||
|                 ResumeNotification(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void timerCursorCheck_Tick(object sender, EventArgs e){ | ||||
|             if (!IsCursorOverNotificationArea){ | ||||
|                 ResumeNotification(); | ||||
|                 timerCursorCheck.Stop(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void timerIdlePauseCheck_Tick(object sender, EventArgs e){ | ||||
|             if (NativeMethods.GetIdleSeconds() < Program.UserConfig.NotificationIdlePauseSeconds){ | ||||
|                 ResumeNotification(); | ||||
|                 timerIdlePauseCheck.Stop(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // notification methods | ||||
| 
 | ||||
|         public override void ShowNotification(TweetNotification notification){ | ||||
|             if (IsPaused){ | ||||
|                 tweetQueue.Enqueue(notification); | ||||
|             } | ||||
|             else{ | ||||
|                 tweetQueue.Enqueue(notification); | ||||
|                 UpdateTitle(); | ||||
| 
 | ||||
|                 if (totalTime == 0){ | ||||
|                     LoadNextNotification(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             needsTrim |= tweetQueue.Count >= TrimMinimum; | ||||
|         } | ||||
| 
 | ||||
|         public override void FinishCurrentNotification(){ | ||||
|             if (tweetQueue.Count > 0){ | ||||
|                 LoadNextNotification(); | ||||
|             } | ||||
|             else{ | ||||
|                 HideNotification(true); | ||||
|                 TrimQueue(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public override void ResumeNotification(){ | ||||
|             bool wasPaused = IsPaused; | ||||
|             base.ResumeNotification(); | ||||
| 
 | ||||
|             if (wasPaused && !IsPaused && !pausedDuringNotification && tweetQueue.Count > 0){ | ||||
|                 LoadNextNotification(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void LoadNextNotification(){ | ||||
|             if (!IsNotificationVisible){ | ||||
|                 if (Program.UserConfig.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit){ | ||||
|                     if (!timerCursorCheck.Enabled){ | ||||
|                         PauseNotification(); | ||||
|                         timerCursorCheck.Start(); | ||||
|                     } | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 else if (Program.UserConfig.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Program.UserConfig.NotificationIdlePauseSeconds){ | ||||
|                     if (!timerIdlePauseCheck.Enabled){ | ||||
|                         PauseNotification(); | ||||
|                         timerIdlePauseCheck.Start(); | ||||
|                     } | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             LoadTweet(tweetQueue.Dequeue()); | ||||
|         } | ||||
| 
 | ||||
|         protected override void UpdateTitle(){ | ||||
|             base.UpdateTitle(); | ||||
| 
 | ||||
|             if (tweetQueue.Count > 0){ | ||||
|                 Text = Text+" ("+tweetQueue.Count+" more left)"; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected override void OnNotificationReady(){ | ||||
|             UpdateTitle(); | ||||
|             base.OnNotificationReady(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,65 +0,0 @@ | ||||
| using System; | ||||
| using System.Drawing; | ||||
| using System.Drawing.Imaging; | ||||
| using System.Windows.Forms; | ||||
| using CefSharp; | ||||
| using TweetDuck.Core.Bridge; | ||||
| using TweetDuck.Core.Other; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetDuck.Data; | ||||
| using TweetDuck.Plugins; | ||||
| using TweetDuck.Resources; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Notification.Screenshot{ | ||||
|     sealed class FormNotificationScreenshotable : FormNotificationBase{ | ||||
|         private readonly PluginManager plugins; | ||||
| 
 | ||||
|         public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager) : base(owner, false){ | ||||
|             this.plugins = pluginManager; | ||||
| 
 | ||||
|             browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback)); | ||||
| 
 | ||||
|             browser.LoadingStateChanged += (sender, args) => { | ||||
|                 if (!args.IsLoading){ | ||||
|                     using(IFrame frame = args.Browser.MainFrame){ | ||||
|                         ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 129)", "gen:screenshot"); | ||||
|                     } | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
|          | ||||
|         protected override string GetTweetHTML(TweetNotification tweet){ | ||||
|             string html = tweet.GenerateHtml("td-screenshot", false); | ||||
| 
 | ||||
|             foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){ | ||||
|                 html = injection.Inject(html); | ||||
|             } | ||||
| 
 | ||||
|             return html; | ||||
|         } | ||||
| 
 | ||||
|         public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){ | ||||
|             LoadTweet(tweet); | ||||
|             SetNotificationSize(width, height); | ||||
|         } | ||||
| 
 | ||||
|         public void TakeScreenshot(){ | ||||
|             IntPtr context = NativeMethods.GetDC(this.Handle); | ||||
| 
 | ||||
|             if (context == IntPtr.Zero){ | ||||
|                 FormMessage.Error("Screenshot Failed", "Could not retrieve a graphics context handle for the notification window to take the screenshot.", FormMessage.OK); | ||||
|             } | ||||
|             else{ | ||||
|                 using(Bitmap bmp = new Bitmap(ClientSize.Width, ClientSize.Height, PixelFormat.Format32bppRgb)){ | ||||
|                     try{ | ||||
|                         NativeMethods.RenderSourceIntoBitmap(context, bmp); | ||||
|                     }finally{ | ||||
|                         NativeMethods.ReleaseDC(this.Handle, context); | ||||
|                     } | ||||
| 
 | ||||
|                     Clipboard.SetImage(bmp); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,86 +0,0 @@ | ||||
| // Uncomment to keep screenshot windows visible for debugging | ||||
| // #define NO_HIDE_SCREENSHOTS | ||||
| 
 | ||||
| using System; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Plugins; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Notification.Screenshot{ | ||||
|     sealed class TweetScreenshotManager : IDisposable{ | ||||
|         private readonly FormBrowser owner; | ||||
|         private readonly PluginManager plugins; | ||||
|         private readonly Timer timeout; | ||||
|         private readonly Timer disposer; | ||||
|          | ||||
|         private FormNotificationScreenshotable screenshot; | ||||
| 
 | ||||
|         public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager){ | ||||
|             this.owner = owner; | ||||
|             this.plugins = pluginManager; | ||||
| 
 | ||||
|             this.timeout = new Timer{ Interval = 8000 }; | ||||
|             this.timeout.Tick += timeout_Tick; | ||||
| 
 | ||||
|             this.disposer = new Timer{ Interval = 1 }; | ||||
|             this.disposer.Tick += disposer_Tick; | ||||
|         } | ||||
| 
 | ||||
|         private void timeout_Tick(object sender, EventArgs e){ | ||||
|             timeout.Stop(); | ||||
|             OnFinished(); | ||||
|         } | ||||
| 
 | ||||
|         private void disposer_Tick(object sender, EventArgs e){ | ||||
|             disposer.Stop(); | ||||
|             screenshot.Dispose(); | ||||
|             screenshot = null; | ||||
|         } | ||||
| 
 | ||||
|         public void Trigger(string html, int width, int height){ | ||||
|             if (screenshot != null){ | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             screenshot = new FormNotificationScreenshotable(Callback, owner, plugins){ | ||||
|                 CanMoveWindow = () => false | ||||
|             }; | ||||
| 
 | ||||
|             screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty), width, height); | ||||
|             screenshot.Show(); | ||||
|             timeout.Start(); | ||||
| 
 | ||||
|             #if !(DEBUG && NO_HIDE_SCREENSHOTS) | ||||
|             owner.IsWaiting = true; | ||||
|             #endif | ||||
|         } | ||||
| 
 | ||||
|         private void Callback(){ | ||||
|             if (!timeout.Enabled){ | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             timeout.Stop(); | ||||
|             screenshot.TakeScreenshot(); | ||||
| 
 | ||||
|             #if !(DEBUG && NO_HIDE_SCREENSHOTS) | ||||
|             OnFinished(); | ||||
|             #else | ||||
|             screenshot.MoveToVisibleLocation(); | ||||
|             screenshot.FormClosed += (sender, args) => disposer.Start(); | ||||
|             #endif | ||||
|         } | ||||
| 
 | ||||
|         private void OnFinished(){ | ||||
|             screenshot.Location = ControlExtensions.InvisibleLocation; | ||||
|             owner.IsWaiting = false; | ||||
|             disposer.Start(); | ||||
|         } | ||||
| 
 | ||||
|         public void Dispose(){ | ||||
|             timeout.Dispose(); | ||||
|             disposer.Dispose(); | ||||
|             screenshot?.Dispose(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,32 +0,0 @@ | ||||
| using System; | ||||
| using TweetLib.Audio; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Notification{ | ||||
|     sealed class SoundNotification : IDisposable{ | ||||
|         public string SupportedFormats => player.SupportedFormats; | ||||
|         public event EventHandler<PlaybackErrorEventArgs> PlaybackError; | ||||
| 
 | ||||
|         private readonly AudioPlayer player; | ||||
| 
 | ||||
|         public SoundNotification(){ | ||||
|             this.player = AudioPlayer.New(); | ||||
|             this.player.PlaybackError += Player_PlaybackError; | ||||
|         } | ||||
| 
 | ||||
|         public void Play(string file){ | ||||
|             player.Play(file); | ||||
|         } | ||||
| 
 | ||||
|         public bool SetVolume(int volume){ | ||||
|             return player.SetVolume(volume); | ||||
|         } | ||||
| 
 | ||||
|         private void Player_PlaybackError(object sender, PlaybackErrorEventArgs e){ | ||||
|             PlaybackError?.Invoke(this, e); | ||||
|         } | ||||
| 
 | ||||
|         public void Dispose(){ | ||||
|             player.Dispose(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,95 +0,0 @@ | ||||
| using System; | ||||
| using System.Text; | ||||
| using TweetDuck.Core.Bridge; | ||||
| using TweetDuck.Resources; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Notification{ | ||||
|     sealed class TweetNotification{ | ||||
|         private const string DefaultFontSizeClass = "medium"; | ||||
|         private const string DefaultHeadContents = @"<meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>"; | ||||
| 
 | ||||
|         private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css"); | ||||
| 
 | ||||
|         private static string ExampleTweetHTML; | ||||
| 
 | ||||
|         public static TweetNotification ExampleTweet{ | ||||
|             get{ | ||||
|                 if (ExampleTweetHTML == null){ | ||||
|                     ExampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true); | ||||
| 
 | ||||
|                     #if DEBUG | ||||
|                     ExampleTweetHTML = ExampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>"); | ||||
|                     #endif | ||||
|                 } | ||||
| 
 | ||||
|                 return new TweetNotification(string.Empty, string.Empty, "Home", ExampleTweetHTML, 95, string.Empty, string.Empty, true); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public enum Position{ | ||||
|             TopLeft, TopRight, BottomLeft, BottomRight, Custom | ||||
|         } | ||||
| 
 | ||||
|         public enum Size{ | ||||
|             Auto, Custom | ||||
|         } | ||||
| 
 | ||||
|         public string ColumnId { get; } | ||||
|         public string ChirpId { get; } | ||||
| 
 | ||||
|         public string ColumnTitle { get; } | ||||
|         public string TweetUrl { get; } | ||||
|         public string QuoteUrl { get; } | ||||
|          | ||||
|         private readonly string html; | ||||
|         private readonly int characters; | ||||
|         private readonly bool isExample; | ||||
| 
 | ||||
|         public TweetNotification(string columnId, string chirpId, string title, string html, int characters, string tweetUrl, string quoteUrl) : this(columnId, chirpId, title, html, characters, tweetUrl, quoteUrl, false){} | ||||
| 
 | ||||
|         private TweetNotification(string columnId, string chirpId, string title, string html, int characters, string tweetUrl, string quoteUrl, bool isExample){ | ||||
|             this.ColumnId = columnId; | ||||
|             this.ChirpId = chirpId; | ||||
| 
 | ||||
|             this.ColumnTitle = title; | ||||
|             this.TweetUrl = tweetUrl; | ||||
|             this.QuoteUrl = quoteUrl; | ||||
| 
 | ||||
|             this.html = html; | ||||
|             this.characters = characters; | ||||
|             this.isExample = isExample; | ||||
|         } | ||||
| 
 | ||||
|         public int GetDisplayDuration(int value){ | ||||
|             return 2000+Math.Max(1000, value*characters); | ||||
|         } | ||||
| 
 | ||||
|         public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){ | ||||
|             StringBuilder build = new StringBuilder(); | ||||
|             build.Append("<!DOCTYPE html>"); | ||||
|             build.Append("<html class='os-windows txt-base-").Append(TweetDeckBridge.FontSizeClass ?? DefaultFontSizeClass).Append("'>"); | ||||
|             build.Append("<head>").Append(TweetDeckBridge.NotificationHeadContents ?? DefaultHeadContents); | ||||
|              | ||||
|             if (enableCustomCSS){ | ||||
|                 build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>"); | ||||
| 
 | ||||
|                 if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){ | ||||
|                     build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>"); | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             build.Append("</head>"); | ||||
|             build.Append("<body class='scroll-styled-v"); | ||||
| 
 | ||||
|             if (!string.IsNullOrEmpty(bodyClasses)){ | ||||
|                 build.Append(' ').Append(bodyClasses); | ||||
|             } | ||||
| 
 | ||||
|             build.Append('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='column' style='width:100%;height:auto;overflow:initial;'>"); | ||||
|             build.Append(html); | ||||
|             build.Append("</div></body>"); | ||||
|             build.Append("</html>"); | ||||
|             return build.ToString(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,41 +0,0 @@ | ||||
| using System.ComponentModel; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other{ | ||||
|     sealed partial class FormAbout : Form{ | ||||
|         private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki"; | ||||
|         private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues"; | ||||
| 
 | ||||
|         public FormAbout(){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             Text = "About "+Program.BrandName+" "+Program.VersionTag; | ||||
| 
 | ||||
|             labelDescription.Text = "TweetDuck was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license."; | ||||
|              | ||||
|             labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website)); | ||||
|             labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink)); | ||||
|             labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink)); | ||||
|         } | ||||
| 
 | ||||
|         private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){ | ||||
|             BrowserUtils.OpenExternalBrowserUnsafe(e.Link.LinkData as string); | ||||
|         } | ||||
| 
 | ||||
|         private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){ | ||||
|             ShowGuide(); | ||||
|         } | ||||
| 
 | ||||
|         private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e){ | ||||
|             e.Cancel = true; | ||||
|             ShowGuide(); | ||||
|         } | ||||
| 
 | ||||
|         private void ShowGuide(){ | ||||
|             new FormGuide().Show(); | ||||
|             Close(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,246 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!--  | ||||
|     Microsoft ResX Schema  | ||||
|      | ||||
|     Version 2.0 | ||||
|      | ||||
|     The primary goals of this format is to allow a simple XML format  | ||||
|     that is mostly human readable. The generation and parsing of the  | ||||
|     various data types are done through the TypeConverter classes  | ||||
|     associated with the data types. | ||||
|      | ||||
|     Example: | ||||
|      | ||||
|     ... ado.net/XML headers & schema ... | ||||
|     <resheader name="resmimetype">text/microsoft-resx</resheader> | ||||
|     <resheader name="version">2.0</resheader> | ||||
|     <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||||
|     <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||||
|     <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||||
|     <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||||
|     <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||||
|         <value>[base64 mime encoded serialized .NET Framework object]</value> | ||||
|     </data> | ||||
|     <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||||
|         <comment>This is a comment</comment> | ||||
|     </data> | ||||
|                  | ||||
|     There are any number of "resheader" rows that contain simple  | ||||
|     name/value pairs. | ||||
|      | ||||
|     Each data row contains a name, and value. The row also contains a  | ||||
|     type or mimetype. Type corresponds to a .NET class that support  | ||||
|     text/value conversion through the TypeConverter architecture.  | ||||
|     Classes that don't support this are serialized and stored with the  | ||||
|     mimetype set. | ||||
|      | ||||
|     The mimetype is used for serialized objects, and tells the  | ||||
|     ResXResourceReader how to depersist the object. This is currently not  | ||||
|     extensible. For a given mimetype the value must be set accordingly: | ||||
|      | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format  | ||||
|     that the ResXResourceWriter will generate, however the reader can  | ||||
|     read any of the formats listed below. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.binary.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.soap.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.bytearray.base64 | ||||
|     value   : The object must be serialized into a byte array  | ||||
|             : using a System.ComponentModel.TypeConverter | ||||
|             : and then encoded with base64 encoding. | ||||
|     --> | ||||
|   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||||
|     <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||||
|     <xsd:element name="root" msdata:IsDataSet="true"> | ||||
|       <xsd:complexType> | ||||
|         <xsd:choice maxOccurs="unbounded"> | ||||
|           <xsd:element name="metadata"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" use="required" type="xsd:string" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="assembly"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:attribute name="alias" type="xsd:string" /> | ||||
|               <xsd:attribute name="name" type="xsd:string" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="data"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="resheader"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|         </xsd:choice> | ||||
|       </xsd:complexType> | ||||
|     </xsd:element> | ||||
|   </xsd:schema> | ||||
|   <resheader name="resmimetype"> | ||||
|     <value>text/microsoft-resx</value> | ||||
|   </resheader> | ||||
|   <resheader name="version"> | ||||
|     <value>2.0</value> | ||||
|   </resheader> | ||||
|   <resheader name="reader"> | ||||
|     <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <resheader name="writer"> | ||||
|     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> | ||||
|   <data name="pictureLogo.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|     <value> | ||||
|         iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAABGdBTUEAALGOfPtRkwAAACBjSFJNAACH | ||||
|         DwAAjA8AAP1SAACBQAAAfXkAAOmLAAA85QAAGcxzPIV3AAAKOWlDQ1BQaG90b3Nob3AgSUNDIHByb2Zp | ||||
|         bGUAAEjHnZZ3VFTXFofPvXd6oc0wAlKG3rvAANJ7k15FYZgZYCgDDjM0sSGiAhFFRJoiSFDEgNFQJFZE | ||||
|         sRAUVLAHJAgoMRhFVCxvRtaLrqy89/Ly++Osb+2z97n77L3PWhcAkqcvl5cGSwGQyhPwgzyc6RGRUXTs | ||||
|         AIABHmCAKQBMVka6X7B7CBDJy82FniFyAl8EAfB6WLwCcNPQM4BOB/+fpFnpfIHomAARm7M5GSwRF4g4 | ||||
|         JUuQLrbPipgalyxmGCVmvihBEcuJOWGRDT77LLKjmNmpPLaIxTmns1PZYu4V8bZMIUfEiK+ICzO5nCwR | ||||
|         3xKxRoowlSviN+LYVA4zAwAUSWwXcFiJIjYRMYkfEuQi4uUA4EgJX3HcVyzgZAvEl3JJS8/hcxMSBXQd | ||||
|         li7d1NqaQffkZKVwBALDACYrmcln013SUtOZvBwAFu/8WTLi2tJFRbY0tba0NDQzMv2qUP91829K3NtF | ||||
|         ehn4uWcQrf+L7a/80hoAYMyJarPziy2uCoDOLQDI3fti0zgAgKSobx3Xv7oPTTwviQJBuo2xcVZWlhGX | ||||
|         wzISF/QP/U+Hv6GvvmckPu6P8tBdOfFMYYqALq4bKy0lTcinZ6QzWRy64Z+H+B8H/nUeBkGceA6fwxNF | ||||
|         hImmjMtLELWbx+YKuGk8Opf3n5r4D8P+pMW5FonS+BFQY4yA1HUqQH7tBygKESDR+8Vd/6NvvvgwIH55 | ||||
|         4SqTi3P/7zf9Z8Gl4iWDm/A5ziUohM4S8jMX98TPEqABAUgCKpAHykAd6ABDYAasgC1wBG7AG/iDEBAJ | ||||
|         VgMWSASpgA+yQB7YBApBMdgJ9oBqUAcaQTNoBcdBJzgFzoNL4Bq4AW6D+2AUTIBnYBa8BgsQBGEhMkSB | ||||
|         5CEVSBPSh8wgBmQPuUG+UBAUCcVCCRAPEkJ50GaoGCqDqqF6qBn6HjoJnYeuQIPQXWgMmoZ+h97BCEyC | ||||
|         qbASrAUbwwzYCfaBQ+BVcAK8Bs6FC+AdcCXcAB+FO+Dz8DX4NjwKP4PnEIAQERqiihgiDMQF8UeikHiE | ||||
|         j6xHipAKpAFpRbqRPuQmMorMIG9RGBQFRUcZomxRnqhQFAu1BrUeVYKqRh1GdaB6UTdRY6hZ1Ec0Ga2I | ||||
|         1kfboL3QEegEdBa6EF2BbkK3oy+ib6Mn0K8xGAwNo42xwnhiIjFJmLWYEsw+TBvmHGYQM46Zw2Kx8lh9 | ||||
|         rB3WH8vECrCF2CrsUexZ7BB2AvsGR8Sp4Mxw7rgoHA+Xj6vAHcGdwQ3hJnELeCm8Jt4G749n43PwpfhG | ||||
|         fDf+On4Cv0CQJmgT7AghhCTCJkIloZVwkfCA8JJIJKoRrYmBRC5xI7GSeIx4mThGfEuSIemRXEjRJCFp | ||||
|         B+kQ6RzpLuklmUzWIjuSo8gC8g5yM/kC+RH5jQRFwkjCS4ItsUGiRqJDYkjiuSReUlPSSXK1ZK5kheQJ | ||||
|         yeuSM1J4KS0pFymm1HqpGqmTUiNSc9IUaVNpf+lU6RLpI9JXpKdksDJaMm4ybJkCmYMyF2TGKQhFneJC | ||||
|         YVE2UxopFykTVAxVm+pFTaIWU7+jDlBnZWVkl8mGyWbL1sielh2lITQtmhcthVZKO04bpr1borTEaQln | ||||
|         yfYlrUuGlszLLZVzlOPIFcm1yd2WeydPl3eTT5bfJd8p/1ABpaCnEKiQpbBf4aLCzFLqUtulrKVFS48v | ||||
|         vacIK+opBimuVTyo2K84p6Ss5KGUrlSldEFpRpmm7KicpFyufEZ5WoWiYq/CVSlXOavylC5Ld6Kn0Cvp | ||||
|         vfRZVUVVT1Whar3qgOqCmrZaqFq+WpvaQ3WCOkM9Xr1cvUd9VkNFw08jT6NF454mXpOhmai5V7NPc15L | ||||
|         Wytca6tWp9aUtpy2l3audov2Ax2yjoPOGp0GnVu6GF2GbrLuPt0berCehV6iXo3edX1Y31Kfq79Pf9AA | ||||
|         bWBtwDNoMBgxJBk6GWYathiOGdGMfI3yjTqNnhtrGEcZ7zLuM/5oYmGSYtJoct9UxtTbNN+02/R3Mz0z | ||||
|         llmN2S1zsrm7+QbzLvMXy/SXcZbtX3bHgmLhZ7HVosfig6WVJd+y1XLaSsMq1qrWaoRBZQQwShiXrdHW | ||||
|         ztYbrE9Zv7WxtBHYHLf5zdbQNtn2iO3Ucu3lnOWNy8ft1OyYdvV2o/Z0+1j7A/ajDqoOTIcGh8eO6o5s | ||||
|         xybHSSddpySno07PnU2c+c7tzvMuNi7rXM65Iq4erkWuA24ybqFu1W6P3NXcE9xb3Gc9LDzWepzzRHv6 | ||||
|         eO7yHPFS8mJ5NXvNelt5r/Pu9SH5BPtU+zz21fPl+3b7wX7efrv9HqzQXMFb0ekP/L38d/s/DNAOWBPw | ||||
|         YyAmMCCwJvBJkGlQXlBfMCU4JvhI8OsQ55DSkPuhOqHC0J4wybDosOaw+XDX8LLw0QjjiHUR1yIVIrmR | ||||
|         XVHYqLCopqi5lW4r96yciLaILoweXqW9KnvVldUKq1NWn46RjGHGnIhFx4bHHol9z/RnNjDn4rziauNm | ||||
|         WS6svaxnbEd2OXuaY8cp40zG28WXxU8l2CXsTphOdEisSJzhunCruS+SPJPqkuaT/ZMPJX9KCU9pS8Wl | ||||
|         xqae5Mnwknm9acpp2WmD6frphemja2zW7Fkzy/fhN2VAGasyugRU0c9Uv1BHuEU4lmmfWZP5Jiss60S2 | ||||
|         dDYvuz9HL2d7zmSue+63a1FrWWt78lTzNuWNrXNaV78eWh+3vmeD+oaCDRMbPTYe3kTYlLzpp3yT/LL8 | ||||
|         V5vDN3cXKBVsLBjf4rGlpVCikF84stV2a9021DbutoHt5turtn8sYhddLTYprih+X8IqufqN6TeV33za | ||||
|         Eb9joNSydP9OzE7ezuFdDrsOl0mX5ZaN7/bb3VFOLy8qf7UnZs+VimUVdXsJe4V7Ryt9K7uqNKp2Vr2v | ||||
|         Tqy+XeNc01arWLu9dn4fe9/Qfsf9rXVKdcV17w5wD9yp96jvaNBqqDiIOZh58EljWGPft4xvm5sUmoqb | ||||
|         PhziHRo9HHS4t9mqufmI4pHSFrhF2DJ9NProje9cv+tqNWytb6O1FR8Dx4THnn4f+/3wcZ/jPScYJ1p/ | ||||
|         0Pyhtp3SXtQBdeR0zHYmdo52RXYNnvQ+2dNt293+o9GPh06pnqo5LXu69AzhTMGZT2dzz86dSz83cz7h | ||||
|         /HhPTM/9CxEXbvUG9g5c9Ll4+ZL7pQt9Tn1nL9tdPnXF5srJq4yrndcsr3X0W/S3/2TxU/uA5UDHdavr | ||||
|         XTesb3QPLh88M+QwdP6m681Lt7xuXbu94vbgcOjwnZHokdE77DtTd1PuvriXeW/h/sYH6AdFD6UeVjxS | ||||
|         fNTws+7PbaOWo6fHXMf6Hwc/vj/OGn/2S8Yv7ycKnpCfVEyqTDZPmU2dmnafvvF05dOJZ+nPFmYKf5X+ | ||||
|         tfa5zvMffnP8rX82YnbiBf/Fp99LXsq/PPRq2aueuYC5R69TXy/MF72Rf3P4LeNt37vwd5MLWe+x7ys/ | ||||
|         6H7o/ujz8cGn1E+f/gUDmPP8usTo0wAAAAlwSFlzAAAuIgAALiIBquLdkgAAEVpJREFUeF7tXQl0VNd5 | ||||
|         Thq3Tk7tc+qmiZMm7YmdJj1OT3Gd0kTMG3mJnZQEJzaSjXETx0lqNwt2jWscQGixBAJLSMhgA8YYbIyQ | ||||
|         hHYJI4lFGMwioQWxyewgMdoXtKFd4u/337lPjDRv9pk3g+A75zsMWv577/fd+9/l3Rl9johu0Y/U/OIt | ||||
|         6sfrLwIYT2de+SJ4F/j34LfB+8Ap4L9J8uvvgfy9b4DfBO8EPy9DBBwC3gCI93fgDHAuuA7cDVaDzWAX | ||||
|         OAAOgcNgP9gN8vc+A4vB9eBr4M/Be8GAMiPgDIBAXwVDwbXgEbAPJC+RDToGvgfOBr8mi/UbAsIACMFp | ||||
|         ZRaYC7aBWuL5gh3gdvBZ8C5ZHV3hVwPQaM7hS0ETqCWQnqwDE8Apsnq6wC8GoJEPgFtAzt9aYviTPJ9k | ||||
|         gkGyuj6FrgagUd8CeVIcBLUaH0gcATeD35XV9wl0MQCN4OXjn8EroFZjA5m8qooE/1o2x6vwuQGouAEs | ||||
|         A7UadyPxOPgj2SyvwacGoMILwRsh3ThLTksx4BdkEz2GTwxABb8C8pJSqxGTgTvAb8jmegSvG4CK/QvI | ||||
|         O1Wtik8mXgSnyma7Da8agAo9Auq5kfI3+Shkhmy+W/CaAajI42CvrNjNRJ7jZksZXIZXDEAFWPxA3FTp | ||||
|         xVHQLRM8NgAFPwTejD1/InkH/biUxWl4ZAAK5LP3mynnO+JV0KWJ2W0DUNCXwVOy4Fu8zhrQ6SWqJwbk | ||||
|         WxQa2Mxop6fSWyk0rYlCUhspZEs9zdxSJ8iv+WuhW5vpqYw27d93nXvB26RUduGWAQg+36KwwCULv7VF | ||||
|         iDx3Wx1tPNxCBy92UU17P7VdHaLWniE619JLu8900Nv7m+jFLBgCM9gs/l3NmCqlqU+nt+D/msYtk3LZ | ||||
|         hcsGIPB/gPaPF7jyKrW+rwNZnJCUBoraUU9H63po9No1NM8++odGac/ZDno5r45C2QitESGF59gv5dVT | ||||
|         clUHzc5gEyb8XOaVa6DDsyMU67wBCHg7WCULsEkezi9/jEptbfKLCdzrn0s30b5znWiS6xgeuUaby1so | ||||
|         NKVubDSwGZzCOG3NzTdRQXW7+NnM4+b2atUDPAPeIeXTBEK4ZMA8i+Ca5Ir+LrOOOvtHKGkfcit6kp4m | ||||
|         sBh/zDGRqaMfzfEMh5CuZiXXUOgWE72Ua6L1Jc10qqmXRkbNo4nT2PMw2sHcESvl0wTCOGcAAv0DyFtv | ||||
|         rULGyAKsO9SMUERD6EnRO+t1M4F76++zTNTUNSjK9wYutvVhzujTTGGrMG/wqNCqiwX5IsA/SxmtgDBO | ||||
|         G7DBIqhN8oR3suEqQpnRh7watcOcU31pAvfCWUgZ1Y3Xy/YlCk+Z28rlqrTTvkwpoxUQyrEBCPCvoONz | ||||
|         fVTg1xkm6hkYQajr4Mktdpd5uecrE9jg5IpWWaJvwRN1SHKtaA/PCfMLmyiq2DxPaNUN5KMKg5RzHBDO | ||||
|         KQNSLYLZJFfgVSz3tMBDeM0BrMNlr9H6fXfJ8V7AErK7f1iW5hsMjYxSSnkjPbnxLP1vbq2YE04399KV | ||||
|         vmGak9/kqF0FUs5xQFj7BuAXvwM6ddDGOfiNnQ0IYxvbTrYjVWDiwkrFW6OB83DaEd/3/obOfiq71Ekt | ||||
|         3YOkzgg8NUTxPOdgLpiV1TE6K7vzASnrGBDCoQF8b0cz6ESyqDG77BvAOI2VxJycy7bX2q4QJoYgDVxo | ||||
|         7ZPR9QVv4OzNb7OyOll8mpnWTNPXnlwnZR0DQtg2AAHuAPnCkmbwieQREFZQjzCOwfPChlJs/3mt7cFo | ||||
|         YAPn5NTRsFwa6on3kII05zUIjt4uXv9icx099u5pCn7rKBkSynoMC1K/LuUVQBi7BswcF9gBWYzfZ5vE | ||||
|         RsZZnG/powjsVsXc4IYR/DtLix2POm+C9wGrPjWfK1nWl3v6MxA+FB1xxgeX6OF3TpKSdASsIiMMMK48 | ||||
|         TkpM4RwprwDC2TUgTw3uFFEZXgo2dA4glPNgu8pruyms0HxAJnaWThrBP7v2oHnfoQd48xWBelqmHe7t | ||||
|         Is2kNNJ/rj9HD648RsoKC+FVwgBj/MGDUl4BhNQ2AIH/FuTLq1aNtkfuFYWfXUEo18FGHKvrofg9DfRs | ||||
|         mknEcjQq2ABejeiFyEKTWXyUbU4z7fSLj0z06NpTELnKLLyl6BOZVDVgDM+8R8ps14AQy4Y6S1fmAXvg | ||||
|         npZ/oo0iiuro2VSYkdIgVhocX0zc0hQ2YDWWt3phIdrGp5+hW1vpZxsv0SOcZrR6uy1iFAQvLvqDlNmu | ||||
|         Ae9aCus0IQzn8/LL3QjnHbAZpZe6RE9fsL2O/jvTJFIdlzMz2URRhZflT/oer+fX0E/ePUXBttKMI3Ia | ||||
|         enNfhpRZ2wAI+XnQ4amnLXLamPdxHQ1i4+IL8AqqHvMMp6vdZ65Q8en2sXW5r/HbjZVYzVRoi+sMYZwx | ||||
|         saxWWbj1Swhn04B/BPkQSVNgu+TUAHKefB/LzMkE3s3PXgfxsbLRFNd5XlMWZT6AkDYN+JmVsE5QLEPz | ||||
|         WujXGY0iZ3N60GOHqheuDgzT4++U80SqJarz5DQUXfAcQlobACE5/fAb4jRFtkdOPatLWqmrf4RSKlvo | ||||
|         1fzL9MSGM7TuQJ04R7nR0dDRSw8mlGqL6gpXniDjkp2xyoKULyCslQH81MutCZgNWLz7+qaI83Jz9yAm | ||||
|         0A5q6/HeGb2/cPxyBxnivWEAT8T7c4Mjc+5EWCsD/gbkt4JqimyP6rGA+sRosqHgWD0Zlh/WFtUVsgHL | ||||
|         S44aI3O/hrBWBvCboN273cwTMJaHvEKZjEgsOkNKogcrIJViJVTeaIzefi/CWhnA7zJvGiesC+TVT85x | ||||
|         8wPryYYXNpl3upqiukQYsKKyR1my63sIa2UAv3WU3xelKbAj8k71lXz/nE76Em09A/Tjt5B+PF0BqUw6 | ||||
|         MogN2RSEtjKAHz+6twdg8k6Yz4NOuXceFKgorm7yTv5XmXRkVIk78H2EtjLgfpBv+moL7AR5Mv5NRp1X | ||||
|         byf4GzH5n3kn/6vkkbS8dCpCWxnAnzrikQFMPiRbWFAnbkXc6OAN2BNreAPmjfwvyQbEH9I0gD/yxfM3 | ||||
|         W/BxRFqTuBrYOzj+lsSNht0nG72bfphJVddgwL8jvJUBHk3C4yhNeCXfRJfaPL+p5i+8ln4Cq59KbSHd | ||||
|         ZVLloLJs7/0Ib2UAL0NbxgnpCWEC75Bnp5rog7IW6ujz7dURb+NsYxdvmswpQ0tItyiWod1KTJHmMpQ3 | ||||
|         YvxhR9qCukiekGenN4tHjU9+VENPbzpLcbtq6GS9954X+BLLtp/y7uTLNG/EmozRBd9GEVYG8Gf37LEU | ||||
|         0RNy7/+o8gqdae6jQxc76eCFTqqo7aTGzsBPSRdbeuihRG/3fpANiC85qkTkaB5F8Adr8CeaaArqKsUj | ||||
|         yiJ9by14C2/keXnpqXIV5pTYPblB8zZ9BcVYGcDH0f9nKaJH5I1ZSgMduNCF8DcOjtS0k+L13C/JBize | ||||
|         EWeYn3wbihpvgHwgw+/71RbUDfIo+FNuvdWl3UAFn+a+KM59vLzyUWl+IPMbFHVd97EXZgPuAb3yKSfq | ||||
|         ZSUeBbFFNSgi8PHRwRrzut8Xvd/Ma8FR+XYfSf4FyJ+PoymqM1TvRIakNtP098/TQ5h4psWXUnzBaRQT | ||||
|         uDhZ1yknXi/uei0JHZTlpbVBr6z7IoqzNkAlRHR9Ira4E/lEcv31O5Hq9Q2Qe9bCrJPU3T+EYgILPajT | ||||
|         r96v8F3qYSL/G5bszuKOjiLHeP2FBETkj5G0FlmDapp5amsrzfiwhh5ZPeFOpCXxNSWxnGauKaed2OI7 | ||||
|         8eZF3RCejXqjbj5MPWYDInJfZo1RpF0D+EOX7L4nbOzqdWqTSDN8J3Kst2sVbkF+sMGj4fmNlZR2uFas | ||||
|         uYflg3t/mLJy11lf530Q6/+3qoaw+vkOa4xibRvAgMj8oaYTRLe+eu3UnUgt8mhgIxLKKBhLvsdXltDc | ||||
|         zRV0qUXfXfKHBy7pID4ongUfKpfyOmXAf00U3ubVa0+IOCzAM+vKqOxCG6qhHzbziodvOvhq0rUk0k9Q | ||||
|         WPpLUl4BVMG2AbOyu+4CW1j8Jx1dvXaHLDx6/09XHabU0loaHNb32cG7n1zQT3w+fkgs75r6Pwl3S3kF | ||||
|         UA3bBjB+vvH8CnH1WqYLzeCukEcNVhnc459YXSaGf0evvk/O2OjovGqZdnQQn/l2NQVF5G2Qso4B1bFv | ||||
|         gCEs/X6kmhHNoCp5NHBDBPm1ShYbTKwQPZ1723T09tfTT4hnrL2D+h9N17Zdpd9uNI88UUet9viCGAFB | ||||
|         r29WpKxjQJXsG8AwLtv3sbhOpxUYjZi+5hg9s+E4zVpfRT9ZeZh+nFRKP11ZRqFry+mFD6soKreakg/V | ||||
|         iPzuz/V/TqVJ1E2s83UVX9yE2294baPV3y5AtRwboIRnT0OgUavAko++fZR++cFxKj7VQgPDI9Q3OCKE | ||||
|         HhgKjLOfc03d9ErqcX1TjiVhADLJdCnnOKB6jg1gGOP2pwsntQpAbxLzw/JSCsuqprNocCCgoaOPEgrP | ||||
|         0MOJJeZjZT17vUo++Vz6yU4poxVQTScNWJT5XfSefvNmQqMgJhuBhirxJTQ/4wRVXGqnUT9c0Drf3E3L | ||||
|         IfyPON2Ina0fer0gVoxJVcNBr31g9QZtFaiucwYwjEt2xdqcCywpRgRWOph0f7m+QuT/y+1XfbrD5Ztr | ||||
|         24/V05wtR8Wmzr/CS7JWscWrpHyaQNWdN0AJS7vDmFh2zmYqmkiZmnjF8WBCibhbuWbPeTqMyZiXnp4Y | ||||
|         wgdnfF2cl7Gc3x9Db+ccr/sEa4u87l9R3hAcnvllKZ8m0BTnDWAo4VmPGldyIXZSkRalGdwzeWTwaonP | ||||
|         gRZlV9O6vRcoq8JEu7E0LTnXSidMHSKNHK29Qofwf76Xw99/Dz8XiRXV7z48QjPeLkOqg+gwN2BEtyRv | ||||
|         WCNyQqRsNuGyAQxlya44p1KRPaqGQDxhCnrvGFlYlRZf558TcwxP+IEmuCXFI8eda6RcduGWAcaFaX9p | ||||
|         jD/0qdOp6GYia7K8pCp4UfqXpFx24ZYBDGVB6jeNKypMt0ywoPm8p9UYkW3zI8omwm0DGIbXN//AuKKy | ||||
|         VxSsVaGbiWYNhrBpdenPnHhkAAMTzUwUPnxTm6C2PWrbc1IWp+GxAQxDePavxlXkpiLazO2OKfqjlMMl | ||||
|         eMUABruPCt1kI4HbiuVm9PY/SRlchtcMYBjCMkJQMcwJN8HEzB0tqWrIGF3wvGy+W/CqAQwlLH2aknDY | ||||
|         xGthzYpPBnIHW1HRYozMeUw222143QDGtD8n3yP2CcKESZaSeAOaUFpuDM+8TzbXI/jEAIZxYepfGaIL | ||||
|         48yVngQpiVMO2qHEFq81Lkr32p819JkBKgyLMmeYD/Bu4NHAdU8sr1Ui85+SzfIafG4Aw7go4y5jbPEy | ||||
|         8TzhRpobxMitGjYu3fOOMWzrV2VzvApdDFDxw3mbpyhL92aL09RATkuibhitb35agIn2h7L6PoGuBqgw | ||||
|         hOc8Zow7UCQayyMiIPYOnONlmow7sEeJzPPoL+Q5C78YoAI76KBpMTtXK4nlLcZV6HVqz9MUyEfkMpkr | ||||
|         KnuMSz/ZpETkPCyrpwv8aoCKH8zdcPe0yG0vYum6X8wTLIivRoZczQgmVQ2izBIluvBVZeHWb8nq6IqA | ||||
|         MMASWDX9k+GN7X8wxO5JU5aXnjeLxoKxIVI4YYzKCQKrX7cUWpiJf/n7CWW1yrJ9WcaYwpeU8Cynj419 | ||||
|         hYAzwBJB8zbdHhyVd78xpuA5ZfHOWEzgWeixFVgSNiBldGG0DICj4uE7PyEzvx4Q6YQ/ECm+5Cgm0jxl | ||||
|         8Y5lyhsfPw9zpyrzU3zyp8ndRUAbwFDrZViQdlvQ/NQ7kKPvNkYX3GuIKbpPWfrJFP7IFyXu4FQYM1V5 | ||||
|         c//3lWV7p/A70JWobfcq4dlfN4Zn3WmYn3y7GseSgYCxulhW7Bb1p+YXb1Ev0uf+H9A3E1Z4VJUaAAAA | ||||
|         AElFTkSuQmCC | ||||
| </value> | ||||
|   </data> | ||||
| </root> | ||||
| @@ -1,71 +0,0 @@ | ||||
| using System; | ||||
| using System.Drawing; | ||||
| using System.Windows.Forms; | ||||
| using CefSharp; | ||||
| using CefSharp.WinForms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Handling; | ||||
| using TweetDuck.Core.Handling.General; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other{ | ||||
|     sealed partial class FormGuide : Form{ | ||||
|         private const string GuideUrl = "https://tweetduck.chylex.com/guide/v1/"; | ||||
| 
 | ||||
|         private readonly ChromiumWebBrowser browser; | ||||
| 
 | ||||
|         public FormGuide(){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             Text = Program.BrandName+" Guide"; | ||||
| 
 | ||||
|             FormBrowser owner = FormManager.TryFind<FormBrowser>(); | ||||
| 
 | ||||
|             if (owner != null){ | ||||
|                 Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4); | ||||
|                 VisibleChanged += (sender, args) => this.MoveToCenter(owner); | ||||
|             } | ||||
|              | ||||
|             this.browser = new ChromiumWebBrowser(GuideUrl){ | ||||
|                 MenuHandler = new ContextMenuGuide(), | ||||
|                 JsDialogHandler = new JavaScriptDialogHandler(), | ||||
|                 LifeSpanHandler = new LifeSpanHandler(), | ||||
|                 RequestHandler = new RequestHandlerBrowser() | ||||
|             }; | ||||
| 
 | ||||
|             browser.LoadingStateChanged += browser_LoadingStateChanged; | ||||
|             browser.FrameLoadStart += browser_FrameLoadStart; | ||||
|              | ||||
|             browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb(); | ||||
|             browser.Dock = DockStyle.None; | ||||
|             browser.Location = ControlExtensions.InvisibleLocation; | ||||
|             Controls.Add(browser); | ||||
| 
 | ||||
|             Disposed += (sender, args) => { | ||||
|                 Program.UserConfig.ZoomLevelChanged -= Config_ZoomLevelChanged; | ||||
|                 browser.Dispose(); | ||||
|             }; | ||||
| 
 | ||||
|             Program.UserConfig.ZoomLevelChanged += Config_ZoomLevelChanged; | ||||
|         } | ||||
| 
 | ||||
|         private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){ | ||||
|             if (!e.IsLoading){ | ||||
|                 this.InvokeAsyncSafe(() => { | ||||
|                     browser.Location = Point.Empty; | ||||
|                     browser.Dock = DockStyle.Fill; | ||||
|                 }); | ||||
| 
 | ||||
|                 browser.LoadingStateChanged -= browser_LoadingStateChanged; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){ | ||||
|             BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel); | ||||
|         } | ||||
| 
 | ||||
|         private void Config_ZoomLevelChanged(object sender, EventArgs e){ | ||||
|             BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,222 +0,0 @@ | ||||
| using System; | ||||
| using System.Drawing; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other{ | ||||
|     [Flags] | ||||
|     enum ControlType{ | ||||
|         None = 0, | ||||
|         Accept = 1, // triggered by pressing enter when a non-button is focused | ||||
|         Cancel = 2, // triggered by closing the dialog without pressing a button | ||||
|         Focused = 4 // active control after the dialog is showed | ||||
|     } | ||||
| 
 | ||||
|     sealed partial class FormMessage : Form{ | ||||
|         public const string OK = "OK"; | ||||
|         public const string Yes = "Yes"; | ||||
|         public const string No = "No"; | ||||
|         public const string Cancel = "Cancel"; | ||||
|         public const string Retry = "Retry"; | ||||
|         public const string Ignore = "Ignore"; | ||||
|         public const string Exit = "Exit"; | ||||
| 
 | ||||
|         public static bool Information(string caption, string text, string buttonAccept, string buttonCancel = null){ | ||||
|             return Show(caption, text, MessageBoxIcon.Information, buttonAccept, buttonCancel); | ||||
|         } | ||||
| 
 | ||||
|         public static bool Warning(string caption, string text, string buttonAccept, string buttonCancel = null){ | ||||
|             return Show(caption, text, MessageBoxIcon.Warning, buttonAccept, buttonCancel); | ||||
|         } | ||||
| 
 | ||||
|         public static bool Error(string caption, string text, string buttonAccept, string buttonCancel = null){ | ||||
|             return Show(caption, text, MessageBoxIcon.Error, buttonAccept, buttonCancel); | ||||
|         } | ||||
| 
 | ||||
|         public static bool Question(string caption, string text, string buttonAccept, string buttonCancel = null){ | ||||
|             return Show(caption, text, MessageBoxIcon.Question, buttonAccept, buttonCancel); | ||||
|         } | ||||
| 
 | ||||
|         public static bool Show(string caption, string text, MessageBoxIcon icon, string button){ | ||||
|             return Show(caption, text, icon, button, null); | ||||
|         } | ||||
| 
 | ||||
|         public static bool Show(string caption, string text, MessageBoxIcon icon, string buttonAccept, string buttonCancel){ | ||||
|             using(FormMessage message = new FormMessage(caption, text, icon)){ | ||||
|                 if (buttonCancel == null){ | ||||
|                     message.AddButton(buttonAccept, DialogResult.OK, ControlType.Cancel | ControlType.Focused); | ||||
|                 } | ||||
|                 else{ | ||||
|                     message.AddButton(buttonCancel, DialogResult.Cancel, ControlType.Cancel); | ||||
|                     message.AddButton(buttonAccept, DialogResult.OK, ControlType.Accept | ControlType.Focused); | ||||
|                 } | ||||
| 
 | ||||
|                 return message.ShowDialog() == DialogResult.OK; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Instance | ||||
| 
 | ||||
|         public Button ClickedButton { get; private set; } | ||||
| 
 | ||||
|         public bool HasIcon => icon != null; | ||||
|         public int ActionPanelY => panelActions.Location.Y; | ||||
| 
 | ||||
|         private int ClientWidth{ | ||||
|             get => ClientSize.Width; | ||||
|             set => ClientSize = new Size(value, ClientSize.Height); | ||||
|         } | ||||
| 
 | ||||
|         private int ButtonDistance{ | ||||
|             get => BrowserUtils.Scale(96, dpiScale); | ||||
|         } | ||||
| 
 | ||||
|         private readonly Icon icon; | ||||
|         private readonly bool isReady; | ||||
|         private readonly float dpiScale; | ||||
| 
 | ||||
|         private int realFormWidth, minFormWidth; | ||||
|         private int buttonCount; | ||||
|         private int prevLabelWidth, prevLabelHeight; | ||||
|         private bool wasLabelMultiline; | ||||
| 
 | ||||
|         public FormMessage(string caption, string text, MessageBoxIcon messageIcon){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             this.dpiScale = this.GetDPIScale(); | ||||
| 
 | ||||
|             this.prevLabelWidth = labelMessage.Width; | ||||
|             this.prevLabelHeight = labelMessage.Height; | ||||
|             this.minFormWidth = BrowserUtils.Scale(42, dpiScale); | ||||
| 
 | ||||
|             switch(messageIcon){ | ||||
|                 case MessageBoxIcon.Information: | ||||
|                     icon = SystemIcons.Information; | ||||
|                     break; | ||||
| 
 | ||||
|                 case MessageBoxIcon.Warning: | ||||
|                     icon = SystemIcons.Warning; | ||||
|                     break; | ||||
|                      | ||||
|                 case MessageBoxIcon.Error: | ||||
|                     icon = SystemIcons.Error; | ||||
|                     break; | ||||
| 
 | ||||
|                 case MessageBoxIcon.Question: | ||||
|                     icon = SystemIcons.Question; | ||||
|                     break; | ||||
| 
 | ||||
|                 default: | ||||
|                     icon = null; | ||||
|                     labelMessage.Location = new Point(BrowserUtils.Scale(19, dpiScale), labelMessage.Location.Y); // 19 instead of 9 due to larger height | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             this.isReady = true; | ||||
| 
 | ||||
|             this.Text = caption; | ||||
|             this.labelMessage.Text = text.Replace("\r", "").Replace("\n", Environment.NewLine); | ||||
|         } | ||||
| 
 | ||||
|         private void FormMessage_SizeChanged(object sender, EventArgs e){ | ||||
|             RecalculateButtonLocation(); | ||||
|         } | ||||
| 
 | ||||
|         public Button AddButton(string title, ControlType type){ | ||||
|             return AddButton(title, DialogResult.OK, type); | ||||
|         } | ||||
| 
 | ||||
|         public Button AddButton(string title, DialogResult result = DialogResult.OK, ControlType type = ControlType.None){ | ||||
|             Button button = new Button{ | ||||
|                 Anchor = AnchorStyles.Bottom, | ||||
|                 Font = SystemFonts.MessageBoxFont, | ||||
|                 Location = new Point(0, 12), | ||||
|                 Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)), | ||||
|                 TabIndex = buttonCount, | ||||
|                 Text = title, | ||||
|                 UseVisualStyleBackColor = true | ||||
|             }; | ||||
| 
 | ||||
|             button.Click += (sender, args) => { | ||||
|                 ClickedButton = (Button)sender; | ||||
|                 DialogResult = result; | ||||
|                 Close(); | ||||
|             }; | ||||
| 
 | ||||
|             panelActions.Controls.Add(button); | ||||
|             ++buttonCount; | ||||
| 
 | ||||
|             minFormWidth += ButtonDistance; | ||||
|             ClientWidth = Math.Max(realFormWidth, minFormWidth); | ||||
|             RecalculateButtonLocation(); | ||||
| 
 | ||||
|             if (type.HasFlag(ControlType.Accept)){ | ||||
|                 AcceptButton = button; | ||||
|             } | ||||
| 
 | ||||
|             if (type.HasFlag(ControlType.Cancel)){ | ||||
|                 CancelButton = button; | ||||
|             } | ||||
| 
 | ||||
|             if (type.HasFlag(ControlType.Focused)){ | ||||
|                 ActiveControl = button; | ||||
|             } | ||||
| 
 | ||||
|             return button; | ||||
|         } | ||||
| 
 | ||||
|         public void AddActionControl(Control control){ | ||||
|             panelActions.Controls.Add(control); | ||||
|              | ||||
|             control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale)); | ||||
| 
 | ||||
|             minFormWidth += control.Width+control.Margin.Horizontal; | ||||
|             ClientWidth = Math.Max(realFormWidth, minFormWidth); | ||||
|         } | ||||
|          | ||||
|         private void RecalculateButtonLocation(){ | ||||
|             int dist = ButtonDistance; | ||||
|             int start = ClientWidth-dist; | ||||
| 
 | ||||
|             for(int index = 0; index < buttonCount; index++){ | ||||
|                 Control control = panelActions.Controls[index]; | ||||
|                 control.Location = new Point(start-index*dist, control.Location.Y); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void labelMessage_SizeChanged(object sender, EventArgs e){ | ||||
|             if (!isReady){ | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height; | ||||
|             int labelOffset = BrowserUtils.Scale(8, dpiScale); | ||||
| 
 | ||||
|             if (isMultiline && !wasLabelMultiline){ | ||||
|                 labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-labelOffset); | ||||
|                 prevLabelHeight += labelOffset; | ||||
|             } | ||||
|             else if (!isMultiline && wasLabelMultiline){ | ||||
|                 labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+labelOffset); | ||||
|                 prevLabelHeight -= labelOffset; | ||||
|             } | ||||
| 
 | ||||
|             realFormWidth = ClientWidth-(icon == null ? BrowserUtils.Scale(50, dpiScale) : 0)+labelMessage.Width-prevLabelWidth; | ||||
|             ClientWidth = Math.Max(realFormWidth, minFormWidth); | ||||
|             Height += labelMessage.Height-prevLabelHeight; | ||||
| 
 | ||||
|             prevLabelWidth = labelMessage.Width; | ||||
|             prevLabelHeight = labelMessage.Height; | ||||
|             wasLabelMultiline = isMultiline; | ||||
|         } | ||||
| 
 | ||||
|         protected override void OnPaint(PaintEventArgs e){ | ||||
|             if (icon != null){ | ||||
|                 e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1+BrowserUtils.Scale(25, dpiScale)); | ||||
|             } | ||||
| 
 | ||||
|             base.OnPaint(e); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,93 +0,0 @@ | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.Drawing; | ||||
| using System.Linq; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Plugins; | ||||
| using TweetDuck.Plugins.Controls; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other{ | ||||
|     sealed partial class FormPlugins : Form{ | ||||
|         private readonly PluginManager pluginManager; | ||||
|          | ||||
|         public FormPlugins(){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             Text = Program.BrandName+" Plugins"; | ||||
|         } | ||||
| 
 | ||||
|         public FormPlugins(PluginManager pluginManager) : this(){ | ||||
|             this.pluginManager = pluginManager; | ||||
| 
 | ||||
|             Shown += (sender, args) => { | ||||
|                 Program.UserConfig.PluginsWindow.Restore(this, false); | ||||
|                 ReloadPluginList(); | ||||
|             }; | ||||
| 
 | ||||
|             FormClosed += (sender, args) => { | ||||
|                 Program.UserConfig.PluginsWindow.Save(this); | ||||
|                 Program.UserConfig.Save(); | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         private int GetPluginOrderIndex(Plugin plugin){ | ||||
|             return !plugin.CanRun ? 0 : pluginManager.Config.IsEnabled(plugin) ? 1 : 2; | ||||
|         } | ||||
| 
 | ||||
|         private void ReloadPluginList(){ | ||||
|             flowLayoutPlugins.SuspendLayout(); | ||||
|             flowLayoutPlugins.Controls.Clear(); | ||||
| 
 | ||||
|             foreach(Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)){ | ||||
|                 flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin)); | ||||
| 
 | ||||
|                 flowLayoutPlugins.Controls.Add(new Panel{ | ||||
|                     BackColor = Color.DimGray, | ||||
|                     Margin = new Padding(0), | ||||
|                     Size = new Size(1, 1) | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             flowLayoutPlugins.ResumeLayout(true); | ||||
|              | ||||
|             // sorry, I guess... | ||||
|             Padding = new Padding(Padding.Left, Padding.Top, Padding.Right+1, Padding.Bottom); | ||||
|             Padding = new Padding(Padding.Left, Padding.Top, Padding.Right-1, Padding.Bottom); | ||||
|         } | ||||
| 
 | ||||
|         private void flowLayoutPlugins_Resize(object sender, EventArgs e){ | ||||
|             if (flowLayoutPlugins.Controls.Count == 0){ | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             Control lastControl = flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count-1]; | ||||
|             bool showScrollBar = lastControl.Location.Y+lastControl.Height >= flowLayoutPlugins.Height; | ||||
|             int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0; | ||||
|              | ||||
|             flowLayoutPlugins.AutoScroll = showScrollBar; | ||||
|             flowLayoutPlugins.VerticalScroll.Visible = showScrollBar; | ||||
| 
 | ||||
|             foreach(Control control in flowLayoutPlugins.Controls){ | ||||
|                 control.Width = flowLayoutPlugins.Width-control.Margin.Horizontal-horizontalOffset; | ||||
|             } | ||||
|              | ||||
|             lastControl.Visible = !showScrollBar; | ||||
|             flowLayoutPlugins.Focus(); | ||||
|         } | ||||
| 
 | ||||
|         private void btnOpenFolder_Click(object sender, EventArgs e){ | ||||
|             using(Process.Start("explorer.exe", "\""+pluginManager.PathCustomPlugins+"\"")){} | ||||
|         } | ||||
| 
 | ||||
|         private void btnReload_Click(object sender, EventArgs e){ | ||||
|             if (FormMessage.Warning("Reloading Plugins", "This will also reload the browser window. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){ | ||||
|                 pluginManager.Reload(); | ||||
|                 ReloadPluginList(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void btnClose_Click(object sender, EventArgs e){ | ||||
|             Close(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,176 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Drawing; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Other.Settings; | ||||
| using TweetDuck.Core.Other.Settings.Dialogs; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetDuck.Plugins; | ||||
| using TweetDuck.Updates; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other{ | ||||
|     sealed partial class FormSettings : Form{ | ||||
|         private readonly FormBrowser browser; | ||||
|         private readonly PluginManager plugins; | ||||
| 
 | ||||
|         private readonly int buttonHeight; | ||||
| 
 | ||||
|         private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(4); | ||||
|         private SettingsTab currentTab; | ||||
| 
 | ||||
|         public bool ShouldReloadBrowser { get; private set; } | ||||
| 
 | ||||
|         public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, Type startTab){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             Text = Program.BrandName+" Options"; | ||||
| 
 | ||||
|             this.browser = browser; | ||||
|             this.browser.PauseNotification(); | ||||
| 
 | ||||
|             this.plugins = plugins; | ||||
|              | ||||
|             this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1; | ||||
| 
 | ||||
|             AddButton("General", () => new TabSettingsGeneral(updates)); | ||||
|             AddButton("System Tray", () => new TabSettingsTray()); | ||||
|             AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false))); | ||||
|             AddButton("Sounds", () => new TabSettingsSounds()); | ||||
|             AddButton("Feedback", () => new TabSettingsFeedback()); | ||||
|             AddButton("Advanced", () => new TabSettingsAdvanced(browser.ReinjectCustomCSS)); | ||||
| 
 | ||||
|             SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]); | ||||
|         } | ||||
| 
 | ||||
|         private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){ | ||||
|             foreach(SettingsTab tab in tabs.Values){ | ||||
|                 if (tab.IsInitialized){ | ||||
|                     tab.Control.OnClosing(); | ||||
|                     tab.Control.Dispose(); | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             Program.UserConfig.Save(); | ||||
|             browser.ResumeNotification(); | ||||
|         } | ||||
| 
 | ||||
|         private void btnManageOptions_Click(object sender, EventArgs e){ | ||||
|             foreach(SettingsTab tab in tabs.Values){ | ||||
|                 if (tab.IsInitialized){ | ||||
|                     tab.Control.OnClosing(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             using(DialogSettingsManage dialog = new DialogSettingsManage(plugins)){ | ||||
|                 if (dialog.ShowDialog() == DialogResult.OK){ | ||||
|                     FormClosing -= FormSettings_FormClosing; | ||||
|                     browser.ResumeNotification(); | ||||
| 
 | ||||
|                     ShouldReloadBrowser = dialog.ShouldReloadBrowser; | ||||
|                     Close(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void btnClose_Click(object sender, EventArgs e){ | ||||
|             Close(); | ||||
|         } | ||||
| 
 | ||||
|         private void AddButton<T>(string title, Func<T> constructor) where T : BaseTabSettings{ | ||||
|             FlatButton btn = new FlatButton{ | ||||
|                 BackColor = SystemColors.Control, | ||||
|                 FlatStyle = FlatStyle.Flat, | ||||
|                 Location = new Point(0, (buttonHeight+1)*(panelButtons.Controls.Count/2)), | ||||
|                 Margin = new Padding(0), | ||||
|                 Size = new Size(panelButtons.Width, buttonHeight), | ||||
|                 Text = title, | ||||
|                 UseVisualStyleBackColor = true | ||||
|             }; | ||||
| 
 | ||||
|             btn.FlatAppearance.BorderSize = 0; | ||||
|             btn.FlatAppearance.MouseDownBackColor = Color.FromArgb(179, 213, 232); | ||||
|             btn.FlatAppearance.MouseOverBackColor = Color.FromArgb(216, 230, 237); | ||||
|              | ||||
|             panelButtons.Controls.Add(btn); | ||||
| 
 | ||||
|             panelButtons.Controls.Add(new Panel{ | ||||
|                 BackColor = Color.DimGray, | ||||
|                 Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count-1].Location.Y+buttonHeight), | ||||
|                 Margin = new Padding(0), | ||||
|                 Size = new Size(panelButtons.Width, 1) | ||||
|             }); | ||||
| 
 | ||||
|             tabs.Add(typeof(T), new SettingsTab(btn, constructor)); | ||||
| 
 | ||||
|             btn.Click += (sender, args) => SelectTab<T>(); | ||||
|         } | ||||
| 
 | ||||
|         private void SelectTab<T>() where T : BaseTabSettings{ | ||||
|             SelectTab(tabs[typeof(T)]); | ||||
|         } | ||||
| 
 | ||||
|         private void SelectTab(SettingsTab tab){ | ||||
|             if (currentTab != null){ | ||||
|                 currentTab.Button.BackColor = SystemColors.Control; | ||||
|             } | ||||
|              | ||||
|             tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor; | ||||
| 
 | ||||
|             if (!tab.IsInitialized){ | ||||
|                 foreach(Control control in tab.Control.InteractiveControls){ | ||||
|                     if (control is ComboBox){ | ||||
|                         control.MouseLeave += control_MouseLeave; | ||||
|                     } | ||||
|                     else if (control is TrackBar){ | ||||
|                         control.MouseWheel += control_MouseWheel; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 tab.Control.OnReady(); | ||||
|             } | ||||
|              | ||||
|             panelContents.VerticalScroll.Enabled = false; // required to stop animation that would otherwise break everything | ||||
|             panelContents.PerformLayout(); | ||||
| 
 | ||||
|             panelContents.SuspendLayout(); | ||||
|             panelContents.VerticalScroll.Value = 0; // https://gfycat.com/GrotesqueTastyAstarte | ||||
|             panelContents.Controls.Clear(); | ||||
|             panelContents.Controls.Add(tab.Control); | ||||
|             panelContents.ResumeLayout(true); | ||||
| 
 | ||||
|             panelContents.VerticalScroll.Enabled = true; | ||||
|             panelContents.Focus(); | ||||
| 
 | ||||
|             currentTab = tab; | ||||
|         } | ||||
| 
 | ||||
|         private void control_MouseLeave(object sender, EventArgs e){ | ||||
|             panelContents.Focus(); | ||||
|         } | ||||
| 
 | ||||
|         private void control_MouseWheel(object sender, MouseEventArgs e){ | ||||
|             ((HandledMouseEventArgs)e).Handled = true; | ||||
|             panelContents.Focus(); | ||||
|         } | ||||
| 
 | ||||
|         private sealed class SettingsTab{ | ||||
|             public Button Button { get; } | ||||
| 
 | ||||
|             public BaseTabSettings Control{ | ||||
|                 get => control ?? (control = constructor()); | ||||
|                 set => control = value; | ||||
|             } | ||||
| 
 | ||||
|             public bool IsInitialized => control != null; | ||||
| 
 | ||||
|             private readonly Func<BaseTabSettings> constructor; | ||||
|             private BaseTabSettings control; | ||||
| 
 | ||||
|             public SettingsTab(Button button, Func<BaseTabSettings> constructor){ | ||||
|                 this.Button = button; | ||||
|                 this.constructor = constructor; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,27 +0,0 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics; | ||||
| using CefSharp; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Management{ | ||||
|     static class BrowserProcesses{ | ||||
|         private static readonly Dictionary<int, int> PIDs = new Dictionary<int, int>(); | ||||
| 
 | ||||
|         public static void Link(int identifier, int pid){ | ||||
|             PIDs[identifier] = pid; | ||||
|         } | ||||
| 
 | ||||
|         public static void Forget(int identifier){ | ||||
|             PIDs.Remove(identifier); | ||||
|         } | ||||
|          | ||||
|         public static Process FindProcess(IBrowser browser){ | ||||
|             if (PIDs.TryGetValue(browser.Identifier, out int pid) && WindowsUtils.IsChildProcess(pid)){ // child process is checked in two places for safety | ||||
|                 return Process.GetProcessById(pid); | ||||
|             } | ||||
|             else{ | ||||
|                 return null; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,91 +0,0 @@ | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.Timers; | ||||
| using System.Windows.Forms; | ||||
| using CefSharp; | ||||
| using Timer = System.Timers.Timer; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Management{ | ||||
|     sealed class MemoryUsageTracker : IDisposable{ | ||||
|         private const int IntervalMemoryCheck = 60000*30; // 30 minutes | ||||
|         private const int IntervalCleanupAttempt = 60000*5; // 5 minutes | ||||
| 
 | ||||
|         private readonly string script; | ||||
|         private readonly Timer timer; | ||||
|         private Form owner; | ||||
|         private IBrowser browser; | ||||
| 
 | ||||
|         private long threshold; | ||||
|         private bool needsCleanup; | ||||
| 
 | ||||
|         public MemoryUsageTracker(string cleanupFunctionName){ | ||||
|             this.script = $"window.{cleanupFunctionName} && window.{cleanupFunctionName}()"; | ||||
| 
 | ||||
|             this.timer = new Timer{ Interval = IntervalMemoryCheck }; | ||||
|             this.timer.Elapsed += timer_Elapsed; | ||||
|         } | ||||
| 
 | ||||
|         public void Start(Form owner, IBrowser browser, int thresholdMB){ | ||||
|             Stop(); | ||||
| 
 | ||||
|             this.owner = owner; | ||||
|             this.browser = browser; | ||||
|             this.threshold = thresholdMB*1024L*1024L; | ||||
|             this.timer.SynchronizingObject = owner; | ||||
|             this.timer.Start(); | ||||
|         } | ||||
| 
 | ||||
|         public void Stop(){ | ||||
|             timer.Stop(); | ||||
|             timer.SynchronizingObject = null; | ||||
|             owner = null; | ||||
|             browser = null; | ||||
|             SetNeedsCleanup(false); | ||||
|         } | ||||
| 
 | ||||
|         public void Dispose(){ | ||||
|             timer.SynchronizingObject = null; | ||||
|             timer.Dispose(); | ||||
|             owner = null; | ||||
|             browser = null; | ||||
|         } | ||||
| 
 | ||||
|         private void SetNeedsCleanup(bool value){ | ||||
|             if (needsCleanup != value){ | ||||
|                 needsCleanup = value; | ||||
|                 timer.Interval = value ? IntervalCleanupAttempt : IntervalMemoryCheck; // restarts timer | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void timer_Elapsed(object sender, ElapsedEventArgs e){ | ||||
|             if (owner == null || browser == null){ | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             if (needsCleanup){ | ||||
|                 if (!owner.ContainsFocus){ | ||||
|                     using(IFrame frame = browser.MainFrame){ | ||||
|                         frame.EvaluateScriptAsync(script).ContinueWith(task => { | ||||
|                             JavascriptResponse response = task.Result; | ||||
| 
 | ||||
|                             if (response.Success && (response.Result as bool? ?? false)){ | ||||
|                                 SetNeedsCleanup(false); | ||||
|                             } | ||||
|                         }); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else{ | ||||
|                 try{ | ||||
|                     using(Process process = BrowserProcesses.FindProcess(browser)){ | ||||
|                         if (process?.PrivateMemorySize64 > threshold){ | ||||
|                             SetNeedsCleanup(true); | ||||
|                         } | ||||
|                     } | ||||
|                 }catch{ | ||||
|                     // ignore I guess? | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,181 +0,0 @@ | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.IO; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetLib.Communication; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Management{ | ||||
|     sealed class VideoPlayer : IDisposable{ | ||||
|         private readonly string PlayerExe = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"); | ||||
| 
 | ||||
|         public bool Running{ | ||||
|             get{ | ||||
|                 if (currentProcess == null){ | ||||
|                     return false; | ||||
|                 } | ||||
| 
 | ||||
|                 currentProcess.Refresh(); | ||||
|                 return !currentProcess.HasExited; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public event EventHandler ProcessExited; | ||||
| 
 | ||||
|         private readonly Form owner; | ||||
|         private string lastUrl; | ||||
| 
 | ||||
|         private Process currentProcess; | ||||
|         private DuplexPipe.Server currentPipe; | ||||
|         private bool isClosing; | ||||
| 
 | ||||
|         public VideoPlayer(Form owner){ | ||||
|             this.owner = owner; | ||||
|             this.owner.FormClosing += owner_FormClosing; | ||||
|         } | ||||
| 
 | ||||
|         public void Launch(string url){ | ||||
|             if (Running){ | ||||
|                 Destroy(); | ||||
|                 isClosing = false; | ||||
|             } | ||||
| 
 | ||||
|             lastUrl = url; | ||||
| 
 | ||||
|             try{ | ||||
|                 currentPipe = DuplexPipe.CreateServer(); | ||||
|                 currentPipe.DataIn += currentPipe_DataIn; | ||||
| 
 | ||||
|                 if ((currentProcess = Process.Start(new ProcessStartInfo{ | ||||
|                     FileName = PlayerExe, | ||||
|                     Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{currentPipe.GenerateToken()}\"", | ||||
|                     UseShellExecute = false, | ||||
|                     RedirectStandardOutput = true | ||||
|                 })) != null){ | ||||
|                     currentProcess.EnableRaisingEvents = true; | ||||
|                     currentProcess.Exited += process_Exited; | ||||
| 
 | ||||
|                     #if DEBUG | ||||
|                     currentProcess.BeginOutputReadLine(); | ||||
|                     currentProcess.OutputDataReceived += (sender, args) => Debug.WriteLine("VideoPlayer: "+args.Data); | ||||
|                     #endif | ||||
|                 } | ||||
| 
 | ||||
|                 currentPipe.DisposeToken(); | ||||
|             }catch(Exception e){ | ||||
|                 Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void SendKeyEvent(Keys key){ | ||||
|             currentPipe?.Write("key", ((int)key).ToString()); | ||||
|         } | ||||
| 
 | ||||
|         private void currentPipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){ | ||||
|             owner.InvokeSafe(() => { | ||||
|                 switch(e.Key){ | ||||
|                     case "vol": | ||||
|                         if (int.TryParse(e.Data, out int volume) && volume != Program.UserConfig.VideoPlayerVolume){ | ||||
|                             Program.UserConfig.VideoPlayerVolume = volume; | ||||
|                             Program.UserConfig.Save(); | ||||
|                         } | ||||
| 
 | ||||
|                         break; | ||||
| 
 | ||||
|                     case "download": | ||||
|                         TwitterUtils.DownloadVideo(lastUrl); | ||||
|                         break; | ||||
| 
 | ||||
|                     case "rip": | ||||
|                         currentPipe.Dispose(); | ||||
|                         currentPipe = null; | ||||
|                      | ||||
|                         currentProcess.Dispose(); | ||||
|                         currentProcess = null; | ||||
| 
 | ||||
|                         isClosing = false; | ||||
|                         TriggerProcessExitEventUnsafe(); | ||||
|                         break; | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         public void Close(){ | ||||
|             if (currentProcess != null){ | ||||
|                 if (isClosing){ | ||||
|                     Destroy(); | ||||
|                     isClosing = false; | ||||
|                 } | ||||
|                 else{ | ||||
|                     isClosing = true; | ||||
|                     currentProcess.Exited -= process_Exited; | ||||
|                     currentPipe.Write("die"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void Dispose(){ | ||||
|             ProcessExited = null; | ||||
| 
 | ||||
|             isClosing = true; | ||||
|             Destroy(); | ||||
|         } | ||||
| 
 | ||||
|         private void Destroy(){ | ||||
|             if (currentProcess != null){ | ||||
|                 try{ | ||||
|                     currentProcess.Kill(); | ||||
|                 }catch{ | ||||
|                     // kill me instead then | ||||
|                 } | ||||
| 
 | ||||
|                 currentProcess.Dispose(); | ||||
|                 currentProcess = null; | ||||
|                  | ||||
|                 currentPipe.Dispose(); | ||||
|                 currentPipe = null; | ||||
| 
 | ||||
|                 TriggerProcessExitEventUnsafe(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void owner_FormClosing(object sender, FormClosingEventArgs e){ | ||||
|             if (currentProcess != null){ | ||||
|                 currentProcess.Exited -= process_Exited; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void process_Exited(object sender, EventArgs e){ | ||||
|             int exitCode = currentProcess.ExitCode; | ||||
| 
 | ||||
|             currentProcess.Dispose(); | ||||
|             currentProcess = null; | ||||
| 
 | ||||
|             currentPipe.Dispose(); | ||||
|             currentPipe = null; | ||||
| 
 | ||||
|             switch(exitCode){ | ||||
|                 case 3: // CODE_LAUNCH_FAIL | ||||
|                     if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in a browser?", FormMessage.Yes, FormMessage.No)){ | ||||
|                         BrowserUtils.OpenExternalBrowser(lastUrl); | ||||
|                     } | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 4: // CODE_MEDIA_ERROR | ||||
|                     if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in a browser?", FormMessage.Yes, FormMessage.No)){ | ||||
|                         BrowserUtils.OpenExternalBrowser(lastUrl); | ||||
|                     } | ||||
| 
 | ||||
|                     break; | ||||
|             } | ||||
|              | ||||
|             owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe); | ||||
|         } | ||||
| 
 | ||||
|         private void TriggerProcessExitEventUnsafe(){ | ||||
|             ProcessExited?.Invoke(this, EventArgs.Empty); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,33 +0,0 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Configuration; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings{ | ||||
|     class BaseTabSettings : UserControl{ | ||||
|         protected static UserConfig Config => Program.UserConfig; | ||||
| 
 | ||||
|         public IEnumerable<Control> InteractiveControls{ | ||||
|             get{ | ||||
|                 foreach(Panel panel in Controls.OfType<Panel>()){ | ||||
|                     foreach(Control control in panel.Controls){ | ||||
|                         yield return control; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected BaseTabSettings(){ | ||||
|             Padding = new Padding(6); | ||||
|         } | ||||
| 
 | ||||
|         public virtual void OnReady(){} | ||||
|         public virtual void OnClosing(){} | ||||
| 
 | ||||
|         protected static void PromptRestart(){ | ||||
|             if (FormMessage.Information("TweetDuck Options", "The application must restart for the option to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){ | ||||
|                 Program.Restart(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,203 +0,0 @@ | ||||
| namespace TweetDuck.Core.Other.Settings.Dialogs { | ||||
|     partial class DialogSettingsCSS { | ||||
|         /// <summary> | ||||
|         /// Required designer variable. | ||||
|         /// </summary> | ||||
|         private System.ComponentModel.IContainer components = null; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Clean up any resources being used. | ||||
|         /// </summary> | ||||
|         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | ||||
|         protected override void Dispose(bool disposing) { | ||||
|             if (disposing && (components != null)) { | ||||
|                 components.Dispose(); | ||||
|             } | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
| 
 | ||||
|         #region Windows Form Designer generated code | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Required method for Designer support - do not modify | ||||
|         /// the contents of this method with the code editor. | ||||
|         /// </summary> | ||||
|         private void InitializeComponent() { | ||||
|             this.components = new System.ComponentModel.Container(); | ||||
|             this.textBoxBrowserCSS = new System.Windows.Forms.TextBox(); | ||||
|             this.btnCancel = new System.Windows.Forms.Button(); | ||||
|             this.btnApply = new System.Windows.Forms.Button(); | ||||
|             this.splitContainer = new System.Windows.Forms.SplitContainer(); | ||||
|             this.labelBrowser = new System.Windows.Forms.Label(); | ||||
|             this.labelNotification = new System.Windows.Forms.Label(); | ||||
|             this.textBoxNotificationCSS = new System.Windows.Forms.TextBox(); | ||||
|             this.labelWarning = new System.Windows.Forms.Label(); | ||||
|             this.btnOpenWiki = new System.Windows.Forms.Button(); | ||||
|             this.timerTestBrowser = new System.Windows.Forms.Timer(this.components); | ||||
|             ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit(); | ||||
|             this.splitContainer.Panel1.SuspendLayout(); | ||||
|             this.splitContainer.Panel2.SuspendLayout(); | ||||
|             this.splitContainer.SuspendLayout(); | ||||
|             this.SuspendLayout(); | ||||
|             //  | ||||
|             // textBoxBrowserCSS | ||||
|             //  | ||||
|             this.textBoxBrowserCSS.Dock = System.Windows.Forms.DockStyle.Bottom; | ||||
|             this.textBoxBrowserCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); | ||||
|             this.textBoxBrowserCSS.Location = new System.Drawing.Point(0, 16); | ||||
|             this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0); | ||||
|             this.textBoxBrowserCSS.Multiline = true; | ||||
|             this.textBoxBrowserCSS.Name = "textBoxBrowserCSS"; | ||||
|             this.textBoxBrowserCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both; | ||||
|             this.textBoxBrowserCSS.Size = new System.Drawing.Size(378, 253); | ||||
|             this.textBoxBrowserCSS.TabIndex = 1; | ||||
|             this.textBoxBrowserCSS.WordWrap = false; | ||||
|             this.textBoxBrowserCSS.KeyUp += new System.Windows.Forms.KeyEventHandler(this.textBoxBrowserCSS_KeyUp); | ||||
|             //  | ||||
|             // btnCancel | ||||
|             //  | ||||
|             this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); | ||||
|             this.btnCancel.Location = new System.Drawing.Point(654, 287); | ||||
|             this.btnCancel.Name = "btnCancel"; | ||||
|             this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); | ||||
|             this.btnCancel.Size = new System.Drawing.Size(56, 23); | ||||
|             this.btnCancel.TabIndex = 2; | ||||
|             this.btnCancel.Text = "Cancel"; | ||||
|             this.btnCancel.UseVisualStyleBackColor = true; | ||||
|             this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); | ||||
|             //  | ||||
|             // btnApply | ||||
|             //  | ||||
|             this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); | ||||
|             this.btnApply.Location = new System.Drawing.Point(716, 287); | ||||
|             this.btnApply.Name = "btnApply"; | ||||
|             this.btnApply.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); | ||||
|             this.btnApply.Size = new System.Drawing.Size(56, 23); | ||||
|             this.btnApply.TabIndex = 1; | ||||
|             this.btnApply.Text = "Apply"; | ||||
|             this.btnApply.UseVisualStyleBackColor = true; | ||||
|             this.btnApply.Click += new System.EventHandler(this.btnApply_Click); | ||||
|             //  | ||||
|             // splitContainer | ||||
|             //  | ||||
|             this.splitContainer.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)  | ||||
|             | System.Windows.Forms.AnchorStyles.Left)  | ||||
|             | System.Windows.Forms.AnchorStyles.Right))); | ||||
|             this.splitContainer.Location = new System.Drawing.Point(12, 12); | ||||
|             this.splitContainer.Name = "splitContainer"; | ||||
|             //  | ||||
|             // splitContainer.Panel1 | ||||
|             //  | ||||
|             this.splitContainer.Panel1.Controls.Add(this.labelBrowser); | ||||
|             this.splitContainer.Panel1.Controls.Add(this.textBoxBrowserCSS); | ||||
|             this.splitContainer.Panel1MinSize = 64; | ||||
|             //  | ||||
|             // splitContainer.Panel2 | ||||
|             //  | ||||
|             this.splitContainer.Panel2.Controls.Add(this.labelNotification); | ||||
|             this.splitContainer.Panel2.Controls.Add(this.textBoxNotificationCSS); | ||||
|             this.splitContainer.Panel2MinSize = 64; | ||||
|             this.splitContainer.Size = new System.Drawing.Size(760, 269); | ||||
|             this.splitContainer.SplitterDistance = 378; | ||||
|             this.splitContainer.SplitterWidth = 5; | ||||
|             this.splitContainer.TabIndex = 0; | ||||
|             //  | ||||
|             // labelBrowser | ||||
|             //  | ||||
|             this.labelBrowser.AutoSize = true; | ||||
|             this.labelBrowser.Location = new System.Drawing.Point(-3, 0); | ||||
|             this.labelBrowser.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); | ||||
|             this.labelBrowser.Name = "labelBrowser"; | ||||
|             this.labelBrowser.Size = new System.Drawing.Size(45, 13); | ||||
|             this.labelBrowser.TabIndex = 0; | ||||
|             this.labelBrowser.Text = "Browser"; | ||||
|             //  | ||||
|             // labelNotification | ||||
|             //  | ||||
|             this.labelNotification.AutoSize = true; | ||||
|             this.labelNotification.Location = new System.Drawing.Point(-3, 0); | ||||
|             this.labelNotification.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); | ||||
|             this.labelNotification.Name = "labelNotification"; | ||||
|             this.labelNotification.Size = new System.Drawing.Size(60, 13); | ||||
|             this.labelNotification.TabIndex = 0; | ||||
|             this.labelNotification.Text = "Notification"; | ||||
|             //  | ||||
|             // textBoxNotificationCSS | ||||
|             //  | ||||
|             this.textBoxNotificationCSS.Dock = System.Windows.Forms.DockStyle.Bottom; | ||||
|             this.textBoxNotificationCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); | ||||
|             this.textBoxNotificationCSS.Location = new System.Drawing.Point(0, 16); | ||||
|             this.textBoxNotificationCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0); | ||||
|             this.textBoxNotificationCSS.Multiline = true; | ||||
|             this.textBoxNotificationCSS.Name = "textBoxNotificationCSS"; | ||||
|             this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both; | ||||
|             this.textBoxNotificationCSS.Size = new System.Drawing.Size(377, 253); | ||||
|             this.textBoxNotificationCSS.TabIndex = 1; | ||||
|             this.textBoxNotificationCSS.WordWrap = false; | ||||
|             //  | ||||
|             // labelWarning | ||||
|             //  | ||||
|             this.labelWarning.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); | ||||
|             this.labelWarning.AutoSize = true; | ||||
|             this.labelWarning.Location = new System.Drawing.Point(91, 292); | ||||
|             this.labelWarning.Name = "labelWarning"; | ||||
|             this.labelWarning.Size = new System.Drawing.Size(341, 13); | ||||
|             this.labelWarning.TabIndex = 3; | ||||
|             this.labelWarning.Text = "The code is not validated, please make sure there are no syntax errors."; | ||||
|             //  | ||||
|             // btnOpenWiki | ||||
|             //  | ||||
|             this.btnOpenWiki.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); | ||||
|             this.btnOpenWiki.Location = new System.Drawing.Point(12, 287); | ||||
|             this.btnOpenWiki.Name = "btnOpenWiki"; | ||||
|             this.btnOpenWiki.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); | ||||
|             this.btnOpenWiki.Size = new System.Drawing.Size(73, 23); | ||||
|             this.btnOpenWiki.TabIndex = 4; | ||||
|             this.btnOpenWiki.Text = "Open Wiki"; | ||||
|             this.btnOpenWiki.UseVisualStyleBackColor = true; | ||||
|             this.btnOpenWiki.Click += new System.EventHandler(this.btnOpenWiki_Click); | ||||
|             //  | ||||
|             // timerTestBrowser | ||||
|             //  | ||||
|             this.timerTestBrowser.Interval = 500; | ||||
|             this.timerTestBrowser.Tick += new System.EventHandler(this.timerTestBrowser_Tick); | ||||
|             //  | ||||
|             // DialogSettingsCSS | ||||
|             //  | ||||
|             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); | ||||
|             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | ||||
|             this.ClientSize = new System.Drawing.Size(784, 322); | ||||
|             this.Controls.Add(this.btnOpenWiki); | ||||
|             this.Controls.Add(this.labelWarning); | ||||
|             this.Controls.Add(this.splitContainer); | ||||
|             this.Controls.Add(this.btnApply); | ||||
|             this.Controls.Add(this.btnCancel); | ||||
|             this.MinimumSize = new System.Drawing.Size(600, 160); | ||||
|             this.Name = "DialogSettingsCSS"; | ||||
|             this.ShowIcon = false; | ||||
|             this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; | ||||
|             this.splitContainer.Panel1.ResumeLayout(false); | ||||
|             this.splitContainer.Panel1.PerformLayout(); | ||||
|             this.splitContainer.Panel2.ResumeLayout(false); | ||||
|             this.splitContainer.Panel2.PerformLayout(); | ||||
|             ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit(); | ||||
|             this.splitContainer.ResumeLayout(false); | ||||
|             this.ResumeLayout(false); | ||||
|             this.PerformLayout(); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         private System.Windows.Forms.TextBox textBoxBrowserCSS; | ||||
|         private System.Windows.Forms.Button btnCancel; | ||||
|         private System.Windows.Forms.Button btnApply; | ||||
|         private System.Windows.Forms.SplitContainer splitContainer; | ||||
|         private System.Windows.Forms.TextBox textBoxNotificationCSS; | ||||
|         private System.Windows.Forms.Label labelBrowser; | ||||
|         private System.Windows.Forms.Label labelNotification; | ||||
|         private System.Windows.Forms.Label labelWarning; | ||||
|         private System.Windows.Forms.Button btnOpenWiki; | ||||
|         private System.Windows.Forms.Timer timerTestBrowser; | ||||
|     } | ||||
| } | ||||
| @@ -1,51 +0,0 @@ | ||||
| using System; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings.Dialogs{ | ||||
|     sealed partial class DialogSettingsCSS : Form{ | ||||
|         public string BrowserCSS => textBoxBrowserCSS.Text; | ||||
|         public string NotificationCSS => textBoxNotificationCSS.Text; | ||||
| 
 | ||||
|         private readonly Action<string> reinjectBrowserCSS; | ||||
| 
 | ||||
|         public DialogSettingsCSS(Action<string> reinjectBrowserCSS){ | ||||
|             InitializeComponent(); | ||||
|              | ||||
|             Text = Program.BrandName+" Options - CSS"; | ||||
| 
 | ||||
|             this.reinjectBrowserCSS = reinjectBrowserCSS; | ||||
|              | ||||
|             textBoxBrowserCSS.EnableMultilineShortcuts(); | ||||
|             textBoxBrowserCSS.Text = Program.UserConfig.CustomBrowserCSS ?? ""; | ||||
| 
 | ||||
|             textBoxNotificationCSS.EnableMultilineShortcuts(); | ||||
|             textBoxNotificationCSS.Text = Program.UserConfig.CustomNotificationCSS ?? ""; | ||||
|         } | ||||
| 
 | ||||
|         private void textBoxBrowserCSS_KeyUp(object sender, KeyEventArgs e){ | ||||
|             timerTestBrowser.Stop(); | ||||
|             timerTestBrowser.Start(); | ||||
|         } | ||||
| 
 | ||||
|         private void timerTestBrowser_Tick(object sender, EventArgs e){ | ||||
|             reinjectBrowserCSS(textBoxBrowserCSS.Text); | ||||
|             timerTestBrowser.Stop(); | ||||
|         } | ||||
| 
 | ||||
|         private void btnOpenWiki_Click(object sender, EventArgs e){ | ||||
|             BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/wiki"); | ||||
|         } | ||||
| 
 | ||||
|         private void btnApply_Click(object sender, EventArgs e){ | ||||
|             DialogResult = DialogResult.OK; | ||||
|             Close(); | ||||
|         } | ||||
| 
 | ||||
|         private void btnCancel_Click(object sender, EventArgs e){ | ||||
|             DialogResult = DialogResult.Cancel; | ||||
|             Close(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,48 +0,0 @@ | ||||
| using System; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetDuck.Data; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings.Dialogs{ | ||||
|     sealed partial class DialogSettingsCefArgs : Form{ | ||||
|         public string CefArgs => textBoxArgs.Text; | ||||
| 
 | ||||
|         public DialogSettingsCefArgs(){ | ||||
|             InitializeComponent(); | ||||
|              | ||||
|             Text = Program.BrandName+" Options - CEF Arguments"; | ||||
|              | ||||
|             textBoxArgs.EnableMultilineShortcuts(); | ||||
|             textBoxArgs.Text = Program.UserConfig.CustomCefArgs ?? ""; | ||||
|             textBoxArgs.Select(textBoxArgs.Text.Length, 0); | ||||
|         } | ||||
| 
 | ||||
|         private void btnHelp_Click(object sender, EventArgs e){ | ||||
|             BrowserUtils.OpenExternalBrowserUnsafe("http://peter.sh/experiments/chromium-command-line-switches/"); | ||||
|         } | ||||
| 
 | ||||
|         private void btnApply_Click(object sender, EventArgs e){ | ||||
|             string prevArgs = Program.UserConfig.CustomCefArgs; | ||||
| 
 | ||||
|             if (CefArgs == prevArgs){ | ||||
|                 DialogResult = DialogResult.Cancel; | ||||
|                 Close(); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             int count = CommandLineArgs.ReadCefArguments(CefArgs).Count; | ||||
|             string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?"; | ||||
| 
 | ||||
|             if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){ | ||||
|                 DialogResult = DialogResult.OK; | ||||
|                 Close(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void btnCancel_Click(object sender, EventArgs e){ | ||||
|             DialogResult = DialogResult.Cancel; | ||||
|             Close(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,203 +0,0 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Configuration; | ||||
| using TweetDuck.Core.Other.Settings.Export; | ||||
| using TweetDuck.Plugins; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings.Dialogs{ | ||||
|     sealed partial class DialogSettingsManage : Form{ | ||||
|         private enum State{ | ||||
|             Deciding, Reset, Import, Export | ||||
|         } | ||||
| 
 | ||||
|         public ExportFileFlags Flags{ | ||||
|             get => selectedFlags; | ||||
| 
 | ||||
|             set{ | ||||
|                 // this will call events and SetFlag, which also updates the UI | ||||
|                 cbConfig.Checked = value.HasFlag(ExportFileFlags.Config); | ||||
|                 cbSession.Checked = value.HasFlag(ExportFileFlags.Session); | ||||
|                 cbPluginData.Checked = value.HasFlag(ExportFileFlags.PluginData); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public bool ShouldReloadBrowser { get; private set; } | ||||
|          | ||||
|         private readonly PluginManager plugins; | ||||
|         private State currentState; | ||||
| 
 | ||||
|         private ExportManager importManager; | ||||
|         private ExportFileFlags selectedFlags = ExportFileFlags.None; | ||||
| 
 | ||||
|         public DialogSettingsManage(PluginManager plugins){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             this.plugins = plugins; | ||||
|             this.currentState = State.Deciding; | ||||
|         } | ||||
| 
 | ||||
|         private void radioDecision_CheckedChanged(object sender, EventArgs e){ | ||||
|             btnContinue.Enabled = true; | ||||
|         } | ||||
| 
 | ||||
|         private void cbConfig_CheckedChanged(object sender, EventArgs e){ | ||||
|             SetFlag(ExportFileFlags.Config, cbConfig.Checked); | ||||
|         } | ||||
| 
 | ||||
|         private void cbSession_CheckedChanged(object sender, EventArgs e){ | ||||
|             SetFlag(ExportFileFlags.Session, cbSession.Checked); | ||||
|         } | ||||
| 
 | ||||
|         private void cbPluginData_CheckedChanged(object sender, EventArgs e){ | ||||
|             SetFlag(ExportFileFlags.PluginData, cbPluginData.Checked); | ||||
|         } | ||||
| 
 | ||||
|         private void btnContinue_Click(object sender, EventArgs e){ | ||||
|             string file; | ||||
| 
 | ||||
|             switch(currentState){ | ||||
|                 case State.Deciding: | ||||
|                     // Reset | ||||
|                     if (radioReset.Checked){ | ||||
|                         currentState = State.Reset; | ||||
| 
 | ||||
|                         Text = "Restore Defaults"; | ||||
|                         Flags = ExportFileFlags.Config; | ||||
|                     } | ||||
| 
 | ||||
|                     // Import | ||||
|                     else if (radioImport.Checked){ | ||||
|                         using(OpenFileDialog dialog = new OpenFileDialog{ | ||||
|                             AutoUpgradeEnabled = true, | ||||
|                             DereferenceLinks = true, | ||||
|                             Title = "Import TweetDuck Profile", | ||||
|                             Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings" | ||||
|                         }){ | ||||
|                             if (dialog.ShowDialog() != DialogResult.OK){ | ||||
|                                 return; | ||||
|                             } | ||||
| 
 | ||||
|                             file = dialog.FileName; | ||||
|                         } | ||||
| 
 | ||||
|                         importManager = new ExportManager(file, plugins); | ||||
|                         currentState = State.Import; | ||||
| 
 | ||||
|                         Text = "Import Profile"; | ||||
|                         Flags = importManager.GetImportFlags(); | ||||
| 
 | ||||
|                         cbConfig.Enabled = cbConfig.Checked; | ||||
|                         cbSession.Enabled = cbSession.Checked; | ||||
|                         cbPluginData.Enabled = cbPluginData.Checked; | ||||
|                     } | ||||
| 
 | ||||
|                     // Export | ||||
|                     else if (radioExport.Checked){ | ||||
|                         currentState = State.Export; | ||||
| 
 | ||||
|                         Text = "Export Profile"; | ||||
|                         btnContinue.Text = "Export Profile"; | ||||
|                         Flags = ExportFileFlags.All & ~ExportFileFlags.Session; | ||||
|                     } | ||||
|                      | ||||
|                     // Continue... | ||||
|                     panelDecision.Visible = false; | ||||
|                     panelExport.Visible = true; | ||||
|                     break; | ||||
| 
 | ||||
|                 case State.Reset: | ||||
|                     if (FormMessage.Warning("Reset TweetDuck Options", "This will reset the selected items. Are you sure you want to proceed?", FormMessage.Yes, FormMessage.No)){ | ||||
|                         if (Flags.HasFlag(ExportFileFlags.Config)){ | ||||
|                             Program.ResetConfig(); | ||||
|                         } | ||||
| 
 | ||||
|                         if (Flags.HasFlag(ExportFileFlags.PluginData)){ | ||||
|                             try{ | ||||
|                                 File.Delete(Program.PluginConfigFilePath); | ||||
|                                 Directory.Delete(Program.PluginDataPath, true); | ||||
|                             }catch(Exception ex){ | ||||
|                                 Program.Reporter.HandleException("Plugin Data Reset Error", "Could not delete plugin data.", true, ex); | ||||
|                             } | ||||
|                         } | ||||
| 
 | ||||
|                         if (Flags.HasFlag(ExportFileFlags.Session)){ | ||||
|                             Program.Restart(Arguments.ArgDeleteCookies); | ||||
|                         } | ||||
|                         else{ | ||||
|                             ShouldReloadBrowser = true; | ||||
|                         } | ||||
| 
 | ||||
|                         DialogResult = DialogResult.OK; | ||||
|                         Close(); | ||||
|                     } | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case State.Import: | ||||
|                     if (importManager.Import(Flags)){ | ||||
|                         Program.UserConfig.Reload(); | ||||
| 
 | ||||
|                         if (importManager.IsRestarting){ | ||||
|                             Program.Restart(Arguments.ArgImportCookies); | ||||
|                         } | ||||
|                         else{ | ||||
|                             ShouldReloadBrowser = true; | ||||
|                         } | ||||
|                     } | ||||
|                     else{ | ||||
|                         Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing TweetDuck profile.", true, importManager.LastException); | ||||
|                     } | ||||
|                      | ||||
|                     DialogResult = DialogResult.OK; | ||||
|                     Close(); | ||||
|                     break; | ||||
| 
 | ||||
|                 case State.Export: | ||||
|                     using(SaveFileDialog dialog = new SaveFileDialog{ | ||||
|                         AddExtension = true, | ||||
|                         AutoUpgradeEnabled = true, | ||||
|                         OverwritePrompt = true, | ||||
|                         DefaultExt = "tdsettings", | ||||
|                         FileName = "TweetDuck.tdsettings", | ||||
|                         Title = "Export TweetDuck Profile", | ||||
|                         Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings" | ||||
|                     }){ | ||||
|                         if (dialog.ShowDialog() != DialogResult.OK){ | ||||
|                             return; | ||||
|                         } | ||||
| 
 | ||||
|                         file = dialog.FileName; | ||||
|                     } | ||||
| 
 | ||||
|                     Program.UserConfig.Save(); | ||||
|                     ExportManager manager = new ExportManager(file, plugins); | ||||
| 
 | ||||
|                     if (!manager.Export(Flags)){ | ||||
|                         Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, manager.LastException); | ||||
|                     } | ||||
| 
 | ||||
|                     DialogResult = DialogResult.OK; | ||||
|                     Close(); | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void btnCancel_Click(object sender, EventArgs e){ | ||||
|             DialogResult = DialogResult.Cancel; | ||||
|             Close(); | ||||
|         } | ||||
| 
 | ||||
|         private void SetFlag(ExportFileFlags flag, bool enable){ | ||||
|             selectedFlags = enable ? selectedFlags | flag : selectedFlags & ~flag; | ||||
|             btnContinue.Enabled = selectedFlags != ExportFileFlags.None; | ||||
| 
 | ||||
|             if (currentState == State.Import){ | ||||
|                 btnContinue.Text = selectedFlags.HasFlag(ExportFileFlags.Session) ? "Import && Restart" : "Import Profile"; | ||||
|             } | ||||
|             else if (currentState == State.Reset){ | ||||
|                 btnContinue.Text = selectedFlags.HasFlag(ExportFileFlags.Session) ? "Restore && Restart" : "Restore Defaults"; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,87 +0,0 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Configuration; | ||||
| using TweetDuck.Data; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings.Dialogs{ | ||||
|     sealed partial class DialogSettingsRestart : Form{ | ||||
|         private const string DefaultLocale = "en-US"; | ||||
| 
 | ||||
|         public CommandLineArgs Args { get; private set; } | ||||
| 
 | ||||
|         public DialogSettingsRestart(CommandLineArgs currentArgs){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             try{ | ||||
|                 object[] locales = Directory.EnumerateFiles(Path.Combine(Program.ProgramPath, "locales"), "*.pak", SearchOption.TopDirectoryOnly).Select(Path.GetFileNameWithoutExtension).ToArray<object>(); | ||||
|                 comboLocale.Items.AddRange(locales); | ||||
|             }catch{ | ||||
|                 comboLocale.Items.Add(DefaultLocale); | ||||
|             } | ||||
| 
 | ||||
|             cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging); | ||||
|             cbDebugUpdates.Checked = currentArgs.HasFlag(Arguments.ArgDebugUpdates); | ||||
|             comboLocale.SelectedItem = currentArgs.GetValue(Arguments.ArgLocale, DefaultLocale); | ||||
| 
 | ||||
|             cbLogging.CheckedChanged += control_Change; | ||||
|             cbDebugUpdates.CheckedChanged += control_Change; | ||||
|             comboLocale.SelectedValueChanged += control_Change; | ||||
| 
 | ||||
|             if (Program.IsPortable){ | ||||
|                 tbDataFolder.Text = "Not available in portable version"; | ||||
|                 tbDataFolder.Enabled = false; | ||||
|             } | ||||
|             else{ | ||||
|                 tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty); | ||||
|                 tbDataFolder.TextChanged += control_Change; | ||||
|             } | ||||
| 
 | ||||
|             control_Change(this, EventArgs.Empty); | ||||
| 
 | ||||
|             Text = Program.BrandName+" Arguments"; | ||||
|         } | ||||
| 
 | ||||
|         private void control_Change(object sender, EventArgs e){ | ||||
|             Args = new CommandLineArgs(); | ||||
|              | ||||
|             if (cbLogging.Checked){ | ||||
|                 Args.AddFlag(Arguments.ArgLogging); | ||||
|             } | ||||
|              | ||||
|             if (cbDebugUpdates.Checked){ | ||||
|                 Args.AddFlag(Arguments.ArgDebugUpdates); | ||||
|             } | ||||
| 
 | ||||
|             string locale = comboLocale.SelectedItem as string; | ||||
| 
 | ||||
|             if (!string.IsNullOrWhiteSpace(locale) && locale != DefaultLocale){ | ||||
|                 Args.SetValue(Arguments.ArgLocale, locale); | ||||
|             } | ||||
| 
 | ||||
|             if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){ | ||||
|                 Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text); | ||||
|             } | ||||
| 
 | ||||
|             tbShortcutTarget.Text = $@"""{Application.ExecutablePath}""{(Args.Count > 0 ? " " : "")}{Args}"; | ||||
|             tbShortcutTarget.Select(tbShortcutTarget.Text.Length, 0); | ||||
|         } | ||||
| 
 | ||||
|         private void tbShortcutTarget_Click(object sender, EventArgs e){ | ||||
|             if (tbShortcutTarget.SelectionLength == 0){ | ||||
|                 tbShortcutTarget.SelectAll(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void btnRestart_Click(object sender, EventArgs e){ | ||||
|             DialogResult = DialogResult.OK; | ||||
|             Close(); | ||||
|         } | ||||
| 
 | ||||
|         private void btnCancel_Click(object sender, EventArgs e){ | ||||
|             DialogResult = DialogResult.Cancel; | ||||
|             Close(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,12 +0,0 @@ | ||||
| using System; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings.Export{ | ||||
|     [Flags] | ||||
|     enum ExportFileFlags{ | ||||
|         None = 0, | ||||
|         Config = 1, | ||||
|         Session = 2, | ||||
|         PluginData = 4, | ||||
|         All = Config|Session|PluginData | ||||
|     } | ||||
| } | ||||
| @@ -1,186 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using TweetDuck.Data; | ||||
| using TweetDuck.Plugins; | ||||
| using TweetDuck.Plugins.Enums; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings.Export{ | ||||
|     sealed class ExportManager{ | ||||
|         private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies"); | ||||
|         private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp"); | ||||
| 
 | ||||
|         public bool IsRestarting { get; private set; } | ||||
|         public Exception LastException { get; private set; } | ||||
| 
 | ||||
|         private readonly string file; | ||||
|         private readonly PluginManager plugins; | ||||
| 
 | ||||
|         public ExportManager(string file, PluginManager plugins){ | ||||
|             this.file = file; | ||||
|             this.plugins = plugins; | ||||
|         } | ||||
| 
 | ||||
|         public bool Export(ExportFileFlags flags){ | ||||
|             try{ | ||||
|                 using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){ | ||||
|                     if (flags.HasFlag(ExportFileFlags.Config)){ | ||||
|                         stream.WriteFile("config", Program.UserConfigFilePath); | ||||
|                     } | ||||
| 
 | ||||
|                     if (flags.HasFlag(ExportFileFlags.PluginData)){ | ||||
|                         stream.WriteFile("plugin.config", Program.PluginConfigFilePath); | ||||
| 
 | ||||
|                         foreach(Plugin plugin in plugins.Plugins){ | ||||
|                             foreach(PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))){ | ||||
|                                 try{ | ||||
|                                     stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full); | ||||
|                                 }catch(ArgumentOutOfRangeException e){ | ||||
|                                     FormMessage.Warning("Export Profile", "Could not include a plugin file in the export. "+e.Message, FormMessage.OK); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     if (flags.HasFlag(ExportFileFlags.Session)){ | ||||
|                         stream.WriteFile("cookies", CookiesPath); | ||||
|                     } | ||||
| 
 | ||||
|                     stream.Flush(); | ||||
|                 } | ||||
| 
 | ||||
|                 return true; | ||||
|             }catch(Exception e){ | ||||
|                 LastException = e; | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public ExportFileFlags GetImportFlags(){ | ||||
|             ExportFileFlags flags = ExportFileFlags.None; | ||||
| 
 | ||||
|             try{ | ||||
|                 using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){ | ||||
|                     string key; | ||||
| 
 | ||||
|                     while((key = stream.SkipFile()) != null){ | ||||
|                         switch(key){ | ||||
|                             case "config": | ||||
|                                 flags |= ExportFileFlags.Config; | ||||
|                                 break; | ||||
| 
 | ||||
|                             case "plugin.config": | ||||
|                             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))){ | ||||
|                     CombinedFileStream.Entry entry; | ||||
| 
 | ||||
|                     while((entry = stream.ReadFile()) != null){ | ||||
|                         switch(entry.KeyName){ | ||||
|                             case "config": | ||||
|                                 if (flags.HasFlag(ExportFileFlags.Config)){ | ||||
|                                     entry.WriteToFile(Program.UserConfigFilePath); | ||||
|                                 } | ||||
| 
 | ||||
|                                 break; | ||||
| 
 | ||||
|                             case "plugin.config": | ||||
|                                 if (flags.HasFlag(ExportFileFlags.PluginData)){ | ||||
|                                     entry.WriteToFile(Program.PluginConfigFilePath); | ||||
|                                 } | ||||
| 
 | ||||
|                                 break; | ||||
| 
 | ||||
|                             case "plugin.data": | ||||
|                                 if (flags.HasFlag(ExportFileFlags.PluginData)){ | ||||
|                                     string[] value = entry.KeyValue; | ||||
| 
 | ||||
|                                     entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true); | ||||
| 
 | ||||
|                                     if (!plugins.IsPluginInstalled(value[0])){ | ||||
|                                         missingPlugins.Add(value[0]); | ||||
|                                     } | ||||
|                                 } | ||||
| 
 | ||||
|                                 break; | ||||
| 
 | ||||
|                             case "cookies": | ||||
|                                 if (flags.HasFlag(ExportFileFlags.Session)){ | ||||
|                                     entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath)); | ||||
|                                     IsRestarting = true; | ||||
|                                 } | ||||
| 
 | ||||
|                                 break; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (missingPlugins.Count > 0){ | ||||
|                     FormMessage.Information("Importing TweetDuck Profile", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK); | ||||
|                 } | ||||
| 
 | ||||
|                 return true; | ||||
|             }catch(Exception e){ | ||||
|                 LastException = e; | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static void ImportCookies(){ | ||||
|             if (File.Exists(TempCookiesPath)){ | ||||
|                 try{ | ||||
|                     if (File.Exists(CookiesPath)){ | ||||
|                         File.Delete(CookiesPath); | ||||
|                     } | ||||
| 
 | ||||
|                     File.Move(TempCookiesPath, CookiesPath); | ||||
|                 }catch(Exception e){ | ||||
|                     Program.Reporter.HandleException("Profile Import Error", "Could not import the cookie file to restore login session.", true, e); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static void DeleteCookies(){ | ||||
|             try{ | ||||
|                 if (File.Exists(CookiesPath)){ | ||||
|                     File.Delete(CookiesPath); | ||||
|                 } | ||||
|             }catch(Exception e){ | ||||
|                 Program.Reporter.HandleException("Session Reset Error", "Could not remove the cookie file to reset the login session.", true, e); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){ | ||||
|             return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{ | ||||
|                 Full = fullPath, | ||||
|                 Relative = fullPath.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) // strip leading separator character | ||||
|             }) : Enumerable.Empty<PathInfo>(); | ||||
|         } | ||||
| 
 | ||||
|         private sealed class PathInfo{ | ||||
|             public string Full { get; set; } | ||||
|             public string Relative { get; set; } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										290
									
								
								Core/Other/Settings/TabSettingsAdvanced.Designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										290
									
								
								Core/Other/Settings/TabSettingsAdvanced.Designer.cs
									
									
									
										generated
									
									
									
								
							| @@ -1,290 +0,0 @@ | ||||
| namespace TweetDuck.Core.Other.Settings { | ||||
|     partial class TabSettingsAdvanced { | ||||
|         /// <summary>  | ||||
|         /// Required designer variable. | ||||
|         /// </summary> | ||||
|         private System.ComponentModel.IContainer components = null; | ||||
| 
 | ||||
|         /// <summary>  | ||||
|         /// Clean up any resources being used. | ||||
|         /// </summary> | ||||
|         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | ||||
|         protected override void Dispose(bool disposing) { | ||||
|             if (disposing && (components != null)) { | ||||
|                 components.Dispose(); | ||||
|             } | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
| 
 | ||||
|         #region Component Designer generated code | ||||
| 
 | ||||
|         /// <summary>  | ||||
|         /// Required method for Designer support - do not modify  | ||||
|         /// the contents of this method with the code editor. | ||||
|         /// </summary> | ||||
|         private void InitializeComponent() { | ||||
|             this.components = new System.ComponentModel.Container(); | ||||
|             this.btnClearCache = new System.Windows.Forms.Button(); | ||||
|             this.checkHardwareAcceleration = new System.Windows.Forms.CheckBox(); | ||||
|             this.toolTip = new System.Windows.Forms.ToolTip(this.components); | ||||
|             this.btnEditCefArgs = new System.Windows.Forms.Button(); | ||||
|             this.btnEditCSS = new System.Windows.Forms.Button(); | ||||
|             this.btnRestartArgs = new System.Windows.Forms.Button(); | ||||
|             this.btnRestart = new System.Windows.Forms.Button(); | ||||
|             this.btnOpenAppFolder = new System.Windows.Forms.Button(); | ||||
|             this.btnOpenDataFolder = new System.Windows.Forms.Button(); | ||||
|             this.numMemoryThreshold = new TweetDuck.Core.Controls.NumericUpDownEx(); | ||||
|             this.checkBrowserGCReload = new System.Windows.Forms.CheckBox(); | ||||
|             this.labelApp = new System.Windows.Forms.Label(); | ||||
|             this.panelApp = new System.Windows.Forms.Panel(); | ||||
|             this.labelPerformance = new System.Windows.Forms.Label(); | ||||
|             this.panelPerformance = new System.Windows.Forms.Panel(); | ||||
|             this.labelMemoryUsage = new System.Windows.Forms.Label(); | ||||
|             this.panelConfiguration = new System.Windows.Forms.Panel(); | ||||
|             this.labelConfiguration = new System.Windows.Forms.Label(); | ||||
|             ((System.ComponentModel.ISupportInitialize)(this.numMemoryThreshold)).BeginInit(); | ||||
|             this.panelApp.SuspendLayout(); | ||||
|             this.panelPerformance.SuspendLayout(); | ||||
|             this.panelConfiguration.SuspendLayout(); | ||||
|             this.SuspendLayout(); | ||||
|             //  | ||||
|             // btnClearCache | ||||
|             //  | ||||
|             this.btnClearCache.Location = new System.Drawing.Point(5, 28); | ||||
|             this.btnClearCache.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); | ||||
|             this.btnClearCache.Name = "btnClearCache"; | ||||
|             this.btnClearCache.Size = new System.Drawing.Size(144, 23); | ||||
|             this.btnClearCache.TabIndex = 1; | ||||
|             this.btnClearCache.Text = "Clear Cache (calculating)"; | ||||
|             this.toolTip.SetToolTip(this.btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources."); | ||||
|             this.btnClearCache.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // checkHardwareAcceleration | ||||
|             //  | ||||
|             this.checkHardwareAcceleration.AutoSize = true; | ||||
|             this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 5); | ||||
|             this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3); | ||||
|             this.checkHardwareAcceleration.Name = "checkHardwareAcceleration"; | ||||
|             this.checkHardwareAcceleration.Size = new System.Drawing.Size(134, 17); | ||||
|             this.checkHardwareAcceleration.TabIndex = 0; | ||||
|             this.checkHardwareAcceleration.Text = "Hardware Acceleration"; | ||||
|             this.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses graphics card to improve performance. Disable if you experience\r\nvisual glitches. This option will not be exported in a profile."); | ||||
|             this.checkHardwareAcceleration.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // btnEditCefArgs | ||||
|             //  | ||||
|             this.btnEditCefArgs.Location = new System.Drawing.Point(5, 3); | ||||
|             this.btnEditCefArgs.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); | ||||
|             this.btnEditCefArgs.Name = "btnEditCefArgs"; | ||||
|             this.btnEditCefArgs.Size = new System.Drawing.Size(144, 23); | ||||
|             this.btnEditCefArgs.TabIndex = 0; | ||||
|             this.btnEditCefArgs.Text = "Edit CEF Arguments"; | ||||
|             this.toolTip.SetToolTip(this.btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework."); | ||||
|             this.btnEditCefArgs.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // btnEditCSS | ||||
|             //  | ||||
|             this.btnEditCSS.Location = new System.Drawing.Point(155, 3); | ||||
|             this.btnEditCSS.Name = "btnEditCSS"; | ||||
|             this.btnEditCSS.Size = new System.Drawing.Size(144, 23); | ||||
|             this.btnEditCSS.TabIndex = 1; | ||||
|             this.btnEditCSS.Text = "Edit CSS"; | ||||
|             this.toolTip.SetToolTip(this.btnEditCSS, "Set custom CSS for browser and notification windows."); | ||||
|             this.btnEditCSS.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // btnRestartArgs | ||||
|             //  | ||||
|             this.btnRestartArgs.Location = new System.Drawing.Point(155, 32); | ||||
|             this.btnRestartArgs.Name = "btnRestartArgs"; | ||||
|             this.btnRestartArgs.Size = new System.Drawing.Size(144, 23); | ||||
|             this.btnRestartArgs.TabIndex = 3; | ||||
|             this.btnRestartArgs.Text = "Restart with Arguments"; | ||||
|             this.toolTip.SetToolTip(this.btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments."); | ||||
|             this.btnRestartArgs.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // btnRestart | ||||
|             //  | ||||
|             this.btnRestart.Location = new System.Drawing.Point(155, 3); | ||||
|             this.btnRestart.Name = "btnRestart"; | ||||
|             this.btnRestart.Size = new System.Drawing.Size(144, 23); | ||||
|             this.btnRestart.TabIndex = 2; | ||||
|             this.btnRestart.Text = "Restart the Program"; | ||||
|             this.toolTip.SetToolTip(this.btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch."); | ||||
|             this.btnRestart.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // btnOpenAppFolder | ||||
|             //  | ||||
|             this.btnOpenAppFolder.Location = new System.Drawing.Point(5, 3); | ||||
|             this.btnOpenAppFolder.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); | ||||
|             this.btnOpenAppFolder.Name = "btnOpenAppFolder"; | ||||
|             this.btnOpenAppFolder.Size = new System.Drawing.Size(144, 23); | ||||
|             this.btnOpenAppFolder.TabIndex = 0; | ||||
|             this.btnOpenAppFolder.Text = "Open Program Folder"; | ||||
|             this.toolTip.SetToolTip(this.btnOpenAppFolder, "Opens the folder where the app is located."); | ||||
|             this.btnOpenAppFolder.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // btnOpenDataFolder | ||||
|             //  | ||||
|             this.btnOpenDataFolder.Location = new System.Drawing.Point(5, 32); | ||||
|             this.btnOpenDataFolder.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); | ||||
|             this.btnOpenDataFolder.Name = "btnOpenDataFolder"; | ||||
|             this.btnOpenDataFolder.Size = new System.Drawing.Size(144, 23); | ||||
|             this.btnOpenDataFolder.TabIndex = 1; | ||||
|             this.btnOpenDataFolder.Text = "Open Data Folder"; | ||||
|             this.toolTip.SetToolTip(this.btnOpenDataFolder, "Opens the folder where your profile data is located."); | ||||
|             this.btnOpenDataFolder.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // numMemoryThreshold | ||||
|             //  | ||||
|             this.numMemoryThreshold.Increment = new decimal(new int[] { | ||||
|             50, | ||||
|             0, | ||||
|             0, | ||||
|             0}); | ||||
|             this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82); | ||||
|             this.numMemoryThreshold.Maximum = 2000; | ||||
|             this.numMemoryThreshold.Minimum = 200; | ||||
|             this.numMemoryThreshold.Name = "numMemoryThreshold"; | ||||
|             this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20); | ||||
|             this.numMemoryThreshold.TabIndex = 4; | ||||
|             this.numMemoryThreshold.TextSuffix = " MB"; | ||||
|             this.toolTip.SetToolTip(this.numMemoryThreshold, "Minimum amount of memory usage by the browser process to trigger the cleanup.\r\nThis is not a limit, the usage is allowed to exceed this value."); | ||||
|             this.numMemoryThreshold.Value = 400; | ||||
|             //  | ||||
|             // checkBrowserGCReload | ||||
|             //  | ||||
|             this.checkBrowserGCReload.AutoSize = true; | ||||
|             this.checkBrowserGCReload.Location = new System.Drawing.Point(6, 84); | ||||
|             this.checkBrowserGCReload.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3); | ||||
|             this.checkBrowserGCReload.Name = "checkBrowserGCReload"; | ||||
|             this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17); | ||||
|             this.checkBrowserGCReload.TabIndex = 3; | ||||
|             this.checkBrowserGCReload.Text = "Enable Browser Memory Threshold"; | ||||
|             this.toolTip.SetToolTip(this.checkBrowserGCReload, "Automatically reloads TweetDeck to save memory. This option only works if\r\nthe browser is in a \'default state\', i.e. all modals and drawers are closed, and\r\nall columns are scrolled to top. This option will not be exported in a profile."); | ||||
|             this.checkBrowserGCReload.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // labelApp | ||||
|             //  | ||||
|             this.labelApp.AutoSize = true; | ||||
|             this.labelApp.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); | ||||
|             this.labelApp.Location = new System.Drawing.Point(6, 8); | ||||
|             this.labelApp.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0); | ||||
|             this.labelApp.Name = "labelApp"; | ||||
|             this.labelApp.Size = new System.Drawing.Size(38, 20); | ||||
|             this.labelApp.TabIndex = 0; | ||||
|             this.labelApp.Text = "App"; | ||||
|             //  | ||||
|             // panelApp | ||||
|             //  | ||||
|             this.panelApp.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)  | ||||
|             | System.Windows.Forms.AnchorStyles.Right))); | ||||
|             this.panelApp.Controls.Add(this.btnOpenDataFolder); | ||||
|             this.panelApp.Controls.Add(this.btnOpenAppFolder); | ||||
|             this.panelApp.Controls.Add(this.btnRestart); | ||||
|             this.panelApp.Controls.Add(this.btnRestartArgs); | ||||
|             this.panelApp.Location = new System.Drawing.Point(9, 31); | ||||
|             this.panelApp.Name = "panelApp"; | ||||
|             this.panelApp.Size = new System.Drawing.Size(322, 59); | ||||
|             this.panelApp.TabIndex = 1; | ||||
|             //  | ||||
|             // labelPerformance | ||||
|             //  | ||||
|             this.labelPerformance.AutoSize = true; | ||||
|             this.labelPerformance.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); | ||||
|             this.labelPerformance.Location = new System.Drawing.Point(6, 114); | ||||
|             this.labelPerformance.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); | ||||
|             this.labelPerformance.Name = "labelPerformance"; | ||||
|             this.labelPerformance.Size = new System.Drawing.Size(100, 20); | ||||
|             this.labelPerformance.TabIndex = 2; | ||||
|             this.labelPerformance.Text = "Performance"; | ||||
|             //  | ||||
|             // panelPerformance | ||||
|             //  | ||||
|             this.panelPerformance.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)  | ||||
|             | System.Windows.Forms.AnchorStyles.Right))); | ||||
|             this.panelPerformance.Controls.Add(this.checkBrowserGCReload); | ||||
|             this.panelPerformance.Controls.Add(this.numMemoryThreshold); | ||||
|             this.panelPerformance.Controls.Add(this.labelMemoryUsage); | ||||
|             this.panelPerformance.Controls.Add(this.checkHardwareAcceleration); | ||||
|             this.panelPerformance.Controls.Add(this.btnClearCache); | ||||
|             this.panelPerformance.Location = new System.Drawing.Point(9, 137); | ||||
|             this.panelPerformance.Name = "panelPerformance"; | ||||
|             this.panelPerformance.Size = new System.Drawing.Size(322, 105); | ||||
|             this.panelPerformance.TabIndex = 3; | ||||
|             //  | ||||
|             // labelMemoryUsage | ||||
|             //  | ||||
|             this.labelMemoryUsage.AutoSize = true; | ||||
|             this.labelMemoryUsage.Location = new System.Drawing.Point(3, 66); | ||||
|             this.labelMemoryUsage.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); | ||||
|             this.labelMemoryUsage.Name = "labelMemoryUsage"; | ||||
|             this.labelMemoryUsage.Size = new System.Drawing.Size(78, 13); | ||||
|             this.labelMemoryUsage.TabIndex = 2; | ||||
|             this.labelMemoryUsage.Text = "Memory Usage"; | ||||
|             //  | ||||
|             // panelConfiguration | ||||
|             //  | ||||
|             this.panelConfiguration.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)  | ||||
|             | System.Windows.Forms.AnchorStyles.Right))); | ||||
|             this.panelConfiguration.Controls.Add(this.btnEditCSS); | ||||
|             this.panelConfiguration.Controls.Add(this.btnEditCefArgs); | ||||
|             this.panelConfiguration.Location = new System.Drawing.Point(9, 289); | ||||
|             this.panelConfiguration.Name = "panelConfiguration"; | ||||
|             this.panelConfiguration.Size = new System.Drawing.Size(322, 29); | ||||
|             this.panelConfiguration.TabIndex = 5; | ||||
|             //  | ||||
|             // labelConfiguration | ||||
|             //  | ||||
|             this.labelConfiguration.AutoSize = true; | ||||
|             this.labelConfiguration.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); | ||||
|             this.labelConfiguration.Location = new System.Drawing.Point(6, 266); | ||||
|             this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); | ||||
|             this.labelConfiguration.Name = "labelConfiguration"; | ||||
|             this.labelConfiguration.Size = new System.Drawing.Size(104, 20); | ||||
|             this.labelConfiguration.TabIndex = 4; | ||||
|             this.labelConfiguration.Text = "Configuration"; | ||||
|             //  | ||||
|             // TabSettingsAdvanced | ||||
|             //  | ||||
|             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); | ||||
|             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | ||||
|             this.Controls.Add(this.labelConfiguration); | ||||
|             this.Controls.Add(this.panelConfiguration); | ||||
|             this.Controls.Add(this.panelPerformance); | ||||
|             this.Controls.Add(this.labelPerformance); | ||||
|             this.Controls.Add(this.panelApp); | ||||
|             this.Controls.Add(this.labelApp); | ||||
|             this.Name = "TabSettingsAdvanced"; | ||||
|             this.Size = new System.Drawing.Size(340, 328); | ||||
|             ((System.ComponentModel.ISupportInitialize)(this.numMemoryThreshold)).EndInit(); | ||||
|             this.panelApp.ResumeLayout(false); | ||||
|             this.panelPerformance.ResumeLayout(false); | ||||
|             this.panelPerformance.PerformLayout(); | ||||
|             this.panelConfiguration.ResumeLayout(false); | ||||
|             this.ResumeLayout(false); | ||||
|             this.PerformLayout(); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         private System.Windows.Forms.Button btnClearCache; | ||||
|         private System.Windows.Forms.CheckBox checkHardwareAcceleration; | ||||
|         private System.Windows.Forms.ToolTip toolTip; | ||||
|         private System.Windows.Forms.Button btnEditCefArgs; | ||||
|         private System.Windows.Forms.Button btnEditCSS; | ||||
|         private System.Windows.Forms.Button btnRestartArgs; | ||||
|         private System.Windows.Forms.Button btnRestart; | ||||
|         private System.Windows.Forms.Button btnOpenAppFolder; | ||||
|         private System.Windows.Forms.Button btnOpenDataFolder; | ||||
|         private System.Windows.Forms.Label labelApp; | ||||
|         private System.Windows.Forms.Panel panelApp; | ||||
|         private System.Windows.Forms.Label labelPerformance; | ||||
|         private System.Windows.Forms.Panel panelPerformance; | ||||
|         private System.Windows.Forms.Panel panelConfiguration; | ||||
|         private System.Windows.Forms.Label labelConfiguration; | ||||
|         private System.Windows.Forms.Label labelMemoryUsage; | ||||
|         private Controls.NumericUpDownEx numMemoryThreshold; | ||||
|         private System.Windows.Forms.CheckBox checkBrowserGCReload; | ||||
|     } | ||||
| } | ||||
| @@ -1,153 +0,0 @@ | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Configuration; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Other.Settings.Dialogs; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings{ | ||||
|     sealed partial class TabSettingsAdvanced : BaseTabSettings{ | ||||
|         private static SystemConfig SysConfig => Program.SystemConfig; | ||||
| 
 | ||||
|         private readonly Action<string> reinjectBrowserCSS; | ||||
| 
 | ||||
|         public TabSettingsAdvanced(Action<string> reinjectBrowserCSS){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             this.reinjectBrowserCSS = reinjectBrowserCSS; | ||||
| 
 | ||||
|             if (SystemConfig.IsHardwareAccelerationSupported){ | ||||
|                 checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration; | ||||
|             } | ||||
|             else{ | ||||
|                 checkHardwareAcceleration.Enabled = false; | ||||
|                 checkHardwareAcceleration.Checked = false; | ||||
|             } | ||||
| 
 | ||||
|             checkBrowserGCReload.Checked = SysConfig.EnableBrowserGCReload; | ||||
|             numMemoryThreshold.Enabled = checkBrowserGCReload.Checked; | ||||
|             numMemoryThreshold.SetValueSafe(SysConfig.BrowserMemoryThreshold); | ||||
| 
 | ||||
|             BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => { | ||||
|                 if (bytes == -1L){ | ||||
|                     btnClearCache.Text = "Clear Cache (unknown size)"; | ||||
|                 } | ||||
|                 else{ | ||||
|                     btnClearCache.Text = "Clear Cache ("+(int)Math.Ceiling(bytes/(1024.0*1024.0))+" MB)"; | ||||
|                 } | ||||
|             })); | ||||
|         } | ||||
| 
 | ||||
|         public override void OnReady(){ | ||||
|             btnClearCache.Click += btnClearCache_Click; | ||||
|             checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged; | ||||
| 
 | ||||
|             checkBrowserGCReload.CheckedChanged += checkBrowserGCReload_CheckedChanged; | ||||
|             numMemoryThreshold.ValueChanged += numMemoryThreshold_ValueChanged; | ||||
| 
 | ||||
|             btnEditCefArgs.Click += btnEditCefArgs_Click; | ||||
|             btnEditCSS.Click += btnEditCSS_Click; | ||||
|              | ||||
|             btnOpenAppFolder.Click += btnOpenAppFolder_Click; | ||||
|             btnOpenDataFolder.Click += btnOpenDataFolder_Click; | ||||
|             btnRestart.Click += btnRestart_Click; | ||||
|             btnRestartArgs.Click += btnRestartArgs_Click; | ||||
|         } | ||||
| 
 | ||||
|         public override void OnClosing(){ | ||||
|             SysConfig.Save(); | ||||
|         } | ||||
| 
 | ||||
|         private void btnClearCache_Click(object sender, EventArgs e){ | ||||
|             btnClearCache.Enabled = false; | ||||
|             BrowserCache.SetClearOnExit(); | ||||
|             FormMessage.Information("Clear Cache", "Cache will be automatically cleared when TweetDuck exits.", FormMessage.OK); | ||||
|         } | ||||
| 
 | ||||
|         private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){ | ||||
|             SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked; | ||||
|             PromptRestart(); // calls OnClosing | ||||
|         } | ||||
| 
 | ||||
|         private void checkBrowserGCReload_CheckedChanged(object sender, EventArgs e){ | ||||
|             SysConfig.EnableBrowserGCReload = checkBrowserGCReload.Checked; | ||||
|             numMemoryThreshold.Enabled = checkBrowserGCReload.Checked; | ||||
|         } | ||||
| 
 | ||||
|         private void numMemoryThreshold_ValueChanged(object sender, EventArgs e){ | ||||
|             SysConfig.BrowserMemoryThreshold = (int)numMemoryThreshold.Value; | ||||
|         } | ||||
| 
 | ||||
|         private void btnEditCefArgs_Click(object sender, EventArgs e){ | ||||
|             DialogSettingsCefArgs form = new DialogSettingsCefArgs(); | ||||
| 
 | ||||
|             form.VisibleChanged += (sender2, args2) => { | ||||
|                 form.MoveToCenter(ParentForm); | ||||
|             }; | ||||
| 
 | ||||
|             form.FormClosed += (sender2, args2) => { | ||||
|                 RestoreParentForm(); | ||||
| 
 | ||||
|                 if (form.DialogResult == DialogResult.OK){ | ||||
|                     Config.CustomCefArgs = form.CefArgs; | ||||
|                     PromptRestart(); | ||||
|                     form.Dispose(); | ||||
|                 } | ||||
|                 else form.Dispose(); | ||||
|             }; | ||||
|              | ||||
|             form.Show(ParentForm); | ||||
|             NativeMethods.SetFormDisabled(ParentForm, true); | ||||
|         } | ||||
| 
 | ||||
|         private void btnEditCSS_Click(object sender, EventArgs e){ | ||||
|             DialogSettingsCSS form = new DialogSettingsCSS(reinjectBrowserCSS); | ||||
| 
 | ||||
|             form.VisibleChanged += (sender2, args2) => { | ||||
|                 form.MoveToCenter(ParentForm); | ||||
|             }; | ||||
| 
 | ||||
|             form.FormClosed += (sender2, args2) => { | ||||
|                 RestoreParentForm(); | ||||
| 
 | ||||
|                 if (form.DialogResult == DialogResult.OK){ | ||||
|                     Config.CustomBrowserCSS = form.BrowserCSS; | ||||
|                     Config.CustomNotificationCSS = form.NotificationCSS; | ||||
|                 } | ||||
| 
 | ||||
|                 reinjectBrowserCSS(Config.CustomBrowserCSS); // reinject on cancel too, because the CSS is updated while typing | ||||
|                 form.Dispose(); | ||||
|             }; | ||||
|              | ||||
|             form.Show(ParentForm); | ||||
|             NativeMethods.SetFormDisabled(ParentForm, true); | ||||
|         } | ||||
| 
 | ||||
|         private void btnOpenAppFolder_Click(object sender, EventArgs e){ | ||||
|             using(Process.Start("explorer.exe", "\""+Program.ProgramPath+"\"")){} | ||||
|         } | ||||
| 
 | ||||
|         private void btnOpenDataFolder_Click(object sender, EventArgs e){ | ||||
|             using(Process.Start("explorer.exe", "\""+Program.StoragePath+"\"")){} | ||||
|         } | ||||
| 
 | ||||
|         private void btnRestart_Click(object sender, EventArgs e){ | ||||
|             Program.Restart(); | ||||
|         } | ||||
| 
 | ||||
|         private void btnRestartArgs_Click(object sender, EventArgs e){ | ||||
|             using(DialogSettingsRestart dialog = new DialogSettingsRestart(Arguments.GetCurrentClean())){ | ||||
|                 if (dialog.ShowDialog() == DialogResult.OK){ | ||||
|                     Program.RestartWithArgs(dialog.Args); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void RestoreParentForm(){ | ||||
|             if (ParentForm != null){ // when the parent is closed first, ParentForm is null in FormClosed event | ||||
|                 NativeMethods.SetFormDisabled(ParentForm, false); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										134
									
								
								Core/Other/Settings/TabSettingsFeedback.Designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										134
									
								
								Core/Other/Settings/TabSettingsFeedback.Designer.cs
									
									
									
										generated
									
									
									
								
							| @@ -1,134 +0,0 @@ | ||||
| namespace TweetDuck.Core.Other.Settings { | ||||
|     partial class TabSettingsFeedback { | ||||
|         /// <summary>  | ||||
|         /// Required designer variable. | ||||
|         /// </summary> | ||||
|         private System.ComponentModel.IContainer components = null; | ||||
| 
 | ||||
|         /// <summary>  | ||||
|         /// Clean up any resources being used. | ||||
|         /// </summary> | ||||
|         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | ||||
|         protected override void Dispose(bool disposing) { | ||||
|             if (disposing && (components != null)) { | ||||
|                 components.Dispose(); | ||||
|             } | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
| 
 | ||||
|         #region Component Designer generated code | ||||
| 
 | ||||
|         /// <summary>  | ||||
|         /// Required method for Designer support - do not modify  | ||||
|         /// the contents of this method with the code editor. | ||||
|         /// </summary> | ||||
|         private void InitializeComponent() { | ||||
|             this.components = new System.ComponentModel.Container(); | ||||
|             this.panelFeedback = new System.Windows.Forms.Panel(); | ||||
|             this.labelDataCollectionLink = new System.Windows.Forms.LinkLabel(); | ||||
|             this.checkDataCollection = new System.Windows.Forms.CheckBox(); | ||||
|             this.labelDataCollection = new System.Windows.Forms.Label(); | ||||
|             this.labelFeedback = new System.Windows.Forms.Label(); | ||||
|             this.toolTip = new System.Windows.Forms.ToolTip(this.components); | ||||
|             this.btnSendFeedback = new System.Windows.Forms.Button(); | ||||
|             this.panelFeedback.SuspendLayout(); | ||||
|             this.SuspendLayout(); | ||||
|             //  | ||||
|             // panelFeedback | ||||
|             //  | ||||
|             this.panelFeedback.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)  | ||||
|             | System.Windows.Forms.AnchorStyles.Right))); | ||||
|             this.panelFeedback.Controls.Add(this.btnSendFeedback); | ||||
|             this.panelFeedback.Controls.Add(this.labelDataCollectionLink); | ||||
|             this.panelFeedback.Controls.Add(this.checkDataCollection); | ||||
|             this.panelFeedback.Controls.Add(this.labelDataCollection); | ||||
|             this.panelFeedback.Location = new System.Drawing.Point(9, 31); | ||||
|             this.panelFeedback.Name = "panelFeedback"; | ||||
|             this.panelFeedback.Size = new System.Drawing.Size(322, 80); | ||||
|             this.panelFeedback.TabIndex = 1; | ||||
|             //  | ||||
|             // labelDataCollectionLink | ||||
|             //  | ||||
|             this.labelDataCollectionLink.AutoSize = true; | ||||
|             this.labelDataCollectionLink.LinkArea = new System.Windows.Forms.LinkArea(1, 10); | ||||
|             this.labelDataCollectionLink.LinkBehavior = System.Windows.Forms.LinkBehavior.HoverUnderline; | ||||
|             this.labelDataCollectionLink.Location = new System.Drawing.Point(141, 60); | ||||
|             this.labelDataCollectionLink.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); | ||||
|             this.labelDataCollectionLink.Name = "labelDataCollectionLink"; | ||||
|             this.labelDataCollectionLink.Size = new System.Drawing.Size(66, 17); | ||||
|             this.labelDataCollectionLink.TabIndex = 3; | ||||
|             this.labelDataCollectionLink.TabStop = true; | ||||
|             this.labelDataCollectionLink.Text = "(learn more)"; | ||||
|             this.labelDataCollectionLink.UseCompatibleTextRendering = true; | ||||
|             this.labelDataCollectionLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.labelDataCollectionLink_LinkClicked); | ||||
|             //  | ||||
|             // checkDataCollection | ||||
|             //  | ||||
|             this.checkDataCollection.AutoSize = true; | ||||
|             this.checkDataCollection.Location = new System.Drawing.Point(6, 59); | ||||
|             this.checkDataCollection.Margin = new System.Windows.Forms.Padding(6, 5, 0, 3); | ||||
|             this.checkDataCollection.Name = "checkDataCollection"; | ||||
|             this.checkDataCollection.Size = new System.Drawing.Size(135, 17); | ||||
|             this.checkDataCollection.TabIndex = 2; | ||||
|             this.checkDataCollection.Text = "Send Anonymous Data"; | ||||
|             this.checkDataCollection.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // labelDataCollection | ||||
|             //  | ||||
|             this.labelDataCollection.AutoSize = true; | ||||
|             this.labelDataCollection.Location = new System.Drawing.Point(3, 41); | ||||
|             this.labelDataCollection.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); | ||||
|             this.labelDataCollection.Name = "labelDataCollection"; | ||||
|             this.labelDataCollection.Size = new System.Drawing.Size(79, 13); | ||||
|             this.labelDataCollection.TabIndex = 1; | ||||
|             this.labelDataCollection.Text = "Data Collection"; | ||||
|             //  | ||||
|             // labelFeedback | ||||
|             //  | ||||
|             this.labelFeedback.AutoSize = true; | ||||
|             this.labelFeedback.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); | ||||
|             this.labelFeedback.Location = new System.Drawing.Point(6, 8); | ||||
|             this.labelFeedback.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0); | ||||
|             this.labelFeedback.Name = "labelFeedback"; | ||||
|             this.labelFeedback.Size = new System.Drawing.Size(80, 20); | ||||
|             this.labelFeedback.TabIndex = 0; | ||||
|             this.labelFeedback.Text = "Feedback"; | ||||
|             //  | ||||
|             // btnSendFeedback | ||||
|             //  | ||||
|             this.btnSendFeedback.AutoSize = true; | ||||
|             this.btnSendFeedback.Location = new System.Drawing.Point(5, 3); | ||||
|             this.btnSendFeedback.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); | ||||
|             this.btnSendFeedback.Name = "btnSendFeedback"; | ||||
|             this.btnSendFeedback.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); | ||||
|             this.btnSendFeedback.Size = new System.Drawing.Size(164, 23); | ||||
|             this.btnSendFeedback.TabIndex = 0; | ||||
|             this.btnSendFeedback.Text = "Send Feedback / Bug Report"; | ||||
|             this.btnSendFeedback.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // TabSettingsFeedback | ||||
|             //  | ||||
|             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); | ||||
|             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | ||||
|             this.Controls.Add(this.panelFeedback); | ||||
|             this.Controls.Add(this.labelFeedback); | ||||
|             this.Name = "TabSettingsFeedback"; | ||||
|             this.Size = new System.Drawing.Size(340, 122); | ||||
|             this.panelFeedback.ResumeLayout(false); | ||||
|             this.panelFeedback.PerformLayout(); | ||||
|             this.ResumeLayout(false); | ||||
|             this.PerformLayout(); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         private System.Windows.Forms.Panel panelFeedback; | ||||
|         private System.Windows.Forms.CheckBox checkDataCollection; | ||||
|         private System.Windows.Forms.Label labelDataCollection; | ||||
|         private System.Windows.Forms.Label labelFeedback; | ||||
|         private System.Windows.Forms.ToolTip toolTip; | ||||
|         private System.Windows.Forms.LinkLabel labelDataCollectionLink; | ||||
|         private System.Windows.Forms.Button btnSendFeedback; | ||||
|     } | ||||
| } | ||||
| @@ -1,30 +0,0 @@ | ||||
| using System; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings{ | ||||
|     sealed partial class TabSettingsFeedback : BaseTabSettings{ | ||||
|         public TabSettingsFeedback(){ | ||||
|             InitializeComponent(); | ||||
|              | ||||
|             checkDataCollection.Checked = Config.AllowDataCollection; | ||||
|         } | ||||
| 
 | ||||
|         public override void OnReady(){ | ||||
|             btnSendFeedback.Click += btnSendFeedback_Click; | ||||
|             checkDataCollection.CheckedChanged += checkDataCollection_CheckedChanged; | ||||
|         } | ||||
| 
 | ||||
|         private void btnSendFeedback_Click(object sender, EventArgs e){ | ||||
|             BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/issues/new"); | ||||
|         } | ||||
| 
 | ||||
|         private void checkDataCollection_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.AllowDataCollection = checkDataCollection.Checked; | ||||
|         } | ||||
| 
 | ||||
|         private void labelDataCollectionLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e){ | ||||
|             BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										262
									
								
								Core/Other/Settings/TabSettingsGeneral.Designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										262
									
								
								Core/Other/Settings/TabSettingsGeneral.Designer.cs
									
									
									
										generated
									
									
									
								
							| @@ -1,262 +0,0 @@ | ||||
| namespace TweetDuck.Core.Other.Settings { | ||||
|     partial class TabSettingsGeneral { | ||||
|         /// <summary>  | ||||
|         /// Required designer variable. | ||||
|         /// </summary> | ||||
|         private System.ComponentModel.IContainer components = null; | ||||
| 
 | ||||
|         /// <summary>  | ||||
|         /// Clean up any resources being used. | ||||
|         /// </summary> | ||||
|         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | ||||
|         protected override void Dispose(bool disposing) { | ||||
|             if (disposing && (components != null)) { | ||||
|                 components.Dispose(); | ||||
|             } | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
| 
 | ||||
|         #region Component Designer generated code | ||||
| 
 | ||||
|         /// <summary>  | ||||
|         /// Required method for Designer support - do not modify  | ||||
|         /// the contents of this method with the code editor. | ||||
|         /// </summary> | ||||
|         private void InitializeComponent() { | ||||
|             this.components = new System.ComponentModel.Container(); | ||||
|             this.checkExpandLinks = new System.Windows.Forms.CheckBox(); | ||||
|             this.toolTip = new System.Windows.Forms.ToolTip(this.components); | ||||
|             this.checkSpellCheck = new System.Windows.Forms.CheckBox(); | ||||
|             this.checkUpdateNotifications = new System.Windows.Forms.CheckBox(); | ||||
|             this.btnCheckUpdates = new System.Windows.Forms.Button(); | ||||
|             this.labelZoomValue = new System.Windows.Forms.Label(); | ||||
|             this.checkSwitchAccountSelectors = new System.Windows.Forms.CheckBox(); | ||||
|             this.checkBestImageQuality = new System.Windows.Forms.CheckBox(); | ||||
|             this.checkOpenSearchInFirstColumn = new System.Windows.Forms.CheckBox(); | ||||
|             this.trackBarZoom = new System.Windows.Forms.TrackBar(); | ||||
|             this.labelZoom = new System.Windows.Forms.Label(); | ||||
|             this.zoomUpdateTimer = new System.Windows.Forms.Timer(this.components); | ||||
|             this.labelUI = new System.Windows.Forms.Label(); | ||||
|             this.panelUI = new System.Windows.Forms.Panel(); | ||||
|             this.panelUpdates = new System.Windows.Forms.Panel(); | ||||
|             this.labelUpdates = new System.Windows.Forms.Label(); | ||||
|             ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit(); | ||||
|             this.panelUI.SuspendLayout(); | ||||
|             this.panelUpdates.SuspendLayout(); | ||||
|             this.SuspendLayout(); | ||||
|             //  | ||||
|             // checkExpandLinks | ||||
|             //  | ||||
|             this.checkExpandLinks.AutoSize = true; | ||||
|             this.checkExpandLinks.Location = new System.Drawing.Point(6, 5); | ||||
|             this.checkExpandLinks.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3); | ||||
|             this.checkExpandLinks.Name = "checkExpandLinks"; | ||||
|             this.checkExpandLinks.Size = new System.Drawing.Size(166, 17); | ||||
|             this.checkExpandLinks.TabIndex = 0; | ||||
|             this.checkExpandLinks.Text = "Expand Links When Hovered"; | ||||
|             this.toolTip.SetToolTip(this.checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead."); | ||||
|             this.checkExpandLinks.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // checkSpellCheck | ||||
|             //  | ||||
|             this.checkSpellCheck.AutoSize = true; | ||||
|             this.checkSpellCheck.Location = new System.Drawing.Point(6, 97); | ||||
|             this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); | ||||
|             this.checkSpellCheck.Name = "checkSpellCheck"; | ||||
|             this.checkSpellCheck.Size = new System.Drawing.Size(119, 17); | ||||
|             this.checkSpellCheck.TabIndex = 4; | ||||
|             this.checkSpellCheck.Text = "Enable Spell Check"; | ||||
|             this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly."); | ||||
|             this.checkSpellCheck.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // checkUpdateNotifications | ||||
|             //  | ||||
|             this.checkUpdateNotifications.AutoSize = true; | ||||
|             this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 5); | ||||
|             this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3); | ||||
|             this.checkUpdateNotifications.Name = "checkUpdateNotifications"; | ||||
|             this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17); | ||||
|             this.checkUpdateNotifications.TabIndex = 0; | ||||
|             this.checkUpdateNotifications.Text = "Check Updates Automatically"; | ||||
|             this.toolTip.SetToolTip(this.checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear again."); | ||||
|             this.checkUpdateNotifications.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // btnCheckUpdates | ||||
|             //  | ||||
|             this.btnCheckUpdates.Location = new System.Drawing.Point(5, 28); | ||||
|             this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); | ||||
|             this.btnCheckUpdates.Name = "btnCheckUpdates"; | ||||
|             this.btnCheckUpdates.Size = new System.Drawing.Size(144, 23); | ||||
|             this.btnCheckUpdates.TabIndex = 1; | ||||
|             this.btnCheckUpdates.Text = "Check Updates Now"; | ||||
|             this.toolTip.SetToolTip(this.btnCheckUpdates, "Forces an update check, even for updates that had been dismissed."); | ||||
|             this.btnCheckUpdates.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // labelZoomValue | ||||
|             //  | ||||
|             this.labelZoomValue.BackColor = System.Drawing.Color.Transparent; | ||||
|             this.labelZoomValue.Location = new System.Drawing.Point(147, 146); | ||||
|             this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); | ||||
|             this.labelZoomValue.Name = "labelZoomValue"; | ||||
|             this.labelZoomValue.Size = new System.Drawing.Size(38, 13); | ||||
|             this.labelZoomValue.TabIndex = 7; | ||||
|             this.labelZoomValue.Text = "100%"; | ||||
|             this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight; | ||||
|             this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots."); | ||||
|             //  | ||||
|             // checkSwitchAccountSelectors | ||||
|             //  | ||||
|             this.checkSwitchAccountSelectors.AutoSize = true; | ||||
|             this.checkSwitchAccountSelectors.Location = new System.Drawing.Point(6, 28); | ||||
|             this.checkSwitchAccountSelectors.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); | ||||
|             this.checkSwitchAccountSelectors.Name = "checkSwitchAccountSelectors"; | ||||
|             this.checkSwitchAccountSelectors.Size = new System.Drawing.Size(172, 17); | ||||
|             this.checkSwitchAccountSelectors.TabIndex = 1; | ||||
|             this.checkSwitchAccountSelectors.Text = "Shift Selects Multiple Accounts"; | ||||
|             this.toolTip.SetToolTip(this.checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect multiple accounts, instead of TweetDeck\'s default behavior."); | ||||
|             this.checkSwitchAccountSelectors.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // checkBestImageQuality | ||||
|             //  | ||||
|             this.checkBestImageQuality.AutoSize = true; | ||||
|             this.checkBestImageQuality.Location = new System.Drawing.Point(6, 74); | ||||
|             this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); | ||||
|             this.checkBestImageQuality.Name = "checkBestImageQuality"; | ||||
|             this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17); | ||||
|             this.checkBestImageQuality.TabIndex = 3; | ||||
|             this.checkBestImageQuality.Text = "Best Image Quality"; | ||||
|             this.toolTip.SetToolTip(this.checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL)."); | ||||
|             this.checkBestImageQuality.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // checkOpenSearchInFirstColumn | ||||
|             //  | ||||
|             this.checkOpenSearchInFirstColumn.AutoSize = true; | ||||
|             this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 51); | ||||
|             this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); | ||||
|             this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn"; | ||||
|             this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(219, 17); | ||||
|             this.checkOpenSearchInFirstColumn.TabIndex = 2; | ||||
|             this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column"; | ||||
|             this.toolTip.SetToolTip(this.checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead."); | ||||
|             this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true; | ||||
|             //  | ||||
|             // trackBarZoom | ||||
|             //  | ||||
|             this.trackBarZoom.AutoSize = false; | ||||
|             this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control; | ||||
|             this.trackBarZoom.LargeChange = 25; | ||||
|             this.trackBarZoom.Location = new System.Drawing.Point(3, 145); | ||||
|             this.trackBarZoom.Maximum = 200; | ||||
|             this.trackBarZoom.Minimum = 50; | ||||
|             this.trackBarZoom.Name = "trackBarZoom"; | ||||
|             this.trackBarZoom.Size = new System.Drawing.Size(148, 30); | ||||
|             this.trackBarZoom.SmallChange = 5; | ||||
|             this.trackBarZoom.TabIndex = 6; | ||||
|             this.trackBarZoom.TickFrequency = 25; | ||||
|             this.trackBarZoom.Value = 100; | ||||
|             //  | ||||
|             // labelZoom | ||||
|             //  | ||||
|             this.labelZoom.AutoSize = true; | ||||
|             this.labelZoom.Location = new System.Drawing.Point(3, 129); | ||||
|             this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); | ||||
|             this.labelZoom.Name = "labelZoom"; | ||||
|             this.labelZoom.Size = new System.Drawing.Size(34, 13); | ||||
|             this.labelZoom.TabIndex = 5; | ||||
|             this.labelZoom.Text = "Zoom"; | ||||
|             //  | ||||
|             // zoomUpdateTimer | ||||
|             //  | ||||
|             this.zoomUpdateTimer.Interval = 250; | ||||
|             this.zoomUpdateTimer.Tick += new System.EventHandler(this.zoomUpdateTimer_Tick); | ||||
|             //  | ||||
|             // labelUI | ||||
|             //  | ||||
|             this.labelUI.AutoSize = true; | ||||
|             this.labelUI.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); | ||||
|             this.labelUI.Location = new System.Drawing.Point(6, 8); | ||||
|             this.labelUI.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0); | ||||
|             this.labelUI.Name = "labelUI"; | ||||
|             this.labelUI.Size = new System.Drawing.Size(111, 20); | ||||
|             this.labelUI.TabIndex = 0; | ||||
|             this.labelUI.Text = "User Interface"; | ||||
|             //  | ||||
|             // panelUI | ||||
|             //  | ||||
|             this.panelUI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)  | ||||
|             | System.Windows.Forms.AnchorStyles.Right))); | ||||
|             this.panelUI.Controls.Add(this.checkOpenSearchInFirstColumn); | ||||
|             this.panelUI.Controls.Add(this.checkBestImageQuality); | ||||
|             this.panelUI.Controls.Add(this.checkExpandLinks); | ||||
|             this.panelUI.Controls.Add(this.checkSwitchAccountSelectors); | ||||
|             this.panelUI.Controls.Add(this.checkSpellCheck); | ||||
|             this.panelUI.Controls.Add(this.labelZoom); | ||||
|             this.panelUI.Controls.Add(this.trackBarZoom); | ||||
|             this.panelUI.Controls.Add(this.labelZoomValue); | ||||
|             this.panelUI.Location = new System.Drawing.Point(9, 31); | ||||
|             this.panelUI.Name = "panelUI"; | ||||
|             this.panelUI.Size = new System.Drawing.Size(322, 179); | ||||
|             this.panelUI.TabIndex = 1; | ||||
|             //  | ||||
|             // panelUpdates | ||||
|             //  | ||||
|             this.panelUpdates.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)  | ||||
|             | System.Windows.Forms.AnchorStyles.Right))); | ||||
|             this.panelUpdates.Controls.Add(this.checkUpdateNotifications); | ||||
|             this.panelUpdates.Controls.Add(this.btnCheckUpdates); | ||||
|             this.panelUpdates.Location = new System.Drawing.Point(9, 257); | ||||
|             this.panelUpdates.Name = "panelUpdates"; | ||||
|             this.panelUpdates.Size = new System.Drawing.Size(322, 55); | ||||
|             this.panelUpdates.TabIndex = 3; | ||||
|             //  | ||||
|             // labelUpdates | ||||
|             //  | ||||
|             this.labelUpdates.AutoSize = true; | ||||
|             this.labelUpdates.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); | ||||
|             this.labelUpdates.Location = new System.Drawing.Point(6, 234); | ||||
|             this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); | ||||
|             this.labelUpdates.Name = "labelUpdates"; | ||||
|             this.labelUpdates.Size = new System.Drawing.Size(70, 20); | ||||
|             this.labelUpdates.TabIndex = 2; | ||||
|             this.labelUpdates.Text = "Updates"; | ||||
|             //  | ||||
|             // TabSettingsGeneral | ||||
|             //  | ||||
|             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); | ||||
|             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | ||||
|             this.Controls.Add(this.labelUpdates); | ||||
|             this.Controls.Add(this.panelUpdates); | ||||
|             this.Controls.Add(this.panelUI); | ||||
|             this.Controls.Add(this.labelUI); | ||||
|             this.Name = "TabSettingsGeneral"; | ||||
|             this.Size = new System.Drawing.Size(340, 322); | ||||
|             ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit(); | ||||
|             this.panelUI.ResumeLayout(false); | ||||
|             this.panelUI.PerformLayout(); | ||||
|             this.panelUpdates.ResumeLayout(false); | ||||
|             this.panelUpdates.PerformLayout(); | ||||
|             this.ResumeLayout(false); | ||||
|             this.PerformLayout(); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
| 
 | ||||
|         private System.Windows.Forms.CheckBox checkExpandLinks; | ||||
|         private System.Windows.Forms.ToolTip toolTip; | ||||
|         private System.Windows.Forms.CheckBox checkSpellCheck; | ||||
|         private System.Windows.Forms.CheckBox checkUpdateNotifications; | ||||
|         private System.Windows.Forms.Button btnCheckUpdates; | ||||
|         private System.Windows.Forms.Label labelZoom; | ||||
|         private System.Windows.Forms.Label labelZoomValue; | ||||
|         private System.Windows.Forms.TrackBar trackBarZoom; | ||||
|         private System.Windows.Forms.Timer zoomUpdateTimer; | ||||
|         private System.Windows.Forms.CheckBox checkSwitchAccountSelectors; | ||||
|         private System.Windows.Forms.Label labelUI; | ||||
|         private System.Windows.Forms.Panel panelUI; | ||||
|         private System.Windows.Forms.Panel panelUpdates; | ||||
|         private System.Windows.Forms.Label labelUpdates; | ||||
|         private System.Windows.Forms.CheckBox checkBestImageQuality; | ||||
|         private System.Windows.Forms.CheckBox checkOpenSearchInFirstColumn; | ||||
|     } | ||||
| } | ||||
| @@ -1,103 +0,0 @@ | ||||
| using System; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Updates; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings{ | ||||
|     sealed partial class TabSettingsGeneral : BaseTabSettings{ | ||||
|         private readonly UpdateHandler updates; | ||||
|         private int updateCheckEventId = -1; | ||||
| 
 | ||||
|         public TabSettingsGeneral(UpdateHandler updates){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             this.updates = updates; | ||||
|             this.updates.CheckFinished += updates_CheckFinished; | ||||
|             Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished; | ||||
|              | ||||
|             toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue)); | ||||
|             trackBarZoom.SetValueSafe(Config.ZoomLevel); | ||||
|             labelZoomValue.Text = trackBarZoom.Value+"%"; | ||||
| 
 | ||||
|             checkExpandLinks.Checked = Config.ExpandLinksOnHover; | ||||
|             checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors; | ||||
|             checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn; | ||||
|             checkBestImageQuality.Checked = Config.BestImageQuality; | ||||
|             checkSpellCheck.Checked = Config.EnableSpellCheck; | ||||
| 
 | ||||
|             checkUpdateNotifications.Checked = Config.EnableUpdateCheck; | ||||
|         } | ||||
| 
 | ||||
|         public override void OnReady(){ | ||||
|             checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged; | ||||
|             checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged; | ||||
|             checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged; | ||||
|             checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged; | ||||
|             checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged; | ||||
|             trackBarZoom.ValueChanged += trackBarZoom_ValueChanged; | ||||
| 
 | ||||
|             checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged; | ||||
|             btnCheckUpdates.Click += btnCheckUpdates_Click; | ||||
|         } | ||||
| 
 | ||||
|         public override void OnClosing(){ | ||||
|             Config.ZoomLevel = trackBarZoom.Value; | ||||
|         } | ||||
| 
 | ||||
|         private void checkExpandLinks_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.ExpandLinksOnHover = checkExpandLinks.Checked; | ||||
|         } | ||||
| 
 | ||||
|         private void checkSwitchAccountSelectors_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked; | ||||
|         } | ||||
| 
 | ||||
|         private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked; | ||||
|         } | ||||
| 
 | ||||
|         private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.BestImageQuality = checkBestImageQuality.Checked; | ||||
|         } | ||||
| 
 | ||||
|         private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.EnableSpellCheck = checkSpellCheck.Checked; | ||||
|             PromptRestart(); | ||||
|         } | ||||
| 
 | ||||
|         private void trackBarZoom_ValueChanged(object sender, EventArgs e){ | ||||
|             if (trackBarZoom.AlignValueToTick()){ | ||||
|                 zoomUpdateTimer.Stop(); | ||||
|                 zoomUpdateTimer.Start(); | ||||
|                 labelZoomValue.Text = trackBarZoom.Value+"%"; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.EnableUpdateCheck = checkUpdateNotifications.Checked; | ||||
|         } | ||||
| 
 | ||||
|         private void btnCheckUpdates_Click(object sender, EventArgs e){ | ||||
|             Config.DismissedUpdate = null; | ||||
| 
 | ||||
|             btnCheckUpdates.Enabled = false; | ||||
|             updateCheckEventId = updates.Check(true); | ||||
|         } | ||||
| 
 | ||||
|         private void updates_CheckFinished(object sender, UpdateEventArgs e){ | ||||
|             this.InvokeAsyncSafe(() => { | ||||
|                 if (e.EventId == updateCheckEventId){ | ||||
|                     btnCheckUpdates.Enabled = true; | ||||
| 
 | ||||
|                     if (!e.IsUpdateAvailable){ | ||||
|                         FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         private void zoomUpdateTimer_Tick(object sender, EventArgs e){ | ||||
|             Config.ZoomLevel = trackBarZoom.Value; | ||||
|             zoomUpdateTimer.Stop(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,262 +0,0 @@ | ||||
| using System; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Notification; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings{ | ||||
|     sealed partial class TabSettingsNotifications : BaseTabSettings{ | ||||
|         private static readonly int[] IdlePauseSeconds = { 0, 30, 60, 120, 300 }; | ||||
| 
 | ||||
|         private readonly FormNotificationMain notification; | ||||
| 
 | ||||
|         public TabSettingsNotifications(FormNotificationMain notification){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             this.notification = notification; | ||||
|              | ||||
|             this.notification.Initialized += (sender, args) => { | ||||
|                 this.InvokeAsyncSafe(() => { | ||||
|                     this.notification.ShowNotificationForSettings(true); | ||||
|                     this.notification.Move += notification_Move; | ||||
|                     this.notification.ResizeEnd += notification_ResizeEnd; | ||||
|                 }); | ||||
|             }; | ||||
| 
 | ||||
|             this.notification.Activated += notification_Activated; | ||||
|             this.notification.Show(); | ||||
| 
 | ||||
|             switch(Config.NotificationPosition){ | ||||
|                 case TweetNotification.Position.TopLeft: radioLocTL.Checked = true; break; | ||||
|                 case TweetNotification.Position.TopRight: radioLocTR.Checked = true; break; | ||||
|                 case TweetNotification.Position.BottomLeft: radioLocBL.Checked = true; break; | ||||
|                 case TweetNotification.Position.BottomRight: radioLocBR.Checked = true; break; | ||||
|                 case TweetNotification.Position.Custom: radioLocCustom.Checked = true; break; | ||||
|             } | ||||
| 
 | ||||
|             comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked; | ||||
| 
 | ||||
|             switch(Config.NotificationSize){ | ||||
|                 case TweetNotification.Size.Auto: radioSizeAuto.Checked = true; break; | ||||
|                 case TweetNotification.Size.Custom: radioSizeCustom.Checked = true; break; | ||||
|             } | ||||
|              | ||||
|             toolTip.SetToolTip(trackBarDuration, toolTip.GetToolTip(labelDurationValue)); | ||||
|             trackBarDuration.SetValueSafe(Config.NotificationDurationValue); | ||||
|             labelDurationValue.Text = Config.NotificationDurationValue+" ms/c"; | ||||
| 
 | ||||
|             comboBoxIdlePause.Items.Add("Disabled"); | ||||
|             comboBoxIdlePause.Items.Add("30 seconds"); | ||||
|             comboBoxIdlePause.Items.Add("1 minute"); | ||||
|             comboBoxIdlePause.Items.Add("2 minutes"); | ||||
|             comboBoxIdlePause.Items.Add("5 minutes"); | ||||
|             comboBoxIdlePause.SelectedIndex = Math.Max(0, Array.FindIndex(IdlePauseSeconds, val => val == Config.NotificationIdlePauseSeconds)); | ||||
| 
 | ||||
|             comboBoxDisplay.Items.Add("(Same as TweetDuck)"); | ||||
| 
 | ||||
|             foreach(Screen screen in Screen.AllScreens){ | ||||
|                 comboBoxDisplay.Items.Add(screen.DeviceName.TrimStart('\\', '.')+" ("+screen.Bounds.Width+"x"+screen.Bounds.Height+")"); | ||||
|             } | ||||
| 
 | ||||
|             comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count-1, Config.NotificationDisplay); | ||||
|              | ||||
|             checkColumnName.Checked = Config.DisplayNotificationColumn; | ||||
|             checkNotificationTimer.Checked = Config.DisplayNotificationTimer; | ||||
|             checkTimerCountDown.Enabled = checkNotificationTimer.Checked; | ||||
|             checkTimerCountDown.Checked = Config.NotificationTimerCountDown; | ||||
|             checkMediaPreviews.Checked = Config.NotificationMediaPreviews; | ||||
|             checkSkipOnLinkClick.Checked = Config.NotificationSkipOnLinkClick; | ||||
|             checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode; | ||||
| 
 | ||||
|             trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed); | ||||
|             labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%"; | ||||
| 
 | ||||
|             trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance); | ||||
|             labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px"; | ||||
|              | ||||
|             this.notification.CanMoveWindow = () => radioLocCustom.Checked; | ||||
|             this.notification.CanResizeWindow = radioSizeCustom.Checked; | ||||
| 
 | ||||
|             Disposed += (sender, args) => this.notification.Dispose(); | ||||
|         } | ||||
| 
 | ||||
|         public override void OnReady(){ | ||||
|             radioLocTL.CheckedChanged += radioLoc_CheckedChanged; | ||||
|             radioLocTR.CheckedChanged += radioLoc_CheckedChanged; | ||||
|             radioLocBL.CheckedChanged += radioLoc_CheckedChanged; | ||||
|             radioLocBR.CheckedChanged += radioLoc_CheckedChanged; | ||||
|             radioLocCustom.Click += radioLocCustom_Click; | ||||
| 
 | ||||
|             radioSizeAuto.CheckedChanged += radioSize_CheckedChanged; | ||||
|             radioSizeCustom.Click += radioSizeCustom_Click; | ||||
| 
 | ||||
|             trackBarDuration.ValueChanged += trackBarDuration_ValueChanged; | ||||
|             btnDurationShort.Click += btnDurationShort_Click; | ||||
|             btnDurationMedium.Click += btnDurationMedium_Click; | ||||
|             btnDurationLong.Click += btnDurationLong_Click; | ||||
| 
 | ||||
|             checkColumnName.CheckedChanged += checkColumnName_CheckedChanged; | ||||
|             checkNotificationTimer.CheckedChanged += checkNotificationTimer_CheckedChanged; | ||||
|             checkTimerCountDown.CheckedChanged += checkTimerCountDown_CheckedChanged; | ||||
|             checkMediaPreviews.CheckedChanged += checkMediaPreviews_CheckedChanged; | ||||
|             checkSkipOnLinkClick.CheckedChanged += checkSkipOnLinkClick_CheckedChanged; | ||||
|             checkNonIntrusive.CheckedChanged += checkNonIntrusive_CheckedChanged; | ||||
| 
 | ||||
|             comboBoxIdlePause.SelectedValueChanged += comboBoxIdlePause_SelectedValueChanged; | ||||
|             trackBarScrollSpeed.ValueChanged += trackBarScrollSpeed_ValueChanged; | ||||
| 
 | ||||
|             comboBoxDisplay.SelectedValueChanged += comboBoxDisplay_SelectedValueChanged; | ||||
|             trackBarEdgeDistance.ValueChanged += trackBarEdgeDistance_ValueChanged; | ||||
|         } | ||||
| 
 | ||||
|         private void TabSettingsNotifications_ParentChanged(object sender, EventArgs e){ | ||||
|             if (Parent == null){ | ||||
|                 notification.HideNotification(false); | ||||
|             } | ||||
|             else{ | ||||
|                 notification.ShowNotificationForSettings(true); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void notification_Activated(object sender, EventArgs e){ | ||||
|             notification.Hide(); | ||||
|             notification.Activated -= notification_Activated; | ||||
|         } | ||||
| 
 | ||||
|         private void notification_Move(object sender, EventArgs e){ | ||||
|             if (radioLocCustom.Checked && notification.Location != ControlExtensions.InvisibleLocation){ | ||||
|                 Config.CustomNotificationPosition = notification.Location; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void notification_ResizeEnd(object sender, EventArgs e){ | ||||
|             if (radioSizeCustom.Checked){ | ||||
|                 Config.CustomNotificationSize = notification.BrowserSize; | ||||
|                 notification.ShowNotificationForSettings(false); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void radioLoc_CheckedChanged(object sender, EventArgs e){ | ||||
|             if (radioLocTL.Checked)Config.NotificationPosition = TweetNotification.Position.TopLeft; | ||||
|             else if (radioLocTR.Checked)Config.NotificationPosition = TweetNotification.Position.TopRight; | ||||
|             else if (radioLocBL.Checked)Config.NotificationPosition = TweetNotification.Position.BottomLeft; | ||||
|             else if (radioLocBR.Checked)Config.NotificationPosition = TweetNotification.Position.BottomRight; | ||||
| 
 | ||||
|             comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = true; | ||||
|             notification.ShowNotificationForSettings(false); | ||||
|         } | ||||
| 
 | ||||
|         private void radioLocCustom_Click(object sender, EventArgs e){ | ||||
|             if (!Config.IsCustomNotificationPositionSet){ | ||||
|                 Config.CustomNotificationPosition = notification.Location; | ||||
|             } | ||||
| 
 | ||||
|             Config.NotificationPosition = TweetNotification.Position.Custom; | ||||
| 
 | ||||
|             comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false; | ||||
|             notification.ShowNotificationForSettings(false); | ||||
| 
 | ||||
|             if (notification.IsFullyOutsideView() && FormMessage.Question("Notification is outside view", "The notification seems to be outside of view, would you like to reset its position?", FormMessage.Yes, FormMessage.No)){ | ||||
|                 Config.NotificationPosition = TweetNotification.Position.TopRight; | ||||
|                 notification.MoveToVisibleLocation(); | ||||
| 
 | ||||
|                 Config.CustomNotificationPosition = notification.Location; | ||||
| 
 | ||||
|                 Config.NotificationPosition = TweetNotification.Position.Custom; | ||||
|                 notification.MoveToVisibleLocation(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void radioSize_CheckedChanged(object sender, EventArgs e){ | ||||
|             if (radioSizeAuto.Checked)Config.NotificationSize = TweetNotification.Size.Auto; | ||||
|              | ||||
|             notification.ShowNotificationForSettings(false); | ||||
|             notification.CanResizeWindow = false; // must be after ShowNotificationForSettings | ||||
|         } | ||||
|          | ||||
|         private void radioSizeCustom_Click(object sender, EventArgs e){ | ||||
|             if (!Config.IsCustomNotificationSizeSet){ | ||||
|                 Config.CustomNotificationSize = notification.BrowserSize; | ||||
|             } | ||||
| 
 | ||||
|             Config.NotificationSize = TweetNotification.Size.Custom; | ||||
| 
 | ||||
|             notification.CanResizeWindow = true; | ||||
|             notification.ShowNotificationForSettings(false); | ||||
|         } | ||||
| 
 | ||||
|         private void trackBarDuration_ValueChanged(object sender, EventArgs e){ | ||||
|             durationUpdateTimer.Stop(); | ||||
|             durationUpdateTimer.Start(); | ||||
| 
 | ||||
|             Config.NotificationDurationValue = trackBarDuration.Value; | ||||
|             labelDurationValue.Text = Config.NotificationDurationValue+" ms/c"; | ||||
|         } | ||||
| 
 | ||||
|         private void btnDurationShort_Click(object sender, EventArgs e){ | ||||
|             trackBarDuration.Value = 15; | ||||
|         } | ||||
| 
 | ||||
|         private void btnDurationMedium_Click(object sender, EventArgs e){ | ||||
|             trackBarDuration.Value = 25; | ||||
|         } | ||||
| 
 | ||||
|         private void btnDurationLong_Click(object sender, EventArgs e){ | ||||
|             trackBarDuration.Value = 35; | ||||
|         } | ||||
| 
 | ||||
|         private void checkColumnName_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.DisplayNotificationColumn = checkColumnName.Checked; | ||||
|             notification.ShowNotificationForSettings(false); | ||||
|         } | ||||
| 
 | ||||
|         private void checkNotificationTimer_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.DisplayNotificationTimer = checkNotificationTimer.Checked; | ||||
|             checkTimerCountDown.Enabled = checkNotificationTimer.Checked; | ||||
|             notification.ShowNotificationForSettings(true); | ||||
|         } | ||||
| 
 | ||||
|         private void checkTimerCountDown_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.NotificationTimerCountDown = checkTimerCountDown.Checked; | ||||
|             notification.ShowNotificationForSettings(true); | ||||
|         } | ||||
| 
 | ||||
|         private void checkMediaPreviews_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.NotificationMediaPreviews = checkMediaPreviews.Checked; | ||||
|         } | ||||
| 
 | ||||
|         private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.NotificationSkipOnLinkClick = checkSkipOnLinkClick.Checked; | ||||
|         } | ||||
| 
 | ||||
|         private void checkNonIntrusive_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.NotificationNonIntrusiveMode = checkNonIntrusive.Checked; | ||||
|         } | ||||
| 
 | ||||
|         private void comboBoxIdlePause_SelectedValueChanged(object sender, EventArgs e){ | ||||
|             Config.NotificationIdlePauseSeconds = IdlePauseSeconds[comboBoxIdlePause.SelectedIndex]; | ||||
|         } | ||||
| 
 | ||||
|         private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){ | ||||
|             if (trackBarScrollSpeed.AlignValueToTick()){ | ||||
|                 labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%"; | ||||
|                 Config.NotificationScrollSpeed = trackBarScrollSpeed.Value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e){ | ||||
|             Config.NotificationDisplay = comboBoxDisplay.SelectedIndex; | ||||
|             notification.ShowNotificationForSettings(false); | ||||
|         } | ||||
| 
 | ||||
|         private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){ | ||||
|             labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px"; | ||||
|             Config.NotificationEdgeDistance = trackBarEdgeDistance.Value; | ||||
|             notification.ShowNotificationForSettings(false); | ||||
|         } | ||||
| 
 | ||||
|         private void durationUpdateTimer_Tick(object sender, EventArgs e){ | ||||
|             notification.ShowNotificationForSettings(true); | ||||
|             durationUpdateTimer.Stop(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,82 +0,0 @@ | ||||
| using System; | ||||
| using System.Drawing; | ||||
| using System.IO; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Notification; | ||||
| using TweetLib.Audio; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings{ | ||||
|     sealed partial class TabSettingsSounds : BaseTabSettings{ | ||||
|         private readonly SoundNotification soundNotification; | ||||
|         private readonly bool supportsChangingVolume; | ||||
| 
 | ||||
|         public TabSettingsSounds(){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             soundNotification = new SoundNotification(); | ||||
|             soundNotification.PlaybackError += sound_PlaybackError; | ||||
|              | ||||
|             supportsChangingVolume = soundNotification.SetVolume(Config.NotificationSoundVolume); | ||||
| 
 | ||||
|             trackBarVolume.Enabled = supportsChangingVolume && !string.IsNullOrEmpty(Config.NotificationSoundPath); | ||||
|             trackBarVolume.SetValueSafe(Config.NotificationSoundVolume); | ||||
|             labelVolumeValue.Text = trackBarVolume.Value+"%"; | ||||
| 
 | ||||
|             tbCustomSound.Text = Config.NotificationSoundPath; | ||||
|             tbCustomSound_TextChanged(tbCustomSound, EventArgs.Empty); | ||||
| 
 | ||||
|             Disposed += (sender, args) => soundNotification.Dispose(); | ||||
|         } | ||||
| 
 | ||||
|         public override void OnReady(){ | ||||
|             tbCustomSound.TextChanged += tbCustomSound_TextChanged; | ||||
|             btnPlaySound.Click += btnPlaySound_Click; | ||||
|             btnBrowseSound.Click += btnBrowseSound_Click; | ||||
|             btnResetSound.Click += btnResetSound_Click; | ||||
|         } | ||||
| 
 | ||||
|         public override void OnClosing(){ | ||||
|             Config.NotificationSoundPath = tbCustomSound.Text; | ||||
|         } | ||||
| 
 | ||||
|         private void tbCustomSound_TextChanged(object sender, EventArgs e){ | ||||
|             bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text); | ||||
|             tbCustomSound.ForeColor = isEmpty || File.Exists(tbCustomSound.Text) ? SystemColors.WindowText : Color.Red; | ||||
|             btnPlaySound.Enabled = !isEmpty; | ||||
|             btnResetSound.Enabled = !isEmpty; | ||||
|             trackBarVolume.Enabled = supportsChangingVolume && !isEmpty; | ||||
|         } | ||||
| 
 | ||||
|         private void btnPlaySound_Click(object sender, EventArgs e){ | ||||
|             soundNotification.Play(tbCustomSound.Text); | ||||
|         } | ||||
| 
 | ||||
|         private void sound_PlaybackError(object sender, PlaybackErrorEventArgs e){ | ||||
|             FormMessage.Error("Notification Sound Error", "Could not play custom notification sound.\n"+e.Message, FormMessage.OK); | ||||
|         } | ||||
| 
 | ||||
|         private void btnBrowseSound_Click(object sender, EventArgs e){ | ||||
|             using(OpenFileDialog dialog = new OpenFileDialog{ | ||||
|                 AutoUpgradeEnabled = true, | ||||
|                 DereferenceLinks = true, | ||||
|                 Title = "Custom Notification Sound", | ||||
|                 Filter = "Sound file ("+soundNotification.SupportedFormats+")|"+soundNotification.SupportedFormats+"|All files (*.*)|*.*" | ||||
|             }){ | ||||
|                 if (dialog.ShowDialog() == DialogResult.OK){ | ||||
|                     tbCustomSound.Text = dialog.FileName; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void btnResetSound_Click(object sender, EventArgs e){ | ||||
|             tbCustomSound.Text = string.Empty; | ||||
|         } | ||||
| 
 | ||||
|         private void trackBarVolume_ValueChanged(object sender, EventArgs e){ | ||||
|             Config.NotificationSoundVolume = trackBarVolume.Value; | ||||
|             soundNotification.SetVolume(Config.NotificationSoundVolume); | ||||
|             labelVolumeValue.Text = Config.NotificationSoundVolume+"%"; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,33 +0,0 @@ | ||||
| using System; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Other.Settings{ | ||||
|     sealed partial class TabSettingsTray : BaseTabSettings{ | ||||
|         public TabSettingsTray(){ | ||||
|             InitializeComponent(); | ||||
|              | ||||
|             comboBoxTrayType.Items.Add("Disabled"); | ||||
|             comboBoxTrayType.Items.Add("Display Icon Only"); | ||||
|             comboBoxTrayType.Items.Add("Minimize to Tray"); | ||||
|             comboBoxTrayType.Items.Add("Close to Tray"); | ||||
|             comboBoxTrayType.Items.Add("Combined"); | ||||
|             comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count-1); | ||||
| 
 | ||||
|             checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon(); | ||||
|             checkTrayHighlight.Checked = Config.EnableTrayHighlight; | ||||
|         } | ||||
| 
 | ||||
|         public override void OnReady(){ | ||||
|             comboBoxTrayType.SelectedIndexChanged += comboBoxTrayType_SelectedIndexChanged; | ||||
|             checkTrayHighlight.CheckedChanged += checkTrayHighlight_CheckedChanged; | ||||
|         } | ||||
| 
 | ||||
|         private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){ | ||||
|             Config.TrayBehavior = (TrayIcon.Behavior)comboBoxTrayType.SelectedIndex; | ||||
|             checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon(); | ||||
|         } | ||||
| 
 | ||||
|         private void checkTrayHighlight_CheckedChanged(object sender, EventArgs e){ | ||||
|             Config.EnableTrayHighlight = checkTrayHighlight.Checked; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,99 +0,0 @@ | ||||
| using System; | ||||
| using System.ComponentModel; | ||||
| using System.Windows.Forms; | ||||
| 
 | ||||
| namespace TweetDuck.Core{ | ||||
|     sealed partial class TrayIcon : Component{ | ||||
|         public enum Behavior{ // keep order | ||||
|             Disabled, DisplayOnly, MinimizeToTray, CloseToTray, Combined | ||||
|         } | ||||
| 
 | ||||
|         public event EventHandler ClickRestore; | ||||
|         public event EventHandler ClickClose; | ||||
| 
 | ||||
|         public bool Visible{ | ||||
|             get => notifyIcon.Visible; | ||||
| 
 | ||||
|             set{ | ||||
|                 if (value){ | ||||
|                     notifyIcon.Icon = Properties.Resources.icon_tray; | ||||
|                 } | ||||
|                  | ||||
|                 notifyIcon.Visible = value; | ||||
|                 hasNotifications = false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public bool HasNotifications{ | ||||
|             get{ | ||||
|                 return hasNotifications; | ||||
|             } | ||||
| 
 | ||||
|             set{ | ||||
|                 if (hasNotifications != value && Visible){ | ||||
|                     notifyIcon.Icon = value ? Properties.Resources.icon_tray_new : Properties.Resources.icon_tray; | ||||
|                     hasNotifications = value; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private readonly ContextMenu contextMenu; | ||||
|         private bool hasNotifications; | ||||
| 
 | ||||
|         public TrayIcon(){ | ||||
|             InitializeComponent(); | ||||
| 
 | ||||
|             this.contextMenu = new ContextMenu(); | ||||
|             this.contextMenu.MenuItems.Add("Restore", menuItemRestore_Click); | ||||
|             this.contextMenu.MenuItems.Add("Mute notifications", menuItemMuteNotifications_Click); | ||||
|             this.contextMenu.MenuItems.Add("Close", menuItemClose_Click); | ||||
|             this.contextMenu.Popup += contextMenu_Popup; | ||||
|                  | ||||
|             this.notifyIcon.ContextMenu = contextMenu; | ||||
|             this.notifyIcon.Text = Program.BrandName; | ||||
|         } | ||||
| 
 | ||||
|         public TrayIcon(IContainer container) : this(){ | ||||
|             container.Add(this); | ||||
|         } | ||||
| 
 | ||||
|         // event handlers | ||||
| 
 | ||||
|         private void trayIcon_MouseClick(object sender, MouseEventArgs e){ | ||||
|             if (e.Button == MouseButtons.Left){ | ||||
|                 menuItemRestore_Click(sender, e); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void contextMenu_Popup(object sender, EventArgs e){ | ||||
|             contextMenu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications; | ||||
|         } | ||||
| 
 | ||||
|         private void menuItemRestore_Click(object sender, EventArgs e){ | ||||
|             ClickRestore?.Invoke(this, e); | ||||
|         } | ||||
| 
 | ||||
|         private void menuItemMuteNotifications_Click(object sender, EventArgs e){ | ||||
|             Program.UserConfig.MuteNotifications = !contextMenu.MenuItems[1].Checked; | ||||
|             Program.UserConfig.Save(); | ||||
|         } | ||||
| 
 | ||||
|         private void menuItemClose_Click(object sender, EventArgs e){ | ||||
|             ClickClose?.Invoke(this, e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static class BehaviorExtensions{ | ||||
|         public static bool ShouldDisplayIcon(this TrayIcon.Behavior behavior){ | ||||
|             return behavior != TrayIcon.Behavior.Disabled; | ||||
|         } | ||||
| 
 | ||||
|         public static bool ShouldHideOnMinimize(this TrayIcon.Behavior behavior){ | ||||
|             return behavior == TrayIcon.Behavior.MinimizeToTray || behavior == TrayIcon.Behavior.Combined; | ||||
|         } | ||||
| 
 | ||||
|         public static bool ShouldHideOnClose(this TrayIcon.Behavior behavior){ | ||||
|             return behavior == TrayIcon.Behavior.CloseToTray || behavior == TrayIcon.Behavior.Combined; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,50 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Threading.Tasks; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Utils{ | ||||
|     static class BrowserCache{ | ||||
|         private static bool ClearOnExit { get; set; } | ||||
| 
 | ||||
|         private static readonly string CacheFolder = Path.Combine(Program.StoragePath, "Cache"); | ||||
| 
 | ||||
|         private static IEnumerable<string> CacheFiles{ | ||||
|             get{ | ||||
|                 return Directory.EnumerateFiles(CacheFolder); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static void CalculateCacheSize(Action<long> callbackBytes){ | ||||
|             Task<long> task = new Task<long>(() => { | ||||
|                 return CacheFiles.Select(file => { | ||||
|                     try{ | ||||
|                         return new FileInfo(file).Length; | ||||
|                     }catch{ | ||||
|                         return 0L; | ||||
|                     } | ||||
|                 }).Sum(); | ||||
|             }); | ||||
|              | ||||
|             task.ContinueWith(originalTask => callbackBytes(originalTask.Exception == null ? originalTask.Result : -1L), TaskContinuationOptions.ExecuteSynchronously); | ||||
|             task.Start(); | ||||
|         } | ||||
| 
 | ||||
|         public static void SetClearOnExit(){ | ||||
|             ClearOnExit = true; | ||||
|         } | ||||
| 
 | ||||
|         public static void Exit(){ | ||||
|             if (ClearOnExit){ | ||||
|                 foreach(string file in CacheFiles){ | ||||
|                     try{ | ||||
|                         File.Delete(file); | ||||
|                     }catch{ | ||||
|                         // welp, too bad | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,140 +0,0 @@ | ||||
| using CefSharp; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics; | ||||
| using System.IO; | ||||
| using System.Net; | ||||
| using System.Windows.Forms; | ||||
| using CefSharp.WinForms; | ||||
| using TweetDuck.Core.Other; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Utils{ | ||||
|     static class BrowserUtils{ | ||||
|         public static string HeaderAcceptLanguage{ | ||||
|             get{ | ||||
|                 string culture = Program.Culture.Name; | ||||
| 
 | ||||
|                 if (culture == "en"){ | ||||
|                     return "en-us,en"; | ||||
|                 } | ||||
|                 else{ | ||||
|                     return culture.ToLower()+",en;q=0.9"; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static string HeaderUserAgent => Program.BrandName+" "+Application.ProductVersion; | ||||
| 
 | ||||
|         public static void SetupCefArgs(IDictionary<string, string> args){ | ||||
|             if (!Program.SystemConfig.HardwareAcceleration){ | ||||
|                 args["disable-gpu"] = "1"; | ||||
|                 args["disable-gpu-vsync"] = "1"; | ||||
|             } | ||||
|              | ||||
|             args["disable-extensions"] = "1"; | ||||
|             args["disable-plugins-discovery"] = "1"; | ||||
|             args["enable-system-flash"] = "0"; | ||||
| 
 | ||||
|             if (args.TryGetValue("js-flags", out string jsFlags)){ | ||||
|                 args["js-flags"] = "--expose-gc "+jsFlags; | ||||
|             } | ||||
|             else{ | ||||
|                 args["js-flags"] = "--expose-gc"; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static ChromiumWebBrowser AsControl(this IWebBrowser browserControl){ | ||||
|             return (ChromiumWebBrowser)browserControl; | ||||
|         } | ||||
| 
 | ||||
|         private const string TwitterTrackingUrl = "t.co"; | ||||
| 
 | ||||
|         public enum UrlCheckResult{ | ||||
|             Invalid, Tracking, Fine | ||||
|         } | ||||
| 
 | ||||
|         public static UrlCheckResult CheckUrl(string url){ | ||||
|             if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){ | ||||
|                 string scheme = uri.Scheme; | ||||
| 
 | ||||
|                 if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){ | ||||
|                     return uri.Host == TwitterTrackingUrl ? UrlCheckResult.Tracking : UrlCheckResult.Fine; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return UrlCheckResult.Invalid; | ||||
|         } | ||||
| 
 | ||||
|         public static void OpenExternalBrowser(string url){ | ||||
|             if (string.IsNullOrWhiteSpace(url))return; | ||||
| 
 | ||||
|             switch(CheckUrl(url)){ | ||||
|                 case UrlCheckResult.Fine: | ||||
|                     OpenExternalBrowserUnsafe(url); | ||||
|                     break; | ||||
| 
 | ||||
|                 case UrlCheckResult.Tracking: | ||||
|                     if (FormMessage.Warning("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, FormMessage.Yes, FormMessage.No)){ | ||||
|                         OpenExternalBrowserUnsafe(url); | ||||
|                     } | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case UrlCheckResult.Invalid: | ||||
|                     FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK); | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static void OpenExternalBrowserUnsafe(string url){ | ||||
|             using(Process.Start(url)){} | ||||
|         } | ||||
| 
 | ||||
|         public static string GetFileNameFromUrl(string url){ | ||||
|             string file = Path.GetFileName(new Uri(url).AbsolutePath); | ||||
|             return string.IsNullOrEmpty(file) ? null : file; | ||||
|         } | ||||
| 
 | ||||
|         public static string GetErrorName(CefErrorCode code){ | ||||
|             return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty); | ||||
|         } | ||||
| 
 | ||||
|         public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){ | ||||
|             WebClient client = new WebClient{ Proxy = null }; | ||||
|             client.Headers[HttpRequestHeader.UserAgent] = HeaderUserAgent; | ||||
| 
 | ||||
|             client.DownloadFileCompleted += (sender, args) => { | ||||
|                 if (args.Cancelled){ | ||||
|                     try{ | ||||
|                         File.Delete(target); | ||||
|                     }catch{ | ||||
|                         // didn't want it deleted anyways | ||||
|                     } | ||||
|                 } | ||||
|                 else if (args.Error != null){ | ||||
|                     onFailure?.Invoke(args.Error); | ||||
|                 } | ||||
|                 else{ | ||||
|                     onSuccess?.Invoke(); | ||||
|                 } | ||||
|             }; | ||||
| 
 | ||||
|             client.DownloadFileAsync(new Uri(url), target); | ||||
|             return client; | ||||
|         } | ||||
| 
 | ||||
|         public static int Scale(int baseValue, double scaleFactor){ | ||||
|             return (int)Math.Round(baseValue*scaleFactor); | ||||
|         } | ||||
| 
 | ||||
|         public static void SetZoomLevel(IBrowser browser, int percentage){ | ||||
|             browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2)); | ||||
|         } | ||||
| 
 | ||||
|         #if DEBUG | ||||
|         public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){ | ||||
|             Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line); | ||||
|         } | ||||
|         #endif | ||||
|     } | ||||
| } | ||||
| @@ -1,133 +0,0 @@ | ||||
| using System; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using System.Drawing; | ||||
| using System.Runtime.InteropServices; | ||||
| using System.Windows.Forms; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Utils{ | ||||
|     [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")] | ||||
|     [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")] | ||||
|     static class NativeMethods{ | ||||
|         public static readonly IntPtr HOOK_HANDLED = new IntPtr(-1); | ||||
| 
 | ||||
|         public const int HWND_TOPMOST = -1; | ||||
|         public const uint SWP_NOACTIVATE = 0x0010; | ||||
|         public const int WS_DISABLED = 0x08000000; | ||||
|         public const int GWL_STYLE = -16; | ||||
| 
 | ||||
|         public const int SB_HORZ = 0; | ||||
| 
 | ||||
|         public const int WM_MOUSE_LL = 14; | ||||
|         public const int WM_MOUSEWHEEL = 0x020A; | ||||
|         public const int WM_XBUTTONDOWN = 0x020B; | ||||
|         public const int WM_XBUTTONUP = 0x020C; | ||||
|         public const int WM_PARENTNOTIFY = 0x0210; | ||||
|          | ||||
|         [StructLayout(LayoutKind.Sequential)] | ||||
|         private struct LASTINPUTINFO{ | ||||
|             public static readonly uint Size = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO)); | ||||
| 
 | ||||
|             public uint cbSize; | ||||
|             public uint dwTime; | ||||
|         } | ||||
| 
 | ||||
|         [StructLayout(LayoutKind.Sequential)] | ||||
|         private struct POINT{ | ||||
|             public int X; | ||||
|             public int Y; | ||||
|         } | ||||
| 
 | ||||
|         [StructLayout(LayoutKind.Sequential)] | ||||
|         private struct MSLLHOOKSTRUCT{ | ||||
|             public POINT pt; | ||||
|             public int mouseData; | ||||
|             public int flags; | ||||
|             public int time; | ||||
|             public UIntPtr dwExtraInfo; | ||||
|         } | ||||
| 
 | ||||
|         public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); | ||||
| 
 | ||||
|         [DllImport("user32.dll", EntryPoint = "SetWindowPos")] | ||||
|         private static extern bool SetWindowPos(int hWnd, int hWndOrder, int x, int y, int width, int height, uint flags); | ||||
| 
 | ||||
|         [DllImport("user32.dll")] | ||||
|         private static extern bool GetLastInputInfo(ref LASTINPUTINFO info); | ||||
| 
 | ||||
|         [DllImport("user32.dll")] | ||||
|         public static extern IntPtr GetDC(IntPtr hWnd); | ||||
| 
 | ||||
|         [DllImport("user32.dll")] | ||||
|         public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC); | ||||
| 
 | ||||
|         [DllImport("gdi32.dll")] | ||||
|         [return: MarshalAs(UnmanagedType.Bool)] | ||||
|         private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop); | ||||
|          | ||||
|         [DllImport("user32.dll")] | ||||
|         [return: MarshalAs(UnmanagedType.Bool)] | ||||
|         public static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow); | ||||
| 
 | ||||
|         [DllImport("user32.dll")] | ||||
|         public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId); | ||||
| 
 | ||||
|         [DllImport("user32.dll")] | ||||
|         public static extern bool UnhookWindowsHookEx(IntPtr idHook); | ||||
| 
 | ||||
|         [DllImport("user32.dll")] | ||||
|         public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam); | ||||
| 
 | ||||
|         [DllImport("user32.dll")] | ||||
|         private static extern int GetWindowLong(IntPtr hWnd, int nIndex); | ||||
| 
 | ||||
|         [DllImport("user32.dll")] | ||||
|         private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); | ||||
| 
 | ||||
|         public static void SetFormPos(Form form, int hWndOrder, uint flags){ | ||||
|             SetWindowPos(form.Handle.ToInt32(), hWndOrder, form.Left, form.Top, form.Width, form.Height, flags); | ||||
|         } | ||||
| 
 | ||||
|         public static void SetFormDisabled(Form form, bool disabled){ | ||||
|             if (disabled){ | ||||
|                 SetWindowLong(form.Handle, GWL_STYLE, GetWindowLong(form.Handle, GWL_STYLE) | WS_DISABLED); | ||||
|             } | ||||
|             else{ | ||||
|                 SetWindowLong(form.Handle, GWL_STYLE, GetWindowLong(form.Handle, GWL_STYLE) & ~WS_DISABLED); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static int GetMouseHookData(IntPtr ptr){ | ||||
|             return Marshal.PtrToStructure<MSLLHOOKSTRUCT>(ptr).mouseData >> 16; | ||||
|         } | ||||
| 
 | ||||
|         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 | ||||
|         } | ||||
| 
 | ||||
|         public static void RenderSourceIntoBitmap(IntPtr source, Bitmap target){ | ||||
|             using(Graphics graphics = Graphics.FromImage(target)){ | ||||
|                 IntPtr graphicsHandle = graphics.GetHdc(); | ||||
| 
 | ||||
|                 try{ | ||||
|                     BitBlt(graphicsHandle, 0, 0, target.Width, target.Height, source, 0, 0, 0x00CC0020); | ||||
|                 }finally{ | ||||
|                     graphics.ReleaseHdc(graphicsHandle); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,22 +0,0 @@ | ||||
| using System; | ||||
| using System.Linq; | ||||
| using System.Text.RegularExpressions; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Utils{ | ||||
|     static class StringUtils{ | ||||
|         public static readonly string[] EmptyArray = new string[0]; | ||||
| 
 | ||||
|         public static string ExtractBefore(string str, char search, int startIndex = 0){ | ||||
|             int index = str.IndexOf(search, startIndex); | ||||
|             return index == -1 ? str : str.Substring(0, index); | ||||
|         } | ||||
| 
 | ||||
|         public static int[] ParseInts(string str, char separator){ | ||||
|             return str.Split(new char[]{ separator }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); | ||||
|         } | ||||
|          | ||||
|         public static string ConvertPascalCaseToScreamingSnakeCase(string str){ | ||||
|             return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpper(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,125 +0,0 @@ | ||||
| using System; | ||||
| using CefSharp; | ||||
| using System.Drawing; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Text.RegularExpressions; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Other; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Utils{ | ||||
|     static class TwitterUtils{ | ||||
|         public const string TweetDeckURL = "https://tweetdeck.twitter.com"; | ||||
| 
 | ||||
|         public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153); | ||||
|         public const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'"; | ||||
|          | ||||
|         private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/(?!signup$|tos$|privacy$)([^/]+)/?$", RegexOptions.Compiled), false); | ||||
|         public static Regex RegexAccount => RegexAccountLazy.Value; | ||||
| 
 | ||||
|         public static readonly string[] DictionaryWords = { | ||||
|             "tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD" | ||||
|         }; | ||||
| 
 | ||||
|         public enum ImageQuality{ | ||||
|             Default, Orig | ||||
|         } | ||||
| 
 | ||||
|         public static bool IsTweetDeckWebsite(IFrame frame){ | ||||
|             return frame.Url.Contains("//tweetdeck.twitter.com/"); | ||||
|         } | ||||
| 
 | ||||
|         public static bool IsTwitterWebsite(IFrame frame){ | ||||
|             return frame.Url.Contains("//twitter.com/"); | ||||
|         } | ||||
| 
 | ||||
|         private static string ExtractMediaBaseLink(string url){ | ||||
|             int dot = url.LastIndexOf('/'); | ||||
|             return dot == -1 ? url : StringUtils.ExtractBefore(url, ':', dot); | ||||
|         } | ||||
| 
 | ||||
|         public static string GetMediaLink(string url, ImageQuality quality){ | ||||
|             if (quality == ImageQuality.Orig){ | ||||
|                 string result = ExtractMediaBaseLink(url); | ||||
| 
 | ||||
|                 if (result != url || url.Contains("//pbs.twimg.com/media/")){ | ||||
|                     result += ":orig"; | ||||
|                 } | ||||
| 
 | ||||
|                 return result; | ||||
|             } | ||||
|             else{ | ||||
|                 return url; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         public static void DownloadImage(string url, string username, ImageQuality quality){ | ||||
|             DownloadImages(new string[]{ url }, username, quality); | ||||
|         } | ||||
| 
 | ||||
|         public static void DownloadImages(string[] urls, string username, ImageQuality quality){ | ||||
|             if (urls.Length == 0){ | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             string firstImageLink = GetMediaLink(urls[0], quality); | ||||
|             int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/')); | ||||
| 
 | ||||
|             string file = BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(firstImageLink)); | ||||
|             string ext = Path.GetExtension(file); // includes dot | ||||
| 
 | ||||
|             string[] fileNameParts = qualityIndex == -1 ? new string[]{ | ||||
|                 Path.ChangeExtension(file, null) | ||||
|             } : new string[]{ | ||||
|                 username, | ||||
|                 Path.ChangeExtension(file, null), | ||||
|                 firstImageLink.Substring(qualityIndex+1) | ||||
|             }; | ||||
|              | ||||
|             using(SaveFileDialog dialog = new SaveFileDialog{ | ||||
|                 AutoUpgradeEnabled = true, | ||||
|                 OverwritePrompt = urls.Length == 1, | ||||
|                 Title = "Save image", | ||||
|                 FileName = $"{string.Join(" ", fileNameParts.Where(part => part.Length > 0))}{ext}", | ||||
|                 Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}") | ||||
|             }){ | ||||
|                 if (dialog.ShowDialog() == DialogResult.OK){ | ||||
|                     void OnFailure(Exception ex){ | ||||
|                         FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK); | ||||
|                     } | ||||
| 
 | ||||
|                     if (urls.Length == 1){ | ||||
|                         BrowserUtils.DownloadFileAsync(firstImageLink, dialog.FileName, null, OnFailure); | ||||
|                     } | ||||
|                     else{ | ||||
|                         string pathBase = Path.ChangeExtension(dialog.FileName, null); | ||||
|                         string pathExt = Path.GetExtension(dialog.FileName); | ||||
| 
 | ||||
|                         for(int index = 0; index < urls.Length; index++){ | ||||
|                             BrowserUtils.DownloadFileAsync(GetMediaLink(urls[index], quality), $"{pathBase} {index+1}{pathExt}", null, OnFailure); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static void DownloadVideo(string url){ | ||||
|             string filename = BrowserUtils.GetFileNameFromUrl(url); | ||||
|             string ext = Path.GetExtension(filename); | ||||
| 
 | ||||
|             using(SaveFileDialog dialog = new SaveFileDialog{ | ||||
|                 AutoUpgradeEnabled = true, | ||||
|                 OverwritePrompt = true, | ||||
|                 Title = "Save video", | ||||
|                 FileName = filename, | ||||
|                 Filter = "Video"+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}") | ||||
|             }){ | ||||
|                 if (dialog.ShowDialog() == DialogResult.OK){ | ||||
|                     BrowserUtils.DownloadFileAsync(url, dialog.FileName, null, ex => { | ||||
|                         FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK); | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,143 +0,0 @@ | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.IO; | ||||
| using System.Management; | ||||
| using System.Runtime.InteropServices; | ||||
| using System.Text.RegularExpressions; | ||||
| using System.Threading; | ||||
| using System.Windows.Forms; | ||||
| 
 | ||||
| namespace TweetDuck.Core.Utils{ | ||||
|     static class WindowsUtils{ | ||||
|         private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false); | ||||
|         private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false); | ||||
| 
 | ||||
|         public static int CurrentProcessID { get; } | ||||
|         public static bool ShouldAvoidToolWindow { get; } | ||||
| 
 | ||||
|         static WindowsUtils(){ | ||||
|             using(Process me = Process.GetCurrentProcess()){ | ||||
|                 CurrentProcessID = me.Id; | ||||
|             } | ||||
| 
 | ||||
|             Version ver = Environment.OSVersion.Version; | ||||
|             ShouldAvoidToolWindow = ver.Major == 6 && ver.Minor == 2; // windows 8/10 | ||||
|         } | ||||
| 
 | ||||
|         public static void CreateDirectoryForFile(string file){ | ||||
|             string dir = Path.GetDirectoryName(file); | ||||
| 
 | ||||
|             if (dir == null){ | ||||
|                 throw new ArgumentException("Invalid file path: "+file); | ||||
|             } | ||||
|             else if (dir.Length > 0){ | ||||
|                 Directory.CreateDirectory(dir); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static bool CheckFolderWritePermission(string path){ | ||||
|             string testFile = Path.Combine(path, ".test"); | ||||
| 
 | ||||
|             try{ | ||||
|                 Directory.CreateDirectory(path); | ||||
| 
 | ||||
|                 using(File.Create(testFile)){} | ||||
|                 File.Delete(testFile); | ||||
|                 return true; | ||||
|             }catch{ | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static Process StartProcess(string file, string arguments, bool runElevated){ | ||||
|             ProcessStartInfo processInfo = new ProcessStartInfo{ | ||||
|                 FileName = file, | ||||
|                 Arguments = arguments | ||||
|             }; | ||||
| 
 | ||||
|             if (runElevated){ | ||||
|                 processInfo.Verb = "runas"; | ||||
|             } | ||||
| 
 | ||||
|             return Process.Start(processInfo); | ||||
|         } | ||||
| 
 | ||||
|         public static bool TrySleepUntil(Func<bool> test, int timeoutMillis, int timeStepMillis){ | ||||
|             for(int waited = 0; waited < timeoutMillis; waited += timeStepMillis){ | ||||
|                 if (test()){ | ||||
|                     return true; | ||||
|                 } | ||||
| 
 | ||||
|                 Thread.Sleep(timeStepMillis); | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         public static void TryDeleteFolderWhenAble(string path, int timeout){ | ||||
|             new Thread(() => { | ||||
|                 TrySleepUntil(() => { | ||||
|                     try{ | ||||
|                         Directory.Delete(path, true); | ||||
|                         return true; | ||||
|                     }catch(DirectoryNotFoundException){ | ||||
|                         return true; | ||||
|                     }catch{ | ||||
|                         return false; | ||||
|                     } | ||||
|                 }, timeout, 500); | ||||
|             }).Start(); | ||||
|         } | ||||
| 
 | ||||
|         public static bool IsChildProcess(int pid){ | ||||
|             try{ | ||||
|                 using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = "+pid)){ | ||||
|                     foreach(ManagementBaseObject obj in searcher.Get()){ | ||||
|                         return (uint)obj["ParentProcessId"] == CurrentProcessID; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 return false; | ||||
|             }catch{ | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         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.Value.Replace(originalHtml, string.Empty); | ||||
| 
 | ||||
|             int removed = originalHtml.Length-updatedHtml.Length; | ||||
|             updatedHtml = RegexOffsetClipboardHtml.Value.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", "TweetDuck could not access the clipboard as it is currently used by another process.", true, e); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,130 +0,0 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Data{ | ||||
|     sealed class CombinedFileStream : IDisposable{ | ||||
|         public const char KeySeparator = '|'; | ||||
| 
 | ||||
|         private readonly Stream stream; | ||||
| 
 | ||||
|         public CombinedFileStream(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){ | ||||
|             byte[] name = Encoding.UTF8.GetBytes(identifier); | ||||
| 
 | ||||
|             if (name.Length > 255){ | ||||
|                 throw new ArgumentOutOfRangeException("Identifier cannot be 256 or more characters long: "+identifier); | ||||
|             } | ||||
| 
 | ||||
|             byte[] contents; | ||||
| 
 | ||||
|             using(FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){ | ||||
|                 int index = 0; | ||||
|                 int left = (int)fileStream.Length; | ||||
| 
 | ||||
|                 contents = new byte[left]; | ||||
| 
 | ||||
|                 while(left > 0){ | ||||
|                     int read = fileStream.Read(contents, index, left); | ||||
|                     index += read; | ||||
|                     left -= read; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             stream.WriteByte((byte)name.Length); | ||||
|             stream.Write(name, 0, name.Length); | ||||
|             stream.Write(BitConverter.GetBytes(contents.Length), 0, 4); | ||||
|             stream.Write(contents, 0, contents.Length); | ||||
|         } | ||||
| 
 | ||||
|         public Entry ReadFile(){ | ||||
|             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); | ||||
| 
 | ||||
|             byte[] contents = new byte[BitConverter.ToInt32(contentLength, 0)]; | ||||
|             stream.Read(contents, 0, contents.Length); | ||||
| 
 | ||||
|             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); | ||||
|             return StringUtils.ExtractBefore(keyName, KeySeparator); | ||||
|         } | ||||
| 
 | ||||
|         public void Flush(){ | ||||
|             stream.Flush(); | ||||
|         } | ||||
| 
 | ||||
|         void IDisposable.Dispose(){ | ||||
|             stream.Dispose(); | ||||
|         } | ||||
| 
 | ||||
|         public sealed class Entry{ | ||||
|             public string Identifier { get; } | ||||
| 
 | ||||
|             public string KeyName{ | ||||
|                 get{ | ||||
|                     return StringUtils.ExtractBefore(Identifier, KeySeparator); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             public string[] KeyValue{ | ||||
|                 get{ | ||||
|                     int index = Identifier.IndexOf(KeySeparator); | ||||
|                     return index == -1 ? StringUtils.EmptyArray : Identifier.Substring(index+1).Split(KeySeparator); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             private readonly byte[] contents; | ||||
| 
 | ||||
|             public Entry(string identifier, byte[] contents){ | ||||
|                 this.Identifier = identifier; | ||||
|                 this.contents = contents; | ||||
|             } | ||||
| 
 | ||||
|             public void WriteToFile(string path){ | ||||
|                 File.WriteAllBytes(path, contents); | ||||
|             } | ||||
| 
 | ||||
|             public void WriteToFile(string path, bool createDirectory){ | ||||
|                 if (createDirectory){ | ||||
|                     WindowsUtils.CreateDirectoryForFile(path); | ||||
|                 } | ||||
| 
 | ||||
|                 File.WriteAllBytes(path, contents); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,137 +0,0 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using System.Text.RegularExpressions; | ||||
| 
 | ||||
| namespace TweetDuck.Data{ | ||||
|     sealed 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); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static CommandLineArgs ReadCefArguments(string argumentString){ | ||||
|             CommandLineArgs args = new CommandLineArgs(); | ||||
| 
 | ||||
|             if (string.IsNullOrWhiteSpace(argumentString)){ | ||||
|                 return args; | ||||
|             } | ||||
|              | ||||
|             foreach(Match match in Regex.Matches(argumentString, @"([^=\s]+(?:=(?:\S*""[^""]*?""\S*|\S*))?)")){ | ||||
|                 string matchValue = match.Value; | ||||
| 
 | ||||
|                 int indexEquals = matchValue.IndexOf('='); | ||||
|                 string key, value; | ||||
| 
 | ||||
|                 if (indexEquals == -1){ | ||||
|                     key = matchValue.TrimStart('-'); | ||||
|                     value = "1"; | ||||
|                 } | ||||
|                 else{ | ||||
|                     key = matchValue.Substring(0, indexEquals).TrimStart('-'); | ||||
|                     value = matchValue.Substring(indexEquals+1).Trim('"'); | ||||
|                 } | ||||
| 
 | ||||
|                 if (key.Length != 0){ | ||||
|                     args.SetValue(key, value); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return args; | ||||
|         } | ||||
| 
 | ||||
|         private readonly HashSet<string> flags = new HashSet<string>(); | ||||
|         private readonly Dictionary<string, string> values = new Dictionary<string, string>(); | ||||
| 
 | ||||
|         public int Count => flags.Count+values.Count; | ||||
| 
 | ||||
|         public void AddFlag(string flag){ | ||||
|             flags.Add(flag.ToLower()); | ||||
|         } | ||||
| 
 | ||||
|         public bool HasFlag(string flag){ | ||||
|             return flags.Contains(flag.ToLower()); | ||||
|         } | ||||
| 
 | ||||
|         public void RemoveFlag(string flag){ | ||||
|             flags.Remove(flag.ToLower()); | ||||
|         } | ||||
| 
 | ||||
|         public void SetValue(string key, string value){ | ||||
|             values[key.ToLower()] = value; | ||||
|         } | ||||
| 
 | ||||
|         public bool HasValue(string key){ | ||||
|             return values.ContainsKey(key.ToLower()); | ||||
|         } | ||||
| 
 | ||||
|         public string GetValue(string key, string defaultValue){ | ||||
|             return values.TryGetValue(key.ToLower(), out string val) ? val : defaultValue; | ||||
|         } | ||||
| 
 | ||||
|         public void RemoveValue(string key){ | ||||
|             values.Remove(key.ToLower()); | ||||
|         } | ||||
| 
 | ||||
|         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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,37 +0,0 @@ | ||||
| using System; | ||||
| 
 | ||||
| namespace TweetDuck.Data{ | ||||
|     sealed class InjectedHTML{ | ||||
|         public enum Position{ | ||||
|             Before, After | ||||
|         } | ||||
| 
 | ||||
|         private readonly Position position; | ||||
|         private readonly string search; | ||||
|         private readonly string html; | ||||
| 
 | ||||
|         public InjectedHTML(Position position, string search, string html){ | ||||
|             this.position = position; | ||||
|             this.search = search; | ||||
|             this.html = html; | ||||
|         } | ||||
| 
 | ||||
|         public string Inject(string targetHTML){ | ||||
|             int index = targetHTML.IndexOf(search, StringComparison.Ordinal); | ||||
| 
 | ||||
|             if (index == -1){ | ||||
|                 return targetHTML; | ||||
|             } | ||||
| 
 | ||||
|             int cutIndex; | ||||
| 
 | ||||
|             switch(position){ | ||||
|                 case Position.Before: cutIndex = index; break; | ||||
|                 case Position.After: cutIndex = index+search.Length; break; | ||||
|                 default: return targetHTML; | ||||
|             } | ||||
| 
 | ||||
|             return targetHTML.Insert(cutIndex, html); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,146 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Reflection; | ||||
| using System.Runtime.Serialization; | ||||
| 
 | ||||
| namespace TweetDuck.Data.Serialization{ | ||||
|     sealed class FileSerializer<T>{ | ||||
|         private const string NewLineReal = "\r\n"; | ||||
|         private const string NewLineCustom = "\r~\n"; | ||||
| 
 | ||||
|         private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter(); | ||||
|          | ||||
|         public delegate void HandleUnknownPropertiesHandler(T obj, Dictionary<string, string> data); | ||||
|         public HandleUnknownPropertiesHandler HandleUnknownProperties { get; set; } | ||||
|          | ||||
|         private readonly Dictionary<string, PropertyInfo> props; | ||||
|         private readonly Dictionary<Type, ITypeConverter> converters; | ||||
| 
 | ||||
|         public FileSerializer(){ | ||||
|             this.props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.CanWrite).ToDictionary(prop => prop.Name); | ||||
|             this.converters = new Dictionary<Type, ITypeConverter>(); | ||||
|         } | ||||
| 
 | ||||
|         public void RegisterTypeConverter(Type type, ITypeConverter converter){ | ||||
|             converters[type] = converter; | ||||
|         } | ||||
| 
 | ||||
|         public void Write(string file, T obj){ | ||||
|             using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){ | ||||
|                 foreach(KeyValuePair<string, PropertyInfo> prop in props){ | ||||
|                     Type type = prop.Value.PropertyType; | ||||
|                     object value = prop.Value.GetValue(obj); | ||||
|                      | ||||
|                     if (!converters.TryGetValue(type, out ITypeConverter serializer)) { | ||||
|                         serializer = BasicSerializerObj; | ||||
|                     } | ||||
| 
 | ||||
|                     if (serializer.TryWriteType(type, value, out string converted)){ | ||||
|                         if (converted != null){ | ||||
|                             writer.Write($"{prop.Key} {converted.Replace(Environment.NewLine, NewLineCustom)}"); | ||||
|                             writer.Write(NewLineReal); | ||||
|                         } | ||||
|                     } | ||||
|                     else{ | ||||
|                         throw new SerializationException($"Invalid serialization type, conversion failed for: {type}"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void Read(string file, T obj){ | ||||
|             Dictionary<string, string> unknownProperties = new Dictionary<string, string>(4); | ||||
| 
 | ||||
|             using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){ | ||||
|                 if (reader.Peek() <= 1){ | ||||
|                     throw new FormatException("Input appears to be a binary file."); | ||||
|                 } | ||||
| 
 | ||||
|                 foreach(string line in reader.ReadToEnd().Split(new string[]{ NewLineReal }, StringSplitOptions.RemoveEmptyEntries)){ | ||||
|                     int space = line.IndexOf(' '); | ||||
| 
 | ||||
|                     if (space == -1){ | ||||
|                         throw new SerializationException($"Invalid file format, missing separator: {line}"); | ||||
|                     } | ||||
| 
 | ||||
|                     string property = line.Substring(0, space); | ||||
|                     string value = line.Substring(space+1).Replace(NewLineCustom, Environment.NewLine); | ||||
| 
 | ||||
|                     if (props.TryGetValue(property, out PropertyInfo info)){ | ||||
|                         if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)) { | ||||
|                             serializer = BasicSerializerObj; | ||||
|                         } | ||||
| 
 | ||||
|                         if (serializer.TryReadType(info.PropertyType, value, out object converted)){ | ||||
|                             info.SetValue(obj, converted); | ||||
|                         } | ||||
|                         else{ | ||||
|                             throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})"); | ||||
|                         } | ||||
|                     } | ||||
|                     else{ | ||||
|                         unknownProperties[property] = value; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (unknownProperties.Count > 0){ | ||||
|                 HandleUnknownProperties?.Invoke(obj, unknownProperties); | ||||
| 
 | ||||
|                 if (unknownProperties.Count > 0){ | ||||
|                     throw new SerializationException($"Invalid file format, unknown properties: {string.Join(", ", unknownProperties.Keys)}"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private sealed class BasicTypeConverter : ITypeConverter{ | ||||
|             bool ITypeConverter.TryWriteType(Type type, object value, out string converted){ | ||||
|                 switch(Type.GetTypeCode(type)){ | ||||
|                     case TypeCode.Boolean: | ||||
|                         converted = value.ToString(); | ||||
|                         return true; | ||||
| 
 | ||||
|                     case TypeCode.Int32: | ||||
|                         converted = ((int)value).ToString(); // cast required for enums | ||||
|                         return true; | ||||
| 
 | ||||
|                     case TypeCode.String: | ||||
|                         converted = value?.ToString(); | ||||
|                         return true; | ||||
| 
 | ||||
|                     default: | ||||
|                         converted = null; | ||||
|                         return false; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             bool ITypeConverter.TryReadType(Type type, string value, out object converted){ | ||||
|                 switch(Type.GetTypeCode(type)){ | ||||
|                     case TypeCode.Boolean: | ||||
|                         if (bool.TryParse(value, out bool b)){ | ||||
|                             converted = b; | ||||
|                             return true; | ||||
|                         } | ||||
|                         else goto default; | ||||
| 
 | ||||
|                     case TypeCode.Int32: | ||||
|                         if (int.TryParse(value, out int i)){ | ||||
|                             converted = i; | ||||
|                             return true; | ||||
|                         } | ||||
|                         else goto default; | ||||
| 
 | ||||
|                     case TypeCode.String: | ||||
|                         converted = value; | ||||
|                         return true; | ||||
| 
 | ||||
|                     default: | ||||
|                         converted = null; | ||||
|                         return false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,8 +0,0 @@ | ||||
| using System; | ||||
| 
 | ||||
| namespace TweetDuck.Data.Serialization{ | ||||
|     interface ITypeConverter{ | ||||
|         bool TryWriteType(Type type, object value, out string converted); | ||||
|         bool TryReadType(Type type, string value, out object converted); | ||||
|     } | ||||
| } | ||||
| @@ -1,28 +0,0 @@ | ||||
| using System; | ||||
| 
 | ||||
| namespace TweetDuck.Data.Serialization{ | ||||
|     sealed class SingleTypeConverter<T> : ITypeConverter{ | ||||
|         public Func<T, string> ConvertToString { get; set; } | ||||
|         public Func<string, T> ConvertToObject { get; set; } | ||||
| 
 | ||||
|         bool ITypeConverter.TryWriteType(Type type, object value, out string converted){ | ||||
|             try{ | ||||
|                 converted = ConvertToString((T)value); | ||||
|                 return true; | ||||
|             }catch{ | ||||
|                 converted = null; | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         bool ITypeConverter.TryReadType(Type type, string value, out object converted){ | ||||
|             try{ | ||||
|                 converted = ConvertToObject(value); | ||||
|                 return true; | ||||
|             }catch{ | ||||
|                 converted = null; | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,102 +0,0 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace TweetDuck.Data{ | ||||
|     sealed 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{ | ||||
|                 if (!dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict)){ | ||||
|                     dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity)); | ||||
|                 } | ||||
| 
 | ||||
|                 innerDict[innerKey] = value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public IEnumerable<V> InnerValues{ | ||||
|             get{ | ||||
|                 foreach(Dictionary<K2, V> innerDict in dict.Values){ | ||||
|                     foreach(V value in innerDict.Values){ | ||||
|                         yield return value; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Members | ||||
| 
 | ||||
|         public void Add(K1 outerKey, K2 innerKey, V value){ // throws on duplicate | ||||
|             if (!dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict)){ | ||||
|                 dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity)); | ||||
|             } | ||||
| 
 | ||||
|             innerDict.Add(innerKey, value); | ||||
|         } | ||||
|          | ||||
|         public void Clear(){ | ||||
|             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){ | ||||
|             if (dict.TryGetValue(outerKey, out Dictionary<K2, V> 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){ | ||||
|             if (dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict)){ | ||||
|                 return innerDict.TryGetValue(innerKey, out value); | ||||
|             } | ||||
|             else{ | ||||
|                 value = default(V); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,42 +0,0 @@ | ||||
| using System.Drawing; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetDuck.Data.Serialization; | ||||
| 
 | ||||
| namespace TweetDuck.Data{ | ||||
|     sealed class WindowState{ | ||||
|         private Rectangle rect; | ||||
|         private bool isMaximized; | ||||
| 
 | ||||
|         public void Save(Form form){ | ||||
|             rect = form.WindowState == FormWindowState.Normal ? form.DesktopBounds : form.RestoreBounds; | ||||
|             isMaximized = form.WindowState == FormWindowState.Maximized; | ||||
|         } | ||||
| 
 | ||||
|         public void Restore(Form form, bool firstTimeFullscreen){ | ||||
|             if (rect != Rectangle.Empty){ | ||||
|                 form.DesktopBounds = rect; | ||||
|                 form.WindowState = isMaximized ? FormWindowState.Maximized : FormWindowState.Normal; | ||||
|             } | ||||
| 
 | ||||
|             if ((rect == Rectangle.Empty && firstTimeFullscreen) || form.IsFullyOutsideView()){ | ||||
|                 form.DesktopBounds = Screen.PrimaryScreen.WorkingArea; | ||||
|                 form.WindowState = FormWindowState.Maximized; | ||||
|                 Save(form); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static readonly SingleTypeConverter<WindowState> Converter = new SingleTypeConverter<WindowState>{ | ||||
|             ConvertToString = value => $"{(value.isMaximized ? 'M' : '_')}{value.rect.X} {value.rect.Y} {value.rect.Width} {value.rect.Height}", | ||||
|             ConvertToObject = value => { | ||||
|                 int[] elements = StringUtils.ParseInts(value.Substring(1), ' '); | ||||
|                  | ||||
|                 return new WindowState{ | ||||
|                     rect = new Rectangle(elements[0], elements[1], elements[2], elements[3]), | ||||
|                     isMaximized = value[0] == 'M' | ||||
|                 }; | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @@ -1,103 +0,0 @@ | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.Drawing; | ||||
| using System.IO; | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Controls; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetDuck.Plugins.Enums; | ||||
| 
 | ||||
| namespace TweetDuck.Plugins.Controls{ | ||||
|     sealed partial class PluginControl : UserControl{ | ||||
|         private readonly PluginManager pluginManager; | ||||
|         private readonly Plugin plugin; | ||||
| 
 | ||||
|         private readonly float dpiScale; | ||||
| 
 | ||||
|         public PluginControl(){ | ||||
|             InitializeComponent(); | ||||
|         } | ||||
| 
 | ||||
|         public PluginControl(PluginManager pluginManager, Plugin plugin) : this(){ | ||||
|             this.pluginManager = pluginManager; | ||||
|             this.plugin = plugin; | ||||
| 
 | ||||
|             this.dpiScale = this.GetDPIScale(); | ||||
| 
 | ||||
|             this.labelName.Text = plugin.Name; | ||||
|             this.labelDescription.Text = plugin.CanRun ? plugin.Description : "This plugin requires TweetDuck "+plugin.RequiredVersion+" or newer."; | ||||
|             this.labelVersion.Text = plugin.Version; | ||||
|             this.labelAuthor.Text = plugin.Author; | ||||
|             this.labelWebsite.Text = plugin.Website; | ||||
|              | ||||
|             this.labelType.LineHeight = BrowserUtils.Scale(9, dpiScale); | ||||
| 
 | ||||
|             UpdatePluginState(); | ||||
| 
 | ||||
|             if (labelDescription.Text.Length == 0){ | ||||
|                 labelDescription.Visible = false; | ||||
|             } | ||||
| 
 | ||||
|             panelDescription_Resize(panelDescription, null); | ||||
|         } | ||||
| 
 | ||||
|         private void panelDescription_Resize(object sender, EventArgs e){ | ||||
|             if (labelDescription.Text.Length == 0){ | ||||
|                 Height = MinimumSize.Height; | ||||
|             } | ||||
|             else{ | ||||
|                 labelDescription.MaximumSize = new Size(panelDescription.Width-SystemInformation.VerticalScrollBarWidth, 0); | ||||
|                 Height = Math.Min(MinimumSize.Height+BrowserUtils.Scale(9, dpiScale)+labelDescription.Height, MaximumSize.Height); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void labelWebsite_Click(object sender, EventArgs e){ | ||||
|             if (labelWebsite.Text.Length > 0){ | ||||
|                 BrowserUtils.OpenExternalBrowser(labelWebsite.Text); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void btnOpenConfig_Click(object sender, EventArgs e){ | ||||
|             using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){} | ||||
|         } | ||||
| 
 | ||||
|         private void btnToggleState_Click(object sender, EventArgs e){ | ||||
|             bool newState = !pluginManager.Config.IsEnabled(plugin); | ||||
|             pluginManager.Config.SetEnabled(plugin, newState); | ||||
| 
 | ||||
|             UpdatePluginState(); | ||||
|         } | ||||
| 
 | ||||
|         private void UpdatePluginState(){ | ||||
|             bool isEnabled = pluginManager.Config.IsEnabled(plugin) && plugin.CanRun; | ||||
|             Color textColor = isEnabled ? Color.Black : Color.FromArgb(90, 90, 90); | ||||
|              | ||||
|             labelVersion.ForeColor = textColor; | ||||
|             labelAuthor.ForeColor = textColor; | ||||
|             labelWebsite.ForeColor = isEnabled ? Color.Blue : Color.FromArgb(90, 90, 249); | ||||
| 
 | ||||
|             if (plugin.Group == PluginGroup.Official){ | ||||
|                 labelType.Text = "OFFICIAL"; | ||||
|                 labelType.BackColor = isEnabled ? Color.FromArgb(154, 195, 217) : Color.FromArgb(185, 185, 185); | ||||
|             } | ||||
|             else{ | ||||
|                 labelType.Text = "CUSTOM"; | ||||
|                 labelType.BackColor = isEnabled ? Color.FromArgb(208, 154, 217) : Color.FromArgb(185, 185, 185); | ||||
|             } | ||||
| 
 | ||||
|             if (plugin.CanRun){ | ||||
|                 labelName.ForeColor = textColor; | ||||
|                 labelDescription.ForeColor = textColor; | ||||
|                 btnToggleState.Text = isEnabled ? "Disable" : "Enable"; | ||||
|                 btnOpenConfig.Visible = plugin.HasConfig; | ||||
|                 btnOpenConfig.Enabled = btnOpenConfig.Visible && File.Exists(plugin.ConfigPath); | ||||
|             } | ||||
|             else{ | ||||
|                 labelName.ForeColor = Color.DarkRed; | ||||
|                 labelDescription.ForeColor = Color.DarkRed; | ||||
|                 btnToggleState.Visible = false; | ||||
|                 btnOpenConfig.Visible = false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| using System.Windows.Forms; | ||||
| using TweetDuck.Core.Utils; | ||||
| 
 | ||||
| namespace TweetDuck.Plugins.Controls{ | ||||
|     sealed class PluginListFlowLayout : FlowLayoutPanel{ | ||||
|         public PluginListFlowLayout(){ | ||||
|             FlowDirection = FlowDirection.TopDown; | ||||
|             WrapContents = false; | ||||
|         } | ||||
| 
 | ||||
|         protected override void WndProc(ref Message m){ | ||||
|             NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much | ||||
|             base.WndProc(ref m); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,49 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace TweetDuck.Plugins.Enums{ | ||||
|     [Flags] | ||||
|     enum PluginEnvironment{ | ||||
|         None = 0, | ||||
|         Browser = 1, | ||||
|         Notification = 2 | ||||
|     } | ||||
| 
 | ||||
|     static class PluginEnvironmentExtensions{ | ||||
|         public static IEnumerable<PluginEnvironment> Values{ | ||||
|             get{ | ||||
|                 yield return PluginEnvironment.Browser; | ||||
|                 yield return PluginEnvironment.Notification; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static bool IncludesDisabledPlugins(this PluginEnvironment environment){ | ||||
|             return environment == PluginEnvironment.Browser; | ||||
|         } | ||||
| 
 | ||||
|         public static string GetScriptIdentifier(this PluginEnvironment environment){ | ||||
|             switch(environment){ | ||||
|                 case PluginEnvironment.None: return "root:plugins"; | ||||
|                 case PluginEnvironment.Browser: return "root:plugins.browser"; | ||||
|                 case PluginEnvironment.Notification: return "root:plugins.notification"; | ||||
|                 default: return null; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static string GetPluginScriptFile(this PluginEnvironment environment){ | ||||
|             switch(environment){ | ||||
|                 case PluginEnvironment.Browser: return "browser.js"; | ||||
|                 case PluginEnvironment.Notification: return "notification.js"; | ||||
|                 default: return null; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public static string GetPluginScriptVariables(this PluginEnvironment environment){ | ||||
|             switch(environment){ | ||||
|                 case PluginEnvironment.Browser: return "$,$TD,$TDP,TD"; | ||||
|                 case PluginEnvironment.Notification: return "$TD,$TDP"; | ||||
|                 default: return string.Empty; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,5 +0,0 @@ | ||||
| namespace TweetDuck.Plugins.Enums{ | ||||
|     enum PluginFolder{ | ||||
|         Root, Data | ||||
|     } | ||||
| } | ||||
| @@ -1,15 +0,0 @@ | ||||
| namespace TweetDuck.Plugins.Enums{ | ||||
|     enum PluginGroup{ | ||||
|         Official, Custom | ||||
|     } | ||||
| 
 | ||||
|     static class PluginGroupExtensions{ | ||||
|         public static string GetIdentifierPrefix(this PluginGroup group){ | ||||
|             switch(group){ | ||||
|                 case PluginGroup.Official: return "official/"; | ||||
|                 case PluginGroup.Custom: return "custom/"; | ||||
|                 default: return "unknown/"; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,13 +0,0 @@ | ||||
| using System; | ||||
| 
 | ||||
| namespace TweetDuck.Plugins.Events{ | ||||
|     sealed class PluginChangedStateEventArgs : EventArgs{ | ||||
|         public Plugin Plugin { get; } | ||||
|         public bool IsEnabled { get; } | ||||
| 
 | ||||
|         public PluginChangedStateEventArgs(Plugin plugin, bool isEnabled){ | ||||
|             this.Plugin = plugin; | ||||
|             this.IsEnabled = isEnabled; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,14 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace TweetDuck.Plugins.Events{ | ||||
|     sealed class PluginErrorEventArgs : EventArgs{ | ||||
|         public bool HasErrors => Errors.Count > 0; | ||||
| 
 | ||||
|         public IList<string> Errors { get; } | ||||
| 
 | ||||
|         public PluginErrorEventArgs(IList<string> errors){ | ||||
|             this.Errors = errors; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,230 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using TweetDuck.Plugins.Enums; | ||||
| 
 | ||||
| namespace TweetDuck.Plugins{ | ||||
|     sealed class Plugin{ | ||||
|         private static readonly Version AppVersion = new Version(Program.VersionTag); | ||||
|         private const string VersionWildcard = "*"; | ||||
| 
 | ||||
|         public string Identifier { get; } | ||||
|         public PluginGroup Group { get; } | ||||
|         public PluginEnvironment Environments { get; private set; } | ||||
| 
 | ||||
|         public string Name => metadata["NAME"]; | ||||
|         public string Description => metadata["DESCRIPTION"]; | ||||
|         public string Author => metadata["AUTHOR"]; | ||||
|         public string Version => metadata["VERSION"]; | ||||
|         public string Website => metadata["WEBSITE"]; | ||||
|         public string ConfigFile => metadata["CONFIGFILE"]; | ||||
|         public string ConfigDefault => metadata["CONFIGDEFAULT"]; | ||||
|         public string RequiredVersion => metadata["REQUIRES"]; | ||||
| 
 | ||||
|         public bool CanRun{ | ||||
|             get => canRun ?? (canRun = CheckRequiredVersion(RequiredVersion)).Value; | ||||
|         } | ||||
| 
 | ||||
|         public bool HasConfig{ | ||||
|             get => ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0; | ||||
|         } | ||||
| 
 | ||||
|         public string ConfigPath{ | ||||
|             get => HasConfig ? Path.Combine(GetPluginFolder(PluginFolder.Data), ConfigFile) : string.Empty; | ||||
|         } | ||||
| 
 | ||||
|         public bool HasDefaultConfig{ | ||||
|             get => ConfigDefault.Length > 0 && GetFullPathIfSafe(PluginFolder.Root, ConfigDefault).Length > 0; | ||||
|         } | ||||
| 
 | ||||
|         public string DefaultConfigPath{ | ||||
|             get => HasDefaultConfig ? Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigDefault) : string.Empty; | ||||
|         } | ||||
| 
 | ||||
|         private readonly string pathRoot; | ||||
|         private readonly string pathData; | ||||
|         private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(8){ | ||||
|             { "NAME", "" }, | ||||
|             { "DESCRIPTION", "" }, | ||||
|             { "AUTHOR", "(anonymous)" }, | ||||
|             { "VERSION", "(unknown)" }, | ||||
|             { "WEBSITE", "" }, | ||||
|             { "CONFIGFILE", "" }, | ||||
|             { "CONFIGDEFAULT", "" }, | ||||
|             { "REQUIRES", VersionWildcard } | ||||
|         }; | ||||
| 
 | ||||
|         private bool? canRun; | ||||
| 
 | ||||
|         private Plugin(string path, PluginGroup group){ | ||||
|             string name = 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.Environments = PluginEnvironment.None; | ||||
|         } | ||||
| 
 | ||||
|         private void OnMetadataLoaded(){ | ||||
|             string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath; | ||||
| 
 | ||||
|             if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){ | ||||
|                 string dataFolder = GetPluginFolder(PluginFolder.Data); | ||||
| 
 | ||||
|                 try{ | ||||
|                     Directory.CreateDirectory(dataFolder); | ||||
|                     File.Copy(defaultConfigPath, configPath, false); | ||||
|                 }catch(Exception e){ | ||||
|                     Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+Identifier+"' plugin.", true, e); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public string GetScriptPath(PluginEnvironment environment){ | ||||
|             if (Environments.HasFlag(environment)){ | ||||
|                 string file = environment.GetPluginScriptFile(); | ||||
|                 return file != null ? Path.Combine(pathRoot, file) : string.Empty; | ||||
|             } | ||||
|             else{ | ||||
|                 return string.Empty; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public string GetPluginFolder(PluginFolder folder){ | ||||
|             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{ | ||||
|                 string folderPathName = new DirectoryInfo(rootFolder).FullName; | ||||
|                 DirectoryInfo currentInfo = new DirectoryInfo(fullPath); | ||||
| 
 | ||||
|                 while(currentInfo.Parent != null){ | ||||
|                     if (currentInfo.Parent.FullName == folderPathName){ | ||||
|                         return fullPath; | ||||
|                     } | ||||
|                      | ||||
|                     currentInfo = currentInfo.Parent; | ||||
|                 } | ||||
|             } | ||||
|             catch{ | ||||
|                 // ignore | ||||
|             } | ||||
| 
 | ||||
|             return string.Empty; | ||||
|         } | ||||
| 
 | ||||
|         public override string ToString(){ | ||||
|             return Identifier; | ||||
|         } | ||||
| 
 | ||||
|         public override int GetHashCode(){ | ||||
|             return Identifier.GetHashCode(); | ||||
|         } | ||||
| 
 | ||||
|         public override bool Equals(object obj){ | ||||
|             return obj is Plugin plugin && plugin.Identifier.Equals(Identifier); | ||||
|         } | ||||
| 
 | ||||
|         public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){ | ||||
|             Plugin plugin = new Plugin(path, group); | ||||
| 
 | ||||
|             if (!LoadMetadata(path, plugin, out error)){ | ||||
|                 return null; | ||||
|             } | ||||
| 
 | ||||
|             if (!LoadEnvironments(path, plugin, out error)){ | ||||
|                 return null; | ||||
|             } | ||||
| 
 | ||||
|             error = string.Empty; | ||||
|             return plugin; | ||||
|         } | ||||
| 
 | ||||
|         private static bool LoadEnvironments(string path, Plugin plugin, out string error){ | ||||
|             foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){ | ||||
|                 plugin.Environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal)); | ||||
|             } | ||||
| 
 | ||||
|             if (plugin.Environments == PluginEnvironment.None){ | ||||
|                 error = "Plugin has no script files."; | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             error = string.Empty; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         private static readonly string[] endTag = { "[END]" }; | ||||
| 
 | ||||
|         private static bool LoadMetadata(string path, Plugin plugin, out string error){ | ||||
|             string metaFile = Path.Combine(path, ".meta"); | ||||
| 
 | ||||
|             if (!File.Exists(metaFile)){ | ||||
|                 error = "Missing .meta file."; | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             string[] lines = File.ReadAllLines(metaFile, Encoding.UTF8); | ||||
|             string currentTag = null, currentContents = ""; | ||||
| 
 | ||||
|             foreach(string line in lines.Concat(endTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){ | ||||
|                 if (line[0] == '[' && line[line.Length-1] == ']'){ | ||||
|                     if (currentTag != null){ | ||||
|                         plugin.metadata[currentTag] = currentContents; | ||||
|                     } | ||||
| 
 | ||||
|                     currentTag = line.Substring(1, line.Length-2).ToUpper(); | ||||
|                     currentContents = ""; | ||||
| 
 | ||||
|                     if (line.Equals(endTag[0])){ | ||||
|                         break; | ||||
|                     } | ||||
| 
 | ||||
|                     if (!plugin.metadata.ContainsKey(currentTag)){ | ||||
|                         error = "Invalid metadata tag: "+currentTag; | ||||
|                         return false; | ||||
|                     } | ||||
|                 } | ||||
|                 else if (currentTag != null){ | ||||
|                     currentContents = currentContents.Length == 0 ? line : currentContents+"\r\n"+line; | ||||
|                 } | ||||
|                 else{ | ||||
|                     error = "Missing metadata tag before value: "+line; | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (plugin.Name.Length == 0){ | ||||
|                 error = "Plugin is missing a name in the .meta file."; | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             if (plugin.RequiredVersion.Length == 0 || !(plugin.RequiredVersion == VersionWildcard || System.Version.TryParse(plugin.RequiredVersion, out Version _))){ | ||||
|                 error = "Plugin contains invalid version: "+plugin.RequiredVersion; | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             plugin.OnMetadataLoaded(); | ||||
| 
 | ||||
|             error = string.Empty; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         private static bool CheckRequiredVersion(string requires){ | ||||
|             return requires == VersionWildcard || AppVersion >= new Version(requires); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,118 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| using TweetDuck.Core.Utils; | ||||
| using TweetDuck.Data; | ||||
| using TweetDuck.Plugins.Enums; | ||||
| using TweetDuck.Plugins.Events; | ||||
| 
 | ||||
| namespace TweetDuck.Plugins{ | ||||
|     sealed class PluginBridge{ | ||||
|         private static string SanitizeCacheKey(string key){ | ||||
|             return key.Replace('\\', '/').Trim(); | ||||
|         } | ||||
| 
 | ||||
|         private readonly PluginManager manager; | ||||
|         private readonly TwoKeyDictionary<int, string, string> fileCache = new TwoKeyDictionary<int, string, string>(4, 2); | ||||
|         private readonly TwoKeyDictionary<int, string, InjectedHTML> notificationInjections = new TwoKeyDictionary<int,string,InjectedHTML>(4, 1); | ||||
| 
 | ||||
|         public IEnumerable<InjectedHTML> NotificationInjections => notificationInjections.InnerValues; | ||||
| 
 | ||||
|         public PluginBridge(PluginManager manager){ | ||||
|             this.manager = manager; | ||||
|             this.manager.Reloaded += manager_Reloaded; | ||||
|             this.manager.PluginChangedState += manager_PluginChangedState; | ||||
|         } | ||||
| 
 | ||||
|         // Event handlers | ||||
| 
 | ||||
|         private void manager_Reloaded(object sender, PluginErrorEventArgs e){ | ||||
|             fileCache.Clear(); | ||||
|         } | ||||
| 
 | ||||
|         private void manager_PluginChangedState(object sender, PluginChangedStateEventArgs e){ | ||||
|             if (!e.IsEnabled){ | ||||
|                 int token = manager.GetTokenFromPlugin(e.Plugin); | ||||
| 
 | ||||
|                 fileCache.Remove(token); | ||||
|                 notificationInjections.Remove(token); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Utility methods | ||||
| 
 | ||||
|         private string GetFullPathOrThrow(int token, PluginFolder folder, string path){ | ||||
|             Plugin plugin = manager.GetPluginFromToken(token); | ||||
|             string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path); | ||||
| 
 | ||||
|             if (fullPath.Length == 0){ | ||||
|                 switch(folder){ | ||||
|                     case PluginFolder.Data: throw new ArgumentException("File path has to be relative to the plugin data folder."); | ||||
|                     case PluginFolder.Root: throw new ArgumentException("File path has to be relative to the plugin root folder."); | ||||
|                     default: throw new ArgumentException("Invalid folder type "+folder+", this is a TweetDuck error."); | ||||
|                 } | ||||
|             } | ||||
|             else{ | ||||
|                 return fullPath; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool readCached){ | ||||
|             cacheKey = SanitizeCacheKey(cacheKey); | ||||
|              | ||||
|             if (readCached && fileCache.TryGetValue(token, cacheKey, out string cachedContents)){ | ||||
|                 return cachedContents; | ||||
|             } | ||||
| 
 | ||||
|             try{ | ||||
|                 return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8); | ||||
|             }catch(FileNotFoundException){ | ||||
|                 throw new FileNotFoundException("File not found."); | ||||
|             }catch(DirectoryNotFoundException){ | ||||
|                 throw new DirectoryNotFoundException("Directory not found."); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Public methods | ||||
| 
 | ||||
|         public void WriteFile(int token, string path, string contents){ | ||||
|             string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path); | ||||
| 
 | ||||
|             WindowsUtils.CreateDirectoryForFile(fullPath); | ||||
|             File.WriteAllText(fullPath, contents, Encoding.UTF8); | ||||
|             fileCache[token, SanitizeCacheKey(path)] = contents; | ||||
|         } | ||||
| 
 | ||||
|         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); | ||||
|         } | ||||
| 
 | ||||
|         public bool CheckFileExists(int token, string path){ | ||||
|             return File.Exists(GetFullPathOrThrow(token, PluginFolder.Data, path)); | ||||
|         } | ||||
| 
 | ||||
|         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)); | ||||
|         } | ||||
| 
 | ||||
|         public void InjectIntoNotificationsBefore(int token, string key, string search, string html){ | ||||
|             notificationInjections[token, key] = new InjectedHTML(InjectedHTML.Position.Before, search, html); | ||||
|         } | ||||
| 
 | ||||
|         public void InjectIntoNotificationsAfter(int token, string key, string search, string html){ | ||||
|             notificationInjections[token, key] = new InjectedHTML(InjectedHTML.Position.After, search, html); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,72 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| using TweetDuck.Plugins.Events; | ||||
| 
 | ||||
| namespace TweetDuck.Plugins{ | ||||
|     sealed class PluginConfig{ | ||||
|         public event EventHandler<PluginChangedStateEventArgs> InternalPluginChangedState; // should only be accessed from PluginManager | ||||
| 
 | ||||
|         public IEnumerable<string> DisabledPlugins => disabled; | ||||
|         public bool AnyDisabled => disabled.Count > 0; | ||||
| 
 | ||||
|         private static readonly string[] DefaultDisabled = { | ||||
|             "official/clear-columns", | ||||
|             "official/reply-account" | ||||
|         }; | ||||
| 
 | ||||
|         private readonly HashSet<string> disabled = new HashSet<string>(); | ||||
| 
 | ||||
|         public void SetEnabled(Plugin plugin, bool enabled){ | ||||
|             if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){ | ||||
|                 InternalPluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled)); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public bool IsEnabled(Plugin plugin){ | ||||
|             return !disabled.Contains(plugin.Identifier); | ||||
|         } | ||||
| 
 | ||||
|         public void Load(string file){ | ||||
|             try{ | ||||
|                 using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)){ | ||||
|                     string line = reader.ReadLine(); | ||||
| 
 | ||||
|                     if (line == "#Disabled"){ | ||||
|                         disabled.Clear(); | ||||
| 
 | ||||
|                         while((line = reader.ReadLine()) != null){ | ||||
|                             disabled.Add(line); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             }catch(FileNotFoundException){ | ||||
|                 disabled.Clear(); | ||||
|                  | ||||
|                 foreach(string identifier in DefaultDisabled){ | ||||
|                     disabled.Add(identifier); | ||||
|                 } | ||||
| 
 | ||||
|                 Save(file); | ||||
|             }catch(DirectoryNotFoundException){ | ||||
|             }catch(Exception e){ | ||||
|                 Program.Reporter.HandleException("Plugin Configuration Error", "Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", true, e); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void Save(string file){ | ||||
|             try{ | ||||
|                 using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){ | ||||
|                     writer.WriteLine("#Disabled"); | ||||
| 
 | ||||
|                     foreach(string identifier in disabled){ | ||||
|                         writer.WriteLine(identifier); | ||||
|                     } | ||||
|                 } | ||||
|             }catch(Exception e){ | ||||
|                 Program.Reporter.HandleException("Plugin Configuration Error", "Could not save the plugin configuration file.", true, e); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,172 +0,0 @@ | ||||
| using CefSharp; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using TweetDuck.Plugins.Enums; | ||||
| using TweetDuck.Plugins.Events; | ||||
| using TweetDuck.Resources; | ||||
| 
 | ||||
| namespace TweetDuck.Plugins{ | ||||
|     sealed class PluginManager{ | ||||
|         private const int InvalidToken = 0; | ||||
| 
 | ||||
|         private static readonly Dictionary<PluginEnvironment, string> PluginSetupScripts = new Dictionary<PluginEnvironment, string>(4){ | ||||
|             { PluginEnvironment.None, ScriptLoader.LoadResource("plugins.js") }, | ||||
|             { PluginEnvironment.Browser, ScriptLoader.LoadResource("plugins.browser.js") }, | ||||
|             { PluginEnvironment.Notification, ScriptLoader.LoadResource("plugins.notification.js") } | ||||
|         }; | ||||
| 
 | ||||
|         public string PathOfficialPlugins => Path.Combine(rootPath, "official"); | ||||
|         public string PathCustomPlugins => Path.Combine(rootPath, "user"); | ||||
| 
 | ||||
|         public IEnumerable<Plugin> Plugins => plugins; | ||||
|         public PluginConfig Config { get; } | ||||
|         public PluginBridge Bridge { get; } | ||||
|          | ||||
|         public event EventHandler<PluginErrorEventArgs> Reloaded; | ||||
|         public event EventHandler<PluginErrorEventArgs> Executed; | ||||
|         public event EventHandler<PluginChangedStateEventArgs> PluginChangedState; | ||||
| 
 | ||||
|         private readonly string rootPath; | ||||
|         private readonly string configPath; | ||||
| 
 | ||||
|         private readonly HashSet<Plugin> plugins = new HashSet<Plugin>(); | ||||
|         private readonly Dictionary<int, Plugin> tokens = new Dictionary<int, Plugin>(); | ||||
|         private readonly Random rand = new Random(); | ||||
| 
 | ||||
|         private List<string> loadErrors; | ||||
| 
 | ||||
|         public PluginManager(string rootPath, string configPath){ | ||||
|             this.rootPath = rootPath; | ||||
|             this.configPath = configPath; | ||||
| 
 | ||||
|             this.Config = new PluginConfig(); | ||||
|             this.Bridge = new PluginBridge(this); | ||||
| 
 | ||||
|             Config.Load(configPath); | ||||
|             Config.InternalPluginChangedState += Config_InternalPluginChangedState; | ||||
|         } | ||||
| 
 | ||||
|         private void Config_InternalPluginChangedState(object sender, PluginChangedStateEventArgs e){ | ||||
|             PluginChangedState?.Invoke(this, e); | ||||
|             Config.Save(configPath); | ||||
|         } | ||||
| 
 | ||||
|         public bool IsPluginInstalled(string identifier){ | ||||
|             return plugins.Any(plugin => plugin.Identifier.Equals(identifier)); | ||||
|         } | ||||
| 
 | ||||
|         public bool HasAnyPlugin(PluginEnvironment 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){ | ||||
|             return tokens.TryGetValue(token, out Plugin plugin) ? plugin : null; | ||||
|         } | ||||
| 
 | ||||
|         public void Reload(){ | ||||
|             Config.Load(configPath); | ||||
| 
 | ||||
|             plugins.Clear(); | ||||
|             tokens.Clear(); | ||||
| 
 | ||||
|             loadErrors = new List<string>(2); | ||||
|              | ||||
|             foreach(Plugin plugin in LoadPluginsFrom(PathOfficialPlugins, PluginGroup.Official)){ | ||||
|                 plugins.Add(plugin); | ||||
|             } | ||||
| 
 | ||||
|             foreach(Plugin plugin in LoadPluginsFrom(PathCustomPlugins, PluginGroup.Custom)){ | ||||
|                 plugins.Add(plugin); | ||||
|             } | ||||
| 
 | ||||
|             Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors)); | ||||
|         } | ||||
| 
 | ||||
|         public void ExecutePlugins(IFrame frame, PluginEnvironment environment){ | ||||
|             if (HasAnyPlugin(environment)){ | ||||
|                 ScriptLoader.ExecuteScript(frame, PluginSetupScripts[environment], environment.GetScriptIdentifier()); | ||||
|                 ScriptLoader.ExecuteScript(frame, PluginSetupScripts[PluginEnvironment.None], PluginEnvironment.None.GetScriptIdentifier()); | ||||
|                 ExecutePluginScripts(frame, environment); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void ExecutePluginScripts(IFrame frame, PluginEnvironment environment){ | ||||
|             bool includeDisabled = environment.IncludesDisabledPlugins(); | ||||
| 
 | ||||
|             if (includeDisabled){ | ||||
|                 ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GenerateConfig(Config), "gen:pluginconfig"); | ||||
|             } | ||||
| 
 | ||||
|             List<string> failedPlugins = new List<string>(1); | ||||
| 
 | ||||
|             foreach(Plugin plugin in Plugins){ | ||||
|                 string path = plugin.GetScriptPath(environment); | ||||
|                 if (string.IsNullOrEmpty(path) || (!includeDisabled && !Config.IsEnabled(plugin)) || !plugin.CanRun)continue; | ||||
| 
 | ||||
|                 string script; | ||||
| 
 | ||||
|                 try{ | ||||
|                     script = File.ReadAllText(path); | ||||
|                 }catch(Exception e){ | ||||
|                     failedPlugins.Add(plugin.Identifier+" ("+Path.GetFileName(path)+"): "+e.Message); | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 int token; | ||||
| 
 | ||||
|                 if (tokens.ContainsValue(plugin)){ | ||||
|                     token = GetTokenFromPlugin(plugin); | ||||
|                 } | ||||
|                 else{ | ||||
|                     token = GenerateToken(); | ||||
|                     tokens[token] = plugin; | ||||
|                 } | ||||
| 
 | ||||
|                 ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, token, environment), "plugin:"+plugin); | ||||
|             } | ||||
| 
 | ||||
|             Executed?.Invoke(this, new PluginErrorEventArgs(failedPlugins)); | ||||
|         } | ||||
| 
 | ||||
|         private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){ | ||||
|             if (!Directory.Exists(path)){ | ||||
|                 yield break; | ||||
|             } | ||||
| 
 | ||||
|             foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){ | ||||
|                 Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out string error); | ||||
| 
 | ||||
|                 if (plugin == null){ | ||||
|                     loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+error); | ||||
|                 } | ||||
|                 else{ | ||||
|                     yield return plugin; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private int GenerateToken(){ | ||||
|             for(int attempt = 0; attempt < 1000; attempt++){ | ||||
|                 int token = rand.Next(); | ||||
| 
 | ||||
|                 if (!tokens.ContainsKey(token) && token != InvalidToken){ | ||||
|                     return token; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return -tokens.Count; | ||||
|         } | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user