commit
						3759347d47
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,5 @@ | ||||
| /target | ||||
| .vscode | ||||
| .idea | ||||
| .DS_Store | ||||
| src/ui/inline.rs | ||||
|  | ||||
| @ -76,8 +76,8 @@ message PunchHoleResponse { | ||||
|   enum Failure { | ||||
|     ID_NOT_EXIST = 1; | ||||
|     OFFLINE = 2; | ||||
|     LICENCE_MISMATCH = 3; | ||||
|     LICENCE_OVERUSE = 4; | ||||
|     LICENSE_MISMATCH = 3; | ||||
|     LICENSE_OVERUSE = 4; | ||||
|   } | ||||
|   Failure failure = 3; | ||||
|   string relay_server = 4; | ||||
|  | ||||
| @ -153,7 +153,10 @@ fn patch(path: PathBuf) -> PathBuf { | ||||
|         { | ||||
|             if _tmp == "/root" { | ||||
|                 if let Ok(output) = std::process::Command::new("whoami").output() { | ||||
|                     let user = String::from_utf8_lossy(&output.stdout).to_string().trim().to_owned(); | ||||
|                     let user = String::from_utf8_lossy(&output.stdout) | ||||
|                         .to_string() | ||||
|                         .trim() | ||||
|                         .to_owned(); | ||||
|                     if user != "root" { | ||||
|                         return format!("/home/{}", user).into(); | ||||
|                     } | ||||
|  | ||||
| @ -193,7 +193,7 @@ pub struct TransferJob { | ||||
|     file: Option<File>, | ||||
|     total_size: u64, | ||||
|     finished_size: u64, | ||||
|     transfered: u64, | ||||
|     transferred: u64, | ||||
| } | ||||
| 
 | ||||
| #[inline] | ||||
| @ -268,8 +268,8 @@ impl TransferJob { | ||||
|     } | ||||
| 
 | ||||
|     #[inline] | ||||
|     pub fn transfered(&self) -> u64 { | ||||
|         self.transfered | ||||
|     pub fn transferred(&self) -> u64 { | ||||
|         self.transferred | ||||
|     } | ||||
| 
 | ||||
|     #[inline] | ||||
| @ -332,7 +332,7 @@ impl TransferJob { | ||||
|             self.file.as_mut().unwrap().write_all(&block.data).await?; | ||||
|             self.finished_size += block.data.len() as u64; | ||||
|         } | ||||
|         self.transfered += block.data.len() as u64; | ||||
|         self.transferred += block.data.len() as u64; | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
| @ -398,7 +398,7 @@ impl TransferJob { | ||||
|                     compressed = true; | ||||
|                 } | ||||
|             } | ||||
|             self.transfered += buf.len() as u64; | ||||
|             self.transferred += buf.len() as u64; | ||||
|         } | ||||
|         Ok(Some(FileTransferBlock { | ||||
|             id: self.id, | ||||
|  | ||||
| @ -34,7 +34,7 @@ fn new_socket(addr: std::net::SocketAddr, reuse: bool) -> Result<TcpSocket, std: | ||||
|     if reuse { | ||||
|         // windows has no reuse_port, but it's reuse_address
 | ||||
|         // almost equals to unix's reuse_port + reuse_address,
 | ||||
|         // though may introduce nondeterministic bahavior
 | ||||
|         // though may introduce nondeterministic behavior
 | ||||
|         #[cfg(unix)] | ||||
|         socket.set_reuseport(true)?; | ||||
|         socket.set_reuseaddr(true)?; | ||||
|  | ||||
| @ -29,7 +29,7 @@ fn new_socket(addr: SocketAddr, reuse: bool) -> Result<Socket, std::io::Error> { | ||||
|     if reuse { | ||||
|         // windows has no reuse_port, but it's reuse_address
 | ||||
|         // almost equals to unix's reuse_port + reuse_address,
 | ||||
|         // though may introduce nondeterministic bahavior
 | ||||
|         // though may introduce nondeterministic behavior
 | ||||
|         #[cfg(unix)] | ||||
|         socket.set_reuse_port(true)?; | ||||
|         socket.set_reuse_address(true)?; | ||||
|  | ||||
| @ -141,7 +141,7 @@ impl Client { | ||||
|                                     punch_hole_response::Failure::OFFLINE => { | ||||
|                                         bail!("Remote desktop is offline"); | ||||
|                                     } | ||||
|                                     punch_hole_response::Failure::LICENCE_MISMATCH => { | ||||
|                                     punch_hole_response::Failure::LICENSE_MISMATCH => { | ||||
|                                         bail!("Key mismatch"); | ||||
|                                     } | ||||
|                                     _ => { | ||||
|  | ||||
| @ -147,7 +147,7 @@ pub async fn new_listener(postfix: &str) -> ResultType<Incoming> { | ||||
|         } | ||||
|         Err(err) => { | ||||
|             log::error!( | ||||
|                 "Faild to start ipc{} server at path {}: {}", | ||||
|                 "Failed to start ipc{} server at path {}: {}", | ||||
|                 postfix, | ||||
|                 path, | ||||
|                 err | ||||
| @ -374,7 +374,7 @@ pub fn set_password(v: String) -> ResultType<()> { | ||||
| 
 | ||||
| pub fn get_id() -> String { | ||||
|     if let Ok(Some(v)) = get_config("id") { | ||||
|         // update salt also, so that nexttime reinstallation not causing first-time auto-login failure
 | ||||
|         // update salt also, so that next time reinstallation not causing first-time auto-login failure
 | ||||
|         if let Ok(Some(v2)) = get_config("salt") { | ||||
|             Config::set_salt(&v2); | ||||
|         } | ||||
|  | ||||
| @ -417,7 +417,7 @@ pub fn fix_login_wayland() { | ||||
|             "sed", | ||||
|             "-i", | ||||
|             "s/#WaylandEnable=false/WaylandEnable=false/g", | ||||
|             &file | ||||
|             &file, | ||||
|         ]) | ||||
|         .output() | ||||
|     { | ||||
| @ -435,20 +435,22 @@ pub fn fix_login_wayland() { | ||||
| 
 | ||||
| pub fn current_is_wayland() -> bool { | ||||
|     let dtype = get_display_server(); | ||||
|     return "wayland" == dtype && unsafe{UNMODIFIED}; | ||||
|     return "wayland" == dtype && unsafe { UNMODIFIED }; | ||||
| } | ||||
| 
 | ||||
| pub fn modify_default_login() -> String { | ||||
|     let dsession = std::env::var("DESKTOP_SESSION").unwrap(); | ||||
|     let user_name = std::env::var("USERNAME").unwrap(); | ||||
|     if let Ok(Some(x)) = run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION}-xorg.desktop".to_owned()) { | ||||
|     if let Ok(Some(x)) = | ||||
|         run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION}-xorg.desktop".to_owned()) | ||||
|     { | ||||
|         if x.trim_end().to_string() != "" { | ||||
|             match std::process::Command::new("pkexec") | ||||
|                 .args(vec![ | ||||
|                     "sed", | ||||
|                     "-i", | ||||
|                     &format!("s/={0}$/={0}-xorg/g", &dsession), | ||||
|                     &format!("/var/lib/AccountsService/users/{}", &user_name) | ||||
|                     &format!("/var/lib/AccountsService/users/{}", &user_name), | ||||
|                 ]) | ||||
|                 .output() | ||||
|             { | ||||
| @ -458,7 +460,9 @@ pub fn modify_default_login() -> String { | ||||
|                         log::error!("modify_default_login failed: {}", x); | ||||
|                         return "Fix failed! Please re-login with X server manually".to_owned(); | ||||
|                     } else { | ||||
|                         unsafe {UNMODIFIED = false;} | ||||
|                         unsafe { | ||||
|                             UNMODIFIED = false; | ||||
|                         } | ||||
|                         return "".to_owned(); | ||||
|                     } | ||||
|                 } | ||||
| @ -467,14 +471,16 @@ pub fn modify_default_login() -> String { | ||||
|                     return "Fix failed! Please re-login with X server manually".to_owned(); | ||||
|                 } | ||||
|             } | ||||
|         } else if let Ok(Some(z)) = run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION:0:-8}.desktop".to_owned()) { | ||||
|         } else if let Ok(Some(z)) = | ||||
|             run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION:0:-8}.desktop".to_owned()) | ||||
|         { | ||||
|             if z.trim_end().to_string() != "" { | ||||
|                 match std::process::Command::new("pkexec") | ||||
|                     .args(vec![ | ||||
|                         "sed", | ||||
|                         "-i", | ||||
|                         &format!("s/={}$/={}/g", &dsession, &dsession[..dsession.len()-8]), | ||||
|                         &format!("/var/lib/AccountsService/users/{}", &user_name) | ||||
|                         &format!("s/={}$/={}/g", &dsession, &dsession[..dsession.len() - 8]), | ||||
|                         &format!("/var/lib/AccountsService/users/{}", &user_name), | ||||
|                     ]) | ||||
|                     .output() | ||||
|                 { | ||||
| @ -484,7 +490,9 @@ pub fn modify_default_login() -> String { | ||||
|                             log::error!("modify_default_login failed: {}", x); | ||||
|                             return "Fix failed! Please re-login with X server manually".to_owned(); | ||||
|                         } else { | ||||
|                             unsafe {UNMODIFIED = false;} | ||||
|                             unsafe { | ||||
|                                 UNMODIFIED = false; | ||||
|                             } | ||||
|                             return "".to_owned(); | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
| @ -145,7 +145,13 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> { | ||||
|         if do_outline { | ||||
|             let mut outline = Vec::new(); | ||||
|             outline.resize(((width + 2) * (height + 2) * 4) as _, 0); | ||||
|             drawOutline(outline.as_mut_ptr(), cbits.as_ptr(), width, height, outline.len() as _); | ||||
|             drawOutline( | ||||
|                 outline.as_mut_ptr(), | ||||
|                 cbits.as_ptr(), | ||||
|                 width, | ||||
|                 height, | ||||
|                 outline.len() as _, | ||||
|             ); | ||||
|             cbits = outline; | ||||
|             width += 2; | ||||
|             height += 2; | ||||
| @ -357,7 +363,7 @@ fn fix_cursor_mask( | ||||
|             let mut alpha = 255; | ||||
|             if mask_idx < bm_size { | ||||
|                 if (mbits[mask_idx] << (x & 0x7)) & 0x80 == 0 { | ||||
|                    alpha =  0; | ||||
|                     alpha = 0; | ||||
|                 } | ||||
|             } | ||||
|             let a = cbits[pix_idx + 2]; | ||||
| @ -393,8 +399,14 @@ extern "C" { | ||||
|     fn LaunchProcessWin(cmd: *const u16, session_id: DWORD, as_user: BOOL) -> HANDLE; | ||||
|     fn selectInputDesktop() -> BOOL; | ||||
|     fn inputDesktopSelected() -> BOOL; | ||||
|     fn handleMask(out: *mut u8, mask: *const u8, width: i32, height: i32, bmWidthBytes: i32, bmHeight: i32) | ||||
|         -> i32; | ||||
|     fn handleMask( | ||||
|         out: *mut u8, | ||||
|         mask: *const u8, | ||||
|         width: i32, | ||||
|         height: i32, | ||||
|         bmWidthBytes: i32, | ||||
|         bmHeight: i32, | ||||
|     ) -> i32; | ||||
|     fn drawOutline(out: *mut u8, in_: *const u8, width: i32, height: i32, out_size: i32); | ||||
|     fn get_di_bits(out: *mut u8, dc: HDC, hbmColor: HBITMAP, width: i32, height: i32) -> i32; | ||||
|     fn blank_screen(v: BOOL); | ||||
| @ -534,7 +546,7 @@ async fn launch_server(session_id: DWORD, close_first: bool) -> ResultType<HANDL | ||||
|     let wstr = wstr.as_ptr(); | ||||
|     let h = unsafe { LaunchProcessWin(wstr, session_id, FALSE) }; | ||||
|     if h.is_null() { | ||||
|         log::error!("Failed to luanch server: {}", get_error()); | ||||
|         log::error!("Failed to launch server: {}", get_error()); | ||||
|     } | ||||
|     Ok(h) | ||||
| } | ||||
| @ -593,7 +605,7 @@ fn send_sas() { | ||||
| } | ||||
| 
 | ||||
| lazy_static::lazy_static! { | ||||
|     static ref SUPRESS: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now())); | ||||
|     static ref SUPPRESS: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now())); | ||||
| } | ||||
| 
 | ||||
| pub fn desktop_changed() -> bool { | ||||
| @ -605,7 +617,7 @@ pub fn try_change_desktop() -> bool { | ||||
|         if inputDesktopSelected() == FALSE { | ||||
|             let res = selectInputDesktop() == TRUE; | ||||
|             if !res { | ||||
|                 let mut s = SUPRESS.lock().unwrap(); | ||||
|                 let mut s = SUPPRESS.lock().unwrap(); | ||||
|                 if s.elapsed() > std::time::Duration::from_secs(3) { | ||||
|                     log::error!("Failed to switch desktop: {}", get_error()); | ||||
|                     *s = Instant::now(); | ||||
|  | ||||
| @ -34,7 +34,7 @@ pub struct RendezvousMediator { | ||||
|     host: String, | ||||
|     host_prefix: String, | ||||
|     rendezvous_servers: Vec<String>, | ||||
|     last_id_pk_registery: String, | ||||
|     last_id_pk_registry: String, | ||||
| } | ||||
| 
 | ||||
| impl RendezvousMediator { | ||||
| @ -90,7 +90,7 @@ impl RendezvousMediator { | ||||
|             host: host.clone(), | ||||
|             host_prefix, | ||||
|             rendezvous_servers, | ||||
|             last_id_pk_registery: "".to_owned(), | ||||
|             last_id_pk_registry: "".to_owned(), | ||||
|         }; | ||||
|         allow_err!(rz.dns_check()); | ||||
|         let mut socket = FramedSocket::new(Config::get_any_listen_addr()).await?; | ||||
| @ -380,7 +380,7 @@ impl RendezvousMediator { | ||||
|             pk.clone() | ||||
|         }; | ||||
|         let id = Config::get_id(); | ||||
|         self.last_id_pk_registery = id.clone(); | ||||
|         self.last_id_pk_registry = id.clone(); | ||||
|         msg_out.set_register_pk(RegisterPk { | ||||
|             id, | ||||
|             uuid, | ||||
| @ -392,7 +392,7 @@ impl RendezvousMediator { | ||||
|     } | ||||
| 
 | ||||
|     async fn handle_uuid_mismatch(&mut self, socket: &mut FramedSocket) -> ResultType<()> { | ||||
|         if self.last_id_pk_registery != Config::get_id() { | ||||
|         if self.last_id_pk_registry != Config::get_id() { | ||||
|             return Ok(()); | ||||
|         } | ||||
|         { | ||||
|  | ||||
| @ -120,13 +120,13 @@ impl Connection { | ||||
|             return; | ||||
|         } | ||||
|         if !conn.keyboard { | ||||
|             conn.send_permisssion(Permission::Keyboard, false).await; | ||||
|             conn.send_permission(Permission::Keyboard, false).await; | ||||
|         } | ||||
|         if !conn.clipboard { | ||||
|             conn.send_permisssion(Permission::Clipboard, false).await; | ||||
|             conn.send_permission(Permission::Clipboard, false).await; | ||||
|         } | ||||
|         if !conn.audio { | ||||
|             conn.send_permisssion(Permission::Audio, false).await; | ||||
|             conn.send_permission(Permission::Audio, false).await; | ||||
|         } | ||||
|         let mut test_delay_timer = | ||||
|             time::interval_at(Instant::now() + TEST_DELAY_TIMEOUT, TEST_DELAY_TIMEOUT); | ||||
| @ -164,7 +164,7 @@ impl Connection { | ||||
|                             log::info!("Change permission {} -> {}", name, enabled); | ||||
|                             if &name == "keyboard" { | ||||
|                                 conn.keyboard = enabled; | ||||
|                                 conn.send_permisssion(Permission::Keyboard, enabled).await; | ||||
|                                 conn.send_permission(Permission::Keyboard, enabled).await; | ||||
|                                 if let Some(s) = conn.server.upgrade() { | ||||
|                                     s.write().unwrap().subscribe( | ||||
|                                         NAME_CURSOR, | ||||
| @ -172,7 +172,7 @@ impl Connection { | ||||
|                                 } | ||||
|                             } else if &name == "clipboard" { | ||||
|                                 conn.clipboard = enabled; | ||||
|                                 conn.send_permisssion(Permission::Clipboard, enabled).await; | ||||
|                                 conn.send_permission(Permission::Clipboard, enabled).await; | ||||
|                                 if let Some(s) = conn.server.upgrade() { | ||||
|                                     s.write().unwrap().subscribe( | ||||
|                                         super::clipboard_service::NAME, | ||||
| @ -180,7 +180,7 @@ impl Connection { | ||||
|                                 } | ||||
|                             } else if &name == "audio" { | ||||
|                                 conn.audio = enabled; | ||||
|                                 conn.send_permisssion(Permission::Audio, enabled).await; | ||||
|                                 conn.send_permission(Permission::Audio, enabled).await; | ||||
|                                 if let Some(s) = conn.server.upgrade() { | ||||
|                                     s.write().unwrap().subscribe( | ||||
|                                         super::audio_service::NAME, | ||||
| @ -331,7 +331,7 @@ impl Connection { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async fn send_permisssion(&mut self, permission: Permission, enabled: bool) { | ||||
|     async fn send_permission(&mut self, permission: Permission, enabled: bool) { | ||||
|         let mut misc = Misc::new(); | ||||
|         misc.set_permission_info(PermissionInfo { | ||||
|             permission: permission.into(), | ||||
|  | ||||
| @ -34,7 +34,7 @@ pub trait Reset { | ||||
| pub struct ServiceTmpl<T: Subscriber + From<ConnInner>>(Arc<RwLock<ServiceInner<T>>>); | ||||
| pub struct ServiceSwap<T: Subscriber + From<ConnInner>>(ServiceTmpl<T>); | ||||
| pub type GenericService = ServiceTmpl<ConnInner>; | ||||
| pub const HIBERATE_TIMEOUT: u64 = 30; | ||||
| pub const HIBERNATE_TIMEOUT: u64 = 30; | ||||
| pub const MAX_ERROR_TIMEOUT: u64 = 1_000; | ||||
| 
 | ||||
| impl<T: Subscriber + From<ConnInner>> ServiceInner<T> { | ||||
| @ -191,7 +191,7 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> { | ||||
|         let sp = self.clone(); | ||||
|         let mut callback = callback; | ||||
|         let thread = thread::spawn(move || { | ||||
|             let mut error_timeout = HIBERATE_TIMEOUT; | ||||
|             let mut error_timeout = HIBERNATE_TIMEOUT; | ||||
|             while sp.active() { | ||||
|                 if sp.has_subscribes() { | ||||
|                     log::debug!("Enter {} service inner loop", sp.name()); | ||||
| @ -199,7 +199,7 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> { | ||||
|                     if let Err(err) = callback(sp.clone()) { | ||||
|                         log::error!("Error of {} service: {}", sp.name(), err); | ||||
|                         if tm.elapsed() > time::Duration::from_millis(MAX_ERROR_TIMEOUT) { | ||||
|                             error_timeout = HIBERATE_TIMEOUT; | ||||
|                             error_timeout = HIBERNATE_TIMEOUT; | ||||
|                         } else { | ||||
|                             error_timeout *= 2; | ||||
|                         } | ||||
| @ -213,7 +213,7 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> { | ||||
|                         log::debug!("Exit {} service inner loop", sp.name()); | ||||
|                     } | ||||
|                 } | ||||
|                 thread::sleep(time::Duration::from_millis(HIBERATE_TIMEOUT)); | ||||
|                 thread::sleep(time::Duration::from_millis(HIBERNATE_TIMEOUT)); | ||||
|             } | ||||
|         }); | ||||
|         self.0.write().unwrap().handle = Some(thread); | ||||
|  | ||||
| @ -99,7 +99,6 @@ fn run(sp: GenericService) -> ResultType<()> { | ||||
|         *SWITCH.lock().unwrap() = false; | ||||
|         sp.send(msg_out); | ||||
|     } | ||||
|     
 | ||||
|     let mut crc = (0, 0); | ||||
|     let start = time::Instant::now(); | ||||
|     let mut last_sent = time::Instant::now(); | ||||
|  | ||||
| @ -489,7 +489,7 @@ impl UI { | ||||
|         #[cfg(target_os = "linux")] | ||||
|         return crate::platform::linux::fix_login_wayland(); | ||||
|     } | ||||
|     
 | ||||
| 
 | ||||
|     fn current_is_wayland(&mut self) -> bool { | ||||
|         #[cfg(target_os = "linux")] | ||||
|         return crate::platform::linux::current_is_wayland(); | ||||
| @ -634,7 +634,7 @@ pub fn check_zombie(childs: Childs) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // notice: avoiding create ipc connecton repeatly,
 | ||||
| // notice: avoiding create ipc connection repeatedly,
 | ||||
| // because windows named pipe has serious memory leak issue.
 | ||||
| #[tokio::main(flavor = "current_thread")] | ||||
| async fn check_connect_status_( | ||||
|  | ||||
| @ -694,8 +694,8 @@ impl Handler { | ||||
|         let mut key_event = KeyEvent::new(); | ||||
|         if down_or_up == 2 { | ||||
|             /* windows send both keyup/keydown and keychar, so here we avoid keychar
 | ||||
|                for <= 0xFF, best practise should only avoid those not on keyboard, but | ||||
|                for now, we have no way to test, so avoid <= 0xFF totaly | ||||
|                for <= 0xFF, best practice should only avoid those not on keyboard, but | ||||
|                for now, we have no way to test, so avoid <= 0xFF totally | ||||
|             */ | ||||
|             if code <= 0xFF { | ||||
|                 return None; | ||||
| @ -891,7 +891,8 @@ impl Handler { | ||||
| 
 | ||||
|         let mut name = name; | ||||
|         #[cfg(target_os = "linux")] | ||||
|         if code == 65383 { // VK_MENU
 | ||||
|         if code == 65383 { | ||||
|             // VK_MENU
 | ||||
|             name = "Apps".to_owned(); | ||||
|         } | ||||
| 
 | ||||
| @ -1435,16 +1436,16 @@ impl Remote { | ||||
|         if elapsed <= 0 { | ||||
|             return; | ||||
|         } | ||||
|         let transfered = job.transfered(); | ||||
|         let last_transfered = { | ||||
|         let transferred = job.transferred(); | ||||
|         let last_transferred = { | ||||
|             if let Some(v) = last_update_jobs_status.1.get(&job.id()) { | ||||
|                 v.to_owned() | ||||
|             } else { | ||||
|                 0 | ||||
|             } | ||||
|         }; | ||||
|         last_update_jobs_status.1.insert(job.id(), transfered); | ||||
|         let speed = (transfered - last_transfered) as f64 / (elapsed as f64 / 1000.); | ||||
|         last_update_jobs_status.1.insert(job.id(), transferred); | ||||
|         let speed = (transferred - last_transferred) as f64 / (elapsed as f64 / 1000.); | ||||
|         let file_num = job.file_num() - 1; | ||||
|         handler.call( | ||||
|             "jobProgress", | ||||
|  | ||||
| @ -89,7 +89,7 @@ extern "C" | ||||
| 
 | ||||
|                 CreateEnvironmentBlock(&lpEnvironment, // Environment block
 | ||||
|                                        hToken,         // New token
 | ||||
|                                        TRUE);          // Inheritence
 | ||||
|                                        TRUE);          // Inheritance
 | ||||
|             } | ||||
|             if (lpEnvironment) | ||||
|             { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user