Compare commits
	
		
			No commits in common. "trunk" and "203f575914be9a58cf81cd7636d2cd531659d7f4" have entirely different histories.
		
	
	
		
			trunk
			...
			203f575914
		
	
		
					 4 changed files with 734 additions and 380 deletions
				
			
		
							
								
								
									
										410
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										410
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -2,6 +2,15 @@
 | 
			
		|||
# It is not intended for manual editing.
 | 
			
		||||
version = 3
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "addr2line"
 | 
			
		||||
version = "0.15.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "gimli",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "adler"
 | 
			
		||||
version = "1.0.2"
 | 
			
		||||
| 
						 | 
				
			
			@ -14,20 +23,32 @@ version = "1.2.0"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "aho-corasick"
 | 
			
		||||
version = "0.6.10"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "memchr",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "anime_telnet"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/alisww/anime-over-ansi#c5b6e21e97584dfe745aec2c75f925abbd93d0d3"
 | 
			
		||||
source = "git+https://github.com/alisww/anime-over-ansi#ac590f449bf069384ebea59bc213ee44e8ef3ea0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "argmm",
 | 
			
		||||
 "derive_builder",
 | 
			
		||||
 "fast_image_resize",
 | 
			
		||||
 "image",
 | 
			
		||||
 "image 0.23.14",
 | 
			
		||||
 "lab",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "lazy_static 1.4.0",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "simd-adler32",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
 "subparse",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "zstd",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
| 
						 | 
				
			
			@ -39,11 +60,23 @@ dependencies = [
 | 
			
		|||
 "winapi",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "anyhow"
 | 
			
		||||
version = "1.0.48"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "62e1f47f7dc0422027a4e370dd4548d4d66b26782e513e98dca1e689e058a80e"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "argmm"
 | 
			
		||||
version = "0.1.2"
 | 
			
		||||
source = "git+https://github.com/alisww/argmm#3c59d9d6803608018ecce501b515cd5779d0e09e"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ascii"
 | 
			
		||||
version = "0.7.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3ae7d751998c189c1d4468cf0a39bb2eae052a9c58d50ebb3b9591ee3813ad50"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "atty"
 | 
			
		||||
version = "0.2.14"
 | 
			
		||||
| 
						 | 
				
			
			@ -61,6 +94,21 @@ version = "1.0.1"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "backtrace"
 | 
			
		||||
version = "0.3.59"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "addr2line",
 | 
			
		||||
 "cc",
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "miniz_oxide 0.4.4",
 | 
			
		||||
 "object",
 | 
			
		||||
 "rustc-demangle",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bitflags"
 | 
			
		||||
version = "1.3.2"
 | 
			
		||||
| 
						 | 
				
			
			@ -79,11 +127,29 @@ version = "1.4.3"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bytes"
 | 
			
		||||
version = "1.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cast"
 | 
			
		||||
version = "0.2.7"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "rustc_version",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cc"
 | 
			
		||||
version = "1.0.72"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "jobserver",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cfg-if"
 | 
			
		||||
| 
						 | 
				
			
			@ -91,6 +157,12 @@ version = "1.0.0"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "chardet"
 | 
			
		||||
version = "0.2.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1a48563284b67c003ba0fb7243c87fab68885e1532c605704228a80238512e31"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "clap"
 | 
			
		||||
version = "2.33.3"
 | 
			
		||||
| 
						 | 
				
			
			@ -112,6 +184,16 @@ version = "1.1.0"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "combine"
 | 
			
		||||
version = "2.5.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1645a65a99c7c8d345761f4b75a6ffe5be3b3b27a93ee731fccc5050ba6be97c"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "ascii",
 | 
			
		||||
 "byteorder",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crc32fast"
 | 
			
		||||
version = "1.2.2"
 | 
			
		||||
| 
						 | 
				
			
			@ -150,7 +232,7 @@ checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
 | 
			
		|||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "lazy_static 1.4.0",
 | 
			
		||||
 "memoffset",
 | 
			
		||||
 "scopeguard",
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			@ -162,7 +244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		|||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "lazy_static 1.4.0",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
| 
						 | 
				
			
			@ -265,20 +347,69 @@ dependencies = [
 | 
			
		|||
 "anime_telnet",
 | 
			
		||||
 "fast_image_resize",
 | 
			
		||||
 "ferretro_components",
 | 
			
		||||
 "image",
 | 
			
		||||
 "image 0.23.14",
 | 
			
		||||
 "sdl2",
 | 
			
		||||
 "structopt",
 | 
			
		||||
 "termion",
 | 
			
		||||
 "tokio",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "encoding_rs"
 | 
			
		||||
version = "0.8.29"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "enum_primitive"
 | 
			
		||||
version = "0.1.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "num-traits 0.1.43",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "error-chain"
 | 
			
		||||
version = "0.10.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "backtrace",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "failure"
 | 
			
		||||
version = "0.1.8"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "backtrace",
 | 
			
		||||
 "failure_derive",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "failure_derive"
 | 
			
		||||
version = "0.1.8"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn",
 | 
			
		||||
 "synstructure",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "fast_image_resize"
 | 
			
		||||
version = "0.4.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2479aea9bfdceba1d18827cbae85c85feb29780ab293717184cf2124998122f4"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "num-traits",
 | 
			
		||||
 "num-traits 0.2.14",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -333,6 +464,12 @@ dependencies = [
 | 
			
		|||
 "weezl",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "gimli"
 | 
			
		||||
version = "0.24.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "heck"
 | 
			
		||||
version = "0.3.3"
 | 
			
		||||
| 
						 | 
				
			
			@ -357,6 +494,19 @@ version = "1.0.1"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "image"
 | 
			
		||||
version = "0.13.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1c3f4f5ea213ed9899eca760a8a14091d4b82d33e27cf8ced336ff730e9f6da8"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "byteorder",
 | 
			
		||||
 "enum_primitive",
 | 
			
		||||
 "num-iter",
 | 
			
		||||
 "num-rational 0.1.42",
 | 
			
		||||
 "num-traits 0.1.43",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "image"
 | 
			
		||||
version = "0.23.14"
 | 
			
		||||
| 
						 | 
				
			
			@ -369,13 +519,31 @@ dependencies = [
 | 
			
		|||
 "gif",
 | 
			
		||||
 "jpeg-decoder",
 | 
			
		||||
 "num-iter",
 | 
			
		||||
 "num-rational",
 | 
			
		||||
 "num-traits",
 | 
			
		||||
 "num-rational 0.3.2",
 | 
			
		||||
 "num-traits 0.2.14",
 | 
			
		||||
 "png",
 | 
			
		||||
 "scoped_threadpool",
 | 
			
		||||
 "tiff",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "itertools"
 | 
			
		||||
version = "0.8.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "either",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "jobserver"
 | 
			
		||||
version = "0.1.24"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "jpeg-decoder"
 | 
			
		||||
version = "0.1.22"
 | 
			
		||||
| 
						 | 
				
			
			@ -391,6 +559,12 @@ version = "0.11.0"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "lazy_static"
 | 
			
		||||
version = "0.2.11"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "lazy_static"
 | 
			
		||||
version = "1.4.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -422,6 +596,30 @@ dependencies = [
 | 
			
		|||
 "libc",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "log"
 | 
			
		||||
version = "0.3.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "log 0.4.14",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "log"
 | 
			
		||||
version = "0.4.14"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "memchr"
 | 
			
		||||
version = "2.3.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "memoffset"
 | 
			
		||||
version = "0.6.4"
 | 
			
		||||
| 
						 | 
				
			
			@ -450,6 +648,12 @@ dependencies = [
 | 
			
		|||
 "autocfg",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "nom"
 | 
			
		||||
version = "2.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e5d4598834859fedb9a0a69d5b862a970e77982a92f544d547257a4d49469067"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "num-integer"
 | 
			
		||||
version = "0.1.44"
 | 
			
		||||
| 
						 | 
				
			
			@ -457,7 +661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		|||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
 "num-traits",
 | 
			
		||||
 "num-traits 0.2.14",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
| 
						 | 
				
			
			@ -468,7 +672,17 @@ checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
 | 
			
		|||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
 "num-integer",
 | 
			
		||||
 "num-traits",
 | 
			
		||||
 "num-traits 0.2.14",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "num-rational"
 | 
			
		||||
version = "0.1.42"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "num-integer",
 | 
			
		||||
 "num-traits 0.2.14",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
| 
						 | 
				
			
			@ -479,7 +693,16 @@ checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
 | 
			
		|||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
 "num-integer",
 | 
			
		||||
 "num-traits",
 | 
			
		||||
 "num-traits 0.2.14",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "num-traits"
 | 
			
		||||
version = "0.1.43"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "num-traits 0.2.14",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
| 
						 | 
				
			
			@ -529,6 +752,12 @@ version = "0.1.0"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "object"
 | 
			
		||||
version = "0.24.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "once_cell"
 | 
			
		||||
version = "1.8.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -671,7 +900,7 @@ dependencies = [
 | 
			
		|||
 "crossbeam-channel",
 | 
			
		||||
 "crossbeam-deque",
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "lazy_static 1.4.0",
 | 
			
		||||
 "num_cpus",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -693,6 +922,28 @@ dependencies = [
 | 
			
		|||
 "redox_syscall",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex"
 | 
			
		||||
version = "0.2.11"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "aho-corasick",
 | 
			
		||||
 "memchr",
 | 
			
		||||
 "regex-syntax",
 | 
			
		||||
 "thread_local",
 | 
			
		||||
 "utf8-ranges",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex-syntax"
 | 
			
		||||
version = "0.5.6"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "ucd-util",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "remove_dir_all"
 | 
			
		||||
version = "0.5.3"
 | 
			
		||||
| 
						 | 
				
			
			@ -702,6 +953,27 @@ dependencies = [
 | 
			
		|||
 "winapi",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rustc-demangle"
 | 
			
		||||
version = "0.1.21"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rustc_version"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "semver",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "safemem"
 | 
			
		||||
version = "0.2.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "scoped_threadpool"
 | 
			
		||||
version = "0.1.9"
 | 
			
		||||
| 
						 | 
				
			
			@ -721,7 +993,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		|||
checksum = "f035f8e87735fa3a8437292be49fe6056450f7cbb13c230b4bcd1bdd7279421f"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bitflags",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "lazy_static 1.4.0",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "sdl2-sys",
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			@ -737,6 +1009,12 @@ dependencies = [
 | 
			
		|||
 "version-compare",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "semver"
 | 
			
		||||
version = "1.0.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde"
 | 
			
		||||
version = "1.0.130"
 | 
			
		||||
| 
						 | 
				
			
			@ -782,7 +1060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		|||
checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "clap",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "lazy_static 1.4.0",
 | 
			
		||||
 "structopt-derive",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -800,16 +1078,42 @@ dependencies = [
 | 
			
		|||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "syn"
 | 
			
		||||
version = "1.0.82"
 | 
			
		||||
name = "subparse"
 | 
			
		||||
version = "0.7.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
 | 
			
		||||
checksum = "a08e0e5404c97213cd361c86370969ca47a7ec3571509710117e707b9f7c29ab"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "chardet",
 | 
			
		||||
 "combine",
 | 
			
		||||
 "encoding_rs",
 | 
			
		||||
 "failure",
 | 
			
		||||
 "itertools",
 | 
			
		||||
 "vobsub",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "syn"
 | 
			
		||||
version = "1.0.81"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "unicode-xid",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "synstructure"
 | 
			
		||||
version = "0.12.6"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn",
 | 
			
		||||
 "unicode-xid",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tempfile"
 | 
			
		||||
version = "3.2.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -865,6 +1169,15 @@ dependencies = [
 | 
			
		|||
 "syn",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thread_local"
 | 
			
		||||
version = "0.3.6"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "lazy_static 1.4.0",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tiff"
 | 
			
		||||
version = "0.6.1"
 | 
			
		||||
| 
						 | 
				
			
			@ -883,6 +1196,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		|||
checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
 "bytes",
 | 
			
		||||
 "memchr",
 | 
			
		||||
 "pin-project-lite",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -895,6 +1210,12 @@ dependencies = [
 | 
			
		|||
 "serde",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ucd-util"
 | 
			
		||||
version = "0.1.8"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c85f514e095d348c279b1e5cd76795082cf15bd59b93207832abe0b1d8fed236"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "unicode-segmentation"
 | 
			
		||||
version = "1.8.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -913,6 +1234,12 @@ version = "0.2.2"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "utf8-ranges"
 | 
			
		||||
version = "1.0.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "vec_map"
 | 
			
		||||
version = "0.8.2"
 | 
			
		||||
| 
						 | 
				
			
			@ -931,6 +1258,22 @@ version = "0.9.3"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "vobsub"
 | 
			
		||||
version = "0.2.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "aa122d660e26d9b6aa8f3436304b667ec81cbc0d48a5d19640d7e55ca8eac812"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cast",
 | 
			
		||||
 "error-chain",
 | 
			
		||||
 "image 0.13.0",
 | 
			
		||||
 "lazy_static 0.2.11",
 | 
			
		||||
 "log 0.3.9",
 | 
			
		||||
 "nom",
 | 
			
		||||
 "regex",
 | 
			
		||||
 "safemem",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasi"
 | 
			
		||||
version = "0.10.2+wasi-snapshot-preview1"
 | 
			
		||||
| 
						 | 
				
			
			@ -964,3 +1307,32 @@ name = "winapi-x86_64-pc-windows-gnu"
 | 
			
		|||
version = "0.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "zstd"
 | 
			
		||||
version = "0.7.0+zstd.1.4.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "9428752481d8372e15b1bf779ea518a179ad6c771cca2d2c60e4fbff3cc2cd52"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "zstd-safe",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "zstd-safe"
 | 
			
		||||
version = "3.1.0+zstd.1.4.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5aa1926623ad7fe406e090555387daf73db555b948134b4d73eac5eb08fb666d"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
 "zstd-sys",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "zstd-sys"
 | 
			
		||||
version = "1.5.0+zstd.1.4.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4e6c094340240369025fc6b731b054ee2a834328fa584310ac96aa4baebdc465"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cc",
 | 
			
		||||
 "libc",
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,169 +0,0 @@
 | 
			
		|||
use std::io::{Stdout, Write};
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
use std::time::Instant;
 | 
			
		||||
 | 
			
		||||
use ferretro_components::base::ControlFlow;
 | 
			
		||||
use ferretro_components::prelude::*;
 | 
			
		||||
 | 
			
		||||
use termion::color::DetectColors;
 | 
			
		||||
use termion::raw::{IntoRawMode, RawTerminal};
 | 
			
		||||
use termion::screen::AlternateScreen;
 | 
			
		||||
use termion::terminal_size;
 | 
			
		||||
 | 
			
		||||
use anime_telnet::encoding::{AnsiEncoder, ProcessorPipeline};
 | 
			
		||||
use anime_telnet::metadata::ColorMode;
 | 
			
		||||
use fast_image_resize::FilterType;
 | 
			
		||||
use image::RgbaImage;
 | 
			
		||||
 | 
			
		||||
use sdl2::pixels::PixelFormatEnum;
 | 
			
		||||
use sdl2::surface::Surface;
 | 
			
		||||
 | 
			
		||||
pub struct AnsiVideoComponent {
 | 
			
		||||
    terminal_width: u32,
 | 
			
		||||
    terminal_height: u32,
 | 
			
		||||
    color_mode: ColorMode,
 | 
			
		||||
    screen: AlternateScreen<RawTerminal<Stdout>>,
 | 
			
		||||
    fps: f32,
 | 
			
		||||
    framerate_sampling_start: Instant,
 | 
			
		||||
    frame_count: usize,
 | 
			
		||||
    frame_skip: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AnsiEncoder for AnsiVideoComponent {
 | 
			
		||||
    fn needs_width(&self) -> u32 {
 | 
			
		||||
        self.terminal_width - 1
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn needs_height(&self) -> u32 {
 | 
			
		||||
        (self.terminal_height - 1) * 2 // half-blocks?
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn needs_color(&self) -> ColorMode {
 | 
			
		||||
        self.color_mode
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for AnsiVideoComponent {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        let output = std::io::stdout().into_raw_mode().unwrap();
 | 
			
		||||
        let mut screen = AlternateScreen::from(output);
 | 
			
		||||
        write!(screen, "{}", termion::cursor::Hide).unwrap();
 | 
			
		||||
 | 
			
		||||
        let (w, h) = terminal_size().unwrap_or((80, 24));
 | 
			
		||||
        let (width, height) = (w as u32, h as u32);
 | 
			
		||||
        let colors = screen.available_colors().unwrap_or(16);
 | 
			
		||||
 | 
			
		||||
        let color_mode = if colors > 16 { ColorMode::True } else { ColorMode::EightBit };
 | 
			
		||||
 | 
			
		||||
        AnsiVideoComponent {
 | 
			
		||||
            terminal_width: width,
 | 
			
		||||
            terminal_height: height,
 | 
			
		||||
            color_mode,
 | 
			
		||||
            screen,
 | 
			
		||||
            fps: 60.0,
 | 
			
		||||
            framerate_sampling_start: Instant::now(),
 | 
			
		||||
            frame_count: 0,
 | 
			
		||||
            frame_skip: 0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Drop for AnsiVideoComponent {
 | 
			
		||||
    fn drop(&mut self) {
 | 
			
		||||
        write!(self.screen, "{}", termion::cursor::Show).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RetroCallbacks for AnsiVideoComponent {
 | 
			
		||||
    fn video_refresh(&mut self, frame: &VideoFrame) {
 | 
			
		||||
        match frame {
 | 
			
		||||
            VideoFrame::XRGB1555 { width, height, .. }
 | 
			
		||||
            | VideoFrame::RGB565 { width, height, .. }
 | 
			
		||||
            | VideoFrame::XRGB8888 { width, height, .. } => {
 | 
			
		||||
                let (bytes, pitch) = frame.data_pitch_as_bytes().unwrap();
 | 
			
		||||
                let pitch = pitch as u32;
 | 
			
		||||
                let format = match frame.pixel_format().unwrap() {
 | 
			
		||||
                    PixelFormat::ARGB1555 => sdl2::pixels::PixelFormatEnum::RGB555,
 | 
			
		||||
                    PixelFormat::ARGB8888 => sdl2::pixels::PixelFormatEnum::ARGB8888,
 | 
			
		||||
                    PixelFormat::RGB565 => sdl2::pixels::PixelFormatEnum::RGB565,
 | 
			
		||||
                };
 | 
			
		||||
                // dirty, but must be &mut for SDL API.
 | 
			
		||||
                // safety: we don't actually mutate or leak the Surface we construct here.
 | 
			
		||||
                let data = unsafe {
 | 
			
		||||
                    core::slice::from_raw_parts_mut(bytes.as_ptr() as *mut u8, bytes.len())
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                // has the screen size changed?
 | 
			
		||||
                let (w, h) = terminal_size().unwrap_or((80, 24));
 | 
			
		||||
                let (w, h) = (w as u32, h as u32);
 | 
			
		||||
                let force_redraw = if self.terminal_width != w || self.terminal_height != h {
 | 
			
		||||
                    self.terminal_width = w as u32;
 | 
			
		||||
                    self.terminal_height = h as u32;
 | 
			
		||||
                    self.frame_skip = 0;
 | 
			
		||||
                    write!(self.screen, "{}", termion::clear::All).unwrap();
 | 
			
		||||
                    true
 | 
			
		||||
                } else {
 | 
			
		||||
                    false
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                if !force_redraw {
 | 
			
		||||
                    if self.frame_skip != 0 && self.frame_count % self.frame_skip != 0 {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if let Ok(surf) = Surface::from_data(data, *width, *height, pitch, format) {
 | 
			
		||||
                    let rgba_raw = surf.into_canvas().unwrap().read_pixels(None, PixelFormatEnum::ABGR8888).unwrap();
 | 
			
		||||
                    let rgba_img = RgbaImage::from_raw(*width, *height, rgba_raw).unwrap();
 | 
			
		||||
 | 
			
		||||
                    let processor = ProcessorPipeline {
 | 
			
		||||
                        filter: FilterType::Hamming,
 | 
			
		||||
                        width: self.needs_width(),
 | 
			
		||||
                        height: self.needs_height(),
 | 
			
		||||
                        color_modes: Some(self.needs_color()).into_iter().collect(),
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    let processed = processor.process(&rgba_img).into_iter().next().unwrap().1;
 | 
			
		||||
                    write!(self.screen, "{}", termion::cursor::Goto(1, 1)).unwrap();
 | 
			
		||||
                    for line in self.encode_frame(&processed).lines() {
 | 
			
		||||
                        write!(self.screen, "{}\r\n", line).unwrap();
 | 
			
		||||
                    }
 | 
			
		||||
                    write!(self.screen, "\x1B[0m").unwrap();
 | 
			
		||||
                    self.screen.flush().unwrap();
 | 
			
		||||
                } else if force_redraw {
 | 
			
		||||
                    // TODO: draw last copy
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _ => {}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_variable(&mut self, key: &str) -> Option<String> {
 | 
			
		||||
        match key {
 | 
			
		||||
            "parallel-n64-gfxplugin" => Some("angrylion".to_string()),
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RetroComponent for AnsiVideoComponent {
 | 
			
		||||
    fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow {
 | 
			
		||||
        self.frame_count += 1;
 | 
			
		||||
        if self.frame_skip < 10 && self.frame_count > 10 {
 | 
			
		||||
            let now = Instant::now();
 | 
			
		||||
            let period = now.duration_since(self.framerate_sampling_start).as_secs_f32();
 | 
			
		||||
            let actual_fps = self.frame_count as f32 / period;
 | 
			
		||||
            if actual_fps < self.fps * 2.0 / 3.0 {
 | 
			
		||||
                self.frame_skip += 1;
 | 
			
		||||
                self.frame_count = 0;
 | 
			
		||||
                self.framerate_sampling_start = now;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ControlFlow::Continue
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> ferretro_components::base::Result<()> {
 | 
			
		||||
        self.fps = retro.get_system_av_info().timing.fps as f32;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										348
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										348
									
								
								src/main.rs
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -1,4 +1,8 @@
 | 
			
		|||
use std::path::PathBuf;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::io::{Stdout, Write};
 | 
			
		||||
use std::path::{Path, PathBuf};
 | 
			
		||||
use std::sync::mpsc::Receiver;
 | 
			
		||||
use std::time::{Duration, Instant};
 | 
			
		||||
 | 
			
		||||
use structopt::StructOpt;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6,8 +10,21 @@ use ferretro_components::base::ControlFlow;
 | 
			
		|||
use ferretro_components::prelude::*;
 | 
			
		||||
use ferretro_components::provided::stdlib::*;
 | 
			
		||||
 | 
			
		||||
mod ansi_video;
 | 
			
		||||
mod term_input;
 | 
			
		||||
use termion::color::DetectColors;
 | 
			
		||||
use termion::event::Key;
 | 
			
		||||
use termion::input::TermRead;
 | 
			
		||||
use termion::raw::{IntoRawMode, RawTerminal};
 | 
			
		||||
use termion::screen::AlternateScreen;
 | 
			
		||||
use termion::terminal_size;
 | 
			
		||||
 | 
			
		||||
use anime_telnet::encoding::Encoder as AnsiArtEncoder;
 | 
			
		||||
use anime_telnet::encoding::ProcessorPipeline;
 | 
			
		||||
use anime_telnet::metadata::ColorMode;
 | 
			
		||||
use fast_image_resize::FilterType;
 | 
			
		||||
use image::RgbaImage;
 | 
			
		||||
 | 
			
		||||
use sdl2::pixels::PixelFormatEnum;
 | 
			
		||||
use sdl2::surface::Surface;
 | 
			
		||||
 | 
			
		||||
#[derive(StructOpt)]
 | 
			
		||||
struct Opt {
 | 
			
		||||
| 
						 | 
				
			
			@ -22,11 +39,332 @@ struct Opt {
 | 
			
		|||
    system: Option<PathBuf>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct AnsiVideoComponent {
 | 
			
		||||
    terminal_width: u32,
 | 
			
		||||
    terminal_height: u32,
 | 
			
		||||
    color_mode: ColorMode,
 | 
			
		||||
    screen: AlternateScreen<RawTerminal<Stdout>>,
 | 
			
		||||
    fps: f32,
 | 
			
		||||
    framerate_sampling_start: Instant,
 | 
			
		||||
    frame_count: usize,
 | 
			
		||||
    frame_skip: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AnsiArtEncoder for AnsiVideoComponent {
 | 
			
		||||
    fn needs_width(&self) -> u32 {
 | 
			
		||||
        self.terminal_width - 1
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn needs_height(&self) -> u32 {
 | 
			
		||||
        (self.terminal_height - 1) * 2 // half-blocks?
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn needs_color(&self) -> ColorMode {
 | 
			
		||||
        self.color_mode
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for AnsiVideoComponent {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        let output = std::io::stdout().into_raw_mode().unwrap();
 | 
			
		||||
        let mut screen = AlternateScreen::from(output);
 | 
			
		||||
        write!(screen, "{}", termion::cursor::Hide).unwrap();
 | 
			
		||||
 | 
			
		||||
        let (w, h) = terminal_size().unwrap_or((80, 24));
 | 
			
		||||
        let (width, height) = (w as u32, h as u32);
 | 
			
		||||
        let colors = screen.available_colors().unwrap_or(16);
 | 
			
		||||
 | 
			
		||||
        let color_mode = if colors > 16 { ColorMode::True } else { ColorMode::EightBit };
 | 
			
		||||
 | 
			
		||||
        AnsiVideoComponent {
 | 
			
		||||
            terminal_width: width,
 | 
			
		||||
            terminal_height: height,
 | 
			
		||||
            color_mode,
 | 
			
		||||
            screen,
 | 
			
		||||
            fps: 60.0,
 | 
			
		||||
            framerate_sampling_start: Instant::now(),
 | 
			
		||||
            frame_count: 0,
 | 
			
		||||
            frame_skip: 0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RetroCallbacks for AnsiVideoComponent {
 | 
			
		||||
    fn video_refresh(&mut self, frame: &VideoFrame) {
 | 
			
		||||
        match frame {
 | 
			
		||||
            VideoFrame::XRGB1555 { width, height, .. }
 | 
			
		||||
            | VideoFrame::RGB565 { width, height, .. }
 | 
			
		||||
            | VideoFrame::XRGB8888 { width, height, .. } => {
 | 
			
		||||
                let (bytes, pitch) = frame.data_pitch_as_bytes().unwrap();
 | 
			
		||||
                let pitch = pitch as u32;
 | 
			
		||||
                let format = match frame.pixel_format().unwrap() {
 | 
			
		||||
                    PixelFormat::ARGB1555 => sdl2::pixels::PixelFormatEnum::RGB555,
 | 
			
		||||
                    PixelFormat::ARGB8888 => sdl2::pixels::PixelFormatEnum::ARGB8888,
 | 
			
		||||
                    PixelFormat::RGB565 => sdl2::pixels::PixelFormatEnum::RGB565,
 | 
			
		||||
                };
 | 
			
		||||
                // dirty, but must be &mut for SDL API.
 | 
			
		||||
                // safety: we don't actually mutate or leak the Surface we construct here.
 | 
			
		||||
                let data = unsafe {
 | 
			
		||||
                    core::slice::from_raw_parts_mut(bytes.as_ptr() as *mut u8, bytes.len())
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                // has the screen size changed?
 | 
			
		||||
                let (w, h) = terminal_size().unwrap_or((80, 24));
 | 
			
		||||
                let (w, h) = (w as u32, h as u32);
 | 
			
		||||
                let force_redraw = if self.terminal_width != w || self.terminal_height != h {
 | 
			
		||||
                    self.terminal_width = w as u32;
 | 
			
		||||
                    self.terminal_height = h as u32;
 | 
			
		||||
                    self.frame_skip = 0;
 | 
			
		||||
                    write!(self.screen, "{}", termion::clear::All).unwrap();
 | 
			
		||||
                    true
 | 
			
		||||
                } else {
 | 
			
		||||
                    false
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                if !force_redraw {
 | 
			
		||||
                    if self.frame_skip != 0 && self.frame_count % self.frame_skip != 0 {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if let Ok(surf) = Surface::from_data(data, *width, *height, pitch, format) {
 | 
			
		||||
                    let rgba_raw = surf.into_canvas().unwrap().read_pixels(None, PixelFormatEnum::ABGR8888).unwrap();
 | 
			
		||||
                    let rgba_img = RgbaImage::from_raw(*width, *height, rgba_raw).unwrap();
 | 
			
		||||
 | 
			
		||||
                    let processor = ProcessorPipeline {
 | 
			
		||||
                        filter: FilterType::Hamming,
 | 
			
		||||
                        width: self.needs_width(),
 | 
			
		||||
                        height: self.needs_height(),
 | 
			
		||||
                        color_modes: Some(self.needs_color()).into_iter().collect(),
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    let processed = processor.process(&rgba_img).into_iter().next().unwrap().1;
 | 
			
		||||
                    write!(self.screen, "{}", termion::cursor::Goto(1, 1)).unwrap();
 | 
			
		||||
                    for line in self.encode_frame(&processed).lines() {
 | 
			
		||||
                        write!(self.screen, "{}\r\n", line).unwrap();
 | 
			
		||||
                    }
 | 
			
		||||
                    write!(self.screen, "\x1B[0m").unwrap();
 | 
			
		||||
                    self.screen.flush().unwrap();
 | 
			
		||||
                } else if force_redraw {
 | 
			
		||||
                    // TODO: draw last copy
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _ => {}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_variable(&mut self, key: &str) -> Option<String> {
 | 
			
		||||
        match key {
 | 
			
		||||
            "parallel-n64-gfxplugin" => Some("angrylion".to_string()),
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RetroComponent for AnsiVideoComponent {
 | 
			
		||||
    fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow {
 | 
			
		||||
        self.frame_count += 1;
 | 
			
		||||
        if self.frame_skip < 10 && self.frame_count > 10 {
 | 
			
		||||
            let now = Instant::now();
 | 
			
		||||
            let period = now.duration_since(self.framerate_sampling_start).as_secs_f32();
 | 
			
		||||
            let actual_fps = self.frame_count as f32 / period;
 | 
			
		||||
            if actual_fps < self.fps * 2.0 / 3.0 {
 | 
			
		||||
                self.frame_skip += 1;
 | 
			
		||||
                self.frame_count = 0;
 | 
			
		||||
                self.framerate_sampling_start = now;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ControlFlow::Continue
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> ferretro_components::base::Result<()> {
 | 
			
		||||
        self.fps = retro.get_system_av_info().timing.fps as f32;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct TermiosInputComponent {
 | 
			
		||||
    //reader: AsyncReader,
 | 
			
		||||
    receiver: Receiver<(Instant, Key)>,
 | 
			
		||||
    want_quit: bool,
 | 
			
		||||
    preferred_pad: Option<u32>,
 | 
			
		||||
    button_map: HashMap<Key, InputDeviceId>,
 | 
			
		||||
    axis_maps: [HashMap<Key, (i16, i16)>; 2],
 | 
			
		||||
    button_state: HashMap<InputDeviceId, (Instant, i16)>,
 | 
			
		||||
    axis_state: [(Instant, [i16; 2]); 2],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for TermiosInputComponent {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        let (sender, receiver) = std::sync::mpsc::channel();
 | 
			
		||||
        std::thread::spawn(move || {
 | 
			
		||||
            for k in std::io::stdin().keys().map(|kr| kr.unwrap()) {
 | 
			
		||||
                sender.send((Instant::now(), k)).unwrap();
 | 
			
		||||
                if k == Key::Esc {
 | 
			
		||||
                    break
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        TermiosInputComponent {
 | 
			
		||||
            //reader: termion::async_stdin(),
 | 
			
		||||
            receiver,
 | 
			
		||||
            want_quit: false,
 | 
			
		||||
            preferred_pad: None,
 | 
			
		||||
            button_map: [
 | 
			
		||||
                (Key::Up, InputDeviceId::Joypad(JoypadButton::Up)),
 | 
			
		||||
                (Key::Down, InputDeviceId::Joypad(JoypadButton::Down)),
 | 
			
		||||
                (Key::Left, InputDeviceId::Joypad(JoypadButton::Left)),
 | 
			
		||||
                (Key::Right, InputDeviceId::Joypad(JoypadButton::Right)),
 | 
			
		||||
                (Key::Char('x'), InputDeviceId::Joypad(JoypadButton::A)),
 | 
			
		||||
                (Key::Char('z'), InputDeviceId::Joypad(JoypadButton::B)),
 | 
			
		||||
                (Key::Char('s'), InputDeviceId::Joypad(JoypadButton::X)),
 | 
			
		||||
                (Key::Char('a'), InputDeviceId::Joypad(JoypadButton::Y)),
 | 
			
		||||
                (Key::Ctrl('x'), InputDeviceId::Joypad(JoypadButton::A)),
 | 
			
		||||
                (Key::Ctrl('z'), InputDeviceId::Joypad(JoypadButton::B)),
 | 
			
		||||
                (Key::Ctrl('s'), InputDeviceId::Joypad(JoypadButton::X)),
 | 
			
		||||
                (Key::Ctrl('a'), InputDeviceId::Joypad(JoypadButton::Y)),
 | 
			
		||||
                (Key::Alt('x'), InputDeviceId::Joypad(JoypadButton::A)),
 | 
			
		||||
                (Key::Alt('z'), InputDeviceId::Joypad(JoypadButton::B)),
 | 
			
		||||
                (Key::Alt('s'), InputDeviceId::Joypad(JoypadButton::X)),
 | 
			
		||||
                (Key::Alt('a'), InputDeviceId::Joypad(JoypadButton::Y)),
 | 
			
		||||
                (Key::Char('q'), InputDeviceId::Joypad(JoypadButton::L)),
 | 
			
		||||
                (Key::Char('w'), InputDeviceId::Joypad(JoypadButton::R)),
 | 
			
		||||
                (Key::Ctrl('q'), InputDeviceId::Joypad(JoypadButton::L2)),
 | 
			
		||||
                (Key::Ctrl('w'), InputDeviceId::Joypad(JoypadButton::R2)),
 | 
			
		||||
                (Key::Alt('q'), InputDeviceId::Joypad(JoypadButton::L3)),
 | 
			
		||||
                (Key::Alt('w'), InputDeviceId::Joypad(JoypadButton::R3)),
 | 
			
		||||
                (Key::Char('\n'), InputDeviceId::Joypad(JoypadButton::Start)),
 | 
			
		||||
                (Key::Backspace, InputDeviceId::Joypad(JoypadButton::Select)),
 | 
			
		||||
                (Key::Char('\t'), InputDeviceId::Joypad(JoypadButton::Select)),
 | 
			
		||||
            ].into_iter().collect(),
 | 
			
		||||
            axis_maps: [
 | 
			
		||||
                [
 | 
			
		||||
                    (Key::Char('1'), (i16::MIN, i16::MAX)),
 | 
			
		||||
                    (Key::Char('2'), (0, i16::MAX)),
 | 
			
		||||
                    (Key::Char('3'), (i16::MAX, i16::MAX)),
 | 
			
		||||
                    (Key::Char('4'), (i16::MIN, 0)),
 | 
			
		||||
                    (Key::Char('5'), (0, 0)),
 | 
			
		||||
                    (Key::Char('6'), (i16::MAX, 0)),
 | 
			
		||||
                    (Key::Char('7'), (i16::MIN + 1, i16::MIN + 1)), // ???
 | 
			
		||||
                    (Key::Char('8'), (0, i16::MIN)),
 | 
			
		||||
                    (Key::Char('9'), (i16::MAX, i16::MIN)),
 | 
			
		||||
                ].into_iter().collect(),
 | 
			
		||||
                [
 | 
			
		||||
                    (Key::Char('r'), (0, i16::MIN)),
 | 
			
		||||
                    (Key::Char('d'), (0, i16::MAX)),
 | 
			
		||||
                    (Key::Char('e'), (i16::MIN, 0)),
 | 
			
		||||
                    (Key::Char('f'), (i16::MAX, 0)),
 | 
			
		||||
                ].into_iter().collect(),
 | 
			
		||||
            ],
 | 
			
		||||
            button_state: Default::default(),
 | 
			
		||||
            axis_state: [(Instant::now(), [0, 0]); 2],
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RetroCallbacks for TermiosInputComponent {
 | 
			
		||||
    fn input_poll(&mut self) {
 | 
			
		||||
        while let Ok((now, k)) = self.receiver.try_recv() {
 | 
			
		||||
            if k == Key::Esc {
 | 
			
		||||
                self.want_quit = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if let Some(mapping) = self.button_map.get(&k) {
 | 
			
		||||
                self.button_state.insert(mapping.to_owned(), (now, 1));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (axis_map, axis_state) in self.axis_maps.iter().zip(self.axis_state.iter_mut()) {
 | 
			
		||||
                let (mut sum_x, mut sum_y) = (0, 0);
 | 
			
		||||
                let mut count = 0;
 | 
			
		||||
                if let Some((x, y)) = axis_map.get(&k) {
 | 
			
		||||
                    sum_x += *x as i32;
 | 
			
		||||
                    sum_y += *y as i32;
 | 
			
		||||
                    count += 1;
 | 
			
		||||
                }
 | 
			
		||||
                if count != 0 {
 | 
			
		||||
                    let average_x = (sum_x / count) as i16;
 | 
			
		||||
                    let average_y = (sum_y / count) as i16;
 | 
			
		||||
                    *axis_state = (now, [average_x, average_y]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn input_state(&mut self, port: u32, device: InputDeviceId, index: InputIndex) -> i16 {
 | 
			
		||||
        if port != 0 {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        if let Some((inst, val)) = self.button_state.get(&device) {
 | 
			
		||||
            // TODO: consult kbdrate.c (and X11?) for durations (can't detect key-up/held, only repeat)
 | 
			
		||||
            if Instant::now().duration_since(*inst) < Duration::from_millis(300) {
 | 
			
		||||
                return *val;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        match device {
 | 
			
		||||
            InputDeviceId::Analog(axis_id) => {
 | 
			
		||||
                let (inst, axes) = self.axis_state[index as u32 as usize];
 | 
			
		||||
                let since = Instant::now().duration_since(inst);
 | 
			
		||||
                let ratio = if since < Duration::from_millis(100) {
 | 
			
		||||
                    1.0
 | 
			
		||||
                } else if since < Duration::from_millis(300) {
 | 
			
		||||
                    (0.3 - since.as_secs_f32()) * 5.0
 | 
			
		||||
                } else {
 | 
			
		||||
                    0.0
 | 
			
		||||
                };
 | 
			
		||||
                (axes[axis_id as u32 as usize] as f32 * ratio) as i16
 | 
			
		||||
            }
 | 
			
		||||
            // TODO: mouse?
 | 
			
		||||
            _ => 0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_variable(&mut self, key: &str) -> Option<String> {
 | 
			
		||||
        match key {
 | 
			
		||||
            "beetle_saturn_analog_stick_deadzone" => Some("15%".to_string()),
 | 
			
		||||
            "parallel-n64-astick-deadzone" => Some("15%".to_string()),
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_input_device_capabilities(&mut self) -> Option<u64> {
 | 
			
		||||
        let bits = (1 << (DeviceType::Joypad as u32)) | (1 << (DeviceType::Analog as u32));
 | 
			
		||||
        Some(bits as u64)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_controller_info(&mut self, controller_info: &[ControllerDescription2]) -> Option<bool> {
 | 
			
		||||
        for ci in controller_info {
 | 
			
		||||
            // so we can have analog support in beetle/mednafen saturn
 | 
			
		||||
            if ci.name.as_str() == "3D Control Pad" {
 | 
			
		||||
                self.preferred_pad = Some(ci.device_id());
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Some(true)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RetroComponent for TermiosInputComponent {
 | 
			
		||||
    fn post_run(&mut self, _: &mut LibretroWrapper) -> ControlFlow {
 | 
			
		||||
        if self.want_quit {
 | 
			
		||||
            ControlFlow::Break
 | 
			
		||||
        } else {
 | 
			
		||||
            ControlFlow::Continue
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> ferretro_components::base::Result<()> {
 | 
			
		||||
        if let Some(device) = self.preferred_pad {
 | 
			
		||||
            retro.set_controller_port_device(0, device);
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | 
			
		||||
    let opt: Opt = Opt::from_args();
 | 
			
		||||
    let mut emu = RetroComponentBase::new(&opt.core);
 | 
			
		||||
    emu.register_component(ansi_video::AnsiVideoComponent::default())?;
 | 
			
		||||
    emu.register_component(term_input::TermiosInputComponent::default())?;
 | 
			
		||||
    emu.register_component(AnsiVideoComponent::default())?;
 | 
			
		||||
    emu.register_component(TermiosInputComponent::default())?;
 | 
			
		||||
    emu.register_component(StatefulInputComponent::default())?;
 | 
			
		||||
    emu.register_component(PathBufComponent {
 | 
			
		||||
        sys_path: opt.system.clone(),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,187 +0,0 @@
 | 
			
		|||
use std::collections::HashMap;
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
use std::sync::mpsc::Receiver;
 | 
			
		||||
use std::time::{Duration, Instant};
 | 
			
		||||
 | 
			
		||||
use ferretro_components::base::ControlFlow;
 | 
			
		||||
use ferretro_components::prelude::*;
 | 
			
		||||
 | 
			
		||||
use termion::event::Key;
 | 
			
		||||
use termion::input::TermRead;
 | 
			
		||||
 | 
			
		||||
pub struct TermiosInputComponent {
 | 
			
		||||
    //reader: AsyncReader,
 | 
			
		||||
    receiver: Receiver<(Instant, Key)>,
 | 
			
		||||
    want_quit: bool,
 | 
			
		||||
    preferred_pad: Option<u32>,
 | 
			
		||||
    button_map: HashMap<Key, InputDeviceId>,
 | 
			
		||||
    axis_maps: [HashMap<Key, (i16, i16)>; 2],
 | 
			
		||||
    button_state: HashMap<InputDeviceId, (Instant, i16)>,
 | 
			
		||||
    axis_state: [(Instant, [i16; 2]); 2],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for TermiosInputComponent {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        let (sender, receiver) = std::sync::mpsc::channel();
 | 
			
		||||
        std::thread::spawn(move || {
 | 
			
		||||
            for k in std::io::stdin().keys().map(|kr| kr.unwrap()) {
 | 
			
		||||
                sender.send((Instant::now(), k)).unwrap();
 | 
			
		||||
                if k == Key::Esc {
 | 
			
		||||
                    break
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        TermiosInputComponent {
 | 
			
		||||
            //reader: termion::async_stdin(),
 | 
			
		||||
            receiver,
 | 
			
		||||
            want_quit: false,
 | 
			
		||||
            preferred_pad: None,
 | 
			
		||||
            button_map: [
 | 
			
		||||
                (Key::Up, InputDeviceId::Joypad(JoypadButton::Up)),
 | 
			
		||||
                (Key::Down, InputDeviceId::Joypad(JoypadButton::Down)),
 | 
			
		||||
                (Key::Left, InputDeviceId::Joypad(JoypadButton::Left)),
 | 
			
		||||
                (Key::Right, InputDeviceId::Joypad(JoypadButton::Right)),
 | 
			
		||||
                (Key::Char('x'), InputDeviceId::Joypad(JoypadButton::A)),
 | 
			
		||||
                (Key::Char('z'), InputDeviceId::Joypad(JoypadButton::B)),
 | 
			
		||||
                (Key::Char('s'), InputDeviceId::Joypad(JoypadButton::X)),
 | 
			
		||||
                (Key::Char('a'), InputDeviceId::Joypad(JoypadButton::Y)),
 | 
			
		||||
                (Key::Ctrl('x'), InputDeviceId::Joypad(JoypadButton::A)),
 | 
			
		||||
                (Key::Ctrl('z'), InputDeviceId::Joypad(JoypadButton::B)),
 | 
			
		||||
                (Key::Ctrl('s'), InputDeviceId::Joypad(JoypadButton::X)),
 | 
			
		||||
                (Key::Ctrl('a'), InputDeviceId::Joypad(JoypadButton::Y)),
 | 
			
		||||
                (Key::Alt('x'), InputDeviceId::Joypad(JoypadButton::A)),
 | 
			
		||||
                (Key::Alt('z'), InputDeviceId::Joypad(JoypadButton::B)),
 | 
			
		||||
                (Key::Alt('s'), InputDeviceId::Joypad(JoypadButton::X)),
 | 
			
		||||
                (Key::Alt('a'), InputDeviceId::Joypad(JoypadButton::Y)),
 | 
			
		||||
                (Key::Char('q'), InputDeviceId::Joypad(JoypadButton::L)),
 | 
			
		||||
                (Key::Char('w'), InputDeviceId::Joypad(JoypadButton::R)),
 | 
			
		||||
                (Key::Ctrl('q'), InputDeviceId::Joypad(JoypadButton::L2)),
 | 
			
		||||
                (Key::Ctrl('w'), InputDeviceId::Joypad(JoypadButton::R2)),
 | 
			
		||||
                (Key::Alt('q'), InputDeviceId::Joypad(JoypadButton::L3)),
 | 
			
		||||
                (Key::Alt('w'), InputDeviceId::Joypad(JoypadButton::R3)),
 | 
			
		||||
                (Key::Char('\n'), InputDeviceId::Joypad(JoypadButton::Start)),
 | 
			
		||||
                (Key::Backspace, InputDeviceId::Joypad(JoypadButton::Select)),
 | 
			
		||||
                (Key::Char('\t'), InputDeviceId::Joypad(JoypadButton::Select)),
 | 
			
		||||
            ].into_iter().collect(),
 | 
			
		||||
            axis_maps: [
 | 
			
		||||
                [
 | 
			
		||||
                    (Key::Char('1'), (i16::MIN, i16::MAX)),
 | 
			
		||||
                    (Key::Char('2'), (0, i16::MAX)),
 | 
			
		||||
                    (Key::Char('3'), (i16::MAX, i16::MAX)),
 | 
			
		||||
                    (Key::Char('4'), (i16::MIN, 0)),
 | 
			
		||||
                    (Key::Char('5'), (0, 0)),
 | 
			
		||||
                    (Key::Char('6'), (i16::MAX, 0)),
 | 
			
		||||
                    (Key::Char('7'), (i16::MIN + 1, i16::MIN + 1)), // ???
 | 
			
		||||
                    (Key::Char('8'), (0, i16::MIN)),
 | 
			
		||||
                    (Key::Char('9'), (i16::MAX, i16::MIN)),
 | 
			
		||||
                ].into_iter().collect(),
 | 
			
		||||
                [
 | 
			
		||||
                    (Key::Char('r'), (0, i16::MIN)),
 | 
			
		||||
                    (Key::Char('d'), (0, i16::MAX)),
 | 
			
		||||
                    (Key::Char('e'), (i16::MIN, 0)),
 | 
			
		||||
                    (Key::Char('f'), (i16::MAX, 0)),
 | 
			
		||||
                ].into_iter().collect(),
 | 
			
		||||
            ],
 | 
			
		||||
            button_state: Default::default(),
 | 
			
		||||
            axis_state: [(Instant::now(), [0, 0]); 2],
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RetroCallbacks for TermiosInputComponent {
 | 
			
		||||
    fn input_poll(&mut self) {
 | 
			
		||||
        while let Ok((now, k)) = self.receiver.try_recv() {
 | 
			
		||||
            if k == Key::Esc {
 | 
			
		||||
                self.want_quit = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if let Some(mapping) = self.button_map.get(&k) {
 | 
			
		||||
                self.button_state.insert(mapping.to_owned(), (now, 1));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (axis_map, axis_state) in self.axis_maps.iter().zip(self.axis_state.iter_mut()) {
 | 
			
		||||
                let (mut sum_x, mut sum_y) = (0, 0);
 | 
			
		||||
                let mut count = 0;
 | 
			
		||||
                if let Some((x, y)) = axis_map.get(&k) {
 | 
			
		||||
                    sum_x += *x as i32;
 | 
			
		||||
                    sum_y += *y as i32;
 | 
			
		||||
                    count += 1;
 | 
			
		||||
                }
 | 
			
		||||
                if count != 0 {
 | 
			
		||||
                    let average_x = (sum_x / count) as i16;
 | 
			
		||||
                    let average_y = (sum_y / count) as i16;
 | 
			
		||||
                    *axis_state = (now, [average_x, average_y]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn input_state(&mut self, port: u32, device: InputDeviceId, index: InputIndex) -> i16 {
 | 
			
		||||
        if port != 0 {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        if let Some((inst, val)) = self.button_state.get(&device) {
 | 
			
		||||
            // TODO: consult kbdrate.c (and X11?) for durations (can't detect key-up/held, only repeat)
 | 
			
		||||
            if Instant::now().duration_since(*inst) < Duration::from_millis(300) {
 | 
			
		||||
                return *val;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        match device {
 | 
			
		||||
            InputDeviceId::Analog(axis_id) => {
 | 
			
		||||
                let (inst, axes) = self.axis_state[index as u32 as usize];
 | 
			
		||||
                let since = Instant::now().duration_since(inst);
 | 
			
		||||
                let ratio = if since < Duration::from_millis(100) {
 | 
			
		||||
                    1.0
 | 
			
		||||
                } else if since < Duration::from_millis(300) {
 | 
			
		||||
                    (0.3 - since.as_secs_f32()) * 5.0
 | 
			
		||||
                } else {
 | 
			
		||||
                    0.0
 | 
			
		||||
                };
 | 
			
		||||
                (axes[axis_id as u32 as usize] as f32 * ratio) as i16
 | 
			
		||||
            }
 | 
			
		||||
            // TODO: mouse?
 | 
			
		||||
            _ => 0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_variable(&mut self, key: &str) -> Option<String> {
 | 
			
		||||
        match key {
 | 
			
		||||
            "beetle_saturn_analog_stick_deadzone" => Some("15%".to_string()),
 | 
			
		||||
            "parallel-n64-astick-deadzone" => Some("15%".to_string()),
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_input_device_capabilities(&mut self) -> Option<u64> {
 | 
			
		||||
        let bits = (1 << (DeviceType::Joypad as u32)) | (1 << (DeviceType::Analog as u32));
 | 
			
		||||
        Some(bits as u64)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_controller_info(&mut self, controller_info: &[ControllerDescription2]) -> Option<bool> {
 | 
			
		||||
        for ci in controller_info {
 | 
			
		||||
            // so we can have analog support in beetle/mednafen saturn
 | 
			
		||||
            if ci.name.as_str() == "3D Control Pad" {
 | 
			
		||||
                self.preferred_pad = Some(ci.device_id());
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Some(true)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RetroComponent for TermiosInputComponent {
 | 
			
		||||
    fn post_run(&mut self, _: &mut LibretroWrapper) -> ControlFlow {
 | 
			
		||||
        if self.want_quit {
 | 
			
		||||
            ControlFlow::Break
 | 
			
		||||
        } else {
 | 
			
		||||
            ControlFlow::Continue
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> ferretro_components::base::Result<()> {
 | 
			
		||||
        if let Some(device) = self.preferred_pad {
 | 
			
		||||
            retro.set_controller_port_device(0, device);
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in a new issue