MIDI2LR 6.3.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

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
Initial value:
std::unique_ptr<typename std::remove_pointer<T>::type,
Definition Ocpp.h:49

Enumeration Type Documentation

◆ CCmethod

enum struct rsj::CCmethod : char
strong
Enumerator
kAbsolute 
kTwosComplement 
kBinaryOffset 
kSignMagnitude 
@ kBinaryOffset
Definition ControlsModel.h:33
@ kTwosComplement
Definition ControlsModel.h:33
@ kAbsolute
Definition ControlsModel.h:33
@ kSignMagnitude
Definition ControlsModel.h:33

◆ MessageType

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

Function Documentation

◆ AppDataFilePath()

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

◆ AppDataMac()

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

◆ AppLogFilePath()

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

◆ AppLogMac()

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

◆ CharToInt() [1/2]

auto rsj::CharToInt ( const char in)
nodiscardconstexprnoexcept
160 {
161 if constexpr (std::numeric_limits<char>::is_signed) { return static_cast<int>(in); }
162 else {
163 return static_cast<unsigned int>(in);
164 }
165 }

◆ CharToInt() [2/2]

template<class T>
auto rsj::CharToInt ( T 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("Automation permission pending for {}.", bundleIdentifierCString),
56 std::source_location::current());
57 break;
58 case noErr:
59 rsj::Log(fmt::format("Automation permission granted for {}.", bundleIdentifierCString),
60 std::source_location::current());
61 break;
62 case errAEEventNotPermitted:
63 {
64 rsj::Log(fmt::format("Automation permission denied for {}.", bundleIdentifierCString),
65 std::source_location::current());
66 const auto title {juce::translate("MIDI2LR needs your authorization to send keystrokes "
67 "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:
76 rsj::Log(fmt::format("Application not found. Automation permission unknown for {}.",
77 bundleIdentifierCString),
78 std::source_location::current());
79 break;
80 default:
81 rsj::Log(fmt::format("Unexpected return value when checking automation permission for {}.",
82 bundleIdentifierCString),
83 std::source_location::current());
84 break;
85 }
86 }
87 else {
88 rsj::Log(fmt::format("AECreateDesc returned error {}.", aeresult),
89 std::source_location::current());
90 }
91}
void Log(const juce::String &info, const std::source_location &location=std::source_location::current()) noexcept
Definition Misc.cpp:112

◆ ExceptionResponse() [1/2]

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

◆ 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 ( )
nodiscard
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("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("Label thread {} failed with {} error.", threadname, result));
53 }
54}

◆ Log() [1/3]

void rsj::Log ( const juce::String & info,
const std::source_location & location = std::source_location::current() )
noexcept
113{
114 try {
115 if (juce::Logger::getCurrentLogger()) {
116 juce::String localname {location.file_name()};
117#ifdef _WIN32
118 localname = localname.substring(localname.lastIndexOfChar('\\') + 1);
119 auto last_error {wil::last_error_context()};
120#else
121 localname = localname.substring(localname.lastIndexOfChar('/') + 1);
122#endif
123 juce::Logger::writeToLog(juce::Time::getCurrentTime().toISO8601(true) + ' ' + localname
124 + '(' + juce::String(location.line()) + ") " + info);
125 }
126 }
127 catch (...) { //-V565 //-V5002 // NOLINT(bugprone-empty-catch)
128 }
129}

◆ Log() [2/3]

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

◆ Log() [3/3]

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

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

◆ LogAndAlertError() [2/3]

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

◆ LogAndAlertError() [3/3]

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

◆ MessageTypeToLabel()

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

◆ MessageTypeToName()

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

◆ operator==()

bool rsj::operator== ( const rsj::MidiMessage & lhs,
const rsj::MidiMessage & rhs )
constexprnoexcept
136 {
137 return lhs.message_type_byte == rhs.message_type_byte && lhs.channel == rhs.channel
138 && lhs.control_number == rhs.control_number && lhs.value == rhs.value;
139 }
int control_number
Definition MidiUtilities.h:123
int channel
Definition MidiUtilities.h:122
MessageType message_type_byte
Definition MidiUtilities.h:121
int value
Definition MidiUtilities.h:124

References rsj::MidiMessage::channel, rsj::MidiMessage::control_number, and rsj::MidiMessage::value.

◆ ReplaceInvisibleChars()

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

◆ 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 {
418 fmt::format("Unsupported character was used: \"{}\", no ANSI equivalent.", key),
419 std::source_location::current());
420 return;
421 }
422 }
423 }
424 if (mods.alt_opt) { flags |= kCGEventFlagMaskAlternate; }
425 if (mods.command) { flags |= kCGEventFlagMaskCommand; }
426 if (mods.control) { flags |= kCGEventFlagMaskControl; }
427 if (mods.shift) { flags |= kCGEventFlagMaskShift; }
428 if (!juce::MessageManager::callAsync([vk, flags] { MacKeyDownUp(lr_pid, vk, flags); })) {
429 rsj::Log("Unable to post keystroke to message queue.", std::source_location::current());
430 }
431 }
432 catch (const std::exception& e) {
433 rsj::LogAndAlertError(fmt::format("Exception in key sending function for key: \"{}\". "
434 "Exception: {}.",
435 key, e.what()),
436 std::source_location::current());
437 }
438 catch (...) {
440 fmt::format("Non-standard exception in key sending function for key: \"{}\".", key),
441 std::source_location::current());
442 }
443}
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)
nodiscard

◆ ToMessageType()

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

◆ 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

Referenced by CommandSet::CommandSet().

◆ ValidMessageType()

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