Author Archives: elwood

Ubuntu, awesome и высокое разрешение

Written by elwood

Стал замечать, что глаза устают от смотрения в монитор домашнего ноутбука. Ноут у меня старенький, скоро почти 4 года исполнится – Dell Vostro 3560. Дисплей в нём HD (1366×768), не очень качественный (бюджетная TN-матрица). Решил посмотреть, что сейчас можно приобрести, чтобы и экран не слишком маленький был (я фанат 15.6″), и матрица получше (IPS). И обнаружил, что разрешение 1366×768 уже почти никто не выпускает, а в ходу в основном FullHD (1920×1080). Тут стало ясно, что нужно как-то решать проблему маленьких шрифтов, потому как если на экране 15.6″ разместить 1920х1080 пикселей, то всё станет в ~2.25 раза меньше (~1.5 в квадрате). А настраивать масштабирование я так и не удосужился научиться. На рабочем 14″-ноуте обычно скручивал разрешение до HD, а внешний монитор имел 24″ и на родном разрешении FullHD всё было нормально. В общем, решил попробовать настроить 14″ в режиме 1920×1080. В результате исследования выяснил следующее.

В обычной Ubuntu с Unity есть 2 источника глобальных настроек для масштабирования. Первый видно сразу, он расположен в разделе Display системных настроек – это

масштабирование элементов меню и заголовков

unity-control-center

(кликабельно) Двигаем ползунок, и всё увеличивается (хотя по идее изменения должны касаться только меню и заголовков). Яндекс Броузер и IntelliJ IDEA, к сожалению, не подхватывают изменения сразу, их нужно перезапускать. Остальные приложения масштабируются мгновенно.

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

unity-tweak-tool

Если у вас не установлен этот пакет, установите его. В нём можно изменять настройки font scale. Изменения тоже подхватываются приложениями на лету (кроме Яндекс Броузера и IDEA, опять же). И что самое приятное, они работают и в awesome. И можно настраивать эту штуку, не выходя из awesome !

unity-tweak-tool

Ещё можно поднастроить сам броузер, заставив его дополнительно масштабировать страницу (это если хочется ещё побольше, я поставил 110%). А в IDEA, понятное дело, можно (и нужно) настроить шрифты для редактора и терминала.

Есть ещё один инструмент для масштабирования: xrandr. Он может вообще целиком отмасштабировать весь экран. Но, к сожалению, результат не очень: много мыла. Возможно, на более высоких разрешениях экрана дело будет лучше, надо попробовать. А пока я остановился на font-scale в tweaker’e, а масштабирование заголовков выключил.

В идеале бы ещё поднастроить titlebar в awesome, но пока забил на это.

В общем, теперь можно не избегать более высоких разрешений. А как вы решаете эту проблему ?

dotnet core: второй подход к снаряду

Written by elwood

Полгода назад я впервые попробовал .NET Core. В тот раз даже запустить helloworld не получилось, но это потому что я запускал его на Ubuntu 15.10, а формально поддерживалась только 14.04. С тех пор прошло немало времени, вышел официальный релиз 1.0 (в котором разработчики отпилили поддержку --native). Добавилась документация на официальном сайте. И я подумал – а почему бы и не попробовать ещё разок ? Пробовать решил на Console Framework. В принципе, это несложный проект, состоящий из нескольких модулей. Внешних зависимостей у него нет.

Установка .NET Core

Не вызвала проблем. Ставил на Ubuntu 14.04 по официальному гайду.

Отдельные модули

Для начала я взялся за те модули, которые не зависят от других. Для того, чтобы .NET Core собрал модуль, нужно, чтобы рядом с кодом модуля лежал файл project.json. Надо где-то получить шаблон этого файла. Я сделал 2 пустых проекта: один командой dotnet new, другой командой dotnet new -t Lib. Почему-то в них оказались разными ссылки на дефолтные библиотеки. Для исполняемого модуля было указано “netcoreapp1.0”:

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true
  },
  "dependencies": {},
  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.0"
        }
      },
      "imports": "dnxcore50"
    }
  }
}

А для библиотеки “netstandard1.6”

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable"
  },
  "dependencies": {},
  "frameworks": {
    "netstandard1.6": {
      "dependencies": {
        "NETStandard.Library": "1.6.0"
      }
    }
  }
}

Чтобы разобраться, что это за наборы библиотек и чем они отличаются, я сходил и почитал документацию, и ещё чуть-чуть. К сожалению, понимания это не добавило. Я просто попробовал оба набора, и в netstandard1.6 вообще ничего не завелось, а netcoreapp всё-таки получилось заставить работать. В общем, взял тот кусок “frameworks”, который про netcoreapp1.0.

Несовместимость

При сборке были ошибки. Например, почему-то отсутствовал метод Type.GetCustomAttributes(). Как выяснилось, в .NET 4.5 добавили промежуточный класс TypeInfo, и теперь надо дёргать Type.GetTypeInfo().GetCustomAttributes() – это новый extension-метод в System.Reflection. Аналогично пришлось поменять вызовы IsGenericType, IsEnum, GetEnumNames и GetEnumValues.

Assembly.Load(string assemblyName) пришлось заменить на AssemblyLoad(new AssemblyName(string assemblyName)).

Пропал ArrayList (который не-generic). Пришлось везде поменять на List<object>.

Пропал CharSet.Auto, заменил на CharSet.Unicode (это в интеропе).

А ещё пропал метод Assembly.GetExecutingAssembly(). Теперь его следует вызывать через typeof(SomeTypeInAssembly).GetTypeInfo().Assembly.

Environment.PlatformID пропал совсем (хз что делать, закостылил пока через условную компиляцию).

Не было обнаружено также свойство EventWaitHandle.SafeWaitHandle (тоже удалил через условную компиляцию, т.к. это был windows-specific код).

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

Многомодульный проект

Когда получилось добиться успешной сборки двух не зависящих ни от чего модулей, я приступил к настройке третьего модуля, который использует первые два. Почему-то в официальном гайде указано, что в родительском каталоге нужно завести файл global.json, однако это совершенно не нужно. Всё работает и без него. Нужно лишь явно указать название и версию каждому модулю в их project.json (“name” и “version”), а в project.json зависящего от них модуля прописать их в dependencies:

  "dependencies": {
    "Binding": {
	"version": "1.0.0",
	"target": "project"
    },
    "Xaml": {
	"version": "1.0.0",
	"target": "project"
    }
  }

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

entry points

Осталось настроить сборку ещё одного модуля – собственно исполняемое приложение. Тут поджидала ещё одна проблема. У меня этот модуль содержал несколько классов Program, в каждой из которых был свой метод Main(). В mono и обычном .net можно было спокойно указать класс, который использовался бы как EntryPoint. В .net core почему-то эта штука вообще не работает. По документации, можно определить название метода, однако судя по всему, эта директива вообще игнорируется. Пришлось просто исключить все “лишние” файлы с точками входа:

"exclude": [
  "RadioButtons/**",
  "TreeView/**",
  "Commands/**",
  "AsyncUIUpdate/**",
  "TabControl/**",
  "MainMenu/**",
  "CheckBoxes/**"
],

Заодно узнал, как прописывать embedded ресурсы:

"resource": [
  "**/*.xml"
],

После всех этих манипуляций проект наконец собрался и даже запустился (когда я подложил libtermkey.so), однако пока рано радоваться:

dotnetcore-consoleframework

Ничего не работает, похоже, что с interop’ом в .NET Core большие проблемы.

Кстати, забавно, что dotnet build не создает exe-файлы для исполняемых модулей, делает только dll.

Документация

Документация очень плоха. Официальные гайды слабы. Взять например project.json reference. Дефолтные значения атрибутов не указаны (а в исходной версии этой доки они были!), часть информации устарела. В сети тоже много всего разного. Часто найденные рецепты противоречат друг другу. И не очень понятно, где искать инфу про то, что есть в библиотеке, а чего нет. Был бы нормальный декомпилятор .NET под линукс, было бы проще, но его, похоже, нет. Встроенный в MonoDevelop декомпилятор не справляется (просто не может открыть сборку). Вкупе с другими многочисленными проблемами, всё это делает меня грустить. Я уже не говорю о тулинге (чтобы не пришлось руками редактировать json-файлики). В общем, пока использовать .NET Core рановато. Буду ждать очередных релизов и выхода Rider.

В завершение прикладываю diff изменений (в отдельной ветке), чтобы можно было посмотреть примеры файлов project.json.

Редиректы из-под nginx

Written by elwood

Предположим у нас есть nginx и проксируемая приложунька.

Nginx отвечает на запросы http://frontend.ru:8080/foo и проксирует их локально на http://localhost:8090/foo

Если приложение хочет ответить редиректом на /somewhere, то пользователь должен увидеть Location: http://frontend.ru:8080/somewhere

Тут есть нюанс. Поведение зависит от того, как мы передаём заголовок Host в бекенд. Можно написать так:

location / {
    proxy_pass http://localhost:8080;

    proxy_set_header HOST $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-NginX-Proxy true;

    proxy_redirect off;
}

И тогда бекенду будет приходить заголовок Host: frontend.ru. Если приложение не экзотическое, оно на такой заголовок сформирует редирект без указания порта (т.к. про порт ему и узнать неоткуда). В результате ответ будет такой: Location: http://frontend.ru/, что совсем не круто. Нам-то нужен нормальный Location ! С портом !

В общем, есть два пути решения этой проблемы.

Первый способ: настроить rewrite для заголовков ответа

proxy_redirect http://$host/ http://$host:$server_port/;

Эта штука будет перехватывать ответ приложения и заменять в заголовках Location, дописывая порт.

И второй: использовать в директиве proxy_set_header HOST $host; переменную $http_host вместо переменной $host:

proxy_set_header HOST $http_host;

$http_host выгодно отличается от просто $host тем, что содержит порт (хехе), и всё сразу начинает работать. Приложение формирует правильные редиректы, их даже и не приходится перезаписывать, пользователи довольны, донатят тысячи и тысячи в валюте.