Skip to main content

2 posts tagged with "windows"

View All Tags

· 4 min read
Swnb

管理员权限(windows)

如果有修改用户注册表的需求, 需要获取管理员权限, 可以在 electron-builder 里面设置

electron-builder.yml

_10
win:
_10
requestedExecutionLevel: requireAdministrator

程序在启动的时候就会请求管理员权限

媒体权限

如果要获取摄像头和麦克风的权限, 首先要创建一个 plist , plist 里面可以指定以下的权限 plist

plist 如下所示

entitlements.mac.plist

_16
<?xml version="1.0" encoding="UTF-8"?>
_16
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
_16
<plist version="1.0">
_16
<dict>
_16
<key>com.apple.security.cs.allow-jit</key>
_16
<true/>
_16
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
_16
<true/>
_16
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
_16
<true/>
_16
<key>com.apple.security.device.audio-input</key>
_16
<true/>
_16
<key>com.apple.security.device.camera</key>
_16
<true/>
_16
</dict>
_16
</plist>

💡提示

plist 必须完整, 如果你删掉了一些内容, 那么应用会崩溃😭

然后配置 electron-builder

配置 plist

加上请求权限的提示

屏幕录制的提示

electron-builder.yml

_10
mac:
_10
hardenedRuntime: true # 启用 hardenedRuntime
_10
entitlements: entitlements.mac.plist # plist 文件路径

辅助功能

如果你的 electron 需要调用一些 .node 文件去做操作,比如锁屏,那就要在 plist 里面指定 disable-library-validation

entitlements.mac.plist

_18
<?xml version="1.0" encoding="UTF-8"?>
_18
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
_18
<plist version="1.0">
_18
<dict>
_18
<key>com.apple.security.cs.allow-jit</key>
_18
<true/>
_18
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
_18
<true/>
_18
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
_18
<true/>
_18
<key>com.apple.security.device.audio-input</key>
_18
<true/>
_18
<key>com.apple.security.device.camera</key>
_18
<true/>
_18
<key>com.apple.security.cs.disable-library-validation</key>
_18
<true/>
_18
</dict>
_18
</plist>

在应用中请求并确认权限

查看是否有 摄像头 权限,第一次会弹出询问框

查看是否有 麦克风 权限,第一次会弹出询问框

查看是否有 屏幕录制 权限

查看是否有 辅助功能 权限

main.tsx

_10
const isCameraAccessGranted = await systemPreferences.askForMediaAccess(
_10
"camera"
_10
)

可以根据上面的权限状态弹出提示框来提示用户打开权限

· 8 min read
Swnb

起因

最近在用 electron 开发一个线上的考试应用,有一个需求是在监考模式下拦截 用户的键盘事件,防止用户锁屏或者使用其它快捷键切换应用,在 github 翻了一圈,没有找到合适的方案 o(╥﹏╥)o,所以用 rust 写了一个 lib,然后用 napi-rs 打包成 nodejs 库。下面记录下整个流程。

mac os

开发 mac os 应用,需要使用 cocoa 库,mozilla 开发了一个 rust 版本的库core-foundation-rs

使用 core_graphicsCGEventTap 可以拦截键盘事件


_16
use core_graphics::event::{
_16
CGEventTap, CGEventTapLocation, CGEventTapOptions, CGEventTapPlacement, CGEventType::KeyDown,
_16
};
_16
_16
fn handle_key_event() {
_16
let cg_event_tap = CGEventTap::new(
_16
CGEventTapLocation::Session,
_16
// 插入队首
_16
CGEventTapPlacement::HeadInsertEventTap,
_16
// 默认行为过滤
_16
CGEventTapOptions::Default,
_16
// 监听的事件
_16
vec![CGEventType::KeyDown],
_16
callback,
_16
);
_16
}

构造 CGEventTap 的 5 参数依次表示

  1. 监听事件的区域 直接选 CGEventTapLocation::Session 即可
  2. 插入队首还是队尾,当一个事件触发的时候,会依次调用回掉队列上面的回调函数,前面的回调函数可以阻止后面的回调函数触发,这里插入队首部,阻止后面的默认系统行为触发
  3. 只监听事件的触发还是可以阻止后面的行为触发 这里选择 Default
  4. 监听的事件,当前只监听键盘 KeyDown 事件
  5. 回调函数 callback

callback 接受 3 个参数,分别是 CGEventTapProxy, CGEventType, CGEvent

添加 callback 函数

callback 函数可以

判断 event 类型

event 类型需要通过判断当前的 CGEventType 是否

获取 keycode

通过 get_integer_value_field 拿到 keycode

判断是否是 meta 键

通过 event.get_flags() 判断 CGEventFlags

禁用某个键

设置 key event 为 NULL , 禁用这个键,比如禁用 control

lib.rs

_19
_19
use core_graphics::event::{
_19
CGEvent, CGEventTap, CGEventTapLocation, CGEventTapOptions, CGEventTapPlacement, CGEventType::KeyDown,
_19
};
_19
_19
fn handle_key_event() {
_19
let cg_event_tap = CGEventTap::new(
_19
CGEventTapLocation::Session,
_19
// 插入队首
_19
CGEventTapPlacement::HeadInsertEventTap,
_19
// 默认行为过滤
_19
CGEventTapOptions::Default,
_19
// 监听的事件
_19
vec![CGEventType::KeyDown],
_19
|proxy: *const std::ffi::c_void, event_type: CGEventType, event: &CGEvent|{
_19
Some(event.clone())
_19
},
_19
);
_19
}

上面的代码,还要加入事件循环中才能生效


_19
use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop};
_19
_19
let current: CFRunLoop = CFRunLoop::get_current();
_19
_19
match cg_event_tap {
_19
Ok(tap) => unsafe {
_19
let loop_source = tap
_19
.mach_port
_19
.create_runloop_source(0)
_19
.expect("sames broken");
_19
_19
current.add_source(&loop_source, kCFRunLoopCommonModes.clone());
_19
_19
tap.enable();
_19
_19
CFRunLoop::run_current();
_19
},
_19
Err(_) => panic!("can't prevent key event"),
_19
}

最终得到在 mac os 下的代码如下所示

💡辅助功能

在 mac os 需要在隐私和安全性里面打开对应程序的辅助功能,该程序才能生效


_51
use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop};
_51
use core_graphics::event::{
_51
CGEvent, CGEventTap, CGEventTapLocation, CGEventTapOptions, CGEventTapPlacement, CGEventType,EventField,CGEventFlags,
_51
};
_51
_51
fn handle_key_event() {
_51
let cg_event_tap = CGEventTap::new(
_51
CGEventTapLocation::Session,
_51
// 插入队首
_51
CGEventTapPlacement::HeadInsertEventTap,
_51
// 默认行为过滤
_51
CGEventTapOptions::Default,
_51
// 监听的事件
_51
vec![CGEventType::KeyDown],
_51
|proxy: *const std::ffi::c_void, event_type: CGEventType, event: &CGEvent| match event_type {
_51
CGEventType::KeyDown => {
_51
let key = event.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE);
_51
_51
// 判断 control 键是否摁下
_51
let is_control_press =
_51
CGEventFlags::CGEventFlagControl & event.get_flags() == CGEventFlags::CGEventFlagControl;
_51
_51
if is_control_press {
_51
// 禁用这个快捷键
_51
event.set_type(CGEventType::Null);
_51
}
_51
_51
Some(event.clone())
_51
}
_51
_ => Some(event.clone()),
_51
},
_51
);
_51
_51
let current: CFRunLoop = CFRunLoop::get_current();
_51
_51
match cg_event_tap {
_51
Ok(tap) => unsafe {
_51
let loop_source = tap
_51
.mach_port
_51
.create_runloop_source(0)
_51
.expect("sames broken");
_51
_51
current.add_source(&loop_source, kCFRunLoopCommonModes.clone());
_51
_51
tap.enable();
_51
_51
CFRunLoop::run_current();
_51
},
_51
Err(_) => panic!("can't prevent key event"),
_51
}
_51
}

windows

windows 下面实现锁屏的功能实在有点复杂,除了写键盘的事件 hook , 还要修改用户的注册表

这里使用了一个第三方库rdev来实现,rdev本质上还是调用了 windows api SetWindowsHookExA 来实现的

微软官方维护了一个 windows-rs 的 crate,也可以通过它来实现相应的功能

具体使用 rdev grab 的代码就不再详细说了


_10
rdev::grab(move |ev| match ev.event_type {
_10
EventType::KeyPress(key) => {
_10
if should_restrict(key) {
_10
None
_10
} else {
_10
Some(ev)
_10
}
_10
}
_10
_ => Some(ev),
_10
});

rdev 可以拦截绝大部分 windows 的按键,但是有一些键很特殊,是不能通过写 hook 来拦截的,比如这三个键

  1. win + l 锁屏
  2. win + g 弹出 windows game
  3. ctrl + alt + delete CAD

上面三个键只能通过修改用户的注册表来实现拦截

💡管理员权限

修改注册表需要 windows 的管理员权限,必须要请求管理员权限

win + l 的注册表位于 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\SystemDisableLockWorkstation 改为 1 即可禁用锁屏

win + g 位于 HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\GameDVRAppCaptureEnabled 设置成 0 即可禁止

cad 位于 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\taskmgr.exeDebugger 设置成 Hotkey Disabled 即可禁止

如果是在 electron 里面直接使用 nodejs 的库 regedit 即可,使用 regedit 禁用 win + l 示例如下


_24
// 禁用 win + l 锁屏
_24
import regeditOrigin from "regedit"
_24
_24
const regedit = regeditOrigin.promisified
_24
_24
async function main() {
_24
const winLRegisterPath =
_24
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"
_24
_24
try {
_24
await regedit.createKey([winLRegisterPath])
_24
} catch (error) {
_24
console.error(error)
_24
}
_24
_24
await regedit.putValue({
_24
[winLRegisterPath]: {
_24
DisableLockWorkstation: {
_24
type: "REG_DWORD",
_24
value: 1,
_24
},
_24
},
_24
})
_24
}

💡注意

regedit 在调用的时候需要将 HKEY_CURRENT_USER 需要改成 HKCU

如法炮制即可禁用剩余的 win 快捷键,使用 napi-rs 打包, 用 github actions 编译成 .node 文件, 便可在 electron 中使用实现 霸屏 的功能

真不好搞 😭