Krolyxon
Commit a0582ea

rmptuia0582ea

get basic tui working with Ratatui

Author
krolxon
Date
2024-04-23 16:10 +0530
Parent
a0a3139
Commit
a0582ead78fda02e4137a82e100963e88362f252

+942 −57

13 files changed

  1. M Cargo.lock +482 −11
  2. M Cargo.toml +2 −0
  3. A src/app.rs +61 −0
  4. M src/cli.rs +22 −22
  5. M src/connection.rs +38 −8
  6. A src/error.rs +6 −0
  7. A src/event.rs +79 −0
  8. A src/handler.rs +47 −0
  9. A src/lib.rs +23 −0
  10. A src/list.rs +24 −0
  11. M src/main.rs +55 −16
  12. A src/tui.rs +66 −0
  13. A src/ui.rs +37 −0
M Cargo.lock View
@@ -2,6 +2,24 @@
22 # It is not intended for manual editing.
33 version = 3
44
5+[[package]]
6+name = "ahash"
7+version = "0.8.11"
8+source = "registry+https://github.com/rust-lang/crates.io-index"
9+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
10+dependencies = [
11+ "cfg-if",
12+ "once_cell",
13+ "version_check",
14+ "zerocopy",
15+]
16+
17+[[package]]
18+name = "allocator-api2"
19+version = "0.2.18"
20+source = "registry+https://github.com/rust-lang/crates.io-index"
21+checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
22+
523 [[package]]
624 name = "anstream"
725 version = "0.6.13"
@@ -37,7 +55,7 @@ version = "1.0.2"
3755 source = "registry+https://github.com/rust-lang/crates.io-index"
3856 checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
3957 dependencies = [
40− "windows-sys",
58+ "windows-sys 0.52.0",
4159 ]
4260
4361 [[package]]
@@ -47,15 +65,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
4765 checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
4866 dependencies = [
4967 "anstyle",
50− "windows-sys",
68+ "windows-sys 0.52.0",
5169 ]
5270
71+[[package]]
72+name = "autocfg"
73+version = "1.2.0"
74+source = "registry+https://github.com/rust-lang/crates.io-index"
75+checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
76+
77+[[package]]
78+name = "bitflags"
79+version = "1.3.2"
80+source = "registry+https://github.com/rust-lang/crates.io-index"
81+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
82+
83+[[package]]
84+name = "bitflags"
85+version = "2.5.0"
86+source = "registry+https://github.com/rust-lang/crates.io-index"
87+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
88+
5389 [[package]]
5490 name = "bufstream"
5591 version = "0.1.4"
5692 source = "registry+https://github.com/rust-lang/crates.io-index"
5793 checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
5894
95+[[package]]
96+name = "cassowary"
97+version = "0.3.0"
98+source = "registry+https://github.com/rust-lang/crates.io-index"
99+checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
100+
101+[[package]]
102+name = "castaway"
103+version = "0.2.2"
104+source = "registry+https://github.com/rust-lang/crates.io-index"
105+checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
106+dependencies = [
107+ "rustversion",
108+]
109+
110+[[package]]
111+name = "cfg-if"
112+version = "1.0.0"
113+source = "registry+https://github.com/rust-lang/crates.io-index"
114+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
115+
59116 [[package]]
60117 name = "clap"
61118 version = "4.5.4"
@@ -84,7 +141,7 @@ version = "4.5.4"
84141 source = "registry+https://github.com/rust-lang/crates.io-index"
85142 checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
86143 dependencies = [
87− "heck",
144+ "heck 0.5.0",
88145 "proc-macro2",
89146 "quote",
90147 "syn",
@@ -102,12 +159,136 @@ version = "1.0.0"
102159 source = "registry+https://github.com/rust-lang/crates.io-index"
103160 checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
104161
162+[[package]]
163+name = "compact_str"
164+version = "0.7.1"
165+source = "registry+https://github.com/rust-lang/crates.io-index"
166+checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
167+dependencies = [
168+ "castaway",
169+ "cfg-if",
170+ "itoa",
171+ "ryu",
172+ "static_assertions",
173+]
174+
175+[[package]]
176+name = "crossterm"
177+version = "0.27.0"
178+source = "registry+https://github.com/rust-lang/crates.io-index"
179+checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
180+dependencies = [
181+ "bitflags 2.5.0",
182+ "crossterm_winapi",
183+ "libc",
184+ "mio",
185+ "parking_lot",
186+ "signal-hook",
187+ "signal-hook-mio",
188+ "winapi",
189+]
190+
191+[[package]]
192+name = "crossterm_winapi"
193+version = "0.9.1"
194+source = "registry+https://github.com/rust-lang/crates.io-index"
195+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
196+dependencies = [
197+ "winapi",
198+]
199+
200+[[package]]
201+name = "either"
202+version = "1.11.0"
203+source = "registry+https://github.com/rust-lang/crates.io-index"
204+checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
205+
206+[[package]]
207+name = "hashbrown"
208+version = "0.14.3"
209+source = "registry+https://github.com/rust-lang/crates.io-index"
210+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
211+dependencies = [
212+ "ahash",
213+ "allocator-api2",
214+]
215+
216+[[package]]
217+name = "heck"
218+version = "0.4.1"
219+source = "registry+https://github.com/rust-lang/crates.io-index"
220+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
221+
105222 [[package]]
106223 name = "heck"
107224 version = "0.5.0"
108225 source = "registry+https://github.com/rust-lang/crates.io-index"
109226 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
110227
228+[[package]]
229+name = "indoc"
230+version = "2.0.5"
231+source = "registry+https://github.com/rust-lang/crates.io-index"
232+checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
233+
234+[[package]]
235+name = "itertools"
236+version = "0.12.1"
237+source = "registry+https://github.com/rust-lang/crates.io-index"
238+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
239+dependencies = [
240+ "either",
241+]
242+
243+[[package]]
244+name = "itoa"
245+version = "1.0.11"
246+source = "registry+https://github.com/rust-lang/crates.io-index"
247+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
248+
249+[[package]]
250+name = "libc"
251+version = "0.2.153"
252+source = "registry+https://github.com/rust-lang/crates.io-index"
253+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
254+
255+[[package]]
256+name = "lock_api"
257+version = "0.4.11"
258+source = "registry+https://github.com/rust-lang/crates.io-index"
259+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
260+dependencies = [
261+ "autocfg",
262+ "scopeguard",
263+]
264+
265+[[package]]
266+name = "log"
267+version = "0.4.21"
268+source = "registry+https://github.com/rust-lang/crates.io-index"
269+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
270+
271+[[package]]
272+name = "lru"
273+version = "0.12.3"
274+source = "registry+https://github.com/rust-lang/crates.io-index"
275+checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc"
276+dependencies = [
277+ "hashbrown",
278+]
279+
280+[[package]]
281+name = "mio"
282+version = "0.8.11"
283+source = "registry+https://github.com/rust-lang/crates.io-index"
284+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
285+dependencies = [
286+ "libc",
287+ "log",
288+ "wasi",
289+ "windows-sys 0.48.0",
290+]
291+
111292 [[package]]
112293 name = "mpd"
113294 version = "0.1.0"
@@ -117,6 +298,41 @@ dependencies = [
117298 "bufstream",
118299 ]
119300
301+[[package]]
302+name = "once_cell"
303+version = "1.19.0"
304+source = "registry+https://github.com/rust-lang/crates.io-index"
305+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
306+
307+[[package]]
308+name = "parking_lot"
309+version = "0.12.1"
310+source = "registry+https://github.com/rust-lang/crates.io-index"
311+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
312+dependencies = [
313+ "lock_api",
314+ "parking_lot_core",
315+]
316+
317+[[package]]
318+name = "parking_lot_core"
319+version = "0.9.9"
320+source = "registry+https://github.com/rust-lang/crates.io-index"
321+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
322+dependencies = [
323+ "cfg-if",
324+ "libc",
325+ "redox_syscall",
326+ "smallvec",
327+ "windows-targets 0.48.5",
328+]
329+
330+[[package]]
331+name = "paste"
332+version = "1.0.14"
333+source = "registry+https://github.com/rust-lang/crates.io-index"
334+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
335+
120336 [[package]]
121337 name = "proc-macro2"
122338 version = "1.0.81"
@@ -135,12 +351,43 @@ dependencies = [
135351 "proc-macro2",
136352 ]
137353
354+[[package]]
355+name = "ratatui"
356+version = "0.26.2"
357+source = "registry+https://github.com/rust-lang/crates.io-index"
358+checksum = "a564a852040e82671dc50a37d88f3aa83bbc690dfc6844cfe7a2591620206a80"
359+dependencies = [
360+ "bitflags 2.5.0",
361+ "cassowary",
362+ "compact_str",
363+ "crossterm",
364+ "indoc",
365+ "itertools",
366+ "lru",
367+ "paste",
368+ "stability",
369+ "strum",
370+ "unicode-segmentation",
371+ "unicode-width",
372+]
373+
374+[[package]]
375+name = "redox_syscall"
376+version = "0.4.1"
377+source = "registry+https://github.com/rust-lang/crates.io-index"
378+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
379+dependencies = [
380+ "bitflags 1.3.2",
381+]
382+
138383 [[package]]
139384 name = "rmptui"
140385 version = "0.1.0"
141386 dependencies = [
142387 "clap",
388+ "crossterm",
143389 "mpd",
390+ "ratatui",
144391 "rust_fzf",
145392 "simple-dmenu",
146393 ]
@@ -151,18 +398,110 @@ version = "0.3.1"
151398 source = "registry+https://github.com/rust-lang/crates.io-index"
152399 checksum = "76a94b9c6a9880356cc6de038a52397509f8ac86a18c30d76da904b08fec2cb1"
153400
401+[[package]]
402+name = "rustversion"
403+version = "1.0.15"
404+source = "registry+https://github.com/rust-lang/crates.io-index"
405+checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47"
406+
407+[[package]]
408+name = "ryu"
409+version = "1.0.17"
410+source = "registry+https://github.com/rust-lang/crates.io-index"
411+checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
412+
413+[[package]]
414+name = "scopeguard"
415+version = "1.2.0"
416+source = "registry+https://github.com/rust-lang/crates.io-index"
417+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
418+
419+[[package]]
420+name = "signal-hook"
421+version = "0.3.17"
422+source = "registry+https://github.com/rust-lang/crates.io-index"
423+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
424+dependencies = [
425+ "libc",
426+ "signal-hook-registry",
427+]
428+
429+[[package]]
430+name = "signal-hook-mio"
431+version = "0.2.3"
432+source = "registry+https://github.com/rust-lang/crates.io-index"
433+checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
434+dependencies = [
435+ "libc",
436+ "mio",
437+ "signal-hook",
438+]
439+
440+[[package]]
441+name = "signal-hook-registry"
442+version = "1.4.2"
443+source = "registry+https://github.com/rust-lang/crates.io-index"
444+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
445+dependencies = [
446+ "libc",
447+]
448+
154449 [[package]]
155450 name = "simple-dmenu"
156451 version = "0.1.0"
157452 source = "registry+https://github.com/rust-lang/crates.io-index"
158453 checksum = "2f392360283341235b298e79f0360bd924db6e6ad1e0964cbf4c43ea79c237eb"
159454
455+[[package]]
456+name = "smallvec"
457+version = "1.13.2"
458+source = "registry+https://github.com/rust-lang/crates.io-index"
459+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
460+
461+[[package]]
462+name = "stability"
463+version = "0.2.0"
464+source = "registry+https://github.com/rust-lang/crates.io-index"
465+checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a"
466+dependencies = [
467+ "quote",
468+ "syn",
469+]
470+
471+[[package]]
472+name = "static_assertions"
473+version = "1.1.0"
474+source = "registry+https://github.com/rust-lang/crates.io-index"
475+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
476+
160477 [[package]]
161478 name = "strsim"
162479 version = "0.11.1"
163480 source = "registry+https://github.com/rust-lang/crates.io-index"
164481 checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
165482
483+[[package]]
484+name = "strum"
485+version = "0.26.2"
486+source = "registry+https://github.com/rust-lang/crates.io-index"
487+checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
488+dependencies = [
489+ "strum_macros",
490+]
491+
492+[[package]]
493+name = "strum_macros"
494+version = "0.26.2"
495+source = "registry+https://github.com/rust-lang/crates.io-index"
496+checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
497+dependencies = [
498+ "heck 0.4.1",
499+ "proc-macro2",
500+ "quote",
501+ "rustversion",
502+ "syn",
503+]
504+
166505 [[package]]
167506 name = "syn"
168507 version = "2.0.60"
@@ -180,19 +519,89 @@ version = "1.0.12"
180519 source = "registry+https://github.com/rust-lang/crates.io-index"
181520 checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
182521
522+[[package]]
523+name = "unicode-segmentation"
524+version = "1.11.0"
525+source = "registry+https://github.com/rust-lang/crates.io-index"
526+checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
527+
528+[[package]]
529+name = "unicode-width"
530+version = "0.1.11"
531+source = "registry+https://github.com/rust-lang/crates.io-index"
532+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
533+
183534 [[package]]
184535 name = "utf8parse"
185536 version = "0.2.1"
186537 source = "registry+https://github.com/rust-lang/crates.io-index"
187538 checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
188539
540+[[package]]
541+name = "version_check"
542+version = "0.9.4"
543+source = "registry+https://github.com/rust-lang/crates.io-index"
544+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
545+
546+[[package]]
547+name = "wasi"
548+version = "0.11.0+wasi-snapshot-preview1"
549+source = "registry+https://github.com/rust-lang/crates.io-index"
550+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
551+
552+[[package]]
553+name = "winapi"
554+version = "0.3.9"
555+source = "registry+https://github.com/rust-lang/crates.io-index"
556+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
557+dependencies = [
558+ "winapi-i686-pc-windows-gnu",
559+ "winapi-x86_64-pc-windows-gnu",
560+]
561+
562+[[package]]
563+name = "winapi-i686-pc-windows-gnu"
564+version = "0.4.0"
565+source = "registry+https://github.com/rust-lang/crates.io-index"
566+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
567+
568+[[package]]
569+name = "winapi-x86_64-pc-windows-gnu"
570+version = "0.4.0"
571+source = "registry+https://github.com/rust-lang/crates.io-index"
572+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
573+
574+[[package]]
575+name = "windows-sys"
576+version = "0.48.0"
577+source = "registry+https://github.com/rust-lang/crates.io-index"
578+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
579+dependencies = [
580+ "windows-targets 0.48.5",
581+]
582+
189583 [[package]]
190584 name = "windows-sys"
191585 version = "0.52.0"
192586 source = "registry+https://github.com/rust-lang/crates.io-index"
193587 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
194588 dependencies = [
195− "windows-targets",
589+ "windows-targets 0.52.5",
590+]
591+
592+[[package]]
593+name = "windows-targets"
594+version = "0.48.5"
595+source = "registry+https://github.com/rust-lang/crates.io-index"
596+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
597+dependencies = [
598+ "windows_aarch64_gnullvm 0.48.5",
599+ "windows_aarch64_msvc 0.48.5",
600+ "windows_i686_gnu 0.48.5",
601+ "windows_i686_msvc 0.48.5",
602+ "windows_x86_64_gnu 0.48.5",
603+ "windows_x86_64_gnullvm 0.48.5",
604+ "windows_x86_64_msvc 0.48.5",
196605 ]
197606
198607 [[package]]
@@ -201,28 +610,46 @@ version = "0.52.5"
201610 source = "registry+https://github.com/rust-lang/crates.io-index"
202611 checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
203612 dependencies = [
204− "windows_aarch64_gnullvm",
205− "windows_aarch64_msvc",
206− "windows_i686_gnu",
613+ "windows_aarch64_gnullvm 0.52.5",
614+ "windows_aarch64_msvc 0.52.5",
615+ "windows_i686_gnu 0.52.5",
207616 "windows_i686_gnullvm",
208− "windows_i686_msvc",
209− "windows_x86_64_gnu",
210− "windows_x86_64_gnullvm",
211− "windows_x86_64_msvc",
617+ "windows_i686_msvc 0.52.5",
618+ "windows_x86_64_gnu 0.52.5",
619+ "windows_x86_64_gnullvm 0.52.5",
620+ "windows_x86_64_msvc 0.52.5",
212621 ]
213622
623+[[package]]
624+name = "windows_aarch64_gnullvm"
625+version = "0.48.5"
626+source = "registry+https://github.com/rust-lang/crates.io-index"
627+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
628+
214629 [[package]]
215630 name = "windows_aarch64_gnullvm"
216631 version = "0.52.5"
217632 source = "registry+https://github.com/rust-lang/crates.io-index"
218633 checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
219634
635+[[package]]
636+name = "windows_aarch64_msvc"
637+version = "0.48.5"
638+source = "registry+https://github.com/rust-lang/crates.io-index"
639+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
640+
220641 [[package]]
221642 name = "windows_aarch64_msvc"
222643 version = "0.52.5"
223644 source = "registry+https://github.com/rust-lang/crates.io-index"
224645 checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
225646
647+[[package]]
648+name = "windows_i686_gnu"
649+version = "0.48.5"
650+source = "registry+https://github.com/rust-lang/crates.io-index"
651+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
652+
226653 [[package]]
227654 name = "windows_i686_gnu"
228655 version = "0.52.5"
@@ -235,26 +662,70 @@ version = "0.52.5"
235662 source = "registry+https://github.com/rust-lang/crates.io-index"
236663 checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
237664
665+[[package]]
666+name = "windows_i686_msvc"
667+version = "0.48.5"
668+source = "registry+https://github.com/rust-lang/crates.io-index"
669+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
670+
238671 [[package]]
239672 name = "windows_i686_msvc"
240673 version = "0.52.5"
241674 source = "registry+https://github.com/rust-lang/crates.io-index"
242675 checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
243676
677+[[package]]
678+name = "windows_x86_64_gnu"
679+version = "0.48.5"
680+source = "registry+https://github.com/rust-lang/crates.io-index"
681+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
682+
244683 [[package]]
245684 name = "windows_x86_64_gnu"
246685 version = "0.52.5"
247686 source = "registry+https://github.com/rust-lang/crates.io-index"
248687 checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
249688
689+[[package]]
690+name = "windows_x86_64_gnullvm"
691+version = "0.48.5"
692+source = "registry+https://github.com/rust-lang/crates.io-index"
693+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
694+
250695 [[package]]
251696 name = "windows_x86_64_gnullvm"
252697 version = "0.52.5"
253698 source = "registry+https://github.com/rust-lang/crates.io-index"
254699 checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
255700
701+[[package]]
702+name = "windows_x86_64_msvc"
703+version = "0.48.5"
704+source = "registry+https://github.com/rust-lang/crates.io-index"
705+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
706+
256707 [[package]]
257708 name = "windows_x86_64_msvc"
258709 version = "0.52.5"
259710 source = "registry+https://github.com/rust-lang/crates.io-index"
260711 checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
712+
713+[[package]]
714+name = "zerocopy"
715+version = "0.7.32"
716+source = "registry+https://github.com/rust-lang/crates.io-index"
717+checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
718+dependencies = [
719+ "zerocopy-derive",
720+]
721+
722+[[package]]
723+name = "zerocopy-derive"
724+version = "0.7.32"
725+source = "registry+https://github.com/rust-lang/crates.io-index"
726+checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
727+dependencies = [
728+ "proc-macro2",
729+ "quote",
730+ "syn",
731+]
M Cargo.toml View
@@ -10,3 +10,5 @@ mpd = "0.1.0"
1010 clap = { version = "4.0.29", features = ["derive"] }
1111 rust_fzf = "0.3.1"
1212 simple-dmenu = "0.1.0"
13+ratatui = "0.26.2"
14+crossterm = "0.27.0"
A src/app.rs View
@@ -0,0 +1,61 @@
1+use crate::connection::Connection;
2+use crate::list::ContentList;
3+use std::collections::VecDeque;
4+
5+// Application result type
6+pub type AppResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
7+
8+/// Application
9+#[derive(Debug)]
10+pub struct App {
11+ /// check if app is running
12+ pub running: bool,
13+ pub conn: Connection,
14+ pub play_deque: VecDeque<String>,
15+ pub list: ContentList,
16+}
17+
18+impl App {
19+ pub fn new(addrs: &str) -> Self {
20+ let mut conn = Connection::new(addrs).unwrap();
21+ let mut vec: VecDeque<String> = VecDeque::new();
22+ Self::get_queue(&mut conn, &mut vec);
23+ Self {
24+ running: true,
25+ conn,
26+ play_deque: vec,
27+ list: ContentList::new(),
28+ }
29+ }
30+
31+ pub fn tick(&self) {}
32+
33+ pub fn quit(&mut self) {
34+ self.running = false;
35+ }
36+
37+ pub fn get_queue(conn: &mut Connection, vec: &mut VecDeque<String>) {
38+ conn.conn.queue().unwrap().into_iter().for_each(|x| {
39+ if let Some(title) = x.title {
40+ if let Some(artist) = x.artist {
41+ vec.push_back(format!("{} - {}", artist, title));
42+ } else {
43+ vec.push_back(title)
44+ }
45+ } else {
46+ vec.push_back(x.file)
47+ }
48+ });
49+ }
50+
51+ pub fn update_queue(&mut self) {
52+ self.play_deque.clear();
53+ Self::get_queue(&mut self.conn, &mut self.play_deque);
54+ }
55+}
56+
57+fn to_vecdeque(filenames: &Vec<String>) -> VecDeque<String> {
58+ let mut v: VecDeque<String> = VecDeque::new();
59+ v = filenames.iter().map(|x| x.to_string()).collect();
60+ v
61+}
M src/cli.rs View
@@ -1,28 +1,12 @@
11 use clap::{Parser, Subcommand};
2+
23 #[derive(Parser, Debug)]
3−#[command(version, about)]
4−#[clap(author = "krolyxon")]
4+#[clap(version, about, author = "krolyxon")]
55 /// MPD client made with Rust
66 pub struct Args {
7− /// pause
8− #[clap(short, long, default_value = "false")]
9− pub pause: bool,
10−
11− /// toggle pause
12− #[arg(short, long, default_value = "false")]
13− pub toggle_pause: bool,
14−
15− /// show current status
16− #[arg(short, long, default_value = "false")]
17− pub show_status: bool,
18−
19− /// use fzf selector for selecting songs
20− #[arg(short, long, default_value = "false")]
21− pub fzf_select: bool,
22−
23− /// use dmenu selector for selecting songss
24− #[arg(short, long, default_value = "false")]
25− pub dmenu_select: bool,
7+ /// No TUI
8+ #[arg(short= 'n', default_value="false")]
9+ pub no_tui: bool,
2610
2711 #[command(subcommand)]
2812 pub command: Command,
@@ -30,14 +14,30 @@ pub struct Args {
3014
3115 #[derive(Debug, Subcommand)]
3216 pub enum Command {
33− #[command(arg_required_else_help = true)]
17+ #[command(arg_required_else_help = true, long_flag = "volume" , short_flag = 'v')]
18+ /// Set Volume
3419 Volume {
3520 vol: String,
3621 },
3722
23+ /// Use dmenu for selection
24+ #[command(long_flag = "dmenu" , short_flag = 'd')]
3825 Dmenu,
26+
27+ /// Use Fzf for selection
28+ #[command(long_flag = "fzf" , short_flag = 'f')]
3929 Fzf,
30+
31+ /// Check Status
32+ #[command(long_flag = "status" , short_flag = 's')]
4033 Status,
34+
35+ /// Pause playback
36+ #[command(long_flag = "pause" , short_flag = 'p')]
4137 Pause,
38+
39+ /// Toggle Playback
40+ #[command(long_flag = "toggle" , short_flag = 't')]
4241 Toggle,
42+
4343 }
M src/connection.rs View
@@ -1,15 +1,20 @@
11 use mpd::song::Song;
22 use mpd::{Client, State};
33 use simple_dmenu::dmenu;
4+use std::process::Command;
45
6+pub type Result<T> = core::result::Result<T, Error>;
7+pub type Error = Box<dyn std::error::Error>;
8+
9+#[derive(Debug)]
510 pub struct Connection {
611 pub conn: Client,
712 pub songs_filenames: Vec<String>,
813 }
914
1015 impl Connection {
11− pub fn new(addrs: &str) -> Result<Self, mpd::error::Error> {
12− let mut conn = Client::connect(addrs)?;
16+ pub fn new(addrs: &str) -> Result<Self> {
17+ let mut conn = Client::connect(addrs).unwrap();
1318 let songs_filenames: Vec<String> = conn
1419 .listall()
1520 .unwrap()
@@ -23,23 +28,29 @@ impl Connection {
2328 })
2429 }
2530
26− pub fn play_fzf(&mut self) {
31+ pub fn play_fzf(&mut self) -> Result<()> {
32+ is_installed("fzf").map_err(|ex| ex)?;
33+
2734 let ss = &self.songs_filenames;
2835 let fzf_choice = rust_fzf::select(ss.clone(), Vec::new()).unwrap();
2936 let index = get_choice_index(&self.songs_filenames, fzf_choice.get(0).unwrap());
3037 let song = self.get_song_with_only_filename(ss.get(index).unwrap());
31− self.push(&song);
38+ self.push(&song)?;
39+
40+ Ok(())
3241 }
3342
34− pub fn play_dmenu(&mut self) {
43+ pub fn play_dmenu(&mut self) -> Result<()> {
44+ is_installed("dmenu").map_err(|ex| ex)?;
3545 let ss: Vec<&str> = self.songs_filenames.iter().map(|x| x.as_str()).collect();
3646 let op = dmenu!(iter &ss; args "-l", "30");
3747 let index = get_choice_index(&self.songs_filenames, &op);
3848 let song = self.get_song_with_only_filename(ss.get(index).unwrap());
39− self.push(&song);
49+ self.push(&song)?;
50+ Ok(())
4051 }
4152
42− fn push(&mut self, song: &Song) {
53+ pub fn push(&mut self, song: &Song) -> Result<()> {
4354 if self.conn.queue().unwrap().is_empty() {
4455 self.conn.push(song).unwrap();
4556 self.conn.play().unwrap();
@@ -50,9 +61,11 @@ impl Connection {
5061 }
5162 self.conn.next().unwrap();
5263 }
64+
65+ Ok(())
5366 }
5467
55− fn get_song_with_only_filename(&self, filename: &str) -> Song {
68+ pub fn get_song_with_only_filename(&self, filename: &str) -> Song {
5669 Song {
5770 file: filename.to_string(),
5871 artist: None,
@@ -66,6 +79,10 @@ impl Connection {
6679 }
6780 }
6881
82+ pub fn get_current_song(&mut self) -> Option<String> {
83+ self.conn.currentsong().unwrap().unwrap_or_default().title
84+
85+ }
6986 pub fn status(&mut self) {
7087 let current_song = self.conn.currentsong();
7188 let status = self.conn.status().unwrap();
@@ -112,3 +129,16 @@ fn get_choice_index(ss: &Vec<String>, selection: &str) -> usize {
112129
113130 choice
114131 }
132+
133+fn is_installed(ss: &str) -> Result<()> {
134+ let output = Command::new("which")
135+ .arg(ss)
136+ .output()
137+ .expect("Failed to execute command");
138+ if output.status.success() {
139+ Ok(())
140+ } else {
141+ let err = format!("{} not installed", ss);
142+ Err(err.into())
143+ }
144+}
A src/error.rs View
@@ -0,0 +1,6 @@
1+#[derive(Debug)]
2+pub enum Error {
3+ DmenuNotInstalled,
4+ FzfNotInstalled,
5+}
6+
A src/event.rs View
@@ -0,0 +1,79 @@
1+use crate::app::AppResult;
2+use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent};
3+use std::sync::mpsc;
4+use std::thread;
5+use std::time::{Duration, Instant};
6+
7+/// Terminal events.
8+#[derive(Clone, Copy, Debug)]
9+pub enum Event {
10+ /// Terminal tick.
11+ Tick,
12+ /// Key press.
13+ Key(KeyEvent),
14+ /// Mouse click/scroll.
15+ Mouse(MouseEvent),
16+ /// Terminal resize.
17+ Resize(u16, u16),
18+}
19+
20+/// Terminal event handler.
21+#[allow(dead_code)]
22+#[derive(Debug)]
23+pub struct EventHandler {
24+ /// Event sender channel.
25+ sender: mpsc::Sender<Event>,
26+ /// Event receiver channel.
27+ receiver: mpsc::Receiver<Event>,
28+ /// Event handler thread.
29+ handler: thread::JoinHandle<()>,
30+}
31+
32+impl EventHandler {
33+ /// Constructs a new instance of [`EventHandler`].
34+ pub fn new(tick_rate: u64) -> Self {
35+ let tick_rate = Duration::from_millis(tick_rate);
36+ let (sender, receiver) = mpsc::channel();
37+ let handler = {
38+ let sender = sender.clone();
39+ thread::spawn(move || {
40+ let mut last_tick = Instant::now();
41+ loop {
42+ let timeout = tick_rate
43+ .checked_sub(last_tick.elapsed())
44+ .unwrap_or(tick_rate);
45+
46+ if event::poll(timeout).expect("failed to poll new events") {
47+ match event::read().expect("unable to read event") {
48+ CrosstermEvent::Key(e) => sender.send(Event::Key(e)),
49+ CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)),
50+ CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)),
51+ CrosstermEvent::FocusGained => Ok(()),
52+ CrosstermEvent::FocusLost => Ok(()),
53+ CrosstermEvent::Paste(_) => unimplemented!(),
54+ }
55+ .expect("failed to send terminal event")
56+ }
57+
58+ if last_tick.elapsed() >= tick_rate {
59+ sender.send(Event::Tick).expect("failed to send tick event");
60+ last_tick = Instant::now();
61+ }
62+ }
63+ })
64+ };
65+ Self {
66+ sender,
67+ receiver,
68+ handler,
69+ }
70+ }
71+
72+ /// Receive the next event from the handler thread.
73+ ///
74+ /// This function will always block the current thread if
75+ /// there is no data available and it's possible for more data to be sent.
76+ pub fn next(&self) -> AppResult<Event> {
77+ Ok(self.receiver.recv()?)
78+ }
79+}
A src/handler.rs View
@@ -0,0 +1,47 @@
1+use crate::app::{App, AppResult};
2+use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
3+
4+pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
5+ match key_event.code {
6+ KeyCode::Char('q') | KeyCode::Esc => app.quit(),
7+ KeyCode::Char('c') | KeyCode::Char('C') => {
8+ if key_event.modifiers == KeyModifiers::CONTROL {
9+ app.quit();
10+ }
11+ }
12+
13+ KeyCode::Char('j') => {
14+ app.list.next();
15+ }
16+
17+ KeyCode::Char('k') => {
18+ app.list.prev();
19+ }
20+
21+ KeyCode::Enter | KeyCode::Char('l') => {
22+ let song = app.conn.get_song_with_only_filename(app.conn.songs_filenames.get(app.list.index).unwrap());
23+ app.conn.push(&song).unwrap();
24+ app.update_queue();
25+ }
26+
27+ // Playback controls
28+ // Toggle Pause
29+ KeyCode::Char('p') => {
30+ app.conn.toggle_pause();
31+ }
32+
33+ // Pause
34+ KeyCode::Char('s') => {
35+ app.conn.pause();
36+ }
37+
38+ // Clearn Queue
39+ KeyCode::Char('x') => {
40+ app.conn.conn.clear()?;
41+ app.update_queue();
42+ }
43+ _ => {}
44+ }
45+
46+ Ok(())
47+}
A src/lib.rs View
@@ -0,0 +1,23 @@
1+/// Command line interface (deprecated)
2+pub mod cli;
3+
4+/// Handle mpd connection
5+pub mod connection;
6+
7+/// Widget renderer
8+pub mod ui;
9+
10+/// Terminal user Interface
11+pub mod tui;
12+
13+/// Content list
14+pub mod list;
15+
16+/// Event Handler
17+pub mod event;
18+
19+/// KeyEvent Handler
20+pub mod handler;
21+
22+/// Application
23+pub mod app;
A src/list.rs View
@@ -0,0 +1,24 @@
1+#[derive(Debug)]
2+pub struct ContentList {
3+ pub index: usize
4+}
5+
6+impl ContentList {
7+ pub fn new() -> Self {
8+ ContentList {
9+ index: 0
10+ }
11+ }
12+
13+ // Go to next item in list
14+ pub fn next(&mut self) {
15+ self.index += 1;
16+ }
17+
18+ /// Go to previous item in list
19+ pub fn prev(&mut self) {
20+ if self.index != 0 {
21+ self.index -= 1;
22+ }
23+ }
24+}
M src/main.rs View
@@ -1,24 +1,63 @@
1−mod cli;
2−mod connection;
1+#![allow(unused_imports)]
32 use clap::Parser;
4−use cli::Args;
5−use cli::Command;
6−use connection::Connection;
3+use rmptui::app;
4+use rmptui::app::App;
5+use rmptui::app::AppResult;
6+use rmptui::cli::Args;
7+use rmptui::cli::Command;
8+use rmptui::connection::Connection;
9+use rmptui::event::Event;
10+use rmptui::event::EventHandler;
11+use rmptui::handler;
12+use rmptui::tui;
13+use std::io;
714
8−fn main() -> Result<(), Box<dyn std::error::Error>> {
9− let args = Args::parse();
10− let mut conn = Connection::new("127.0.0.1:6600")?;
15+use crossterm::event::{self, KeyCode, KeyEvent, KeyEventKind};
16+use ratatui::{
17+ prelude::*,
18+ symbols::border,
19+ widgets::{block::*, *},
20+};
1121
12− match args.command {
13− Command::Volume { vol } => {
14− conn.set_volume(vol);
22+pub type Result<T> = core::result::Result<T, Error>;
23+pub type Error = Box<dyn std::error::Error>;
24+
25+fn main() -> AppResult<()> {
26+ // let args = Args::parse();
27+
28+ // if args.no_tui {
29+ // handle_tui()?;
30+ // } else {
31+ // match args.command {
32+ // Command::Volume { vol } => {
33+ // conn.set_volume(vol);
34+ // }
35+ // Command::Dmenu => conn.play_dmenu().unwrap(),
36+ // Command::Fzf => conn.play_fzf().unwrap(),
37+ // Command::Status => conn.status(),
38+ // Command::Pause => conn.pause(),
39+ // Command::Toggle => conn.toggle_pause(),
40+ // };
41+ // }
42+
43+ let backend = CrosstermBackend::new(io::stderr());
44+ let terminal = Terminal::new(backend)?;
45+ let mut app = App::new("127.0.0.1:6600");
46+ let events = EventHandler::new(250);
47+
48+ let mut tui = tui::Tui::new(terminal, events);
49+ tui.init()?;
50+
51+ while app.running {
52+ tui.draw(&mut app)?;
53+ match tui.events.next()? {
54+ Event::Tick => app.tick(),
55+ Event::Key(key_event) => handler::handle_key_events(key_event, &mut app)?,
56+ Event::Mouse(_) => {}
57+ Event::Resize(_, _) => {}
1558 }
16− Command::Dmenu => conn.play_dmenu(),
17− Command::Fzf => conn.play_fzf(),
18− Command::Status => conn.status(),
19− Command::Pause => conn.pause(),
20− Command::Toggle => conn.toggle_pause(),
2159 }
2260
61+
2362 Ok(())
2463 }
A src/tui.rs View
@@ -0,0 +1,66 @@
1+use crate::connection::Connection;
2+use crate::ui;
3+use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
4+use crossterm::{
5+ terminal::{self, *},
6+};
7+use ratatui::prelude::*;
8+use std::io::{self, stdout, Stdout};
9+use std::panic;
10+
11+use crate::app::{App, AppResult};
12+use crate::event::EventHandler;
13+
14+pub type CrosstermTerminal = ratatui::Terminal<ratatui::backend::CrosstermBackend<std::io::Stderr>>;
15+
16+#[derive(Debug)]
17+pub struct Tui {
18+ terminal: CrosstermTerminal,
19+ pub events: EventHandler,
20+}
21+
22+impl Tui {
23+ pub fn new(terminal: CrosstermTerminal, events: EventHandler) -> Self {
24+ Self { terminal, events }
25+ }
26+
27+ pub fn init(&mut self) -> AppResult<()> {
28+ terminal::enable_raw_mode()?;
29+ crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?;
30+
31+ let panic_hook = panic::take_hook();
32+ panic::set_hook(Box::new(move |panic| {
33+ Self::reset().expect("failed to reset the terminal");
34+ panic_hook(panic);
35+ }));
36+ Ok(())
37+ }
38+
39+ /// [`Draw`] the terminal interface by [`rendering`] the widgets.
40+ ///
41+ /// [`Draw`]: ratatui::Terminal::draw
42+ /// [`rendering`]: crate::ui::render
43+ pub fn draw(&mut self, app: &mut App) -> AppResult<()> {
44+ self.terminal.draw(|frame| ui::render(app, frame))?;
45+ Ok(())
46+ }
47+
48+ /// Resets the terminal interface.
49+ ///
50+ /// This function is also used for the panic hook to revert
51+ /// the terminal properties if unexpected errors occur.
52+ fn reset() -> AppResult<()> {
53+ terminal::disable_raw_mode()?;
54+ crossterm::execute!(io::stderr(), LeaveAlternateScreen, DisableMouseCapture)?;
55+ Ok(())
56+ }
57+
58+ /// Exits the terminal interface.
59+ ///
60+ /// It disables the raw mode and reverts back the terminal properties.
61+ pub fn exit(&mut self) -> AppResult<()> {
62+ Self::reset()?;
63+ self.terminal.show_cursor()?;
64+ Ok(())
65+ }
66+}
A src/ui.rs View
@@ -0,0 +1,37 @@
1+use crate::{app::App, connection::Connection};
2+use ratatui::{prelude::*, widgets::*};
3+
4+/// Renders the user interface widgets
5+pub fn render(app: &mut App, frame: &mut Frame) {
6+ // This is where you add new widgets.
7+ // See the following resources:
8+ // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html
9+ // - https://github.com/ratatui-org/ratatui/tree/master/examples
10+
11+ // List of songs
12+ let mut state = ListState::default();
13+ let size = Rect::new(100, 0, frame.size().width, frame.size().height - 3);
14+ let list = List::new(app.conn.songs_filenames.clone())
15+ .block(Block::default().title("Song List").borders(Borders::ALL))
16+ .highlight_style(Style::new().add_modifier(Modifier::REVERSED))
17+ .highlight_symbol(">>")
18+ .repeat_highlight_symbol(true);
19+
20+ state.select(Some(app.list.index));
21+ frame.render_stateful_widget(list, size, &mut state);
22+
23+ // Play Queue
24+ let size = Rect::new(0, 0, 100, frame.size().height - 25);
25+ let list = List::new(app.play_deque.clone())
26+ .block(Block::default().title("Play Queue").borders(Borders::ALL))
27+ .highlight_style(Style::new().add_modifier(Modifier::REVERSED))
28+ .highlight_symbol(">>")
29+ .repeat_highlight_symbol(true);
30+ frame.render_widget(list, size);
31+
32+ // Status
33+ let size = Rect::new(0, frame.size().height - 3, frame.size().width, 3);
34+ let status = Paragraph::new(app.conn.conn.status().unwrap().volume.to_string())
35+ .block(Block::default().title("Status").borders(Borders::ALL));
36+ frame.render_widget(status, size);
37+}