программирование

Магия с сенсором освещения

Помимо сенсора внезапного движения многие новые «Макбуки» оборудованы сенсором освещённости, он едва заметно располагается рядом с видеокамерой.

Мне интересно стало попробовать поработать и с ним тоже. На этот раз цель была — научиться эффектным жестом блокировать свой ноут. Конечно, ложных срабатываний масса — всё-таки этот сенсор крайне примитивен, он видит только освещённость, но в качестве фокуса годится:

Фокус

По-моему, довольно эффектно смотрится — блокировка экрана магическим пассом.

Код на Си получился чуть более ста строк и на этот раз я распознаю шаблон поведения, он задаётся в подстановке «+vvv000-^^^», что означает «сначала идёт положительное отклонение от среднего, потом в последовательности не менее трёх, отклонение должно уменьшаться, потом не менее трёх раз должно встретиться нулевое отклонение от среднего, потом отрицательное отклонение, потом отклонение должно увеличиваться».

При сканировании чисел с датчика я всегда рассматриваю десять значений от которых считаю среднее и каждое значение рассматриваю как отклонение от этого среднего, собственно, вот код:

// Магические пассы, улавливаемые через датчик освещённости
// Written by Evgeny Stepanischev Jun 2013
// gcc lightlock.c -framework IOKit -framework ApplicationServices -O3 -o lightlock

#include <IOKit/IOKitLib.h>
#include <CoreFoundation/CoreFoundation.h>
#include <ApplicationServices/ApplicationServices.h>
#include <unistd.h>

#define WINDOWSIZE 10
#define GESTURES "+vvv000-^^^"
#define UPDATEINTERVAL .01

const kGetSensorReadingID = 0;
int makeReaction(uint64_t current);
static io_connect_t port = 0;

void macLock () {
    CGSCreateLoginSession(NULL);
}

void updateTimerCallBack(CFRunLoopTimerRef timer, void *info) {
    IOItemCount osize = 2;
    uint64_t values[osize];

    kern_return_t ret = IOConnectCallMethod(port, kGetSensorReadingID,
    NULL, 0, NULL, 0, values, &osize, NULL, 0);

    if (ret == KERN_SUCCESS) {
        if (makeReaction(values[0])) {
            macLock();
        }
    }
    else if (ret != kIOReturnBusy) {
      mach_error("IOConnectCallMethod: ", ret);
      exit(-3);
    }   
}

int main() {
    io_service_t service = IOServiceGetMatchingService(
        kIOMasterPortDefault, IOServiceMatching("AppleLMUController")
    );

    if (service == kIOReturnNoDevice) {
        fprintf(stderr, "Cannot find Ambient Light Sensor\n");
        exit(-1);
    }

    kern_return_t ret = IOServiceOpen(service, mach_task_self(), 0, &port);
    IOObjectRelease(service);

    if (ret != KERN_SUCCESS) {
        mach_error("IOServiceOpen:", ret);
        exit(-2);
    }

    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(
        kCFAllocatorDefault,
        CFAbsoluteTimeGetCurrent() + UPDATEINTERVAL,
        UPDATEINTERVAL, 0, 0, updateTimerCallBack, NULL
    );
    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
    CFRunLoopRun();

    exit(0);
}

int checkMask(char mask, const int64_t diff, const int64_t prevdiff) {
    switch (mask) {
        case '+': return diff > 0;
        case '-': return diff < 0;
        case '0': return diff == 0;
        case 'v': return prevdiff > diff;
        case '^': return diff > prevdiff;
    }

    return 0;
}

int makeReaction(uint64_t current) {
    static uint64_t window[WINDOWSIZE];
    static uint64_t prevdiff = 0;
    static gesturepos = 0;

    uint64_t average = current;
    int i, notzerocnt = 1;

    for (i = WINDOWSIZE-1; i > 0; i--) {
        if (window[i] = window[i-1]) {
            average += window[i];
            notzerocnt++;
        }
    }

    window[0] = current;
    average /= notzerocnt;

    int64_t diff = average - current;
    if (checkMask(GESTURES[gesturepos], diff, prevdiff)) {
        gesturepos++;
    } else {
        if (!gesturepos || !checkMask(GESTURES[gesturepos-1], diff, prevdiff)) {
            gesturepos = 0;
        }
    }

    prevdiff = diff;
    return gesturepos == sizeof GESTURES - 1;
}

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

Ну и традиционно, если кто-то что-то понимает в Си, поругайте код. Заранее спасибо!

6 комментариев

Многозначная логика в действии

Это прекрасно чуть более, чем полностью, друзья!

random()>0.1 (15.26КиБ)

На скриншоте 213-я строка джавовского драйвера для Монги, внутри класса ConnectionStatus, метод update.

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

if (!_ok && Math.random() <= 0.1) {
    return res;
}

7 комментариев

MacGreener C#

MacGreener C# (36.34КиБ)

Переписал МакГринер с Си на Си#. Основной код более-менее повторяется, но код получился более чем в два раза длинее.

Весь код разбит, преимущественно, на три класса — основной класс, где выполняется приложение, класс IOKit — низкоуровневые и чуть более высокоуровневые биндинги для библиотек МакОСи и IOKitMotionSensor — ещё более высокоуровневая обёртка над куском кода для опроса датчика.

Есть ещё два вспомогательных класса — IOKitException, класс кидаемых исключений и IOKitObject — класс всех ресурсов, которые требуется высвобождать (он следит за своевременным их высвобождением при помощи блока using).

Не знаю какую версию я буду дальше развивать (и буду ли), но версия на Си# более прожорлива — из-за фреймворка Mono, который требуется для исполнения программ на этом языке. Версия на Си# занимает в памяти 6,2Мб (+49,8Мб виртуальной), на Си — 840Кб (+18,6Мб виртуальной).

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

Оставить комментарий

Как узнать, что пользователь печатает веб-страницу на принтере?

У нас тут задача в процессе производства продукта появилась — занести в лог, что пользователь сделал попытку печати чего-либо в браузере. Есть событие onbeforeprint, но оно есть только в Эксплорере и Файерфоксе.

Придумал такое решение — перед печатью браузер накладывает стиль CSS для печати, если он есть, этим можно воспользоваться:

@media print {
    body {
        background: url(http://example.org/user-tries-to-print.php);
    }
}

Браузер попытается загрузить фоновое изображение (и загружает, я попробовал), выполнится серверный скрипт и положит в логи то, что нужно.

15 комментариев

Первые эксперименты с C#

Поучился обращаться к динамическим библиотекам «Мака» (dylib) из Си-шарпа. Не очень удобно, надо сказать. Вот во что превратилась моя функция macSleepAwake из сишной программы MacGreener:

using System; 
using System.Runtime.InteropServices;
using MonoMac;
using MonoMac.CoreFoundation;
using MonoMac.ObjCRuntime;

namespace MacGreenerFragment
{
    class SleepAwake
    {
        [DllImport ("IOKit", CharSet = CharSet.Ansi)]
        static extern int IORegistryEntryFromPath (IntPtr masterPort, string path);

        [DllImport ("IOKit")]
        static extern void IOObjectRelease (int @object);

        [DllImport ("IOKit")]
        static extern void IORegistryEntrySetCFProperty(int entry, IntPtr propertyName, IntPtr property);

        static void macSleepAwake(bool sleep)
        {
            var handle   = Dlfcn.dlopen (Constants.CoreFoundationLibrary, 0);
            IntPtr True  = Dlfcn.GetIntPtr (handle, "kCFBooleanTrue");
            IntPtr False = Dlfcn.GetIntPtr (handle, "kCFBooleanFalse");

            int r = IORegistryEntryFromPath(IntPtr.Zero, "IOService:/IOResources/IODisplayWrangler");
            if (r != 0) {
                IORegistryEntrySetCFProperty(r, ((CFString)"IORequestIdle").Handle, sleep ? True : False);
                IOObjectRelease(r);
            }
        }
    }
}

На Си это 11 строк.

8 комментариев

exe2com под Линуксом и «Маком»

Если вам для чего-то понадобится (как мне) скомпилировать утилиту exe2com под «Маком», то, во-первых, нужно будет добавить в начало файла следующий код:

char *strlwr(char *string) {
  unsigned char *p;

  for (p = (unsigned char *)string; *p != 0; ++p)
    *p = (unsigned char)tolower (*p);
  return string;
}

А во-вторых, компилировать нужно с ключом -fno-builtin-exit:

gcc -fno-builtin-exit exe2com.c -o exec2com

После этого утилита нормально работает.

Оставить комментарий

Обнаружение Ретины

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

Наиболее животрепещущий вопрос — подготовка изображений для Ретины.

Напомню, что Ретина — технология Эпл, ставшая уже нарицательной, заключающаяся в примерении экранов высокого разрешения (например, на моём ноуте это 2560×1600) с отображением на нём обычной графики так, как бы это выглядело на экранах вдвое низкого разрешения. При этом шрифт смотрится просто великолепно, как на бумаге, а вот с картинками беда.

В вебе бо́льшая часть графики выглядит не очень (хотя масса сайтов уже подготовилась). Для того, чтобы стало хорошо, все изображения надо готовить вдвое большего размера, при этом указывая для них в коде вдвое меньший размер, тогда проявляется вся мощь Ретины — будет видно самые мелкие детали изображения и пропадёт размытие.

Пока ещё не у всех Ретины, хочется, чтобы клиенты, у которых её нет, не грузили лишнего. К сожалению, загвоздка в том, что браузеры пока не научились сообщать серверу, что мы имеем дело с таким (пока) необычным экраном и есть только клиенские способы выяснить это.

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

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

Вообще, у Джипега, например, есть прогрессивная загрузка — по мере загрузки «прогрессивное» изображение не грузится сверху вниз, а становится всё чётче и чётче (хорошая иллюстрация этого есть в одном из параграфов «Ководства»). Надо будет поэксперементировать в эту сторону — нельзя ли прервать передачу на нужной детализации, если экран не «ретиновый».

А пока, у меня такое решение. Используем старые-добрые «печеньки» и mod_rewrite.

На стороне клиента первой строкой каждой нашей страницы ставим проверку по «ретиновые» экраны с установкой «печенья»:

<script>if(window.devicePixelRatio>=2)document.cookie='retina=1'</script>

Этот скрипт выполнится первым и все дальнейшие запросы на сервер будут содержать строку «retina=1», если мы имеем дело с Ретиной. Все изображения, для которых у нас есть две копии — обычная и «ретиновая» (в два раза большая) называем одинаково, но с постфиксом «@2x» для большей, при этом в коде указываем данные разрешения для более мелкой картинки:

<img src="/images/owl@2x.jpg" width="400" height="560" alt="Совушка">

Сейчас, конечно, Апач теряет популярность, но я воспользуюсь синтаксисом mod_rewrite, желающие могут переделать в Энжин Икс или какой-то другой синтакис:

RewriteEngine On

RewriteCond %{HTTP_COOKIE} !retina=1
RewriteRule (.*)@2x\.(gif|jpe?g|png|webp) $1.$2 [L,NC]

Этот код означает, что если не установлено «печенье» «retina=1», то нужно убрать постфикс «@2x» из имени картинки.

В итоге, если у нас Ретина, «печенька» ставится в самом начале, все дальнейшие запросы к серверу идут с ней и картинки грузятся как указано в коде — например, если указано «owl@2x.jpg», то эта картинка и грузится.

Если у нас обычный экран, «печеньки» нет, запросы к серверу идут без неё и из картинок вырезается постфикс «@2x», т. е. картинка «cat.jpg» грузится по своему имени, а вместо «owl@2x.jpg» загрузится «owl.jpg».

Voilà!

66 комментариев

MacGreener

Выложил утилиту, которую описывал недавно (используя датчики «Макбука Про», заставляет его засыпать, если его поднять со стола) на «Гитхаб», назвал её MacGreener. Не знаю как сейчас, а раньше «зелёный» (green) в технике ассоциировался с экономией электроэнергии, так как этой утилитой я делаю «Мак» «зеленее» (greener), отсюда и название.

Я немного изменил алгоритм и внёс ещё одно важное изменение: теперь, если «Мак» поставить на стол, он проснётся. Напомню, программа работает только при наличии соотвествующего датчика. Рядом с исходным кодом лежит скомпилированная версия.

11 комментариев

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

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

// 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 комментариев

Сенсор внезапного движения

Очень давно, на всех носимых устройствах, оснащённых жёстким диском (ноута, смартфонах) появился сенсор внезапного движения. В жёстком диске считывающая головка находится, не касаясь, над поверхностью диска с данными. Если ноут резко поднять, головка может чиркнуть по поверхности диска и испортить его. Для этого нужен сенсор внезапного движения (акселерометр), он реагирует на ускорение в любую сторону и «паркует» головку — уводит её в зону, где она ничего не может повредить.

На ноутах с твёрдотельным накопителем (ССД), этого сенсора обычно не бывает — он там не нужен. Например, на моём Макбуке Эйр его не было, нет его и на 15″ Макбуке Про с Ретиной моего друга, я специально смотрел — в этих ноутах нет жёстких дисков. В Макбуках Про предыдущей модели, без Ретины и с жёстким диском, сенсор, естественно, был.

Каково же было моё удивление, когда я обнаружил, после покупки Макбука Про 13″, что в сведениях о системе есть упомянутый сенсор:

Сенсор внезапного движения (148.70КиБ)

Думаю, его просто забыли убрать, наверное это рудимент прежней модели Макбука. Но наличие сенсора не говорит о том, что он работает. Я сегодня потратил пару часов, чтобы разобраться как в Маке читают этот сенсор и написал вот такую программу на Си:

// MacbookPro (OS X 10.5+) SMS reading. Evgeny Stepanischev May 2013
// gcc  -framework IOKit  motion.c
#include <IOKit/IOKitLib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.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)

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

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);

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

    printf("%d, %d, %d\n", (*out).x, (*out).y, (*out).z);

    return 0;
}

Программа выводит три числа: показатели наклона влево/вправо, назад/вперёд и показатель ускорения, который меняется, если ноут резко поднять или опустить.

Размер структуры 40 и второй параметр функции IOConnectCallStructMethod («5») — «магические» значения, первое — фиксированный размер буфера, который требуется функции, второе — номер вызываемой функции в ядре (SMCMotionSensor).

2 комментария

MongoDB разонравилась

Очень много раньше хорошего слышал про «Монгу», прочитал по ней пару книг, даже пытался использовать в рабочих проектах. Всё, теперь ни ногой.

Во-первых, видел как база два раза разлетелась и больше не собиралась. Очень неприятно, одну из коллекций спасти не удалось.

Во-вторых, «Монга» до сих пор не умеет блокировать при записи только одну строку в коллекции, лочит всю базу, причём до версии 2.2 было ещё хуже — на записи срабатывала глобальная блокировка. В будущем предполагается блокировка на уровне коллекции, а когда это будет делаться на уровне строки — неизвестно.

Естественно, если кто-то один пишет в базу, остальные ждут — читать и писать нельзя. Кроме того, блокировка на запись имеет приоритет. На производительности, конечно, это сказывается всё не лучшим образом.

20 комментариев

Инвертирование в CSS без фильтров

Немногие знают, что инвертирование части ХТМЛ-страницы можно и без новомодных ЦСС-фильтров. Правда, количество браузеров, где это можно сделать, всё уменьшается. В данный момент такая возможность всё ещё остаётся в Эксплорере (версия 8 и выше) и «Опере» (7.0 и выше). Так как «Опера» скоро перейдёт на Вебкит, где этой возможности нет и не было, останется один Эксплорер. Файэрфокс лишился поддержки этой функции в версии 3.0 и возвращать её пока не планирует.

Я говорю о поддержке значения invert свойства outline-color. Это значение говорит браузеру, что нужно инвертировать всё, что лежит ниже. Пользоваться не очень-то удобно, но сойдёт при необходимости, результат может выглядеть, например, так:

Инвертирование при помощи outline-color (33.90КиБ)

Наложить сверху элемент с аутлайном можно любым способом, я использовал position: absolute:

<style>
.invert {
    outline: invert solid 75px;
    position: absolute;

    width: 50px; height: 0;
    margin: 75px 0 0 75px;
}
.content {
    width: 200px;
    height: 150px;
    position: absolute;
    left: 200px;
    top: 200px;
    text-align: center;
}
</style>

<div class="content">
    <div class="invert"></div>
    <img src="/i/foto.jpg">
</div>

Для того, чтобы высчитать размеры накрывающего блока, нужно немного арифметики. У меня большую размерность имеет ширина (накрываемый блок размером 200×150), поэтому будем плясать от неё. Высота накрывающего блока должна быть в этом случае равна нулю, ширина — разности между шириной и высотой накрываемого блока, outline-width, margin-top и margin-left — половине высоты накрываемого блока.

Думаю, ни для кого не составит труда самостоятельно провести вычисления для обратного случая — когда высота больше ширины.

1 комментарий

Тонкое подчёркивание ссылок с разрывами на буквах «р», «у» и других

У меня на сайте два изменения. Во-первых, я опять втихую увеличил шрифт (я этим занимаюсь время от времени), во-вторых, я изменил стиль подчёркивания ссылок. После сайтов Тёмы и Ильи Бирмана, подчёркивание у меня казалось тяжёлым и давящим, а у них — естественным и воздушным.

Но просто сделать так же не интересно, и я решил сделать лучше. Спорно получилось ли лучше, тут есть простор для экспериментов.

Подчёркивание ссылок и Тёмы и Ильи сделано одинаково — убрано родное подчёркивание, а взамен установлено свойство border-bottom, задающее бордюр толщиной в один пиксель. Мне в этом подходе не понравилось, что бордюр находится неестественно далеко — за хвостиками букв «у», «р» и прочих.

Сначала позвольте показать что получилось, обратите внимание на разрыв на хвостике «р», когда я добился более плотного прилегания, оказалось, что перечёркивание хвостов меня не устраивает, хорошо что удалось это поправить:

Результат (21.88КиБ)

Собственно, мне захотелось сначала исправить этот недостаток — слишком далеко отстоящее подчёркивание. Готовое решение у меня было, я когда-то его делал, но оказалось, что у него недостаток — плохо себя ведёт с переносом в ссылках, на вторую и следущие строчки подчёркивание не распространялось.

Я довольно долго эксперементировал и нашёл, в конце-концов, решение — я сделал цветной однопиксельный задний фон (простой ГИФ в data URI) по одному для каждого цвета ссылок: непосещённые, посещённые и с наведением мыши, а потом просто спозиционировал так как мне понравилось.

И вот тут меня ждала неприятность. Дело в том, что некоторое время назад появился способ, позволяющий узнать какие сайты вы посещали; интернет пестрел скриптами, которые в шутку журили вас, если обнаруживали, что вы до этого посещали какие-то порносайты.

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

Естественно, браузеры не могли не отреагировать на эту дыру. Кажется, Файерфокс первым отреагировавшим на проблему — они предложили «врать» разработчику о стиле посещённых ссылок и разрешили менять для них только строго определённый список ЦСС-правил.

Увы, это меняет многое. У меня для того, чтобы цвет посещённых ссылок не различался с цветом подчёркивания, нужно было поменять background-image, а он не входит в список разрешённых к изменению стилей для посещённых ссылок (это естественно, если подумать). На этом этапе я загрустил и долго ничего не мог придумать, но когда начал засыпать, меня осенило.

В голове перед сном я прокручивал список разрешённых к изменению свойств и одно из них, background-color показалось мне подходящим, чтобы решить мою проблему. Я нарисовал ГИФ единичной ширины, совершенно белый, но с одним прозрачным третьим с конца пикселем:

Мой фон (15.23КиБ)

Теперь, если выставить его в качестве стиля заднего фона ссылки, а потом ещё задать цвет фона, ссылка оказывается аккуратно подчёркнутой в нужном мне, регулируемом месте (я же могу сделать прозрачным любой пиксель в фоне).

Ок, это сработало для посещённых ссылок, но в «Хроме» и «Сафари» меня ждал неприятный сюрприз: на мгновение, до того как браузер успевал отрисовать мой ГИФ на заднем фоне, все ссылки выглядели уродливо: был виден их цветной задний фон:

Грусть-печаль (36.94КиБ)

Я подумал, что мне бы помогло, если я бы как-то рисовал подходящий фон без картинки. Долго думать не пришлось, я когда-то эксперементировал с картинками, сделанными при помощи linear-gradient, поэтому для меня было естественными решением сделать стиль, который рисует нужный мне фон этим способом.

Получился следующий стиль:

a {
    background: #00e url(data:image/gif;base64,R0lGODlhAQA7APABAP …и так далее… AAACCISPqcvtEEwBADs=) 0 100% repeat-x;
    background-image: -webkit-linear-gradient(90deg, #fff 0, #fff 2px, rgba(0,0,0,0) 2px, #fff 3px, #fff 59px);
}

Это полностью решило проблему — мигание на вебкитовских браузерах пропало. Кстати, я применил стиль линейного градиента, в новом формате — он работает в «Сафари» с версии 5.1 и в «Хроме» с десятой. Мне кажется, в этом нет ничего страшного — доля старых браузеров, в данном случае, ничтожна мала.

Когда я поставил подчёркивание куда мне хочется, возникла другая проблема. Эстетическая. Подчёркивание довольно уродливо перечёркивало нижние выносные элементы букв. На память, по аналогии, пришло моё баловство с фильтром Glow в Интернет Эксплорере. Там удавалось добиться интересных эффектов с задним фоном, поскольку эффект ложится и на него. Тут у меня ситуация такая же, но фильтров больше не существует, правда есть их реинкарнация —W3C заботливо слизало идею в другом синтаксисе.

После некоторых экспериментов, я решил, что тут подходит text-shadow — если сделать тень белого цвета, она очень хорошо закрашивает задний фон. Для этого я сделал несколько однопиксельных теней, и одну двухпиксельную на самой границе разрыва — тогда края разрыва размываются и он становится более мягким. И поднял тень немного вверх, иначе некоторые буквы давали неприятные «блики» на подчёркивание:

a {
    text-shadow: 1px -1px 1px white, -1px -1px 1px white, 2px -1px 2px white, -2px -1px 2px white;
}

В браузерах, которые этот стиль не поддерживают, подчёркивание просто не будет разорвано.

Стиль, в результате выглядит примерно так:

a {
    text-shadow: 1px -1px 1px white, -1px -1px 1px white, 2px -1px 2px white, -2px -1px 2px white;
    text-decoration: none;

    background: #00e url(data:image/gif;base64,R0lGODlhAQA7APABAP … и так далее … AAACCISPqcvtEEwBADs=) 0 100% repeat-x;
    background-image: -webkit-linear-gradient(90deg, #fff 0, #fff 2px, rgba(0,0,0,0) 2px, #fff 3px, #fff 59px);
}

a:visited {
    color: #551a8b;
    background-color: #551a8b;
}

a:hover {
    background-color: red;
    color: red;
}

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

59 комментариев

Почему меня беспокоит размер программы

В комментариях к одной из заметок, где я показывал программу на Гугл Гоу меня спросили почему мне так важен размер программы, я там как раз сетовал, что размер скомпилированной программы получился около четырёх мегабайт.

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

Поэтому данность: количество оперативной памяти ограничено, в случае моего ноута — сильно ограничено, у меня их всего 2ГиБ, это мало по нынешним временам, но что делать. Типичные современные объёмы оперативной памяти не отличаются от этой цифры на порядки, обычно это 4—8 ГиБ.

Моя память (17.74КиБ)

Соответственно, меня расстраивают большие программы просто по той причине, что они занимают много памяти, которой и так немного. Если у вас открыт браузер, Фотошоп, Офис и ещё что-то, велика вероятность, что свободной памяти в системе почти нет и запуск ещё одной программы вызовет сброс на диск какого-то региона памяти и высвобождение его для новой программы.

Соответственно, чем больше некомпактных утилит (например, на «Гоу») будет работать, тем меньше у меня памяти, тем медленнее система, что меня расстраивает, конечно.

11 комментариев

Удивительный Си

Кажется, на любом языке программирования можно написать очень запутанную программу. Лидер, наверное, всё-таки Перл (если не брать эзотерические языки), а вот образчик на Си:

main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">'txiZ^(~z?"-48]>>";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1:10);}

Эта программа выводит время на момент компиляции в следующем формате:

Вывод программы (4.71КиБ)

У Анатолия Воробья есть полный анализ этой программы, остроумно сделано!

7 комментариев

Как скачать документ из АИС ЦГАСО

АИС ЦГАСО (104.73КиБ)

Изучаю тут самарский электронный архив, который сделан в «Эларе». Насколько я понял, продукт называется АИС ЦГАСО. Убиваю сразу двух зайцев — разбираюсь как всё работает и пытаюсь найти нужные мне документы по родственникам.

Ребята из «Элара» молодцы, делают очень нужное дело — оцифровывают бумажные архивы, это невероятный труд, но, к сожалению, интерфейс просмотра документов оставляет желать лучшего, он мне сжёг массу нервов. Решил просто выкачать нужные мне документы на компьютер и дело с концом. Написал специальную программу для скачивания документов из АИС ЦГАСО, на Гугл Гоу. Получилось довольно компактно.

Основная фишка — паралельное скачивание документов в несколько потоков. Не уверен, что это действительно работает (я не до конца понимаю как Гоу переключает свои горутины), но в теории должно. Добавлено: «полевые испытания» подтвердили, что документы действительно скачиваются параллельно. Удалось сделать крайне просто:

ch := make(chan byte, N)

for i, id := range documents {
    ch <- 1

    go func(id string, index int) {
        name := padZero5(strconv.Itoa(index)) + ".jpg"
        copyUrlToFile(strings.Replace(url, `%id`, id, 1), *dir + "/" + name)

        <-ch
    }(id, i)
}

Первой строкой в коде создаётся канал заданной размерности. С ним будут работать горутины (это такие легковесные потоки Гоу). В него можно положить только N значений, после чего следующая операция записи будет ждать его освобождения. С помещением в канал каждого нового значения, запускается горутина для скачивания одного листа документа, как только горутина отработала, она убирает из канала одно значение и цикл основного потока снова его кладёт и запускает новую горутину.

В итоге, одновременно, не будет запущено более N горутин.

Пользоваться программой просто (если кому-нибудь кроме меня это надо) — нужно в браузере (я в «Хроме» пробовал, выбирать при сохранении надо формат «веб-страница полностью») перейти на чтение документа (на снимке экрана иконка книжки рядом с каждым документом), когда загрузится первая страница документа, сохранить текущий документ браузера на диск (Ctrl+S в Винде, ⌘+S на «Маке») и скормить его программе в командной строке:

go run cgodownloader.go скачанный-файл.html

Программа обработает документ, покажет сколько страниц найдено и начнёт их скачивать в виде больших джпегов в ту же папку, показывая их имена. У меня на большом документе (около 1,5 тысяч страниц) заняло прилично времени — час с лишним.

Запуск программы (6.56КиБ)

Зато теперь просматривать удобно.

54 комментария

30 банковских дней-2

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

package main

import (
    "encoding/json"
    "os"
    s "strconv"
    t "time"
    "net/http"
    "io/ioutil"
    "fmt"
    r "regexp"
    "bufio"
    "strings"
    fp "path/filepath"
)

type date struct {
    D byte
    M byte
    Y int
}

func CreateDate(d, m string, y int) date {
    day, _   := s.Atoi(d)
    month, _ := s.Atoi(m)

    return date{D:byte(day), M:byte(month), Y:y}
}

func (d date) FindIn(list []date) bool {
    for _, cur := range list {
        if d.D == cur.D && d.M == cur.M && (d.Y == cur.Y || cur.Y == 0) {
            return true
        }
    } 

    return false
}

func (d date) ToTime() t.Time {
    return t.Date(d.Y, t.Month(d.M), int(d.D), 0, 0, 0, 0, t.UTC)
}

func (d date) IsWeekEnd() bool {
    dw := string(d.ToTime().Weekday())

    return dw == "Saturday" || dw == "Sunday"
}


func (d date) Add(delta string) date {
    dur, _ := t.ParseDuration(delta)
    t := d.ToTime().Add(dur)

    return date{D:byte(t.Day()), M:byte(t.Month()), Y:t.Year()}
}

func (d date) String() string {
    return fmt.Sprintf("%02d.%02d.%04d", d.D, d.M, d.Y)
}

func loadCache(name string) (holidays []date) {
    f, err := os.OpenFile(name, os.O_RDONLY, 0666)

    if err != nil {
        return nil
    }

    defer f.Close()

    if err := json.NewDecoder(f).Decode(&holidays); err != nil {
        return nil
    }

    return
}

func saveCache(name string, holidays []date) bool {
    f, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)

    if err != nil {
        return false
    }

    defer f.Close()

    if err := json.NewEncoder(f).Encode(&holidays); err != nil {
        return false
    }

    return true
}

func getCurrentCalend() []byte {
    if resp, err := http.Get("http://www.calend.ru/work/"); err == nil {
        defer resp.Body.Close()

        if body, err := ioutil.ReadAll(resp.Body); err == nil {
            return body
        }
    }

    return nil
}

func getCurrentHolidays() (dates []date) {
    table, _ := r.Compile(`(?s)<table\s.+?time_of_death(.+)</table>`)
    chunk, _ := r.Compile(`(?s)<td\s+class="\S+\s+col5"\s+day="(\d+)"\s+month="(\d+)"`)

    dates = []date{}

    year := t.Now().Year()

    for _, value := range chunk.FindAllStringSubmatch(string(table.Find(getCurrentCalend())), -1) {
        dates = append(dates, CreateDate(value[1], value[2], year))
    }

    return
}

func ReadUserInput(message string) string {
    fmt.Print(message)

    input, _ := bufio.NewReader(os.Stdin).ReadString('\n')
    return input
}

func main() {
    var currentholidays []date

    year := t.Now().Year()
    cachename := s.Itoa(year) + ".json"

    // если кеш существует
    if _, e := os.Stat(cachename); e == nil {
        currentholidays = loadCache(cachename)
    } else {
        currentholidays = getCurrentHolidays()
        saveCache(cachename, currentholidays)
    }

    // загружаем другие кеши
    caches, _ := fp.Glob("*.json")
    for _, name := range caches {
        if name != cachename {
            currentholidays = append(currentholidays, loadCache(name)...)
        }
    }

    // опрашиваем пользователя
    mask, _  := r.Compile(`^(?m)(\d+)\D+(\d+)`)

    for {
        var userdate date

        for {
            d := mask.FindStringSubmatch(ReadUserInput("Enter date (dd.mm): "))
            if len(d) > 0 {
                userdate = CreateDate(d[1], d[2], year)
                break
            }
        }

        var period int

        for {
            period, _ = s.Atoi(strings.TrimRight(ReadUserInput("Enter period: "), "\r\n"))

            if period > 0 {
                break
            }
        }

        for ; period > 0; period-- {
            for {
                userdate = userdate.Add("24h")

                if !userdate.FindIn(currentholidays) && !userdate.IsWeekEnd() {
                    break
                }
            }
        }


        fmt.Printf("Result: %s\n\n", userdate)
    }
}

Меня поразил размер скомпилированной программы. Вот эта мелочь под «Маком» занимает 3,9Мб, под «Виндой» — 3,7! Жена попросила убрать гуишное окошко, поэтому я сделал программу полностью консольной. Два разочарования: в регулярных выражениях под Виндой неверно определяется конец строки (не учитывается «\r», судя по всему) и второе — в Гоу нет ничего, чтобы работать с однобайтными кодировками, а в консоли Винды до сих пор CP866. Пришлось выводить всё по-английски, писать конвертирующую функцию мне лень.

Объём увеличился, во-первых, из-за улучшений, во-вторых, мне хотелось хранить файлы кешей в каком-нибудь более-менее читаемом формате, чтобы вручную добавить в кеш татарстанские праздники, так что часть объёма (класс «date и методы работы с ним) как раз для этого.

13 комментариев

30 банковских дней

Делал жене программу, чтобы отсчитывать от введёной даты 30 банковских дней в текущем году, может пригодится кому (в запускаемый файл преобразовал при помощи PyInstaller, получается один файл, очень удобно). При первом запуске программа скачивает календарь с сайта Calend.Ru и сохраняет рядом, потом берёт из него выходные и праздничные дни.

# coding: utf-8
import re
import httplib
import pickle
import ctypes
from datetime import date, datetime, timedelta

MB_OKCANCEL = 1
MB_SYSTEMMODAL = 4096
IDOK = 1
IDCANCEL = 2

def getCalend():
    h = httplib.HTTPConnection('www.calend.ru')
    h.request('GET', '/work/')
    return h.getresponse().read()

def getHolidays():
    table = re.findall(r'<table\s.+?time_of_death(.+)</table>', getCalend(), re.DOTALL)[0]
    return re.findall(r'<td\s+class="\S+\s+col5"\s+day="(\d+)"\s+month="(\d+)"', table, re.DOTALL)

def readHolidays():
    year = str(date.today().year)
    try:
        with open(year + '.cache', 'r') as f:
            holidays = pickle.load(f)
    except IOError:
        with open(year + '.cache', 'w') as f:
            holidays = tuple(getHolidays())
            pickle.dump(holidays, f)

    return [datetime(day=int(d[0]), month=int(d[1]), year=int(year)) for d in holidays]

def getStartDate():
    while 1:
        input = re.split(r'\D', raw_input("Enter start date (dd.mm): "))
        try:
            input = [int(x) for x in input]
            if len(input) == 2:
                break
        except ValueError:
            pass

    return (date.today().year, input[1], input[0])

delta = timedelta(days = 1)

holidays = readHolidays()
msgbox = ctypes.windll.user32.MessageBoxW

while 1:
    try:
        date = datetime(*getStartDate())

        for i in xrange(0, 30):
            while 1:
                date += delta

                if date not in holidays: break

    if msgbox(
            0,
            u'Получилась следующая дата: %02d.%02d.%04d' % (date.day, date.month, date.year),
            u'Ответ',
            MB_OKCANCEL | MB_SYSTEMMODAL
        ) == IDCANCEL:
        raise StopIteration
    except (KeyboardInterrupt, StopIteration):
        break

Программу написал быстро, а сложнее всего было её отправить — ГМэйл упорно не принимает запускаемые файлы, даже в архивах.

15 комментариев

Какие графические форматы поддерживает ваш браузер?

Скриншоты браузеров Opera, Chrome, IE, Safari, Firefox и IE на Nokia Lumia 920: список поддерживаемых форматов изображений (JPEG, PNG, GIF и др.) с пометкой «Done».
Форматы, поддерживаемые браузерами

Я тут скрипт накидал, который проверяет какие графические форматы поддерживает ваш браузер. Проверяется JPEG, GIF, PNG, XMB, WebP, BMP, SVG, JPEG XR, TIFF, PDF, EMF, WMF, WBMP (Wireless BMP), JPEG-2000, ICO, APNG и MNG.

Тестируется поддержка в теге IMG.

Любопытно, что «Сафари» до сих пор поддерживает JPEG-2000, а «Опера» — WBMP (как единственный десктопный браузер, поддерживающий WAP, где WBMP и применяется).

Дополнение: оказывается, SVG в моем тесте был не до конца верный (я его просто сконвертировал из гифа 1×1 какой-то программой), так что скриншоты теста в браузерах верны не до конца, «Хром» и «Сафари» (т. е. браузеры на вебките) не поняли мой SVG.

19 комментариев

Чудны́ дела твои, PHP!

Может быть кто-то заметил, что у меня аватары в комментариях отваливались, я довольно долго не мог найти время и посмотреть что происходит, а вчера, вот, поправил. Дело оказалось в том, что мой хостер выставил директиву open_basedir и копирование аватара с интернета на файловую систему отвалилось.

Я делал это функцией copy, первом аргументом передавал УРЛ, вторым — путь на файловой системе. В логах, после изменения конфигурации, ПХП ругался, что «http:» — это неправильное начало для разрешённого пути. Удивительно, но стоило заменить copy// на её, фактически, эквивалент, как всё заработало:

if ($fin = fopen($url, 'rb') and $fout = fopen($file, 'wb')) {
    stream_copy_to_stream($fin, $fout);

    fclose($fout);
    fclose($fin);

    return true;
} else {
    return false;
}

Такое поведение больше похоже на баг, ПХП у меня не последний (5.3.21 сейчас), может поправили уже.

2 комментария

← 1 30 31 32 33 34 67 →