Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

440 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wdeprecated-declarations")
  21. extern NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId,
  22. int topLevelIndex, bool addDelegate);
  23. //==============================================================================
  24. struct StatusItemContainer : public Timer
  25. {
  26. //==============================================================================
  27. StatusItemContainer (SystemTrayIconComponent& iconComp, const Image& im)
  28. : owner (iconComp), statusIcon (imageToNSImage (im))
  29. {
  30. }
  31. virtual void configureIcon() = 0;
  32. virtual void setHighlighted (bool shouldHighlight) = 0;
  33. //==============================================================================
  34. void setIconSize()
  35. {
  36. [statusIcon.get() setSize: NSMakeSize (20.0f, 20.0f)];
  37. }
  38. void updateIcon (const Image& newImage)
  39. {
  40. statusIcon.reset (imageToNSImage (newImage));
  41. setIconSize();
  42. configureIcon();
  43. }
  44. void showMenu (const PopupMenu& menu)
  45. {
  46. if (NSMenu* m = createNSMenu (menu, "MenuBarItem", -2, -3, true))
  47. {
  48. setHighlighted (true);
  49. stopTimer();
  50. // There's currently no good alternative to this.
  51. [statusItem.get() popUpStatusItemMenu: m];
  52. startTimer (1);
  53. }
  54. }
  55. //==============================================================================
  56. void timerCallback() override
  57. {
  58. stopTimer();
  59. setHighlighted (false);
  60. }
  61. //==============================================================================
  62. SystemTrayIconComponent& owner;
  63. std::unique_ptr<NSStatusItem, NSObjectDeleter> statusItem;
  64. std::unique_ptr<NSImage, NSObjectDeleter> statusIcon;
  65. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StatusItemContainer)
  66. };
  67. //==============================================================================
  68. struct ButtonBasedStatusItem : public StatusItemContainer
  69. {
  70. //==============================================================================
  71. ButtonBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im)
  72. : StatusItemContainer (iconComp, im)
  73. {
  74. static ButtonEventForwarderClass cls;
  75. eventForwarder.reset ([cls.createInstance() init]);
  76. ButtonEventForwarderClass::setOwner (eventForwarder.get(), this);
  77. setIconSize();
  78. configureIcon();
  79. statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
  80. auto button = [statusItem.get() button];
  81. button.image = statusIcon.get();
  82. button.target = eventForwarder.get();
  83. button.action = @selector (handleEvent:);
  84. #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
  85. [button sendActionOn: NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskScrollWheel];
  86. #else
  87. [button sendActionOn: NSLeftMouseDownMask | NSRightMouseDownMask | NSScrollWheelMask];
  88. #endif
  89. }
  90. void configureIcon() override
  91. {
  92. [statusIcon.get() setTemplate: true];
  93. [statusItem.get() button].image = statusIcon.get();
  94. }
  95. void setHighlighted (bool shouldHighlight) override
  96. {
  97. [[statusItem.get() button] setHighlighted: shouldHighlight];
  98. }
  99. //==============================================================================
  100. void handleEvent()
  101. {
  102. auto e = [NSApp currentEvent];
  103. NSEventType type = [e type];
  104. const bool isLeft = (type == NSEventTypeLeftMouseDown);
  105. const bool isRight = (type == NSEventTypeRightMouseDown);
  106. if (owner.isCurrentlyBlockedByAnotherModalComponent())
  107. {
  108. if (isLeft || isRight)
  109. if (auto* current = Component::getCurrentlyModalComponent())
  110. current->inputAttemptWhenModal();
  111. }
  112. else
  113. {
  114. auto eventMods = ComponentPeer::getCurrentModifiersRealtime();
  115. if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
  116. eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
  117. auto now = Time::getCurrentTime();
  118. auto mouseSource = Desktop::getInstance().getMainMouseSource();
  119. auto pressure = (float) e.pressure;
  120. if (isLeft || isRight)
  121. {
  122. owner.mouseDown ({ mouseSource, {},
  123. eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
  124. : ModifierKeys::rightButtonModifier),
  125. pressure,
  126. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  127. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  128. &owner, &owner, now, {}, now, 1, false });
  129. owner.mouseUp ({ mouseSource, {},
  130. eventMods.withoutMouseButtons(),
  131. pressure,
  132. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  133. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  134. &owner, &owner, now, {}, now, 1, false });
  135. }
  136. else if (type == NSEventTypeMouseMoved)
  137. {
  138. owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
  139. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  140. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  141. &owner, &owner, now, {}, now, 1, false));
  142. }
  143. }
  144. }
  145. //==============================================================================
  146. class ButtonEventForwarderClass : public ObjCClass<NSObject>
  147. {
  148. public:
  149. ButtonEventForwarderClass() : ObjCClass<NSObject> ("JUCEButtonEventForwarderClass_")
  150. {
  151. addIvar<ButtonBasedStatusItem*> ("owner");
  152. addMethod (@selector (handleEvent:), handleEvent, "v@:@");
  153. registerClass();
  154. }
  155. static ButtonBasedStatusItem* getOwner (id self) { return getIvar<ButtonBasedStatusItem*> (self, "owner"); }
  156. static void setOwner (id self, ButtonBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); }
  157. private:
  158. static void handleEvent (id self, SEL, id)
  159. {
  160. if (auto* owner = getOwner (self))
  161. owner->handleEvent();
  162. }
  163. };
  164. //==============================================================================
  165. std::unique_ptr<NSObject, NSObjectDeleter> eventForwarder;
  166. };
  167. //==============================================================================
  168. struct ViewBasedStatusItem : public StatusItemContainer
  169. {
  170. //==============================================================================
  171. ViewBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im)
  172. : StatusItemContainer (iconComp, im)
  173. {
  174. static SystemTrayViewClass cls;
  175. view.reset ([cls.createInstance() init]);
  176. SystemTrayViewClass::setOwner (view.get(), this);
  177. SystemTrayViewClass::setImage (view.get(), statusIcon.get());
  178. setIconSize();
  179. statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
  180. [statusItem.get() setView: view.get()];
  181. SystemTrayViewClass::frameChanged (view.get(), SEL(), nullptr);
  182. [[NSNotificationCenter defaultCenter] addObserver: view.get()
  183. selector: @selector (frameChanged:)
  184. name: NSWindowDidMoveNotification
  185. object: nil];
  186. }
  187. ~ViewBasedStatusItem() override
  188. {
  189. [[NSNotificationCenter defaultCenter] removeObserver: view.get()];
  190. [[NSStatusBar systemStatusBar] removeStatusItem: statusItem.get()];
  191. SystemTrayViewClass::setOwner (view.get(), nullptr);
  192. SystemTrayViewClass::setImage (view.get(), nil);
  193. }
  194. void configureIcon() override
  195. {
  196. SystemTrayViewClass::setImage (view.get(), statusIcon.get());
  197. [statusItem.get() setView: view.get()];
  198. }
  199. void setHighlighted (bool shouldHighlight) override
  200. {
  201. isHighlighted = shouldHighlight;
  202. [view.get() setNeedsDisplay: true];
  203. }
  204. //==============================================================================
  205. void handleStatusItemAction (NSEvent* e)
  206. {
  207. NSEventType type = [e type];
  208. const bool isLeft = (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp);
  209. const bool isRight = (type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp);
  210. if (owner.isCurrentlyBlockedByAnotherModalComponent())
  211. {
  212. if (isLeft || isRight)
  213. if (auto* current = Component::getCurrentlyModalComponent())
  214. current->inputAttemptWhenModal();
  215. }
  216. else
  217. {
  218. auto eventMods = ComponentPeer::getCurrentModifiersRealtime();
  219. if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
  220. eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
  221. auto now = Time::getCurrentTime();
  222. auto mouseSource = Desktop::getInstance().getMainMouseSource();
  223. auto pressure = (float) e.pressure;
  224. if (isLeft || isRight) // Only mouse up is sent by the OS, so simulate a down/up
  225. {
  226. setHighlighted (true);
  227. startTimer (150);
  228. owner.mouseDown (MouseEvent (mouseSource, {},
  229. eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
  230. : ModifierKeys::rightButtonModifier),
  231. pressure, MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  232. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  233. &owner, &owner, now, {}, now, 1, false));
  234. owner.mouseUp (MouseEvent (mouseSource, {}, eventMods.withoutMouseButtons(), pressure,
  235. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  236. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  237. &owner, &owner, now, {}, now, 1, false));
  238. }
  239. else if (type == NSEventTypeMouseMoved)
  240. {
  241. owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
  242. MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  243. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  244. &owner, &owner, now, {}, now, 1, false));
  245. }
  246. }
  247. }
  248. //==============================================================================
  249. struct SystemTrayViewClass : public ObjCClass<NSControl>
  250. {
  251. SystemTrayViewClass() : ObjCClass<NSControl> ("JUCESystemTrayView_")
  252. {
  253. addIvar<ViewBasedStatusItem*> ("owner");
  254. addIvar<NSImage*> ("image");
  255. addMethod (@selector (mouseDown:), handleEventDown, "v@:@");
  256. addMethod (@selector (rightMouseDown:), handleEventDown, "v@:@");
  257. addMethod (@selector (drawRect:), drawRect, "v@:@");
  258. addMethod (@selector (frameChanged:), frameChanged, "v@:@");
  259. registerClass();
  260. }
  261. static ViewBasedStatusItem* getOwner (id self) { return getIvar<ViewBasedStatusItem*> (self, "owner"); }
  262. static NSImage* getImage (id self) { return getIvar<NSImage*> (self, "image"); }
  263. static void setOwner (id self, ViewBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); }
  264. static void setImage (id self, NSImage* image) { object_setInstanceVariable (self, "image", image); }
  265. static void frameChanged (id self, SEL, NSNotification*)
  266. {
  267. if (auto* owner = getOwner (self))
  268. {
  269. NSRect r = [[[owner->statusItem.get() view] window] frame];
  270. NSRect sr = [[[NSScreen screens] objectAtIndex: 0] frame];
  271. r.origin.y = sr.size.height - r.origin.y - r.size.height;
  272. owner->owner.setBounds (convertToRectInt (r));
  273. }
  274. }
  275. private:
  276. static void handleEventDown (id self, SEL, NSEvent* e)
  277. {
  278. if (auto* owner = getOwner (self))
  279. owner->handleStatusItemAction (e);
  280. }
  281. static void drawRect (id self, SEL, NSRect)
  282. {
  283. NSRect bounds = [self bounds];
  284. if (auto* owner = getOwner (self))
  285. [owner->statusItem.get() drawStatusBarBackgroundInRect: bounds
  286. withHighlight: owner->isHighlighted];
  287. if (NSImage* const im = getImage (self))
  288. {
  289. NSSize imageSize = [im size];
  290. [im drawInRect: NSMakeRect (bounds.origin.x + ((bounds.size.width - imageSize.width) / 2.0f),
  291. bounds.origin.y + ((bounds.size.height - imageSize.height) / 2.0f),
  292. imageSize.width, imageSize.height)
  293. fromRect: NSZeroRect
  294. operation: NSCompositingOperationSourceOver
  295. fraction: 1.0f];
  296. }
  297. }
  298. };
  299. //==============================================================================
  300. std::unique_ptr<NSControl, NSObjectDeleter> view;
  301. bool isHighlighted = false;
  302. };
  303. //==============================================================================
  304. class SystemTrayIconComponent::Pimpl
  305. {
  306. public:
  307. //==============================================================================
  308. Pimpl (SystemTrayIconComponent& iconComp, const Image& im)
  309. {
  310. if (std::floor (NSFoundationVersionNumber) > NSFoundationVersionNumber10_10)
  311. statusItemHolder = std::make_unique<ButtonBasedStatusItem> (iconComp, im);
  312. else
  313. statusItemHolder = std::make_unique<ViewBasedStatusItem> (iconComp, im);
  314. }
  315. //==============================================================================
  316. std::unique_ptr<StatusItemContainer> statusItemHolder;
  317. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  318. };
  319. //==============================================================================
  320. void SystemTrayIconComponent::setIconImage (const Image&, const Image& templateImage)
  321. {
  322. if (templateImage.isValid())
  323. {
  324. if (pimpl == nullptr)
  325. pimpl.reset (new Pimpl (*this, templateImage));
  326. else
  327. pimpl->statusItemHolder->updateIcon (templateImage);
  328. }
  329. else
  330. {
  331. pimpl.reset();
  332. }
  333. }
  334. void SystemTrayIconComponent::setIconTooltip (const String&)
  335. {
  336. // xxx not yet implemented!
  337. }
  338. void SystemTrayIconComponent::setHighlighted (bool shouldHighlight)
  339. {
  340. if (pimpl != nullptr)
  341. pimpl->statusItemHolder->setHighlighted (shouldHighlight);
  342. }
  343. void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
  344. {
  345. // xxx Not implemented!
  346. }
  347. void SystemTrayIconComponent::hideInfoBubble()
  348. {
  349. // xxx Not implemented!
  350. }
  351. void* SystemTrayIconComponent::getNativeHandle() const
  352. {
  353. return pimpl != nullptr ? pimpl->statusItemHolder->statusItem.get() : nullptr;
  354. }
  355. void SystemTrayIconComponent::showDropdownMenu (const PopupMenu& menu)
  356. {
  357. if (pimpl != nullptr)
  358. pimpl->statusItemHolder->showMenu (menu);
  359. }
  360. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  361. } // namespace juce