Пишу, по большей части, про историю, свою жизнь и немного про программирование.

Берём «Макбук» со стола, экран гаснет или блокируется

В предыдущей заметке я писал, что обнаружил сенсор внезапного движения у своего «Макбука». Решил извлечь практическую пользу из этого — написал программу на Си, которая отправляет ноут в спячку, стоит поднять его со стола:

// gcc -framework CoreFoundation -framework IOKit  gethensleep.c -o getthensleep
// Evgeny Stepanischev May 2013
#include <IOKit/IOKitLib.h>
#include <CoreFoundation/CoreFoundation.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/param.h>

#define RETURNONFAIL(code) do { if (errcode != KERN_SUCCESS) {\
    fprintf(stderr, "Error code: 0x%Xn", GETCODE(errcode));\
    return code;\
} } while (0)


#define GETCODE(err) ((err)&0x3fff)

#define THRESHOLD 0.2

typedef struct {
    int16_t x;
    int16_t y;
    int16_t z;
} ostruct;

void macSleep() {
    io_registry_entry_t r = IORegistryEntryFromPath(
        kIOMasterPortDefault,
        "IOService:/IOResources/IODisplayWrangler"
    );

    if (r) {
        IORegistryEntrySetCFProperty(r, CFSTR("IORequestIdle"), kCFBooleanTrue);
        IOObjectRelease(r);
    }
}

int main() {
    mach_port_t masterPort;
    kern_return_t errcode;
    io_iterator_t iterator;
    io_object_t service;
    io_connect_t connect;

    void *in;
    ostruct *out;
    size_t osize = 40, isize = 40;

    errcode = IOMasterPort(MACH_PORT_NULL, &masterPort);
    RETURNONFAIL(-1);

    CFMutableDictionaryRef matchingDictionary = IOServiceMatching("SMCMotionSensor");

    errcode = IOServiceGetMatchingServices(masterPort, matchingDictionary, &iterator);
    RETURNONFAIL(-2);

    service = IOIteratorNext(iterator);
    IOObjectRelease(iterator);

    if (service == kIOReturnNoDevice) {
        return -3;
    }

    errcode = IOServiceOpen(service, mach_task_self(), 0, &connect);
    IOObjectRelease(service);
    RETURNONFAIL(-4);

    in = malloc(isize);
    out = malloc(osize);

    if (in == NULL || out == NULL) {
        return -6;
    }

    memset(out, 0, osize);
    memset(in, 1, isize);

    int16_t prevz;
    bool inited = false;

    for(;;) {
        errcode = IOConnectCallStructMethod(connect, 5, in, osize, out, &isize);
        RETURNONFAIL(-5);

        if (inited) {
            float min = MIN(prevz, (*out).z);
            float max = MAX(prevz, (*out).z);

            if (max) {
                float dev = 1 - min / max;

                if (dev > THRESHOLD) {
                    macSleep();
                }
            }
        } else {
            inited = true;
        }

        prevz = (*out).z;
        usleep(10000);
    }

    return 0;
}

Порог срабатывания (0.2) я подобрал экспериментальным путём. Скомпилировать можно компилятором gcc (входит в XCode), строка компиляции указана первой в приведёном коде. Можно засунуть в автозагрузку и наслаждаться эффектом.

Кстати, если хотите, то можно вместо спячки заставить ноут заблокировать экран (т. е. показать экран логина), для этого надо вместо функции macSleep вызвать другую:

// нужно добавить ещё один ключ компиляции: -framework ApplicationServices
#include <ApplicationServices/ApplicationServices.h>

void macLock () {
    CGSCreateLoginSession(NULL);
}
27 комментариев
malinnikov (malinnikov.livejournal.com) 2013

Клево. float dev от каких-то экспериментов осталась? Нигде не используется вроде.

Кстати, а почему (*out).z вместо out->z?

И out = malloc(size) без приведения к (ostruct *) цепляет глаз.

Евгений Степанищев (bolknote.ru) 2013

Комментарий для malinnikov.livejournal.com:

Клево. float dev от каких-то экспериментов осталась? Нигде не используется вроде.

Ага, сейчас удалю.

Кстати, а почему (*out).z вместо out->z?

А почему нет? Я разницы не вижу :)

И out = malloc(size) без приведения к (ostruct *) цепляет глаз.

Компилятор не ругается же :)

Леша 2013

А почему вы пишете «XCode», а не (например) «ИксКоуд»?

Евгений Степанищев (bolknote.ru) 2013

Комментарий для Леша:

http://bolknote.ru/all/3461/#n32882

Igor M Podlesny (poige.livejournal.com) 2013

Комментарий для Евгения Степанищева:

И зачем же эта куча static в main?

Igor M Podlesny (poige.livejournal.com) 2013

Ну и ещё поворчать:

1) EXITONFAIL назван неудачно, поскольку там не exit(), а return

2) Если макрос начинается с if, и в нём отсутствует else, то его стоит поместить искусственно: if (foo) { bar } else

3) Диагностику принято выводить в stderr — fprintf(stder, …)

4) Глаз режет не приведение типа malloc (достаточно бессмысленное занятие, нужно сказать), а отсутствие проверки рез-та на NULL.

Евгений Степанищев (bolknote.ru) 2013

Комментарий для poige.livejournal.com:

И зачем же эта куча static в main?

Я Си не очень-то хорошо знаю, в последний раз системно учил его в Университете, лет 15 назад, с тех пор многое позабылось. Можете мне рассказать где и для чего ставится это ключевое слово, развеете мои заблуждения.

EXITONFAIL назван неудачно, поскольку там не exit(), а return

Согласен, переименовал.

Если макрос начинается с if, и в нём отсутствует else, то его стоит поместить искусственно: if (foo) { bar } else

А это зачем? Там дальше логика не предусматривает, что из макроса будет торчать else.

Диагностику принято выводить в stderr — fprintf(stder, …)

Действительно, неаккуратно, поправил.

Глаз режет не приведение типа malloc (достаточно бессмысленное занятие, нужно сказать), а отсутствие проверки рез-та на NULL.

В смысле, проверки, что в системе не нашлось 80 байт? Не перестраховка?

Igor M Podlesny (poige.livejournal.com) 2013

Комментарий для Евгения Степанищева:

Обычно макросы, где более одного оператора, помещают в обёртки, типа
«do { … } while(0)». Если макрос начинается с if, то его дополняют до else — для инвариантности, так сказать. В противном случае:

#define Pitfall() if () { … }

if (…)
   Pitfall()
else

— else будет соотнесён к тому if, что в раскроется из макроса, а не к первому. Если Pitfall будет с else, этого не произойдёт.

Что касается malloc(), то отсутствие таковой проверки ещё более неаккуратно, чем error printf в stdout. Если не хочется заморчиваться каждый раз с ней, делается макрос, который просто вызовет exit. Но не проверять вовсе — плохая практика, однозначно.

static внутри функции это такой класс хранения, а не видимости, в первую очередь. Зачем гарантировать хранение локальных переменных main(), пока выполняется программа, если и без static они будут точно так же храниться, неясно абсолютно. Короче — просто нечто бессмысленное.

Евгений Степанищев (bolknote.ru) 2013

Комментарий для poige.livejournal.com:

Обычно макросы, где более одного оператора, помещают в обёртки, типа
«do { … } while(0)». Если макрос начинается с if, то его дополняют до else — для инвариантности, так сказать

Понятно, спасибо! Принято всегда добиваться такой универсальности макроса? Или есть случаи, когда с этим можно не заморачиваться.

Что касается malloc(), то отсутствие таковой проверки ещё более неаккуратно, чем error printf в stdout. Если не хочется заморчиваться каждый раз с ней, делается макрос, который просто вызовет exit. Но не проверять вовсе — плохая практика, однозначно.

Мне показалось, что в условиях, когда у системы нет 80 байт, система работать просто не будет, поэтому можно не заморачиваться с такой мелочью. Но раз это серьёзная ошибка, сейчас добавлю код.

static внутри функции это такой класс хранения, а не видимости, в первую очередь. Зачем гарантировать хранение локальных переменных main()…

Меня в университете учили относиться к main как к обычной, любой другой функции. В т.ч. такой, которая может быть вызвана откуда-то ещё. Это неправильно?

Igor M Podlesny (poige.livejournal.com) 2013

Комментарий для Евгения Степанищева:

Вообще говоря, если в тексте программы используются свободные определения переменных внутри кода, то проще решить для себя, что пишешь на C++, и не заморачиваться с макросами, а использовать те же template’ы, к примеру. Я вообще не понимаю стремления людей продолжать использовать C, когда во многих случаях C++ — то, что доктор прописал, — на нём код понятнее, надёжнее. Почему-то считается, что C++ это что-то зверское и дико сложное. Это не так (для большинства подобных программ). Как уже ни раз говорилось многими, C++ можно использовать просто как улучшенный C. Не придётся париться с проверками «нам нехватило памяти» в каждом вызове malloc, а обработать соответствующее исключение (ну или не обработать), ну и всё такое прочее.

Что касается main() и static var внутри неё. Какая разница о чём «говорили в универе», если понимания что такое static не осталось? Не static-переменные объявленные внутри функции хранятся в стеке. Static — похожи на глобальные (по сроку жизни и способу хранения), но, при этом, недоступны извне функции (scope). А ещё есть атрибут static у функции, но смысл у него совсем другой. Так что если хочется разобраться действительно, нужно просто открыть учебник.

Евгений Степанищев (bolknote.ru) 2013

Комментарий для poige.livejournal.com:

Вообще говоря, если в тексте программы используются свободные определения переменных внутри кода, то проще решить для себя, что пишешь на Си++, и не заморачиваться с макросами … Почему-то считается, что Си++ это что-то зверское и дико сложное.

Си++ страшненький. Его в руки-то брать не хочется.

Что касается main() и static var внутри неё. Какая разница о чём «говорили в универе», если понимания что такое static не осталось?

Понимание у меня осталось такое: под статики в машинном коде прямо в этом месте забивается пространство под переменную, прямо в скомпилированном коде (всякие там DB, DW и прочие в Ассемблере). В случае функции всё то же самое — забивается место под указатель на неё.

Наверное, действительно надо перечитать Си просто. Спасибо вам за замечания к коду!

Евгений Степанищев (bolknote.ru) 2013

Комментарий для poige.livejournal.com:

Только это место я не понял:

Вообще говоря, если в тексте программы используются свободные определения переменных внутри кода … проще решить для себя, что пишешь на Си++

Это звучит как «если внутри кода используются итераторы, проще решить, что пишешь на Пайтоне». Не понимаю в чём связь между свободными определениями в Си и Си++, кроме того, что в обоих языках это можно делать.

Igor M Podlesny (poige.livejournal.com) 2013

Комментарий для Евгения Степанищева:

Ну я имел в виду, что если уж заимствовать одну из удобных фич C++, то почему бы не решиться и не изменить расширение на .cc, после чего использовать другие удобства. Я не помню в какой книжке читал это, но там вроде бы так глава и называлась — «C++ как расширенный C». В C++ на самом деле много чего такого, что ещё не «сносит башню», не уходит в ООП, но зато существенно упрощает написание программ. Тот же самый new появился как ответ на проблему того, что многие программисты заб{и,ы}вают проверять коды ошибок malloc.

Я помню один проект с callback’ами, и вечным void *, а ведь если бы использовать те же самые классы (и их иерархию), то проще было бы обнаруживать ошибки. Просто раньше это были отмазки в духе «компиляторы C++ глючат круче, чем C’ишные», или «у нас embedded», но спустя десятилетия это уже несколько нелепо.

Igor M Podlesny (poige.livejournal.com) 2013

Комментарий для Евгения Степанищева:

В случае функции всё то же самое — забивается место под указатель на неё.

В случае с функцией она становится исключительно локальной для файла, в котором объявлена — обратиться к ней извне нельзя.

Евгений Степанищев (bolknote.ru) 2013

Комментарий для poige.livejournal.com:

Ну я имел в виду, что если уж заимствовать одну из удобных фич Cи++

Так я и не заимствую. Произвольный порядок определения есть в Си (в Си99 появилось).

…если уж заимствовать одну из удобных фич Cи++, то почему бы не решиться и не изменить расширение на .cc, после чего использовать другие удобства.

Проще поменять расширение на «.go» и продолжить программировать на более удобном языке :)

В случае с функцией она становится исключительно локальной для файла, в котором объявлена — обратиться к ней извне нельзя.

И правда надо будет почитать учебник всё-таки. Хотя я редко использую Си, но иногда всё-таки использую.

malinnikov (malinnikov.livejournal.com) 2013

Комментарий для Евгения Степанищева:

И out = malloc(size) без приведения к (ostruct *) цепляет глаз.

Компилятор не ругается же :)

Ну раз совместимость с C++ не планируется, то оно и не надо.

Igor M Podlesny (poige.livejournal.com) 2013

Комментарий для Евгения Степанищева:

Так я и не заимствую. Произвольный порядок определения есть в Си (в Си99 появилось).

Угу. Но там он появился после C++. Вот поэтому я и говорю — вместо того, чтобы побираться по крохам, лучше просто переименовать файл в .cc, и не париться. ;)

Igor M Podlesny (poige.livejournal.com) 2013

Комментарий для Евгения Степанищева:

Проще поменять расширение на «.go» и продолжить программировать на более удобном языке :)

Возможно. Но .cc всё-таки куда ближе к .c… (если это роляет, конечно).

Евгений Степанищев (bolknote.ru) 2013

Выложил на Гитхаб: https://github.com/bolknote/macgreener

Igor M Podlesny (poige.livejournal.com) 2013

Выложил на Гитхаб: https://github.com/bolknote/macgreener

Хм, а почему у shell-скрипта расширение .c? )

Евгений Степанищев (bolknote.ru) 2013

Комментарий для poige.livejournal.com:

Потому что это не шелл-скрипт, а программа на Си.

Igor M Podlesny (poige.livejournal.com) 2013

Потому что это не шелл-скрипт, а программа на Си.

А с каких пор программы на си начинаются с #!/bin/bash ? )

Евгений Степанищев (bolknote.ru) 2013

Комментарий для poige.livejournal.com:

Всё что ниже — программа, мне так удобно компилировать :)

Igor M Podlesny (poige.livejournal.com) 2013

Комментарий для Евгения Степанищева:

Всё что ниже — программа, мне так удобно компилировать :)

Странный довод — так можно и задницу занавеской вытирать… и «бинд» вместо «байнд» говорить (на радость англоязычным коллегам, чей родной язык и стал IT-стандартом де-факто).

Нормальные люди для этого Makefile используют, ну или банальный SHELL-скрипт отдельный. И я правильно понимаю, что сообщения об ошибках, указывают на сдвинутые номера строк, ага?

Евгений Степанищев (bolknote.ru) 2013

Комментарий для poige.livejournal.com:

Странный довод — так можно и задницу занавеской вытирать

Можно, если это моя занавеска и моя задница, то в чём проблема?

Евгений Степанищев (bolknote.ru) 2013

Комментарий для poige.livejournal.com:

Нормальные люди для этого Makefile используют, ну или банальный SHELL-скрипт отдельный.

Если бы я равнялся на нормальных людей, то бы сейчас пил в подворотне и не решал задачи, перед которыми остальные пасуют.

И я правильно понимаю, что сообщения об ошибках, указывают на сдвинутые номера строк, ага?

Нет, конечно. С чего бы это?

Артём 2013

А раньше сигнализации писали под это дело.