From c8f5dfb91e634f8f23266f16a7427f584e5f6726 Mon Sep 17 00:00:00 2001 From: stuart_zhang Date: Fri, 25 Aug 2023 07:46:37 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=99=E6=98=AF=E4=B8=80=E6=AC=A1=E5=A4=A7?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=EF=BC=8C=E5=8C=85=E6=8B=AC=EF=BC=9A=20?= =?UTF-8?q?=EF=BC=881=EF=BC=89=E6=A0=B9=E6=8D=AE=E3=80=90=E8=BF=9B?= =?UTF-8?q?=E7=A8=8BID=E3=80=91=EF=BC=8C=E9=AB=98=E4=BA=AE=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E7=AA=97=E4=BD=93=E7=9A=84=E4=BB=BB=E5=8A=A1=E6=9D=A1?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E3=80=82=20=EF=BC=882=EF=BC=89=E7=BB=99?= =?UTF-8?q?=E6=AF=8F=E4=B8=AA=E5=8A=9F=E8=83=BD=20js=20api=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=B8=80=E4=B8=AA=E5=8F=AF=E9=80=89=E7=9A=84=E3=80=90?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E5=87=BD=E6=95=B0=E3=80=91=E5=8F=82=E6=95=B0?= =?UTF-8?q?=EF=BC=8C=E6=9D=A5=E6=97=A5=E5=BF=97=20c-addon=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=86=85=E7=9A=84=E5=B7=A5=E4=BD=9C=E7=BB=86=E8=8A=82?= =?UTF-8?q?=E3=80=82=E6=96=B9=E4=BE=BF=E8=B0=83=E8=AF=95=E4=B8=8E=E5=8F=91?= =?UTF-8?q?=E7=8E=B0=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- README.md | 51 +++++++++++++++-- package.json | 2 +- request-window-attention.d.ts | 27 ++++++++- src/enum_windows.rs | 52 +++++++++++++++++ src/lib.rs | 101 +++++++++++++++++++++++++++------- src/main.rs | 8 ++- test.js | 13 ++++- 8 files changed, 219 insertions(+), 37 deletions(-) create mode 100644 src/enum_windows.rs diff --git a/Cargo.toml b/Cargo.toml index d895d6a..5c89c0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "request-window-attention" -version = "0.1.2" +version = "0.1.3" edition = "2021" license = "MIT" description = "在 windows 系统,根据窗体“标题名”闪烁窗体的任务栏图标来请求用户注意" diff --git a/README.md b/README.md index 0c72b6e..aed066d 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,32 @@ struct GitEdition { extern "C" { /// 结束闪烁,但窗口任务栏还会继续高亮,直到窗体获得用户操作的焦点 /// @param winTitle 被闪烁窗体“标题名” - void stopFlashC(const char *win_title); - /// 开始闪烁。 + void stopFlashByTitleC(const char *win_title); + /// /// 开始闪烁。 /// (1)在 stopFlashJs() 接口被调用后,闪烁会停止但高亮会继续。 /// (2)在窗体获得了焦点之后,闪烁与高亮才都会结束。 /// @param winTitle 被闪烁窗体“标题名” /// @param blinkCount 闪烁次数。超过闪烁次数之后,任务栏会一直保持高亮状态。 /// @param blinkRate 相邻闪烁的间隔时间(单位:毫秒) - void startFlashC(const char *win_title, unsigned int count, unsigned int blink_rate); + void startFlashByTitleC(const char *win_title, + unsigned int count, + unsigned int blink_rate); + /// 结束闪烁,但窗口任务栏还会继续高亮,直到窗体获得用户操作的焦点 + /// @param process_id 被闪烁窗体的进程ID或nwjs的进程PID + void stopFlashByPpidC(unsigned int process_id) ; + /// /// 开始闪烁。 + /// (1)在 stopFlashJs() 接口被调用后,闪烁会停止但高亮会继续。 + /// (2)在窗体获得了焦点之后,闪烁与高亮才都会结束。 + /// @param process_id 被闪烁窗体的进程ID或nwjs的进程PID + /// @param blinkCount 闪烁次数。超过闪烁次数之后,任务栏会一直保持高亮状态。 + /// @param blinkRate 相邻闪烁的间隔时间(单位:毫秒) + void startFlashByPpidC(unsigned int process_id, + unsigned int count, + unsigned int blink_rate); /// 模块版本信息 GitEdition *getEditionC(); } // extern "C" + ``` ```typescript @@ -76,6 +91,9 @@ export interface GitEdition { pkgName: string; pkgVersion: string; } +export interface Logger { + (text: string): void; +} /** * 开始闪烁。 * (1)在 stopFlashJs() 接口被调用后,闪烁会停止但高亮会继续。 @@ -83,13 +101,31 @@ export interface GitEdition { * @param winTitle 被闪烁窗体“标题名” * @param blinkCount 闪烁次数。超过闪烁次数之后,任务栏会一直保持高亮状态。 * @param blinkRate 相邻闪烁的间隔时间(单位:毫秒) + * @param log 【可选】日志回调函数 */ -export function startFlashJs(winTitle: string, blinkCount: number, blinkRate: number); +export function startFlashByTitleJs(winTitle: string, blinkCount: number, blinkRate: number, log?: Logger); /** * 结束闪烁,但窗口任务栏还会继续高亮,直到窗体获得用户操作的焦点 * @param winTitle 被闪烁窗体“标题名” + * @param log 【可选】日志回调函数 + */ +export function stopFlashByTitleJs(winTitle: string, log?: Logger); +/** + * 开始闪烁。 + * (1)在 stopFlashJs() 接口被调用后,闪烁会停止但高亮会继续。 + * (2)在窗体获得了焦点之后,闪烁与高亮才都会结束。 + * @param ppid 被闪烁窗体的进程ID或nwjs的进程PID + * @param blinkCount 闪烁次数。超过闪烁次数之后,任务栏会一直保持高亮状态。 + * @param blinkRate 相邻闪烁的间隔时间(单位:毫秒) + * @param log 【可选】日志回调函数 */ -export function stopFlashJs(winTitle: string); +export function startFlashByPpidJs(ppid: number, blinkCount: number, blinkRate: number, log?: Logger); +/** + * 结束闪烁,但窗口任务栏还会继续高亮,直到窗体获得用户操作的焦点 + * @param ppid 被闪烁窗体的进程ID或nwjs的进程PID + * @param log 【可选】日志回调函数 + */ +export function stopFlashByPpidJs(ppid: number, log?: Logger); /** * 模块版本信息 * @returns GitEdition @@ -107,11 +143,14 @@ const attention = require('./dist/win-x64/request-window-attention.node'); (async () => { console.info('版本信息', attention.getEdition()); // 通知操作系统,开始闪烁桌面任务栏图标 - attention.startFlashJs('有道云笔记', 10, 500); + attention.startFlashJs('有道云笔记', 10, 500, logger); await new Promise(resolve => setTimeout(resolve, 10000)); // 通知操作系统,停止闪烁桌面任务栏图标。但,任务栏图标还会继续高亮。 attention.stopFlashJs('有道云笔记'); })(); +function logger(text){ + console.log('[attention]', text); +} ``` 另外,你也可以直接在工程根目录下运行指令`node test.js`来执行测试。 diff --git a/package.json b/package.json index 79293f4..69440f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "request-window-attention", - "version": "0.1.2", + "version": "0.1.3", "description": "在 windows 系统,根据窗体“标题名”闪烁窗体的任务栏图标来请求用户注意", "scripts": { "prepublishOnly": "npm run build", diff --git a/request-window-attention.d.ts b/request-window-attention.d.ts index 8145f28..a398e7b 100644 --- a/request-window-attention.d.ts +++ b/request-window-attention.d.ts @@ -8,6 +8,9 @@ export interface GitEdition { pkgName: string; pkgVersion: string; } +export interface Logger { + (text: string): void; +} /** * 开始闪烁。 * (1)在 stopFlashJs() 接口被调用后,闪烁会停止但高亮会继续。 @@ -15,15 +18,33 @@ export interface GitEdition { * @param winTitle 被闪烁窗体“标题名” * @param blinkCount 闪烁次数。超过闪烁次数之后,任务栏会一直保持高亮状态。 * @param blinkRate 相邻闪烁的间隔时间(单位:毫秒) + * @param log 【可选】日志回调函数 */ -export function startFlashJs(winTitle: string, blinkCount: number, blinkRate: number); +export function startFlashByTitleJs(winTitle: string, blinkCount: number, blinkRate: number, log?: Logger); /** * 结束闪烁,但窗口任务栏还会继续高亮,直到窗体获得用户操作的焦点 * @param winTitle 被闪烁窗体“标题名” + * @param log 【可选】日志回调函数 + */ +export function stopFlashByTitleJs(winTitle: string, log?: Logger); +/** + * 开始闪烁。 + * (1)在 stopFlashJs() 接口被调用后,闪烁会停止但高亮会继续。 + * (2)在窗体获得了焦点之后,闪烁与高亮才都会结束。 + * @param ppid 被闪烁窗体的进程ID或nwjs的进程PID + * @param blinkCount 闪烁次数。超过闪烁次数之后,任务栏会一直保持高亮状态。 + * @param blinkRate 相邻闪烁的间隔时间(单位:毫秒) + * @param log 【可选】日志回调函数 + */ +export function startFlashByPpidJs(ppid: number, blinkCount: number, blinkRate: number, log?: Logger); +/** + * 结束闪烁,但窗口任务栏还会继续高亮,直到窗体获得用户操作的焦点 + * @param ppid 被闪烁窗体的进程ID或nwjs的进程PID + * @param log 【可选】日志回调函数 */ -export function stopFlashJs(winTitle: string); +export function stopFlashByPpidJs(ppid: number, log?: Logger); /** * 模块版本信息 * @returns GitEdition */ -export function getEdition(): GitEdition; \ No newline at end of file +export function getEdition(): GitEdition; diff --git a/src/enum_windows.rs b/src/enum_windows.rs new file mode 100644 index 0000000..6cead59 --- /dev/null +++ b/src/enum_windows.rs @@ -0,0 +1,52 @@ +use ::winapi::{shared::{minwindef::{BOOL, FALSE, LPARAM, TRUE}, windef::HWND}, um::winuser::{EnumWindows, EnumChildWindows, GetWindowThreadProcessId}}; +use ::std::{ffi::c_void, mem}; + +pub fn enumerate_windows(process_id: u32, log: Option<&Box>) -> Option { + let mut result: Option = None; + let mut callback = |hwnd1: HWND| -> bool { + let mut pid: u32 = 0; + unsafe { GetWindowThreadProcessId(hwnd1, &mut pid) }; + #[cfg(debug_assertions)] + dbg!("enumerate_windows", hwnd1, pid); + log.map(|log| log(format!("[enumerate_windows] hwnd={hwnd1:?}; pid={pid}; process_id={process_id}"))); + if process_id == pid { + result.replace(hwnd1); + false + } else if let Some(hwnd2) = enumerate_child_windows(hwnd1, process_id, log) { + result.replace(hwnd2); + false + } else { + true + } + }; + let mut trait_obj: &mut dyn FnMut(HWND) -> bool = &mut callback; + let closure_pointer_pointer: *mut c_void = unsafe { mem::transmute(&mut trait_obj) }; + let lparam = closure_pointer_pointer as LPARAM; + unsafe { EnumWindows(Some(enumerate_callback), lparam) }; + result +} +pub fn enumerate_child_windows(hwnd1: HWND, process_id: u32, log: Option<&Box>) -> Option { + let mut result: Option = None; + let mut callback = |hwnd2: HWND| -> bool { + let mut pid: u32 = 0; + unsafe { GetWindowThreadProcessId(hwnd2, &mut pid) }; + #[cfg(debug_assertions)] + dbg!("enumerate_child_windows", hwnd2, pid); + log.map(|log| log(format!("[enumerate_child_windows] hwnd={hwnd2:?}; pid={pid}; process_id={process_id}"))); + if process_id == pid { + result.replace(hwnd2); + false + } else { + true + } + }; + let mut trait_obj: &mut dyn FnMut(HWND) -> bool = &mut callback; + let closure_pointer_pointer: *mut c_void = unsafe { mem::transmute(&mut trait_obj) }; + let lparam = closure_pointer_pointer as LPARAM; + unsafe { EnumChildWindows(hwnd1, Some(enumerate_callback), lparam) }; + result +} +unsafe extern "system" fn enumerate_callback(hwnd: HWND, lparam: LPARAM) -> BOOL { + let closure: &mut &mut dyn FnMut(HWND) -> bool = mem::transmute(lparam as *mut c_void); + if closure(hwnd) { TRUE } else { FALSE } +} diff --git a/src/lib.rs b/src/lib.rs index d7cd01e..e10176f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,25 +1,45 @@ #![cfg_attr(debug_assertions, feature(trace_macros, log_syntax))] mod git_edition; +mod enum_windows; #[cfg(any(feature = "nodejs", feature = "nw"))] -use ::node_bindgen::{derive::node_bindgen, init::node_bindgen_init_once}; -use ::std::{ffi::{c_char, c_uint, CString, OsString}, iter, mem, os::windows::ffi::OsStrExt}; -use ::winapi::{_core::ptr::null_mut, um::winuser::{FindWindowW, FlashWindowEx, FLASHWINFO, FLASHW_CAPTION, FLASHW_TIMER, FLASHW_TIMERNOFG, FLASHW_STOP, FLASHW_TRAY}}; +use ::node_bindgen::{core::{JSValue, NjError, val::{JsCallback, JsCallbackFunction, JsEnv, JsObject}}, derive::node_bindgen, init::node_bindgen_init_once, sys::{napi_callback_info, napi_value}}; +use ::std::{cell::RefCell, ffi::{c_char, c_uint, CString, OsString}, iter, mem, os::windows::ffi::OsStrExt}; +use ::winapi::{_core::ptr::null_mut, um::winuser::{FindWindowW, FlashWindowEx, FLASHWINFO, FLASHW_CAPTION, FLASHW_TIMER, FLASHW_TIMERNOFG, FLASHW_STOP, FLASHW_TRAY}, shared::windef::HWND}; pub use git_edition::GitEdition; +// ---------------------------------------------------------------------- // Rust -pub fn stop_flash(win_title: OsString){ - flash(win_title, FLASHW_STOP, 0, 0) +// ---------------------------------------------------------------------- +pub fn stop_flash_by_title(win_title: OsString, cb: Option>) { + flash_by_title(win_title, FLASHW_STOP, 0, 0, cb) } -pub fn start_flash(win_title: OsString, count: u32, blink_rate: u32){ - flash(win_title, FLASHW_CAPTION | FLASHW_TIMER | FLASHW_TIMERNOFG | FLASHW_TRAY, count, blink_rate) +pub fn start_flash_by_title(win_title: OsString, count: u32, blink_rate: u32, cb: Option>) { + flash_by_title(win_title, FLASHW_CAPTION | FLASHW_TIMER | FLASHW_TIMERNOFG | FLASHW_TRAY, count, blink_rate, cb) } -fn flash(win_title: OsString, action: u32, count: u32, blink_rate: u32){ +fn flash_by_title(win_title: OsString, action: u32, count: u32, blink_rate: u32, cb: Option>){ let winname = win_title.encode_wide().chain(iter::once(0)).collect::>(); let hwnd = unsafe { FindWindowW(null_mut(), winname.as_ptr()) }; if hwnd.is_null() { return; } + flash_only(hwnd, action, count, blink_rate, cb.as_ref()); +} +// +pub fn stop_flash_by_ppid(process_id: u32, cb: Option>){ + flash_by_ppid(process_id, FLASHW_STOP, 0, 0, cb) +} +pub fn start_flash_by_ppid(process_id: u32, count: u32, blink_rate: u32, cb: Option>) { + flash_by_ppid(process_id, FLASHW_CAPTION | FLASHW_TIMER | FLASHW_TIMERNOFG | FLASHW_TRAY, count, blink_rate, cb) +} +fn flash_by_ppid(process_id: u32, action: u32, count: u32, blink_rate: u32, cb: Option>) { + if let Some(hwnd) = enum_windows::enumerate_windows(process_id, cb.as_ref()) { + flash_only(hwnd, action, count, blink_rate, cb.as_ref()); + } +} +// +fn flash_only(hwnd: HWND, action: u32, count: u32, blink_rate: u32, cb: Option<&Box>) { #[cfg(debug_assertions)] dbg!(hwnd); + cb.map(|log| log(format!("[flash_only]hwnd={hwnd:?}; action={action}; count={count}; blink_rate={blink_rate}"))); let mut flash_info = FLASHWINFO { cbSize: mem::size_of::() as u32, hwnd, @@ -27,17 +47,20 @@ fn flash(win_title: OsString, action: u32, count: u32, blink_rate: u32){ uCount: count, dwTimeout: blink_rate }; - let _result = unsafe { FlashWindowEx(&mut flash_info) }; + let result = unsafe { FlashWindowEx(&mut flash_info) }; #[cfg(debug_assertions)] - dbg!(_result); + dbg!(result); + cb.map(|log| log(format!("[flash_only]result={result:?};"))); } +// ---------------------------------------------------------------------- // C +// ---------------------------------------------------------------------- /// 结束闪烁,但窗口任务栏还会继续高亮,直到窗体获得用户操作的焦点 /// @param winTitle 被闪烁窗体“标题名” -#[export_name = "stopFlashC"] -pub extern fn stop_flash_c(win_title: *const c_char) { +#[export_name = "stopFlashByTitleC"] +pub extern fn stop_flash_by_title_c(win_title: *const c_char) { let win_title = unsafe { CString::from_raw(win_title as *mut i8) }.into_string().unwrap(); - stop_flash(win_title.into()) + stop_flash_by_title(win_title.into(), None) } /// /// 开始闪烁。 /// (1)在 stopFlashJs() 接口被调用后,闪烁会停止但高亮会继续。 @@ -45,17 +68,35 @@ pub extern fn stop_flash_c(win_title: *const c_char) { /// @param winTitle 被闪烁窗体“标题名” /// @param blinkCount 闪烁次数。超过闪烁次数之后,任务栏会一直保持高亮状态。 /// @param blinkRate 相邻闪烁的间隔时间(单位:毫秒) -#[export_name = "startFlashC"] -pub extern fn start_flash_c(win_title: *const c_char, count: c_uint, blink_rate: c_uint) { +#[export_name = "startFlashByTitleC"] +pub extern fn start_flash_by_title_c(win_title: *const c_char, count: c_uint, blink_rate: c_uint) { let win_title = unsafe { CString::from_raw(win_title as *mut i8) }.into_string().unwrap(); - start_flash(win_title.into(), count, blink_rate) + start_flash_by_title(win_title.into(), count, blink_rate, None) +} +/// 结束闪烁,但窗口任务栏还会继续高亮,直到窗体获得用户操作的焦点 +/// @param process_id 被闪烁窗体的进程ID或nwjs的进程PID +#[export_name = "stopFlashByPpidC"] +pub extern fn stop_flash_by_ppid_c(process_id: c_uint) { + stop_flash_by_ppid(process_id, None) +} +/// /// 开始闪烁。 +/// (1)在 stopFlashJs() 接口被调用后,闪烁会停止但高亮会继续。 +/// (2)在窗体获得了焦点之后,闪烁与高亮才都会结束。 +/// @param process_id 被闪烁窗体的进程ID或nwjs的进程PID +/// @param blinkCount 闪烁次数。超过闪烁次数之后,任务栏会一直保持高亮状态。 +/// @param blinkRate 相邻闪烁的间隔时间(单位:毫秒) +#[export_name = "startFlashByPpidC"] +pub extern fn start_flash_by_ppid_c(process_id: c_uint, count: c_uint, blink_rate: c_uint) { + start_flash_by_ppid(process_id, count, blink_rate, None) } /// 模块版本信息 #[export_name = "getEditionC"] pub extern fn get_edition_c() -> *mut GitEdition { Box::into_raw(Box::new(GitEdition::default())) } +// ---------------------------------------------------------------------- // nodejs +// ---------------------------------------------------------------------- #[cfg_attr(any(feature = "nodejs", feature = "nw"), node_bindgen_init_once)] #[cfg(any(feature = "nodejs", feature = "nw"))] fn main() { @@ -63,16 +104,34 @@ fn main() { } #[cfg_attr(any(feature = "nodejs", feature = "nw"), node_bindgen)] #[cfg(any(feature = "nodejs", feature = "nw"))] -fn stop_flash_js(win_title: String) { - stop_flash(win_title.into()) +fn stop_flash_by_title_js(win_title: String, cb: Option) { + stop_flash_by_title(win_title.into(), build_callback(cb)) +} +#[cfg_attr(any(feature = "nodejs", feature = "nw"), node_bindgen)] +#[cfg(any(feature = "nodejs", feature = "nw"))] +fn start_flash_by_title_js(win_title: String, count: u32, blink_rate: u32, cb: Option) { + start_flash_by_title(win_title.into(), count, blink_rate, build_callback(cb)) } #[cfg_attr(any(feature = "nodejs", feature = "nw"), node_bindgen)] #[cfg(any(feature = "nodejs", feature = "nw"))] -fn start_flash_js(win_title: String, count: u32, blink_rate: u32) { - start_flash(win_title.into(), count, blink_rate) +fn stop_flash_by_ppid_js(process_id: i32, cb: Option) { + stop_flash_by_ppid(process_id as u32, build_callback(cb)) } -#[cfg_attr(any(feature = "nodejs", feature = "nw"), node_bindgen(name = "getEdition"))] +#[cfg_attr(any(feature = "nodejs", feature = "nw"), node_bindgen)] +#[cfg(any(feature = "nodejs", feature = "nw"))] +fn start_flash_by_ppid_js(process_id: i32, count: u32, blink_rate: u32, cb: Option) { + start_flash_by_ppid(process_id as u32, count, blink_rate, build_callback(cb)) +} +#[cfg_attr(any(feature = "nodejs", feature = "nw"), node_bindgen)] #[cfg(any(feature = "nodejs", feature = "nw"))] fn get_edition() -> GitEdition { GitEdition::default() +} +#[cfg(any(feature = "nodejs", feature = "nw"))] +fn build_callback(cb: Option) -> Option> { + cb.map(|cb| { + Box::new(move |text: String| { + cb.call(vec![text]).unwrap(); + }) as Box + }) } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 5d3aeac..29c040d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,10 @@ use ::std::{thread, time::Duration}; fn main() { - request_window_attention::start_flash("有道云笔记".into(), 10, 500); + request_window_attention::start_flash_by_title("有道云笔记".into(), 10, 500, None); thread::sleep(Duration::from_secs(2)); - request_window_attention::stop_flash("有道云笔记".into()); + request_window_attention::stop_flash_by_title("有道云笔记".into(), None); + // + request_window_attention::start_flash_by_ppid(3300, 10, 500, None); + thread::sleep(Duration::from_secs(2)); + request_window_attention::stop_flash_by_ppid(3300, None); } \ No newline at end of file diff --git a/test.js b/test.js index ef5a6a1..5383970 100644 --- a/test.js +++ b/test.js @@ -2,7 +2,14 @@ const attention = require('./dist/nodejs/win-x64/request-window-attention.node'); (async () => { console.info('版本信息', attention.getEdition()); - attention.startFlashJs('有道云笔记', 10, 500); + attention.startFlashByTitleJs('有道云笔记', 10, 500, logger); await new Promise(resolve => setTimeout(resolve, 10000)); - attention.stopFlashJs('有道云笔记'); -})(); \ No newline at end of file + attention.stopFlashByTitleJs('有道云笔记'); + + attention.startFlashByPpidJs(18928, 10, 500, logger); + await new Promise(resolve => setTimeout(resolve, 10000)); + attention.stopFlashByPpidJs(18928); +})(); +function logger(text){ + console.log('[attention]', text); +}