linux

Fedora перестает грузиться на UEFI после обновения (и показывает MOK)

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

Что меня больше всего удивило так это то, что efibootmgr -v выдавал кучу записей загрузчиков shim.efi с некорректными uuid разделов на которых они размещены.

$ efibootmgr -v
BootCurrent: 0002
Timeout: 0 seconds
BootOrder: 0007,0002,2001,2002,2003
Boot0000* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\Fedora\shim.efi)
Boot0001* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\fedora\shim.efi)
Boot0002* Linux    PciRoot(0x0)/Pci(0x1c,0x4)/Pci(0x0,0x0)/NVMe(0x1,00-00-00-00-00-00-00-00)/HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\BOOT\BOOTX64.EFI)A01 ..
Boot0003* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\fedora\shim.efi)
Boot0004* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\fedora\shim.efi)
Boot0005* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\fedora\shim.efi)
Boot0006* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\fedora\shim.efi)
Boot0007* Fedora    HD(1,GPT,f627bf87-5440-4997-8310-aa80dba7e383,0x800,0x64000)/File(\EFI\fedora\shim.efi)
Boot2001* EFI USB Device    RC
Boot2002* EFI DVD/CDROM    RC
Boot2003* EFI Network    RC

Конечно в данном листинге уже все верно поскольку он был сделан на рабочей машине, но в оригинальном листинге в идентификаторе HD были прописаны несуществующие uuid разделов. И подобных записей было далеко за 20 штук.

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

Далее нам потребуется удалить все записи загрузчика с неверными данными. Это записи вида Boot0ХХХ.

Сначала надо переустановить grub-efi и shim как это рекомендует документация.

# dnf reinstall grub-efi shim

Теперь удаляем невалидные записи. Для их удаления нам потребуется выполнять команду

# efibootmgr -B -b XXXX
  • -B — удалить запись
  • -b XXXX — выбрать активной запись XXXX

В качестве XXXX будут выступать идентификаторы неугодных записей (не трогайте записи, которые начинаются не с нуля — они системные). И конечно же перед каждым удалением следите за состоянием записей (efibootmgr -v).

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

efibootmgr -c -w -L Fedora -d /dev/nvme0n1 -p 1 -l '\EFI\Fedora\shim.efi'
  • -c — создать запись
  • -w — сделать запись в mbr если это требуется
  • -L Fedora — метка новой записи в загрузчике
  • -d /dev/nvme0n1 — жесткий диск на котором размещен efi-раздел (у вас может быть /dev/sda или любой другой)
  • -p 1 — номер раздела на диске (если efi у вас это /dev/sda1, то 1, sda2 — 2 и т.д.)
  • -l ‘\EFI\Fedora\shim.efi’ — расположение файла загрузчика относительно корня диска efi (а не корня файловой системы в которую он подмонтирован). Обратите внимание, что тут нам обязательно надо указать загрузчик shim.efi, а не что-то другое.

После завершения можно перезагружаться и пробовать войти в систему. Mok Manager больше не должен появляться. Если это не так, то где-то вы допустили ошибку.

Литература

Разработка

Часть 2: Тестирование простого приложения (Тестирование ПО)

Оглавление

Первое приложение

Программа считывает три целочисленных значения из консоли. Эти значения интерпретируются как длины сторон треугольника. Программа выводит сообщение о том, каким является данный треугольник — разносторонним, равнобедренным или равносторонним.

Напишем, функцию, которая принимает на вход три стороны треугольника, которые заданы целыми числами и возвращает тип треугольника. Сохраним написанный код в файле triangle.php.

/**
 * Не треугольник
 */
define('TRIANGLE_BAD', 0);
/**
 * Равносторонний треугольник
 */
define('TRIANGLE_EQUILATERAL', 1);
/**
 * Прямоугольный треугольник
 */
define('TRIANGLE_RIGHT', 2);
/**
 * Равнобедренный треугольник
 */
define('TRIANGLE_ISOSCELES', 3);
/**
 * Разносторонний треугольник
 */
define('TRIANGLE_SIDED', 4);

/**
 * По длинам сторон $a, $b и $c возвращает тип треугольника.
 * Если стороны не являются целочисленными, то выбрасывает исключение.
 *
 * @param $a
 * @param $b
 * @param $c
 * @return int
 * @throws Exception
 */
function triangle_type($a, $b, $c)
{
   // Вполне ожидаемо,
   // что нецелочисленные значения должны приводить к исключительной ситуации.
   if (!is_int($a) or !is_int($b) or !is_int($c))
   {
       throw new \Exception('Invalid triangle definition');
   }

  $max = null;
  $min1 = null;
  $min2 = null;

  if (($a+$b)>$c and ($a+$c)>$b and ($b+$c)>$a)
  {
     if (($a>$b) and ($a>$c))
     {
        $max = $a;
        $min1 = $b;
        $min2 = $c;
     }
     else if (($b>$c) and ($b>$a))
     {
        $max = $b;
        $min1 = $a;
        $min2 = $c;
     }
     else
     {
        $max = $c;
        $min1 = $a;
        $min2 = $b;
     }

     if (pow($max, 2) == pow($min1, 2) + pow($min2, 2))
     {
        return TRIANGLE_RIGHT;
     }
       else if (($max==$min1) and ($max==$min2))
       {
           return TRIANGLE_EQUILATERAL;
       }
     else if (($max==$min1) or ($max==$min2) or ($min1==$min2))
     {
        return TRIANGLE_ISOSCELES;
     }
     else
     {
        return TRIANGLE_SIDED;
     }
  }
  else
  {
     return TRIANGLE_BAD;
  }
}

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

Для начала потребуется реализовать механизм, который позволит вводить данные с консоли и получать результат. Сохраним следующий код в файле main.php. Чуть позже вы поймете, почему мы используем разные файлы для самой функции и для кода, который обрабатывает пользовательский ввод.

// здесь мы подключим ранее написанную функцию для определения типа треугольника
require __DIR__ . DIRECTORY_SEPARATOR . 'triangle.php';

function main()
{
   // проинициализируем переменные
   $a = $b = $c = 0;

   // получим длины сторон со стандартного ввода
   $num = fscanf(STDIN, "%d %d %d\n", $a, $b, $c);
   // если мы смогли считать длины трех сторон,
   // то вызовем нашу функцию и покажем результат
   if ($num == 3)
   {
       switch (triangle_type($a, $b, $c))
       {
           case TRIANGLE_BAD:
               echo "Это не треугольник\n";
               break;
           case TRIANGLE_EQUILATERAL:
               echo "Это равносторонний треугольник\n";
               break;
           case TRIANGLE_ISOSCELES:
               echo "Это равнобедренный треугольник\n";
               break;
           case TRIANGLE_RIGHT:
               echo "Это прямоугольный треугольник\n";
               break;
           case TRIANGLE_SIDED:
               echo "Это разносторонний треугольник\n";
               break;
       }
   }
}

main();

Код также достаточно тривиален. Теперь мы можем запустить полученное приложение (да, это именно приложение — последовательность инструкций, определяющих процедуру решения конкретной задачи компьютером).

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

$ php main.php

Программа будет ожидать ввод трех чисел, разделенных пробелами.

И вот что мы можем увидеть на экране.

null

Поэкспериментируйте немного с программой вводя разные наборы чисел.

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

Итак. Ниже приведен набор тестовых сценариев, которые должны быть написаны для нашей функции.

  1. тест для проверки действительно неравностороннего треугольника (наборы [1, 2, 3], [2, 5, 10] треугольниками не являются).
  2. проверка на действительно равносторонний треугольник
  3. проверка на равнобедренный треугольник (наборы вида [2, 2, 4] треугольником не являются)
  4. как минимум три теста для проверки равнобедренного треугольника, которые представляют собой перестановки одного и того же набора чисел ([3, 3, 4], [3, 4, 3], [4, 3, 3])
  5. тест на нулевую длину одной из сторон
  6. тест на сторону, имеющую длину меньше нуля
  7. проверка набора чисел, в котором сумма длин двух сторон равна третьей
  8. тест перестановок для троек чисел из теста 7
  9. проверка набора чисел, в котором сумма длин двух сторон меньше третьей ([12, 15, 30])
  10. тест перестановок для троек чисел из теста 9
  11. проверка на нулевую длину всех трех сторон
  12. проверка на передачу нецелочисленных значений
  13. проверка на передачу неполного набора значений
  14. проверка не только входных данных, но и ожидаемого выходного значения в каждом из тестов 1-13

Если вы не смогли назвать все кейсы, то не пугайтесь. Среднее число тестов, которые называли в разное время опытные разработчики составило 7,8.

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

Это упражнение должно было продемонстрировать вам, что тестирование простых программ наподобие вышеприведенной является отнюдь не тривиальной задачей. А теперь попытайтесь представить себе, насколько трудоемким окажется тестирование, скажем, бухгалтерской программы крупного предприятия, компилятора или же системы управления воздушным движением, объем кода которых может достигать сотен тысяч строк. Еще большие трудности возникают с приложениями, которые написаны с использованием объектно-ориентированных языков (куда входит и php) и подходов. В частности, тесты для подобных приложений должны выявлять ошибки с созданием экземпляров объектов и взаимодействия между ними.

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

Тестируем

Конечно же самым простым решением будет просто закодировать все тестовые случаи для нашего проекта и написать нечто вроде следующего кода (файл triangle_test_simple.php).

require __DIR__ . DIRECTORY_SEPARATOR . 'triangle.php';

function testForIsoscelesTriangle()
{
   echo "Test for [3, 4, 4]: ";
   if (triangle_type(3, 4, 4) == TRIANGLE_ISOSCELES) {
       echo "ok\n";
   } else {
       echo "fail\n";
   }
}

function main()
{
   testForIsoscelesTriangle();
}

main();

И такое часто практикуется. Особенно в среде разработчиков на C\C++. На каждый логически связанный набор тестовых случаев создается свой файл. Который содержит множество функций обрабатывающих по одному сценарию каждая.

В этом нет ничего плохого. Единственный минус такого подхода — отсутствие готового инструментария, который реализует все необходимые операции по обслуживанию и запуску тестов. Весь инструментарий приходится для каждого проекта реализовывать заново. Либо изготавливать свою собственную обвязку, которая будет кочевать из проекта в проект.

Один из вариантов создания инструмента для работы с подобными тестами вы можете увидеть в файле triangle_test.php. Запустите его и увидите на экране подробный лог тестирования проекта.

null

Литература

Исходные тексты программ

Оглавление

Разработка

Часть 1: краткая историческая справка (Тестирование ПО)

Оглавление

Баг. Кто он?

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

Баг. Кто он?

Известно, что первыми, кто назвал неправильную работу устройств «насекомым» были инженеры телеграфных компаний. И было это задолго до появления первых компьютеров — еще в 19 веке.

Откуда пошло тестирование

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

В 1970-х годах процесс проверки программы обозначался как «доказательство правильности». И включал в себя проверку всех возможных путей исполнения кода.

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

В дальнейшем было отмечено, что исчерпывающее тестирование практически невозможно.

В эпоху процессора 8086 тестирование стало подразумевать еще понятием «предупреждение дефектов». А с 1990-х годов в это понятие стали включать планирование, проектирование, создание, поддержку и выполнение тестов и тестовых окружений, и это означало переход от тестирования к обеспечению качества, охватывающего весь цикл разработки программного обеспечения.

Формальное определение

Очень часто от инженеров можно услышать определения, которые коверкают саму суть процесса:

  • Тестирование — это процесс, демонстрирующий отсутствие ошибок в программе.
  • Цель тестирования — показать, что программа корректно выполняет заданные функции.
  • Тестирование — это процесс, позволяющий убедиться в том, что программа реализует запланированные действия.

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

Поэтому правильнее будет назвать следующее определение:

Тестирование это процесс выполнения программы с целью обнаружения ошибок.

Виды тестирования (по знанию устройства ПО)

Тестирование методом «черного ящика»

Или другими словами «тестирование, управляемое данными» (data-driven testing).

В соответствии с этим методом программа рассматривается как черный ящик, внутреннее поведение и устройство которого не имеет никакого значения.

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

Тестирование методом «белого ящика»

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

Это определение нельзя назвать полным, поскольку подобранный набор данных не всегда соответствует спецификации, но это наиболее полное определение, которое дает представление о тестировании методом «белого ящика».

Виды тестирования по уровню изолированности компонент

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

Блочное тестирование (юнит-тестирование)

Этот аспект проверяет самые мелкие аспекты работы системы. Например корректность каждого метода класса или отдельной функции.

Также отдельные аспекты проверки логики работы модулей входят в эту фазу тестирования. Например проверка функционала регистрации\авторизации пользователя на уровне объекта «пользователь».

Этот этап является проверкой методом «белого ящика».

Интеграционное тестирование

После того, как модули программы прошли предварительную проверку они собираются в функциональные группы и проверяются аспекты бизнес-логики. Данные этап — это тестирование методом «черного ящика». Тесты проектируются таким образом, чтобы проверить соответствие полученного результата заявленному.

Функциональное тестирование

Этот этап тестирование является тестированием методом черного ящика. Этап проверяем отсутствие ошибок в реализации спецификации ПО. На входы форм подаются значения, и выполняется проверка данных на выходе тем, что описаны в спецификации.

Приемочное тестирование

Ваше приложение развертывается в окружении, конфигурация которого близка к реальной. А также на данных, которые являются или соответствуют реальным.

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

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

Этот вид тестирования так же является тестированием методом «черного ящика».

Литература

  1. Книга «Искусство тестирования программ» Гленфорд Майерс, Том Баджетт, Кори Сандлер, ISBN 978-5-8459-1974-8
  2. Wikipedia: Software testing

Оглавление

Разработка, HowTo

PHP: дело о загадачном пробеле

%d0%b2%d1%8b%d0%b4%d0%b5%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5_103Или история о том, как побились картинки.

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

Проверил данные в кеше — картинки были. Все как положено — пережатые и с водяными знаками. После ряда танце с бубном удалось выяснить, что скрипт, который картинку генерирует пока ее в кеше нет отдает пробел в каждой картинке — на скриншоте это хорошо выдно. Идет пробел, а после стандартный заголовок jpeg.

Чтож. Очень вероятно, что какой-то вредный скрипт оказался с пробелом. Мы же помним вро стандартные открывающиеся и закрывающиеся теги php, которые принесли много головной боли.

Но как среди тысяч строк кода выяснить, где начинается вывод злобного пробела?

  1. отключить вывод самой картинки и убедится, что пробел все еще выводится.
  2. отключить буферизацию вывода на стороне сервера — убедится, что пробел есть
  3. в самом конце скрипта поставить setcookie или header — в логах пояится сообщение вида «Warning: Cannot modify header information — headers already sent by (output started at file.php:XXX)». Где file.php — это соответсвенно файл, а XXX — номер строки, где начался вывод.

Чтобы отключить буферизацию нужно прописать в конфигах php.ini

output_buffering=0

Или в конфигах апача или htaccess

php_flag "output_buffering" Off
HowTo, linux

OpenWRT: блокировка рекламы

1280px-openwrt_logo-svgЕсть отличная прошивка на базе всеми любимого линупса для всякой разной техники вроде роутеров и микрокомпьютеров — это OpenWRT.

А поскольку эта штука работает в качестве локального днс-сервера, то грех не научить ее блокировать на корню всякие даблклик.нет и другое непотребство.

Рассматривать будем ситуацию при которой у нас установлена дефолтная конфигурация c dnsmasq.

Сначала нам потребуется поставить пакет dnsmasq-full взамен стандартного (он чуть больше по объему и тянет больше зависимостей, и предоставляет больше возможностей по настройке).

# opkg update
# opkg remove dnsmasq
# opkg install dnsmasq-full
# /etc/init.d/dnsmasq restart

Посмотрим на файл /tmp/etc/dnsmasq.conf. Нас будут интересовать следующие два параметра.

addn-hosts=/tmp/hosts
conf-dir=/tmp/dnsmasq.d

Буквально это значит, что дополнительные файлы в формате /etc/hosts хранятся в /tmp/hosts, а дополнительные конфиги — в /tmp/dnsmasq.d.

Теперь нам потребуется скрипт, который будет скачивать листы. Поместим его в /root/bin/adblock.sh

#!/bin/sh
echo "download adblock rules"

if [ ! -d /tmp/dnsmasq.d ]; then
 mkdir /tmp/dnsmasq.d
fi

if [ ! -d /tmp/hosts ]; then
 mkdir /tmp/hosts
fi

touch /tmp/dnsmasq.d/adblock.conf

wget -O /tmp/dnsmasq.d/adblock.conf "http://pgl.yoyo.org/adservers/serverlist.php?hostformat=dnsmasq&showintro=0&mimetype=plaintext"

echo "" > /tmp/hosts/adblock

wget -O /tmp/adblock https://hosts-file.net/ad_servers.txt
sed 's/^\(.*\).$/\1/' /tmp/adblock >> /tmp/hosts/adblock
wget -O /tmp/adblock https://adaway.org/hosts.txt
sed 's/^\(.*\).$/\1/' /tmp/adblock >> /tmp/hosts/adblock
wget -O /tmp/adblock http://winhelp2002.mvps.org/hosts.txt
sed 's/^\(.*\).$/\1/' /tmp/adblock >> /tmp/hosts/adblock

if [ -f /tmp/adblock ]; then
 rm /tmp/adblock
fi

/etc/init.d/dnsmasq enabled && /etc/init.d/dnsmasq restart

Первым мы скачиваем файл с блокировками в формате dnsmasq, а последующие запросы — это блокировки в формате hosts. Соответственно раскладываем их по разным папкам и рестартим сервис.

Не забываем поставить +x на файл.

Теперь нужно добавить этот скрипт в крон дабы он обновлялся.

 # crontab -e

и пишем туда что-то вроде

* */12 * * *  /bin/sh /root/bin/adblock.sh

Это означает, что раз в 12 часов списки будут обновляться.

Естественно, что надо включить крон. По дефолту он выключен.

# /etc/init.d/cron enable
# /etc/init.d/cron enabled && /etc/init.d/cron start

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

А для этого нам потребуется создать файл /etc/hotplug.d/iface/50-adblock примерно следующего вида.

#!/bin/sh
[ ifup = "$ACTION" -a "$DEVICE" = eth1 ] && {
	/bin/sh /root/bin/update_adblock.sh
}

Где eth1 — это ваш wan-интерфейс.

Все. Можно перезагрузить роутер для проверки что все настроено корректно.

Рекламные домены вроде doubleclick.de должны резолвиться либо на 0.0.0.0, либо на 127.0.0.1.

$ nslookup  doubleclick.de
Server:		192.168.0.1
Address:	192.168.0.1#53

Name:	doubleclick.de
Address: 127.0.0.1

Чтобы точно убедиться, что все работает как надо — проверяйте что сразу после поднятия wan появились файлы /tmp/dnsmasq.d/adblock.conf и /tmp/hosts/adblock.

Учтите, что в сумме размер списков блокировки составит более 100 тысяч записей. И если роутер слабоват, то придется какой-то из списков отключать.

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

Источники:

Обработка изображений, Разработка

JavaScript: копируем img в canvas

Существует несколько способов скопировать имеющееся на странице изображение (img) в canvas. Конечно все они сводятся к простейшему drawImage, но каждый из этих методов применим в своем контексте.

Первый и самый очевидный (а еще универсальный) — загрузить изображение при помощи объекта Image. Он работает только в случае, когда у img есть атрибут src.

var img = new Image();
var srcImg = document.getElementById('src-img');
img.onload = function() {
    // создаем (или получаем) канву
    var backCanvas = document.getElementById('dest-canvas');
    backCanvas.width = this.width;
    backCanvas.height = this.height;
    var backCtx = backCanvas.getContext('2d');

    // рисуем
    backCtx.drawImage(this, 0,0);
};
img.setAttribute('src', srcImg.src);

Недостаток — еще один запрос в сеть.

Второй способ — менее универсален. Картинка у нас уже есть — почему ее нельзя нарисовать на канве?

var backCanvas = document.getElementById('dest-canvas');
var srcImg = document.getElementById('src-img');
backCanvas.width = srcImg.naturalWidth;
backCanvas.height = srcImg.naturalHeight;

var backCtx = backCanvas.getContext('2d');

// рисуем картинку
backCtx.drawImage(srcImg, 0,0);

И вот тут мы используем naturalWidth/naturalHeight. Зачем?

Все очень просто. Если в первом случае width и height означали размеры картинки, то во втором — это будут физические размеры элемента img на странице. И если картинка внутри него будет отмасштабирована, то мы мы скопируем только часть картинки размером width на height.

Как на иллюстрации ниже (картинка там имеет разрешение в десять раз больше нежели физические размеры img и масштабируется).

Выделение_001

А свойства naturalWidth/naturalHeight не поддерживаются в ряде браузеров. поэтому способ универсальным назвать нельзя.

linux

Xmonad: Фиксим менюшки у saleae logic

2016-06-22-22:49:47_409x324 Есть довольно хороший бюджетный логический анализатор от saleae (и масса совместимых китайских клонов).

Что примечательно — это то, что ребята из этой конторы написали софт под все платформы (и не забыли линупс конечно же).

Софт написан на qt. И выглядит как бы совсем нестандартно.

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

Фикс проблемы есть у саппорта saleae — нужно заигнорить окна с определенным классом добавив соответствующее правило к оконным хукам.

className =? "Logic" --> doIgnore