1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39#include "qcocoaaccessibilityelement.h"
40#include "qcocoaaccessibility.h"
41#include "qcocoahelpers.h"
42#include "qcocoawindow.h"
43#include "qcocoascreen.h"
44
45#include <QtGui/private/qaccessiblecache_p.h>
46#include <QtAccessibilitySupport/private/qaccessiblebridgeutils_p.h>
47#include <QtGui/qaccessible.h>
48
49#import <AppKit/NSAccessibility.h>
50
51QT_USE_NAMESPACE
52
53#ifndef QT_NO_ACCESSIBILITY
54
55/**
56 * Converts between absolute character offsets and line numbers of a
57 * QAccessibleTextInterface. Works in exactly one of two modes:
58 *
59 *  - Pass *line == -1 in order to get a line containing character at the given
60 *    *offset
61 *  - Pass *offset == -1 in order to get the offset of first character of the
62 *    given *line
63 *
64 * You can optionally also pass non-NULL `start` and `end`, which will in both
65 * modes be filled with the offset of the first and last characters of the
66 * relevant line.
67 */
68static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *offset, NSUInteger *start = 0, NSUInteger *end = 0)
69{
70    Q_ASSERT(*line == -1 || *offset == -1);
71    Q_ASSERT(*line != -1 || *offset != -1);
72    Q_ASSERT(*offset <= text->characterCount());
73
74    int curLine = -1;
75    int curStart = 0, curEnd = 0;
76
77    do {
78        curStart = curEnd;
79        text->textAtOffset(curStart, QAccessible::LineBoundary, &curStart, &curEnd);
80        // If the text is empty then we just return
81        if (curStart == -1 || curEnd == -1) {
82            if (start)
83                *start = 0;
84            if (end)
85                *end = 0;
86            return;
87        }
88        ++curLine;
89        {
90            // check for a case where a single word longer than the text edit's width and gets wrapped
91            // in the middle of the word; in this case curEnd will be an offset belonging to the next line
92            // and therefore nextEnd will not be equal to curEnd
93            int nextStart;
94            int nextEnd;
95            text->textAtOffset(curEnd, QAccessible::LineBoundary, &nextStart, &nextEnd);
96            if (nextEnd == curEnd)
97                ++curEnd;
98        }
99    } while ((*line == -1 || curLine < *line) && (*offset == -1 || (curEnd <= *offset)) && curEnd <= text->characterCount());
100
101    curEnd = qMin(curEnd, text->characterCount());
102
103    if (*line == -1)
104        *line = curLine;
105    if (*offset == -1)
106        *offset = curStart;
107
108    Q_ASSERT(curStart >= 0);
109    Q_ASSERT(curEnd >= 0);
110    if (start)
111        *start = curStart;
112    if (end)
113        *end = curEnd;
114}
115
116@implementation QMacAccessibilityElement {
117    QAccessible::Id axid;
118}
119
120- (instancetype)initWithId:(QAccessible::Id)anId
121{
122    Q_ASSERT((int)anId < 0);
123    self = [super init];
124    if (self) {
125        axid = anId;
126    }
127
128    return self;
129}
130
131+ (instancetype)elementWithId:(QAccessible::Id)anId
132{
133    Q_ASSERT(anId);
134    if (!anId)
135        return nil;
136
137    QAccessibleCache *cache = QAccessibleCache::instance();
138
139    QMacAccessibilityElement *element = cache->elementForId(anId);
140    if (!element) {
141        QAccessibleInterface *iface = QAccessible::accessibleInterface(anId);
142        Q_ASSERT(iface);
143        if (!iface || !iface->isValid())
144            return nil;
145        element = [[self alloc] initWithId:anId];
146        cache->insertElement(anId, element);
147    }
148    return element;
149}
150
151- (void)invalidate {
152    axid = 0;
153    NSAccessibilityPostNotification(self, NSAccessibilityUIElementDestroyedNotification);
154    [self release];
155}
156
157- (void)dealloc {
158    [super dealloc];
159}
160
161- (BOOL)isEqual:(id)object {
162    if ([object isKindOfClass:[QMacAccessibilityElement class]]) {
163        QMacAccessibilityElement *other = object;
164        return other->axid == axid;
165    } else {
166        return NO;
167    }
168}
169
170- (NSUInteger)hash {
171    return axid;
172}
173
174//
175// accessibility protocol
176//
177
178- (BOOL)isAccessibilityFocused
179{
180    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
181    if (!iface || !iface->isValid()) {
182        return false;
183    }
184    // Just check if the app thinks we're focused.
185    id focusedElement = NSApp.accessibilityApplicationFocusedUIElement;
186    return [focusedElement isEqual:self];
187}
188
189// attributes
190
191+ (id) lineNumberForIndex: (int)index forText:(const QString &)text
192{
193    QStringRef textBefore = QStringRef(&text, 0, index);
194    int newlines = textBefore.count(QLatin1Char('\n'));
195    return @(newlines);
196}
197
198- (BOOL) accessibilityNotifiesWhenDestroyed {
199    return YES;
200}
201
202- (NSString *) accessibilityRole {
203    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
204    if (!iface || !iface->isValid())
205        return NSAccessibilityUnknownRole;
206    return QCocoaAccessible::macRole(iface);
207}
208
209- (NSString *) accessibilitySubRole {
210    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
211    if (!iface || !iface->isValid())
212        return NSAccessibilityUnknownRole;
213    return QCocoaAccessible::macSubrole(iface);
214}
215
216- (NSString *) accessibilityRoleDescription {
217    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
218    if (!iface || !iface->isValid())
219        return NSAccessibilityUnknownRole;
220    return NSAccessibilityRoleDescription(self.accessibilityRole, self.accessibilitySubRole);
221}
222
223- (NSArray *) accessibilityChildren {
224    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
225    if (!iface || !iface->isValid())
226        return nil;
227    return QCocoaAccessible::unignoredChildren(iface);
228}
229
230- (id) accessibilityWindow {
231    // We're in the same window as our parent.
232    return [self.accessibilityParent accessibilityWindow];
233}
234
235- (id) accessibilityTopLevelUIElementAttribute {
236    // We're in the same top level element as our parent.
237    return [self.accessibilityParent accessibilityTopLevelUIElementAttribute];
238}
239
240- (NSString *) accessibilityTitle {
241    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
242    if (!iface || !iface->isValid())
243        return nil;
244    if (iface->role() == QAccessible::StaticText)
245        return nil;
246    return iface->text(QAccessible::Name).toNSString();
247}
248
249- (BOOL) accessibilityEnabledAttribute {
250    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
251    if (!iface || !iface->isValid())
252        return false;
253    return !iface->state().disabled;
254}
255
256- (id)accessibilityParent {
257    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
258    if (!iface || !iface->isValid())
259        return nil;
260
261    // macOS expects that the hierarchy is:
262    // App -> Window -> Children
263    // We don't actually have the window reflected properly in QAccessibility.
264    // Check if the parent is the application and then instead return the native window.
265
266    if (QAccessibleInterface *parent = iface->parent()) {
267        if (parent->role() != QAccessible::Application) {
268            QAccessible::Id parentId = QAccessible::uniqueId(parent);
269            return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId: parentId]);
270        }
271    }
272
273    if (QWindow *window = iface->window()) {
274        QPlatformWindow *platformWindow = window->handle();
275        if (platformWindow) {
276            QCocoaWindow *win = static_cast<QCocoaWindow*>(platformWindow);
277            return NSAccessibilityUnignoredAncestor(qnsview_cast(win->view()));
278        }
279    }
280    return nil;
281}
282
283- (NSRect)accessibilityFrame {
284    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
285    if (!iface || !iface->isValid())
286        return NSZeroRect;
287    return QCocoaScreen::mapToNative(iface->rect());
288}
289
290- (NSString*)accessibilityLabel {
291    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
292    if (!iface || !iface->isValid()) {
293        qWarning() << "Called accessibilityLabel on invalid object: " << axid;
294        return nil;
295    }
296    return iface->text(QAccessible::Description).toNSString();
297}
298
299- (void)setAccessibilityLabel:(NSString*)label{
300    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
301    if (!iface || !iface->isValid())
302        return;
303    iface->setText(QAccessible::Description, QString::fromNSString(label));
304}
305
306- (id) accessibilityMinValue:(QAccessibleInterface*)iface {
307    if (QAccessibleValueInterface *val = iface->valueInterface())
308        return @(val->minimumValue().toDouble());
309    return nil;
310}
311
312- (id) accessibilityMaxValue:(QAccessibleInterface*)iface {
313    if (QAccessibleValueInterface *val = iface->valueInterface())
314        return @(val->maximumValue().toDouble());
315    return nil;
316}
317
318- (id) accessibilityValue {
319    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
320    if (!iface || !iface->isValid())
321        return nil;
322
323    // VoiceOver asks for the value attribute for all elements. Return nil
324    // if we don't want the element to have a value attribute.
325    if (!QCocoaAccessible::hasValueAttribute(iface))
326        return nil;
327
328    return QCocoaAccessible::getValueAttribute(iface);
329}
330
331- (NSInteger) accessibilityNumberOfCharacters {
332    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
333    if (!iface || !iface->isValid())
334        return 0;
335    if (QAccessibleTextInterface *text = iface->textInterface())
336        return text->characterCount();
337    return 0;
338}
339
340- (NSString *) accessibilitySelectedText {
341    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
342    if (!iface || !iface->isValid())
343        return nil;
344    if (QAccessibleTextInterface *text = iface->textInterface()) {
345        int start = 0;
346        int end = 0;
347        text->selection(0, &start, &end);
348        return text->text(start, end).toNSString();
349    }
350    return nil;
351}
352
353- (NSRange) accessibilitySelectedTextRange {
354    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
355    if (!iface || !iface->isValid())
356        return NSRange();
357    if (QAccessibleTextInterface *text = iface->textInterface()) {
358        int start = 0;
359        int end = 0;
360        if (text->selectionCount() > 0) {
361            text->selection(0, &start, &end);
362        } else {
363            start = text->cursorPosition();
364            end = start;
365        }
366        return NSMakeRange(quint32(start), quint32(end - start));
367    }
368    return NSMakeRange(0, 0);
369}
370
371- (NSInteger)accessibilityLineForIndex:(NSInteger)index {
372    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
373    if (!iface || !iface->isValid())
374        return 0;
375    if (QAccessibleTextInterface *text = iface->textInterface()) {
376        QString textToPos = text->text(0, index);
377        return textToPos.count('\n');
378    }
379    return 0;
380}
381
382- (NSRange)accessibilityVisibleCharacterRange {
383    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
384    if (!iface || !iface->isValid())
385        return NSRange();
386    // FIXME This is not correct and may impact performance for big texts
387    if (QAccessibleTextInterface *text = iface->textInterface())
388        return NSMakeRange(0, static_cast<uint>(text->characterCount()));
389    return NSMakeRange(0, static_cast<uint>(iface->text(QAccessible::Name).length()));
390}
391
392- (NSInteger) accessibilityInsertionPointLineNumber {
393    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
394    if (!iface || !iface->isValid())
395        return 0;
396    if (QAccessibleTextInterface *text = iface->textInterface()) {
397        int position = text->cursorPosition();
398        return [self accessibilityLineForIndex:position];
399    }
400    return 0;
401}
402
403- (NSArray *)accessibilityParameterizedAttributeNames {
404
405    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
406    if (!iface || !iface->isValid()) {
407        qWarning() << "Called attribute on invalid object: " << axid;
408        return nil;
409    }
410
411    if (iface->textInterface()) {
412        return @[
413            NSAccessibilityStringForRangeParameterizedAttribute,
414            NSAccessibilityLineForIndexParameterizedAttribute,
415            NSAccessibilityRangeForLineParameterizedAttribute,
416            NSAccessibilityRangeForPositionParameterizedAttribute,
417//          NSAccessibilityRangeForIndexParameterizedAttribute,
418            NSAccessibilityBoundsForRangeParameterizedAttribute,
419//          NSAccessibilityRTFForRangeParameterizedAttribute,
420            NSAccessibilityStyleRangeForIndexParameterizedAttribute,
421            NSAccessibilityAttributedStringForRangeParameterizedAttribute
422        ];
423    }
424
425    return nil;
426}
427
428- (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter {
429    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
430    if (!iface || !iface->isValid()) {
431        qWarning() << "Called attribute on invalid object: " << axid;
432        return nil;
433    }
434
435    if (!iface->textInterface())
436        return nil;
437
438    if ([attribute isEqualToString: NSAccessibilityStringForRangeParameterizedAttribute]) {
439        NSRange range = [parameter rangeValue];
440        QString text = iface->textInterface()->text(range.location, range.location + range.length);
441        return text.toNSString();
442    }
443    if ([attribute isEqualToString: NSAccessibilityLineForIndexParameterizedAttribute]) {
444        int index = [parameter intValue];
445        if (index < 0 || index > iface->textInterface()->characterCount())
446            return nil;
447        int line = 0; // true for all single line edits
448        if (iface->state().multiLine) {
449            line = -1;
450            convertLineOffset(iface->textInterface(), &line, &index);
451        }
452        return @(line);
453    }
454    if ([attribute isEqualToString: NSAccessibilityRangeForLineParameterizedAttribute]) {
455        int line = [parameter intValue];
456        if (line < 0)
457            return nil;
458        int lineOffset = -1;
459        NSUInteger startOffset = 0;
460        NSUInteger endOffset = 0;
461        convertLineOffset(iface->textInterface(), &line, &lineOffset, &startOffset, &endOffset);
462        return [NSValue valueWithRange:NSMakeRange(startOffset, endOffset - startOffset)];
463    }
464    if ([attribute isEqualToString: NSAccessibilityBoundsForRangeParameterizedAttribute]) {
465        NSRange range = [parameter rangeValue];
466        QRect firstRect = iface->textInterface()->characterRect(range.location);
467        QRectF rect;
468        if (range.length > 0) {
469            NSUInteger position = range.location + range.length - 1;
470            if (position > range.location && iface->textInterface()->text(position, position + 1) == QStringLiteral("\n"))
471                --position;
472            QRect lastRect = iface->textInterface()->characterRect(position);
473            rect = firstRect.united(lastRect);
474        } else {
475            rect = firstRect;
476            rect.setWidth(1);
477        }
478        return [NSValue valueWithRect:QCocoaScreen::mapToNative(rect)];
479    }
480    if ([attribute isEqualToString: NSAccessibilityAttributedStringForRangeParameterizedAttribute]) {
481        NSRange range = [parameter rangeValue];
482        QString text = iface->textInterface()->text(range.location, range.location + range.length);
483        return [[NSAttributedString alloc] initWithString:text.toNSString()];
484    } else if ([attribute isEqualToString: NSAccessibilityRangeForPositionParameterizedAttribute]) {
485        QPoint point = QCocoaScreen::mapFromNative([parameter pointValue]).toPoint();
486        int offset = iface->textInterface()->offsetAtPoint(point);
487        return [NSValue valueWithRange:NSMakeRange(static_cast<NSUInteger>(offset), 1)];
488    } else if ([attribute isEqualToString: NSAccessibilityStyleRangeForIndexParameterizedAttribute]) {
489        int start = 0;
490        int end = 0;
491        iface->textInterface()->attributes([parameter intValue], &start, &end);
492        return [NSValue valueWithRange:NSMakeRange(static_cast<NSUInteger>(start), static_cast<NSUInteger>(end - start))];
493    }
494    return nil;
495}
496
497- (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
498    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
499    if (!iface || !iface->isValid())
500        return NO;
501
502    if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
503        return iface->state().focusable ? YES : NO;
504    } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
505        if (iface->textInterface() && iface->state().editable)
506            return YES;
507        if (iface->valueInterface())
508            return YES;
509        return NO;
510    } else if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
511        return iface->textInterface() ? YES : NO;
512    }
513    return NO;
514}
515
516- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute {
517    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
518    if (!iface || !iface->isValid())
519        return;
520    if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
521        if (QAccessibleActionInterface *action = iface->actionInterface())
522            action->doAction(QAccessibleActionInterface::setFocusAction());
523    } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
524        if (iface->textInterface()) {
525            QString text = QString::fromNSString((NSString *)value);
526            iface->setText(QAccessible::Value, text);
527        } else if (QAccessibleValueInterface *valueIface = iface->valueInterface()) {
528            double val = [value doubleValue];
529            valueIface->setCurrentValue(val);
530        }
531    } else if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
532        if (QAccessibleTextInterface *text = iface->textInterface()) {
533            NSRange range = [value rangeValue];
534            if (range.length > 0)
535                text->setSelection(0, range.location, range.location + range.length);
536            else
537                text->setCursorPosition(range.location);
538        }
539    }
540}
541
542// actions
543
544- (NSArray *)accessibilityActionNames {
545    NSMutableArray *nsActions = [[NSMutableArray new] autorelease];
546    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
547    if (!iface || !iface->isValid())
548        return nsActions;
549
550    const QStringList &supportedActionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
551    for (const QString &qtAction : supportedActionNames) {
552        NSString *nsAction = QCocoaAccessible::getTranslatedAction(qtAction);
553        if (nsAction)
554            [nsActions addObject : nsAction];
555    }
556
557    return nsActions;
558}
559
560- (NSString *)accessibilityActionDescription:(NSString *)action {
561    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
562    if (!iface || !iface->isValid())
563        return nil; // FIXME is that the right return type??
564    QString qtAction = QCocoaAccessible::translateAction(action, iface);
565    QString description;
566    // Return a description from the action interface if this action is not known to the OS.
567    if (qtAction.isEmpty()) {
568        if (QAccessibleActionInterface *actionInterface = iface->actionInterface()) {
569            qtAction = QString::fromNSString((NSString *)action);
570            description = actionInterface->localizedActionDescription(qtAction);
571        }
572    } else {
573        description = qAccessibleLocalizedActionDescription(qtAction);
574    }
575    return description.toNSString();
576}
577
578- (void)accessibilityPerformAction:(NSString *)action {
579    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
580    if (iface && iface->isValid()) {
581        const QString qtAction = QCocoaAccessible::translateAction(action, iface);
582        QAccessibleBridgeUtils::performEffectiveAction(iface, qtAction);
583    }
584}
585
586// misc
587
588- (BOOL)accessibilityIsIgnored {
589    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
590    if (!iface || !iface->isValid())
591        return true;
592    return QCocoaAccessible::shouldBeIgnored(iface);
593}
594
595- (id)accessibilityHitTest:(NSPoint)point {
596    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
597    if (!iface || !iface->isValid()) {
598//        qDebug("Hit test: INVALID");
599        return NSAccessibilityUnignoredAncestor(self);
600    }
601
602    QPointF screenPoint = QCocoaScreen::mapFromNative(point);
603    QAccessibleInterface *childInterface = iface->childAt(screenPoint.x(), screenPoint.y());
604    // No child found, meaning we hit this element.
605    if (!childInterface || !childInterface->isValid())
606        return NSAccessibilityUnignoredAncestor(self);
607
608    // find the deepest child at the point
609    QAccessibleInterface *childOfChildInterface = nullptr;
610    do {
611        childOfChildInterface = childInterface->childAt(screenPoint.x(), screenPoint.y());
612        if (childOfChildInterface && childOfChildInterface->isValid())
613            childInterface = childOfChildInterface;
614    } while (childOfChildInterface && childOfChildInterface->isValid());
615
616    QAccessible::Id childId = QAccessible::uniqueId(childInterface);
617    // hit a child, forward to child accessible interface.
618    QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithId:childId];
619    if (accessibleElement)
620        return NSAccessibilityUnignoredAncestor(accessibleElement);
621    return NSAccessibilityUnignoredAncestor(self);
622}
623
624- (id)accessibilityFocusedUIElement {
625    QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
626
627    if (!iface || !iface->isValid()) {
628        qWarning("FocusedUIElement for INVALID");
629        return nil;
630    }
631
632    QAccessibleInterface *childInterface = iface->focusChild();
633    if (childInterface && childInterface->isValid()) {
634        QAccessible::Id childAxid = QAccessible::uniqueId(childInterface);
635        QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithId:childAxid];
636        return NSAccessibilityUnignoredAncestor(accessibleElement);
637    }
638
639    return NSAccessibilityUnignoredAncestor(self);
640}
641
642@end
643
644#endif // QT_NO_ACCESSIBILITY
645