Add NightShot source
This commit is contained in:
1 parent
0174ecaf32
commit
dfcbacfb0c
33 files changed
+8358
-1
No files matched your search
@@ -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;
|
||||
@@ -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();
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
#define IDI_APP_ICON 101
|
||||
#define IDR_APP_MANIFEST 1
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in new issue
Block a user