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

В предыдущей заметке я писал, что обнаружил сенсор внезапного движения у своего «Макбука». Решил извлечь практическую пользу из этого — написал программу на Си, которая отправляет ноут в спячку, стоит поднять его со стола:
// 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);
}
19 мая 2013 11:04

malinnikov (malinnikov.livejournal.com)
19 мая 2013, 15:09

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

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

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

Евгений Степанищев (bolknote.ru)
19 мая 2013, 16:11, ответ предназначен malinnikov (malinnikov.livejournal.com):

Клево. float dev от каких-то экспериментов осталась? Нигде не используется вроде.
Ага, сейчас удалю.
Кстати, а почему (*out).z вместо out->z?
А почему нет? Я разницы не вижу :)
И out = malloc(size) без приведения к (ostruct *) цепляет глаз.
Компилятор не ругается же :)

Леша (инкогнито)
19 мая 2013, 18:27

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

Евгений Степанищев (bolknote.ru)
19 мая 2013, 18:37, ответ предназначен Леша

http://bolknote.ru/2011/10/29/~3461/#n32882

Igor M Podlesny (poige.livejournal.com)
19 мая 2013, 20:01, ответ предназначен Евгений Степанищев (bolknote.ru):

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

Igor M Podlesny (poige.livejournal.com)
19 мая 2013, 20:10

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

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

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

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

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

Евгений Степанищев (bolknote.ru)
19 мая 2013, 20:35, ответ предназначен Igor M Podlesny (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)
19 мая 2013, 20:59, ответ предназначен Евгений Степанищев (bolknote.ru):

Обычно макросы, где более одного оператора, помещают в обёртки, типа
«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)
19 мая 2013, 21:23, ответ предназначен Igor M Podlesny (poige.livejournal.com):

Обычно макросы, где более одного оператора, помещают в обёртки, типа
«do { … } while(0)». Если макрос начинается с if, то его дополняют до else — для инвариантности, так сказать
Понятно, спасибо! Принято всегда добиваться такой универсальности макроса? Или есть случаи, когда с этим можно не заморачиваться.
Что касается malloc(), то отсутствие таковой проверки ещё более неаккуратно, чем error printf в stdout. Если не хочется заморчиваться каждый раз с ней, делается макрос, который просто вызовет exit. Но не проверять вовсе — плохая практика, однозначно.
Мне показалось, что в условиях, когда у системы нет 80 байт, система работать просто не будет, поэтому можно не заморачиваться с такой мелочью. Но раз это серьёзная ошибка, сейчас добавлю код.
static внутри функции это такой класс хранения, а не видимости, в первую очередь. Зачем гарантировать хранение локальных переменных main()…
Меня в университете учили относиться к main как к обычной, любой другой функции. В т.ч. такой, которая может быть вызвана откуда-то ещё. Это неправильно?

Igor M Podlesny (poige.livejournal.com)
20 мая 2013, 04:20, ответ предназначен Евгений Степанищев (bolknote.ru):

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

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

Евгений Степанищев (bolknote.ru)
20 мая 2013, 06:14, ответ предназначен Igor M Podlesny (poige.livejournal.com):

Вообще говоря, если в тексте программы используются свободные определения переменных внутри кода, то проще решить для себя, что пишешь на Си++, и не заморачиваться с макросами … Почему-то считается, что Си++ это что-то зверское и дико сложное.
Си++ страшненький. Его в руки-то брать не хочется.
Что касается main() и static var внутри неё. Какая разница о чём «говорили в универе», если понимания что такое static не осталось?
Понимание у меня осталось такое: под статики в машинном коде прямо в этом месте забивается пространство под переменную, прямо в скомпилированном коде (всякие там DB, DW и прочие в Ассемблере). В случае функции всё то же самое — забивается место под указатель на неё.

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

Евгений Степанищев (bolknote.ru)
20 мая 2013, 06:16, ответ предназначен Igor M Podlesny (poige.livejournal.com):

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

Igor M Podlesny (poige.livejournal.com)
20 мая 2013, 06:59, ответ предназначен Евгений Степанищев (bolknote.ru):

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

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

Igor M Podlesny (poige.livejournal.com)
20 мая 2013, 07:01, ответ предназначен Евгений Степанищев (bolknote.ru):

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

Евгений Степанищев (bolknote.ru)
20 мая 2013, 07:13, ответ предназначен Igor M Podlesny (poige.livejournal.com):

Ну я имел в виду, что если уж заимствовать одну из удобных фич Cи++
Так я и не заимствую. Произвольный порядок определения есть в Си (в Си99 появилось).
…если уж заимствовать одну из удобных фич Cи++, то почему бы не решиться и не изменить расширение на .cc, после чего использовать другие удобства.
Проще поменять расширение на «.go» и продолжить программировать на более удобном языке :)
В случае с функцией она становится исключительно локальной для файла, в котором объявлена — обратиться к ней извне нельзя.
И правда надо будет почитать учебник всё-таки. Хотя я редко использую Си, но иногда всё-таки использую.

malinnikov (malinnikov.livejournal.com)
20 мая 2013, 08:05, ответ предназначен Евгений Степанищев (bolknote.ru):

> И out = malloc(size) без приведения к (ostruct *) цепляет глаз.
Компилятор не ругается же :)
Ну раз совместимость с C++ не планируется, то оно и не надо.

Igor M Podlesny (poige.livejournal.com)
20 мая 2013, 09:25, ответ предназначен Евгений Степанищев (bolknote.ru):

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

Igor M Podlesny (poige.livejournal.com)
20 мая 2013, 09:27, ответ предназначен Евгений Степанищев (bolknote.ru):

Проще поменять расширение на «.go» и продолжить программировать на более удобном языке :)
Возможно. Но .cc всё-таки куда ближе к .c… (если это роляет, конечно).

Евгений Степанищев (bolknote.ru)
20 мая 2013, 12:35

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

Igor M Podlesny (poige.livejournal.com)
20 мая 2013, 13:35

Выложил на Гитхаб: https://github.com/bolknote/macgreener
Хм, а почему у shell-скрипта расширение .c? )

Евгений Степанищев (bolknote.ru)
20 мая 2013, 13:40, ответ предназначен Igor M Podlesny (poige.livejournal.com):

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

Igor M Podlesny (poige.livejournal.com)
20 мая 2013, 13:48

Потому что это не шелл-скрипт, а программа на Си.
А с каких пор программы на си начинаются с #!/bin/bash ? )

Евгений Степанищев (bolknote.ru)
20 мая 2013, 13:56, ответ предназначен Igor M Podlesny (poige.livejournal.com):

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

Igor M Podlesny (poige.livejournal.com)
21 мая 2013, 06:22, ответ предназначен Евгений Степанищев (bolknote.ru):

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

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

Евгений Степанищев (bolknote.ru)
21 мая 2013, 08:04, ответ предназначен Igor M Podlesny (poige.livejournal.com):

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

Евгений Степанищев (bolknote.ru)
21 мая 2013, 08:10, ответ предназначен Igor M Podlesny (poige.livejournal.com):

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

Артём (инкогнито)
29 мая 2013, 03:54

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

Ваше имя или адрес блога (можно OpenID):

Текст вашего комментария, не HTML:

Кому бы вы хотели ответить (или кликните на его аватару)