Skip to content

Instantly share code, notes, and snippets.

@rf5860
Created August 26, 2024 08:45
Show Gist options
  • Save rf5860/3a366a6be7c09b8a491c7a3dae711dc8 to your computer and use it in GitHub Desktop.
Save rf5860/3a366a6be7c09b8a491c7a3dae711dc8 to your computer and use it in GitHub Desktop.
A janky OBS Lua script to display a pop-up notification using GDI when a replay is saved
local obs = obslua
local ffi = require("ffi")
local bit = require("bit")
-- Import the necessary libraries for graphics
local gdi32 = ffi.load("gdi32")
local kernel32 = ffi.load("kernel32")
local user32 = ffi.load("user32")
-- Define the necessary Windows API types and functions
ffi.cdef[[
typedef void* HWND;
typedef void* HDC;
typedef void* HGDIOBJ;
typedef HGDIOBJ HFONT;
typedef HGDIOBJ HBRUSH;
typedef HGDIOBJ HPEN;
typedef unsigned long DWORD;
typedef int BOOL;
typedef unsigned char BYTE;
typedef const char* LPCSTR;
typedef unsigned int UINT;
typedef unsigned long ULONG_PTR;
typedef ULONG_PTR UINT_PTR;
typedef ULONG_PTR WPARAM;
typedef long LPARAM;
typedef unsigned long COLORREF;
typedef struct {
long left;
long top;
long right;
long bottom;
} RECT;
HDC GetDC(HWND hWnd);
int ReleaseDC(HWND hWnd, HDC hDC);
HWND GetDesktopWindow();
HFONT CreateFontA(int cHeight, int cWidth, int cEscapement, int cOrientation, int cWeight, DWORD bItalic,
DWORD bUnderline, DWORD bStrikeOut, DWORD iCharSet, DWORD iOutPrecision, DWORD iClipPrecision,
DWORD iQuality, DWORD iPitchAndFamily, LPCSTR pszFaceName);
HFONT SelectObject(HDC hdc, HFONT hfont);
int SetBkMode(HDC hdc, int mode);
BOOL DeleteObject(HFONT hFont);
BOOL GetClientRect(HWND hWnd, RECT* lpRect);
int DrawTextA(HDC hdc, LPCSTR lpchText, int cchText, RECT* lprc, UINT format);
HBRUSH CreateSolidBrush(DWORD color);
int FillRect(HDC hdc, const RECT *lprc, HBRUSH hbr);
BOOL SetLayeredWindowAttributes(HWND hwnd, DWORD crKey, BYTE bAlpha, DWORD dwFlags);
HWND CreateWindowExA(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle,
int X, int Y, int nWidth, int nHeight, HWND hWndParent, void* hMenu, void* hInstance, void* lpParam);
BOOL ShowWindow(HWND hWnd, int nCmdShow);
BOOL UpdateWindow(HWND hWnd);
BOOL DestroyWindow(HWND hWnd);
UINT_PTR SetTimer(HWND hWnd, UINT_PTR nIDEvent, UINT uElapse, void* lpTimerFunc);
BOOL KillTimer(HWND hWnd, UINT_PTR uIDEvent);
int GetSystemMetrics(int nIndex);
BOOL SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags);
DWORD GetLastError();
DWORD FormatMessageA(DWORD dwFlags, const void* lpSource, DWORD dwMessageId, DWORD dwLanguageId, char* lpBuffer, DWORD nSize, va_list* Arguments);
BOOL PostMessageA(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
COLORREF SetTextColor(HDC hdc, COLORREF color);
HPEN CreatePen(int iStyle, int cWidth, COLORREF color);
BOOL MoveToEx(HDC hdc, int x, int y, void* lpPoint);
BOOL LineTo(HDC hdc, int x, int y);
]]
-- Constants for FormatMessage
local FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
local FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200
-- Constants for window styles
local WS_EX_LAYERED = 0x00080000
local WS_EX_TOPMOST = 0x00000008
local WS_POPUP = 0x80000000
local WS_VISIBLE = 0x10000000
local SWP_NOMOVE = 0x0002
local SWP_NOSIZE = 0x0001
local SWP_SHOWWINDOW = 0x0040
local WM_CLOSE = 0x0010
-- Global variables
local popup_window = nil
-- Function to create and show the pop-up overlay
function show_popup(title, message)
local screen_width = ffi.C.GetSystemMetrics(0) -- SM_CXSCREEN
local screen_height = ffi.C.GetSystemMetrics(1) -- SM_CYSCREEN
local popup_width = 350
local popup_height = 120
local x = screen_width - popup_width - 20
local y = 20 -- Position at the top
-- Create a layered window for transparency
popup_window = ffi.C.CreateWindowExA(
bit.bor(WS_EX_LAYERED, WS_EX_TOPMOST),
"Static",
"",
bit.bor(WS_POPUP, WS_VISIBLE),
x, y, popup_width, popup_height,
nil, nil, nil, nil
)
-- Set window transparency
ffi.C.SetLayeredWindowAttributes(popup_window, 0, 230, 2) -- Increased opacity
-- Show and update the window
ffi.C.ShowWindow(popup_window, 1)
ffi.C.UpdateWindow(popup_window)
-- Ensure the window stays on top
ffi.C.SetWindowPos(popup_window, ffi.cast("HWND", -1), 0, 0, 0, 0,
bit.bor(SWP_NOMOVE, SWP_NOSIZE, SWP_SHOWWINDOW))
-- Get the device context
local hdc = ffi.C.GetDC(popup_window)
-- Create and select the fonts
local hFontTitle = ffi.C.CreateFontA(20, 0, 0, 0, 700, 0, 0, 0, 0, 0, 0, 0, 0, "Segoe UI")
local hFontMessage = ffi.C.CreateFontA(16, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 0, 0, "Segoe UI")
-- Set transparent background
ffi.C.SetBkMode(hdc, 1)
-- Create and fill rectangle with semi-transparent background
local rect = ffi.new("RECT", {0, 0, popup_width, popup_height})
local hBrush = ffi.C.CreateSolidBrush(0x00303030) -- Dark gray background
ffi.C.FillRect(hdc, rect, hBrush)
-- Draw title
ffi.C.SelectObject(hdc, hFontTitle)
ffi.C.SetTextColor(hdc, 0x00FFFFFF) -- White text
rect.top = 10
rect.left = 15
ffi.C.DrawTextA(hdc, title, #title, rect, 0x00000000)
-- Draw message
ffi.C.SelectObject(hdc, hFontMessage)
ffi.C.SetTextColor(hdc, 0x00E0E0E0) -- Light gray text
rect.top = 45
rect.left = 15
ffi.C.DrawTextA(hdc, message, #message, rect, 0x00000000)
-- Draw a subtle border
local hPen = ffi.C.CreatePen(0, 1, 0x00505050) -- Slightly lighter gray
local oldPen = ffi.C.SelectObject(hdc, hPen)
ffi.C.MoveToEx(hdc, 0, 0, nil)
ffi.C.LineTo(hdc, popup_width - 1, 0)
ffi.C.LineTo(hdc, popup_width - 1, popup_height - 1)
ffi.C.LineTo(hdc, 0, popup_height - 1)
ffi.C.LineTo(hdc, 0, 0)
-- Clean up
ffi.C.SelectObject(hdc, oldPen)
ffi.C.DeleteObject(hFontTitle)
ffi.C.DeleteObject(hFontMessage)
ffi.C.DeleteObject(hBrush)
ffi.C.DeleteObject(hPen)
ffi.C.ReleaseDC(popup_window, hdc)
-- Set a timer to close the popup after 2 seconds
obs.timer_add(on_timer, 2000)
end
-- Function to handle OBS events
function on_event(event)
if event == obs.OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED then
print("Replay saved")
show_popup("Replay Saved", "Your replay has been successfully saved.")
end
end
-- Function to get the last error message
local function get_last_error_message()
local error_code = ffi.C.GetLastError()
local buffer = ffi.new("char[?]", 256)
local size = ffi.C.FormatMessageA(
bit.bor(FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS),
nil,
error_code,
0,
buffer,
256,
nil
)
if size == 0 then
return "Unknown error"
else
return ffi.string(buffer, size)
end
end
-- Function to handle the timer event
function on_timer()
print("Timer triggered, closing popup")
if popup_window ~= nil then
-- Post a WM_CLOSE message to the window
local result = ffi.C.PostMessageA(popup_window, WM_CLOSE, 0, 0)
if result == 0 then
local error_message = get_last_error_message()
print("Failed to post WM_CLOSE message: " .. error_message)
else
print("WM_CLOSE message posted successfully")
end
popup_window = nil
end
obs.timer_remove(on_timer)
end
-- Script load function
function script_load(settings)
obs.obs_frontend_add_event_callback(on_event)
end
-- Script unload function
function script_unload()
obs.obs_frontend_remove_event_callback(on_event)
if popup_window ~= nil then
ffi.C.DestroyWindow(popup_window)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment