Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@ If your development setup lives on an external drive, this probably sounds famil

## What it will never do

- Force-quit your apps or services, or force-eject without a separate confirmation.
- Force-close apps or processes, or force-eject, without a separate confirmation of the exact scope and data-loss risk.
- Close something just because its name matches. It only acts on a program it can see holding files on this drive.
- Tell you "nobody is using it" or "safe to unplug" without checking. If a check fails or times out, it tells you.
- Touch system processes or other users' processes.
- Ask for admin rights. When a system setting needs changing, it gives you a command to copy instead of running it for you.

Verified terminal and background tasks can also be selected explicitly for termination; these choices start unchecked and may interrupt work. Unverified processes remain manual. If an app is waiting for you to save, use **Continue checking and eject** afterwards. System refusals retain the blocking PID and error details.

After preflight, **Select all / Deselect all** covers verified current-user apps, services and other tasks. **Force close selected…** closes only the selected occupants and rescans without ejecting. **Force close and force eject…** is available without first failing ordinary eject: its separate confirmation lists occupants, images and all affected volumes. With nothing selected, the action reads **Force eject…** and explicitly closes no apps or processes. System/other-user/unverified occupants cannot be selected. Force closing is unavailable on multi-volume targets or while related simulators are active. Scope changes require review again; failed or unverified process closure stops disk actions. App closure can also lose unsaved work on other disks.

Writable disk images can be detached normally after confirmation. If normal detach or eject fails, **Force eject…** shows the affected images and volumes and asks you to accept the risk of lost unsaved data or damaged images. Force is never automatic; timeout results are checked before further action. A forced result is clearly labelled.

## Language
Expand Down
4 changes: 3 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@

## 它不会做的事

- 不会强杀程序或后台服务,也不会未经单独确认就强制弹出。
- 不会未经单独确认具体范围和数据丢失风险,就强制关闭应用、进程或强制弹出。
- 不会因为名字对得上就关掉一个程序,只处理它亲眼看到正在占用这块盘的。
- 没检查清楚,不会说「没人在用」或「可以拔了」。检查失败或超时,会直接告诉你。
- 不碰系统进程和其他用户的进程。
- 不要管理员权限。需要改系统设置时,只给你一条能复制的命令,不替你执行。

身份已核验的终端任务和后台任务也能逐项勾选终止,默认不勾选,并提醒可能中断工作;无法核验的对象仍需手动处理。应用等待保存时,处理后可以「继续检查并弹出」;系统拒绝时会保留阻塞 PID 和错误详情。

预检完成后,可「全选/取消全选」身份和占用已核验的当前用户应用、服务及其他任务。「强制关闭所选项…」仅关闭选中对象,再重新检测,不弹出磁盘;「强制关闭并强制弹出…」无需先让普通弹出失败,单独确认页列明占用者、映像及所有受影响卷。零选中时入口显示「强制弹出…」,明确不会关闭任何应用或进程。系统、其他用户及无法核验的对象不可选。多卷磁盘或仍有相关模拟器运行时,不支持强制关闭;范围变化需要重新确认,关闭失败或结果未确认则停止磁盘操作。关闭整个应用也可能丢失其他磁盘上的未保存工作。

可写磁盘映像也能在确认后普通推出。普通推出或弹出失败后,可点击「强制弹出…」,核对受影响的映像和卷,并确认可能丢失未保存数据、损坏映像的风险。强制不会自动发生;超时先核对结果。完成后会明确标注是否使用过强制操作。

## 语言
Expand Down
4 changes: 2 additions & 2 deletions Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
<!-- Semantic version, matched by the git tag (v1.0.0) and compared against
the latest GitHub release by UpdateChecker. -->
<key>CFBundleShortVersionString</key>
<string>1.4.1</string>
<string>1.5.0</string>
<key>CFBundleVersion</key>
<string>17</string>
<string>18</string>
<key>LSMinimumSystemVersion</key>
<string>14.0</string>
<!-- Required to ask Android Studio / Xcode to quit via AppleScript. Without this
Expand Down
4 changes: 3 additions & 1 deletion Sources/DevDiskKit/Core/EjectFailure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ struct EjectFailure: Equatable {
var allowsForce = false
var relatedImages: [DiskImage] = []
var pendingImage: DiskImage? = nil
var forceRequested: Set<ProcessIdentity> = []

private var stageLabel: String {
let keys = ["validate": "ejectflow.verify.target.and.scope", "apps": "ejectflow.request.apps.to.quit",
let keys = ["force-processes": "forceprocess.close.selected", "validate": "ejectflow.verify.target.and.scope", "apps": "ejectflow.request.apps.to.quit",
"simulators": "ejectflow.shutdown.related.simulators",
"daemons": "ejectflow.stop.approved.background.services", "images": "ejectflow.eject.read.only.disk.images",
"recheck": "ejectflow.recheck.open.files", "unmount": "ejectflow.ask.macos.to.eject",
Expand All @@ -36,6 +37,7 @@ struct EjectFailure: Equatable {
M("ejectfailure.disk", target.physicalDisk).text, M("ejectfailure.stage", stageLabel).text,
M("ejectfailure.pid", blockingPID.map(String.init) ?? L("ejectfailure.unknown")).text,
M("ejectflow.apps.quit.service.processes.stopped.images.ejected.requests", completedApps, completedProcesses, completedImages).text]
if !forceRequested.isEmpty { lines.append(L("forceprocess.requests", forceRequested.count)) }
if forceUsed { lines.append(L("ejectforce.used")) }
if let commandExitCode {
lines.append(M("ejectfailure.command.result", commandExitCode,
Expand Down
Loading