Add NightShot source

This commit is contained in:
f1den committed 2026-07-31 17:03:49 +03:00
1 parent 0174ecaf32
commit dfcbacfb0c
33 files changed
+8358 -1

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <windows.h>
inline constexpr UINT WM_NIGHTSHOT_SCREENSHOT_COPIED = WM_APP + 12;
inline constexpr UINT WM_NIGHTSHOT_OPEN_SETTINGS = WM_APP + 13;
inline constexpr UINT WM_NIGHTSHOT_SETTINGS_CHANGED = WM_APP + 14;
inline constexpr UINT WM_NIGHTSHOT_UPLOAD_FINISHED = WM_APP + 15;
inline constexpr UINT WM_NIGHTSHOT_BEGIN_GIF = WM_APP + 16;
inline constexpr UINT WM_NIGHTSHOT_GIF_FINISHED = WM_APP + 17;
inline constexpr UINT WM_NIGHTSHOT_SCREENSHOT_SAVED = WM_APP + 18;
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "config.hpp"
#include <windows.h>
struct ScreenCapture {
HDC memoryDc = nullptr;
HBITMAP bitmap = nullptr;
HGDIOBJ oldBitmap = nullptr;
void* pixels = nullptr;
int x = 0;
int y = 0;
int width = 0;
int height = 0;
int stride = 0;
ScreenCapture() = default;
ScreenCapture(const ScreenCapture&) = delete;
ScreenCapture& operator=(const ScreenCapture&) = delete;
ScreenCapture(ScreenCapture&& other) noexcept;
ScreenCapture& operator=(ScreenCapture&& other) noexcept;
~ScreenCapture();
[[nodiscard]] bool valid() const noexcept { return memoryDc && bitmap && width > 0 && height > 0; }
void reset() noexcept;
};
[[nodiscard]] ScreenCapture captureVirtualDesktop();
+119
View File
@@ -0,0 +1,119 @@
#pragma once
#include "config.hpp"
#include <windows.h>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cwctype>
#include <iomanip>
#include <sstream>
#include <string>
namespace color_picker_math {
struct Hsv {
float h = 0.0f; // [0, 360)
float s = 0.0f; // [0, 1]
float v = 0.0f; // [0, 1]
};
inline Hsv rgbToHsv(BYTE red, BYTE green, BYTE blue) {
const float r = static_cast<float>(red) / 255.0f;
const float g = static_cast<float>(green) / 255.0f;
const float b = static_cast<float>(blue) / 255.0f;
const float maximum = std::max({r, g, b});
const float minimum = std::min({r, g, b});
const float delta = maximum - minimum;
Hsv result;
result.v = maximum;
result.s = maximum <= 0.0f ? 0.0f : delta / maximum;
if (delta <= 0.00001f) {
result.h = 0.0f;
} else if (maximum == r) {
result.h = 60.0f * std::fmod((g - b) / delta, 6.0f);
} else if (maximum == g) {
result.h = 60.0f * (((b - r) / delta) + 2.0f);
} else {
result.h = 60.0f * (((r - g) / delta) + 4.0f);
}
if (result.h < 0.0f) result.h += 360.0f;
if (result.h >= 360.0f) result.h = std::fmod(result.h, 360.0f);
return result;
}
inline COLORREF hsvToRgb(float hue, float saturation, float value) {
hue = std::fmod(hue, 360.0f);
if (hue < 0.0f) hue += 360.0f;
saturation = std::clamp(saturation, 0.0f, 1.0f);
value = std::clamp(value, 0.0f, 1.0f);
const float chroma = value * saturation;
const float section = hue / 60.0f;
const float x = chroma * (1.0f - std::fabs(std::fmod(section, 2.0f) - 1.0f));
const float m = value - chroma;
float r = 0.0f;
float g = 0.0f;
float b = 0.0f;
if (section < 1.0f) { r = chroma; g = x; }
else if (section < 2.0f) { r = x; g = chroma; }
else if (section < 3.0f) { g = chroma; b = x; }
else if (section < 4.0f) { g = x; b = chroma; }
else if (section < 5.0f) { r = x; b = chroma; }
else { r = chroma; b = x; }
const auto channel = [m](float component) -> BYTE {
return static_cast<BYTE>(std::clamp(static_cast<int>(std::lround((component + m) * 255.0f)), 0, 255));
};
return RGB(channel(r), channel(g), channel(b));
}
inline std::uint32_t toArgb(COLORREF color, BYTE alpha) {
return (static_cast<std::uint32_t>(alpha) << 24u) |
(static_cast<std::uint32_t>(GetRValue(color)) << 16u) |
(static_cast<std::uint32_t>(GetGValue(color)) << 8u) |
static_cast<std::uint32_t>(GetBValue(color));
}
inline COLORREF colorFromArgb(std::uint32_t argb) {
return RGB((argb >> 16u) & 0xFFu, (argb >> 8u) & 0xFFu, argb & 0xFFu);
}
inline BYTE alphaFromArgb(std::uint32_t argb) {
return static_cast<BYTE>((argb >> 24u) & 0xFFu);
}
inline std::wstring hexRgba(COLORREF color, BYTE alpha) {
std::wostringstream stream;
stream << L'#' << std::uppercase << std::hex << std::setfill(L'0')
<< std::setw(2) << static_cast<unsigned int>(GetRValue(color))
<< std::setw(2) << static_cast<unsigned int>(GetGValue(color))
<< std::setw(2) << static_cast<unsigned int>(GetBValue(color))
<< std::setw(2) << static_cast<unsigned int>(alpha);
return stream.str();
}
inline bool parseHexRgba(std::wstring text, COLORREF& color, BYTE& alpha) {
if (!text.empty() && text.front() == L'#') text.erase(text.begin());
if (text.size() != 6 && text.size() != 8) return false;
for (wchar_t ch : text) {
if (!std::iswxdigit(static_cast<wint_t>(ch))) return false;
}
wchar_t* end = nullptr;
const unsigned long long value = std::wcstoull(text.c_str(), &end, 16);
if (!end || *end != L'\0') return false;
if (text.size() == 6) {
color = RGB((value >> 16u) & 0xFFu, (value >> 8u) & 0xFFu, value & 0xFFu);
alpha = 255;
} else {
color = RGB((value >> 24u) & 0xFFu, (value >> 16u) & 0xFFu, (value >> 8u) & 0xFFu);
alpha = static_cast<BYTE>(value & 0xFFu);
}
return true;
}
} // namespace color_picker_math
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#if !defined(_WIN32)
# error "NightShot targets Windows."
#endif
// The distributed build is intentionally Win64-only. Developers may explicitly
// opt into a compatibility build for testing, but build.bat never does this.
#if !defined(_WIN64) && !defined(NIGHTSHOT_ALLOW_WIN32)
# error "NightShot is Win64-only. Define NIGHTSHOT_ALLOW_WIN32 only for unsupported compatibility testing."
#endif
#ifndef UNICODE
# define UNICODE
#endif
#ifndef _UNICODE
# define _UNICODE
#endif
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include "config.hpp"
#include "gif_recorder.hpp"
#include "settings.hpp"
#include <windows.h>
namespace gif_options_window {
// Native modal Win32 window. Returns true when the user presses Start.
[[nodiscard]] bool show(HWND owner, const settings::AppSettings& defaults,
gif_recorder::Options& options);
} // namespace gif_options_window
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include "config.hpp"
#include <windows.h>
#include <string>
namespace gif_recorder {
struct Options {
int fps = 30;
int quality = 8;
// 0 means manual stop. hardLimitSeconds is always enforced.
int durationSeconds = 0;
int hardLimitSeconds = 30;
bool captureCursor = true;
};
struct StartRequest {
RECT screenRect{};
Options options{};
HWND notificationTarget = nullptr;
};
struct Result {
bool success = false;
std::wstring path;
std::wstring message;
int frameCount = 0;
int elapsedMilliseconds = 0;
};
[[nodiscard]] bool start(const StartRequest& request);
void stop();
[[nodiscard]] bool active();
// Called by the main window after consuming WM_NIGHTSHOT_GIF_FINISHED.
void releaseFinished();
void shutdown();
} // namespace gif_recorder
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "config.hpp"
#include "settings.hpp"
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
#include <string>
#include <vector>
[[nodiscard]] int findEncoderClsid(const WCHAR* mimeType, CLSID* clsid);
[[nodiscard]] bool saveBitmapAsPng(Gdiplus::Bitmap& bitmap, const std::wstring& path);
[[nodiscard]] bool saveBitmapAsJpeg(Gdiplus::Bitmap& bitmap, const std::wstring& path, int quality);
[[nodiscard]] bool copyBitmapToClipboard(HWND owner, Gdiplus::Bitmap& bitmap);
[[nodiscard]] bool encodeBitmapAsPng(Gdiplus::Bitmap& bitmap, std::vector<BYTE>& output);
// Reserves a non-existing incremented file name with CREATE_NEW. The returned
// empty file belongs to the caller and may safely be filled/overwritten.
[[nodiscard]] std::wstring reserveIncrementedPath(
const std::wstring& directory, const wchar_t* prefix,
const wchar_t* extension);
[[nodiscard]] bool saveScreenshotIncremented(
Gdiplus::Bitmap& bitmap, const settings::AppSettings& configuration,
std::wstring& savedPath);
enum class PrintBitmapResult { Printed, Cancelled, Failed };
[[nodiscard]] PrintBitmapResult printBitmap(HWND owner, Gdiplus::Bitmap& bitmap);
[[nodiscard]] std::wstring choosePngPath(HWND owner);
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "config.hpp"
#include <windows.h>
namespace notifications {
// Registers the portable executable as a Start-menu notification source and
// initializes the Windows Runtime notification API. Failure is non-fatal.
[[nodiscard]] bool initialize();
[[nodiscard]] bool show(const wchar_t* title, const wchar_t* body);
[[nodiscard]] bool showScreenshotCopied();
void shutdown();
} // namespace notifications
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "capture.hpp"
#include "config.hpp"
#include <windows.h>
enum class OverlayMode {
Screenshot,
GifQuick,
};
class OverlayWindow {
public:
static HWND create(HINSTANCE instance, ScreenCapture&& capture,
HWND notificationTarget,
OverlayMode mode = OverlayMode::Screenshot);
private:
OverlayWindow(ScreenCapture&& capture, HWND notificationTarget,
OverlayMode mode);
~OverlayWindow();
OverlayWindow(const OverlayWindow&) = delete;
OverlayWindow& operator=(const OverlayWindow&) = delete;
static LRESULT CALLBACK windowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT handleMessage(UINT message, WPARAM wParam, LPARAM lParam);
struct Impl;
Impl* impl_;
int dispatchDepth_ = 0;
bool deletePending_ = false;
};
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#define IDI_APP_ICON 101
#define IDR_APP_MANIFEST 1
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#include "config.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
namespace settings {
inline constexpr std::size_t kSavedColorCount = 48;
enum class ScreenshotFormat : int {
Png = 0,
Jpeg = 1,
};
struct AppSettings {
bool notificationsEnabled = true;
bool startWithWindows = true;
bool silentAdmin = false;
bool copyUploadedUrl = true;
bool openUploadedUrl = false;
bool closeEditorAfterUpload = true;
std::wstring uploadUrl;
std::wstring uploadName = L"Screenshot1";
std::wstring multipartField = L"file";
// Files are saved automatically with non-destructive incrementing names:
// Screenshot_N.png/.jpg and GIFShot_N.gif.
std::wstring saveDirectory;
ScreenshotFormat screenshotFormat = ScreenshotFormat::Png;
int jpegQuality = 92;
int gifFps = 30;
int gifQuality = 8;
int gifMaxDurationSeconds = 30;
bool gifCaptureCursor = true;
// Drawing widths are physical pixels and intentionally persist immediately
// when the mouse wheel changes the active tool.
int penWidth = 3;
int lineWidth = 3;
int arrowWidth = 3;
int rectangleWidth = 3;
int markerWidth = 15;
// ARGB values. Empty slots remain available for one-click saving from the
// in-editor 8x6 color picker.
std::array<std::uint32_t, kSavedColorCount> paletteArgb{};
std::array<bool, kSavedColorCount> paletteOccupied{};
};
[[nodiscard]] AppSettings load();
[[nodiscard]] bool save(const AppSettings& value);
[[nodiscard]] bool exists();
[[nodiscard]] std::wstring filePath();
[[nodiscard]] std::wstring defaultSaveDirectory();
[[nodiscard]] std::wstring normalizeEndpoint(std::wstring value);
} // namespace settings
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "config.hpp"
#include <windows.h>
namespace settings_window {
void show(HWND owner);
[[nodiscard]] bool handleDialogMessage(MSG* message);
[[nodiscard]] HWND window();
} // namespace settings_window
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "config.hpp"
#include <windows.h>
#include <string>
namespace startup {
[[nodiscard]] bool isProcessElevated();
// Returns true only in the elevated instance that should continue running.
// A non-elevated launch starts one elevated copy through UAC and returns false.
// When an existing NightShot window is already running, it returns false without
// producing another UAC prompt.
[[nodiscard]] bool ensureElevated(const wchar_t* mainWindowClass);
// Normal mode stores an HKCU Run entry. At logon that starts a tiny unelevated
// instance which immediately requests UAC once. Silent mode replaces it with a
// highest-privilege Task Scheduler task, so no UAC appears at logon.
[[nodiscard]] bool applyAutostart(bool enabled, bool silentAdmin,
std::wstring* error = nullptr);
} // namespace startup
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <windows.h>
namespace theme {
// Graphite / indigo palette from the supplied native GDI+ UI reference.
inline constexpr COLORREF kAccent = RGB(59, 130, 246);
inline constexpr COLORREF kAccentEnd = RGB(139, 92, 246);
inline constexpr COLORREF kAccentHover = RGB(96, 165, 250);
inline constexpr COLORREF kWindow = RGB(18, 18, 24);
inline constexpr COLORREF kPanel = RGB(24, 24, 34);
inline constexpr COLORREF kPanelTop = RGB(30, 30, 42);
inline constexpr COLORREF kPanelBottom = RGB(19, 19, 28);
inline constexpr COLORREF kPanelHover = RGB(43, 43, 57);
inline constexpr COLORREF kPanelActive = RGB(59, 70, 108);
inline constexpr COLORREF kBorder = RGB(60, 60, 80);
inline constexpr COLORREF kText = RGB(240, 240, 250);
inline constexpr COLORREF kMuted = RGB(150, 150, 170);
inline constexpr COLORREF kSelectionBorder = RGB(96, 165, 250);
// Annotation widths stay in physical pixels. Toolbar metrics are calculated
// per monitor from its effective DPI in overlay.cpp.
inline constexpr int kStrokeWidth = 3;
inline constexpr int kMarkerWidth = 15;
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "config.hpp"
#include "settings.hpp"
#include <windows.h>
#include <string>
#include <vector>
namespace uploader {
struct Result {
bool success = false;
DWORD httpStatus = 0;
std::wstring responseUrl;
std::wstring message;
};
// Result ownership is transferred to the window through
// WM_NIGHTSHOT_UPLOAD_FINISHED in lParam.
[[nodiscard]] bool start(HWND notificationTarget, std::vector<BYTE>&& png,
const settings::AppSettings& configuration);
} // namespace uploader