Берём «Макбук» со стола, экран гаснет или блокируется
В предыдущей заметке я писал, что обнаружил сенсор внезапного движения у своего «Макбука». Решил извлечь практическую пользу из этого — написал программу на Си, которая отправляет ноут в спячку, стоит поднять его со стола:
// 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);
}
Клево. float dev от каких-то экспериментов осталась? Нигде не используется вроде.
Кстати, а почему (*out).z вместо out->z?
И out = malloc(size) без приведения к (ostruct *) цепляет глаз.
Комментарий для malinnikov.livejournal.com:
Ага, сейчас удалю.
А почему нет? Я разницы не вижу :)
Компилятор не ругается же :)
А почему вы пишете «XCode», а не (например) «ИксКоуд»?
Комментарий для Леша:
http://bolknote.ru/all/3461/#n32882
Комментарий для Евгения Степанищева:
И зачем же эта куча static в main?
Ну и ещё поворчать:
1) EXITONFAIL назван неудачно, поскольку там не exit(), а return
2) Если макрос начинается с if, и в нём отсутствует else, то его стоит поместить искусственно: if (foo) { bar } else
3) Диагностику принято выводить в stderr — fprintf(stder, …)
4) Глаз режет не приведение типа malloc (достаточно бессмысленное занятие, нужно сказать), а отсутствие проверки рез-та на NULL.
Комментарий для poige.livejournal.com:
Я Си не очень-то хорошо знаю, в последний раз системно учил его в Университете, лет 15 назад, с тех пор многое позабылось. Можете мне рассказать где и для чего ставится это ключевое слово, развеете мои заблуждения.
Согласен, переименовал.
А это зачем? Там дальше логика не предусматривает, что из макроса будет торчать else.
Действительно, неаккуратно, поправил.
В смысле, проверки, что в системе не нашлось 80 байт? Не перестраховка?
Комментарий для Евгения Степанищева:
Обычно макросы, где более одного оператора, помещают в обёртки, типа
«do { … } while(0)». Если макрос начинается с if, то его дополняют до else — для инвариантности, так сказать. В противном случае:
#define Pitfall() if () { … }
if (…)
Pitfall()
else
— else будет соотнесён к тому if, что в раскроется из макроса, а не к первому. Если Pitfall будет с else, этого не произойдёт.
Что касается malloc(), то отсутствие таковой проверки ещё более неаккуратно, чем error printf в stdout. Если не хочется заморчиваться каждый раз с ней, делается макрос, который просто вызовет exit. Но не проверять вовсе — плохая практика, однозначно.
static внутри функции это такой класс хранения, а не видимости, в первую очередь. Зачем гарантировать хранение локальных переменных main(), пока выполняется программа, если и без static они будут точно так же храниться, неясно абсолютно. Короче — просто нечто бессмысленное.
Комментарий для poige.livejournal.com:
Понятно, спасибо! Принято всегда добиваться такой универсальности макроса? Или есть случаи, когда с этим можно не заморачиваться.
Мне показалось, что в условиях, когда у системы нет 80 байт, система работать просто не будет, поэтому можно не заморачиваться с такой мелочью. Но раз это серьёзная ошибка, сейчас добавлю код.
Меня в университете учили относиться к main как к обычной, любой другой функции. В т.ч. такой, которая может быть вызвана откуда-то ещё. Это неправильно?
Комментарий для Евгения Степанищева:
Вообще говоря, если в тексте программы используются свободные определения переменных внутри кода, то проще решить для себя, что пишешь на C++, и не заморачиваться с макросами, а использовать те же template’ы, к примеру. Я вообще не понимаю стремления людей продолжать использовать C, когда во многих случаях C++ — то, что доктор прописал, — на нём код понятнее, надёжнее. Почему-то считается, что C++ это что-то зверское и дико сложное. Это не так (для большинства подобных программ). Как уже ни раз говорилось многими, C++ можно использовать просто как улучшенный C. Не придётся париться с проверками «нам нехватило памяти» в каждом вызове malloc, а обработать соответствующее исключение (ну или не обработать), ну и всё такое прочее.
Что касается main() и static var внутри неё. Какая разница о чём «говорили в универе», если понимания что такое static не осталось? Не static-переменные объявленные внутри функции хранятся в стеке. Static — похожи на глобальные (по сроку жизни и способу хранения), но, при этом, недоступны извне функции (scope). А ещё есть атрибут static у функции, но смысл у него совсем другой. Так что если хочется разобраться действительно, нужно просто открыть учебник.
Комментарий для poige.livejournal.com:
Си++ страшненький. Его в руки-то брать не хочется.
Понимание у меня осталось такое: под статики в машинном коде прямо в этом месте забивается пространство под переменную, прямо в скомпилированном коде (всякие там DB, DW и прочие в Ассемблере). В случае функции всё то же самое — забивается место под указатель на неё.
Наверное, действительно надо перечитать Си просто. Спасибо вам за замечания к коду!
Комментарий для poige.livejournal.com:
Только это место я не понял:
Это звучит как «если внутри кода используются итераторы, проще решить, что пишешь на Пайтоне». Не понимаю в чём связь между свободными определениями в Си и Си++, кроме того, что в обоих языках это можно делать.
Комментарий для Евгения Степанищева:
Ну я имел в виду, что если уж заимствовать одну из удобных фич C++, то почему бы не решиться и не изменить расширение на .cc, после чего использовать другие удобства. Я не помню в какой книжке читал это, но там вроде бы так глава и называлась — «C++ как расширенный C». В C++ на самом деле много чего такого, что ещё не «сносит башню», не уходит в ООП, но зато существенно упрощает написание программ. Тот же самый new появился как ответ на проблему того, что многие программисты заб{и,ы}вают проверять коды ошибок malloc.
Я помню один проект с callback’ами, и вечным void *, а ведь если бы использовать те же самые классы (и их иерархию), то проще было бы обнаруживать ошибки. Просто раньше это были отмазки в духе «компиляторы C++ глючат круче, чем C’ишные», или «у нас embedded», но спустя десятилетия это уже несколько нелепо.
Комментарий для Евгения Степанищева:
В случае с функцией она становится исключительно локальной для файла, в котором объявлена — обратиться к ней извне нельзя.
Комментарий для poige.livejournal.com:
Так я и не заимствую. Произвольный порядок определения есть в Си (в Си99 появилось).
Проще поменять расширение на «.go» и продолжить программировать на более удобном языке :)
И правда надо будет почитать учебник всё-таки. Хотя я редко использую Си, но иногда всё-таки использую.
Комментарий для Евгения Степанищева:
Ну раз совместимость с C++ не планируется, то оно и не надо.
Комментарий для Евгения Степанищева:
Угу. Но там он появился после C++. Вот поэтому я и говорю — вместо того, чтобы побираться по крохам, лучше просто переименовать файл в .cc, и не париться. ;)
Комментарий для Евгения Степанищева:
Возможно. Но .cc всё-таки куда ближе к .c… (если это роляет, конечно).
Выложил на Гитхаб: https://github.com/bolknote/macgreener
Хм, а почему у shell-скрипта расширение .c? )
Комментарий для poige.livejournal.com:
Потому что это не шелл-скрипт, а программа на Си.
А с каких пор программы на си начинаются с #!/bin/bash ? )
Комментарий для poige.livejournal.com:
Всё что ниже — программа, мне так удобно компилировать :)
Комментарий для Евгения Степанищева:
Странный довод — так можно и задницу занавеской вытирать… и «бинд» вместо «байнд» говорить (на радость англоязычным коллегам, чей родной язык и стал IT-стандартом де-факто).
Нормальные люди для этого Makefile используют, ну или банальный SHELL-скрипт отдельный. И я правильно понимаю, что сообщения об ошибках, указывают на сдвинутые номера строк, ага?
Комментарий для poige.livejournal.com:
Можно, если это моя занавеска и моя задница, то в чём проблема?
Комментарий для poige.livejournal.com:
Если бы я равнялся на нормальных людей, то бы сейчас пил в подворотне и не решал задачи, перед которыми остальные пасуют.
Нет, конечно. С чего бы это?
А раньше сигнализации писали под это дело.