1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use std::io::{self, Read};
use std::fs::{File, OpenOptions};
use std::os::unix::io::AsRawFd;
use crate::ffi;
use crate::vt::{Vt, VtNumber, AsVtNumber};
/// Handle to a console device file, usually located at `/dev/console`.
/// This structure allows managing virtual terminals.
pub struct Console {
file: File
}
impl Console {
/// Opens a new handle to the console device file.
pub fn open() -> Result<Console, io::Error> {
OpenOptions::new()
.read(true)
.write(true)
.open("/dev/console")
.map(|file| Console { file })
}
/// Returns the currently active virtual terminal.
pub fn current_vt_number(&self) -> io::Result<VtNumber>{
let vtstate = ffi::vt_getstate(self.file.as_raw_fd())?;
Ok(VtNumber::new(vtstate.v_active.into()))
}
/// Allocates a new virtual terminal.
/// To switch to the newly created terminal, use [`Vt::switch`] or [`Console::switch_to`].
///
/// [`Console::switch_to`]: crate::Console::switch_to
/// [`Vt::switch`]: crate::Vt::switch
pub fn new_vt(&self) -> io::Result<Vt> {
self.new_vt_with_minimum_number(0)
}
/// Allocates a new virtual terminal with a number greater than or equal to the given number.
/// Be careful not to exaggerate too much with the minimum threshold: usually systems have
/// a maximum number of 16 or 64 vts.
///
/// To switch to the newly created terminal, use [`Vt::switch`] or [`Console::switch_to`].
///
/// [`Console::switch_to`]: crate::Console::switch_to
/// [`Vt::switch`]: crate::Vt::switch
pub fn new_vt_with_minimum_number(&self, min: i32) -> io::Result<Vt> {
// Get the first available vt number
let mut n = ffi::vt_openqry(self.file.as_raw_fd())? as i32;
let vt: Vt;
if n >= min {
vt = Vt::with_number(self, n.into())?;
} else {
n = min;
// Fast path: the kernel provides a quick way to get the state of the first 16 vts
// by returning a mask with 1s indicating the ones in use.
let vtstate = ffi::vt_getstate(self.file.as_raw_fd())?;
let mut found = false;
let mut mask = 1 << n;
while n < 16 {
if vtstate.v_state & mask == 0 {
found = true;
break;
}
n += 1;
mask <<= 1;
}
if found {
vt = Vt::with_number(self, n.into())?;
} else {
// Slow path: we might be unlucky, and all the first 16 vts are already occupied.
// This should never happen in a real case, but better safe than sorry.
//
// Here the kernel does not help us and we have to test each single vt one by one:
// by issuing a VT_OPENQRY ioctl we can get back the first free vt.
// We keep opening file descriptors until the next free vt is greater than `min`.
//
// I don't have words to describe how ugly and problematic this is,
// but it's the only stable working solution I found. I seriously hope that this will never be needed.
let mut files: Vec<File> = Vec::new();
let mut first_free = 0;
while first_free < n {
first_free = ffi::vt_openqry(self.file.as_raw_fd())? as i32;
files.push(OpenOptions::new().read(true).write(true).open(format!("/dev/tty{}", first_free))?);
}
n = first_free;
vt = Vt::with_number_and_file(self, n.into(), files.pop().unwrap())?;
}
}
Ok(vt)
}
/// Releases the kernel resources for the terminal with the given number.
pub(crate) fn disallocate_vt<N:AsVtNumber>(&self, vt_number: N) -> io::Result<()> {
ffi::vt_disallocate(self.file.as_raw_fd(), vt_number.as_vt_number().as_native())
}
/// Opens the terminal with the given number.
pub fn open_vt<N: AsVtNumber>(&self, vt_number: N) -> io::Result<Vt> {
Vt::with_number(self, vt_number.as_vt_number())
}
/// Switches to the virtual terminal with the given number.
pub fn switch_to<N: AsVtNumber>(&self, vt_number: N) -> io::Result<()> {
let n = vt_number.as_vt_number().as_native();
ffi::vt_activate(self.file.as_raw_fd(), n)?;
ffi::vt_waitactive(self.file.as_raw_fd(), n)
}
/// Enables or disables virtual terminal switching (usually done with `Ctrl + Alt + F<n>`).
pub fn lock_switch(&self, lock: bool) -> io::Result<()> {
if lock {
ffi::vt_lockswitch(self.file.as_raw_fd(), 1)
} else {
ffi::vt_unlockswitch(self.file.as_raw_fd(), 1)
}
}
/// Returns the current console blank timer value. A value of `0` means that the timer is disabled.
/// To change the blank timer, use the [`Vt::set_blank_timer`] method.
///
/// [`Vt::set_blank_timer`]: crate::Vt::set_blank_timer
pub fn blank_timer(&self) -> io::Result<u32> {
OpenOptions::new().read(true).open("/sys/module/kernel/parameters/consoleblank")
.and_then(|mut f| {
let mut s = String::new();
f.read_to_string(&mut s).map(|_| s.trim().parse().expect("Expected consoleblank to contain an unsigned integer"))
})
}
}