Compare commits

..

22 Commits

Author SHA1 Message Date
57383e284f Update README.md 2025-02-17 08:09:56 +00:00
a1190e46b5 Update PKGBUILD 2025-01-20 10:16:38 +00:00
7c725f351d Update README.md 2025-01-20 10:15:58 +00:00
ca5dab736a fix 2025-01-20 09:35:57 +00:00
f4e6c2bfd0 pkg update 2025-01-18 14:15:16 +00:00
f41aa74de0 argeparse 2025-01-18 14:10:25 +00:00
5274c8a839 Update README.md 2024-11-26 12:57:52 +00:00
fb77671c94 update name 2024-10-23 14:03:48 +01:00
21f2d4c5ab arch build 2024-10-23 14:02:50 +01:00
11ef7f988d i dont like cmake 2024-10-23 13:54:46 +01:00
d24dce7ffe uuuuuh 2024-10-23 12:28:41 +01:00
13a50afcca uhh 2024-10-23 11:30:54 +01:00
b8a41bd474 deb build 2024-10-23 11:28:23 +01:00
e53fd06a68 added cmake again :| 2024-10-23 11:06:49 +01:00
19b8a1921e update 2024-10-21 12:49:14 +01:00
fd6b5cb8de Merge remote-tracking branch 'origin/main' 2024-10-21 12:47:01 +01:00
9c299777d3 update makefile 2024-10-21 12:45:34 +01:00
c484b52630 added custom repo install guide 2024-10-14 23:27:05 +00:00
b794e003cb Update README.md 2024-09-19 12:14:05 +00:00
e597d59cb2 remove clip-lib 2024-09-19 13:11:19 +01:00
729a4a0a0c auto find cpp compiler 2024-09-16 15:18:54 +01:00
8d8f39a2ff fixed folder structure 2024-09-16 15:11:25 +01:00
28 changed files with 775 additions and 4311 deletions

View File

@@ -1,19 +0,0 @@
pkgbase = term-owo-git
pkgdesc = A simple C++ program that owofies text
pkgver = 1.0
pkgrel = 1
url = https://git.alfieking.dev/acetheking987/term-owo-cpp
arch = x86_64
arch = i686
arch = arm
arch = armv6h
arch = armv7h
arch = aarch64
license = MIT
makedepends = cmake
makedepends = git
makedepends = gcc
source = git+https://git.alfieking.dev/acetheking987/term-owo-cpp.git
md5sums = SKIP
pkgname = term-owo-git

2
.gitignore vendored
View File

@@ -32,4 +32,6 @@
*.out *.out
*.app *.app
# Build files
build/ build/
.vscode/

View File

@@ -1,6 +0,0 @@
cmake_minimum_required(VERSION 2.8)
project(owo)
add_executable(owo owo.cpp)
add_subdirectory(clip-lib)
target_link_libraries(owo clip)

View File

@@ -1,5 +1,5 @@
pkgname="term-owo-git" pkgname="owo"
pkgver=1.0 pkgver=2.1
pkgrel=1 pkgrel=1
pkgdesc="A simple C++ program that owofies text" pkgdesc="A simple C++ program that owofies text"
url="https://git.alfieking.dev/acetheking987/term-owo-cpp" url="https://git.alfieking.dev/acetheking987/term-owo-cpp"
@@ -12,8 +12,7 @@ md5sums=('SKIP')
build() { build() {
cd "$srcdir/term-owo-cpp" cd "$srcdir/term-owo-cpp"
cmake . -B build make dirs build_unix
cd build && make
} }
package() { package() {

View File

@@ -16,27 +16,9 @@ $ echo "Hello, world!" | ./owo -p
Hewwo, wowld! Hewwo, wowld!
``` ```
### from file:
```bash
$ ./owo -f file.txt
Hewwo, wowld!
```
### to file:
```bash
$ ./owo -o output.txt "Hello, world!"
```
### to clipboard:
```bash
$ ./owo -c "Hello, world!"
```
## Building ## Building
to build the program, run the following command:
```bash ```bash
$ cmake . -B build $ make dev
$ cd build && make
$ ./owo "Hello, world!" $ ./owo "Hello, world!"
hewwo, wowld! hewwo, wowld!
``` ```
@@ -44,10 +26,21 @@ hewwo, wowld!
## Installation ## Installation
### archlinux: ### archlinux:
#### Aur
```bash ```bash
$ git clone `https://git.alfieking.dev/acetheking987/term-owo-cpp.git` $ yay -S term-owo-git
$ cd term-owo-cpp ```
$ makepkg -si #### Custom Repo
/etc/pacman.conf
```
[alfieking.dev]
Server = https://repo.alfieking.dev/x86_64
```
bash
```bash
$ pacman-key --recv-keys 1553B1410D80686C8D2A27338035E1AC0B1440DC
$ pacman-key --lsign-key 1553B1410D80686C8D2A27338035E1AC0B1440DC
$ pacman -Sy owo
``` ```
### other distros: ### other distros:

View File

@@ -1,98 +0,0 @@
# Clip Library
# Copyright (c) 2015-2024 David Capello
cmake_minimum_required(VERSION 3.5)
project(clip LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
# Use libc++ explicitly so we can compile for
# CMAKE_OSX_DEPLOYMENT_TARGET=10.7 or 10.8
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
endif()
option(CLIP_ENABLE_IMAGE "Compile with support to copy/paste images" on)
if(WIN32)
option(CLIP_ENABLE_LIST_FORMATS "Compile with support to list clipboard formats" off)
endif()
option(CLIP_EXAMPLES "Compile clip examples" on)
option(CLIP_TESTS "Compile clip tests" on)
if(UNIX AND NOT APPLE)
option(CLIP_X11_WITH_PNG "Compile with libpng to support copy/paste image in png format" on)
endif()
add_library(clip clip.cpp)
if(CLIP_ENABLE_IMAGE)
target_sources(clip PRIVATE image.cpp)
target_compile_definitions(clip PUBLIC -DCLIP_ENABLE_IMAGE=1)
endif()
if(CLIP_ENABLE_LIST_FORMATS)
target_compile_definitions(clip PUBLIC -DCLIP_ENABLE_LIST_FORMATS=1)
endif()
if(WIN32)
option(CLIP_SUPPORT_WINXP "Enable Windows XP support" OFF)
target_sources(clip PRIVATE clip_win.cpp)
if(CLIP_ENABLE_IMAGE)
target_sources(clip PRIVATE clip_win_bmp.cpp clip_win_wic.cpp)
target_link_libraries(clip shlwapi)
endif()
if(MSVC)
target_compile_definitions(clip PRIVATE -D_SCL_SECURE_NO_WARNINGS)
endif()
if (CLIP_SUPPORT_WINXP)
target_compile_definitions(clip PRIVATE -DCLIP_SUPPORT_WINXP)
endif()
# MinGW requires the windowscodecs just because CLSIDs are defined
# in the windowscodecs.a file instead of the wincodec.h file (?!)
if(MINGW)
find_library(CLIP_WINDOWSCODECS_LIBRARY windowscodecs)
if(CLIP_WINDOWSCODECS_LIBRARY)
target_link_libraries(clip ${CLIP_WINDOWSCODECS_LIBRARY})
endif()
endif()
elseif(APPLE)
target_compile_options(clip PRIVATE -fobjc-arc)
find_library(COCOA_LIBRARY Cocoa)
if(COCOA_LIBRARY)
target_sources(clip PRIVATE clip_osx.mm)
target_link_libraries(clip ${COCOA_LIBRARY})
else()
target_sources(clip PRIVATE clip_none.cpp)
endif()
elseif(UNIX)
include(CheckIncludeFiles)
check_include_files(xcb/xcb.h HAVE_XCB_XLIB_H)
if(HAVE_XCB_XLIB_H)
target_compile_definitions(clip PRIVATE -DHAVE_XCB_XLIB_H)
target_link_libraries(clip xcb pthread)
if(CLIP_ENABLE_IMAGE AND CLIP_X11_WITH_PNG)
check_include_files(png.h HAVE_PNG_H)
if(CLIP_X11_PNG_LIBRARY)
set(PNG_LIBRARY ${CLIP_X11_PNG_LIBRARY})
else()
find_library(PNG_LIBRARY png)
endif()
if(HAVE_PNG_H AND PNG_LIBRARY)
target_compile_definitions(clip PRIVATE -DHAVE_PNG_H)
endif()
target_link_libraries(clip ${PNG_LIBRARY})
endif()
target_sources(clip PRIVATE clip_x11.cpp)
else()
target_sources(clip PRIVATE clip_none.cpp)
endif()
else()
target_sources(clip PRIVATE clip_none.cpp)
endif()

View File

@@ -1,5 +0,0 @@
By submitting a pull request, you represent that you have the right to
license your contribution to the Clip project owners and the community,
agree by submitting the patch that your contributions are licensed under
the [Clip license](https://raw.githubusercontent.com/dacap/clip/main/LICENSE.txt),
and agree to future changes to the licensing.

View File

@@ -1,77 +0,0 @@
# Clip Library
*Copyright (c) 2015-2024 David Capello*
[![build](https://github.com/dacap/clip/workflows/build/badge.svg)](https://github.com/dacap/clip/actions?query=workflow%3Abuild)
[![MIT Licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt)
Library to copy/retrieve content to/from the clipboard/pasteboard.
## Features
Available features on Windows, macOS, and Linux (X11):
* Copy/paste UTF-8 text.
* Copy/paste user-defined data.
* Copy/paste RGB/RGBA images. This library use non-premultiplied alpha RGB values.
## Example
```cpp
#include "clip.h"
#include <iostream>
int main() {
clip::set_text("Hello World");
std::string value;
clip::get_text(value);
std::cout << value << "\n";
}
```
## User-defined clipboard formats
```cpp
#include "clip.h"
int main() {
clip::format my_format =
clip::register_format("com.appname.FormatName");
int value = 32;
clip::lock l;
l.clear();
l.set_data(clip::text_format(), "Alternative text for value 32");
l.set_data(my_format, &value, sizeof(int));
}
```
## Platform specific details
* If two versions of your application (32-bit and 64-bit) can run at
at the same time, remember to avoid storing data types that could
change depending on the platform (e.g. `size_t`) in your custom
format data.
* **Windows**:
- [Limited number of clipboard formats on Windows](http://blogs.msdn.com/b/oldnewthing/archive/2015/03/19/10601208.aspx)
* **Linux**:
- To be able to copy/paste on Linux you need `libx11-dev`/`libX11-devel` package.
- To copy/paste images you will need `libpng-dev`/`libpng-devel` package.
## Compilation Flags
* `CLIP_ENABLE_IMAGE`: Enables the support to
[copy](examples/put_image.cpp)/[paste](examples/show_image.cpp) images.
* `CLIP_ENABLE_LIST_FORMATS` (only for Windows): Enables the
`clip::lock::list_formats()` API function and the
[list_clip_formats](examples/list_clip_formats.cpp) example.
* `CLIP_EXAMPLES`: Compile [examples](examples/).
* `CLIP_TESTS`: Compile [tests](tests/).
* `CLIP_X11_WITH_PNG` (only for Linux/X11): Enables support to
copy/paste images using the `libpng` library on Linux.
## Who is using this library?
[Check the wiki](https://github.com/dacap/clip/wiki#who-is-using-clip)
to know what projects are using the `clip` library.

View File

@@ -1,192 +0,0 @@
// Clip Library
// Copyright (c) 2015-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include "clip_lock_impl.h"
#include <vector>
#include <stdexcept>
namespace clip {
namespace {
void default_error_handler(ErrorCode code) {
static const char* err[] = {
"Cannot lock clipboard",
"Image format is not supported"
};
throw std::runtime_error(err[static_cast<int>(code)]);
}
} // anonymous namespace
error_handler g_error_handler = default_error_handler;
lock::lock(void* native_window_handle)
: p(new impl(native_window_handle)) {
}
lock::~lock() = default;
bool lock::locked() const {
return p->locked();
}
bool lock::clear() {
return p->clear();
}
bool lock::is_convertible(format f) const {
return p->is_convertible(f);
}
bool lock::set_data(format f, const char* buf, size_t length) {
return p->set_data(f, buf, length);
}
bool lock::get_data(format f, char* buf, size_t len) const {
return p->get_data(f, buf, len);
}
size_t lock::get_data_length(format f) const {
return p->get_data_length(f);
}
#if CLIP_ENABLE_IMAGE
bool lock::set_image(const image& img) {
return p->set_image(img);
}
bool lock::get_image(image& img) const {
return p->get_image(img);
}
bool lock::get_image_spec(image_spec& spec) const {
return p->get_image_spec(spec);
}
#endif // CLIP_ENABLE_IMAGE
#if CLIP_ENABLE_LIST_FORMATS
std::vector<format_info> lock::list_formats() const {
return p->list_formats();
}
#endif // CLIP_ENABLE_LIST_FORMATS
format empty_format() { return 0; }
format text_format() { return 1; }
#if CLIP_ENABLE_IMAGE
format image_format() { return 2; }
#endif
bool has(format f) {
lock l;
if (l.locked())
return l.is_convertible(f);
else
return false;
}
bool clear() {
lock l;
if (l.locked())
return l.clear();
else
return false;
}
bool set_text(const std::string& value) {
lock l;
if (l.locked()) {
l.clear();
return l.set_data(text_format(), value.c_str(), value.size());
}
else
return false;
}
bool get_text(std::string& value) {
lock l;
if (!l.locked())
return false;
format f = text_format();
if (!l.is_convertible(f))
return false;
size_t len = l.get_data_length(f);
if (len > 0) {
std::vector<char> buf(len);
l.get_data(f, &buf[0], len);
value = &buf[0];
return true;
}
else {
value.clear();
return true;
}
}
#if CLIP_ENABLE_IMAGE
bool set_image(const image& img) {
lock l;
if (l.locked()) {
l.clear();
return l.set_image(img);
}
else
return false;
}
bool get_image(image& img) {
lock l;
if (!l.locked())
return false;
format f = image_format();
if (!l.is_convertible(f))
return false;
return l.get_image(img);
}
bool get_image_spec(image_spec& spec) {
lock l;
if (!l.locked())
return false;
format f = image_format();
if (!l.is_convertible(f))
return false;
return l.get_image_spec(spec);
}
#endif // CLIP_ENABLE_IMAGE
void set_error_handler(error_handler handler) {
g_error_handler = handler;
}
error_handler get_error_handler() {
return g_error_handler;
}
#ifdef HAVE_XCB_XLIB_H
static int g_x11_timeout = 1000;
void set_x11_wait_timeout(int msecs) { g_x11_timeout = msecs; }
int get_x11_wait_timeout() { return g_x11_timeout; }
#else
void set_x11_wait_timeout(int) { }
int get_x11_wait_timeout() { return 1000; }
#endif
} // namespace clip

View File

@@ -1,211 +0,0 @@
// Clip Library
// Copyright (c) 2015-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_H_INCLUDED
#define CLIP_H_INCLUDED
#pragma once
#include <cassert>
#include <memory>
#include <string>
#include <vector>
namespace clip {
// ======================================================================
// Low-level API to lock the clipboard/pasteboard and modify it
// ======================================================================
// Clipboard format identifier.
typedef size_t format;
#if CLIP_ENABLE_IMAGE
class image;
struct image_spec;
#endif // CLIP_ENABLE_IMAGE
#if CLIP_ENABLE_LIST_FORMATS
struct format_info {
format id = 0;
std::string name;
format_info(const format id,
const std::string& name)
: id(id),
name(name) {
}
};
#endif // CLIP_ENABLE_LIST_FORMATS
class lock {
public:
// You can give your current HWND as the "native_window_handle."
// Windows clipboard functions use this handle to open/close
// (lock/unlock) the clipboard. From the MSDN documentation we
// need this handler so SetClipboardData() doesn't fail after a
// EmptyClipboard() call. Anyway it looks to work just fine if we
// call OpenClipboard() with a null HWND.
lock(void* native_window_handle = nullptr);
~lock();
// Returns true if we've locked the clipboard successfully in
// lock() constructor.
bool locked() const;
// Clears the clipboard content. If you don't clear the content,
// previous clipboard content (in unknown formats) could persist
// after the unlock.
bool clear();
// Returns true if the clipboard can be converted to the given
// format.
bool is_convertible(format f) const;
bool set_data(format f, const char* buf, size_t len);
bool get_data(format f, char* buf, size_t len) const;
size_t get_data_length(format f) const;
#if CLIP_ENABLE_IMAGE
// For images
bool set_image(const image& image);
bool get_image(image& image) const;
bool get_image_spec(image_spec& spec) const;
#endif // CLIP_ENABLE_IMAGE
#if CLIP_ENABLE_LIST_FORMATS
// Returns the list of available formats (by name) in the
// clipboard.
std::vector<format_info> list_formats() const;
#endif // CLIP_ENABLE_LIST_FORMATS
private:
class impl;
std::unique_ptr<impl> p;
};
format register_format(const std::string& name);
// This format is when the clipboard has no content.
format empty_format();
// When the clipboard has UTF8 text.
format text_format();
#if CLIP_ENABLE_IMAGE
// When the clipboard has an image.
format image_format();
#endif
// Returns true if the clipboard has content of the given type.
bool has(format f);
// Clears the clipboard content.
bool clear();
// ======================================================================
// Error handling
// ======================================================================
enum class ErrorCode {
CannotLock,
#if CLIP_ENABLE_IMAGE
ImageNotSupported,
#endif
};
typedef void (*error_handler)(ErrorCode code);
void set_error_handler(error_handler f);
error_handler get_error_handler();
// ======================================================================
// Text
// ======================================================================
// High-level API to put/get UTF8 text in/from the clipboard. These
// functions returns false in case of error.
bool set_text(const std::string& value);
bool get_text(std::string& value);
// ======================================================================
// Image
// ======================================================================
#if CLIP_ENABLE_IMAGE
struct image_spec {
unsigned long width = 0;
unsigned long height = 0;
unsigned long bits_per_pixel = 0;
unsigned long bytes_per_row = 0;
unsigned long red_mask = 0;
unsigned long green_mask = 0;
unsigned long blue_mask = 0;
unsigned long alpha_mask = 0;
unsigned long red_shift = 0;
unsigned long green_shift = 0;
unsigned long blue_shift = 0;
unsigned long alpha_shift = 0;
unsigned long required_data_size() const;
};
// The image data must contain straight RGB values
// (non-premultiplied by alpha). The image retrieved from the
// clipboard will be non-premultiplied too. Basically you will be
// always dealing with straight alpha images.
//
// Details: Windows expects premultiplied images on its clipboard
// content, so the library code make the proper conversion
// automatically. macOS handles straight alpha directly, so there is
// no conversion at all. Linux/X11 images are transferred in
// image/png format which are specified in straight alpha.
class image {
public:
image();
image(const image_spec& spec);
image(const void* data, const image_spec& spec);
image(const image& image);
image(image&& image);
~image();
image& operator=(const image& image);
image& operator=(image&& image);
char* data() const { return m_data; }
const image_spec& spec() const { return m_spec; }
bool is_valid() const { return m_data != nullptr; }
void reset();
private:
void copy_image(const image& image);
void move_image(image&& image);
bool m_own_data;
char* m_data;
image_spec m_spec;
};
// High-level API to set/get an image in/from the clipboard. These
// functions returns false in case of error.
bool set_image(const image& img);
bool get_image(image& img);
bool get_image_spec(image_spec& spec);
#endif // CLIP_ENABLE_IMAGE
// ======================================================================
// Platform-specific
// ======================================================================
// Only for X11: Sets the time (in milliseconds) that we must wait
// for the selection/clipboard owner to receive the content. This
// value is 1000 (one second) by default.
void set_x11_wait_timeout(int msecs);
int get_x11_wait_timeout();
} // namespace clip
#endif // CLIP_H_INCLUDED

View File

@@ -1,82 +0,0 @@
// Clip Library
// Copyright (C) 2020-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_COMMON_H_INCLUDED
#define CLIP_COMMON_H_INCLUDED
#pragma once
#include "clip.h"
namespace clip {
namespace details {
#if CLIP_ENABLE_IMAGE
inline void divide_rgb_by_alpha(image& img,
bool hasAlphaGreaterThanZero = false) {
const image_spec& spec = img.spec();
bool hasValidPremultipliedAlpha = true;
for (unsigned long y=0; y<spec.height; ++y) {
const uint32_t* dst = (uint32_t*)(img.data()+y*spec.bytes_per_row);
for (unsigned long x=0; x<spec.width; ++x, ++dst) {
const uint32_t c = *dst;
const int r = ((c & spec.red_mask ) >> spec.red_shift );
const int g = ((c & spec.green_mask) >> spec.green_shift);
const int b = ((c & spec.blue_mask ) >> spec.blue_shift );
const int a = ((c & spec.alpha_mask) >> spec.alpha_shift);
if (a > 0)
hasAlphaGreaterThanZero = true;
if (r > a || g > a || b > a)
hasValidPremultipliedAlpha = false;
}
}
for (unsigned long y=0; y<spec.height; ++y) {
uint32_t* dst = (uint32_t*)(img.data()+y*spec.bytes_per_row);
for (unsigned long x=0; x<spec.width; ++x, ++dst) {
const uint32_t c = *dst;
int r = ((c & spec.red_mask ) >> spec.red_shift );
int g = ((c & spec.green_mask) >> spec.green_shift);
int b = ((c & spec.blue_mask ) >> spec.blue_shift );
int a = ((c & spec.alpha_mask) >> spec.alpha_shift);
// If all alpha values = 0, we make the image opaque.
if (!hasAlphaGreaterThanZero) {
a = 255;
// We cannot change the image spec (e.g. spec.alpha_mask=0) to
// make the image opaque, because the "spec" of the image is
// read-only. The image spec used by the client is the one
// returned by get_image_spec().
}
// If there is alpha information and it's pre-multiplied alpha
else if (hasValidPremultipliedAlpha) {
if (a > 0) {
// Convert it to straight alpha
r = r * 255 / a;
g = g * 255 / a;
b = b * 255 / a;
}
}
*dst =
(r << spec.red_shift ) |
(g << spec.green_shift) |
(b << spec.blue_shift ) |
(a << spec.alpha_shift);
}
}
}
#endif // CLIP_ENABLE_IMAGE
} // namespace details
} // namespace clip
#endif // CLIP_H_INCLUDED

View File

@@ -1,40 +0,0 @@
// Clip Library
// Copyright (c) 2015-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_LOCK_IMPL_H_INCLUDED
#define CLIP_LOCK_IMPL_H_INCLUDED
namespace clip {
class lock::impl {
public:
impl(void* native_window_handle);
~impl();
bool locked() const { return m_locked; }
bool clear();
bool is_convertible(format f) const;
bool set_data(format f, const char* buf, size_t len);
bool get_data(format f, char* buf, size_t len) const;
size_t get_data_length(format f) const;
#if CLIP_ENABLE_IMAGE
bool set_image(const image& image);
bool get_image(image& image) const;
bool get_image_spec(image_spec& spec) const;
#endif // CLIP_ENABLE_IMAGE
#if CLIP_ENABLE_LIST_FORMATS
std::vector<format_info> list_formats() const;
#endif // CLIP_ENABLE_LIST_FORMATS
private:
bool m_locked;
};
} // namespace clip
#endif

View File

@@ -1,86 +0,0 @@
// Clip Library
// Copyright (c) 2015-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include "clip_lock_impl.h"
#include <cassert>
#include <map>
#include <vector>
namespace clip {
typedef std::vector<char> Buffer;
typedef std::map<format, Buffer> Map;
static format g_last_format = 100; // TODO create an enum with common formats
static Map g_data;
lock::impl::impl(void* native_handle) : m_locked(true) {
}
lock::impl::~impl() {
}
bool lock::impl::clear() {
g_data.clear();
return true;
}
bool lock::impl::is_convertible(format f) const {
return (g_data.find(f) != g_data.end());
}
bool lock::impl::set_data(format f, const char* buf, size_t len) {
Buffer& dst = g_data[f];
dst.resize(len);
if (buf && len > 0)
std::copy(buf, buf+len, dst.begin());
if (f == text_format() &&
len > 0 && dst.back() != 0) {
dst.push_back(0);
}
return true;
}
bool lock::impl::get_data(format f, char* buf, size_t len) const {
assert(buf);
if (!buf || !is_convertible(f))
return false;
const Buffer& src = g_data[f];
std::copy(src.begin(), src.end(), buf);
return true;
}
size_t lock::impl::get_data_length(format f) const {
if (is_convertible(f))
return g_data[f].size();
else
return 0;
}
bool lock::impl::set_image(const image& image) {
return false; // TODO
}
bool lock::impl::get_image(image& image) const {
return false; // TODO
}
bool lock::impl::get_image_spec(image_spec& spec) const {
return false; // TODO
}
format register_format(const std::string& name) {
return g_last_format++;
}
} // namespace clip

View File

@@ -1,35 +0,0 @@
// Clip Library
// Copyright (c) 2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_OSX_H_INCLUDED
#define CLIP_OSX_H_INCLUDED
#pragma once
#ifdef __OBJC__
#include <Cocoa/Cocoa.h>
namespace clip {
class image;
struct image_spec;
namespace osx {
#if CLIP_ENABLE_IMAGE
bool get_image_from_clipboard(NSPasteboard* pasteboard,
image* output_img,
image_spec* output_spec);
#endif // CLIP_ENABLE_IMAGE
} // namespace osx
} // namespace clip
#endif
#endif

View File

@@ -1,377 +0,0 @@
// Clip Library
// Copyright (c) 2015-2023 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include "clip_common.h"
#include "clip_lock_impl.h"
#include <cassert>
#include <vector>
#include <map>
#include <CoreFoundation/CoreFoundation.h>
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>
namespace clip {
namespace {
format g_last_format = 100;
std::map<std::string, format> g_name_to_format;
std::map<format, std::string> g_format_to_name;
}
namespace osx {
#if CLIP_ENABLE_IMAGE
bool get_image_from_clipboard(NSPasteboard* pasteboard,
image* output_img,
image_spec* output_spec)
{
NSString* result = [pasteboard availableTypeFromArray:
[NSArray arrayWithObjects:NSPasteboardTypeTIFF,NSPasteboardTypePNG,nil]];
if (!result)
return false;
NSData* data = [pasteboard dataForType:result];
if (!data)
return false;
NSBitmapImageRep* bitmap = [NSBitmapImageRep imageRepWithData:data];
if ((bitmap.bitmapFormat & NSBitmapFormatFloatingPointSamples) ||
(bitmap.planar)) {
error_handler e = get_error_handler();
if (e)
e(ErrorCode::ImageNotSupported);
return false;
}
image_spec spec;
spec.width = bitmap.pixelsWide;
spec.height = bitmap.pixelsHigh;
spec.bits_per_pixel = bitmap.bitsPerPixel;
spec.bytes_per_row = bitmap.bytesPerRow;
// We need three samples for Red/Green/Blue
if (bitmap.samplesPerPixel >= 3) {
// Here we are guessing the bits per sample (generally 8, not
// sure how many bits per sample macOS uses for 16bpp
// NSBitmapFormat or if this format is even used).
int bits_per_sample = (bitmap.bitsPerPixel == 16 ? 5: 8);
int bits_shift = 0;
// With alpha
if (bitmap.alpha) {
if (bitmap.bitmapFormat & NSBitmapFormatAlphaFirst) {
spec.alpha_shift = 0;
bits_shift += bits_per_sample;
}
else {
spec.alpha_shift = 3*bits_per_sample;
}
}
unsigned long* masks = &spec.red_mask;
unsigned long* shifts = &spec.red_shift;
// Red/green/blue shifts
for (unsigned long* shift=shifts; shift<shifts+3; ++shift) {
*shift = bits_shift;
bits_shift += bits_per_sample;
}
// With alpha
if (bitmap.alpha) {
if (bitmap.bitmapFormat & NSBitmapFormatSixteenBitBigEndian ||
bitmap.bitmapFormat & NSBitmapFormatThirtyTwoBitBigEndian) {
std::swap(spec.red_shift, spec.alpha_shift);
std::swap(spec.green_shift, spec.blue_shift);
}
}
// Without alpha
else {
if (bitmap.bitmapFormat & NSBitmapFormatSixteenBitBigEndian ||
bitmap.bitmapFormat & NSBitmapFormatThirtyTwoBitBigEndian) {
std::swap(spec.red_shift, spec.blue_shift);
}
}
// Calculate all masks
for (unsigned long* shift=shifts, *mask=masks; shift<shifts+4; ++shift, ++mask)
*mask = ((1ul<<bits_per_sample)-1ul) << (*shift);
// Without alpha
if (!bitmap.alpha)
spec.alpha_mask = 0;
}
if (output_spec) {
*output_spec = spec;
}
if (output_img) {
unsigned long size = spec.bytes_per_row*spec.height;
image img(spec);
std::copy(bitmap.bitmapData,
bitmap.bitmapData+size, img.data());
// Convert premultiplied data to unpremultiplied if needed.
if (bitmap.alpha &&
bitmap.samplesPerPixel >= 3 &&
!(bitmap.bitmapFormat & NSBitmapFormatAlphaNonpremultiplied)) {
details::divide_rgb_by_alpha(
img,
true); // hasAlphaGreaterThanZero=true because we have valid alpha information
}
std::swap(*output_img, img);
}
return true;
}
#endif // CLIP_ENABLE_IMAGE
} // namespace osx
lock::impl::impl(void*) : m_locked(true) {
}
lock::impl::~impl() {
}
bool lock::impl::clear() {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
[pasteboard clearContents];
return true;
}
}
bool lock::impl::is_convertible(format f) const {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSString* result = nil;
if (f == text_format()) {
result = [pasteboard availableTypeFromArray:[NSArray arrayWithObject:NSPasteboardTypeString]];
}
#if CLIP_ENABLE_IMAGE
else if (f == image_format()) {
result = [pasteboard availableTypeFromArray:
[NSArray arrayWithObjects:NSPasteboardTypeTIFF,NSPasteboardTypePNG,nil]];
}
#endif // CLIP_ENABLE_IMAGE
else {
auto it = g_format_to_name.find(f);
if (it != g_format_to_name.end()) {
const std::string& name = it->second;
NSString* string = [[NSString alloc] initWithBytesNoCopy:(void*)name.c_str()
length:name.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
result = [pasteboard availableTypeFromArray:[NSArray arrayWithObject:string]];
}
}
return (result ? true: false);
}
}
bool lock::impl::set_data(format f, const char* buf, size_t len) {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
if (f == text_format()) {
NSString* string = [[NSString alloc] initWithBytesNoCopy:(void*)buf
length:len
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
[pasteboard setString:string forType:NSPasteboardTypeString];
return true;
}
else {
auto it = g_format_to_name.find(f);
if (it != g_format_to_name.end()) {
const std::string& formatName = it->second;
NSString* typeString = [[NSString alloc]
initWithBytesNoCopy:(void*)formatName.c_str()
length:formatName.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSData* data = [NSData dataWithBytesNoCopy:(void*)buf
length:len
freeWhenDone:NO];
if ([pasteboard setData:data forType:typeString])
return true;
}
}
return false;
}
}
bool lock::impl::get_data(format f, char* buf, size_t len) const {
@autoreleasepool {
assert(buf);
if (!buf || !is_convertible(f))
return false;
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
if (f == text_format()) {
NSString* string = [pasteboard stringForType:NSPasteboardTypeString];
int reqsize = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]+1;
assert(reqsize <= len);
if (reqsize > len) {
// Buffer is too small
return false;
}
if (reqsize == 0)
return true;
memcpy(buf, [string UTF8String], reqsize);
return true;
}
auto it = g_format_to_name.find(f);
if (it == g_format_to_name.end())
return false;
const std::string& formatName = it->second;
NSString* typeString =
[[NSString alloc] initWithBytesNoCopy:(void*)formatName.c_str()
length:formatName.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSData* data = [pasteboard dataForType:typeString];
if (!data)
return false;
[data getBytes:buf length:len];
return true;
}
}
size_t lock::impl::get_data_length(format f) const {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
if (f == text_format()) {
NSString* string = [pasteboard stringForType:NSPasteboardTypeString];
return [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]+1;
}
auto it = g_format_to_name.find(f);
if (it == g_format_to_name.end())
return 0;
const std::string& formatName = it->second;
NSString* typeString =
[[NSString alloc] initWithBytesNoCopy:(void*)formatName.c_str()
length:formatName.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSData* data = [pasteboard dataForType:typeString];
if (!data)
return 0;
return data.length;
}
}
#if CLIP_ENABLE_IMAGE
bool lock::impl::set_image(const image& image) {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
const image_spec& spec = image.spec();
NSBitmapFormat bitmapFormat = 0;
int samples_per_pixel = 0;
if (spec.alpha_mask) {
samples_per_pixel = 4;
if (spec.alpha_shift == 0)
bitmapFormat |= NSBitmapFormatAlphaFirst;
bitmapFormat |= NSBitmapFormatAlphaNonpremultiplied;
}
else if (spec.red_mask || spec.green_mask || spec.blue_mask) {
samples_per_pixel = 3;
}
else {
samples_per_pixel = 1;
}
if (spec.bits_per_pixel == 32)
bitmapFormat |= NSBitmapFormatThirtyTwoBitLittleEndian;
else if (spec.bits_per_pixel == 16)
bitmapFormat |= NSBitmapFormatSixteenBitLittleEndian;
std::vector<unsigned char*> planes(1);
planes[0] = (unsigned char*)image.data();
NSBitmapImageRep* bitmap =
[[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:&planes[0]
pixelsWide:spec.width
pixelsHigh:spec.height
bitsPerSample:spec.bits_per_pixel / samples_per_pixel
samplesPerPixel:samples_per_pixel
hasAlpha:(spec.alpha_mask ? YES: NO)
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:bitmapFormat
bytesPerRow:spec.bytes_per_row
bitsPerPixel:spec.bits_per_pixel];
if (!bitmap)
return false;
NSData* data = bitmap.TIFFRepresentation;
if (!data)
return false;
if ([pasteboard setData:data forType:NSPasteboardTypeTIFF])
return true;
return false;
}
}
bool lock::impl::get_image(image& img) const {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
return osx::get_image_from_clipboard(pasteboard, &img, nullptr);
}
bool lock::impl::get_image_spec(image_spec& spec) const {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
return osx::get_image_from_clipboard(pasteboard, nullptr, &spec);
}
#endif // CLIP_ENABLE_IMAGE
format register_format(const std::string& name) {
// Check if the format is already registered
auto it = g_name_to_format.find(name);
if (it != g_name_to_format.end())
return it->second;
format new_format = g_last_format++;
g_name_to_format[name] = new_format;
g_format_to_name[new_format] = name;
return new_format;
}
} // namespace clip

View File

@@ -1,407 +0,0 @@
// Clip Library
// Copyright (C) 2015-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip_win.h"
#include "clip.h"
#include "clip_lock_impl.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
namespace clip {
namespace {
// Data type used as header for custom formats to indicate the exact
// size of the user custom data. This is necessary because it looks
// like GlobalSize() might not return the exact size, but a greater
// value.
typedef uint64_t CustomSizeT;
class Hglobal {
public:
Hglobal() : m_handle(nullptr) {
}
explicit Hglobal(HGLOBAL handle) : m_handle(handle) {
}
explicit Hglobal(size_t len) : m_handle(GlobalAlloc(GHND, len)) {
}
~Hglobal() {
if (m_handle)
GlobalFree(m_handle);
}
void release() {
m_handle = nullptr;
}
operator HGLOBAL() {
return m_handle;
}
private:
HGLOBAL m_handle;
};
// From: https://issues.chromium.org/issues/40080988#comment8
//
// "Adds impersonation of the anonymous token around calls to the
// CloseClipboard() system call. On Windows 8+ the win32k driver
// captures the access token of the caller and makes it available to
// other users on the desktop through the system call
// GetClipboardAccessToken(). This introduces a risk of privilege
// escalation in sandboxed processes. By performing the
// impersonation then whenever Chrome writes data to the clipboard
// only the anonymous token is available."
//
class AnonymousTokenImpersonator {
public:
AnonymousTokenImpersonator()
: m_must_revert(ImpersonateAnonymousToken(GetCurrentThread()))
{}
~AnonymousTokenImpersonator() {
if (m_must_revert)
RevertToSelf();
}
private:
const bool m_must_revert;
};
} // anonymous namespace
lock::impl::impl(void* hwnd) : m_locked(false) {
for (int i=0; i<5; ++i) {
if (OpenClipboard((HWND)hwnd)) {
m_locked = true;
break;
}
Sleep(20);
}
if (!m_locked) {
error_handler e = get_error_handler();
if (e)
e(ErrorCode::CannotLock);
}
}
lock::impl::~impl() {
if (m_locked) {
AnonymousTokenImpersonator guard;
CloseClipboard();
}
}
bool lock::impl::clear() {
return (EmptyClipboard() ? true: false);
}
bool lock::impl::is_convertible(format f) const {
if (f == text_format()) {
return
(IsClipboardFormatAvailable(CF_TEXT) ||
IsClipboardFormatAvailable(CF_UNICODETEXT) ||
IsClipboardFormatAvailable(CF_OEMTEXT));
}
#if CLIP_ENABLE_IMAGE
else if (f == image_format()) {
return (IsClipboardFormatAvailable(CF_DIB) ||
win::wic_image_format_available(nullptr) != nullptr);
}
#endif // CLIP_ENABLE_IMAGE
else
return IsClipboardFormatAvailable(f);
}
bool lock::impl::set_data(format f, const char* buf, size_t len) {
bool result = false;
if (f == text_format()) {
if (len > 0) {
int reqsize = MultiByteToWideChar(CP_UTF8, 0, buf, len, NULL, 0);
if (reqsize > 0) {
++reqsize;
Hglobal hglobal(sizeof(WCHAR)*reqsize);
LPWSTR lpstr = static_cast<LPWSTR>(GlobalLock(hglobal));
MultiByteToWideChar(CP_UTF8, 0, buf, len, lpstr, reqsize);
GlobalUnlock(hglobal);
result = (SetClipboardData(CF_UNICODETEXT, hglobal)) ? true: false;
if (result)
hglobal.release();
}
}
}
else {
Hglobal hglobal(len+sizeof(CustomSizeT));
if (hglobal) {
auto dst = (uint8_t*)GlobalLock(hglobal);
if (dst) {
*((CustomSizeT*)dst) = len;
memcpy(dst+sizeof(CustomSizeT), buf, len);
GlobalUnlock(hglobal);
result = (SetClipboardData(f, hglobal) ? true: false);
if (result)
hglobal.release();
}
}
}
return result;
}
bool lock::impl::get_data(format f, char* buf, size_t len) const {
assert(buf);
if (!buf || !is_convertible(f))
return false;
bool result = false;
if (f == text_format()) {
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
HGLOBAL hglobal = GetClipboardData(CF_UNICODETEXT);
if (hglobal) {
LPWSTR lpstr = static_cast<LPWSTR>(GlobalLock(hglobal));
if (lpstr) {
size_t reqsize =
WideCharToMultiByte(CP_UTF8, 0, lpstr, -1,
nullptr, 0, nullptr, nullptr);
assert(reqsize <= len);
if (reqsize <= len) {
WideCharToMultiByte(CP_UTF8, 0, lpstr, -1,
buf, reqsize, nullptr, nullptr);
result = true;
}
GlobalUnlock(hglobal);
}
}
}
else if (IsClipboardFormatAvailable(CF_TEXT)) {
HGLOBAL hglobal = GetClipboardData(CF_TEXT);
if (hglobal) {
LPSTR lpstr = static_cast<LPSTR>(GlobalLock(hglobal));
if (lpstr) {
// TODO check length
memcpy(buf, lpstr, len);
result = true;
GlobalUnlock(hglobal);
}
}
}
}
else {
if (IsClipboardFormatAvailable(f)) {
HGLOBAL hglobal = GetClipboardData(f);
if (hglobal) {
const SIZE_T total_size = GlobalSize(hglobal);
auto ptr = (const uint8_t*)GlobalLock(hglobal);
if (ptr) {
CustomSizeT reqsize = *((CustomSizeT*)ptr);
// If the registered length of data in the first CustomSizeT
// number of bytes of the hglobal data is greater than the
// GlobalSize(hglobal), something is wrong, it should not
// happen.
assert(reqsize <= total_size);
if (reqsize > total_size)
reqsize = total_size - sizeof(CustomSizeT);
if (reqsize <= len) {
memcpy(buf, ptr+sizeof(CustomSizeT), reqsize);
result = true;
}
GlobalUnlock(hglobal);
}
}
}
}
return result;
}
size_t lock::impl::get_data_length(format f) const {
size_t len = 0;
if (f == text_format()) {
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
HGLOBAL hglobal = GetClipboardData(CF_UNICODETEXT);
if (hglobal) {
LPWSTR lpstr = static_cast<LPWSTR>(GlobalLock(hglobal));
if (lpstr) {
len =
WideCharToMultiByte(CP_UTF8, 0, lpstr, -1,
nullptr, 0, nullptr, nullptr);
GlobalUnlock(hglobal);
}
}
}
else if (IsClipboardFormatAvailable(CF_TEXT)) {
HGLOBAL hglobal = GetClipboardData(CF_TEXT);
if (hglobal) {
LPSTR lpstr = (LPSTR)GlobalLock(hglobal);
if (lpstr) {
len = strlen(lpstr) + 1;
GlobalUnlock(hglobal);
}
}
}
}
else if (f != empty_format()) {
if (IsClipboardFormatAvailable(f)) {
HGLOBAL hglobal = GetClipboardData(f);
if (hglobal) {
const SIZE_T total_size = GlobalSize(hglobal);
auto ptr = (const uint8_t*)GlobalLock(hglobal);
if (ptr) {
len = *((CustomSizeT*)ptr);
assert(len <= total_size);
if (len > total_size)
len = total_size - sizeof(CustomSizeT);
GlobalUnlock(hglobal);
}
}
}
}
return len;
}
#if CLIP_ENABLE_LIST_FORMATS
std::vector<format_info> lock::impl::list_formats() const {
static const char* standard_formats[CF_MAX] = {
"", "CF_TEXT", "CF_BITMAP", "CF_METAFILEPICT",
"CF_SYLK", "CF_DIF", "CF_TIFF", "CF_OEMTEXT",
"CF_DIB", "CF_PALETTE", "CF_PENDATA", "CF_RIFF",
"CF_WAVE", "CF_UNICODETEXT", "CF_ENHMETAFILE", "CF_HDROP",
"CF_LOCALE", "CF_DIBV5"
};
std::vector<format_info> formats;
std::vector<char> format_name(512);
formats.reserve(CountClipboardFormats());
UINT format_id = EnumClipboardFormats(0);
while (format_id != 0) {
if (format_id >= CF_TEXT && format_id < CF_MAX) {
// Standard clipboard format
formats.emplace_back(format_id, standard_formats[format_id]);
}
// Get user-defined format name
else {
int size = GetClipboardFormatNameA(
format_id,
format_name.data(),
format_name.size());
formats.emplace_back(format_id, std::string(format_name.data(), size));
}
format_id = EnumClipboardFormats(format_id);
}
return formats;
}
#endif // CLIP_ENABLE_LIST_FORMATS
#if CLIP_ENABLE_IMAGE
bool lock::impl::set_image(const image& image) {
const image_spec& spec = image.spec();
// Add the PNG clipboard format for images with alpha channel
// (useful to communicate with some Windows programs that only use
// alpha data from PNG clipboard format)
if (spec.bits_per_pixel == 32 &&
spec.alpha_mask) {
UINT png_format = RegisterClipboardFormatA("PNG");
if (png_format) {
Hglobal png_handle(win::write_png(image));
if (png_handle)
SetClipboardData(png_format, png_handle);
}
}
Hglobal hmem(clip::win::create_dibv5(image));
if (!hmem)
return false;
SetClipboardData(CF_DIBV5, hmem);
return true;
}
bool lock::impl::get_image(image& output_img) const {
// Tries to get the first image format that can be read using WIC
// ("PNG", "JPG", "GIF", etc).
UINT cbformat;
if (auto read_img = win::wic_image_format_available(&cbformat)) {
HANDLE handle = GetClipboardData(cbformat);
if (handle) {
size_t size = GlobalSize(handle);
uint8_t* data = (uint8_t*)GlobalLock(handle);
bool result = read_img(data, size, &output_img, nullptr);
GlobalUnlock(handle);
if (result)
return true;
}
}
// If we couldn't find any, we try to use the regular DIB format.
win::BitmapInfo bi;
return bi.to_image(output_img);
}
bool lock::impl::get_image_spec(image_spec& spec) const {
UINT cbformat;
if (auto read_img = win::wic_image_format_available(&cbformat)) {
HANDLE handle = GetClipboardData(cbformat);
if (handle) {
size_t size = GlobalSize(handle);
uint8_t* data = (uint8_t*)GlobalLock(handle);
bool result = read_img(data, size, nullptr, &spec);
GlobalUnlock(handle);
if (result)
return true;
}
}
win::BitmapInfo bi;
if (!bi.is_valid())
return false;
bi.fill_spec(spec);
return true;
}
#endif // CLIP_ENABLE_IMAGE
format register_format(const std::string& name) {
int reqsize = 1+MultiByteToWideChar(CP_UTF8, 0,
name.c_str(), name.size(), NULL, 0);
std::vector<WCHAR> buf(reqsize);
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), name.size(),
&buf[0], reqsize);
// From MSDN, registered clipboard formats are identified by values
// in the range 0xC000 through 0xFFFF.
return (format)RegisterClipboardFormatW(&buf[0]);
}
} // namespace clip

View File

@@ -1,26 +0,0 @@
// Clip Library
// Copyright (c) 2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_WIN_H_INCLUDED
#define CLIP_WIN_H_INCLUDED
#pragma once
#include <windows.h>
#ifndef LCS_WINDOWS_COLOR_SPACE
#define LCS_WINDOWS_COLOR_SPACE 'Win '
#endif
#ifndef CF_DIBV5
#define CF_DIBV5 17
#endif
#if CLIP_ENABLE_IMAGE
#include "clip_win_bmp.h"
#include "clip_win_wic.h"
#endif
#endif // CLIP_WIN_H_INCLUDED

View File

@@ -1,347 +0,0 @@
// Clip Library
// Copyright (c) 2015-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip_win_bmp.h"
#include "clip.h"
#include "clip_common.h"
#include <vector>
namespace clip {
namespace win {
namespace {
unsigned long get_shift_from_mask(unsigned long mask) {
unsigned long shift = 0;
for (shift=0; shift<sizeof(unsigned long)*8; ++shift)
if (mask & (1 << shift))
return shift;
return shift;
}
} // anonymous namespace
BitmapInfo::BitmapInfo() {
// Use DIBV5 only for 32 bpp uncompressed bitmaps and when all
// masks are valid.
if (IsClipboardFormatAvailable(CF_DIBV5)) {
b5 = (BITMAPV5HEADER*)GetClipboardData(CF_DIBV5);
if (load_from(b5))
return;
}
if (IsClipboardFormatAvailable(CF_DIB)) {
bi = (BITMAPINFO*)GetClipboardData(CF_DIB);
load_from(bi);
}
}
bool BitmapInfo::load_from(BITMAPV5HEADER* b5) {
if (b5 &&
b5->bV5BitCount == 32 &&
((b5->bV5Compression == BI_RGB) ||
(b5->bV5Compression == BI_BITFIELDS &&
b5->bV5RedMask && b5->bV5GreenMask &&
b5->bV5BlueMask && b5->bV5AlphaMask))) {
width = b5->bV5Width;
height = b5->bV5Height;
bit_count = b5->bV5BitCount;
compression = b5->bV5Compression;
if (compression == BI_BITFIELDS) {
red_mask = b5->bV5RedMask;
green_mask = b5->bV5GreenMask;
blue_mask = b5->bV5BlueMask;
alpha_mask = b5->bV5AlphaMask;
}
else {
red_mask = 0xff0000;
green_mask = 0xff00;
blue_mask = 0xff;
alpha_mask = 0xff000000;
}
return true;
}
return false;
}
bool BitmapInfo::load_from(BITMAPINFO* bi) {
if (!bi)
return false;
width = bi->bmiHeader.biWidth;
height = bi->bmiHeader.biHeight;
bit_count = bi->bmiHeader.biBitCount;
compression = bi->bmiHeader.biCompression;
if (compression == BI_BITFIELDS) {
red_mask = *((uint32_t*)&bi->bmiColors[0]);
green_mask = *((uint32_t*)&bi->bmiColors[1]);
blue_mask = *((uint32_t*)&bi->bmiColors[2]);
if (bit_count == 32)
alpha_mask = 0xff000000;
return true;
}
if (compression == BI_RGB) {
switch (bit_count) {
case 32:
red_mask = 0xff0000;
green_mask = 0xff00;
blue_mask = 0xff;
alpha_mask = 0xff000000;
break;
case 24:
case 8: // We return 8bpp images as 24bpp
red_mask = 0xff0000;
green_mask = 0xff00;
blue_mask = 0xff;
break;
case 16:
red_mask = 0x7c00;
green_mask = 0x03e0;
blue_mask = 0x001f;
break;
}
return true;
}
return false;
}
BitmapInfo::BitmapInfo(BITMAPV5HEADER* pb5) {
if (load_from(pb5))
b5 = pb5;
}
BitmapInfo::BitmapInfo(BITMAPINFO* pbi) {
if (load_from(pbi))
bi = pbi;
}
void BitmapInfo::fill_spec(image_spec& spec) const {
spec.width = width;
spec.height = (height >= 0 ? height: -height);
// We convert indexed to 24bpp RGB images to match the OS X behavior
spec.bits_per_pixel = bit_count;
if (spec.bits_per_pixel <= 8)
spec.bits_per_pixel = 24;
spec.bytes_per_row = width*((spec.bits_per_pixel+7)/8);
spec.red_mask = red_mask;
spec.green_mask = green_mask;
spec.blue_mask = blue_mask;
spec.alpha_mask = alpha_mask;
switch (spec.bits_per_pixel) {
case 24: {
// We need one extra byte to avoid a crash updating the last
// pixel on last row using:
//
// *((uint32_t*)ptr) = pixel24bpp;
//
++spec.bytes_per_row;
// Align each row to 32bpp
int padding = (4-(spec.bytes_per_row&3))&3;
spec.bytes_per_row += padding;
break;
}
case 16: {
int padding = (4-(spec.bytes_per_row&3))&3;
spec.bytes_per_row += padding;
break;
}
}
unsigned long* masks = &spec.red_mask;
unsigned long* shifts = &spec.red_shift;
for (unsigned long* shift=shifts, *mask=masks; shift<shifts+4; ++shift, ++mask) {
if (*mask)
*shift = get_shift_from_mask(*mask);
}
}
bool BitmapInfo::to_image(image& output_img) const {
if (!is_valid()) {
// There is no valid image. Maybe because there is no image at all
// in the clipboard when using the BitmapInfo default
// constructor. No need to report this as an error, just return
// false.
return false;
}
image_spec spec;
fill_spec(spec);
image img(spec);
switch (bit_count) {
case 32:
case 24:
case 16: {
const uint8_t* src = nullptr;
if (compression == BI_RGB ||
compression == BI_BITFIELDS) {
if (b5)
src = ((uint8_t*)b5) + b5->bV5Size;
else
src = ((uint8_t*)bi) + bi->bmiHeader.biSize;
if (compression == BI_BITFIELDS)
src += sizeof(RGBQUAD)*3;
}
if (src) {
const int src_bytes_per_row = spec.width*((bit_count+7)/8);
const int padding = (4-(src_bytes_per_row&3))&3;
for (long y=spec.height-1; y>=0; --y, src+=src_bytes_per_row+padding) {
char* dst = img.data()+y*spec.bytes_per_row;
std::copy(src, src+src_bytes_per_row, dst);
}
}
// Windows uses premultiplied RGB values, and we use straight
// alpha. So we have to divide all RGB values by its alpha.
if (bit_count == 32 && spec.alpha_mask) {
details::divide_rgb_by_alpha(img);
}
break;
}
case 8: {
assert(bi);
const int colors = (bi->bmiHeader.biClrUsed > 0 ? bi->bmiHeader.biClrUsed: 256);
std::vector<uint32_t> palette(colors);
for (int c=0; c<colors; ++c) {
palette[c] =
(bi->bmiColors[c].rgbRed << spec.red_shift) |
(bi->bmiColors[c].rgbGreen << spec.green_shift) |
(bi->bmiColors[c].rgbBlue << spec.blue_shift);
}
const uint8_t* src = (((uint8_t*)bi) + bi->bmiHeader.biSize + sizeof(RGBQUAD)*colors);
const int padding = (4-(spec.width&3))&3;
for (long y=spec.height-1; y>=0; --y, src+=padding) {
char* dst = img.data()+y*spec.bytes_per_row;
for (unsigned long x=0; x<spec.width; ++x, ++src, dst+=3) {
int idx = *src;
if (idx < 0)
idx = 0;
else if (idx >= colors)
idx = colors-1;
*((uint32_t*)dst) = palette[idx];
}
}
break;
}
}
std::swap(output_img, img);
return true;
}
HGLOBAL create_dibv5(const image& image) {
const image_spec& spec = image.spec();
image_spec out_spec = spec;
int palette_colors = 0;
int padding = 0;
switch (spec.bits_per_pixel) {
case 24: padding = (4-((spec.width*3)&3))&3; break;
case 16: padding = ((4-((spec.width*2)&3))&3)/2; break;
case 8: padding = (4-(spec.width&3))&3; break;
}
out_spec.bytes_per_row += padding;
// Create the BITMAPV5HEADER structure
HGLOBAL hmem =
GlobalAlloc(
GHND,
sizeof(BITMAPV5HEADER)
+ palette_colors*sizeof(RGBQUAD)
+ out_spec.bytes_per_row*out_spec.height);
if (!hmem)
return nullptr;
out_spec.red_mask = 0x00ff0000;
out_spec.green_mask = 0xff00;
out_spec.blue_mask = 0xff;
out_spec.alpha_mask = 0xff000000;
out_spec.red_shift = 16;
out_spec.green_shift = 8;
out_spec.blue_shift = 0;
out_spec.alpha_shift = 24;
BITMAPV5HEADER* bi = (BITMAPV5HEADER*)GlobalLock(hmem);
bi->bV5Size = sizeof(BITMAPV5HEADER);
bi->bV5Width = out_spec.width;
bi->bV5Height = out_spec.height;
bi->bV5Planes = 1;
bi->bV5BitCount = (WORD)out_spec.bits_per_pixel;
bi->bV5Compression = BI_RGB;
bi->bV5SizeImage = out_spec.bytes_per_row*spec.height;
bi->bV5RedMask = out_spec.red_mask;
bi->bV5GreenMask = out_spec.green_mask;
bi->bV5BlueMask = out_spec.blue_mask;
bi->bV5AlphaMask = out_spec.alpha_mask;
bi->bV5CSType = LCS_WINDOWS_COLOR_SPACE;
bi->bV5Intent = LCS_GM_GRAPHICS;
bi->bV5ClrUsed = 0;
switch (spec.bits_per_pixel) {
case 32: {
const char* src = image.data();
char* dst = (((char*)bi)+bi->bV5Size) + (out_spec.height-1)*out_spec.bytes_per_row;
for (long y=spec.height-1; y>=0; --y) {
const uint32_t* src_x = (const uint32_t*)src;
uint32_t* dst_x = (uint32_t*)dst;
for (unsigned long x=0; x<spec.width; ++x, ++src_x, ++dst_x) {
uint32_t c = *src_x;
int r = ((c & spec.red_mask ) >> spec.red_shift );
int g = ((c & spec.green_mask) >> spec.green_shift);
int b = ((c & spec.blue_mask ) >> spec.blue_shift );
int a = ((c & spec.alpha_mask) >> spec.alpha_shift);
// Windows requires premultiplied RGBA values
r = r * a / 255;
g = g * a / 255;
b = b * a / 255;
*dst_x =
(r << out_spec.red_shift ) |
(g << out_spec.green_shift) |
(b << out_spec.blue_shift ) |
(a << out_spec.alpha_shift);
}
src += spec.bytes_per_row;
dst -= out_spec.bytes_per_row;
}
break;
}
default:
GlobalUnlock(hmem);
GlobalFree(hmem);
error_handler e = get_error_handler();
if (e)
e(ErrorCode::ImageNotSupported);
return nullptr;
}
GlobalUnlock(hmem);
return hmem;
}
} // namespace win
} // namespace clip

View File

@@ -1,66 +0,0 @@
// Clip Library
// Copyright (c) 2015-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_WIN_BMP_H_INCLUDED
#define CLIP_WIN_BMP_H_INCLUDED
#pragma once
#if !CLIP_ENABLE_IMAGE
#error This file can be include only when CLIP_ENABLE_IMAGE is defined
#endif
#include <cstdint>
#include <windows.h>
namespace clip {
class image;
struct image_spec;
namespace win {
struct BitmapInfo {
BITMAPV5HEADER* b5 = nullptr;
BITMAPINFO* bi = nullptr;
int width = 0;
int height = 0;
uint16_t bit_count = 0;
uint32_t compression = 0;
uint32_t red_mask = 0;
uint32_t green_mask = 0;
uint32_t blue_mask = 0;
uint32_t alpha_mask = 0;
BitmapInfo();
explicit BitmapInfo(BITMAPV5HEADER* pb5);
explicit BitmapInfo(BITMAPINFO* pbi);
bool is_valid() const {
return (b5 || bi);
}
void fill_spec(image_spec& spec) const;
// Fills the output_img with the data provided by this
// BitmapInfo. Returns true if it was able to fill the output image
// or false otherwise.
bool to_image(image& output_img) const;
private:
bool load_from(BITMAPV5HEADER* b5);
bool load_from(BITMAPINFO* bi);
};
// Returns a handle to the HGLOBAL memory reserved to create a DIBV5
// based on the image passed by parameter. Returns null if it cannot
// create the handle.
HGLOBAL create_dibv5(const image& image);
} // namespace win
} // namespace clip
#endif // CLIP_WIN_BMP_H_INCLUDED

View File

@@ -1,472 +0,0 @@
// Clip Library
// Copyright (c) 2020-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip_win_wic.h"
#include "clip.h"
#include <algorithm>
#include <vector>
#include <shlwapi.h>
#include <wincodec.h>
namespace clip {
namespace win {
namespace {
// Successful calls to CoInitialize() (S_OK or S_FALSE) must match
// the calls to CoUninitialize().
// From: https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-couninitialize#remarks
struct coinit {
HRESULT hr;
coinit() {
hr = CoInitialize(nullptr);
}
~coinit() {
if (hr == S_OK || hr == S_FALSE)
CoUninitialize();
}
};
template<class T>
class comptr {
public:
comptr() { }
explicit comptr(T* ptr) : m_ptr(ptr) { }
comptr(const comptr&) = delete;
comptr& operator=(const comptr&) = delete;
~comptr() { reset(); }
T** operator&() { return &m_ptr; }
T* operator->() { return m_ptr; }
bool operator!() const { return !m_ptr; }
T* get() { return m_ptr; }
void reset() {
if (m_ptr) {
m_ptr->Release();
m_ptr = nullptr;
}
}
private:
T* m_ptr = nullptr;
};
#ifdef CLIP_SUPPORT_WINXP
class hmodule {
public:
hmodule(LPCWSTR name) : m_ptr(LoadLibraryW(name)) { }
hmodule(const hmodule&) = delete;
hmodule& operator=(const hmodule&) = delete;
~hmodule() {
if (m_ptr)
FreeLibrary(m_ptr);
}
operator HMODULE() { return m_ptr; }
bool operator!() const { return !m_ptr; }
private:
HMODULE m_ptr = nullptr;
};
#endif
struct WicImageFormat {
const char* names[3]; // Alternative names of this format
UINT ids[3]; // Clipboard format ID for each name of this format
ReadWicImageFormatFunc read; // Function used to decode data in this format
};
WicImageFormat wic_image_formats[] = {
{ { "PNG", "image/png", nullptr }, { 0, 0, 0 }, read_png },
{ { "JPG", "image/jpeg", "JPEG" }, { 0, 0, 0 }, read_jpg },
{ { "BMP", "image/bmp", nullptr }, { 0, 0, 0 }, read_bmp },
{ { "GIF", "image/gif", nullptr }, { 0, 0, 0 }, read_gif }
};
} // anonymous namespace
ReadWicImageFormatFunc wic_image_format_available(UINT* output_cbformat) {
for (auto& fmt : wic_image_formats) {
for (int i=0; i<3; ++i) {
const char* name = fmt.names[i];
if (!name)
break;
// Although RegisterClipboardFormatA() already returns the same
// value for the same "name" (even for different apps), we
// prefer to cache the value to avoid calling
// RegisterClipboardFormatA() several times (as internally that
// function must do some kind of hash map name -> ID
// conversion).
UINT cbformat = fmt.ids[i];
if (cbformat == 0)
fmt.ids[i] = cbformat = RegisterClipboardFormatA(name);
if (cbformat && IsClipboardFormatAvailable(cbformat)) {
if (output_cbformat)
*output_cbformat = cbformat;
return fmt.read;
}
}
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////
// Encode the image as PNG format
bool write_png_on_stream(const image& image,
IStream* stream) {
const image_spec& spec = image.spec();
comptr<IWICBitmapEncoder> encoder;
HRESULT hr = CoCreateInstance(CLSID_WICPngEncoder,
nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&encoder));
if (FAILED(hr))
return false;
hr = encoder->Initialize(stream, WICBitmapEncoderNoCache);
if (FAILED(hr))
return false;
comptr<IWICBitmapFrameEncode> frame;
comptr<IPropertyBag2> options;
hr = encoder->CreateNewFrame(&frame, &options);
if (FAILED(hr))
return false;
hr = frame->Initialize(options.get());
if (FAILED(hr))
return false;
// PNG encoder (and decoder) only supports GUID_WICPixelFormat32bppBGRA for 32bpp.
// See: https://docs.microsoft.com/en-us/windows/win32/wic/-wic-codec-native-pixel-formats#png-native-codec
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppBGRA;
hr = frame->SetPixelFormat(&pixelFormat);
if (FAILED(hr))
return false;
hr = frame->SetSize(spec.width, spec.height);
if (FAILED(hr))
return false;
std::vector<uint32_t> buf;
uint8_t* ptr = (uint8_t*)image.data();
int bytes_per_row = spec.bytes_per_row;
// Convert to GUID_WICPixelFormat32bppBGRA if needed
if (spec.red_mask != 0xff0000 ||
spec.green_mask != 0xff00 ||
spec.blue_mask != 0xff ||
spec.alpha_mask != 0xff000000) {
buf.resize(spec.width * spec.height);
uint32_t* dst = (uint32_t*)&buf[0];
uint32_t* src = (uint32_t*)image.data();
for (int y=0; y<spec.height; ++y) {
auto src_line_start = src;
for (int x=0; x<spec.width; ++x) {
uint32_t c = *src;
*dst = ((((c & spec.red_mask ) >> spec.red_shift ) << 16) |
(((c & spec.green_mask) >> spec.green_shift) << 8) |
(((c & spec.blue_mask ) >> spec.blue_shift ) ) |
(((c & spec.alpha_mask) >> spec.alpha_shift) << 24));
++dst;
++src;
}
src = (uint32_t*)(((uint8_t*)src_line_start) + spec.bytes_per_row);
}
ptr = (uint8_t*)&buf[0];
bytes_per_row = 4 * spec.width;
}
hr = frame->WritePixels(spec.height,
bytes_per_row,
bytes_per_row * spec.height,
(BYTE*)ptr);
if (FAILED(hr))
return false;
hr = frame->Commit();
if (FAILED(hr))
return false;
hr = encoder->Commit();
if (FAILED(hr))
return false;
return true;
}
HGLOBAL write_png(const image& image) {
coinit com;
comptr<IStream> stream;
HRESULT hr = CreateStreamOnHGlobal(nullptr, false, &stream);
if (FAILED(hr))
return nullptr;
bool result = write_png_on_stream(image, stream.get());
HGLOBAL handle;
hr = GetHGlobalFromStream(stream.get(), &handle);
if (result)
return handle;
GlobalFree(handle);
return nullptr;
}
IStream* create_stream(const BYTE* pInit, UINT cbInit)
{
#ifdef CLIP_SUPPORT_WINXP
// Pull SHCreateMemStream from shlwapi.dll by ordinal 12
// for Windows XP support
// From: https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shcreatememstream#remarks
typedef IStream*(WINAPI * SHCreateMemStreamPtr)(const BYTE* pInit,
UINT cbInit);
hmodule shlwapiDll(L"shlwapi.dll");
if (!shlwapiDll)
return false;
auto SHCreateMemStream = reinterpret_cast<SHCreateMemStreamPtr>(
GetProcAddress(shlwapiDll, (LPCSTR)12));
if (!SHCreateMemStream)
return false;
#endif
return SHCreateMemStream(pInit, cbInit);
}
image_spec spec_from_pixelformat(const WICPixelFormatGUID& pixelFormat, unsigned long w, unsigned long h)
{
image_spec spec;
spec.width = w;
spec.height = h;
if (pixelFormat == GUID_WICPixelFormat32bppBGRA ||
pixelFormat == GUID_WICPixelFormat32bppBGR) {
spec.bits_per_pixel = 32;
spec.red_mask = 0xff0000;
spec.green_mask = 0xff00;
spec.blue_mask = 0xff;
spec.alpha_mask = 0xff000000;
spec.red_shift = 16;
spec.green_shift = 8;
spec.blue_shift = 0;
spec.alpha_shift = 24;
// Reset mask and shift for BGR pixel format.
if (pixelFormat == GUID_WICPixelFormat32bppBGR) {
spec.alpha_mask = 0;
spec.alpha_shift = 0;
}
}
else if (pixelFormat == GUID_WICPixelFormat24bppBGR ||
pixelFormat == GUID_WICPixelFormat8bppIndexed) {
spec.bits_per_pixel = 24;
spec.red_mask = 0xff0000;
spec.green_mask = 0xff00;
spec.blue_mask = 0xff;
spec.alpha_mask = 0;
spec.red_shift = 16;
spec.green_shift = 8;
spec.blue_shift = 0;
spec.alpha_shift = 0;
}
spec.bytes_per_row = ((w*spec.bits_per_pixel+31) / 32) * 4;
return spec;
}
// Tries to decode the input buf of size len using the specified
// decoders. If output_image is not null, the decoded image is
// returned there, if output_spec is not null then the image
// specifications are set there.
bool decode(const GUID decoder_clsid1,
const GUID decoder_clsid2,
const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec)
{
coinit com;
comptr<IWICBitmapDecoder> decoder;
HRESULT hr = CoCreateInstance(decoder_clsid1, nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&decoder));
if (FAILED(hr) && decoder_clsid2 != GUID_NULL) {
hr = CoCreateInstance(decoder_clsid2, nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&decoder));
}
if (FAILED(hr))
return false;
// Can decoder be nullptr if hr is S_OK/successful? We've received
// some crash reports that might indicate this.
if (!decoder)
return false;
comptr<IStream> stream(create_stream(buf, len));
if (!stream)
return false;
hr = decoder->Initialize(stream.get(), WICDecodeMetadataCacheOnDemand);
if (FAILED(hr))
return false;
comptr<IWICBitmapFrameDecode> frame;
hr = decoder->GetFrame(0, &frame);
if (FAILED(hr))
return false;
WICPixelFormatGUID pixelFormat;
hr = frame->GetPixelFormat(&pixelFormat);
if (FAILED(hr))
return false;
// Only support these pixel formats
// TODO add support for more pixel formats
if (pixelFormat != GUID_WICPixelFormat32bppBGRA &&
pixelFormat != GUID_WICPixelFormat32bppBGR &&
pixelFormat != GUID_WICPixelFormat24bppBGR &&
pixelFormat != GUID_WICPixelFormat8bppIndexed)
return false;
UINT width = 0, height = 0;
hr = frame->GetSize(&width, &height);
if (FAILED(hr))
return false;
image_spec spec = spec_from_pixelformat(pixelFormat, width, height);
if (output_spec)
*output_spec = spec;
image img;
if (output_image) {
if (pixelFormat == GUID_WICPixelFormat8bppIndexed) {
std::vector<BYTE> pixels(spec.width * spec.height);
hr = frame->CopyPixels(nullptr, // Entire bitmap
spec.width,
spec.width * spec.height,
pixels.data());
if (FAILED(hr))
return false;
comptr<IWICImagingFactory> factory;
HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&factory));
if (FAILED(hr))
return false;
comptr<IWICPalette> palette;
hr = factory->CreatePalette(&palette);
if (FAILED(hr))
return false;
hr = frame->CopyPalette(palette.get());
if (FAILED(hr))
return false;
UINT numcolors;
hr = palette->GetColorCount(&numcolors);
if (FAILED(hr))
return false;
UINT actualNumcolors;
std::vector<WICColor> colors(numcolors);
hr = palette->GetColors(numcolors, colors.data(), &actualNumcolors);
if (FAILED(hr))
return false;
BOOL hasAlpha = false;
palette->HasAlpha(&hasAlpha);
if (hasAlpha) {
spec = spec_from_pixelformat(GUID_WICPixelFormat32bppBGRA, width, height);
}
img = image(spec);
char* dst = img.data();
BYTE* src = pixels.data();
for (int y = 0; y < spec.height; ++y) {
char* dst_x = dst;
for (int x = 0; x < spec.width; ++x, dst_x+=spec.bits_per_pixel/8, ++src) {
*((uint32_t*)dst_x) = (*src < numcolors ? colors[*src] : 0);
}
dst += spec.bytes_per_row;
}
}
else {
img = image(spec);
hr = frame->CopyPixels(nullptr, // Entire bitmap
spec.bytes_per_row,
spec.bytes_per_row * spec.height,
(BYTE*)img.data());
if (FAILED(hr))
return false;
}
std::swap(*output_image, img);
}
return true;
}
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from PNG format
bool read_png(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec) {
return decode(CLSID_WICPngDecoder2, CLSID_WICPngDecoder1,
buf, len, output_image, output_spec);
}
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from JPEG format
bool read_jpg(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec)
{
return decode(CLSID_WICJpegDecoder, GUID_NULL,
buf, len, output_image, output_spec);
}
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from GIF format
bool read_gif(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec)
{
return decode(CLSID_WICGifDecoder, GUID_NULL,
buf, len, output_image, output_spec);
}
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from BMP format
bool read_bmp(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec)
{
return decode(CLSID_WICBmpDecoder, GUID_NULL,
buf, len, output_image, output_spec);
}
} // namespace win
} // namespace clip

View File

@@ -1,76 +0,0 @@
// Clip Library
// Copyright (c) 2020-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_WIN_WIC_H_INCLUDED
#define CLIP_WIN_WIC_H_INCLUDED
#pragma once
#if !CLIP_ENABLE_IMAGE
#error This file can be include only when CLIP_ENABLE_IMAGE is defined
#endif
#include <cstdint>
#include <windows.h>
namespace clip {
class image;
struct image_spec;
namespace win {
typedef bool (*ReadWicImageFormatFunc)(const uint8_t*,
const UINT,
clip::image*,
clip::image_spec*);
ReadWicImageFormatFunc wic_image_format_available(UINT* output_cbformat);
//////////////////////////////////////////////////////////////////////
// Encode the image as PNG format
bool write_png_on_stream(const image& image, IStream* stream);
HGLOBAL write_png(const image& image);
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from PNG format
bool read_png(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec);
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from JPEG format
bool read_jpg(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec);
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from GIF format
bool read_gif(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec);
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from BMP format
bool read_bmp(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec);
} // namespace win
} // namespace clip
#endif // CLIP_WIN_WIC_H_INCLUDED

File diff suppressed because it is too large Load Diff

View File

@@ -1,230 +0,0 @@
// Clip Library
// Copyright (c) 2018-2021 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include <algorithm>
#include <vector>
#include "png.h"
namespace clip {
namespace x11 {
//////////////////////////////////////////////////////////////////////
// Functions to convert clip::image into png data to store it in the
// clipboard.
void write_data_fn(png_structp png, png_bytep buf, png_size_t len) {
std::vector<uint8_t>& output = *(std::vector<uint8_t>*)png_get_io_ptr(png);
const size_t i = output.size();
output.resize(i+len);
std::copy(buf, buf+len, output.begin()+i);
}
bool write_png(const image& image,
std::vector<uint8_t>& output) {
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
nullptr, nullptr, nullptr);
if (!png)
return false;
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_write_struct(&png, nullptr);
return false;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_write_struct(&png, &info);
return false;
}
png_set_write_fn(png,
(png_voidp)&output,
write_data_fn,
nullptr); // No need for a flush function
const image_spec& spec = image.spec();
int color_type = (spec.alpha_mask ?
PNG_COLOR_TYPE_RGB_ALPHA:
PNG_COLOR_TYPE_RGB);
png_set_IHDR(png, info,
spec.width, spec.height, 8, color_type,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png, info);
png_set_packing(png);
png_bytep row =
(png_bytep)png_malloc(png, png_get_rowbytes(png, info));
for (png_uint_32 y=0; y<spec.height; ++y) {
const uint32_t* src =
(const uint32_t*)(((const uint8_t*)image.data())
+ y*spec.bytes_per_row);
uint8_t* dst = row;
unsigned int x, c;
for (x=0; x<spec.width; x++) {
c = *(src++);
*(dst++) = (c & spec.red_mask ) >> spec.red_shift;
*(dst++) = (c & spec.green_mask) >> spec.green_shift;
*(dst++) = (c & spec.blue_mask ) >> spec.blue_shift;
if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
*(dst++) = (c & spec.alpha_mask) >> spec.alpha_shift;
}
png_write_rows(png, &row, 1);
}
png_free(png, row);
png_write_end(png, info);
png_destroy_write_struct(&png, &info);
return true;
}
//////////////////////////////////////////////////////////////////////
// Functions to convert png data stored in the clipboard to a
// clip::image.
struct read_png_io {
const uint8_t* buf;
size_t len;
size_t pos;
};
void read_data_fn(png_structp png, png_bytep buf, png_size_t len) {
read_png_io& io = *(read_png_io*)png_get_io_ptr(png);
if (io.pos < io.len) {
size_t n = std::min(len, io.len-io.pos);
if (n > 0) {
std::copy(io.buf+io.pos,
io.buf+io.pos+n,
buf);
io.pos += n;
}
}
}
bool read_png(const uint8_t* buf,
const size_t len,
image* output_image,
image_spec* output_spec) {
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr, nullptr, nullptr);
if (!png)
return false;
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_read_struct(&png, nullptr, nullptr);
return false;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_read_struct(&png, &info, nullptr);
return false;
}
read_png_io io = { buf, len, 0 };
png_set_read_fn(png, (png_voidp)&io, read_data_fn);
png_read_info(png, info);
png_uint_32 width, height;
int bit_depth, color_type, interlace_type;
png_get_IHDR(png, info, &width, &height,
&bit_depth, &color_type,
&interlace_type,
nullptr, nullptr);
image_spec spec;
spec.width = width;
spec.height = height;
spec.bits_per_pixel = 32;
// Don't use png_get_rowbytes(png, info) here because this is the
// bytes_per_row of the output clip::image (the png file could
// contain 24bpp but we want to return a 32bpp anyway with alpha=255
// in that case).
spec.bytes_per_row = 4*width;
spec.red_mask = 0x000000ff;
spec.green_mask = 0x0000ff00;
spec.blue_mask = 0x00ff0000;
spec.red_shift = 0;
spec.green_shift = 8;
spec.blue_shift = 16;
if ((color_type & PNG_COLOR_MASK_ALPHA) == PNG_COLOR_MASK_ALPHA) {
spec.alpha_mask = 0xff000000;
spec.alpha_shift = 24;
}
else {
spec.alpha_mask = 0;
spec.alpha_shift = 0;
}
if (output_spec)
*output_spec = spec;
if (output_image &&
width > 0 &&
height > 0) {
image img(spec);
// We want RGB 24-bit or RGBA 32-bit as a result
png_set_strip_16(png); // Down to 8-bit (TODO we might support 16-bit values)
png_set_packing(png); // Use one byte if color depth < 8-bit
png_set_expand_gray_1_2_4_to_8(png);
png_set_palette_to_rgb(png);
png_set_gray_to_rgb(png);
png_set_tRNS_to_alpha(png);
int number_passes = png_set_interlace_handling(png);
png_read_update_info(png, info);
const int src_bytes_per_row = png_get_rowbytes(png, info);
png_bytepp rows = (png_bytepp)png_malloc(png, sizeof(png_bytep)*height);
png_uint_32 y;
for (y=0; y<height; ++y)
rows[y] = (png_bytep)png_malloc(png, src_bytes_per_row);
for (int pass=0; pass<number_passes; ++pass)
for (y=0; y<height; ++y)
png_read_rows(png, rows+y, nullptr, 1);
for (y=0; y<height; ++y) {
const uint8_t* src = rows[y];
uint32_t* dst = (uint32_t*)(img.data() + y*spec.bytes_per_row);
unsigned int x, r, g, b, a = 0;
for (x=0; x<width; x++) {
r = *(src++);
g = *(src++);
b = *(src++);
if (spec.alpha_mask)
a = *(src++);
*(dst++) =
(r << spec.red_shift) |
(g << spec.green_shift) |
(b << spec.blue_shift) |
(a << spec.alpha_shift);
}
png_free(png, rows[y]);
}
png_free(png, rows);
std::swap(*output_image, img);
}
png_destroy_read_struct(&png, &info, nullptr);
return true;
}
} // namespace x11
} // namespace clip

View File

@@ -1,99 +0,0 @@
// Clip Library
// Copyright (c) 2015-2022 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
namespace clip {
unsigned long image_spec::required_data_size() const
{
unsigned long n = (bytes_per_row * height);
// For 24bpp we add some extra space to access the last pixel (3
// bytes) as an uint32_t
if (bits_per_pixel == 24) {
if ((n % 4) > 0)
n += 4 - (n % 4);
else
++n;
}
return n;
}
image::image()
: m_own_data(false),
m_data(nullptr)
{
}
image::image(const image_spec& spec)
: m_own_data(true),
m_data(new char[spec.required_data_size()]),
m_spec(spec) {
}
image::image(const void* data, const image_spec& spec)
: m_own_data(false),
m_data((char*)data),
m_spec(spec) {
}
image::image(const image& image)
: m_own_data(false),
m_data(nullptr),
m_spec(image.m_spec) {
copy_image(image);
}
image::image(image&& image)
: m_own_data(false),
m_data(nullptr) {
move_image(std::move(image));
}
image::~image() {
reset();
}
image& image::operator=(const image& image) {
copy_image(image);
return *this;
}
image& image::operator=(image&& image) {
move_image(std::move(image));
return *this;
}
void image::reset() {
if (m_own_data) {
delete[] m_data;
m_own_data = false;
m_data = nullptr;
}
}
void image::copy_image(const image& image) {
reset();
m_spec = image.spec();
std::size_t n = m_spec.required_data_size();
m_own_data = true;
m_data = new char[n];
std::copy(image.data(),
image.data()+n,
m_data);
}
void image::move_image(image&& image) {
std::swap(m_own_data, image.m_own_data);
std::swap(m_data, image.m_data);
std::swap(m_spec, image.m_spec);
}
} // namespace clip

591
include/argparse.hpp Normal file
View File

@@ -0,0 +1,591 @@
#pragma once
//
// @author : Morris Franken
// https://github.com/morrisfranken/argparse
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#include <cctype> // for isdigit, tolower
#include <sstream>
#include <cstdlib> // for size_t, exit
#include <algorithm> // for max, transform, copy, min
#include <iomanip> // for operator<<, setw
#include <iostream> // for operator<<, basic_ostream, endl, ostream
#include <iterator> // for ostream_iterator
#include <map> // for operator!=, map, _Rb_tree_iterator
#include <memory> // for allocator, shared_ptr, __shared_ptr_ac...
#include <optional> // for optional, nullopt
#include <stdexcept> // for runtime_error, invalid_argument
#include <filesystem> // for getting program_name from path
#include <string> // for string, operator+, basic_string, char_...
#include <type_traits> // for declval, false_type, true_type, is_enum
#include <utility> // for move, pair
#include <vector> // for vector
#if __has_include(<magic_enum.hpp>)
#include <magic_enum.hpp> // for enum_entries
#define HAS_MAGIC_ENUM
#endif
#define ARGPARSE_VERSION 4
namespace argparse {
class Args;
using std::cout, std::cerr, std::endl, std::setw, std::size_t;
template<typename T> struct is_vector : public std::false_type {};
template<typename T, typename A> struct is_vector<std::vector<T, A>> : public std::true_type {};
template<typename T> struct is_optional : public std::false_type {};
template<typename T> struct is_optional<std::optional<T>> : public std::true_type {};
template<typename T> struct is_shared_ptr : public std::false_type {};
template<typename T> struct is_shared_ptr<std::shared_ptr<T>> : public std::true_type {};
template <typename, typename = void> struct has_ostream_operator : std::false_type {};
template <typename T> struct has_ostream_operator<T, decltype(void(std::declval<std::ostream&>() << std::declval<const T&>()))> : std::true_type {};
inline std::string bold(const std::string& input_str) {
#ifdef _WIN32
return input_str; // no bold for windows
#else
return "\033[1m" + input_str + "\033[0m";
#endif
}
template<typename T> std::string toString(const T &v) {
if constexpr (has_ostream_operator<T>::value) {
return static_cast<std::ostringstream &&>((std::ostringstream() << std::boolalpha << v)).str(); // https://github.com/stan-dev/math/issues/590#issuecomment-550122627
} else {
return "unknown";
}
}
std::vector<std::string> inline split(const std::string &str) {
std::vector<std::string> splits;
std::stringstream ss(str);
std::string key;
while (std::getline(ss, key, ',')) {
if (!key.empty() && key.back() == '\0')
key.pop_back(); // last variables contain a '\0', which is unexpected when comparing to raw string, e.g. value == "test" will fail when the last character is '\0'. Therefore we can remove it
splits.emplace_back(std::move(key));
}
return splits;
}
template <typename T> std::string to_lower(const T &str_) { // both std::string and std::basic_string_view<char> (for magic_enum) are using to_lower
std::string str(str_.size(), '\0');
std::transform(str_.begin(), str_.end(), str.begin(), ::tolower);
return str;
}
template<typename T> inline T get(const std::string &v);
template<> inline std::string get(const std::string &v) { return v; }
template<> inline char get(const std::string &v) { return v.empty()? throw std::invalid_argument("empty string") : v.size() > 1? v.substr(0,2) == "0x"? (char)std::stoul(v, nullptr, 16) : (char)std::stoi(v) : v[0]; }
template<> inline int get(const std::string &v) { return std::stoi(v); }
template<> inline short get(const std::string &v) { return std::stoi(v); }
template<> inline long get(const std::string &v) { return std::stol(v); }
template<> inline long long get(const std::string &v) { return std::stol(v); }
template<> inline bool get(const std::string &v) { return to_lower(v) == "true" || v == "1"; }
template<> inline float get(const std::string &v) { return std::stof(v); }
template<> inline double get(const std::string &v) { return std::stod(v); }
template<> inline unsigned char get(const std::string &v) { return get<char>(v); }
template<> inline unsigned int get(const std::string &v) { return std::stoul(v); }
template<> inline unsigned short get(const std::string &v) { return std::stoul(v); }
template<> inline unsigned long get(const std::string &v) { return std::stoul(v); }
template<> inline unsigned long long get(const std::string &v) { return std::stoul(v); }
template<typename T> inline T get(const std::string &v) { // remaining types
if constexpr (is_vector<T>::value) {
const std::vector<std::string> splitted = split(v);
T res(splitted.size());
if (!v.empty())
std::transform (splitted.begin(), splitted.end(), res.begin(), get<typename T::value_type>);
return res;
} else if constexpr (std::is_pointer<T>::value) {
return new typename std::remove_pointer<T>::type(get<typename std::remove_pointer<T>::type>(v));
} else if constexpr (is_shared_ptr<T>::value) {
return std::make_shared<typename T::element_type>(get<typename T::element_type>(v));
} else if constexpr (is_optional<T>::value) {
return get<typename T::value_type>(v);
} else if constexpr (std::is_enum<T>::value) { // case-insensitive enum conversion
#ifdef HAS_MAGIC_ENUM
constexpr auto& enum_entries = magic_enum::enum_entries<T>();
const std::string lower_str = to_lower(v);
for (const auto &[value, name] : enum_entries) {
if (to_lower(name) == lower_str)
return value;
}
std::string error = "enum is only accepting [";
for (size_t i = 0; i < enum_entries.size(); i++)
error += (i==0? "" : ", ") + to_lower(enum_entries[i].second);
error += "]";
throw std::runtime_error(error);
#else
throw std::runtime_error("Enum not supported, please install magic_enum (https://github.com/Neargye/magic_enum)");
#endif
} else {
return T(v);
}
}
struct ConvertBase {
virtual ~ConvertBase() = default;
virtual void convert(const std::string &v) = 0;
virtual void set_default(const std::unique_ptr<ConvertBase> &default_value, const std::string &default_string) = 0;
[[nodiscard]] virtual size_t get_type_id() const = 0;
[[nodiscard]] virtual std::string get_allowed_entries() const = 0;
};
template <typename T> struct ConvertType : public ConvertBase {
T data;
~ConvertType() override = default;
ConvertType() : ConvertBase() {};
explicit ConvertType(const T &value) : ConvertBase(), data(value) {};
void convert(const std::string &v) override {
data = get<T>(v);
}
void set_default(const std::unique_ptr<ConvertBase> &default_value, const std::string &default_string) override {
if (this->get_type_id() == default_value->get_type_id()) // When the types do not match exactly. resort to string conversion
data = ((ConvertType<T>*)(default_value.get()))->data;
else
data = get<T>(default_string);
}
[[nodiscard]] size_t get_type_id() const override {
return typeid(T).hash_code();
}
[[nodiscard]] std::string get_allowed_entries() const override {
std::stringstream ss;
#ifdef HAS_MAGIC_ENUM
if constexpr (std::is_enum<T>::value) {
for (const auto &[value, name] : magic_enum::enum_entries<T>()) {
ss << to_lower(name) << ", ";
}
}
#endif
return ss.str();
}
};
struct Entry {
enum ARG_TYPE {ARG, KWARG, FLAG} type;
Entry(ARG_TYPE type, const std::string& key, std::string help, std::optional<std::string> implicit_value=std::nullopt) :
type(type),
keys_(split(key)),
help(std::move(help)),
implicit_value_(std::move(implicit_value)) {
}
// Allow both string inputs and direct-type inputs. Where a string-input will be converted like it would when using the commandline, and the direct approach is to simply use the value provided.
template <typename T> Entry &set_default(const T &default_value) {
this->default_str_ = toString(default_value);
if constexpr (!(std::is_array<T>::value || std::is_same<typename std::remove_all_extents<T>::type, char>::value)) {
data_default = std::make_unique<ConvertType<T>>(default_value);
}
return *this;
}
Entry &multi_argument() {
_is_multi_argument = true;
return *this;
}
// Magically convert the value string to the requested type
template <typename T> operator T&() {
// Automatically set the default to nullptr for pointer types and empty for optional types
if constexpr (is_optional<T>::value || std::is_pointer<T>::value || is_shared_ptr<T>::value) {
if (!default_str_.has_value()) {
default_str_ = "none";
if constexpr(is_optional<T>::value) {
data_default = std::make_unique<ConvertType<T>> (T{std::nullopt});
} else {
data_default = std::make_unique<ConvertType<T>> ((T) nullptr);
}
}
}
datap = std::make_unique<ConvertType<T>>();
return ((ConvertType<T>*)(datap.get()))->data;
}
// Force an ambiguous error when not using a reference.
template <typename T> operator T() {} // When you get here because you received an error, make sure all parameters of argparse are references (e.g. with `&`)
private:
std::vector<std::string> keys_;
std::string help;
std::optional<std::string> value_;
std::optional<std::string> implicit_value_;
std::optional<std::string> default_str_;
std::string error;
std::unique_ptr<ConvertBase> datap;
std::unique_ptr<ConvertBase> data_default;
bool _is_multi_argument = false;
bool is_set_by_user = true;
[[nodiscard]] std::string _get_keys() const {
std::stringstream ss;
for (size_t i = 0; i < keys_.size(); i++)
ss << (i? "," : "") << (type == ARG? "" : (keys_[i].size() > 1 ? "--" : "-")) + keys_[i];
return ss.str();
}
void _convert(const std::string &value) {
try {
this->value_ = value;
datap->convert(value);
} catch (const std::invalid_argument &e) {
error = "Invalid argument, could not convert \"" + value + "\" for " + _get_keys() + " (" + help + ")";
} catch (const std::runtime_error &e) {
error = "Invalid argument \"" + value + "\" for " + _get_keys() + " (" + help + "). Error: " + e.what();
}
}
void _apply_default() {
is_set_by_user = false;
if (data_default != nullptr) {
value_ = *default_str_; // for printing
datap->set_default(data_default, *default_str_);
} else if (default_str_.has_value()) { // in cases where a string is provided to the `set_default` function
_convert(default_str_.value());
} else {
error = "Argument missing: " + _get_keys() + " (" + help + ")";
}
}
[[nodiscard]] std::string info() const {
const std::string allowed_entries = datap->get_allowed_entries();
const std::string default_value = default_str_.has_value() ? "default: " + *default_str_ : "required";
const std::string implicit_value = implicit_value_.has_value() ? "implicit: \"" + *implicit_value_ + "\", ": "";
const std::string allowed_value = !allowed_entries.empty()? "allowed: <" + allowed_entries.substr(0, allowed_entries.size()-2) + ">, ": "";
return " [" + allowed_value + implicit_value + default_value + "]";
}
friend class Args;
};
struct SubcommandEntry {
std::shared_ptr<Args> subargs;
std::string subcommand_name;
explicit SubcommandEntry(std::string subcommand_name) : subcommand_name(std::move(subcommand_name)) {}
template<typename T> operator T &() {
static_assert(std::is_base_of_v<Args, T>, "Subcommand type must be a derivative of argparse::Args");
std::shared_ptr<T> res = std::make_shared<T>();
res->program_name = subcommand_name;
subargs = res;
return *(T*)(subargs.get());
}
// Force an ambiguous error when not using a reference.
template <typename T> operator T() {} // When you get here because you received an error, make sure all parameters of argparse are references (e.g. with `&`)
};
class Args {
private:
size_t _arg_idx = 0;
std::vector<std::string> params;
std::vector<std::shared_ptr<Entry>> all_entries;
std::map<std::string, std::shared_ptr<Entry>> kwarg_entries;
std::vector<std::shared_ptr<Entry>> arg_entries;
std::map<std::string, std::shared_ptr<SubcommandEntry>> subcommand_entries;
bool has_options() {
return std::find_if(all_entries.begin(), all_entries.end(), [](auto e) { return e->type != Entry::ARG; }) != all_entries.end();
};
public:
std::string program_name;
bool is_valid = false;
virtual ~Args() = default;
/* Add a positional argument, the order in which it is defined equals the order in which they are being read.
* help : Description of the variable
*
* Returns a reference to the Entry, which will collapse into the requested type in `Entry::operator T()`
*/
Entry &arg(const std::string &help) {
return arg("arg_" + std::to_string(_arg_idx), help);
}
/* Add a *named* positional argument, the order in which it is defined equals the order in which they are being read.
* key : The name of the argument, otherwise arg_<position> will be used
* help : Description of the variable
*
* Returns a reference to the Entry, which will collapse into the requested type in `Entry::operator T()`
*/
Entry &arg(const std::string& key, const std::string &help) {
std::shared_ptr<Entry> entry = std::make_shared<Entry>(Entry::ARG, key, help);
// Increasing _arg_idx, so that arg2 will be arg_2, irregardless of whether it is preceded by other positional arguments
_arg_idx++;
arg_entries.emplace_back(entry);
all_entries.emplace_back(entry);
return *entry;
}
/* Add a Key-Worded argument that takes a variable.
* key : A comma-separated string, e.g. "k,key", which denotes the short (-k) and long(--key) keys_
* help : Description of the variable
* implicit_value : Implicit values are used when no value is provided.
*
* Returns a reference to the Entry, which will collapse into the requested type in `Entry::operator T()`
*/
Entry &kwarg(const std::string &key, const std::string &help, const std::optional<std::string>& implicit_value=std::nullopt) {
std::shared_ptr<Entry> entry = std::make_shared<Entry>(Entry::KWARG, key, help, implicit_value);
all_entries.emplace_back(entry);
for (const std::string &k : entry->keys_) {
kwarg_entries[k] = entry;
}
return *entry;
}
/* Add a flag which will be false by default.
* key : A comma-separated string, e.g. "k,key", which denotes the short (-k) and long(--key) keys_
* help : Description of the variable
*
* Returns reference to Entry like kwarg
*/
Entry &flag(const std::string &key, const std::string &help) {
return kwarg(key, help, "true").set_default<bool>(false);
}
/* Add a a subcommand
* command : name of the subcommand, e.g. 'commit', if you wish to implement a function like 'git commit'
*
* Returns a reference to the Entry, which will collapse into the requested type in `Entry::operator T()`
* Expected type *Must* be an std::shared_ptr of derivative of the argparse::Args class
*/
SubcommandEntry &subcommand(const std::string &command) {
std::shared_ptr<SubcommandEntry> entry = std::make_shared<SubcommandEntry>(command);
subcommand_entries[command] = entry;
return *entry;
}
virtual void welcome() {} // Allow to overwrite the `welcome` function to add a welcome-message to the help output
virtual void help() {
welcome();
cout << "Usage: " << program_name << " ";
for (const auto &entry : arg_entries)
cout << entry->keys_[0] << ' ';
if (has_options()) cout << " [options...]";
if (!subcommand_entries.empty()) {
cout << " [SUBCOMMAND: ";
for (const auto &[subcommand, subentry]: subcommand_entries) {
cout << subcommand << ", ";
}
cout << "]";
}
cout << endl;
for (const auto &entry : arg_entries) {
cout << setw(17) << entry->keys_[0] << " : " << entry->help << entry->info() << endl;
}
if (has_options()) cout << endl << "Options:" << endl;
for (const auto &entry : all_entries) {
if (entry->type != Entry::ARG) {
cout << setw(17) << entry->_get_keys() << " : " << entry->help << entry->info() << endl;
}
}
for (const auto &[subcommand, subentry] : subcommand_entries) {
cout << endl << endl << bold("Subcommand: ") << bold(subcommand) << endl;
subentry->subargs->help();
}
}
void validate(const bool &raise_on_error) {
for (const auto &entry : all_entries) {
if (!entry->error.empty()) {
if (raise_on_error) {
throw std::runtime_error(entry->error);
} else {
std::cerr << entry->error << std::endl;
exit(-1);
}
}
}
}
/* parse all parameters and also check for the help_flag which was set in this constructor
* Upon error, it will print the error and exit immediately if validation_action is ValidationAction::EXIT_ON_ERROR
*/
void parse(int argc, const char* const *argv, const bool &raise_on_error) {
auto parse_subcommands = [&]() -> int {
for (int i = 1; i < argc; i++) {
for (auto &[subcommand, subentry] : subcommand_entries) {
if (subcommand == argv[i]) {
subentry->subargs->parse(argc - i, argv + i, raise_on_error);
return i;
}
}
}
return argc;
};
argc = parse_subcommands(); // argc_ is the number of arguments that should be parsed after the subcommand has finished parsing
program_name = std::filesystem::path(argv[0]).stem().string();
params = std::vector<std::string>(argv + 1, argv + argc);
bool& _help = flag("?,help", "print help");
auto is_value = [&](const size_t &i) -> bool {
return params.size() > i && (params[i][0] != '-' || (params[i].size() > 1 && std::isdigit(params[i][1]))); // check for number to not accidentally mark negative numbers as non-parameter
};
auto parse_param = [&](size_t &i, const std::string &key, const bool is_short, const std::optional<std::string> &equal_value=std::nullopt) {
auto itt = kwarg_entries.find(key);
if (itt != kwarg_entries.end()) {
auto &entry = itt->second;
if (equal_value.has_value()) {
entry->_convert(equal_value.value());
} else if (entry->implicit_value_.has_value()) {
entry->_convert(*entry->implicit_value_);
} else if (!is_short) { // short values are not allowed to look ahead for the next parameter
if (is_value(i + 1)) {
std::string value = params[++i];
if (entry->_is_multi_argument) {
while (is_value(i + 1))
value += "," + params[++i];
}
entry->_convert(value);
} else if (entry->_is_multi_argument) {
entry->_convert(""); // for multiargument parameters, return an empty vector when not passing any more values
} else {
entry->error = "No value provided for: " + key;
}
} else {
entry->error = "No value provided for: " + key;
}
} else {
cerr << "unrecognised commandline argument: " << key << endl;
}
};
auto add_param = [&](size_t &i, const size_t &start) {
size_t eq_idx = params[i].find('='); // check if value was passed using the '=' sign
if (eq_idx != std::string::npos) { // key/value from = notation
std::string key = params[i].substr(start, eq_idx - start);
std::string value = params[i].substr(eq_idx + 1);
parse_param(i, key, false, value);
} else {
std::string key = std::string(params[i].substr(start));
parse_param(i, key, false);
}
};
std::vector<std::string> arguments_flat;
for (size_t i = 0; i < params.size(); i++) {
if (!is_value(i)) {
if (params[i].size() > 1 && params[i][1] == '-') { // long --
add_param(i, 2);
} else { // short -
const size_t j_end = std::min(params[i].size(), params[i].find('=')) - 1;
for (size_t j = 1; j < j_end; j++) { // add possible other flags
const std::string key = std::string(1, params[i][j]);
parse_param(i, key, true);
}
add_param(i, j_end);
}
} else {
arguments_flat.emplace_back(params[i]);
}
}
// Parse all the positional arguments, making sure multi_argument positional arguments are processed last to enable arguments afterwards
size_t arg_i = 0;
for (; arg_i < arg_entries.size() && !arg_entries[arg_i]->_is_multi_argument; arg_i++) { // iterate over positional arguments until a multi-argument is found
if (arg_i < arguments_flat.size())
arg_entries[arg_i]->_convert(arguments_flat[arg_i]);
}
size_t arg_j = 1;
for (size_t j_end = arg_entries.size() - arg_i; arg_j <= j_end; arg_j++) { // iterate from back to front, to ensure non-multi-arguments in the front and back are given preference
size_t flat_idx = arguments_flat.size() - arg_j;
if (flat_idx < arguments_flat.size() && flat_idx >= arg_i) {
if (arg_entries[arg_entries.size() - arg_j]->_is_multi_argument) {
std::stringstream s; // Combine multiple arguments into 1 comma-separated string for parsing
copy(&arguments_flat[arg_i],&arguments_flat[flat_idx] + 1, std::ostream_iterator<std::string>(s,","));
std::string value = s.str();
value.back() = '\0'; // remove trailing ','
arg_entries[arg_i]->_convert(value);
} else {
arg_entries[arg_entries.size() - arg_j]->_convert(arguments_flat[flat_idx]);
}
}
}
// try to apply default values for arguments which have not been set
for (const auto &entry : all_entries) {
if (!entry->value_.has_value()) {
entry->_apply_default();
}
}
if (_help) {
help();
exit(0);
}
validate(raise_on_error);
is_valid = true;
}
void print() const {
for (const auto &entry : all_entries) {
std::string snip = entry->type == Entry::ARG ? "(" + (entry->help.size() > 10 ? entry->help.substr(0, 7) + "..." : entry->help) + ")" : "";
cout << setw(21) << entry->_get_keys() + snip << " : " << (entry->is_set_by_user? bold(entry->value_.value_or("null")) : entry->value_.value_or("null")) << endl;
}
for (const auto &[subcommand, subentry] : subcommand_entries) {
if (subentry->subargs->is_valid) {
cout << endl << "--- Subcommand: " << subcommand << endl;
subentry->subargs->print();
}
}
}
virtual int run() {return 0;} // For automatically running subcommands
int run_subcommands() {
for (const auto &[subcommand, subentry] : subcommand_entries) {
if (subentry->subargs->is_valid) {
return subentry->subargs->run();
}
}
std::cerr << "No subcommand provided" << std::endl;
help();
return -1;
}
};
template <typename T> T parse(int argc, const char* const *argv, const bool &raise_on_error=false) {
T args = T();
args.parse(argc, argv, raise_on_error);
return args;
}
}

43
makefile Normal file
View File

@@ -0,0 +1,43 @@
all: clean dirs build_all
dirs:
@mkdir -p build/
clean:
@rm -rf build/
build_unix:
@echo "Building for unix..."
g++ -o build/owo src/main.cpp -I include
build_win:
@echo "Building for windows..."
x86_64-w64-mingw32-g++ -o build/owo.exe src/main.cpp -I include
make_deb:
mkdir build/owo_deb
mkdir build/owo_deb/DEBIAN
mkdir -p build/owo_deb/usr/local/bin
cp build/owo build/owo_deb/usr/local/bin
touch build/owo_deb/DEBIAN/control
echo "Package: owo" >> build/owo_deb/DEBIAN/control
echo "Version: 1.0" >> build/owo_deb/DEBIAN/control
echo "Maintainer: Alfie King" >> build/owo_deb/DEBIAN/control
echo "Architecture: amd64" >> build/owo_deb/DEBIAN/control
echo "Description: owoify text" >> build/owo_deb/DEBIAN/control
dpkg-deb --build build/owo_deb
mv build/owo_deb.deb build/owo.deb
rm -rf build/owo_deb
make_arch:
mkdir build/owo_arch
cp PKGBUILD build/owo_arch
makepkg -D build/owo_arch
cp build/owo_arch/owo-2.0-1-x86_64.pkg.tar.zst build/owo.pkg.tar.zst
rm -rf build/owo_arch
build_all: build_unix build_win make_deb make_arch
dev: clean dirs build_unix
.PHONY: all dirs clean build_all build_unix build_win

206
owo.cpp
View File

@@ -1,206 +0,0 @@
#include <iostream>
#include <regex>
#include <string>
#include <fstream>
#include "clip-lib/clip.h"
#include <random>
using namespace std;
string replacements[6][2] = {
{"(?:r|l)", "w"},
{"(?:R|L)", "W"},
{"n([aeiou])", "ny$1"},
{"N([aeiou])", "Ny$1"},
{"N([AEIOU])", "Ny$1"},
{"ove", "uv"}
};
string random_ending_replacents[11] = {
"uwu",
"owo",
"OwO",
"UwU",
"uwu",
"OwO",
">w<",
"Owo",
"X3",
":3",
">:3",
};
string full_word_replacents[20][2] = {
{"hello", "h-hello there"},
{"goodbye", "bai"},
{"bye", "bai"},
{"please", "p-pwease"},
{"thanks", "t-thank u"},
{"thank you", "t-thank u"},
{"you", "u"},
{"your", "ur"},
{"you're", "ur"},
{"you are", "ur"},
{"are", "r"},
{"the", "da"},
{"this", "dis"},
{"that", "dat"},
{"there", "dere"},
{"they", "dey"},
{"their", "dere"},
{"them", "dem"},
{"those", "dose"},
{"these", "deez"},
};
string random_ending(string input) {
string output = input;
for (int i = 0; i < output.length(); i++) {
if (output[i] == '.' || output[i] == '!' || output[i] == '?') {
output.insert(i, " " + random_ending_replacents[rand() % 11]);
i += random_ending_replacents[rand() % 11].length() + 1;
}
}
if (output[output.length()-1] != '.' && output[output.length()-1] != '!' && output[output.length()-1] != '?') {
output += " " + random_ending_replacents[rand() % 11];
}
return output;
}
string full_word(string input) {
string output = input;
for (int i = 0; i < 20; i++) {
output = regex_replace(output, regex(full_word_replacents[i][0]), full_word_replacents[i][1]);
}
return output;
}
string owo(string input, bool no_full_word = false, bool no_random_ending = false) {
string output = input;
if (!no_full_word){
output = full_word(output);
}
for (int i = 0; i < 6; i++) {
output = regex_replace(output, regex(replacements[i][0]), replacements[i][1]);
}
if (!no_random_ending) {
output = random_ending(output);
}
return output;
}
int help() {
cout << "Usage: owo [-p] [-h] [-f file] [-o file] [text]" << endl;
cout << " owo [text]" << endl;
cout << " owo -f [file] -o [file]" << endl;
cout << " echo [text] | owo -p" << endl << endl;
cout << "Options:" << endl;
cout << " -p Read from stdin" << endl;
cout << " -h Show this help message" << endl;
cout << " -f Read from file" << endl;
cout << " -o Write to file" << endl;
cout << " -c Copy to clipboard" << endl;
cout << " -ne Stops extra stuff from being added to the end of sentences" << endl;
cout << " -nf Stops full word replacements" << endl;
cout << " text Text to convert" << endl;
return 0;
}
int main(int argc, char *argv[]) {
srand(time(NULL));
string input, inputfile, outputfile;
bool _stdin = false, clipboard = false, no_full_word = false, no_random_ending = false;
if (argc < 2) {
cout << owo("No text provided") << endl << endl;
help();
return 1;
}
for (int i = 1; i < argc; i++) {
if (string(argv[i]) == "-f") {
inputfile = string(argv[i+1]);
i++;
} else if (string(argv[i]) == "-o") {
outputfile = string(argv[i+1]);
i++;
} else if (string(argv[i]) == "-p") {
_stdin = true;
} else if (string(argv[i]) == "-h") {
return help();
} else if (i == argc-1) {
input = string(argv[i]);
} else if (string(argv[i]) == "-c") {
clipboard = true;
} else if (string(argv[i]) == "-ne") {
no_random_ending = true;
} else if (string(argv[i]) == "-nf") {
no_full_word = true;
} else {
cout << owo("Invalid argument: " + string(argv[i])) << endl << endl;
help();
return 1;
}
}
if (_stdin && inputfile != "" || _stdin && input != "" || inputfile != "" && input != "") {
cout << owo("Cannot use multiple input methods at once") << endl << endl;
help();
return 1;
}
ostream *output;
if (outputfile != "") {
output = new ofstream(outputfile);
if (!output->good()) {
cout << owo("Failed to open output file") << endl;
return 1;
}
}
if (_stdin) {
while (getline(cin, input)) {
if (outputfile != "") {
*output << owo(input, no_full_word, no_random_ending) << endl;
} else {
cout << owo(input, no_full_word, no_random_ending) << endl;
}
}
} else if (inputfile != "") {
ifstream inputstream(inputfile);
if (!inputstream.good()) {
cout << owo("Failed to open input file") << endl;
if (outputfile != "") {
((ofstream*)output)->close();
}
return 1;
}
while (getline(inputstream, input)) {
if (outputfile != "") {
*output << owo(input, no_full_word, no_random_ending) << endl;
} else {
cout << owo(input, no_full_word, no_random_ending) << endl;
}
}
inputstream.close();
} else if (input != "") {
if (outputfile != "") {
*output << owo(input, no_full_word, no_random_ending) << endl;
} else if (clipboard) {
clip::set_text(owo(input, no_full_word, no_random_ending));
cout << owo("Copied to clipboard") << endl;
} else {
cout << owo(input, no_full_word, no_random_ending) << endl;
}
}
if (outputfile != "") {
((ofstream*)output)->close();
}
return 0;
}

116
src/main.cpp Normal file
View File

@@ -0,0 +1,116 @@
#include <iostream>
#include <regex>
#include <string>
#include <random>
#include <argparse.hpp>
using namespace std;
string replacements[6][2] = {
{"(?:r|l)", "w"},
{"(?:R|L)", "W"},
{"n([aeiou])", "ny$1"},
{"N([aeiou])", "Ny$1"},
{"N([AEIOU])", "Ny$1"},
{"ove", "uv"}
};
string random_ending_replacents[11] = {
"uwu",
"owo",
"OwO",
"UwU",
"uwu",
"OwO",
">w<",
"Owo",
"X3",
":3",
">:3",
};
string full_word_replacents[20][2] = {
{"hello", "h-hello there"},
{"goodbye", "bai"},
{"bye", "bai"},
{"please", "p-pwease"},
{"thanks", "t-thank u"},
{"thank you", "t-thank u"},
{"you", "u"},
{"your", "ur"},
{"you're", "ur"},
{"you are", "ur"},
{"are", "r"},
{"the", "da"},
{"this", "dis"},
{"that", "dat"},
{"there", "dere"},
{"they", "dey"},
{"their", "dere"},
{"them", "dem"},
{"those", "dose"},
{"these", "deez"},
};
string random_ending(string input) {
string output = input;
for (int i = 0; i < output.length(); i++) {
if (output[i] == '.' || output[i] == '!' || output[i] == '?') {
output.insert(i, " " + random_ending_replacents[rand() % 11]);
i += random_ending_replacents[rand() % 11].length() + 1;
}
}
if (output[output.length()-1] != '.' && output[output.length()-1] != '!' && output[output.length()-1] != '?') {
output += " " + random_ending_replacents[rand() % 11];
}
return output;
}
string full_word(string input) {
string output = input;
for (int i = 0; i < 20; i++) {
output = regex_replace(output, regex(full_word_replacents[i][0]), full_word_replacents[i][1]);
}
return output;
}
string owo(string input, bool no_full_word = false, bool no_random_ending = false) {
string output = input;
if (!no_full_word){
output = full_word(output);
}
for (int i = 0; i < 6; i++) {
output = regex_replace(output, regex(replacements[i][0]), replacements[i][1]);
}
if (!no_random_ending) {
output = random_ending(output);
}
return output;
}
struct Args : public argparse::Args {
bool &no_full_word = flag("nfw", "Don't replace full words");
bool &no_random_ending = flag("nre", "Don't add random ending");
bool &_stdin = flag("p", "Read from stdin");
string &input = arg("input", "Input string").set_default("");
};
int main(int argc, char *argv[]) {
auto args = argparse::parse<Args>(argc, argv);
srand(time(nullptr));
if (argc == 1) {
args.help();
return 0;
}
if (args._stdin) {
string line;
while (getline(cin, line)) {
cout << owo(line, args.no_full_word, args.no_random_ending) << endl;
}
} else {
cout << owo(args.input, args.no_full_word, args.no_random_ending) << endl;
}
return 0;
}