Created
August 26, 2024 08:45
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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