1// This file is part of Desktop App Toolkit,
2// a set of libraries for developing nice desktop applications.
3//
4// For license and copyright information please follow this link:
5// https://github.com/desktop-app/legal/blob/master/LEGAL
6//
7#include "ui/platform/mac/ui_utility_mac.h"
8
9#include "ui/integration.h"
10
11#include <QtGui/QPainter>
12#include <QtGui/QtEvents>
13#include <QtGui/QWindow>
14
15#include <Cocoa/Cocoa.h>
16
17#ifndef OS_MAC_STORE
18extern "C" {
19void _dispatch_main_queue_callback_4CF(mach_msg_header_t *msg);
20} // extern "C"
21#endif // OS_MAC_STORE
22
23namespace Ui {
24namespace Platform {
25
26bool IsApplicationActive() {
27	return [[NSApplication sharedApplication] isActive];
28}
29
30void InitOnTopPanel(not_null<QWidget*> panel) {
31	Expects(!panel->windowHandle());
32
33	// Force creating windowHandle() without creating the platform window yet.
34	panel->setAttribute(Qt::WA_NativeWindow, true);
35	panel->windowHandle()->setProperty("_td_macNonactivatingPanelMask", QVariant(true));
36	panel->setAttribute(Qt::WA_NativeWindow, false);
37
38	panel->createWinId();
39
40	auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window];
41	Assert([platformWindow isKindOfClass:[NSPanel class]]);
42
43	auto platformPanel = static_cast<NSPanel*>(platformWindow);
44	[platformPanel setBackgroundColor:[NSColor clearColor]];
45	[platformPanel setLevel:NSModalPanelWindowLevel];
46	[platformPanel setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle];
47	[platformPanel setHidesOnDeactivate:NO];
48	//[platformPanel setFloatingPanel:YES];
49
50	Integration::Instance().activationFromTopPanel();
51}
52
53void DeInitOnTopPanel(not_null<QWidget*> panel) {
54	auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window];
55	Assert([platformWindow isKindOfClass:[NSPanel class]]);
56
57	auto platformPanel = static_cast<NSPanel*>(platformWindow);
58	auto newBehavior = ([platformPanel collectionBehavior] & (~NSWindowCollectionBehaviorCanJoinAllSpaces)) | NSWindowCollectionBehaviorMoveToActiveSpace;
59	[platformPanel setCollectionBehavior:newBehavior];
60}
61
62void ReInitOnTopPanel(not_null<QWidget*> panel) {
63	auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window];
64	Assert([platformWindow isKindOfClass:[NSPanel class]]);
65
66	auto platformPanel = static_cast<NSPanel*>(platformWindow);
67	auto newBehavior = ([platformPanel collectionBehavior] & (~NSWindowCollectionBehaviorMoveToActiveSpace)) | NSWindowCollectionBehaviorCanJoinAllSpaces;
68	[platformPanel setCollectionBehavior:newBehavior];
69}
70
71void ShowOverAll(not_null<QWidget*> widget, bool canFocus) {
72	NSWindow *wnd = [reinterpret_cast<NSView*>(widget->winId()) window];
73	[wnd setLevel:NSPopUpMenuWindowLevel];
74	if (!canFocus) {
75		[wnd setStyleMask:NSUtilityWindowMask | NSNonactivatingPanelMask];
76		[wnd setCollectionBehavior:NSWindowCollectionBehaviorMoveToActiveSpace|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle];
77	}
78}
79
80void BringToBack(not_null<QWidget*> widget) {
81	NSWindow *wnd = [reinterpret_cast<NSView*>(widget->winId()) window];
82	[wnd setLevel:NSModalPanelWindowLevel];
83}
84
85void DrainMainQueue() {
86#ifndef OS_MAC_STORE
87	_dispatch_main_queue_callback_4CF(nullptr);
88#endif // OS_MAC_STORE
89}
90
91void IgnoreAllActivation(not_null<QWidget*> widget) {
92}
93
94std::optional<bool> IsOverlapped(
95		not_null<QWidget*> widget,
96		const QRect &rect) {
97	NSWindow *window = [reinterpret_cast<NSView*>(widget->window()->winId()) window];
98	Assert(window != nullptr);
99
100	if (![window isOnActiveSpace]) {
101		return true;
102	}
103
104	const auto nativeRect = CGRectMake(
105		rect.x(),
106		rect.y(),
107		rect.width(),
108		rect.height());
109
110	CGWindowID windowId = (CGWindowID)[window windowNumber];
111	const CGWindowListOption options = kCGWindowListExcludeDesktopElements
112		| kCGWindowListOptionOnScreenAboveWindow;
113	CFArrayRef windows = CGWindowListCopyWindowInfo(options, windowId);
114	if (!windows) {
115		return std::nullopt;
116	}
117	const auto guard = gsl::finally([&] {
118		CFRelease(windows);
119	});
120	NSMutableArray *list = (__bridge NSMutableArray*)windows;
121	for (NSDictionary *window in list) {
122		NSNumber *alphaValue = [window objectForKey:@"kCGWindowAlpha"];
123		const auto alpha = alphaValue ? [alphaValue doubleValue] : 1.;
124		if (alpha == 0.) {
125			continue;
126		}
127		NSString *owner = [window objectForKey:@"kCGWindowOwnerName"];
128		NSNumber *layerValue = [window objectForKey:@"kCGWindowLayer"];
129		const auto layer = layerValue ? [layerValue intValue] : 0;
130		if (owner && [owner isEqualToString:@"Dock"] && layer == 20) {
131			// It is always full screen.
132			continue;
133		}
134		CFDictionaryRef bounds = (__bridge CFDictionaryRef)[window objectForKey:@"kCGWindowBounds"];
135		if (!bounds) {
136			continue;
137		}
138		CGRect rect;
139		if (!CGRectMakeWithDictionaryRepresentation(bounds, &rect)) {
140			continue;
141		} else if (CGRectIntersectsRect(rect, nativeRect)) {
142			return true;
143		}
144	}
145	return false;
146}
147
148TitleControls::Layout TitleControlsLayout() {
149	return TitleControls::Layout{
150		.left = {
151			TitleControls::Control::Close,
152			TitleControls::Control::Minimize,
153			TitleControls::Control::Maximize,
154		}
155	};
156}
157
158} // namespace Platform
159} // namespace Ui
160