차비쓰리움(ChaBCrium) [1] - 투명 윈도우 어플리케이션 만들기

박창훈 (MERHENNE)·2020년 7월 7일
1

ChaBCrium

목록 보기
1/2
post-thumbnail

평소 즐겨 보는 고차비님이 좋아하는 상어를 컴퓨터에서 항상 보실 수 있는 프로그램을 제작하기로 하였다.

우선, 방송 중이나 일반적으로 컴퓨터를 사용할 때에도 프로그램을 사용할 수 있도록 위의 썸네일과 같은 투명 어플리케이션을 만들기로 한다.


"User32.dll"

윈도우 어플리케이션을 투명하게 만들려면 윈도우 스타일을 수정해야 한다. 하지만, 유니티에서는 관련 함수를 지원하지 않는다.

"User32.dll"는 윈도우 USER 구성 요소를 구현한다. 윈도우 구성 요소는 창이나 메뉴 같은 윈도우 사용자 인터페이스의 표준 요소들을 생성하고 다룬다. "USer32.dll" 내부의 함수는 창 생성이나 관리, 그리고 창 메세지 받기 등을 수행하는 기능을 제공한다.

사용 방법
1. DllImport Attribute를 사용하기 위해 System.Runtime.InteropServices를 임포트 한다.
2.DllImport Attribute 이하에 필요한 함수는 다음 예와 같이 선언한다.

[DllImport("User32.dll")]
public static extern IntPtr GetActiveWindow();

코드

    [DllImport("User32.dll")]
    public static extern IntPtr GetActiveWindow();

    [DllImport("User32.dll", EntryPoint = "FindWindowA")]
    public static extern IntPtr FindWindow(string className, string windowName);

    [DllImport("User32.dll")]
    public static extern IntPtr GetForegroundWindow();

    [DllImport("User32.dll")]
    public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex);

    [DllImport("User32.dll")]
    public static extern uint GetWindowLong(IntPtr hWnd, int nIndex);

    [DllImport("User32.dll")]
    public static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, uint dwNewLong);

    [DllImport("User32.dll")]
    public static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);

    [DllImport("User32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
    public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

    [DllImport("User32.dll")]
    public static extern int SetLayeredWindowAttributes(IntPtr hWnd, uint crKey, byte bAlpha, uint dwFlags);

    [DllImport("Dwmapi.dll")]
    public static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins);

윈도우 창 설정에 필요한 옵션 선언

더 많은 설정이 있지만, 본 프로젝트에서 사용되었던 것만 나열한다.

이름종류설명
STYLEGWL-16윈도우 스타일
EXSTYLEGWL-20확장 스타일
POPUPWS0x80000000팝업 윈도우로 생성
VISIBLEWS0x10000000윈도우를 만들자 마자 화면에 출력
LAYEREDWS_EX0x00080000레이어드 윈도우로 생성
TRANSPARENTWS_EX0x00000020투명 윈도우로 생성
TOPMOSTWS_EX0x00000008최상위 윈도우로 생성
NOMOVESWP0x0002윈도우를 이동하지 않음
NOSIZESWP0x0001윈도우의 크기를 변경하지 않음
NOOWNERZORDERSWP0x0200소유 윈도우의 Z순서를 변경하지 않음
SHOWWINDOWSWP0x0040윈도우를 나타냄
NOACTIVATESWP0x0010윈도우를 활성화하지 않음
TOPMOSTSWPNOMOVE | NOSIZE | NOOWNERZORDER이동 불가, 사이즈 변경 불가, Z순서 변경 불가
ALPHALWA0x00000002지정한 Alpha 값으로 윈도우를 투명하게 만듬

본 프로젝트에서는 구조체로 구분하여 선언하였다.

코드

public struct GWL {
    public const int STYLE = -16;
    public const int EXSTYLE = -20;
}

public struct WS {
    public const uint POPUP = 0x80000000;
    public const uint VISIBLE = 0x10000000;
}

public struct WS_EX {
    public const uint LAYERED = 0x00080000;
    public const uint TRANSPARENT = 0x00000020;
    public const uint TOPMOST = 0x00000008;
}

public struct SWP {
    public const uint NOMOVE = 0x0002;
    public const uint NOSIZE = 0x0001;
    public const uint NOOWNERZORDER = 0x0200;
    public const uint SHOWWINDOW = 0x0040;
    public const uint NOACTIVATE = 0x0010;
    public const uint TOPMOST = NOMOVE | NOSIZE | NOOWNERZORDER;
}

public struct LWA {
    pulic const uint ALPHA = 0x00000002;
}

완성 코드

using UnityEngine;

using System;
using System.Runtime.InteropServices;

public class Window {
    public struct MARGINS {
        public int cxLeftWidth;
        public int cxRightWidth;
        public int cyTopHeight;
        public int cyBottomHeight;
    }

    #region Win API
    [DllImport("User32.dll")]
    public static extern IntPtr GetActiveWindow();

    [DllImport("User32.dll", EntryPoint = "FindWindowA")]
    public static extern IntPtr FindWindow(string className, string windowName);

    [DllImport("User32.dll")]
    public static extern IntPtr GetForegroundWindow();

    [DllImport("User32.dll")]
    public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex);

    [DllImport("User32.dll")]
    public static extern uint GetWindowLong(IntPtr hWnd, int nIndex);

    [DllImport("User32.dll")]
    public static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, uint dwNewLong);

    [DllImport("User32.dll")]
    public static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);

    [DllImport("User32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
    public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

    [DllImport("User32.dll")]
    public static extern int SetLayeredWindowAttributes(IntPtr hWnd, uint crKey, byte bAlpha, uint dwFlags);

    [DllImport("Dwmapi.dll")]
    public static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins);
    #endregion

    #region Const Value Struct
    public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
    public static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
    public static readonly IntPtr HWND_TOP = new IntPtr(0);
    public static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
    
    public struct GWL
    {
        public const int STYLE = -16;
        public const int EXSTYLE = -20;
    }

    public struct WS
    {
        public const uint POPUP = 0x80000000;
        public const uint VISIBLE = 0x10000000;
    }

    public struct WS_EX
    {
        public const uint LAYERED = 0x00080000;
        public const uint TRANSPARENT = 0x00000020;
        public const uint TOPMOST = 0x00000008;
    }

    public struct SWP
    {
        public const uint NOMOVE = 0x0002;
        public const uint NOSIZE = 0x0001;
        public const uint NOOWNERZORDER = 0x0200;
        public const uint SHOWWINDOW = 0x0040;
        public const uint NOACTIVATE = 0x0010;
        public const uint TOPMOST = NOMOVE | NOSIZE | NOOWNERZORDER;
    }

    public struct LWA
    {
        public const uint ALPHA = 0x00000002;
    }
    #endregion
}

public class TransparentWindow : MonoBehaviour
{
    protected IntPtr hWnd;
    protected uint oldWindowLong;

    protected void Awake()
    {
        hWnd = Window.GetActiveWindow();

        oldWindowLong = Window.GetWindowLong(hWnd, Window.GWL.EXSTYLE);
        oldWindowLong = oldWindowLong | Window.WS_EX.LAYERED | Window.WS_EX.TRANSPARENT;

        Window.SetWindowLong(hWnd, Window.GWL.EXSTYLE, oldWindowLong);
        Window.SetWindowPos(hWnd, Window.HWND_TOPMOST, 0, 0, 0, 0, Window.SWP.TOPMOST);

        Window.MARGINS margins = new Window.MARGINS { cxLeftWidth = -1 };
        Window.DwmExtendFrameIntoClientArea(hWnd, ref margins);
    }
}

추가 이슈

  1. 실제 어떤 기능을 하는지는 더 공부하여 추가가 필요하지만, 2019이상의 버전에서 "Use DXGI Flip Model Swapchain for D3D11" 옵션을 꺼주어야 투명 윈도우가 적용된다.

  2. 다른 윈도우로 포커스가 옮겨갈 때, 어플리케이션이 내려가는 이슈가 있다. 이 때, "Visible In Background" 옵션을 켜주면 된다.

  3. 2번과 동일하게 다른 포커스로 옮겨갔을 때도 계쏙해서 동작하도록 하기 위해서 "Run In Background" 옵션을 켜주어야 한다.

추가 이슈에 대한 설정

결과

실행 결과


참고

  1. 위키백과 - 윈도우 라이브러리 파일
  2. 유니티 커뮤니티

0개의 댓글