MIDI2LR 6.2.0.1
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)
 
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
134 {
135 if constexpr (std::numeric_limits<char>::is_signed) { return static_cast<int>(in); }
136 else {
137 return static_cast<unsigned int>(in);
138 }
139 }

◆ 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(FMT_STRING("Automation permission pending for {}."),
56 bundleIdentifierCString),
57 std::source_location::current());
58 break;
59 case noErr:
60 rsj::Log(fmt::format(FMT_STRING("Automation permission granted for {}."),
61 bundleIdentifierCString),
62 std::source_location::current());
63 break;
64 case errAEEventNotPermitted:
65 {
66 rsj::Log(fmt::format(FMT_STRING("Automation permission denied for {}."),
67 bundleIdentifierCString),
68 std::source_location::current());
69 const auto title {juce::translate("MIDI2LR needs your authorization to send keystrokes "
70 "to Lightroom")};
71 const auto message {juce::translate(
72 "To authorize MIDI2LR to send keystrokes to Lightroom, please follow these "
73 "steps:\r\n1) Open System Preferences\r\n2) Open Accessibility preferences \r\n3) "
74 "Select \"Accessibility Apps\"\r\n4) Add this application to the approval list")};
75 juce::NativeMessageBox::showMessageBox(juce::AlertWindow::WarningIcon, title, message);
76 break;
77 }
78 case procNotFound:
79 rsj::Log(fmt::format(FMT_STRING("Application not found. Automation permission unknown for "
80 "{}."),
81 bundleIdentifierCString),
82 std::source_location::current());
83 break;
84 default:
85 rsj::Log(fmt::format(FMT_STRING("Unexpected return value when checking automation "
86 "permission for {}."),
87 bundleIdentifierCString),
88 std::source_location::current());
89 break;
90 }
91 }
92 else {
93 rsj::Log(fmt::format(FMT_STRING("AECreateDesc returned error {}."), aeresult),
94 std::source_location::current());
95 }
96}
void Log(const juce::String &info, const std::source_location &location=std::source_location::current()) noexcept
Definition Misc.cpp:113

◆ ExceptionResponse() [1/2]

void rsj::ExceptionResponse ( const std::exception &  e,
const std::source_location &  location = std::source_location::current() 
)
noexcept
190{
191 try {
192 const auto alert_text {juce::translate("Exception ").toStdString() + e.what()};
193 const auto error_text {std::string("Exception ") + e.what()};
194 rsj::LogAndAlertError(alert_text, error_text, location);
195 }
196 catch (...) { //-V565 //-V5002 // NOLINT(bugprone-empty-catch)
197 }
198}
void LogAndAlertError(const juce::String &error_text, const std::source_location &location=std::source_location::current()) noexcept
Definition Misc.cpp:142

◆ 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 std::source_location::current());
162 }
163 for (int i = 0; i < 4; ++i) {
164 if (FillInSucceeded()) { break; }
165 std::this_thread::sleep_for(20ms);
166 rsj::Log("20ms sleep for message queue waiting for FillInMessageLoop to run.",
167 std::source_location::current());
168 }
169 rsj::Log(fmt::format(FMT_STRING("Making KeyMap. Keyboard type {}. KeyMap is {}."),
170 GetKeyboardLayout(), FillInSucceeded()), std::source_location::current());
171 return InternalKeyMap();
172}

◆ 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
114{
115 try {
116 if (juce::Logger::getCurrentLogger()) {
117 juce::String localname {location.file_name()};
118#ifdef _WIN32
119 localname = localname.substring(localname.lastIndexOfChar('\\') + 1);
120 auto last_error {wil::last_error_context()};
121#else
122 localname = localname.substring(localname.lastIndexOfChar('/') + 1);
123#endif
124 juce::Logger::writeToLog(juce::Time::getCurrentTime().toISO8601(true) + ' ' + localname
125 + '(' + juce::String(location.line()) + ") " + info);
126 }
127 }
128 catch (...) { //-V565 //-V5002 // NOLINT(bugprone-empty-catch)
129 }
130}

◆ Log() [2/3]

void rsj::Log ( gsl::cwzstring  info,
const std::source_location &  location = std::source_location::current() 
)
noexcept
138{
139 rsj::Log(juce::String(info), location);
140}

◆ Log() [3/3]

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

◆ 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
160{
161 try {
162 {
163 juce::MessageManager::callAsync([=] {
164 juce::NativeMessageBox::showMessageBox(juce::AlertWindow::WarningIcon,
165 juce::translate("Error"), alert_text);
166 });
167 }
168 rsj::Log(error_text, location);
169 }
170 catch (...) { //-V565 //-V5002 // NOLINT(bugprone-empty-catch)
171 }
172}

◆ LogAndAlertError() [2/3]

void rsj::LogAndAlertError ( const juce::String &  error_text,
const std::source_location &  location = std::source_location::current() 
)
noexcept
144{
145 try {
146 {
147 juce::MessageManager::callAsync([=] {
148 juce::NativeMessageBox::showMessageBox(juce::AlertWindow::WarningIcon,
149 juce::translate("Error"), error_text);
150 });
151 }
152 rsj::Log(error_text, location);
153 }
154 catch (...) { //-V565 //-V5002 // NOLINT(bugprone-empty-catch)
155 }
156}

◆ LogAndAlertError() [3/3]

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

◆ 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 std::source_location::current());
386 return;
387 }
388 CGKeyCode vk {0};
389 CGEventFlags flags {0};
390 if (const auto mapped_key {kKeyMap.find(rsj::ToLower(key))}; mapped_key != kKeyMap.end()) {
391 vk = mapped_key->second;
392 }
393 else {
394 const UniChar uc {ww898::utf::conv<char16_t>(key).front()};
395 const auto key_code_result {KeyCodeForChar(uc)};
396 if (key_code_result) {
397 const auto k_data {*key_code_result};
398 vk = k_data.keycode;
399 if (k_data.shift) { flags |= kCGEventFlagMaskShift; }
400 if (k_data.option) { flags |= kCGEventFlagMaskAlternate; }
401 }
402 else {
403 if (const auto mapped_unshifted_key {kANSIKeyMap.find(rsj::ToLower(key))};
404 mapped_unshifted_key != kANSIKeyMap.end()) {
405 vk = mapped_unshifted_key->second;
406 if (gsl::narrow_cast<char>(std::tolower(static_cast<unsigned char>(key.front())))
407 != key.front()) {
408 flags |= kCGEventFlagMaskShift;
409 }
410 }
411 else if (const auto mapped_shifted_key {kANSIKeyMapShifted.find(key)};
412 mapped_shifted_key != kANSIKeyMapShifted.end()) {
413 vk = mapped_shifted_key->second;
414 flags |= kCGEventFlagMaskShift;
415 }
416 else {
417 rsj::LogAndAlertError(fmt::format(FMT_STRING("Unsupported character was used: "
418 "\"{}\", no ANSI equivalent."),
419 key),
420 std::source_location::current());
421 return;
422 }
423 }
424 }
425 if (mods.alt_opt) { flags |= kCGEventFlagMaskAlternate; }
426 if (mods.command) { flags |= kCGEventFlagMaskCommand; }
427 if (mods.control) { flags |= kCGEventFlagMaskControl; }
428 if (mods.shift) { flags |= kCGEventFlagMaskShift; }
429 if (!juce::MessageManager::callAsync([vk, flags] { MacKeyDownUp(lr_pid, vk, flags); })) {
430 rsj::Log("Unable to post keystroke to message queue.", std::source_location::current());
431 }
432 }
433 catch (const std::exception& e) {
434 rsj::LogAndAlertError(fmt::format(FMT_STRING("Exception in key sending function for key: "
435 "\"{}\". Exception: {}."),
436 key, e.what()),
437 std::source_location::current());
438 }
439 catch (...) {
440 rsj::LogAndAlertError(fmt::format(FMT_STRING("Non-standard exception in key sending function "
441 "for key: \"{}\"."),
442 key),
443 std::source_location::current());
444 }
445}
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) {
72 rsj::ExceptionResponse(e, std::source_location::current());
73 throw;
74 }
75}
void ExceptionResponse(gsl::czstring id, gsl::czstring fu, const std::exception &e) 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 }