Skip to content
8 changes: 6 additions & 2 deletions Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"Quit Signal" = "Quit Signal";
"Signal – Status Light" = "Signal – Status Light";
"Status Control" = "Status Control";
"Claude History" = "Claude History";
"AI History" = "AI History";
"Breathing Effect" = "Breathing Effect";
"No conversation history" = "No conversation history";
"Loading..." = "Loading...";
Expand All @@ -27,4 +27,8 @@
"Could not locate the 'sgnl' binary inside the application bundle resources." = "Could not locate the 'sgnl' binary inside the application bundle resources.";
"An error occurred during installation:\n%@" = "An error occurred during installation:\n%@";
"OK" = "OK";

"Screen Flash Effect" = "Screen Flash Effect";
"Gaming-style overlay on color change" = "Gaming-style overlay on color change";
"1 Flash" = "1 Flash";
"3 Flashes" = "3 Flashes";
"Continuous" = "Continuous";
8 changes: 6 additions & 2 deletions Resources/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"Quit Signal" = "退出";
"Signal – Status Light" = "Signal – 状态指示灯";
"Status Control" = "状态控制";
"Claude History" = "Claude 历史";
"AI History" = "AI 历史";
"Breathing Effect" = "呼吸效果";
"No conversation history" = "暂无对话历史";
"Loading..." = "加载中...";
Expand All @@ -27,4 +27,8 @@
"Could not locate the 'sgnl' binary inside the application bundle resources." = "无法在应用包资源中找到 'sgnl' 二进制文件。";
"An error occurred during installation:\n%@" = "安装过程中发生错误:\n%@";
"OK" = "确定";

"Screen Flash Effect" = "屏幕闪烁效果";
"Gaming-style overlay on color change" = "颜色改变时显示游戏风格的屏幕边缘覆层";
"1 Flash" = "闪烁 1 次";
"3 Flashes" = "闪烁 3 次";
"Continuous" = "持续呼吸";
39 changes: 30 additions & 9 deletions Sources/Signal/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
applyIcon()
setupPopover()

// Start screen overlay manager
ScreenOverlayManager.shared.start()

// Listen for color-change commands from sgnl
DistributedNotificationCenter.default().addObserver(
self,
Expand All @@ -55,13 +58,15 @@ class AppDelegate: NSObject, NSApplicationDelegate {

@objc private func appearanceDidChange() {
applyIcon()
popover?.appearance = NSApp.effectiveAppearance
}

// MARK: Popover Setup

private func setupPopover() {
popover = NSPopover()
popover.behavior = .transient
popover.appearance = NSApp.effectiveAppearance

let hostingController = NSHostingController(rootView: MainView(viewModel: viewModel))
hostingController.preferredContentSize = NSSize(width: 350, height: 480)
Expand All @@ -83,6 +88,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {
viewModel.onColorChange = { [weak self] color in
self?.switchColor(to: color)
}
viewModel.onFlashModeChange = { [weak self] mode in
guard let self = self else { return }
if mode != .off {
ScreenOverlayManager.shared.triggerFlash(color: self.currentColor, mode: mode)
} else {
ScreenOverlayManager.shared.triggerFlash(color: .black, mode: .off)
}
}
viewModel.onBreathingChange = { [weak self] enabled in
guard let self = self else { return }
if self.breathingEnabled != enabled {
Expand Down Expand Up @@ -162,7 +175,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
try fileManager.copyItem(at: sourceURL, to: targetURL)

// Make executable (chmod +x)
chmod(targetURL.path, 0o755)
if chmod(targetURL.path, 0o755) != 0 {
print("Warning: failed to chmod target binary at \(targetURL.path)")
}

// Show success alert
let alert = NSAlert()
Expand Down Expand Up @@ -190,20 +205,26 @@ class AppDelegate: NSObject, NSApplicationDelegate {

// MARK: Color Switching

private func switchColor(to newColor: LightColor) {
guard newColor != currentColor else { return }
currentColor = newColor
viewModel.currentColor = newColor
applyIcon()
private func switchColor(to newColor: LightColor, forceFlash: Bool = false) {
let changed = (newColor != currentColor)
if changed {
currentColor = newColor
viewModel.currentColor = newColor
applyIcon()
}

if changed || forceFlash {
ScreenOverlayManager.shared.triggerFlash(color: newColor, mode: viewModel.screenFlashMode)
}
}

@objc private func toggleBreathing() {
private func toggleBreathing() {
breathingEnabled.toggle()
viewModel.breathingEnabled = breathingEnabled
applyIcon()
}

// MARK: Distributed Notification Handler
// MARK: - Distributed Notification Handler

@objc private func didReceiveColorChange(_ notification: Notification) {
guard let userInfo = notification.userInfo,
Expand All @@ -212,7 +233,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return
}
DispatchQueue.main.async { [weak self] in
self?.switchColor(to: color)
self?.switchColor(to: color, forceFlash: true)
}
}

Expand Down
Loading
Loading