using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
using System.Collections.Generic;
// 이 스크립트는 Thomas의 '주 모니터를 제외한 모든 모니터 어둡게 하기(Dim All Monitors Except Primary)' 기능을 기반으로 제작된 NetMage의 '주 모니터를 제외한 모든 모니터를 검은색으로 만들기' 기능을 전적으로 참고하여 작성되었습니다."
// This script is entirely based on NetMage's 'Blacken All Monitors Except Primary' function, which is based on Thomas's 'Dim All Monitors Except Primary' function.
public static class DisplayFusionFunction {
private static readonly string SettingName = "BlackenMonitors_Run";
public static void Run(IntPtr windowHandle) {
// 설정값을 '실행 중'에서 '중단'으로, 또는 그 반대로 전환(토글)합니다.
// Toggles the setting value from 'running' to 'stopped', or vice versa.
ToggleSetting();
if (RunFadeMonitors()) {
// 모니터 선택 창을 띄웁니다.
// Opens the monitor selection window.
using (var selector = new MonitorSelectorForm()) {
var result = selector.ShowDialog();
if (result == DialogResult.OK && selector.SelectedMonitorIds.Count > 0) {
// 선택한 모니터들을 관리 목록에 추가합니다.
// Adds the selected monitors to the management list.
var forms = new List<Form>();
foreach (uint monitorId in selector.SelectedMonitorIds) {
forms.Add(new TransparentForm(monitorId));
}
// 사용자 정의 애플리케이션 컨텍스트를 사용하여 목록에 추가된 폼(검은 화면)을 엽니다.
// Opens the forms (black screens) added to the list using a custom application context.
Application.Run(new MultipleFormApplicationContext(forms));
} else {
// 취소했거나 선택된 모니터가 없으면 설정을 다시 원래대로(꺼짐 상태로) 돌려놓습니다.
// If cancelled or no monitor is selected, reverts the setting to its original state (off).
ToggleSetting();
}
}
}
}
// 스크립트의 현재 실행 상태(켜짐/꺼짐)를 확인하는 함수입니다.
// Function to check the current execution status (on/off) of the script.
private static bool RunFadeMonitors() {
return BFS.ScriptSettings.ReadValue(SettingName) == "run";
}
// 스크립트 설정을 '실행 중' 또는 '중단'으로 전환하는 함수입니다.
// Function to toggle the script setting to 'running' or 'stopped'.
private static void ToggleSetting() {
BFS.ScriptSettings.WriteValue(SettingName, RunFadeMonitors() ? "not" : "run");
}
// 여러 개의 폼(창)을 여는 것을 지원하도록 클래스를 확장합니다.
// Extends the class to support opening multiple forms (windows).
private class MultipleFormApplicationContext : ApplicationContext {
internal MultipleFormApplicationContext(List<Form> forms) {
// 각 폼을 열고, 폼이 닫힐 때 발생하는 이벤트를 추가합니다.
// Opens each form and adds an event that occurs when the form is closed.
foreach(Form form in forms) {
form.FormClosed += OnFormClosed;
form.Show();
}
}
// 모든 폼이 닫히면 애플리케이션을 안전하게 종료합니다.
// Safely exits the application when all forms are closed.
private void OnFormClosed(object sender, EventArgs e) {
if(Application.OpenForms.Count == 0)
ExitThread();
}
}
// 모니터 선택을 위한 폼 클래스
// Form class for monitor selection.
private class MonitorSelectorForm : Form {
public List<uint> SelectedMonitorIds { get; private set; }
private FlowLayoutPanel panel;
private Button btnOk;
private Button btnCancel;
public MonitorSelectorForm() {
SelectedMonitorIds = new List<uint>();
InitializeComponent();
}
private void InitializeComponent() {
// 제목
// Title
this.Text = "선택";
// 고정 사이즈 제거 및 AutoSize 설정
// Remove fixed size and set AutoSize.
this.AutoSize = true;
this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
this.StartPosition = FormStartPosition.CenterScreen;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
// 폼 패딩 제거 (TableLayoutPanel에서 처리)
// Remove form padding (handled in TableLayoutPanel).
this.Padding = new Padding(0);
// 메인 레이아웃 패널 (TableLayoutPanel 사용으로 중앙 정렬)
// Main layout panel (Use TableLayoutPanel for center alignment).
TableLayoutPanel mainLayout = new TableLayoutPanel();
mainLayout.AutoSize = true;
mainLayout.AutoSizeMode = AutoSizeMode.GrowAndShrink;
mainLayout.ColumnCount = 1;
mainLayout.RowCount = 2;
mainLayout.Padding = new Padding(10); // 10px 여백 (10px padding)
// 모니터 리스트 패널
// Monitor list panel.
panel = new FlowLayoutPanel();
panel.AutoSize = true;
panel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
panel.FlowDirection = FlowDirection.TopDown;
panel.WrapContents = false;
panel.Margin = new Padding(0, 0, 0, 20); // 버튼과의 간격 (Spacing with buttons)
// 체크박스들을 중앙 정렬하기 위해 Anchor 설정은 FlowLayout 내부에서 적용되지 않음.
// 하지만 panel 자체가 TableLayout 내에서 중앙 정렬됨.
// 모든 모니터 ID를 가져와서 체크박스 생성
// Get all monitor IDs and create checkboxes.
uint[] monitorIds = BFS.Monitor.GetMonitorIDs();
int index = 1;
foreach (uint id in monitorIds) {
CheckBox cb = new CheckBox();
// 해상도 정보 제거
// Remove resolution information.
cb.Text = $"Monitor {index}";
cb.Tag = id;
cb.AutoSize = true;
cb.Margin = new Padding(3);
panel.Controls.Add(cb);
index++;
}
// 버튼 패널 (수평 정렬)
// Button panel (horizontal alignment).
FlowLayoutPanel buttonPanel = new FlowLayoutPanel();
buttonPanel.FlowDirection = FlowDirection.LeftToRight;
buttonPanel.AutoSize = true;
buttonPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
buttonPanel.WrapContents = false;
buttonPanel.Margin = new Padding(0);
//확인/취소버튼
//OK/Cancel botton
btnOk = new Button();
btnOk.Text = "확인";
btnOk.DialogResult = DialogResult.OK;
btnOk.AutoSize = true;
btnOk.Margin = new Padding(0, 0, 10, 0); // 버튼 사이 간격 (Spacing between buttons)
btnOk.Click += BtnOk_Click;
btnCancel = new Button();
btnCancel.Text = "취소";
btnCancel.DialogResult = DialogResult.Cancel;
btnCancel.AutoSize = true;
btnCancel.Margin = new Padding(0);
buttonPanel.Controls.Add(btnOk);
buttonPanel.Controls.Add(btnCancel);
// 메인 레이아웃에 추가 (Anchor = None으로 설정하여 셀 내에서 중앙 정렬)
// Add to main layout (Set Anchor = None to center align within the cell).
mainLayout.Controls.Add(panel, 0, 0);
panel.Anchor = AnchorStyles.None;
mainLayout.Controls.Add(buttonPanel, 0, 1);
buttonPanel.Anchor = AnchorStyles.None;
this.Controls.Add(mainLayout);
this.AcceptButton = btnOk;
this.CancelButton = btnCancel;
}
private void BtnOk_Click(object sender, EventArgs e) {
SelectedMonitorIds.Clear();
foreach (Control c in panel.Controls) {
if (c is CheckBox cb && cb.Checked) {
SelectedMonitorIds.Add((uint)cb.Tag);
}
}
}
}
// 검은색 화면을 구현하기 위해 폼 클래스를 확장합니다.
// Extends the Form class to implement the black screen.
private class TransparentForm : Form {
private uint MonitorId; // 어떤 모니터에 띄울지 결정 (Decides which monitor to display on)
private uint FadeWait; // 어두워지는 속도 대기 시간 (Fade speed wait time)
private decimal TransparentIncrement; // 투명도 변화 단계 (Transparency change step)
private IntPtr HandleTHREADSAFE; // 스레드 안전한 핸들값 (Thread-safe handle value)
private decimal Transparency; // 현재 투명도 상태 (Current transparency state)
internal TransparentForm(uint monitorId) {
MonitorId = monitorId;
Transparency = 0m;
const decimal FadeTime = 0.2m; // 어두워지는 데 걸리는 시간 (0.2초) (Time to fade: 0.2s)
TransparentIncrement = 10m; // 투명도 변화 단위 (10%) (Transparency change unit: 10%)
FadeWait = (uint)(TransparentIncrement * 10m * FadeTime);
SuspendLayout();
// 폼 레이아웃 설정: 검은색 배경, 테두리 없음, 작업표시줄 표시 안 함
// Form layout settings: Black background, no border, do not show in taskbar.
BackColor = Color.Black;
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
// 해당 모니터의 크기와 위치에 맞게 창 배치
// Position the window according to the size and location of the monitor.
Rectangle bounds = BFS.Monitor.GetMonitorBoundsByID(MonitorId);
Location = new Point(bounds.X, bounds.Y);
Size = new Size(bounds.Width, bounds.Height);
StartPosition = FormStartPosition.Manual;
// 창을 항상 위로 설정하고 투명도 적용 시작
// Set the window to always be on top and start applying transparency.
BFS.Window.SetAlwaysOnTop(Handle, true);
BFS.Window.SetTransparency(Handle, Transparency);
// 폼이 로드될 때 실행될 이벤트 설정
// Set the event to be executed when the form is loaded.
Load += Form_Load;
ResumeLayout(false);
}
private void Form_Load(object sender, EventArgs e) {
HandleTHREADSAFE = Handle;
// 윈도우 스타일을 설정하여 이 창이 사용자 입력(클릭 등)을 무시하고 통과시키도록 만듭니다.
// (터치스크린이어도 이 창 뒤에 있는 앱을 조작할 수 있게 해줍니다.)
// Sets the window style so that this window ignores and passes through user input (clicks, etc.).
// (Allows manipulation of apps behind this window even with a touchscreen.)
uint style = (uint)BFS.Window.GetWindowStyleEx(Handle) | (uint)BFS.WindowEnum.WindowStyleEx.WS_EX_TRANSPARENT | (uint)BFS.WindowEnum.WindowStyleEx.WS_EX_LAYERED;
BFS.Window.SetWindowStyleEx((BFS.WindowEnum.WindowStyleEx)style, Handle);
// 종료 이벤트를 감시하기 위한 별도의 스레드를 시작합니다.
// Starts a separate thread to monitor the exit event.
new Thread(new ThreadStart(ExitListener)).Start();
}
private void ExitListener() {
try {
while(true) {
if(RunFadeMonitors()) {
// 점점 어둡게 만들기 (Fade to Black)
if (Transparency < 100m) {
Transparency += TransparentIncrement;
BFS.Window.SetTransparency(HandleTHREADSAFE, Transparency);
}
BFS.General.ThreadWait((Transparency < 100m) ? FadeWait : 250u);
}
else {
if (this.Transparency > 0m) {
// 점점 밝게 만들기 (Fade back in)
Transparency -= TransparentIncrement;
BFS.Window.SetTransparency(HandleTHREADSAFE, Transparency);
BFS.General.ThreadWait((Transparency < 100m) ? FadeWait : 250u);
}
else {
// 완전히 밝아지면 창을 닫고 스레드 종료
// When fully bright, close the window and end the thread.
try {
this.Invoke((MethodInvoker) delegate {
Close();
});
} catch { }
break;
}
}
}
} catch { }
}
}
}