MIDI2LR 6.1.0.0
MIDI2LR is an application that interfaces MIDI controllers with Lightroom 6+/CC Classic. It processes MIDI input into develop parameter updates and photo actions, and sends MIDI output when parameters are changed for motorized feedback (on controllers that have motorized faders). A listing of available LightRoom commands is in the Wiki. Assistance on the code and design is welcome.
Loading...
Searching...
No Matches
rsj Namespace Reference

Classes

struct  ActiveModifiers
 
struct  CFDeleter
 
class  ConcurrentQueue
 
struct  KeyData
 
struct  MidiMessage
 
struct  MidiMessageId
 
struct  SettingsStruct
 

Typedefs

template<typename T >
using CFAutoRelease = std::unique_ptr< typename std::remove_pointer< T >::type, CFDeleter< typename std::remove_pointer< T >::type > >
 

Enumerations

enum struct  CCmethod : char { kAbsolute , kTwosComplement , kBinaryOffset , kSignMagnitude }
 
enum struct  MessageType : uint8_t {
  kNoteOff = 0x8 , kNoteOn = 0x9 , kKeyPressure = 0xA , kCc = 0xB ,
  kPgmChange = 0xC , kChanPressure = 0xD , kPw = 0xE , kSystem = 0xF
}
 

Functions

std::string AppDataFilePath (const std::string &file_name)
 
std::string AppDataMac ()
 
std::string AppLogFilePath (const std::string &file_name)
 
std::string AppLogMac ()
 
constexpr auto CharToInt (const char in) noexcept
 
template<class T >
auto CharToInt (T t)=delete
 
void CheckPermission (pid_t pid)
 
void ExceptionResponse (const std::exception &e, const std::source_location &location=std::source_location::current()) noexcept
 
void ExceptionResponse (gsl::czstring id, gsl::czstring fu, const std::exception &e) noexcept
 
std::unordered_map< UniChar, KeyDataGetKeyMap ()
 
void LabelThread (gsl::czstring threadname)
 
void Log (const juce::String &info, const std::source_location &location=std::source_location::current()) noexcept
 
void Log (gsl::cwzstring info, const std::source_location &location=std::source_location::current()) noexcept
 
void Log (gsl::czstring info, const std::source_location &location=std::source_location::current()) noexcept
 
void LogAndAlertError (const juce::String &alert_text, const juce::String &error_text, const std::source_location &location=std::source_location::current()) noexcept
 
void LogAndAlertError (const juce::String &error_text, const std::source_location &location=std::source_location::current()) noexcept
 
void LogAndAlertError (gsl::czstring error_text, const std::source_location &location=std::source_location::current()) noexcept
 
const char * MessageTypeToLabel (MessageType from) noexcept
 
const char * MessageTypeToName (MessageType from) noexcept
 
constexpr bool operator== (const rsj::MidiMessage &lhs, const rsj::MidiMessage &rhs) noexcept
 
std::string ReplaceInvisibleChars (std::string_view in)
 
void SendKeyDownUp (const std::string &key, rsj::ActiveModifiers mods) noexcept
 
std::string ToLower (std::string_view in)
 
constexpr MessageType ToMessageType (std::underlying_type_t< MessageType > value)
 
void Translate (const std::string &lg)
 
void Trim (std::string_view &&value)=delete
 
void Trim (std::string_view &value) noexcept
 
void TrimL (std::string_view &&value)=delete
 
void TrimL (std::string_view &value) noexcept
 
constexpr bool ValidMessageType (std::underlying_type_t< MessageType > value) noexcept
 

Typedef Documentation

◆ CFAutoRelease

template<typename T >
using rsj::CFAutoRelease = typedef std::unique_ptr<typename std::remove_pointer<T>::type, CFDeleter<typename std::remove_pointer<T>::type> >

Enumeration Type Documentation

◆ CCmethod

enum struct rsj::CCmethod : char
strong
Enumerator
kAbsolute 
kTwosComplement 
kBinaryOffset 
kSignMagnitude 

◆ MessageType

enum struct rsj::MessageType : uint8_t
strong
Enumerator
kNoteOff 
kNoteOn 
kKeyPressure 
kCc 
kPgmChange 
kChanPressure 
kPw 
kSystem 
42 : uint8_t {
43 kNoteOff = 0x8,
44 kNoteOn = 0x9,
45 kKeyPressure = 0xA, /* Individual key pressure */
46 kCc = 0xB,
47 kPgmChange = 0xC,
48 kChanPressure = 0xD, /* max key pressure */
49 kPw = 0xE, /* pitch wheel */
50 kSystem = 0xF
51 };

Function Documentation

◆ AppDataFilePath()

std::string rsj::AppDataFilePath ( const std::string &  file_name)

◆ AppDataMac()

std::string rsj::AppDataMac ( )
29{
30 const auto result {(@"~/Library/Application Support").stringByExpandingTildeInPath};
31 return result.UTF8String;
32}

◆ AppLogFilePath()

std::string rsj::AppLogFilePath ( const std::string &  file_name)

◆ AppLogMac()

std::string rsj::AppLogMac ( )
35{
36 const auto result {(@"~/Library/Logs").stringByExpandingTildeInPath};
37 return result.UTF8String;
38}

◆ CharToInt() [1/2]

constexpr auto rsj::CharToInt ( const char  in)
constexprnoexcept
138 {
139 if constexpr (std::numeric_limits<char>::is_signed) { return static_cast<int>(in); }
140 else {
141 return static_cast<unsigned int>(in);
142 }
143 }

◆ CharToInt() [2/2]

template<class T >
auto rsj::CharToInt ( t)
delete

◆ CheckPermission()

void rsj::CheckPermission ( pid_t  pid)
41{
42 AEAddressDesc addressDesc;
43 const auto bundleIdentifier {
44 [NSRunningApplication runningApplicationWithProcessIdentifier:pid].bundleIdentifier};
45 const auto bundleIdentifierCString {
46 [bundleIdentifier cStringUsingEncoding:NSUTF8StringEncoding]};
47 const auto aeresult {AECreateDesc(typeApplicationBundleID, bundleIdentifierCString,
48 std::strlen(bundleIdentifierCString), &addressDesc)};
49 if (aeresult == noErr) {
50 const auto status {
51 AEDeterminePermissionToAutomateTarget(&addressDesc, typeWildCard, typeWildCard, true)};
52 AEDisposeDesc(&addressDesc);
53 switch (status) {
54 case errAEEventWouldRequireUserConsent:
55 rsj::Log(fmt::format(
56 FMT_STRING("Automation permission pending for {}."), bundleIdentifierCString));
57 break;
58 case noErr:
59 rsj::Log(fmt::format(
60 FMT_STRING("Automation permission granted for {}."), bundleIdentifierCString));
61 break;
62 case errAEEventNotPermitted:
63 {
64 rsj::Log(fmt::format(
65 FMT_STRING("Automation permission denied for {}."), bundleIdentifierCString));
66 const auto title {juce::translate(
67 "MIDI2LR needs your authorization to send keystrokes to Lightroom")};
68 const auto message {juce::translate(
69 "To authorize MIDI2LR to send keystrokes to Lightroom, please follow these "
70 "steps:\r\n1) Open System Preferences\r\n2) Open Accessibility preferences \r\n3) "
71 "Select \"Accessibility Apps\"\r\n4) Add this application to the approval list")};
72 juce::NativeMessageBox::showMessageBox(juce::AlertWindow::WarningIcon, title, message);
73 break;
74 }
75 case procNotFound:
77 fmt::format(FMT_STRING("Application not found. Automation permission unknown for {}."),
78 bundleIdentifierCString));
79 break;
80 default:
81 rsj::Log(fmt::format(
82 FMT_STRING("Unexpected return value when checking automation permission for {}."),
83 bundleIdentifierCString));
84 break;
85 }
86 }
87 else {
88 rsj::Log(fmt::format(FMT_STRING("AECreateDesc returned error {}."), aeresult));
89 }
90}
void Log(const juce::String &info, const std::source_location &location=std::source_location::current()) noexcept
Definition Misc.cpp:131

◆ ExceptionResponse() [1/2]

void rsj::ExceptionResponse ( const std::exception &  e,
const std::source_location &  location = std::source_location::current() 
)
noexcept
208{
209 try {
210 const auto alert_text {juce::translate("Exception ").toStdString() + e.what()};
211 const auto error_text {std::string("Exception ") + e.what()};
212 rsj::LogAndAlertError(alert_text, error_text, location);
213 }
214 catch (...) { //-V565 //-V5002 // NOLINT(bugprone-empty-catch)
215 }
216}
void LogAndAlertError(const juce::String &error_text, const std::source_location &location=std::source_location::current()) noexcept
Definition Misc.cpp:160

◆ ExceptionResponse() [2/2]

void rsj::ExceptionResponse ( gsl::czstring  id,
gsl::czstring  fu,
const std::exception &  e 
)
noexcept

◆ GetKeyMap()

std::unordered_map< UniChar, rsj::KeyData > rsj::GetKeyMap ( )
157{
158 using namespace std::chrono_literals;
159 if (!FillInSucceeded() && !juce::MessageManager::callAsync(FillInMessageLoop)) {
160 rsj::Log("Unable to post FillInMessageLoop to message queue.");
161 }
162 for (int i = 0; i < 4; ++i) {
163 if (FillInSucceeded()) { break; }
164 std::this_thread::sleep_for(20ms);
165 rsj::Log("20ms sleep for message queue waiting for FillInMessageLoop to run.");
166 }
167 rsj::Log(fmt::format(FMT_STRING("Making KeyMap. Keyboard type {}. KeyMap is {}."),
168 GetKeyboardLayout(), FillInSucceeded()));
169 return InternalKeyMap();
170}

◆ LabelThread()

void rsj::LabelThread ( gsl::czstring  threadname)
49{
50 auto result {pthread_setname_np(threadname)};
51 if (result) {
52 rsj::Log(fmt::format(FMT_STRING("Label thread {} failed with {} error."), threadname,
53 result));
54 }
55}

◆ Log() [1/3]

void rsj::Log ( const juce::String &  info,
const std::source_location &  location = std::source_location::current() 
)
noexcept
132{
133 try {
134 if (juce::Logger::getCurrentLogger()) {
135 juce::String localname {location.file_name()};
136#ifdef _WIN32
137 localname = localname.substring(localname.lastIndexOfChar('\\') + 1);
138 auto last_error {wil::last_error_context()};
139#else
140 localname = localname.substring(localname.lastIndexOfChar('/') + 1);
141#endif
142 juce::Logger::writeToLog(juce::Time::getCurrentTime().toISO8601(true) + ' ' + localname
143 + '(' + juce::String(location.line()) + ") " + info);
144 }
145 }
146 catch (...) { //-V565 //-V5002 // NOLINT(bugprone-empty-catch)
147 }
148}

◆ Log() [2/3]

void rsj::Log ( gsl::cwzstring  info,
const std::source_location &  location = std::source_location::current() 
)
noexcept
156{
157 rsj::Log(juce::String(info), location);
158}

◆ Log() [3/3]

void rsj::Log ( gsl::czstring  info,
const std::source_location &  location = std::source_location::current() 
)
noexcept
151{
152 rsj::Log(juce::String::fromUTF8(info), location);
153}

◆ LogAndAlertError() [1/3]

void rsj::LogAndAlertError ( const juce::String &  alert_text,
const juce::String &  error_text,
const std::source_location &  location = std::source_location::current() 
)
noexcept
178{
179 try {
180 {
181 juce::MessageManager::callAsync([=] {
182 juce::NativeMessageBox::showMessageBox(juce::AlertWindow::WarningIcon,
183 juce::translate("Error"), alert_text);
184 });
185 }
186 rsj::Log(error_text, location);
187 }
188 catch (...) { //-V565 //-V5002 // NOLINT(bugprone-empty-catch)
189 }
190}

◆ LogAndAlertError() [2/3]

void rsj::LogAndAlertError ( const juce::String &  error_text,
const std::source_location &  location = std::source_location::current() 
)
noexcept
162{
163 try {
164 {
165 juce::MessageManager::callAsync([=] {
166 juce::NativeMessageBox::showMessageBox(juce::AlertWindow::WarningIcon,
167 juce::translate("Error"), error_text);
168 });
169 }
170 rsj::Log(error_text, location);
171 }
172 catch (...) { //-V565 //-V5002 // NOLINT(bugprone-empty-catch)
173 }
174}

◆ LogAndAlertError() [3/3]

void rsj::LogAndAlertError ( gsl::czstring  error_text,
const std::source_location &  location = std::source_location::current() 
)
noexcept
193{
194 try {
195 {
196 juce::MessageManager::callAsync([=] {
197 juce::NativeMessageBox::showMessageBox(juce::AlertWindow::WarningIcon,
198 juce::translate("Error"), error_text);
199 });
200 }
201 rsj::Log(error_text, location);
202 }
203 catch (...) { //-V565 //-V5002 // NOLINT(bugprone-empty-catch)
204 }
205}

◆ MessageTypeToLabel()

const char * rsj::MessageTypeToLabel ( MessageType  from)
inlinenoexcept
83 {
84 constexpr std::array kTranslationTable {"NOTE OFF", "NOTE ON", "KEY PRESSURE", "CC",
85 "PROGRAM CHANGE", "CHANNEL PRESSURE", "PITCHBEND", "SYSTEM"};
86#pragma warning(suppress : 26446 26482)
87 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
88 return kTranslationTable[static_cast<size_t>(from)
89 - static_cast<size_t>(MessageType::kNoteOff)];
90 }

◆ MessageTypeToName()

const char * rsj::MessageTypeToName ( MessageType  from)
inlinenoexcept
73 {
74 constexpr std::array translation_table {"Note Off", "Note On", "Key Pressure",
75 "Control Change", "Program Change", "Channel Pressure", "Pitch Bend", "System"};
76#pragma warning(suppress : 26446 26482)
77 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
78 return translation_table[static_cast<size_t>(from)
79 - static_cast<size_t>(MessageType::kNoteOff)];
80 }

◆ operator==()

constexpr bool rsj::operator== ( const rsj::MidiMessage lhs,
const rsj::MidiMessage rhs 
)
constexprprivatenoexcept
139 {
140 return lhs.message_type_byte == rhs.message_type_byte && lhs.channel == rhs.channel
141 && lhs.control_number == rhs.control_number && lhs.value == rhs.value;
142 }
int control_number
Definition MidiUtilities.h:126
int channel
Definition MidiUtilities.h:125
MessageType message_type_byte
Definition MidiUtilities.h:124
int value
Definition MidiUtilities.h:127

◆ ReplaceInvisibleChars()

std::string rsj::ReplaceInvisibleChars ( std::string_view  in)

◆ SendKeyDownUp()

void rsj::SendKeyDownUp ( const std::string &  key,
rsj::ActiveModifiers  mods 
)
noexcept
379{
380 try {
381 Expects(!key.empty());
382 static const pid_t lr_pid {GetPid()};
383 if (!lr_pid) {
384 rsj::LogAndAlertError("Unable to obtain PID for Lightroom in SendKeys.cpp.");
385 return;
386 }
387 CGKeyCode vk {0};
388 CGEventFlags flags {0};
389 if (const auto mapped_key {kKeyMap.find(rsj::ToLower(key))}; mapped_key != kKeyMap.end()) {
390 vk = mapped_key->second;
391 }
392 else {
393 const UniChar uc {ww898::utf::conv<char16_t>(key).front()};
394 const auto key_code_result {KeyCodeForChar(uc)};
395 if (key_code_result) {
396 const auto k_data {*key_code_result};
397 vk = k_data.keycode;
398 if (k_data.shift) { flags |= kCGEventFlagMaskShift; }
399 if (k_data.option) { flags |= kCGEventFlagMaskAlternate; }
400 }
401 else {
402 if (const auto mapped_unshifted_key {kANSIKeyMap.find(rsj::ToLower(key))};
403 mapped_unshifted_key != kANSIKeyMap.end()) {
404 vk = mapped_unshifted_key->second;
405 if (gsl::narrow_cast<char>(std::tolower(static_cast<unsigned char>(key.front())))
406 != key.front()) {
407 flags |= kCGEventFlagMaskShift;
408 }
409 }
410 else if (const auto mapped_shifted_key {kANSIKeyMapShifted.find(key)};
411 mapped_shifted_key != kANSIKeyMapShifted.end()) {
412 vk = mapped_shifted_key->second;
413 flags |= kCGEventFlagMaskShift;
414 }
415 else {
416 rsj::LogAndAlertError(fmt::format(
417 FMT_STRING("Unsupported character was used: \"{}\", no ANSI equivalent."), key));
418 return;
419 }
420 }
421 }
422 if (mods.alt_opt) { flags |= kCGEventFlagMaskAlternate; }
423 if (mods.command) { flags |= kCGEventFlagMaskCommand; }
424 if (mods.control) { flags |= kCGEventFlagMaskControl; }
425 if (mods.shift) { flags |= kCGEventFlagMaskShift; }
426 if (!juce::MessageManager::callAsync([vk, flags] { MacKeyDownUp(lr_pid, vk, flags); })) {
427 rsj::Log("Unable to post keystroke to message queue.");
428 }
429 }
430 catch (const std::exception& e) {
431 rsj::LogAndAlertError(fmt::format(FMT_STRING("Exception in key sending function for key: "
432 "\"{}\". Exception: {}."),
433 key, e.what()));
434 }
435 catch (...) {
436 rsj::LogAndAlertError(fmt::format(
437 FMT_STRING("Non-standard exception in key sending function for key: \"{}\"."), key));
438 }
439}
std::string ToLower(std::string_view in)
bool alt_opt
Definition SendKeys.h:22
bool shift
Definition SendKeys.h:25
bool control
Definition SendKeys.h:24
bool command
Definition SendKeys.h:23

◆ ToLower()

std::string rsj::ToLower ( std::string_view  in)

◆ ToMessageType()

constexpr MessageType rsj::ToMessageType ( std::underlying_type_t< MessageType value)
constexpr
62 {
63 static_assert(std::is_unsigned_v<decltype(value)>, "Avoid sign extension");
64 const auto from {value >> 4U & 0xFU};
65 if (std::cmp_less(from,
66 static_cast<std::underlying_type_t<MessageType>>(MessageType::kNoteOff))) {
67 throw std::out_of_range("ToMessageType: MessageType range error, must be 0x8 to 0xF");
68 }
69 return static_cast<MessageType>(from);
70 }
MessageType
Definition MidiUtilities.h:42

◆ Translate()

void rsj::Translate ( const std::string &  lg)
36{
37 try {
38 static const std::map<std::string, TransType> kTranslationTable {
39 { "de", de},
40 { "es", es},
41 { "fr", fr},
42 { "hi", hi},
43 { "it", it},
44 { "ja", ja},
45 { "ko", ko},
46 { "nb", nb},
47 { "nl", nl},
48 { "pl", pl},
49 { "pt", pt},
50 { "ru", ru},
51 { "sv", sv},
52 { "th", th},
53 {"zh_cn", zh_cn},
54 {"zh_tw", zh_tw}
55 };
56 const auto language {rsj::ToLower(lg)};
57 if (const auto found {kTranslationTable.find(language)}; found != kTranslationTable.end()) {
58#pragma warning(suppress : 26490)
59 /* SEE: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1423r0.html for rationale
60 * for reinterpret_cast */
61 const juce::String str( // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
62 juce::CharPointer_UTF8(reinterpret_cast<const char*>(found->second)));
63 auto ls {std::make_unique<juce::LocalisedStrings>(str, false)};
64 /* take ownership of ls */
65 juce::LocalisedStrings::setCurrentMappings(ls.release());
66 }
67 else {
68 juce::LocalisedStrings::setCurrentMappings(nullptr);
69 }
70 }
71 catch (const std::exception& e) {
73 throw;
74 }
75}
void ExceptionResponse(gsl::czstring id, gsl::czstring fu, const std::exception &e) noexcept

◆ Trim() [1/2]

void rsj::Trim ( std::string_view &&  value)
delete

◆ Trim() [2/2]

void rsj::Trim ( std::string_view &  value)
noexcept

◆ TrimL() [1/2]

void rsj::TrimL ( std::string_view &&  value)
delete

◆ TrimL() [2/2]

void rsj::TrimL ( std::string_view &  value)
noexcept

◆ ValidMessageType()

constexpr bool rsj::ValidMessageType ( std::underlying_type_t< MessageType value)
constexprnoexcept
54 {
55 static_assert(std::is_unsigned_v<decltype(value)>, "Avoid sign extension");
56 const auto from {value >> 4U & 0xFU};
57 return std::cmp_greater_equal(from,
58 static_cast<std::underlying_type_t<MessageType>>(MessageType::kNoteOff));
59 }