Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ target_sources(${PROJECT_NAME}
src/external/fontaudio/data/FontAudioIcons.h
)

if(APPLE)
target_sources(${PROJECT_NAME} PRIVATE src/utils/copy/CopyMacOS.mm)
elseif(WIN32)
target_sources(${PROJECT_NAME} PRIVATE src/utils/copy/CopyWindows.cpp)
elseif(LINUX)
target_sources(${PROJECT_NAME} PRIVATE src/utils/copy/CopyLinux.cpp)
endif()

# `target_compile_definitions` adds some preprocessor definitions to our target. In a Projucer
# project, these might be passed in the 'Preprocessor Definitions' field. JUCE modules also make use
# of compile definitions to switch certain features on/off, so if there's a particular feature you
Expand Down Expand Up @@ -196,6 +204,11 @@ target_link_libraries(${PROJECT_NAME}
juce::juce_recommended_warning_flags
)

if(LINUX)
find_package(X11 REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE X11::X11)
endif()

# C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.36.32532\x64\Microsoft.VC143.CRT\msvcp140.dll
# C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.36.32532\x64\Microsoft.VC143.CRT\vcruntime140_1.dll
# C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.36.32532\x64\Microsoft.VC143.CRT\vcruntime140.dll
Expand Down
53 changes: 50 additions & 3 deletions src/media/MediaDisplayComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#include "AudioDisplayComponent.h"
#include "MidiDisplayComponent.h"

#include "../utils/Interface.h"

MediaDisplayComponent::MediaDisplayComponent() : MediaDisplayComponent("Media Track") {}

MediaDisplayComponent::MediaDisplayComponent(String name, bool req, bool fromDAW, DisplayMode mode)
Expand Down Expand Up @@ -99,6 +101,22 @@ void MediaDisplayComponent::initializeButtons()
saveFileButton.addMode(saveFileButtonInactiveInfo);
headerComponent.addAndMakeVisible(saveFileButton);

// Mode when an copyable file is loaded
copyFileButtonActiveInfo = MultiButton::Mode { "Copy-Active",
"Click to copy the media file.",
[this] { copyFileCallback(); },
MultiButton::DrawingMode::IconOnly,
Colours::lightblue,
fontawesome::Copy };
// Mode when there is nothing to copy
copyFileButtonInactiveInfo =
MultiButton::Mode { "Copy-Inactive", "Nothing to copy.",
[this] {}, MultiButton::DrawingMode::IconOnly,
Colours::lightgrey, fontawesome::Copy };
copyFileButton.addMode(copyFileButtonActiveInfo);
copyFileButton.addMode(copyFileButtonInactiveInfo);
headerComponent.addAndMakeVisible(copyFileButton);

resetButtonState();
}

Expand Down Expand Up @@ -235,16 +253,18 @@ void MediaDisplayComponent::resized()

// Add buttons to flex with equal height
buttonsFlexBox.items.add(
FlexItem(playStopButton).withHeight(22).withWidth(22).withMargin({ 2, 0, 2, 0 }));
FlexItem(playStopButton).withHeight(25).withWidth(25).withMargin({ 2, 0, 2, 0 }));
if (isInputTrack())
{
buttonsFlexBox.items.add(
FlexItem(chooseFileButton).withHeight(22).withWidth(22).withMargin({ 2, 0, 2, 0 }));
FlexItem(chooseFileButton).withHeight(25).withWidth(25).withMargin({ 2, 0, 2, 0 }));
}
if (isOutputTrack())
{
buttonsFlexBox.items.add(
FlexItem(saveFileButton).withHeight(22).withWidth(22).withMargin({ 2, 0, 2, 0 }));
FlexItem(saveFileButton).withHeight(25).withWidth(25).withMargin({ 2, 0, 2, 0 }));
buttonsFlexBox.items.add(
FlexItem(copyFileButton).withHeight(25).withWidth(25).withMargin({ 2, 0, 2, 0 }));
}

buttonsFlexBox.performLayout(buttonsComponent.getBounds());
Expand Down Expand Up @@ -428,6 +448,7 @@ void MediaDisplayComponent::resetButtonState()
playStopButton.setMode(playButtonInactiveInfo.displayLabel);
chooseFileButton.setMode(chooseFileButtonInfo.displayLabel);
saveFileButton.setMode(saveFileButtonInactiveInfo.displayLabel);
copyFileButton.setMode(copyFileButtonInactiveInfo.displayLabel);
}

void MediaDisplayComponent::initializeDisplay(const URL& filePath)
Expand Down Expand Up @@ -460,6 +481,7 @@ void MediaDisplayComponent::updateDisplay(const URL& filePath)

playStopButton.setMode(playButtonActiveInfo.displayLabel);
saveFileButton.setMode(saveFileButtonActiveInfo.displayLabel);
copyFileButton.setMode(copyFileButtonActiveInfo.displayLabel);
}

void MediaDisplayComponent::setOriginalFilePath(URL filePath)
Expand Down Expand Up @@ -740,6 +762,31 @@ void MediaDisplayComponent::saveFileCallback()
}
}

void MediaDisplayComponent::copyFileCallback()
{
if (isFileLoaded())
{
File file = getOriginalFilePath().getLocalFile();

if (file.exists())
{
copyFileToClipboard(file);

if (statusMessage != nullptr)
{
statusMessage->setMessage("File copied to clipboard.");
}
}
else
{
if (statusMessage != nullptr)
{
statusMessage->setMessage("Failed to copy file to clipboard.");
}
}
}
}

float MediaDisplayComponent::getPixelsPerSecond()
{
if (visibleRange.getLength())
Expand Down
4 changes: 4 additions & 0 deletions src/media/MediaDisplayComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class MediaDisplayComponent : public Component,
bool isDuplicateFile(const URL& fileParth);

void saveFileCallback();
void copyFileCallback();

virtual double getTotalLengthInSecs() = 0;
virtual double getTimeAtOrigin() { return visibleRange.getStart(); }
Expand Down Expand Up @@ -247,6 +248,9 @@ class MediaDisplayComponent : public Component,
MultiButton saveFileButton;
MultiButton::Mode saveFileButtonActiveInfo;
MultiButton::Mode saveFileButtonInactiveInfo;
MultiButton copyFileButton;
MultiButton::Mode copyFileButtonActiveInfo;
MultiButton::Mode copyFileButtonInactiveInfo;

// Panel displaying overhead labels
ColorablePanel overheadPanel { overheadPanelColor };
Expand Down
4 changes: 3 additions & 1 deletion src/utils/Interface.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @file Interface.h
* @brief Simple helper functions for interface.
* @author hugofloresgarcia
* @author hugofloresgarcia, JEYuhas
*/

#pragma once
Expand All @@ -20,3 +20,5 @@ inline Colour getUIColourIfAvailable(LookAndFeel_V4::ColourScheme::UIColour uiCo

return fallback;
}

void copyFileToClipboard(const File& file);
175 changes: 175 additions & 0 deletions src/utils/copy/CopyLinux.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/**
* @file CopyLinux.cpp
* @brief Copy file path to clipboard on Linux.
* @author JEYuhas, cwitkowitz
*/

extern "C"
{
#include <X11/Xatom.h>
#include <X11/Xlib.h>
}

#include <atomic>
#include <thread>

#include "../Interface.h"

namespace
{
struct Clipboard
{
Display* display = nullptr;
Window window = 0;

Atom clipboard;
Atom targets;
Atom uriList;
Atom textPlain;
Atom utf8;
Atom gnome;

std::string dataUri;
std::string dataPlain;
std::string dataGnome;

std::atomic<bool> running { true };
};

Clipboard* clipboardState = nullptr;

void handleRequest(XEvent& e)
{
auto* req = &e.xselectionrequest;

XEvent res {};
res.xselection.type = SelectionNotify;
res.xselection.display = req->display;
res.xselection.requestor = req->requestor;
res.xselection.selection = req->selection;
res.xselection.target = req->target;
res.xselection.time = req->time;
res.xselection.property = None;

if (req->target == clipboardState->targets)
{
Atom list[] = { clipboardState->targets,
clipboardState->uriList,
clipboardState->textPlain,
clipboardState->utf8,
clipboardState->gnome };

XChangeProperty(req->display,
req->requestor,
req->property,
XA_ATOM,
32,
PropModeReplace,
(unsigned char*) list,
5);

res.xselection.property = req->property;
}
else if (req->target == clipboardState->uriList)
{
XChangeProperty(req->display,
req->requestor,
req->property,
clipboardState->uriList,
8,
PropModeReplace,
(unsigned char*) clipboardState->dataUri.c_str(),
(int) clipboardState->dataUri.size());

res.xselection.property = req->property;
}
else if (req->target == clipboardState->textPlain || req->target == clipboardState->utf8)
{
XChangeProperty(req->display,
req->requestor,
req->property,
req->target,
8,
PropModeReplace,
(unsigned char*) clipboardState->dataPlain.c_str(),
(int) clipboardState->dataPlain.size());

res.xselection.property = req->property;
}
else if (req->target == clipboardState->gnome)
{
XChangeProperty(req->display,
req->requestor,
req->property,
clipboardState->gnome,
8,
PropModeReplace,
(unsigned char*) clipboardState->dataGnome.c_str(),
(int) clipboardState->dataGnome.size());

res.xselection.property = req->property;
}

XSendEvent(req->display, req->requestor, False, 0, &res);
XFlush(req->display);
}

void runLoop()
{
while (clipboardState->running)
{
while (XPending(clipboardState->display))
{
XEvent e;
XNextEvent(clipboardState->display, &e);

if (e.type == SelectionRequest)
handleRequest(e);
}

std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
} // namespace

void copyFileToClipboard(const File& file)
{
if (! file.existsAsFile())
return;

if (clipboardState)
{
clipboardState->running = false;
delete clipboardState;
clipboardState = nullptr;
}

clipboardState = new Clipboard();

clipboardState->display = XOpenDisplay(nullptr);
if (! clipboardState->display)
return;

clipboardState->window = XCreateSimpleWindow(
clipboardState->display, DefaultRootWindow(clipboardState->display), 0, 0, 1, 1, 0, 0, 0);

clipboardState->clipboard = XInternAtom(clipboardState->display, "CLIPBOARD", False);
clipboardState->targets = XInternAtom(clipboardState->display, "TARGETS", False);
clipboardState->uriList = XInternAtom(clipboardState->display, "text/uri-list", False);
clipboardState->textPlain = XInternAtom(clipboardState->display, "text/plain", False);
clipboardState->utf8 = XInternAtom(clipboardState->display, "UTF8_STRING", False);
clipboardState->gnome =
XInternAtom(clipboardState->display, "x-special/gnome-copied-files", False);

std::string path = file.getFullPathName().toStdString();
std::string uri = "file://" + path;

clipboardState->dataUri = uri + "\r\n";
clipboardState->dataPlain = path;
clipboardState->dataGnome = "copy\n" + uri + "\n";

XSetSelectionOwner(
clipboardState->display, clipboardState->clipboard, clipboardState->window, CurrentTime);

std::thread(runLoop).detach();
}
26 changes: 26 additions & 0 deletions src/utils/copy/CopyMacOS.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @file CopyMacOS.mm
* @brief Copy file path to clipboard on MacOS.
* @author JEYuhas
*/

#import <Cocoa/Cocoa.h>

#include "../Interface.h"

void copyFileToClipboard (const juce::File& file)
{
if (! file.existsAsFile())
return;

NSPasteboard* pb = [NSPasteboard generalPasteboard];

[pb declareTypes: [NSArray arrayWithObject: NSPasteboardTypeFileURL]
owner: nil];

NSString* path = [NSString stringWithUTF8String: file.getFullPathName().toUTF8()];
NSURL* fileURL = [NSURL fileURLWithPath: path];

[pb setString: [fileURL absoluteString]
forType: NSPasteboardTypeFileURL];
}
Loading
Loading