@@ -5,7 +5,8 @@ use std::io;
5
5
use std:: io:: { BufRead , BufReader } ;
6
6
use std:: mem;
7
7
use std:: os:: unix:: io:: AsRawFd ;
8
- use std:: ptr;
8
+ use std:: os:: unix:: io:: FromRawFd ;
9
+ use std:: os:: unix:: io:: IntoRawFd ;
9
10
use std:: str;
10
11
11
12
use crate :: kb:: Key ;
@@ -360,3 +361,289 @@ pub fn wants_emoji() -> bool {
360
361
pub fn set_title < T : Display > ( title : T ) {
361
362
print ! ( "\x1b ]0;{}\x07 " , title) ;
362
363
}
364
+
365
+ fn with_raw_terminal < R > ( f : impl FnOnce ( & mut fs:: File ) -> R ) -> io:: Result < R > {
366
+ // We need a custom drop implementation for File,
367
+ // so that the fd for stdin does not get closed
368
+ enum CustomDropFile {
369
+ CloseFd ( Option < fs:: File > ) ,
370
+ NotCloseFd ( Option < fs:: File > ) ,
371
+ }
372
+
373
+ impl Drop for CustomDropFile {
374
+ fn drop ( & mut self ) {
375
+ match self {
376
+ CustomDropFile :: CloseFd ( _) => { }
377
+ CustomDropFile :: NotCloseFd ( inner) => {
378
+ if let Some ( file) = inner. take ( ) {
379
+ file. into_raw_fd ( ) ;
380
+ }
381
+ }
382
+ }
383
+ }
384
+ }
385
+
386
+ let ( mut tty_handle, tty_fd) = if unsafe { libc:: isatty ( libc:: STDIN_FILENO ) } == 1 {
387
+ (
388
+ CustomDropFile :: NotCloseFd ( Some ( unsafe { fs:: File :: from_raw_fd ( libc:: STDIN_FILENO ) } ) ) ,
389
+ libc:: STDIN_FILENO ,
390
+ )
391
+ } else {
392
+ let handle = fs:: OpenOptions :: new ( )
393
+ . read ( true )
394
+ . write ( true )
395
+ . open ( "/dev/tty" ) ?;
396
+ let fd = handle. as_raw_fd ( ) ;
397
+ ( CustomDropFile :: CloseFd ( Some ( handle) ) , fd)
398
+ } ;
399
+
400
+ // Get current mode
401
+ let mut termios = mem:: MaybeUninit :: uninit ( ) ;
402
+ c_result ( || unsafe { libc:: tcgetattr ( tty_fd, termios. as_mut_ptr ( ) ) } ) ?;
403
+
404
+ let mut termios = unsafe { termios. assume_init ( ) } ;
405
+ let old_iflag = termios. c_iflag ;
406
+ let old_oflag = termios. c_oflag ;
407
+ let old_cflag = termios. c_cflag ;
408
+ let old_lflag = termios. c_lflag ;
409
+
410
+ // Go into raw mode
411
+ unsafe { libc:: cfmakeraw ( & mut termios) } ;
412
+ if old_lflag & libc:: ISIG != 0 {
413
+ // Re-enable INTR, QUIT, SUSP, DSUSP, if it was activated before
414
+ termios. c_lflag |= libc:: ISIG ;
415
+ }
416
+ c_result ( || unsafe { libc:: tcsetattr ( tty_fd, libc:: TCSADRAIN , & termios) } ) ?;
417
+
418
+ let result = match & mut tty_handle {
419
+ CustomDropFile :: CloseFd ( Some ( handle) ) => f ( handle) ,
420
+ CustomDropFile :: NotCloseFd ( Some ( handle) ) => f ( handle) ,
421
+ _ => unreachable ! ( ) ,
422
+ } ;
423
+
424
+ // Reset to previous mode
425
+ termios. c_iflag = old_iflag;
426
+ termios. c_oflag = old_oflag;
427
+ termios. c_cflag = old_cflag;
428
+ termios. c_lflag = old_lflag;
429
+ c_result ( || unsafe { libc:: tcsetattr ( tty_fd, libc:: TCSADRAIN , & termios) } ) ?;
430
+
431
+ Ok ( result)
432
+ }
433
+
434
+ pub fn supports_synchronized_output ( ) -> bool {
435
+ * sync_output:: SUPPORTS_SYNCHRONIZED_OUTPUT
436
+ }
437
+
438
+ /// Specification: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
439
+ mod sync_output {
440
+ use std:: convert:: TryInto as _;
441
+ use std:: io:: Read as _;
442
+ use std:: io:: Write as _;
443
+ use std:: os:: unix:: io:: AsRawFd as _;
444
+ use std:: time;
445
+
446
+ use lazy_static:: lazy_static;
447
+
448
+ use super :: select_or_poll_term_fd;
449
+ use super :: with_raw_terminal;
450
+
451
+ const RESPONSE_TIMEOUT : time:: Duration = time:: Duration :: from_millis ( 10 ) ;
452
+
453
+ lazy_static ! {
454
+ pub ( crate ) static ref SUPPORTS_SYNCHRONIZED_OUTPUT : bool =
455
+ supports_synchronized_output_uncached( ) ;
456
+ }
457
+
458
+ struct ResponseParser {
459
+ state : ResponseParserState ,
460
+ response : u8 ,
461
+ }
462
+
463
+ #[ derive( PartialEq ) ]
464
+ enum ResponseParserState {
465
+ None ,
466
+ CsiOne ,
467
+ CsiTwo ,
468
+ QuestionMark ,
469
+ ModeDigit1 ,
470
+ ModeDigit2 ,
471
+ ModeDigit3 ,
472
+ ModeDigit4 ,
473
+ Semicolon ,
474
+ Response ,
475
+ DollarSign ,
476
+ Ypsilon ,
477
+ }
478
+
479
+ impl ResponseParser {
480
+ const fn new ( ) -> Self {
481
+ Self {
482
+ state : ResponseParserState :: None ,
483
+ response : u8:: MAX ,
484
+ }
485
+ }
486
+
487
+ fn process_byte ( & mut self , byte : u8 ) {
488
+ match byte {
489
+ b'\x1b' => {
490
+ self . state = ResponseParserState :: CsiOne ;
491
+ }
492
+ b'[' => {
493
+ self . state = if self . state == ResponseParserState :: CsiOne {
494
+ ResponseParserState :: CsiTwo
495
+ } else {
496
+ ResponseParserState :: None
497
+ } ;
498
+ }
499
+ b'?' => {
500
+ self . state = if self . state == ResponseParserState :: CsiTwo {
501
+ ResponseParserState :: QuestionMark
502
+ } else {
503
+ ResponseParserState :: None
504
+ } ;
505
+ }
506
+ byte @ b'0' => {
507
+ self . state = if self . state == ResponseParserState :: Semicolon {
508
+ self . response = byte;
509
+ ResponseParserState :: Response
510
+ } else if self . state == ResponseParserState :: ModeDigit1 {
511
+ ResponseParserState :: ModeDigit2
512
+ } else {
513
+ ResponseParserState :: None
514
+ } ;
515
+ }
516
+ byte @ b'2' => {
517
+ self . state = if self . state == ResponseParserState :: Semicolon {
518
+ self . response = byte;
519
+ ResponseParserState :: Response
520
+ } else if self . state == ResponseParserState :: QuestionMark {
521
+ ResponseParserState :: ModeDigit1
522
+ } else if self . state == ResponseParserState :: ModeDigit2 {
523
+ ResponseParserState :: ModeDigit3
524
+ } else {
525
+ ResponseParserState :: None
526
+ } ;
527
+ }
528
+ byte @ b'1' | byte @ b'3' | byte @ b'4' => {
529
+ self . state = if self . state == ResponseParserState :: Semicolon {
530
+ self . response = byte;
531
+ ResponseParserState :: Response
532
+ } else {
533
+ ResponseParserState :: None
534
+ } ;
535
+ }
536
+ b'6' => {
537
+ self . state = if self . state == ResponseParserState :: ModeDigit3 {
538
+ ResponseParserState :: ModeDigit4
539
+ } else {
540
+ ResponseParserState :: None
541
+ } ;
542
+ }
543
+ b';' => {
544
+ self . state = if self . state == ResponseParserState :: ModeDigit4 {
545
+ ResponseParserState :: Semicolon
546
+ } else {
547
+ ResponseParserState :: None
548
+ } ;
549
+ }
550
+ b'$' => {
551
+ self . state = if self . state == ResponseParserState :: Response {
552
+ ResponseParserState :: DollarSign
553
+ } else {
554
+ ResponseParserState :: None
555
+ } ;
556
+ }
557
+ b'y' => {
558
+ self . state = if self . state == ResponseParserState :: DollarSign {
559
+ ResponseParserState :: Ypsilon
560
+ } else {
561
+ ResponseParserState :: None
562
+ } ;
563
+ }
564
+ _ => {
565
+ self . state = ResponseParserState :: None ;
566
+ }
567
+ }
568
+ }
569
+
570
+ fn get_response ( & self ) -> Option < u8 > {
571
+ if self . state == ResponseParserState :: Ypsilon {
572
+ Some ( self . response - b'0' )
573
+ } else {
574
+ None
575
+ }
576
+ }
577
+ }
578
+
579
+ fn supports_synchronized_output_uncached ( ) -> bool {
580
+ with_raw_terminal ( |term_handle| {
581
+ // Query the state of the (DEC) mode 2026 (Synchronized Output)
582
+ write ! ( term_handle, "\x1b [?2026$p" ) . ok ( ) ?;
583
+ term_handle. flush ( ) . ok ( ) ?;
584
+
585
+ // Wait for response or timeout
586
+ let term_fd = term_handle. as_raw_fd ( ) ;
587
+ let mut parser = ResponseParser :: new ( ) ;
588
+ let mut buf = [ 0u8 ; 256 ] ;
589
+ let deadline = time:: Instant :: now ( ) + RESPONSE_TIMEOUT ;
590
+
591
+ loop {
592
+ let remaining_time = deadline
593
+ . saturating_duration_since ( time:: Instant :: now ( ) )
594
+ . as_millis ( )
595
+ . try_into ( )
596
+ . ok ( ) ?;
597
+
598
+ if remaining_time == 0 {
599
+ // Timeout
600
+ return Some ( false ) ;
601
+ }
602
+
603
+ match select_or_poll_term_fd ( term_fd, remaining_time) {
604
+ Ok ( false ) => {
605
+ // Timeout
606
+ return Some ( false ) ;
607
+ }
608
+ Ok ( true ) => {
609
+ ' read: loop {
610
+ match term_handle. read ( & mut buf) {
611
+ Ok ( 0 ) => {
612
+ // Reached EOF
613
+ return Some ( false ) ;
614
+ }
615
+ Ok ( size) => {
616
+ for byte in & buf[ ..size] {
617
+ parser. process_byte ( * byte) ;
618
+
619
+ match parser. get_response ( ) {
620
+ Some ( 1 ) | Some ( 2 ) => return Some ( true ) ,
621
+ Some ( _) => return Some ( false ) ,
622
+ None => { }
623
+ }
624
+ }
625
+
626
+ break ' read;
627
+ }
628
+ Err ( err) if err. kind ( ) == std:: io:: ErrorKind :: Interrupted => {
629
+ // Got interrupted, retry read
630
+ continue ' read;
631
+ }
632
+ Err ( _) => {
633
+ return Some ( false ) ;
634
+ }
635
+ }
636
+ }
637
+ }
638
+ Err ( _) => {
639
+ // Error
640
+ return Some ( false ) ;
641
+ }
642
+ }
643
+ }
644
+ } )
645
+ . ok ( )
646
+ . flatten ( )
647
+ . unwrap_or ( false )
648
+ }
649
+ }
0 commit comments