775 lines
21 KiB
Rust
775 lines
21 KiB
Rust
use log::info;
|
|
use std::ptr::{null, null_mut};
|
|
use std::{ffi::CString, rc::Rc};
|
|
|
|
use x11::xlib::{
|
|
self, AnyButton, AnyKey, AnyModifier, Atom, ButtonPressMask,
|
|
ButtonReleaseMask, CWEventMask, ControlMask, CurrentTime, EnterWindowMask,
|
|
FocusChangeMask, LockMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask,
|
|
Mod5Mask, PointerMotionMask, PropertyChangeMask, ShiftMask,
|
|
StructureNotifyMask, SubstructureNotifyMask, SubstructureRedirectMask,
|
|
Window, XCloseDisplay, XConfigureRequestEvent, XDefaultScreen, XEvent,
|
|
XGetTransientForHint, XGrabPointer, XInternAtom, XKillClient, XMapWindow,
|
|
XOpenDisplay, XRaiseWindow, XRootWindow, XSetErrorHandler, XSync,
|
|
XUngrabButton, XUngrabKey, XUngrabPointer, XWarpPointer, XWindowAttributes,
|
|
};
|
|
use xlib::GrabModeAsync;
|
|
|
|
use log::error;
|
|
|
|
use crate::clients::Client;
|
|
|
|
pub struct XLib {
|
|
display: Display,
|
|
root: Window,
|
|
_screen: i32,
|
|
atoms: Atoms,
|
|
global_keybinds: Vec<KeyOrButton>,
|
|
}
|
|
|
|
struct Atoms {
|
|
protocols: Atom,
|
|
delete_window: Atom,
|
|
active_window: Atom,
|
|
take_focus: Atom,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum KeyOrButton {
|
|
Key(i32, u32),
|
|
Button(u32, u32, u64),
|
|
}
|
|
|
|
impl KeyOrButton {
|
|
#[allow(dead_code)]
|
|
pub fn key(keycode: i32, modmask: u32) -> Self {
|
|
Self::Key(keycode, modmask)
|
|
}
|
|
pub fn button(button: u32, modmask: u32, buttonmask: i64) -> Self {
|
|
Self::Button(button, modmask, buttonmask as u64)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct Display(Rc<*mut xlib::Display>);
|
|
|
|
impl Drop for XLib {
|
|
fn drop(&mut self) {
|
|
self.close_dpy();
|
|
}
|
|
}
|
|
|
|
impl XLib {
|
|
pub fn new() -> Self {
|
|
let (display, _screen, root) = unsafe {
|
|
let display = XOpenDisplay(null());
|
|
|
|
assert_ne!(display, null_mut());
|
|
|
|
let display = Display::new(display);
|
|
let screen = XDefaultScreen(display.get());
|
|
let root = XRootWindow(display.get(), screen);
|
|
|
|
(display, screen, root)
|
|
};
|
|
|
|
Self {
|
|
atoms: Atoms::init(display.clone()),
|
|
global_keybinds: Vec::new(),
|
|
root,
|
|
_screen,
|
|
display,
|
|
}
|
|
}
|
|
|
|
pub fn init(&mut self) {
|
|
unsafe {
|
|
let mut window_attributes =
|
|
std::mem::MaybeUninit::<xlib::XSetWindowAttributes>::zeroed()
|
|
.assume_init();
|
|
|
|
window_attributes.event_mask = SubstructureRedirectMask
|
|
| StructureNotifyMask
|
|
| SubstructureNotifyMask
|
|
| EnterWindowMask
|
|
| PointerMotionMask
|
|
| ButtonPressMask;
|
|
|
|
xlib::XChangeWindowAttributes(
|
|
self.dpy(),
|
|
self.root,
|
|
CWEventMask,
|
|
&mut window_attributes,
|
|
);
|
|
|
|
xlib::XSelectInput(
|
|
self.dpy(),
|
|
self.root,
|
|
window_attributes.event_mask,
|
|
);
|
|
|
|
XSetErrorHandler(Some(xlib_error_handler));
|
|
|
|
XSync(self.dpy(), 0);
|
|
}
|
|
|
|
self.grab_global_keybinds(self.root);
|
|
}
|
|
|
|
pub fn add_global_keybind(&mut self, key: KeyOrButton) {
|
|
self.global_keybinds.push(key);
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn ungrab_global_keybings(&self, window: Window) {
|
|
unsafe {
|
|
XUngrabButton(self.dpy(), AnyButton as u32, AnyModifier, window);
|
|
XUngrabKey(self.dpy(), AnyKey, AnyModifier, window);
|
|
}
|
|
}
|
|
|
|
fn grab_global_keybinds(&self, window: Window) {
|
|
for kb in self.global_keybinds.iter() {
|
|
self.grab_key_or_button(window, kb);
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn remove_global_keybind(&mut self, key: &KeyOrButton) {
|
|
if self.global_keybinds.contains(key) {
|
|
self.global_keybinds.retain(|kb| kb != key);
|
|
}
|
|
}
|
|
|
|
fn dpy(&self) -> *mut xlib::Display {
|
|
self.display.get()
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn squash_event(&self, event_type: i32) -> XEvent {
|
|
unsafe {
|
|
let mut event =
|
|
std::mem::MaybeUninit::<xlib::XEvent>::zeroed().assume_init();
|
|
|
|
while xlib::XCheckTypedEvent(self.dpy(), event_type, &mut event)
|
|
!= 0
|
|
{}
|
|
|
|
event
|
|
}
|
|
}
|
|
|
|
pub fn next_event(&self) -> XEvent {
|
|
unsafe {
|
|
let mut event =
|
|
std::mem::MaybeUninit::<xlib::XEvent>::zeroed().assume_init();
|
|
xlib::XNextEvent(self.dpy(), &mut event);
|
|
|
|
event
|
|
}
|
|
}
|
|
|
|
pub fn grab_key_or_button(&self, window: Window, key: &KeyOrButton) {
|
|
let numlock_mask = self.get_numlock_mask();
|
|
let modifiers =
|
|
vec![0, LockMask, numlock_mask, LockMask | numlock_mask];
|
|
|
|
for modifier in modifiers.iter() {
|
|
match key {
|
|
&KeyOrButton::Key(keycode, modmask) => {
|
|
unsafe {
|
|
xlib::XGrabKey(
|
|
self.dpy(),
|
|
keycode,
|
|
modmask | modifier,
|
|
window,
|
|
1, /* true */
|
|
GrabModeAsync,
|
|
GrabModeAsync,
|
|
);
|
|
}
|
|
}
|
|
&KeyOrButton::Button(button, modmask, buttonmask) => {
|
|
unsafe {
|
|
xlib::XGrabButton(
|
|
self.dpy(),
|
|
button,
|
|
modmask | modifier,
|
|
window,
|
|
1, /*true */
|
|
buttonmask as u32,
|
|
GrabModeAsync,
|
|
GrabModeAsync,
|
|
0,
|
|
0,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn focus_client(&self, client: &Client) {
|
|
unsafe {
|
|
xlib::XSetInputFocus(
|
|
self.dpy(),
|
|
client.window,
|
|
xlib::RevertToPointerRoot,
|
|
xlib::CurrentTime,
|
|
);
|
|
|
|
let screen = xlib::XDefaultScreenOfDisplay(self.dpy()).as_ref();
|
|
|
|
if let Some(screen) = screen {
|
|
xlib::XSetWindowBorder(
|
|
self.dpy(),
|
|
client.window,
|
|
screen.white_pixel,
|
|
);
|
|
}
|
|
|
|
xlib::XChangeProperty(
|
|
self.dpy(),
|
|
self.root,
|
|
self.atoms.active_window,
|
|
xlib::XA_WINDOW,
|
|
32,
|
|
xlib::PropModeReplace,
|
|
&client.window as *const u64 as *const _,
|
|
1,
|
|
);
|
|
}
|
|
|
|
self.send_protocol(client, self.atoms.take_focus);
|
|
}
|
|
|
|
pub fn unfocus_client(&self, client: &Client) {
|
|
//info!("unfocusing client: {:?}", client);
|
|
|
|
unsafe {
|
|
xlib::XSetInputFocus(
|
|
self.dpy(),
|
|
self.root,
|
|
xlib::RevertToPointerRoot,
|
|
xlib::CurrentTime,
|
|
);
|
|
|
|
let screen = xlib::XDefaultScreenOfDisplay(self.dpy()).as_ref();
|
|
|
|
if let Some(screen) = screen {
|
|
xlib::XSetWindowBorder(
|
|
self.dpy(),
|
|
client.window,
|
|
screen.black_pixel,
|
|
);
|
|
}
|
|
|
|
// xlib::XDeleteProperty(
|
|
// self.dpy(),
|
|
// self.root,
|
|
// self.atoms.active_window,
|
|
// );
|
|
}
|
|
}
|
|
|
|
pub fn move_resize_client(&self, client: &Client) {
|
|
let mut windowchanges = xlib::XWindowChanges {
|
|
x: client.position.x,
|
|
y: client.position.y,
|
|
width: client.size.x,
|
|
height: client.size.y,
|
|
border_width: 0,
|
|
sibling: 0,
|
|
stack_mode: 0,
|
|
};
|
|
|
|
if client.size.x < 1 || client.size.y < 1 {
|
|
error!("client {:?} size is less than 1 pixel!", client);
|
|
} else {
|
|
unsafe {
|
|
xlib::XConfigureWindow(
|
|
self.dpy(),
|
|
client.window,
|
|
(xlib::CWY | xlib::CWX | xlib::CWHeight | xlib::CWWidth)
|
|
as u32,
|
|
&mut windowchanges,
|
|
);
|
|
|
|
// I don't think I have to call this ~
|
|
//self.configure_client(client);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn move_client(&self, client: &Client) {
|
|
let mut wc = xlib::XWindowChanges {
|
|
x: client.position.x,
|
|
y: client.position.y,
|
|
width: client.size.x,
|
|
height: client.size.y,
|
|
border_width: 0,
|
|
sibling: 0,
|
|
stack_mode: 0,
|
|
};
|
|
|
|
if client.size.x < 1 || client.size.y < 1 {
|
|
error!("client {:?} size is less than 1 pixel!", client);
|
|
} else {
|
|
unsafe {
|
|
xlib::XConfigureWindow(
|
|
self.dpy(),
|
|
client.window,
|
|
(xlib::CWX | xlib::CWY) as u32,
|
|
&mut wc,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn resize_client(&self, client: &Client) {
|
|
let mut wc = xlib::XWindowChanges {
|
|
x: client.position.x,
|
|
y: client.position.y,
|
|
width: client.size.x,
|
|
height: client.size.y,
|
|
border_width: 0,
|
|
sibling: 0,
|
|
stack_mode: 0,
|
|
};
|
|
|
|
if client.size.x < 1 || client.size.y < 1 {
|
|
error!("client {:?} size is less than 1 pixel!", client);
|
|
} else {
|
|
unsafe {
|
|
xlib::XConfigureWindow(
|
|
self.dpy(),
|
|
client.window,
|
|
(xlib::CWWidth | xlib::CWHeight) as u32,
|
|
&mut wc,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn hide_client(&self, client: &Client) {
|
|
let mut wc = xlib::XWindowChanges {
|
|
x: client.size.x * -2,
|
|
y: client.position.y,
|
|
width: client.size.x,
|
|
height: client.size.y,
|
|
border_width: 0,
|
|
sibling: 0,
|
|
stack_mode: 0,
|
|
};
|
|
|
|
if client.size.x < 1 || client.size.y < 1 {
|
|
error!("client {:?} size is less than 1 pixel!", client);
|
|
} else {
|
|
unsafe {
|
|
xlib::XConfigureWindow(
|
|
self.dpy(),
|
|
client.window,
|
|
(xlib::CWX | xlib::CWY) as u32,
|
|
&mut wc,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn raise_client(&self, client: &Client) {
|
|
unsafe {
|
|
XRaiseWindow(self.dpy(), client.window);
|
|
}
|
|
}
|
|
|
|
pub fn get_window_size(&self, window: Window) -> Option<(i32, i32)> {
|
|
let mut wa = unsafe {
|
|
std::mem::MaybeUninit::<xlib::XWindowAttributes>::zeroed()
|
|
.assume_init()
|
|
};
|
|
|
|
if unsafe {
|
|
xlib::XGetWindowAttributes(self.dpy(), window, &mut wa) != 0
|
|
} {
|
|
Some((wa.width, wa.height))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn get_window_attributes(
|
|
&self,
|
|
window: Window,
|
|
) -> Option<XWindowAttributes> {
|
|
let mut wa = unsafe {
|
|
std::mem::MaybeUninit::<xlib::XWindowAttributes>::zeroed()
|
|
.assume_init()
|
|
};
|
|
|
|
if unsafe {
|
|
xlib::XGetWindowAttributes(self.dpy(), window, &mut wa) != 0
|
|
} {
|
|
Some(wa)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn get_transient_for_window(&self, window: Window) -> Option<Window> {
|
|
let mut transient_for: Window = 0;
|
|
|
|
if unsafe {
|
|
XGetTransientForHint(self.dpy(), window, &mut transient_for) != 0
|
|
} {
|
|
Some(transient_for)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn expose_client(&self, client: &Client) {
|
|
self.expose_window(client.window);
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn expose_window(&self, window: Window) {
|
|
if let Some(wa) = self.get_window_attributes(window) {
|
|
unsafe {
|
|
xlib::XClearArea(
|
|
self.dpy(),
|
|
window,
|
|
0,
|
|
0,
|
|
wa.width as u32,
|
|
wa.height as u32,
|
|
1,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn configure_window(&self, event: &XConfigureRequestEvent) {
|
|
let mut wc = xlib::XWindowChanges {
|
|
x: event.x,
|
|
y: event.y,
|
|
width: event.width,
|
|
height: event.height,
|
|
border_width: event.border_width,
|
|
sibling: event.above,
|
|
stack_mode: event.detail,
|
|
};
|
|
|
|
unsafe {
|
|
xlib::XConfigureWindow(
|
|
self.dpy(),
|
|
event.window,
|
|
event.value_mask as u32,
|
|
&mut wc,
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn configure_client(&self, client: &Client, border: i32) {
|
|
let mut event = xlib::XConfigureEvent {
|
|
type_: xlib::ConfigureNotify,
|
|
display: self.dpy(),
|
|
event: client.window,
|
|
window: client.window,
|
|
x: client.position.x,
|
|
y: client.position.y,
|
|
width: client.size.x,
|
|
height: client.size.y,
|
|
border_width: border,
|
|
override_redirect: 0,
|
|
send_event: 0,
|
|
serial: 0,
|
|
above: 0,
|
|
};
|
|
|
|
unsafe {
|
|
xlib::XSetWindowBorderWidth(
|
|
self.dpy(),
|
|
event.window,
|
|
event.border_width as u32,
|
|
);
|
|
|
|
xlib::XSendEvent(
|
|
self.dpy(),
|
|
event.window,
|
|
0,
|
|
StructureNotifyMask,
|
|
&mut event as *mut _ as *mut XEvent,
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn map_window(&self, window: Window) {
|
|
unsafe {
|
|
XMapWindow(self.dpy(), window);
|
|
|
|
xlib::XSelectInput(
|
|
self.dpy(),
|
|
window,
|
|
EnterWindowMask
|
|
| FocusChangeMask
|
|
| PropertyChangeMask
|
|
| StructureNotifyMask,
|
|
);
|
|
}
|
|
|
|
self.grab_global_keybinds(window);
|
|
}
|
|
|
|
pub fn dimensions(&self) -> (i32, i32) {
|
|
unsafe {
|
|
let mut wa =
|
|
std::mem::MaybeUninit::<xlib::XWindowAttributes>::zeroed()
|
|
.assume_init();
|
|
|
|
xlib::XGetWindowAttributes(self.dpy(), self.root, &mut wa);
|
|
|
|
info!("Root window dimensions: {}, {}", wa.width, wa.height);
|
|
|
|
(wa.width, wa.height)
|
|
}
|
|
}
|
|
|
|
fn close_dpy(&self) {
|
|
unsafe {
|
|
XCloseDisplay(self.dpy());
|
|
}
|
|
}
|
|
|
|
pub fn kill_client(&self, client: &Client) {
|
|
if !self.send_protocol(client, self.atoms.delete_window) {
|
|
unsafe {
|
|
XKillClient(self.dpy(), client.window);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn grab_cursor(&self) {
|
|
unsafe {
|
|
XGrabPointer(
|
|
self.dpy(),
|
|
self.root,
|
|
0,
|
|
(ButtonPressMask | ButtonReleaseMask | PointerMotionMask)
|
|
as u32,
|
|
GrabModeAsync,
|
|
GrabModeAsync,
|
|
0,
|
|
0,
|
|
CurrentTime,
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn release_cursor(&self) {
|
|
unsafe {
|
|
XUngrabPointer(self.dpy(), CurrentTime);
|
|
}
|
|
}
|
|
|
|
pub fn move_cursor(&self, window: Option<Window>, position: (i32, i32)) {
|
|
unsafe {
|
|
XWarpPointer(
|
|
self.dpy(),
|
|
0,
|
|
window.unwrap_or(self.root),
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
position.0,
|
|
position.1,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn check_for_protocol(&self, client: &Client, proto: xlib::Atom) -> bool {
|
|
let mut protos: *mut xlib::Atom = null_mut();
|
|
let mut num_protos: i32 = 0;
|
|
|
|
unsafe {
|
|
if xlib::XGetWMProtocols(
|
|
self.dpy(),
|
|
client.window,
|
|
&mut protos,
|
|
&mut num_protos,
|
|
) != 0
|
|
{
|
|
for i in 0..num_protos {
|
|
if *protos.offset(i as isize) == proto {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
fn send_protocol(&self, client: &Client, proto: xlib::Atom) -> bool {
|
|
if self.check_for_protocol(client, proto) {
|
|
let mut data = xlib::ClientMessageData::default();
|
|
data.set_long(0, proto as i64);
|
|
|
|
let mut event = XEvent {
|
|
client_message: xlib::XClientMessageEvent {
|
|
type_: xlib::ClientMessage,
|
|
serial: 0,
|
|
display: self.dpy(),
|
|
send_event: 0,
|
|
window: client.window,
|
|
format: 32,
|
|
message_type: self.atoms.protocols,
|
|
data,
|
|
},
|
|
};
|
|
|
|
unsafe {
|
|
xlib::XSendEvent(
|
|
self.dpy(),
|
|
client.window,
|
|
0,
|
|
xlib::NoEventMask,
|
|
&mut event,
|
|
);
|
|
}
|
|
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn make_key<S>(&self, key: S, modmask: u32) -> KeyOrButton
|
|
where
|
|
S: AsRef<str>,
|
|
{
|
|
let key = self.keycode(key);
|
|
|
|
KeyOrButton::Key(key, modmask)
|
|
}
|
|
|
|
fn keycode<S>(&self, string: S) -> i32
|
|
where
|
|
S: AsRef<str>,
|
|
{
|
|
let c_string = CString::new(string.as_ref()).unwrap();
|
|
|
|
unsafe {
|
|
let keysym = xlib::XStringToKeysym(c_string.as_ptr());
|
|
xlib::XKeysymToKeycode(self.dpy(), keysym) as i32
|
|
}
|
|
}
|
|
|
|
fn get_numlock_mask(&self) -> u32 {
|
|
unsafe {
|
|
let modmap = xlib::XGetModifierMapping(self.dpy());
|
|
let max_keypermod = (*modmap).max_keypermod;
|
|
|
|
for i in 0..8 {
|
|
for j in 0..max_keypermod {
|
|
if *(*modmap)
|
|
.modifiermap
|
|
.offset((i * max_keypermod + j) as isize)
|
|
== xlib::XKeysymToKeycode(
|
|
self.dpy(),
|
|
x11::keysym::XK_Num_Lock as u64,
|
|
)
|
|
{
|
|
return 1 << i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
0
|
|
}
|
|
|
|
pub fn get_clean_mask(&self) -> u32 {
|
|
!(self.get_numlock_mask() | LockMask)
|
|
& (ShiftMask
|
|
| ControlMask
|
|
| Mod1Mask
|
|
| Mod2Mask
|
|
| Mod3Mask
|
|
| Mod4Mask
|
|
| Mod5Mask)
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn clean_mask(&self, mask: u32) -> u32 {
|
|
mask & self.get_clean_mask()
|
|
}
|
|
|
|
pub fn are_masks_equal(&self, rhs: u32, lhs: u32) -> bool {
|
|
let clean = self.get_clean_mask();
|
|
|
|
rhs & clean == lhs & clean
|
|
}
|
|
}
|
|
|
|
impl Display {
|
|
pub fn new(display: *mut x11::xlib::Display) -> Self {
|
|
Self {
|
|
0: Rc::new(display),
|
|
}
|
|
}
|
|
|
|
pub fn get(&self) -> *mut x11::xlib::Display {
|
|
*self.0
|
|
}
|
|
}
|
|
|
|
impl Atoms {
|
|
fn init(display: Display) -> Self {
|
|
unsafe {
|
|
Self {
|
|
protocols: {
|
|
let name = CString::new("WM_PROTOCOLS").unwrap();
|
|
XInternAtom(display.get(), name.as_c_str().as_ptr(), 0)
|
|
},
|
|
delete_window: {
|
|
let name = CString::new("WM_DELETE_WINDOW").unwrap();
|
|
XInternAtom(display.get(), name.as_c_str().as_ptr(), 0)
|
|
},
|
|
active_window: {
|
|
let name = CString::new("WM_ACTIVE_WINDOW").unwrap();
|
|
XInternAtom(display.get(), name.as_c_str().as_ptr(), 0)
|
|
},
|
|
take_focus: {
|
|
let name = CString::new("WM_TAKE_FOCUS").unwrap();
|
|
XInternAtom(display.get(), name.as_c_str().as_ptr(), 0)
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
unsafe extern "C" fn xlib_error_handler(
|
|
_dpy: *mut x11::xlib::Display,
|
|
ee: *mut x11::xlib::XErrorEvent,
|
|
) -> std::os::raw::c_int {
|
|
let err = ee.as_ref().unwrap();
|
|
|
|
if err.error_code == x11::xlib::BadWindow
|
|
|| err.error_code == x11::xlib::BadDrawable
|
|
|| err.error_code == x11::xlib::BadAccess
|
|
|| err.error_code == x11::xlib::BadMatch
|
|
{
|
|
0
|
|
} else {
|
|
error!(
|
|
"wm: fatal error:\nrequest_code: {}\nerror_code: {}",
|
|
err.request_code, err.error_code
|
|
);
|
|
std::process::exit(1);
|
|
}
|
|
}
|