Skip to content

Commit 99a0118

Browse files
Destroy666xWarmUpTill
authored andcommitted
Add clipboard text/image copy actions
1 parent bb7ee17 commit 99a0118

6 files changed

Lines changed: 377 additions & 3 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ target_sources(
9393
${LIB_NAME}
9494
PRIVATE src/macro-core/macro-action-audio.cpp
9595
src/macro-core/macro-action-audio.hpp
96+
src/macro-core/macro-action-clipboard.cpp
97+
src/macro-core/macro-action-clipboard.hpp
9698
src/macro-core/macro-action-edit.cpp
9799
src/macro-core/macro-action-edit.hpp
98100
src/macro-core/macro-action-file.cpp

data/locale/en-US.ini

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,12 @@ AdvSceneSwitcher.action.twitch.announcement.blue="Blue"
874874
AdvSceneSwitcher.action.twitch.announcement.green="Green"
875875
AdvSceneSwitcher.action.twitch.announcement.orange="Orange"
876876
AdvSceneSwitcher.action.twitch.announcement.purple="Purple"
877+
AdvSceneSwitcher.action.clipboard="Clipboard"
878+
AdvSceneSwitcher.action.clipboard.type.copy.text="Copy text"
879+
AdvSceneSwitcher.action.clipboard.type.copy.image="Copy image"
880+
AdvSceneSwitcher.action.clipboard.copy.text.text.placeholder="Enter text"
881+
AdvSceneSwitcher.action.clipboard.copy.image.url.placeholder="Enter direct image URL"
882+
AdvSceneSwitcher.action.clipboard.copy.image.url.tooltip="Currently supported formats: PNG, JPG/JPEG, BMP, GIF."
877883

878884
; Hotkey
879885
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
@@ -1442,6 +1448,11 @@ AdvSceneSwitcher.tempVar.audio.sync_offset="Source audio sync offset"
14421448
AdvSceneSwitcher.tempVar.audio.monitor="Source audio monitor type"
14431449
AdvSceneSwitcher.tempVar.audio.balance="Source audio balance"
14441450

1451+
AdvSceneSwitcher.tempVar.clipboard.mimeType.primary="Primary clipboard item MIME type"
1452+
AdvSceneSwitcher.tempVar.clipboard.mimeType.primary.description="Highest priority MIME type of the current item stored in clipboard, if available."
1453+
AdvSceneSwitcher.tempVar.clipboard.mimeType.all="All clipboard item MIME types"
1454+
AdvSceneSwitcher.tempVar.clipboard.mimeType.all.description="All MIME types of the current item stored in clipboard separated with space, if available."
1455+
14451456
AdvSceneSwitcher.tempVar.scene.current="Current scene"
14461457
AdvSceneSwitcher.tempVar.scene.previous="Previous scene"
14471458
AdvSceneSwitcher.tempVar.scene.preview="Preview scene"
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
#include "macro-action-clipboard.hpp"
2+
#include "curl-helper.hpp"
3+
#include "switcher-data.hpp"
4+
5+
#include <QApplication>
6+
#include <QClipboard>
7+
#include <QImage>
8+
#include <QMimeData>
9+
10+
namespace advss {
11+
12+
const std::string MacroActionClipboard::id = "clipboard";
13+
14+
bool MacroActionClipboard::_registered = MacroActionFactory::Register(
15+
MacroActionClipboard::id,
16+
{MacroActionClipboard::Create, MacroActionClipboardEdit::Create,
17+
"AdvSceneSwitcher.action.clipboard"});
18+
19+
const static std::map<MacroActionClipboard::Action, std::string> actionTypes = {
20+
{MacroActionClipboard::Action::COPY_TEXT,
21+
"AdvSceneSwitcher.action.clipboard.type.copy.text"},
22+
{MacroActionClipboard::Action::COPY_IMAGE,
23+
"AdvSceneSwitcher.action.clipboard.type.copy.image"},
24+
};
25+
26+
static size_t writeCallback(void *ptr, size_t size, size_t nmemb,
27+
QByteArray *buffer)
28+
{
29+
buffer->append((char *)ptr, nmemb);
30+
return size * nmemb;
31+
}
32+
33+
static std::optional<QImage> getImageFromUrl(const char *url)
34+
{
35+
QByteArray response;
36+
37+
switcher->curl.SetOpt(CURLOPT_URL, url);
38+
switcher->curl.SetOpt(CURLOPT_HTTPGET, 1L);
39+
switcher->curl.SetOpt(CURLOPT_TIMEOUT_MS, 30000);
40+
switcher->curl.SetOpt(CURLOPT_WRITEFUNCTION, writeCallback);
41+
switcher->curl.SetOpt(CURLOPT_WRITEDATA, &response);
42+
auto code = switcher->curl.Perform();
43+
44+
if (code != CURLE_OK) {
45+
blog(LOG_WARNING,
46+
"Retrieving image failed in %s with error: %s", __func__,
47+
switcher->curl.GetError(code));
48+
return {};
49+
}
50+
51+
return QImage::fromData(response);
52+
}
53+
54+
static void setMimeTypeParams(ClipboardQueueParams *params,
55+
QClipboard *clipboard)
56+
{
57+
auto mimeData = clipboard->mimeData();
58+
if (!mimeData) {
59+
return;
60+
}
61+
auto mimeTypeList = mimeData->formats();
62+
if (mimeTypeList.empty()) {
63+
return;
64+
}
65+
params->mimeTypePrimary = mimeTypeList.first().toStdString();
66+
params->mimeTypeAll = mimeTypeList.join(" ").toStdString();
67+
}
68+
69+
static void copyText(void *param)
70+
{
71+
auto params = static_cast<ClipboardTextQueueParams *>(param);
72+
QClipboard *clipboard = QApplication::clipboard();
73+
74+
clipboard->setText(params->text.c_str());
75+
76+
setMimeTypeParams(params, clipboard);
77+
}
78+
79+
static void copyImageFromUrl(void *param)
80+
{
81+
auto params = static_cast<ClipboardImageQueueParams *>(param);
82+
auto url = params->url.c_str();
83+
auto image = getImageFromUrl(url);
84+
85+
if (!image || (*image).isNull()) {
86+
blog(LOG_WARNING, "Failed to convert %s URL to image!", url);
87+
return;
88+
}
89+
90+
QClipboard *clipboard = QApplication::clipboard();
91+
clipboard->setImage(*image);
92+
93+
setMimeTypeParams(params, clipboard);
94+
}
95+
96+
bool MacroActionClipboard::PerformAction()
97+
{
98+
switch (_action) {
99+
case Action::COPY_TEXT: {
100+
ClipboardTextQueueParams params{"", "", _text};
101+
obs_queue_task(OBS_TASK_UI, copyText, &params, true);
102+
SetTempVarValues(params);
103+
break;
104+
}
105+
case Action::COPY_IMAGE: {
106+
ClipboardImageQueueParams params{"", "", _url};
107+
obs_queue_task(OBS_TASK_UI, copyImageFromUrl, &params, true);
108+
SetTempVarValues(params);
109+
break;
110+
}
111+
default:
112+
break;
113+
}
114+
115+
return true;
116+
}
117+
118+
void MacroActionClipboard::SetupTempVars()
119+
{
120+
MacroAction::SetupTempVars();
121+
122+
AddTempvar(
123+
"mimeType.primary",
124+
obs_module_text(
125+
"AdvSceneSwitcher.tempVar.clipboard.mimeType.primary"),
126+
obs_module_text(
127+
"AdvSceneSwitcher.tempVar.clipboard.mimeType.primary.description"));
128+
AddTempvar(
129+
"mimeType.all",
130+
obs_module_text(
131+
"AdvSceneSwitcher.tempVar.clipboard.mimeType.all"),
132+
obs_module_text(
133+
"AdvSceneSwitcher.tempVar.clipboard.mimeType.all.description"));
134+
}
135+
136+
void MacroActionClipboard::SetTempVarValues(const ClipboardQueueParams &params)
137+
{
138+
SetTempVarValue("mimeType.primary", params.mimeTypePrimary);
139+
SetTempVarValue("mimeType.all", params.mimeTypeAll);
140+
}
141+
142+
bool MacroActionClipboard::Save(obs_data_t *obj) const
143+
{
144+
MacroAction::Save(obj);
145+
obs_data_set_int(obj, "action", static_cast<int>(_action));
146+
_text.Save(obj, "text");
147+
_url.Save(obj, "url");
148+
149+
return true;
150+
}
151+
152+
bool MacroActionClipboard::Load(obs_data_t *obj)
153+
{
154+
MacroAction::Load(obj);
155+
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
156+
_text.Load(obj, "text");
157+
_url.Load(obj, "url");
158+
159+
return true;
160+
}
161+
162+
static inline void populateActionSelection(QComboBox *list)
163+
{
164+
for (const auto &[action, name] : actionTypes) {
165+
list->addItem(obs_module_text(name.c_str()),
166+
static_cast<int>(action));
167+
}
168+
}
169+
170+
MacroActionClipboardEdit::MacroActionClipboardEdit(
171+
QWidget *parent, std::shared_ptr<MacroActionClipboard> entryData)
172+
: QWidget(parent),
173+
_actions(new FilterComboBox(this)),
174+
_text(new VariableLineEdit(this)),
175+
_url(new VariableLineEdit(this))
176+
{
177+
populateActionSelection(_actions);
178+
_url->setToolTip(obs_module_text(
179+
"AdvSceneSwitcher.action.clipboard.copy.image.url.tooltip"));
180+
181+
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
182+
SLOT(ActionChanged(int)));
183+
QWidget::connect(_text, SIGNAL(editingFinished()), this,
184+
SLOT(TextChanged()));
185+
QWidget::connect(_url, SIGNAL(editingFinished()), this,
186+
SLOT(UrlChanged()));
187+
188+
auto mainLayout = new QHBoxLayout();
189+
mainLayout->addWidget(_actions);
190+
mainLayout->addWidget(_text);
191+
mainLayout->addWidget(_url);
192+
setLayout(mainLayout);
193+
194+
_entryData = entryData;
195+
UpdateEntryData();
196+
_loading = false;
197+
}
198+
199+
void MacroActionClipboardEdit::ActionChanged(int index)
200+
{
201+
if (_loading || !_entryData) {
202+
return;
203+
}
204+
205+
auto lock = LockContext();
206+
_entryData->_action = static_cast<MacroActionClipboard::Action>(
207+
_actions->itemData(index).toInt());
208+
209+
SetWidgetVisibility();
210+
}
211+
212+
void MacroActionClipboardEdit::TextChanged()
213+
{
214+
if (_loading || !_entryData) {
215+
return;
216+
}
217+
218+
auto lock = LockContext();
219+
_entryData->_text = _text->text().toStdString();
220+
}
221+
222+
void MacroActionClipboardEdit::UrlChanged()
223+
{
224+
if (_loading || !_entryData) {
225+
return;
226+
}
227+
228+
auto lock = LockContext();
229+
_entryData->_url = _url->text().toStdString();
230+
}
231+
232+
void MacroActionClipboardEdit::SetWidgetVisibility()
233+
{
234+
_text->setVisible(_entryData->_action ==
235+
MacroActionClipboard::Action::COPY_TEXT);
236+
_url->setVisible(_entryData->_action ==
237+
MacroActionClipboard::Action::COPY_IMAGE);
238+
239+
adjustSize();
240+
updateGeometry();
241+
}
242+
243+
void MacroActionClipboardEdit::UpdateEntryData()
244+
{
245+
if (!_entryData) {
246+
return;
247+
}
248+
249+
_actions->setCurrentIndex(
250+
_actions->findData(static_cast<int>(_entryData->_action)));
251+
_text->setText(_entryData->_text);
252+
_url->setText(_entryData->_url);
253+
254+
SetWidgetVisibility();
255+
}
256+
257+
} // namespace advss
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#pragma once
2+
#include "macro-action-edit.hpp"
3+
#include "filter-combo-box.hpp"
4+
5+
#include <variable-line-edit.hpp>
6+
7+
namespace advss {
8+
9+
struct ClipboardQueueParams {
10+
std::string mimeTypePrimary;
11+
std::string mimeTypeAll;
12+
};
13+
14+
struct ClipboardTextQueueParams : ClipboardQueueParams {
15+
StringVariable text;
16+
};
17+
18+
struct ClipboardImageQueueParams : ClipboardQueueParams {
19+
StringVariable url;
20+
};
21+
22+
class MacroActionClipboard : public MacroAction {
23+
public:
24+
MacroActionClipboard(Macro *m) : MacroAction(m) {}
25+
static std::shared_ptr<MacroAction> Create(Macro *m)
26+
{
27+
return std::make_shared<MacroActionClipboard>(m);
28+
}
29+
std::string GetId() const { return id; };
30+
31+
bool PerformAction();
32+
bool Save(obs_data_t *obj) const;
33+
bool Load(obs_data_t *obj);
34+
35+
enum class Action {
36+
COPY_TEXT,
37+
COPY_IMAGE,
38+
};
39+
40+
Action _action = Action::COPY_TEXT;
41+
42+
StringVariable _text = obs_module_text(
43+
"AdvSceneSwitcher.action.clipboard.copy.text.text.placeholder");
44+
StringVariable _url = obs_module_text(
45+
"AdvSceneSwitcher.action.clipboard.copy.image.url.placeholder");
46+
47+
private:
48+
void SetupTempVars();
49+
void SetTempVarValues(const ClipboardQueueParams &params);
50+
51+
static bool _registered;
52+
static const std::string id;
53+
};
54+
55+
class MacroActionClipboardEdit : public QWidget {
56+
Q_OBJECT
57+
58+
public:
59+
MacroActionClipboardEdit(
60+
QWidget *parent,
61+
std::shared_ptr<MacroActionClipboard> entryData = nullptr);
62+
static QWidget *Create(QWidget *parent,
63+
std::shared_ptr<MacroAction> action)
64+
{
65+
return new MacroActionClipboardEdit(
66+
parent, std::dynamic_pointer_cast<MacroActionClipboard>(
67+
action));
68+
}
69+
void UpdateEntryData();
70+
71+
protected:
72+
std::shared_ptr<MacroActionClipboard> _entryData;
73+
74+
private slots:
75+
void ActionChanged(int index);
76+
void TextChanged();
77+
void UrlChanged();
78+
79+
private:
80+
void SetWidgetVisibility();
81+
82+
bool _loading = true;
83+
84+
FilterComboBox *_actions;
85+
VariableLineEdit *_text;
86+
VariableLineEdit *_url;
87+
};
88+
89+
} // namespace advss

0 commit comments

Comments
 (0)