1/* 2 Copyright (c) 2020, Lukas Holecek <hluk@email.cz> 3 4 This file is part of CopyQ. 5 6 CopyQ is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 CopyQ is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with CopyQ. If not, see <http://www.gnu.org/licenses/>. 18*/ 19 20#include "macplatform.h" 21 22#include "app/applicationexceptionhandler.h" 23#include "common/log.h" 24#include "copyqpasteboardmime.h" 25#include "foregroundbackgroundfilter.h" 26#include "macplatformwindow.h" 27#include "platform/mac/macactivity.h" 28#include "urlpasteboardmime.h" 29#include "macclipboard.h" 30 31#include <QApplication> 32#include <QCoreApplication> 33#include <QDir> 34#include <QGuiApplication> 35#include <QScopedPointer> 36#include <QStringList> 37 38#include <Cocoa/Cocoa.h> 39#include <Carbon/Carbon.h> 40 41namespace { 42 class ClipboardApplication : public QApplication 43 { 44 public: 45 ClipboardApplication(int &argc, char **argv) 46 : QApplication(argc, argv) 47 , m_pasteboardMime() 48 , m_pasteboardMimeUrl(QLatin1String("public.url")) 49 , m_pasteboardMimeFileUrl(QLatin1String("public.file-url")) 50 { 51 } 52 53 private: 54 CopyQPasteboardMime m_pasteboardMime; 55 UrlPasteboardMime m_pasteboardMimeUrl; 56 UrlPasteboardMime m_pasteboardMimeFileUrl; 57 }; 58 59 template<typename T> inline T* objc_cast(id from) 60 { 61 if (from && [from isKindOfClass:[T class]]) { 62 return static_cast<T*>(from); 63 } 64 return nil; 65 } 66 67 bool isApplicationInItemList(LSSharedFileListRef list) { 68 bool flag = false; 69 UInt32 seed; 70 CFArrayRef items = LSSharedFileListCopySnapshot(list, &seed); 71 if (items) { 72 CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; 73 if (url) { 74 for (id item in(__bridge NSArray *) items) { 75 LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item; 76 if (LSSharedFileListItemResolve(itemRef, 0, &url, NULL) == noErr) { 77 if ([[(__bridge NSURL *) url path] hasPrefix:[[NSBundle mainBundle] bundlePath]]) { 78 flag = true; 79 break; 80 } 81 } 82 } 83 } 84 CFRelease(items); 85 } 86 return flag; 87 } 88 89 void addToLoginItems() 90 { 91 LSSharedFileListRef list = LSSharedFileListCreate(kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, NULL); 92 if (list) { 93 if (!isApplicationInItemList(list)) { 94 CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; 95 if (url) { 96 // Don't "Hide on Launch", as we don't have a window to show anyway 97 NSDictionary *properties = [NSDictionary 98 dictionaryWithObject: [NSNumber numberWithBool:NO] 99 forKey: @"com.apple.loginitem.HideOnLaunch"]; 100 LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(list, kLSSharedFileListItemLast, NULL, NULL, url, (__bridge CFDictionaryRef)properties, NULL); 101 if (item) 102 CFRelease(item); 103 } else { 104 ::log("Unable to find url for bundle, can't auto-load app", LogWarning); 105 } 106 } 107 CFRelease(list); 108 } else { 109 ::log("Unable to access shared file list, can't auto-load app", LogWarning); 110 } 111 } 112 113 void removeFromLoginItems() 114 { 115 LSSharedFileListRef list = LSSharedFileListCreate(kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, NULL); 116 if (list) { 117 if (isApplicationInItemList(list)) { 118 CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; 119 if (url) { 120 UInt32 seed; 121 CFArrayRef items = LSSharedFileListCopySnapshot(list, &seed); 122 if (items) { 123 for (id item in(__bridge NSArray *) items) { 124 LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item; 125 if (LSSharedFileListItemResolve(itemRef, 0, &url, NULL) == noErr) 126 if ([[(__bridge NSURL *) url path] hasPrefix:[[NSBundle mainBundle] bundlePath]]) 127 LSSharedFileListItemRemove(list, itemRef); 128 } 129 CFRelease(items); 130 } else { 131 ::log("No items in list of auto-loaded apps, can't stop auto-load of app", LogWarning); 132 } 133 } else { 134 ::log("Unable to find url for bundle, can't stop auto-load of app", LogWarning); 135 } 136 } 137 CFRelease(list); 138 } else { 139 ::log("Unable to access shared file list, can't stop auto-load of app", LogWarning); 140 } 141 } 142 143 QString absoluteResourcesePath(const QString &path) 144 { 145 return QCoreApplication::applicationDirPath() + "/../Resources/" + path; 146 } 147 148 template <typename QtApplication> 149 class Activity 150 : public MacActivity 151 , public ApplicationExceptionHandler<QtApplication> 152 { 153 public: 154 Activity(int &argc, char **argv, const QString &reason) 155 : MacActivity(reason) 156 , ApplicationExceptionHandler<QtApplication>(argc, argv) 157 { 158 [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited]; 159 } 160 }; 161 162} // namespace 163 164PlatformNativeInterface *platformNativeInterface() 165{ 166 static MacPlatform platform; 167 return &platform; 168} 169 170MacPlatform::MacPlatform() 171{ 172} 173 174QCoreApplication *MacPlatform::createConsoleApplication(int &argc, char **argv) 175{ 176 return new ApplicationExceptionHandler<QCoreApplication>(argc, argv); 177} 178 179QApplication *MacPlatform::createServerApplication(int &argc, char **argv) 180{ 181 QApplication *app = new Activity<ClipboardApplication>(argc, argv, "CopyQ Server"); 182 183 // Switch the app to foreground when in foreground 184 ForegroundBackgroundFilter::installFilter(app); 185 186 return app; 187} 188 189QGuiApplication *MacPlatform::createMonitorApplication(int &argc, char **argv) 190{ 191 return new Activity<ClipboardApplication>(argc, argv, "CopyQ clipboard monitor"); 192} 193 194QGuiApplication *MacPlatform::createClipboardProviderApplication(int &argc, char **argv) 195{ 196 return new Activity<ClipboardApplication>(argc, argv, "CopyQ clipboard provider"); 197} 198 199QCoreApplication *MacPlatform::createClientApplication(int &argc, char **argv) 200{ 201 return new Activity<QCoreApplication>(argc, argv, "CopyQ Client"); 202} 203 204QGuiApplication *MacPlatform::createTestApplication(int &argc, char **argv) 205{ 206 return new Activity<QGuiApplication>(argc, argv, "CopyQ Tests"); 207} 208 209PlatformClipboardPtr MacPlatform::clipboard() 210{ 211 return PlatformClipboardPtr(new MacClipboard()); 212} 213 214QStringList MacPlatform::getCommandLineArguments(int argc, char **argv) 215{ 216 QStringList arguments; 217 218 for (int i = 1; i < argc; ++i) 219 arguments.append( QString::fromUtf8(argv[i]) ); 220 221 return arguments; 222} 223 224bool MacPlatform::findPluginDir(QDir *pluginsDir) 225{ 226 pluginsDir->setPath( qApp->applicationDirPath() ); 227 if (pluginsDir->dirName() != "MacOS") { 228 if ( pluginsDir->cd("plugins")) { 229 COPYQ_LOG("Found plugins in build tree"); 230 return true; 231 } 232 return false; 233 } 234 235 if ( pluginsDir->cdUp() // Contents 236 && pluginsDir->cd("PlugIns") 237 && pluginsDir->cd("copyq")) 238 { 239 // OK, found it in the bundle 240 COPYQ_LOG("Found plugins in application bundle"); 241 return true; 242 } 243 244 pluginsDir->setPath( qApp->applicationDirPath() ); 245 246 if ( pluginsDir->cdUp() // Contents 247 && pluginsDir->cdUp() // copyq.app 248 && pluginsDir->cdUp() // repo root 249 && pluginsDir->cd("plugins")) { 250 COPYQ_LOG("Found plugins in build tree"); 251 return true; 252 } 253 254 return false; 255} 256 257QString MacPlatform::defaultEditorCommand() 258{ 259 return "open -t -W -n %1"; 260} 261 262QString MacPlatform::translationPrefix() 263{ 264 return absoluteResourcesePath("translations"); 265} 266 267QString MacPlatform::themePrefix() 268{ 269 return absoluteResourcesePath("themes"); 270} 271 272PlatformWindowPtr MacPlatform::getCurrentWindow() 273{ 274 // FIXME: frontmostApplication doesn't seem to work well for own windows (at least in tests). 275 auto window = QApplication::activeWindow(); 276 if (window == nullptr) 277 window = QApplication::activeModalWidget(); 278 if (window != nullptr) 279 return PlatformWindowPtr(new MacPlatformWindow(window->winId())); 280 281 NSRunningApplication *runningApp = [[NSWorkspace sharedWorkspace] frontmostApplication]; 282 return PlatformWindowPtr(new MacPlatformWindow(runningApp)); 283} 284 285PlatformWindowPtr MacPlatform::getWindow(WId winId) { 286 return PlatformWindowPtr(new MacPlatformWindow(winId)); 287} 288 289bool MacPlatform::isAutostartEnabled() 290{ 291 // Note that this will need to be done differently if CopyQ goes into 292 // the App Store. 293 // http://rhult.github.io/articles/sandboxed-launch-on-login/ 294 bool isInList = false; 295 LSSharedFileListRef list = LSSharedFileListCreate(kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, NULL); 296 if (list) { 297 isInList = isApplicationInItemList(list); 298 CFRelease(list); 299 } 300 return isInList; 301} 302 303void MacPlatform::setAutostartEnabled(bool shouldEnable) 304{ 305 if (shouldEnable != isAutostartEnabled()) { 306 if (shouldEnable) { 307 addToLoginItems(); 308 } else { 309 removeFromLoginItems(); 310 } 311 } 312} 313