Публикации

»

Emacs не уме­ет ра­бо­тать с кноп­ка­ми аль­тер­на­тив­ных рас­кла­док. Точ­нее, уме­ет слиш­ком хо­ро­шо: для это­го на­до все го­ря­чие кноп­ки за­но­во объ­явить в аль­тер­на­тив­ной рас­клад­ке. Я лич­но к та­ким по­дви­гам не го­тов.

Есть не­сколь­ко ре­ше­ний этой про­бле­мы (1, 2), но все они чу­до­вищ­ны и пло­хо управ­ля­емы:

  • Пер­вый ко­стыль опре­де­ля­ет класс те­ку­ще­го ок­на и, ес­ли это Emacs, вмес­то сме­ны рас­клад­ки шлет код кноп­ки F31/F32. В Emacs на­зна­ча­ют­ся ху­ки для об­ра­бот­ки этих кнопок, ко­то­рые пе­ре­клю­ча­ют внут­рен­ний те­ку­щий ме­тод вво­да Ема­кса. Про­бле­ма в том, что этим спо­со­бом нам уда­лось толь­ко обес­пе­чить об­щий ин­тер­фейс пе­ре­клю­че­ния рас­кла­док в Emacs и осталь­ной си­сте­ме. Что де­ла­еть, ес­ли мы в дру­гом ок­не пе­ре­клю­чи­ли XKB на рус­скую рас­клад­ку, а по­том при­шли в Emacs? У нас не ра­бо­та­ют все го­ря­чие кноп­ки… То есть, нет со­гласо­ван­но­го со­сто­яния рас­клад­ки.
  • Вто­рой ко­стыль пред­ла­га­ет нам от­ка­зать­ся от клас­си­чес­ко­го X Input Method (XIM) в поль­зу UIM. Отлич­но, но мы не ки­тай­цы. Для справ­ки: аль­тер­на­тив­ные ме­то­ды вво­да со­зда­ны на 100% для предо­став­ле­ния спо­со­ба эк­ран­но­го вво­да ие­ро­гли­фов. То есть, ко­гда мы вы­бра­ли “тра­ди­ци­он­ный ки­тай­ский”, а по­том по­сле­до­ва­тель­но на­жи­мая x i s m по­лу­чи­ли ка­кой-ни­будь (как при­мер). Боль­ше ни для че­го эти стран­ные шту­ки на са­мом де­ле не ну­жны. Зато про­блем ко­нфи­гу­ри­ро­ва­ния, со­пря­жен­ных с от­ка­зом от XIM — до­ста­точ­но.

Ещё один ко­стыль

Дав­но си­жу под xmonad. Кон­фиг на нём пи­шет­ся на Хас­ке­ле. Но да­же не смот­ря на пы­ля­щу­юся до­ма мно­го лет книж­ку, мой опыт силь­но даль­ше “Hello world” не ушел. Кон­фиг к xmonad пи­сал в ос­нов­ном ме­то­дом copy-paste. Но тут что-то про­рва­ло, и за ве­чер в об­щих чер­тах этот ваш Хас­кель изу­чил.

В чем суть ре­ше­ния:

  1. Вмес­то внеш­них xbindkeys об­ра­бот­чи­ком кнопок пе­ре­клю­че­ния рас­кла­док бу­дет xmonad;
  2. xmonad по­мнит со­сто­яние те­ку­щей рас­клад­ки;
  3. xmonad при со­бы­тии «сме­ни­лось ок­но» про­ве­ря­ет класс ок­на. Если это Emacs, то с по­мощью xkblayoyt те­ку­щий XIM layout вы­став­ля­ет в en и шлет ок­ну код кноп­ки mod1-F11 или mod1-F12 — в за­ви­си­мос­ти от за­пом­нен­ной рас­клад­ки. Если ок­но не Emacs, то с по­мощью xkblayoyt вы­став­ля­ет со­хра­нен­ную в со­сто­янии рас­клад­ку.
  4. Если по­лу­чи­ли кноп­ку сме­ны рас­клад­ки, то де­ла­ем всё то же са­мое, плюс со­хра­ня­ем но­вое со­сто­яние рас­клад­ки.
  5. В ка­чест­ве бо­ну­са, ме­ня­ем цвет рам­ки ок­на в за­ви­си­мос­ти от рас­клад­ки.

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

Мож­но до­пи­сы­вать вс­якую эв­рис­ти­ку, на­при­мер для тер­ми­на­ла по умо­лча­нию вклю­чать en.

Настра­ива­ем xmonad

Опи­сы­ва­ем но­вый тип и спо­со­бы ра­бо­ты с ним:

data KeyboardLayout = KbdUs | KbdRu

stringOfKbdLayout KbdUs = "us"
stringOfKbdLayout KbdRu = "ru"

colorOfKbdLayout KbdUs = "#dd7300"
colorOfKbdLayout KbdRu = "#f00000"

emacsKeyOfKbdLayout KbdUs = xK_F11
emacsKeyOfKbdLayout KbdRu = xK_F12

Функ­ция, ме­ня­ющая цвет за­дан­но­го ок­на на ука­зан­ный цвет (я её где-то ско­пи­рай­тил):

setWindowBorder' c w = do
    XConf { display = d } <- ask
    ~(Just pc) <- io $ initColor d c
    io $ setWindowBorder d w pc

У нас из­ме­ни­лось со­сто­яние? Надо по­слать update в лог­гер, что­бы xmobar пе­ре­ри­со­вал за­го­ло­вок (ему xmonad шлет вир­туаль­ную рас­клад­ку). Вот эта функ­ция шлет сиг­нал:

sendUpdateEvent :: () -> X ()
sendUpdateEvent _ =
    ask >>= logHook . config

Функ­ции, ко­то­рые смот­рят на те­ку­щее ок­но и в за­ви­си­мос­ти от его ти­па и пе­ре­дан­но­го KeyboardLayout, ме­ня­ют рас­клад­ку ну­жным спо­со­бом, со­хра­няя её в state-мо­на­ду:

refreshKeyboardLayoutEmacs layout = do
  spawn $ "setxkbmap -layout " ++ stringOfKbdLayout KbdUs
  sendKey mod1Mask $ emacsKeyOfKbdLayout layout

refreshKeyboardLayoutOtherWindow layout =
    spawn $ "setxkbmap -layout " ++ stringOfKbdLayout layout

setKeyboardLayout currentKeyboardLayoutRef layout = do
  io $ writeIORef currentKeyboardLayoutRef layout
  withWindowSet $ \w -> case Win.peek w of
                          Nothing -> io $ return ()
                          Just w -> do
                            setWindowBorder' (colorOfKbdLayout layout) w
                            windowClass <- runQuery className w
                            case windowClass of
                              "Emacs" -> refreshKeyboardLayoutEmacs layout
                              _ -> refreshKeyboardLayoutOtherWindow layout

setNewKeyboardLayout :: IORef KeyboardLayout -> KeyboardLayout -> X ()
setNewKeyboardLayout currentKeyboardLayoutRef layout =
    do
      setKeyboardLayout currentKeyboardLayoutRef layout
      sendUpdateEvent ()

На рус­ский бу­дем пе­ре­клю­чать­ся по Ctrl-Menu, на ан­глий­ский — по Menu:

myConfig currentKeyboardLayoutRef = def {
           `additionalKeys` [
                ((noModMask, xK_Menu), setKeyboardLayoutWithFocused currentKeyboardLayoutRef KbdUs >>= sendUpdateEvent)
                , ((controlMask, xK_Menu), setKeyboardLayoutWithFocused currentKeyboardLayoutRef KbdRu >>= sendUpdateEvent)
                -- ...

Взле­та­ем:

main = do
  do currentKeyboardLayoutRef <- newIORef KbdUs
     xmonad (myConfig currentKeyboardLayoutRef) {
     -- ...

Вот мы в об­щих чер­тах воспро­из­ве­ли то, что де­ла­ет emxkb из аль­тер­на­тив­ных ре­ше­ний, и да­же боль­ше: мы ещё и state хра­ним. Теперь хо­те­лось бы этот state на­учить­ся ис­поль­зо­вать.

Про­пи­шем функ­ции, ко­то­рые уме­ют об­нов­лять рас­клад­ку те­ку­ще­го ок­на на со­хра­нен­ную. Это прос­то! Вот функ­ция, ко­то­рая по­лу­ча­ет пре­ды­ду­щее ок­но, те­ку­щее ок­но, ссыл­ку на со­сто­яние рас­клад­ки и ме­ня­ет рас­клад­ку:

updateKbdLayout :: IORef KeyboardLayout -> Maybe Window -> Window -> X ()
updateKbdLayout currentKeyboardLayoutRef _ currentWindow =
    do currentKeyboardLayout <- io $ readIORef currentKeyboardLayoutRef
       setKeyboardLayout currentKeyboardLayoutRef currentKeyboardLayout currentWindow

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

type WindowSwitchHook = Maybe Window -> Maybe Window -> X ()

windowSwitchHandler :: IORef (Maybe Window) -> WindowSwitchHook -> X () -> X ()
windowSwitchHandler lastWindowRef hook x = do
    withWindowSet $ \w -> do
        let currentWindow = Win.peek w
        lastWindow <- io $ readIORef lastWindowRef
        if lastWindow /= currentWindow then
            do
              io $ writeIORef lastWindowRef currentWindow
              hook lastWindow currentWindow
        else
            io $ return ()
    x

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

main = do
  do currentKeyboardLayoutRef <- newIORef KbdUs
     lastWindow <- newIORef Nothing
     xmonad (myConfig currentKeyboardLayoutRef) {
                  logHook = windowSwitchHandler lastWindow (updateKbdLayout currentKeyboardLayoutRef) $ dynamicLogWithPP (xmobarPrinter currentKeyboardLayoutRef xmobarHdl)

Допол­ни­тель­но я об­ра­ба­ты­ваю currentKeyboardLayout в dynamicLogWithPP и имею за­мет­ную сиг­на­ли­за­цию рус­ской рас­клад­ки в ста­тус-ба­ре.

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

Обра­бо­тка кнопок в Emacs

По чь­ему-то ре­цеп­ту при рус­ской рас­клад­ке до­пол­ни­тель­но подсве­чи­ваю крас­ным кур­сор. В осталь­ном код прос­той и вряд ли тре­бует по­яс­не­ний:

(defun my-update-cursor ()
  (set-cursor-color
   (if (string= current-input-method "russian-computer") "red" "black")))

(add-hook 'buffer-list-update-hook 'my-update-cursor)

(defun my-update-isearch-input-method ()
  (if isearch-mode
      (progn
        (setq isearch-input-method-function input-method-function
              isearch-input-method-local-p t)
        (isearch-update))))

(defun my-update-input-method (is-ru)
  (if is-ru
      (set-input-method 'russian-computer)
    (inactivate-input-method))
  (my-update-isearch-input-method)
  (my-update-cursor))

(defun my-select-input-eng ()
  (interactive)
  (my-update-input-method nil))

(defun my-select-input-rus ()
  (interactive)
  (my-update-input-method t))

(global-set-key (kbd "<M-f11>") 'my-select-input-eng)
(global-set-key (kbd "<M-f12>") 'my-select-input-rus)
(define-key isearch-mode-map (kbd "<M-f11>") 'my-select-input-eng)
(define-key isearch-mode-map (kbd "<M-f12>") 'my-select-input-rus)
»

                                                          .

org-mode . ob-tangle SRC- .

                      org-mode                        :

dotfiles.sorg.

                      git?                                :
  1. ;
  2. , ,

          .
    
                           Emacs           [     ](https://github.com/johnlepikhin/emacs-public).
    

    , .

,

                 ,
   .                      :
* Моё имя

#+name: person-name
#+BEGIN_SRC text
Evgenii Lepikhin
#+END_SRC

* Имя пользователя в системе

#+name: system-username
#+BEGIN_SRC text
eugene
#+END_SRC

* E-mail

#+name: person-email
#+BEGIN_SRC text
johnlepikhin@gmail.com
#+END_SRC

* Батарейка в ~/sys/class/power_supply~

#+name: sys-power-supply
#+BEGIN_SRC
BAT1
#+END_SRC

* Ширина экрана

#+name: screen-width
#+BEGIN_SRC text
1600
#+END_SRC

* Каким светодиодом показывать альтернативную раскладку

#+name: kbd-layout-led
#+BEGIN_SRC text
caps
#+END_SRC
                                         ~/.dotfiles.conf.org.

Emacs Library Of Babel:

(org-babel-lob-ingest "~/.dotfiles.conf.org")

bashrc

                                .bashrc        :
. ~/.bashrc.d/00_public

XIM

export XMODIFIERS=@im=none
export GTK_IM_MODULE=none
export QT_IM_MODULE=none
export QT4_IM_MODULE=none
export EDITOR='emacsclient -c'
export PATH=~/bin:~/bin/phone-shared:$PATH
alias usetmp=USETMP_DIR\=\`mktemp\ -d\ /tmp/tmpdir-XXXX\`\;\ bash\ -c\ \"cd\ \$USETMP_DIR\;\ bash\"\;\ rmdir\ \$USETMP_DIR

Go

export GOROOT=$HOME/bin/go
export GOPATH=$HOME/work/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

Perl

PATH="~/perl5/bin${PATH:+:${PATH}}"; export PATH;
PERL5LIB="~/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}"; export PERL5LIB;
PERL_LOCAL_LIB_ROOT="~/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}"; export PERL_LOCAL_LIB_ROOT;
PERL_MB_OPT="--install_base \"~/perl5\""; export PERL_MB_OPT;
PERL_MM_OPT="INSTALL_BASE=~/perl5"; export PERL_MM_OPT;

Ocaml

eval `opam config env`

git

[user]
name = Evgenii Lepikhin
email = johnlepikhin@gmail.com
[core]
        quotePath = false
        autocrlf = input
        excludesfile = ~/.gitignore

.gitignore

*_flymake.pl
*_flymake.pm
.PerlySenseProject
TAGS
CTAGS
GTAGS
*.elc
flycheck_*.el
tramp
\#*\#
.\#*
*~
gradlew.bat
perltidy.ERR

inputrc

Ctrl-B .

C-b: unix-filename-rubout

Ctrl-left ctrl-right

"\e[1;5C": forward-word
"\e[1;5D": backward-word

parcellite

                  .
[rc]
RCVersion=1
use_copy=true
use_primary=true
synchronize=true
save_history=true
history_pos=false
history_x=1
history_y=1
history_limit=25
data_size=0
item_size=5
automatic_paste=false
auto_key=false
auto_mouse=true
key_input=false
restore_empty=true
rc_edit=true
type_search=true
case_search=false
ignore_whiteonly=false
trim_wspace_begend=false
trim_newline=false
hyperlinks_only=false
confirm_clear=true
current_on_top=true
single_line=true
reverse_history=false
item_length=50
persistent_history=false
persistent_separate=false
persistent_on_top=false
persistent_delim=\\n
nonprint_disp=false
ellipsize=2
multi_user=false
icon_name=parcellite
menu_key=<Ctrl><Alt>P
history_key=<Ctrl><Super>C
phistory_key=<Ctrl><Alt>X
actions_key=

perlcritic

severity = 1
theme = bugs + maintenance + security + complexity + pbp + cosmetic + core

#This one must be disabled, since flymake will create temp files which
#by definition never match the specified package name
[-Modules::RequireFilenameMatchesPackage]

[-Subroutines::ProhibitSubroutinePrototypes]
[-Subroutines::ProhibitExplicitReturnUndef]
[-Subroutines::RequireArgUnpacking]
[-InputOutput::RequireCheckedSyscalls]
[-ValuesAndExpressions::ProhibitInterpolationOfLiterals]
[-ValuesAndExpressions::ProhibitMagicNumbers]
[-RegularExpressions::RequireDotMatchAnything]
[-RegularExpressions::RequireExtendedFormatting]
[-RegularExpressions::RequireLineBoundaryMatching]
[-Modules::RequireVersionVar]
[-Modules::ProhibitMultiplePackages]
[-RegularExpressions::ProhibitComplexRegexes]
[-ValuesAndExpressions::ProhibitLongChainsOfMethodCalls]
[-InputOutput::RequireCheckedClose]
[-Variables::ProhibitPunctuationVars]
[-ControlStructures::ProhibitDeepNests]

[-Documentation::RequirePodAtEnd]
[-Documentation::RequirePodSections]
[-Documentation::PodSpelling]

[InputOutput::RequireBriefOpen]
lines = 20

perltidy

# Max line width is 78 cols
-l=140
# Indent level is 4 cols
-i=4
# Continuation indent is 4 cols
-ci=4
# Output to STDOUT
-st
# Errors to STDERR
-se
# Maximal vertical tightness
-vt=2
# No extra indentation for closing brackets
-cti=0
# Medium parenthesis tightness
-pt=1
# Medium brace tightness
-bt=1
# Medium square bracket tightness
-sbt=1
# Medium block brace tightness
-bbt=1
# No space before semicolons
-nsfs
# Don't outdent long quoted strings
-nolq
# Break before all operators
-wbb="% + - * / x != == >= <= =~ !~ < > | & >= < = **= += *= &= <<= &&= -= /= |= >>= ||= .= %= ^= x="
# No newline before else
-ce

-bar
-otr
-sct
-dnl
-wrs='! ++'

ssh

Host *
     Compression yes
     CompressionLevel 9
Host *
     ServerAliveInterval 10
     ConnectTimeout 15
Host *
     ControlMaster auto
     ControlPath ~/.ssh/control/connections:%h:%p:%r
     ControlPersist yes
Host *
     User root

stalonetray

                             670
  :

(- 1600 670)
decorations none
transparent false
dockapp_mode none
geometry 10x1+nil+0
max_geometry 5x1-325-10
background "#000000"
kludges force_icons_size
grow_gravity NE
icon_gravity NE
icon_size 16
slot_size 22
sticky true
window_type dock
window_layer bottom
#no_shrink false
skip_taskbar true

urxvt

use File::Temp;

sub msg {
    my ( $self, $msg ) = @_;

    $self->{overlay} =
      $self->overlay( 0, -1, $self->ncol, 2, urxvt::OVERLAY_RSTYLE, 0 );
    $self->{overlay}->set( 0, 0, $msg );
}

sub on_start {
    my $self = shift;

    $self->bind_action( "M-e" => '%:edit_screen' );
}

Meta-E

sub edit_screen {
    my $self = shift;

    my $history = q{};
    my $row     = $self->top_row;
    while ( $self->nrow > $row ) {
        my $line = $self->line($row);
        $history .= $line->t() . "\n";
        $row++;
    }

    my $fh = File::Temp->new( TEMPLATE => '/tmp/urxvt_edit_screen_XXXXXX' );
    binmode( $fh, ":utf8" );
    print $fh $history;

    my $q_file = $fh->filename;
    system("editor $q_file &");
    close $fh;
}

sub on_action {
    my $self   = shift;
    my $action = shift;
    if ( $action eq 'edit_screen' ) {
        edit_screen($self);
    }

}
sub on_tt_paste {
    my ( $self, $str ) = @_;

    $str =~
      s/\xc2[«»]|\xe2\x80[\xb9\xba\x9e\x9c\x9d]|\xe3\x80[\x8c-\x8f]/"/gs;
    my $count = ( $str =~ tr/\012\015// );

    if ( !$count ) {
        $self->tt_paste($str);
        return 1;
    }

    $self->{paste} = \$str;
    $self->msg("Paste of $count lines, continue? (y/n)");
    my $preview = substr $self->locale_decode($str), 0, $self->ncol;
    $preview =~ s/\n/\\n/g;
    $self->{overlay}->set( 0, 1, $self->special_encode($preview) );
    $self->enable( key_press => \&tt_key_press );

    1;
}

sub tt_leave {
    my ($self) = @_;

    $self->{paste} = undef;
    delete $self->{overlay};
    $self->disable("key_press");
}

sub tt_key_press {
    my ( $self, $event, $keysym, $string ) = @_;

    if ( $keysym == 121 ) {    # y
        $self->tt_paste( ${ $self->{paste} } );
        $self->tt_leave;
    }
    elsif ( $keysym == 110 ) {    # n
        $self->tt_leave;
    }

    1;
}

Ctrl-Up/Down

                   Xresources
use strict;
use warnings;

my %escapecodes = (
    "font"           => 710,
    "boldFont"       => 711,
    "italicFont"     => 712,
    "boldItalicFont" => 713
);

sub on_start
{
    my ($self) = @_;

    $self->{step} = $self->x_resource("%.step") || 1;

    foreach my $type (qw(font boldFont italicFont boldItalicFont)) {
        $self->{$type} = $self->x_resource($type) || "undef";
    }
}

sub on_user_command
{
    my ($self, $cmd) = @_;

    my $step = $self->{step};

    if ($cmd eq "font-size:increase") {
        fonts_change_size($self,  $step, 0);
    } elsif ($cmd eq "font-size:decrease") {
        fonts_change_size($self, -$step, 0);
    } elsif ($cmd eq "font-size:incglobal") {
        fonts_change_size($self,  $step, 1);
    } elsif ($cmd eq "font-size:decglobal") {
        fonts_change_size($self, -$step, 1);
    } elsif ($cmd eq "font-size:incsave") {
        fonts_change_size($self,  $step, 2);
    } elsif ($cmd eq "font-size:decsave") {
        fonts_change_size($self, -$step, 2);
    } elsif ($cmd eq "font-size:reset") {
        fonts_reset($self);
    }
}

sub fonts_change_size
{
    my ($term, $change, $save) = @_;

    my @newfonts = ();

    my $curres = $term->resource('font');
    if (!$curres) {
        $term->scr_add_lines("\r\nWarning: No font configured, trying a default.\r\nPlease set a font with the 'URxvt.font' resource.");
        $curres = "fixed";
    }
    my @curfonts = split(/,/, $curres);

    my $basefont = shift(@curfonts);
    my ($newbasefont, $newbasesize) = handle_font($term, $basefont, $change, 0);
    push @newfonts, $newbasefont;

    # Only adjust other fonts if base font changed
    if ($newbasefont ne $basefont) {
        foreach my $font (@curfonts) {
            my ($newfont, $newsize) = handle_font($term, $font, $change, $newbasesize);
            push @newfonts, $newfont;
        }
        my $newres = join(",", @newfonts);
        font_apply_new($term, $newres, "font", $save);

        handle_type($term, "boldFont",       $change, $newbasesize, $save);
        handle_type($term, "italicFont",     $change, $newbasesize, $save);
        handle_type($term, "boldItalicFont", $change, $newbasesize, $save);
    }

    if ($save > 1) {
        # write the new values back to the file
        my $xresources = readlink $ENV{"HOME"} . "/.Xresources";
        system("xrdb -edit " . $xresources);
    }
}

sub fonts_reset
{
    my ($term) = @_;

    foreach my $type (qw(font boldFont italicFont boldItalicFont)) {
        my $initial = $term->{$type};
        if ($initial ne "undef") {
            font_apply_new($term, $initial, $type, 0);
        }
    }
}

sub handle_type
{
    my ($term, $type, $change, $basesize, $save) = @_;

    my $curres = $term->resource($type);
    if (!$curres) {
        return;
    }
    my @curfonts = split(/,/, $curres);
    my @newfonts = ();

    foreach my $font (@curfonts) {
        my ($newfont, $newsize) = handle_font($term, $font, $change, $basesize);
        push @newfonts, $newfont;
    }

    my $newres = join(",", @newfonts);
    font_apply_new($term, $newres, $type, $save);
}

sub handle_font
{
    my ($term, $font, $change, $basesize) = @_;

    my $newfont;
    my $newsize;
    my $prefix = 0;

    if ($font =~ /^\s*x:/) {
        $font =~ s/^\s*x://;
        $prefix = 1;
    }
    if ($font =~ /^\s*(\[.*\])?xft:/) {
        ($newfont, $newsize) = font_change_size_xft($term, $font, $change, $basesize);
    } elsif ($font =~ /^\s*-/) {
        ($newfont, $newsize) = font_change_size_xlfd($term, $font, $change, $basesize);
    } else {
        # check whether the font is a valid alias and if yes resolve it to the
        # actual font
        my $lsfinfo = `xlsfonts -l $font 2>/dev/null`;

        if ($lsfinfo eq "") {
            # not a valid alias, ring the bell if it is the base font and just
            # return the current font
            if ($basesize == 0) {
                $term->scr_bell;
            }
            return ($font, $basesize);
        }

        my $fontinfo = (split(/\n/, $lsfinfo))[-1];
        my ($fontfull) = ($fontinfo =~ /\s+([-a-z0-9]+$)/);
        ($newfont, $newsize) = font_change_size_xlfd($term, $fontfull, $change, $basesize);
    }

    # $term->scr_add_lines("\r\nNew font is $newfont\n");
    if ($prefix) {
        $newfont = "x:$newfont";
    }
    return ($newfont, $newsize);
}

sub font_change_size_xft
{
    my ($term, $fontstring, $change, $basesize) = @_;

    my @pieces   = split(/:/, $fontstring);
    my @resized  = ();
    my $size     = 0;
    my $new_size = 0;

    foreach my $piece (@pieces) {
        if ($piece =~ /^(?:(?:pixel)?size=|[^=-]+-)(\d+(\.\d*)?)$/) {
            $size = $1;

            if ($basesize != 0) {
                $new_size = $basesize;
            } else {
                $new_size = $size + $change
            }

            $piece =~ s/(=|-)$size/$1$new_size/;
        }
        push @resized, $piece;
    }

    my $resized_str = join(":", @resized);

    # don't make fonts too small
    if ($new_size >= 6) {
        return ($resized_str, $new_size);
    } else {
        if ($basesize == 0) {
            $term->scr_bell;
        }
        return ($fontstring, $size);
    }
}

sub font_change_size_xlfd
{
    my ($term, $fontstring, $change, $basesize) = @_;

    #-xos4-terminus-medium-r-normal-*-12-*-*-*-*-*-*-1

    my @fields = qw(foundry family weight slant setwidth style pixelSize pointSize Xresolution Yresolution spacing averageWidth registry encoding);

    my %font;
    $fontstring =~ s/^-//;  # Strip leading - before split
    @font{@fields} = split(/-/, $fontstring);

    if ($font{pixelSize} eq '*') {
        $term->scr_add_lines("\r\nWarning: Font size undefined, assuming 12.\r\nPlease set the 'URxvt.font' resource to a font with a concrete size.");
        $font{pixelSize} = '12'
    }
    if ($font{registry} eq '*') {
        $font{registry} ='iso8859';
    }

    # Blank out the size for the pattern
    my %pattern = %font;
    $pattern{foundry} = '*';
    $pattern{setwidth} = '*';
    $pattern{pixelSize} = '*';
    $pattern{pointSize} = '*';
    # if ($basesize != 0) {
    #     $pattern{Xresolution} = '*';
    #     $pattern{Yresolution} = '*';
    # }
    $pattern{averageWidth} = '*';
    # make sure there are no empty fields
    foreach my $field (@fields) {
        $pattern{$field} = '*' unless defined($pattern{$field});
    }
    my $new_fontstring = '-' . join('-', @pattern{@fields});

    my @possible;
    # $term->scr_add_lines("\r\nPattern is $new_fontstring\n");
    open(FOO, "xlsfonts -fn '$new_fontstring' | sort -u |") or die $!;
    while (<FOO>) {
        chomp;
        s/^-//;  # Strip leading '-' before split
        my @fontdata = split(/-/, $_);

        push @possible, [$fontdata[6], "-$_"];
        # $term->scr_add_lines("\r\npossibly $fontdata[6] $_\n");
    }
    close(FOO);

    if (!@possible) {
        die "No possible fonts!";
    }

    if ($basesize != 0) {
        # sort by font size, descending
        @possible = sort {$b->[0] <=> $a->[0]} @possible;

        # font is not the base font, so find the largest font that is at most
        # as large as the base font. If the largest possible font is smaller
        # than the base font bail and hope that a 0-size font can be found at
        # the end of the function
        if ($possible[0]->[0] > $basesize) {
            foreach my $candidate (@possible) {
                if ($candidate->[0] <= $basesize) {
                    return ($candidate->[1], $candidate->[0]);
                }
            }
        }
    } elsif ($change > 0) {
        # sort by font size, ascending
        @possible = sort {$a->[0] <=> $b->[0]} @possible;

        foreach my $candidate (@possible) {
            if ($candidate->[0] >= $font{pixelSize} + $change) {
                return ($candidate->[1], $candidate->[0]);
            }
        }
    } elsif ($change < 0) {
        # sort by font size, descending
        @possible = sort {$b->[0] <=> $a->[0]} @possible;

        foreach my $candidate (@possible) {
            if ($candidate->[0] <= $font{pixelSize} + $change && $candidate->[0] != 0) {
                return ($candidate->[1], $candidate->[0]);
            }
        }
    }

    # no fitting font available, check whether a 0-size font can be used to
    # fit the size of the base font
    @possible = sort {$a->[0] <=> $b->[0]} @possible;
    if ($basesize != 0 && $possible[0]->[0] == 0) {
        return ($possible[0]->[1], $basesize);
    } else {
        # if there is absolutely no smaller/larger font that can be used
        # return the current one, and beep if this is the base font
        if ($basesize == 0) {
            $term->scr_bell;
        }
        return ("-$fontstring", $font{pixelSize});
    }
}

sub font_apply_new
{
    my ($term, $newfont, $type, $save) = @_;

    # $term->scr_add_lines("\r\nnew font is $newfont\n");

    $term->cmd_parse("\033]" . $escapecodes{$type} . ";" . $newfont . "\033\\");

    # load the xrdb db
    # system("xrdb -load " . X_RESOURCES);

    if ($save > 0) {
        # merge the new values
        open(XRDB_MERGE, "| xrdb -merge") || die "can't fork: $!";
        local $SIG{PIPE} = sub { die "xrdb pipe broken" };
        print XRDB_MERGE "URxvt." . $type . ": " . $newfont;
        close(XRDB_MERGE) || die "bad xrdb: $! $?";
    }
}

vim

set nowrapscan
set ch=2
map <S-Insert> <MiddleMouse>
map! <S-Insert> <MiddleMouse>

set viminfo=\'1000,f1,<500,/50

au BufNewFile,BufRead *.eliom set filetype=ocaml

let c_comment_strings=1
syntax on
set hlsearch
set tags=./TAGS
"set mouse=a

set cindent
set smartindent
set autoindent
"set expandtab
set tabstop=3
set shiftwidth=3

set pastetoggle=<F10>

map ё `
map й q
map ц w
map у e
map к r
map е t
map н y
map г u
map ш i
map щ o
map з p
map х [
map ъ ]
map ф a
map ы s
map в d
map а f
map п g
map р h
map о j
map л k
map д l
map ж ;
map я z
map ч x
map с c
map м v
map и b
map т n
map ь m
map б ,
map ю .
map Ё ~
map Й Q
map Ц W
map У E
map К R
map Е T
map Н Y
map Г U
map Ш I
map Щ O
map З P
map Х {
map Ъ }
map Ф A
map Ы S
map В D
map А F
map П G
map Р H
map О J
map Л K
map Д L
map Ж :
map Э "
map Я Z
map Ч X
map С C
map М V
map И B
map Т N
map Ь M
map Б <
map Ю >
map . /

OPAM/Ocaml

" ## added by OPAM user-setup for vim / base ## 93ee63e278bdfc07d1139a748ed3fff2 ## you can edit, but keep this line
let s:opam_share_dir = system("opam config var share")
let s:opam_share_dir = substitute(s:opam_share_dir, '[\r\n]*$', '', '')

let s:opam_configuration = {}

function! OpamConfOcpIndent()
  execute "set rtp^=" . s:opam_share_dir . "/ocp-indent/vim"
endfunction
let s:opam_configuration['ocp-indent'] = function('OpamConfOcpIndent')

function! OpamConfOcpIndex()
  execute "set rtp+=" . s:opam_share_dir . "/ocp-index/vim"
endfunction
let s:opam_configuration['ocp-index'] = function('OpamConfOcpIndex')

function! OpamConfMerlin()
  let l:dir = s:opam_share_dir . "/merlin/vim"
  execute "set rtp+=" . l:dir
endfunction
let s:opam_configuration['merlin'] = function('OpamConfMerlin')

let s:opam_packages = ["ocp-indent", "ocp-index", "merlin"]
let s:opam_check_cmdline = ["opam list --installed --short --safe --color=never"] + s:opam_packages
let s:opam_available_tools = split(system(join(s:opam_check_cmdline)))
for tool in s:opam_packages
  " Respect package order (merlin should be after ocp-index)
  if count(s:opam_available_tools, tool) > 0
    call s:opam_configuration[tool]()
  endif
endfor
" ## end of OPAM user-setup addition for vim / base ## keep this line

xcompose

include "%S/en_US.UTF-8/Compose"
include "%L"
<Multi_key> <Cyrillic_yu> <Cyrillic_yu>     : "…"   ellipsis # HORIZONTAL ELLIPSIS
<Multi_key> <minus> <minus>                 : "–"   U2013 # EN DASH

<Multi_key> <Cyrillic_BE> <Cyrillic_BE>     : "«"
<Multi_key> <Cyrillic_YU> <Cyrillic_YU>     : "»"

<Multi_key> <grave> <grave>             : "́" U0301 # ударение
<Multi_key> <Cyrillic_io> <Cyrillic_io>     : "́" # ударениее (комбинирующееся)
<Multi_key> <G> <A> : "Α"    U0391    # GREEK CAPITAL LETTER ALPHA
<Multi_key> <G> <B> : "Β"    U0392    # GREEK CAPITAL LETTER BETA
<Multi_key> <G> <G> : "Γ"    U0393    # GREEK CAPITAL LETTER GAMMA
<Multi_key> <G> <D> : "Δ"    U0394    # GREEK CAPITAL LETTER DELTA
<Multi_key> <G> <E> : "Ε"    U0395    # GREEK CAPITAL LETTER EPSILON
<Multi_key> <G> <Z> : "Ζ"    U0396    # GREEK CAPITAL LETTER ZETA
<Multi_key> <G> <H> : "Η"    U0397    # GREEK CAPITAL LETTER ETA
<Multi_key> <G> <I> : "Ι"    U0399    # GREEK CAPITAL LETTER IOTA
<Multi_key> <G> <K> : "Κ"    U039A    # GREEK CAPITAL LETTER KAPPA
<Multi_key> <G> <L> : "Λ"    U039B    # GREEK CAPITAL LETTER LAMDA
<Multi_key> <G> <M> : "Μ"    U039C    # GREEK CAPITAL LETTER MU
<Multi_key> <G> <N> : "Ν"    U039D    # GREEK CAPITAL LETTER NU
<Multi_key> <G> <P> : "Π"    U03A0    # GREEK CAPITAL LETTER PI
<Multi_key> <G> <R> : "Ρ"    U03A1    # GREEK CAPITAL LETTER RHO
<Multi_key> <G> <S> : "Σ"    U03A3    # GREEK CAPITAL LETTER SIGMA
<Multi_key> <G> <T> : "Τ"    U03A4    # GREEK CAPITAL LETTER TAU
<Multi_key> <G> <U> : "Υ"    U03A5    # GREEK CAPITAL LETTER UPSILON
<Multi_key> <G> <F> : "Φ"    U03A6    # GREEK CAPITAL LETTER PHI
<Multi_key> <G> <X> : "Χ"    U03A7    # GREEK CAPITAL LETTER CHI
<Multi_key> <G> <O> : "Ω"    U03A9    # GREEK CAPITAL LETTER OMEGA

<Multi_key> <G> <Q> <T> : "Θ"    U0398    # GREEK CAPITAL LETTER THETA
<Multi_key> <G> <Q> <O> : "Ο"    U039F    # GREEK CAPITAL LETTER OMICRON
<Multi_key> <G> <Q> <X> : "Ξ"    U039E    # GREEK CAPITAL LETTER XI
<Multi_key> <G> <Q> <P> : "Ψ"    U03A8    # GREEK CAPITAL LETTER PSI

<Multi_key> <g> <a> : "α"    U03B1    # GREEK SMALL LETTER ALPHA
<Multi_key> <g> <b> : "β"    U03B2    # GREEK SMALL LETTER BETA
<Multi_key> <g> <g> : "γ"    U03B3    # GREEK SMALL LETTER GAMMA
<Multi_key> <g> <d> : "δ"    U03B4    # GREEK SMALL LETTER DELTA
<Multi_key> <g> <e> : "ε"    U03B5    # GREEK SMALL LETTER EPSILON
<Multi_key> <g> <z> : "ζ"    U03B6    # GREEK SMALL LETTER ZETA
<Multi_key> <g> <h> : "η"    U03B7    # GREEK SMALL LETTER ETA
<Multi_key> <g> <i> : "ι"    U03B9    # GREEK SMALL LETTER IOTA
<Multi_key> <g> <k> : "κ"    U03BA    # GREEK SMALL LETTER KAPPA
<Multi_key> <g> <l> : "λ"    U03BB    # GREEK SMALL LETTER LAMDA
<Multi_key> <g> <m> : "μ"    U03BC    # GREEK SMALL LETTER MU
<Multi_key> <g> <n> : "ν"    U03BD    # GREEK SMALL LETTER NU
<Multi_key> <g> <p> : "π"    U03C0    # GREEK SMALL LETTER PI
<Multi_key> <g> <r> : "ρ"    U03C1    # GREEK SMALL LETTER RHO
<Multi_key> <g> <s> : "σ"    U03C3    # GREEK SMALL LETTER SIGMA
<Multi_key> <g> <t> : "τ"    U03C4    # GREEK SMALL LETTER TAU
<Multi_key> <g> <u> : "υ"    U03C5    # GREEK SMALL LETTER UPSILON
<Multi_key> <g> <f> : "φ"    U03C6    # GREEK SMALL LETTER PHI
<Multi_key> <g> <x> : "χ"    U03C7    # GREEK SMALL LETTER CHI
<Multi_key> <g> <o> : "ω"    U03C9    # GREEK SMALL LETTER OMEGA

<Multi_key> <g> <q> <t> : "θ"    U03B8    # GREEK SMALL LETTER THETA
<Multi_key> <g> <q> <o> : "ο"    U03BF    # GREEK SMALL LETTER OMICRON
<Multi_key> <g> <q> <p> : "ψ"    U03C8    # GREEK SMALL LETTER PSI
<Multi_key> <g> <q> <s> : "ς"    U03C2    # GREEK SMALL LETTER FINAL SIGMA
<Multi_key> <g> <q> <x> : "ξ"    U03BE    # GREEK SMALL LETTER XI

xmobar

Config {
         font = "xft:Monospace-Bold:size=9"
       , borderColor = "#303030"
       , border = BottomBM 3
       , borderWidth = 1
       , bgColor = "black"
       , fgColor = "grey"
       , position = Static { xpos = 0, ypos = 0, width = 1600, height = 32 }
       , lowerOnStart = True
       , pickBroadest = False
       , persistent = False
       , hideOnStart = False
       , iconRoot = "."
       , allDesktops = True
       , overrideRedirect = False
       , commands = [
          Run Weather "UUDD" ["-t", "<tempC>C"
                             , "-L", "18", "-H", "25"
                             , "--normal", "green"
                             , "--high", "red"
                             , "--low", "lightblue" ] 36000
         , Run Memory ["-t","Mem: <usedratio>%"] 50
         , Run Swap [] 100
         , Run Date "%b %_d %Y %H:%M:%S" "date" 10
         , Run StdinReader

TODO

                                          ,      98%
      .
  . [            ](https://archives.haskell.org/projects.haskell.org/xmobar/#battery-args-refreshrate).
, Run BatteryP ["BAT1"]
  [
    "-t", "<acstatus>"
  , "-L", "20"
  , "-l", "red"
  , "--", "-O", "Charging", "-o", "<fc=white,red>Bat: <left>%</fc>"
  ] 10
]
, sepChar = "%"
, alignSep = "}{"
, template = "%StdinReader% }{ | %memory% * %swap% | %battery% | %UUDD% | %date% "
}

xmonad

import Data.IORef
import GHC.IO.Handle.Types
import XMonad
import XMonad.Config.Gnome
import XMonad.Util.EZConfig
import XMonad.Hooks.ManageDocks
import qualified XMonad.StackSet as Win
import XMonad.Hooks.ManageHelpers
import XMonad.Layout.NoBorders
import XMonad.Layout.Named
import XMonad.Layout.Grid
import XMonad.Util.Run
import XMonad.Hooks.EwmhDesktops

import XMonad.Hooks.DynamicLog

import XMonad.Prompt
import qualified XMonad.Prompt.Shell
import qualified XMonad.Prompt.Window

import XMonad.Util.Loggers
import XMonad.Util.Paste
import Data.Monoid
import Control.Monad (when, unless)

color1 = "#E1A81C"
color2 = "#290b64"
color3 = "#926b09"
color7 = "#808080"
color8 = "#404040"
color9 = "#101010"
color10 = "#f00000"

colorXmobarTitle = color1
colorXmobarWSHidden = color8
colorXmobarWSHiddenNoWindows = color8
colorXmobarWSActive = color10
colorXmobarSeparator = color7
colorXmobarLayoutRuBG = color10

colorPromptBG = color9
colorPromptFG = color1

colorBorderInactive = color2
colorBorderUS = color1
colorBorderRU = color10

role :: Query String
role = stringProperty "WM_WINDOW_ROLE"

gimpHooks :: Query (Endo WindowSet)
gimpHooks = composeAll [
             (role =? "gimp-toolbox" <||> role =? "gimp-image-window") --> (ask >>= doF . Win.sink)
            ]

myManageHook :: Query (Endo WindowSet)
myManageHook = composeAll [
                className =? "Xfce4-notifyd" -->  doIgnore
               , className =? "Do" --> doFloat
               , title =? "Eclipse" --> doFloat
               , className =? "skypeforlinux" --> doShift "5"
               , className =? "TelegramDesktop" --> doShift "5"
               , className =? "VirtualBox" --> doShift "8"
               , role =? "CallWindow" --> doFullFloat
               , composeOne [ isFullscreen -?> doFullFloat ]
               , gimpHooks
               ]

gridLayout = named "Grid" $ Grid

myLayouts =
    tiled ||| Mirror tiled ||| Full ||| gridLayout
        where
          tiled   = Tall nmaster delta ratio
          nmaster = 1
          ratio   = 2/3
          delta   = 3/100

myPromptConfig :: XPConfig
myPromptConfig = def {
                   height       = 25
                 , font         = "-misc-fixed-*-*-*-*-14-*-*-*-*-*-*-*"
                 , bgColor      = colorPromptBG
                 , fgColor      = colorPromptFG
                 }

myStartupHook = spawn "killall stalonetray; sleep 1; stalonetray &"

notify :: String -> String -> IO ()
notify timeout msg = safeSpawn "notify-send" ["-t", show timeout, msg]

get_internal_speakers :: IO String
get_internal_speakers = runProcessWithInput "sh" ["-c", "~/bin/sink_of_internal_speakers"] ""

setVolume :: String -> String -> IO ()
setVolume pct sink = spawn ("~/bin/set_sink_volume '" ++ sink ++ "' '" ++ pct ++ "'")

setVolumeInterval :: String -> X ()
setVolumeInterval pct =
    io (get_internal_speakers >>= setVolume pct)

getBrightness :: () -> IO String
getBrightness () = runProcessWithInput "sh" ["-c", "sleep 0.2; xbacklight -get|cut -d. -f1"] ""

setBrightness :: String -> X ()
setBrightness cmdKeys =
    io $ do
      spawn ("xbacklight -time 1 -steps 1 " ++ cmdKeys)
      v <- getBrightness ()
      notify "500" ("Brightness " ++ v)

setWindowBorder' :: String -> Window -> X ()
setWindowBorder' c w = do
    XConf { display = d } <- ask
    ~(Just pc) <- io $ initColor d c
    io $ setWindowBorder d w pc

data KeyboardLayout = KbdUs | KbdRu

stringOfKbdLayout KbdUs = "us"
stringOfKbdLayout KbdRu = "ru"

colorOfKbdLayout KbdUs = colorBorderUS
colorOfKbdLayout KbdRu = colorBorderRU

emacsKeyOfKbdLayout KbdUs = xK_F11
emacsKeyOfKbdLayout KbdRu = xK_F12

refreshKeyboardLayoutEmacs layout = do
  spawn $ "setxkbmap -layout " ++ stringOfKbdLayout KbdUs
  sendKey mod1Mask $ emacsKeyOfKbdLayout layout

refreshKeyboardLayoutOtherWindow layout =
    spawn $ "setxkbmap -layout " ++ stringOfKbdLayout layout

setKeyboardLayout currentKeyboardLayoutRef layout = do
  io $ writeIORef currentKeyboardLayoutRef layout
  withWindowSet $ \w -> case Win.peek w of
                          Nothing -> io $ return ()
                          Just w -> do
                            setWindowBorder' (colorOfKbdLayout layout) w
                            windowClass <- runQuery className w
                            case windowClass of
                              "Emacs" -> refreshKeyboardLayoutEmacs layout
                              _ -> refreshKeyboardLayoutOtherWindow layout

sendUpdateEvent :: () -> X ()
sendUpdateEvent _ =
    ask >>= logHook . config

setNewKeyboardLayout :: IORef KeyboardLayout -> KeyboardLayout -> X ()
setNewKeyboardLayout currentKeyboardLayoutRef layout =
    do
      setKeyboardLayout currentKeyboardLayoutRef layout
      sendUpdateEvent ()

loggerKbdLayout :: IORef KeyboardLayout -> XMonad.Util.Loggers.Logger
loggerKbdLayout currentKeyboardLayoutRef =
       io $ do layout <- readIORef currentKeyboardLayoutRef
               return $ Just $ case layout of
                                 KbdUs -> "<fc=white> ENG </fc>"
                                 KbdRu -> "<fc=white," ++ colorXmobarLayoutRuBG ++ "> RUS </fc>"
 0.2                            . .
     .                 , xautolock
                               .
lockScreen :: IORef KeyboardLayout -> X ()
lockScreen currentKeyboardLayoutRef =
    do
      setNewKeyboardLayout currentKeyboardLayoutRef KbdUs
      spawn "sleep 0.2; xautolock -locknow"

myConfig currentKeyboardLayoutRef = def {
             manageHook = manageDocks <+> myManageHook <+> manageHook gnomeConfig
           , layoutHook = avoidStruts $ smartBorders $ myLayouts
           , modMask = mod4Mask
           , terminal = "urxvtc"
           , startupHook = myStartupHook
           , borderWidth = 4
           , normalBorderColor  = colorBorderInactive
           , focusedBorderColor = colorBorderUS
           , handleEventHook    = fullscreenEventHook
           }
           `additionalKeys` [
                ((noModMask, xK_Menu), setNewKeyboardLayout currentKeyboardLayoutRef KbdUs)
                , ((controlMask, xK_Menu), setNewKeyboardLayout currentKeyboardLayoutRef KbdRu)
                , ((mod4Mask, xK_Print ), spawn "mate-screenshot -i") -- не работает именно на <Print> почему-то
                ]
           `additionalKeysP` [
                 ("M-n", spawn "firefox")
                , ("M-S-n c", spawn "google-chrome")
                , ("M-S-n i", spawn "google-chrome --incognito")
                , ("M-S-n f", spawn "firefox")

                -- other start
                , ("M-t", spawn "urxvtc")
                , ("M-e", spawn "editor")
                , ("S-M-<Space>", XMonad.Prompt.Shell.shellPrompt myPromptConfig)
                , ("M-S-s", XMonad.Prompt.Window.windowPromptGoto myPromptConfig)

                -- показать перевод слова из буфера обмена
                , ("M-S-t", spawn "sdcv -n \"`xclip -o | perl -C7 -pe 's/[^\\w-]//ig'`\" >/tmp/dict.txt 2>&1 ; editor /tmp/dict.txt")

                -- sound volume
                , ("<XF86AudioMute>", spawn "pactl -- set-sink-mute 0 toggle")
                , ("S-<XF86AudioMute>", setVolumeInterval "100%")
                , ("<XF86AudioLowerVolume>", setVolumeInterval "-2%")
                , ("<XF86AudioRaiseVolume>", setVolumeInterval "+2%")
                , ("S-<XF86AudioLowerVolume>", setVolumeInterval "-20%")
                , ("S-<XF86AudioRaiseVolume>", setVolumeInterval "+20%")

                -- LCD brightness
                , ("<XF86MonBrightnessUp>", setBrightness "-inc 10")
                , ("<XF86MonBrightnessDown>", setBrightness "-dec 10")
                , ("S-<XF86MonBrightnessUp>", setBrightness "-set 100")

                -- power management
                , ("<XF86Search>", lockScreen currentKeyboardLayoutRef)
                , ("<XF86HomePage>", lockScreen currentKeyboardLayoutRef)

                , ("M-f", withFocused $ windows . Win.sink)
                , ("M-d", sendMessage $ ToggleStrut L)

                , ("M-i d d q d", lockScreen currentKeyboardLayoutRef)

                , ("M-c c", spawn "emacsclient -c --eval '(org-capture)'")
                ]

xmobarPrinter :: IORef KeyboardLayout -> GHC.IO.Handle.Types.Handle -> PP
xmobarPrinter currentKeyboardLayoutRef handler
    = def {
        ppOutput   = hPutStrLn handler
      , ppTitle    = pangoColor colorXmobarTitle . pangoSanitize
      , ppExtras = [
         XMonad.Util.Loggers.logLayout
        , (loggerKbdLayout currentKeyboardLayoutRef)
        ]
      , ppOrder = \(workspace : _ : wintitle : layout : kbd : _ ) -> [kbd, workspace, layout, wintitle]
      , ppCurrent  = pangoColor colorXmobarWSActive . pangoSanitize
      , ppHidden   = pangoSanitize
      , ppHiddenNoWindows = pangoColor colorXmobarWSHiddenNoWindows . pangoSanitize
      , ppUrgent   = pangoColor "#E1340F"
      , ppLayout   = const ""
      , ppSep      = pangoColor colorXmobarSeparator " | "
      }

type WindowSwitchHook = Maybe Window -> Maybe Window -> X ()

windowSwitchHandler :: IORef (Maybe Window) -> WindowSwitchHook -> X () -> X ()
windowSwitchHandler lastWindowRef hook x = do
    withWindowSet $ \w -> do
        let currentWindow = Win.peek w
        lastWindow <- io $ readIORef lastWindowRef
        if lastWindow /= currentWindow then
            do
              io $ writeIORef lastWindowRef currentWindow
              hook lastWindow currentWindow
        else
            io $ return ()
    x

updateKbdLayout :: IORef KeyboardLayout -> Maybe Window -> Maybe Window -> X ()
updateKbdLayout currentKeyboardLayoutRef _ _ =
    do currentKeyboardLayout <- io $ readIORef currentKeyboardLayoutRef
       setKeyboardLayout currentKeyboardLayoutRef currentKeyboardLayout

main = do
  xmobarHdl <- spawnPipe "xmobar"
  do currentKeyboardLayoutRef <- newIORef KbdUs
     lastWindow <- newIORef Nothing
     xmonad (myConfig currentKeyboardLayoutRef) {
                  logHook = windowSwitchHandler lastWindow (updateKbdLayout currentKeyboardLayoutRef) $ dynamicLogWithPP (xmobarPrinter currentKeyboardLayoutRef xmobarHdl)
                }

pangoColor :: String -> String -> String
pangoColor fg = wrap left right
    where
      left  = "<fc=" ++ fg ++ ">"
      right = "</fc>"

pangoSanitize :: String -> String
pangoSanitize = Prelude.foldr sanitize ""
    where
      sanitize '>'  xs = "&gt;" ++ xs
      sanitize '<'  xs = "&lt;" ++ xs
      sanitize '\"' xs = "&quot;" ++ xs
      sanitize '&'  xs = "&amp;" ++ xs
      sanitize x    xs = x:xs

xmonad-session-rc

#!/bin/bash

export PATH=~/bin/:"$PATH"

xrdb -merge <~/.Xresources
hsetroot -center ~/Pictures/wallpaper-cron.jpg


parcellite &
blueman-applet &
nm-applet &
pasystray &

eval $(/usr/bin/gnome-keyring-daemon --start --components=pkcs11,secrets,ssh)
export SSH_AUTH_SOCK

urxvtd &
emacs --daemon &
synclient VertEdgeScroll=1

run-parts ~/.xmonad/xmonad-session-local.d
         xscreensaver          xautolock+i3lock              ,

xscreensaver .

UPDATE [2019-02-04 Mon]: i3lock , .

xset dpms 180 300 360 &
xautolock -time 3 -locker 'setxkbmap -layout us; i3lock -f -c "#101010"' &
                 xmonad,                                :
  • Caps en
  • Ctrl-Caps ru

             ,                         Caps      -
    ,                                            X Server.
                            CapsLock
    

    Menu, xmonad Menu Ctrl-Menu.

             ,    . .                :
    
setxkbmap -option ''
setxkbmap -layout 'us,ru'
      XCompose           Alt,            CapsLock

Menu:

setxkbmap -option 'compose:ralt,caps:menu,grp_led:caps'

X Server

xhost +si:localuser:root

xresources

Emacs.font: Monospace-10

Xcursor.size: 16

XTerm*background: black
XTerm*foreground: gray
XTerm*scrollTtyOutput: no
XTerm*scrollKey: yes

XTerm*faceSize: 10
Xft.antialias: true
Xft.dpi: 96
Xft.hinting: true
Xft.hintstyle: hintsfull
Xft.rgba: none

URxvt*geometry: 120x35
URxvt*font: xft:DejaVu Sans Mono-12
URxvt*background: black
URxvt*foreground: gray
URxvt*scrollstyle: rxvt
URxvt*termName: xterm
URxvt.perl-ext-common:  default,el-public-snippets,el-public-set-font-size
URxvt.urlLauncher:   chromium-browser
URxvt.matcher.button:   1
URxvt.colorUL:       #86a2be
URxvt*secondaryScroll: true
URxvt*fading: 40
URxvt*scrollBar: false

URxvt.keysym.C-Up:     perl:font-size:increase
URxvt.keysym.C-Down:   perl:font-size:decrease

URxvt*iso14755: False
URxvt*iso14755_52: False

! Home
URxvt*keysym.0xff50:    \033OH
! End
URxvt*keysym.0xff57:    \033OF

! Shift-PgUp
!URxvt*keysym.0xff55:      \033[5~
! Shift-PgDown
!URxvt*keysym.0xff56:      \033[6~

URxvt*keysym.0xffff:    \033[3~

*Rxvt*meta8: false
*Rxvt.keysym.C-Left: \033[1;5D
*Rxvt.keysym.C-Right: \033[1;5C
*Rxvt.keysym.M-Left: \033[1;3D
*Rxvt.keysym.M-Right: \033[1;3C
                            ,
                                 .          ,     urxvt
           PrintScreen          **                   **.
                           Insert.
                             .
URxvt*print-pipe: cat >/dev/null
                 : [dotfiles.sorg](/ox-hugo/dotfiles.sorg).

(tangle )

crontab

                           :
18 *            * * *           (wget -O ~/Pictures/wallpaper-cron.jpg http://images.opentopia.com/sunlight/world_sunlight_map_rectangular.jpg && DISPLAY=:0 hsetroot -center ~/Pictures/wallpaper-cron.jpg) >/dev/null 2>&1
         ssh-                  :
*/3 *           * * *           ~/bin/update-ssh-config >/dev/null 2>&1
                 :
*/10 *  * * *           (timeout -s9 120 ~/bin/offlineimap -o -1; notmuch new ) >/dev/null 2>&1
                      ,           org-mode     :
0	6		* * *		~/bin/john_apt_check_updates >/dev/null 2>&1
           BBDB   google-          :
21 21   * * *   python ~/bin/ASynK/asynk.py --op=sync --name main >/dev/null 2>&1
»

Боль­ше 10 лет про­дол­жа­ет­ся мое осо­зна­ное зна­ком­ст­во с Ocaml и око­ло 18 лет зна­ком (на уров­не лю­би­те­ля) с лиспом. Пожа­луй, се­йчас мож­но раз­ре­шить се­бе ска­зать пуб­лич­но: я счи­таю ООП лиш­ним в боль­шинст­ве за­дач. И се­йчас объ­яс­ню по­че­му.

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

В па­ра­диг­ме ООП на этом язы­ке ин­тер­фей­са удоб­но опи­сы­вать уст­ро­йст­во ми­ра, пря­мо как пи­шут в кни­жках: вот это класс всех об­ъек­тов, а эти име­ют ра­диус, и они кру­ги. А вот эти име­ют два ра­диу­са, по­то­му что эллип­сы. А ра­диус — то­же класс об­ъек­тов, «по­ло­жи­тель­ное чис­ло». То есть в рам­ках этой па­ра­диг­мы мы опи­сы­ва­ем ро­до­слов­ную об­ъек­тов, кто и ка­кие ка­кие ха­рак­те­рис­ти­ки (ге­ны) имеет. Даль­ше, им­ея вот эту из­дан­ную и за­ком­ми­чен­ную энцикло­пе­дию ви­дов, на язы­ке ре­али­за­ции на­чи­на­ет­ся им­пе­ра­тив­ное на­пи­са­ние ин­ст­рук­ций то­го, че­го же мы собст­вен­но хо­тим от об­ъек­тов это­го ми­ра. Пишем ин­ст­рук­ции для Listener ин­тер­фей­са, ко­то­рый бу­дет слать Events в фер­му Worker’ов, со­зда­ва­емых на Factory. Потом мы пи­шем ин­ст­рук­ции для Listerner что де­лать в слу­чае, ес­ли Worker по­сре­ди вы­пол­не­ния ин­ст­рук­ции «по­ст­ро­ить дом» по ка­кой-то при­чи­не ско­нчал­ся, не до­ст­ро­ив по­след­ний этаж.

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

Таким об­ра­зом, в функ­ци­ональ­ной па­ра­диг­ме уже на эт­апе про­ек­ти­ро­ва­ния мы опи­сы­ва­ем це­ли и граф пу­тей её до­сти­же­ния. Мне ка­жет­ся это бо­лее ес­тест­вен­ным спо­со­бом про­ек­ти­ро­ва­ния.

И здесь та­кая ин­дук­ция: ре­али­за­ция функ­ции (опи­са­ние кон­крет­но­го спо­со­ба пре­об­ра­зо­ва­ния из ти­па A в тип B) то­же есть цель, ко­то­рую точ­но так же не­об­хо­ди­мо вы­вес­ти, пу­тем по­ст­ро­ения ми­ни­маль­но­го до­ста­точ­но­го гра­фа спо­со­бов по­лу­че­ния B из A. То есть, язык ре­али­за­ции функ­ции в не­ко­то­ром смыс­ле ста­но­вит­ся язы­ком, о тер­ми­нах ко­то­ро­го бу­дут рас­су­ждать уже в свою оче­редь при на­пи­са­нии функ­ций под­це­лей этой функ­ции.

В ми­ре ООП есть тен­ден­ция к пе­ре­хо­ду от спо­со­ба рас­суж­де­ния в де­кла­ра­ци­ях ви­да «ме­тод m со­вмес­тим с об­ъек­та­ми клас­са B» к де­кла­ра­ци­ям ви­да «ме­тод m ждет лю­бой об­ъект со свойст­ва­ми P₁ + P₂». Ниче­го не хо­чу ска­зать, но во­об­ще-то это чис­тая функ­ци­ональ­щи­на :-)

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

»

Искрен­не не по­ни­маю, по­че­му я поль­зу­юсь Емак­сом вот уже 17 лет, а Org Mode вклю­чил в свою жизнь толь­ко не­дав­но (по­то­му что до не­дав­них пор его под­дер­жка на Android бы­ла очень груст­ной?)

Как у лю­бо­го за­мо­ро­чен­но­го outdoor-ак­ти­вис­та, у ме­ня есть па­чка раз­лич­ных ша­блон­ных за­пи­сей для ор­га­ни­за­ции оче­ред­ной по­езд­ки. Сей­час с по­мощью не­боль­шо­го ко­ли­чест­ва ко­да сде­лал так, что­бы пла­ни­ро­ва­ние оче­ред­ной экс­пе­ди­ции за­ни­ма­ло букваль­но ми­ну­ту. Речь им­ен­но о пла­ни­ро­ва­нии под­го­то­ви­тель­ных дел: ре­монт сна­ря­же­ния, за­куп­ки, стра­хов­ки, транспорт и т.д.

Шаб­лон экс­пе­ди­ции имеет при­мер­но та­кой вид:

#+CATEGORY: %category%

* %event%
  :PROPERTIES:
  :CREATED:  %t
  :END:

** Подготовка дома
*** TODO Заказать литиевые батарейки
   SCHEDULED: %date_5_months%
*** TODO Купить билеты
   SCHEDULED: %date_5_months%

... и ещё сотня пунктов ...

Преж­де мне при­хо­ди­лось вруч­ную рас­став­лять да­ты в план. Сей­час этот ша­блон об­ра­ба­ты­ва­ет­ся та­ким ку­соч­ком ко­да:

(require 's)

;; Генератор шаблона
(defun sport/expedition-template ()
  (interactive)
  (let* ((event (org-completing-read-no-i "Название экспедиции: " nil))
         (category (org-completing-read-no-i "Категория в org: " nil))
         (date-input (org-read-date nil t nil "Начало экспедиции: "))
         (date-fmt (car org-time-stamp-formats))
         (date-5-months (format-time-string date-fmt (time-subtract date-input (seconds-to-time (* 86400 30 5)))))
         (date-1-month (format-time-string date-fmt (time-subtract date-input (seconds-to-time (* 86400 30)))))
         (date-2-weeks (format-time-string date-fmt (time-subtract date-input (seconds-to-time (* 86400 7 2)))))
         (date-1-week (format-time-string date-fmt (time-subtract date-input (seconds-to-time (* 86400 7)))))
         (date-start (format-time-string date-fmt date-input))
         (src-tpl (with-temp-buffer
                    (insert-file-contents "~/org/personal/sport/организация-экспедиции.org.tpl")
                    (buffer-string)))
         (tpl
          (s-replace
           "%event%" event
           (s-replace
            "%category%" category
            (s-replace
             "%date_5_months%" date-5-months
             (s-replace
              "%date_1_month%" date-1-month
              (s-replace
               "%date_2_weeks%" date-2-weeks
               (s-replace
                "%date_1_week%" date-1-week
                (s-replace "%date_start%" date-start src-tpl)))))))))
    tpl))

;; Подключение генератора
(custom-set-variables
  '(org-capture-templates
    (quote
     (("e" "Экспедиция/мероприятие" plain
       (file (lambda () (read-file-name "Как назвать файл экспы: " "~/org/personal/sport/")))
       (function sport/expedition-template))))))

Теперь до­ста­точ­но на­жать C-c c e, вбить на­зва­ние экс­пе­ди­ции, ка­те­го­рию (для по­ряд­ка в org-agenda), имя фа­йла и вы­брать да­ту на­ча­ла экс­пы. Всё, план со­став­лен! Даль­ше мне ос­та­ет­ся его при не­об­хо­ди­мос­ти под­ре­дак­ти­ро­вать и прос­то ему сле­до­вать. Пре­бы­ваю в бур­ном во­стор­ге!

Внут­ри org-фа­йла ша­бло­на па­чка таб­ли­чек и Perl-скрип­тов для ра­бо­ты с ни­ми — они счи­та­ют при­мер­ные рас­хо­ды по день­гам/га­зу, ор­га­ни­зу­ют ми­ни­маль­ную ло­гис­ти­ку и т.д. Но об этом как-ни­будь в дру­гой раз.

»

Пить или не пить?

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

В клас­си­чес­кой ма­те­ма­ти­чес­кой ло­ги­ке есть та­кое по­ня­тие: про­по­зи­ция. Она же утверж­де­ние, суж­де­ние и т.д. Утверж­де­ние мо­жет быть вер­ным, вер­ным мо­жет быть и его от­ри­ца­ние. Пока вро­де всё нор­маль­но:

int p=1;
if (p) {
   printf ("OK!\n");
}

int p=0;
if (!p) {
   printf ("OK!\n");
}

Но это — бу­ле­ва ло­ги­ка. В бо­лее об­щем опи­са­нии по­ня­тие утверж­де­ния мож­но сфор­му­ли­ро­вать так:

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

Есть суж­де­ния, ко­то­рые вер­ны (до­ка­за­ны) по опре­де­ле­нию. Если по­мни­те школь­ную ма­те­ма­ти­ку, их там на­зы­ва­ли ак­си­ома­ми или ги­по­те­за­ми. А те­перь лов­кость рук.

Вот спи­сок ак­си­ом в не­кой все­лен­ной:

  • Ког­да у Пет­ро­ви­ча зарп­ла­та, он пь­ян.
  • Ког­да у Пет­ро­ви­ча зарп­ла­та, он тре­зв.

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

Нет, со все­лен­ной всё хо­ро­шо. Пото­му что ни из че­го не следу­ет, что Пет­ро­ви­чу вы­да­ли зарп­ла­ту, а сле­до­ва­тель­но во все­лен­ной нет по­во­да для про­ти­во­ре­чий. Что­бы воз­ник­ло про­ти­во­ре­чие и все­лен­ная раз­ру­ши­лась, не­об­хо­ди­мо ввес­ти ещё од­ну ак­си­ому:

  • У Пет­ро­ви­ча зарп­ла­та.

И да, и нет?

На са­мом де­ле, ди­лем­ма Пет­ро­ви­ча сво­дит­ся к за­ко­ну про­ти­во­ре­чия, на ко­то­ром по­ст­ро­ена ма­те­ма­ти­ка и чуть ли не все точ­ные на­уки:

\begin{equation} P \land \neg P \implies \bot \end{equation}

«Утверж­де­ние \(P\) од­но­вре­мен­но с утверж­де­ни­ем \(\text{not } P\) — аб­сурд­но, не имеет до­ка­за­тельств». Но преж­де на­до на­шей все­лен­ной ока­зать­ся в та­кой по­зе, что­бы вот это «не имеет до­ка­за­тельств» из за­ко­на про­ти­во­ре­чия вы­ст­ре­ли­ло. Поэ­то­му зарп­ла­ты Пет­ро­ви­чу не ви­дать.

Для на­ча­ла, фор­ма­лизу­ем Пет­ро­ви­ча, ко­то­рый счаст­лив, по­то­му что пьёт:

(* Определим список необходимых переменных, которые имеют тип «пропозиция» (утверждение) *)
Variables PetrovichHasSalary PetrovichDrinks PetrovichIsHappy : Prop.

(* Аксиома a1 : когда верно утверждение что у Петровича зарплата, верно и утверждение, что он пьёт *)
Axiom a1 : PetrovichHasSalary -> PetrovichDrinks.

(* Аксиома a2 : когда верно утверждение что Петрович пьёт, верно и утверждение о его счастливой жизни *)
Axiom a2 : PetrovichDrinks -> PetrovichIsHappy.

(* Аксиома a3 : безусловно доказывает, что зарплата у Петровича есть *)
Axiom a3 : PetrovichHasSalary.

(* Теорема 'he_is_happy' к доказательству: Петрович счастлив *)
Theorem he_is_happy : PetrovichIsHappy.
  (* Нам надо доказать верность утверждения PetrovichIsHappy. Из аксиомы a2 известно, что он счастлив,
     когда он пьёт. Применим это утверждение. *)
  apply a2.
  (* После применения a2 надо доказать, что Петрович пьёт. Верность этого утверждение может следовать из
     аксиомы a1. Попробуем её применить *)
  apply a1.
  (* Теперь осталось доказать, что у Петровича есть зарплата. Это легко, ведь именно это утверждает
     аксиома a3. Мы можем её применить, а можем подставить в качестве доказуемого. Варианты
     применения тактик:
       - apply a3.
       - exact a3.
     Для разнообразия, применим тактику exact *)
  exact a3.
  (* Подзадач не осталось. Теорема доказана *)
Qed.

Теперь мо­жем ис­поль­зо­вать те­оре­му he_is_happy в ка­чест­ве те­зи­са вся­кий раз ко­гда на­до по­ка­зать, что Пет­ро­вич счаст­лив.

Арг­умен­ти­ро­ван­ные утверж­де­ния (но не со­всем)

А что ес­ли пить предст­оит Пет­ро­ви­чу, а зарп­ла­та у же­ны Марьи? Как нам это об­озна­чить? На по­мощь при­хо­дят пре­ди­ка­ты, или утверж­де­ния с ар­гу­мен­та­ми. Для на­ча­ла, пе­ре­пи­шем до­ка­за­тельст­во вы­ше с по­мощью пре­ди­ка­тов:

(* Утверждение "человек" *)
Variable Human : Prop.

(* Петрович и Марья - люди *)
Variables Petrovich Maria : Human.

(* Утверждение "оно пьёт" требует указания личности *)
Variable IsDrinking : Human -> Prop.

(* Аналогично с утверждениями "оно хранит зарплату" и "оно счастливо" *)
Variable HoldingSalary : Human -> Prop.
Variable IsHappy : Human -> Prop.

(* Аксиома a1 : когда верно утверждение что у Петровича зарплата, верно и утверждение что он пьёт *)
Axiom a1 : HoldingSalary Petrovich -> IsDrinking Petrovich.

(* Аксиома a2 : пьющий Петрович — счастливый Петрович *)
Axiom a2 : IsDrinking Petrovich -> IsHappy Petrovich.

(* Аксиома a3 : эта аксиома доказывает, что зарплата у Петровича всё же есть *)
Axiom a3 : HoldingSalary Petrovich.

(* Теорема 'petrovich_is_happy' к доказательству: Петрович счастлив *)
Theorem petrovich_is_happy : IsHappy Petrovich.
  apply a2.
  apply a1.
  exact a3.
Qed.

Теперь мож­но по­про­бо­вать до­ка­зать, что Пет­ро­вич не­счаст­лив, ко­гда Марья успе­ла ото­брать зарп­ла­ту. Для это­го нам не­об­хо­ди­мо ввес­ти не­сколь­ко уточ­ня­ющих им­пли­ка­ций (свя­зей):

  • Пет­ро­вич не­счаст­лив, ко­гда не пьёт
  • Пет­ро­вич не пьёт, ко­гда нет де­нег
  • Зар­пла­та мо­жет быть толь­ко у од­но­го из них
Variable Human : Prop.
Variables Petrovich Maria : Human.
Variable IsDrinking : Human -> Prop.
Variable HoldingSalary : Human -> Prop.
Variable IsHappy : Human -> Prop.

Axiom a1 : HoldingSalary Petrovich -> IsDrinking Petrovich.
Axiom a2 : IsDrinking Petrovich -> IsHappy Petrovich.

(* Зарплата в этот раз у Марьи *)
Axiom a3 : HoldingSalary Maria.

(* Без водки счастья нет *)
Axiom a4 : ~ IsDrinking Petrovich -> ~ IsHappy Petrovich.
(* Без зарплаты нет водки *)
Axiom a5 : ~ HoldingSalary Petrovich -> ~ IsDrinking Petrovich.
(* Когда зарплата у Марьи - у Петровича её нет *)
Axiom a6 : HoldingSalary Maria -> ~ HoldingSalary Petrovich.

(* Теорема 'petrovich_is_unhappy' к доказательству: счастья у Петровича нет *)
Theorem petrovich_is_unhappy : ~ IsHappy Petrovich.
  (* Без водки счастья нет *)
  apply a4.
  (* Нет водки, поскольку нет зарплаты *)
  apply a5.
  (* Надо доказать, что зарплаты у Петровича нет. Применим аксиому a6, которая доказывает, что
     у Петровича действительно нет зарплаты, когда она у Марьи *)
  apply a6.
  (* ... А зарплата действительно у Марьи, ведь это утверждает аксиома a3 *)
  apply a3.
Qed.

Кста­ти, мож­но по­смот­реть до­ка­за­тельст­во те­оре­мы с по­мощью ко­ман­ды Print petrovich_is_unhappy.:

petrovich_is_unhappy = a4 (a5 (a6 a3))
     : ~ IsHappy Petrovich

Абсурд и без­до­ка­за­тель­ность

В Coq есть два утверж­де­ния, на­зва­ния для ко­то­рых, по мне­нию мно­гих, вы­бра­ны не со­всем удач­но:

  • True, она же \(\top\). Это утверж­де­ние, ко­то­рое вс­ег­да до­ка­за­но.
  • False, или \(\bot\). Утверж­де­ние, не име­ющее до­ка­за­тельств.

На са­мом де­ле, от­ри­ца­ние утверж­де­ния (\(\neg P\)) рав­но­силь­но им­пли­ка­ции ви­да:

\begin{equation} P \implies \bot \end{equation}

То есть, букваль­но, «Из \(P\) следу­ет, что до­ка­за­тельст­ва не су­щест­ву­ют». А раз до­ка­за­но, что нет до­ка­за­тельств \(P\), то до­ка­за­но от­ри­ца­ние \(P\).

Без­до­ка­за­тель­ность мож­но до­ка­зать, и ино­гда это очень удоб­но. Дока­жем, что ко­гда од­но­вре­мен­но вер­но «быть» и «не быть», то кон­ст­рук­ция слиш­ком шат­ка, что­бы быть вер­ной:

(* Объявляем секцию. Поскольку ниже мы захотим видоизменять гипотезы (они же переменные),
   необходимо локализовать их в секции *)
Section ToBeAndNotToBe.
  Variable Be : Prop.

  (* Гипотеза доказывает утверждение «быть» *)
  Variable to_be : Be.

  (* Гипотеза доказывает утверждение «не быть» *)
  Variable not_to_be : ~ Be.

  (* Необходимо доказать, что это не имеет доказательств *)
  Theorem this_is_absurdity : False.

    (* Гипотезу «не быть» заменим на утверждение «быть → не имеет доказательств» *)
    unfold not in not_to_be.
    (* Поскольку нам надо доказать False («не имеет доказательств»), мы можем смело
       применить нашу видоизмененную not_to_be *)
    apply not_to_be.
    (* Теперь нам осталось доказать «быть». Это легко т.к. именно это утверждает гипотеза to_be *)
    apply to_be.
  Qed.
  (* Всё, после доказательства to_be доказывать больше нечего.
     Теорема об отсутствии доказательств доказана *)
End ToBeAndNotToBe.

Горе Пет­ро­ви­ча

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

Section PetrovichInTrouble.
  Variable Human : Prop.
  Variables Petrovich Maria : Human.
  Variable IsDrinking : Human -> Prop.
  Variable HoldingSalary : Human -> Prop.
  Variable IsHappy : Human -> Prop.

  Axiom a1 : HoldingSalary Petrovich -> IsDrinking Petrovich.
  Axiom a2 : IsDrinking Petrovich -> IsHappy Petrovich.
  Axiom a3 : HoldingSalary Maria.
  Axiom a4 : ~ IsDrinking Petrovich -> ~ IsHappy Petrovich.
  Axiom a5 : ~ HoldingSalary Petrovich -> ~ IsDrinking Petrovich.
  Axiom a6 : HoldingSalary Maria -> ~ HoldingSalary Petrovich.

  (* Предположим, что ни смотря ни на что Петрович всё ещё счастлив *)
  Axiom a7 : IsHappy Petrovich.

  (* Задача доказать невозможность такой комбинации аксиом *)
  Theorem sorry_petrovich : False.
    (* Чтобы доказать абсурдность, воспользуемся законом противоречия, который утверждает False, когда
       пропозиция одновременно и верна, и не верна. Докажем, что у нас одновременно верно утверждение,
       что Петрович счастлив и верно обратно утверждение. Знакомьтесь, тактика absurd *)
    absurd (IsHappy Petrovich).
    (* Строчка выше доказала False, но теперь предлагает доказать верность двух новых утверждений.
       Сначала докажем, что утверждение о счастье Петровича неверно: *)
    apply a4.
    apply a5.
    apply a6.
    apply a3.
    (* Теперь докажем, что утверждение о счастье Петровича верно: *)
    apply a7.
  Qed.
  (* Больше доказывать нечего, абсурдность доказана *)
End PetrovichInTrouble.

Бес­цен­ные ссыл­ки

»

По ра­бо­те раз­би­рал­ся с SELinux под CentOS, на­пи­сал для се­бя до­ку­мен­та­цию. Решил по­де­лить­ся.

Подго­то­вка

Поста­вить па­ке­ты (CentOS)

yum install setools-console policycoreutils-python

Конц­епт

Поли­ти­ки

Для вс­ей си­сте­мы за­да­ет­ся policy, ко­то­рая бу­дет при­ме­не­на ко всем об­ъек­там. По умо­лча­нию дейст­ву­ет по­ли­ти­ка targeted. Поли­ти­ка за­да­ет­ся в фа­йле /etc/sysconfig/selinux

Крат­ко:

  • targeted: всем всё раз­ре­ше­но, за ис­клю­че­ни­ем про­цес­сов, для ко­то­рых пра­ви­ла яв­но за­да­ны (в CentOS их до­воль­но мно­го, в ос­нов­ном се­те­вые де­мо­ны)
  • strict: то же что и targeted, толь­ко до­ступ ог­ра­ни­чен и не толь­ко се­те­вым про­цес­сам
  • minimum (в CentOS ста­вит­ся от­дель­но, па­ке­том selinux-policy-minimum): targeted, толь­ко по умо­лча­нию со­всем нет ни­ка­ких пра­вил
  • MLS: ?

Режи­мы SELinux

  • disabled: SELinux вы­клю­чен
  • enforcing: вклю­чен, пра­ви­ла бло­ки­ру­ют, со­об­ще­ния о бло­ки­ров­ке ухо­дят в /var/log/audit/audit.log
  • permissive: ни­че­го не бло­ки­ру­ет­ся, толь­ко ухо­дят со­об­ще­ния в /var/log/audit/audit.log

Пере­клю­чить­ся в permissive:

setenforce 0

Пере­клю­чить­ся в enforcing:

setenforce 1

Кон­тек­сты

Каж­дый об­ъект ми­ра (файл, про­цесс и т.д.) име­ют свойст­во context. В SELinux со­вер­шен­но не важ­но кто и ку­да сде­лал su, fork и т.д. — кон­текст на­следу­ет­ся до тех пор, по­ка яв­но в пра­ви­лах SELinux не ска­за­но, что на­до сме­нить кон­текст

Каж­дый кон­текст со­сто­ит из трех час­тей:

  1. user
  2. role
  3. type (у про­цес­сов его при­ня­то на­зы­вать domain, но раз­ни­цы ни­ка­кой нет)

Для фа­йлов кон­текст мож­но по­смот­реть так:

# ls -dZ /
dr-xr-xr-x. root root system_u:object_r:root_t:s0      /

Здесь:

  • system_u: user
  • object_r: role
  • s0: type

Ана­ло­гич­но, мож­но по­смот­реть для про­цес­сов:

# ps auxwZ|head -2
LABEL                           USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
system_u:system_r:init_t:s0     root         1  0.0  0.3  19236  1572 ?        Ss   10:54   0:00 /sbin/init

То, что у /sbin/init и у кор­не­вой ди­рек­то­рии оди­на­ко­вый кон­текст ни о чём не го­во­рит. Пра­ва на до­ступ к кор­ню у init бу­дут толь­ко ес­ли об этом яв­но ска­за­но в пра­ви­лах по­ли­ти­ки.

Точ­но так же мож­но узнать кон­текст те­ку­ще­го шел­ла:

# id -Z
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

Аудит

В ре­жи­мах enforcing и permissive си­сте­ма пи­шет в лог /var/log/audit/audit.log обо всех со­бы­ти­ях безопа­снос­ти. Этот лог по­ле­зен как при руч­ном со­зда­нии пра­вил, так и с по­мощью audit2allow.

Если auditd не за­пу­щен, со­об­ще­ния уй­дут в /var/log/messages.

Соз­да­ние но­вых пра­вил

Лучше все­го это де­лать че­рез на­пи­са­ние мо­ду­ля. Модуль SELinux мож­но под­гр­ужать, вы­гру­жать, рас­прост­ра­нять. Удоб­но.

Общий план

  • Соз­дать но­вый type для за­пус­ка­емо­го фа­йла
  • Соз­дать но­вый type, ко­то­рый бу­дет domain ра­бо­та­юще­го при­ло­же­ния
  • По умо­лча­нию у но­во­го до­ме­на нет ни­ка­ких прав. Поэ­то­му его мож­но за­пус­тить в permissive mode
  • С по­мощью audit2allow со­здать ша­блон пра­вил, от­ре­дак­ти­ро­вать их.
  • Вклю­чить ре­жим enforcing.

Соз­да­ние мо­ду­ля

Подго­то­вка

mkdir "/root/selinux_my_module"
cd "/root/selinux_my_module"
ln -s "/usr/share/selinux/devel/Makefile"
vim "my_module.te"

Соз­да­ние ми­ни­маль­но­го мо­ду­ля

module my_module 1.0;

require {
  type unconfined_t;
  class process { transition sigchld };
  class file { read x_file_perms };
}

type mytype_t;
type mytype_exec_t;

role unconfined_r types mytype_t;

type_transition unconfined_t mytype_exec_t : process mytype_t;

Сбор­ка и за­груз­ка

make && make load

Заг­ру­зить за­но­во по­сле пе­рес­бор­ки:

make && make reload

Про­вер­ка

cp /bin/date /root/test
chcon -t mytype_exec_t /root/test
setenforce 1
/root/test

Сей­час у би­нар­ни­ка нет ни на что прав, по­это­му в по­след­ней строч­ке дол­жны по­лу­чить /root/test: Permission denied.

Добав­ле­ние пра­вил с по­мощью audit2allow

Ути­ли­та пред­на­зна­че­на для ге­не­ра­ции пра­вил на ос­но­ве ло­га ау­ди­та.

grep mytype /var/log/audit/audit.log | audit2allow -m mytype >my_module.te

Теперь мож­но по­пра­вить мо­дуль, за­тем за­но­во его за­гру­зить.

Полез­ные пра­ви­ла

Воз­мож­ность ме­нять type об­рат­но

По умо­лча­ни

...
require {
...
   class file { ... relabelfrom ... }
}

allow unconfined_t mytype_exec_t:file { ... relabelfrom ... }

Базо­вые пра­ви­ла для за­пус­ка фа­йла

# доступ к /
allow mytype_t root_t:dir search;

# разрешить unconfined_t запускать mytype_exec_t
allow unconfined_t mytype_exec_t:file { read getattr open execute };

# разрешить unconfined_t работу с процессом
allow unconfined_t mytype_t:process { siginh rlimitinh transition
noatsecure };

# разрешить unconfined_t менять контекст бинарника и переименовывать его
allow unconfined_t mytype_exec_t:file { rename relabelfrom relabelto };
allow mytype_exec_t fs_t:filesystem associate;

При­мер го­то­во­го мо­ду­ля

Разре­шить би­нар­ни­ку с type mytype_t до­ступ толь­ко к /tmp

module mytype 1.0;

require {
        type unconfined_t;
	type tmp_t;
	type root_t;
	type sshd_t;
	type mytype_t;
	type mytype_exec_t;
	class dir { getattr append read write lock create rename link rmdir open search };
	class file { read entrypoint getattr open execute rename relabelfrom relabelto };
	class process { siginh transition sigchld noatsecure rlimitinh };
	type user_devpts_t;
	class chr_file { open read write getattr append ioctl };
	class fd use;
	class filesystem associate;
}

#============= mytype_t ==============

# access to /
allow mytype_t root_t:dir search;

# access /tmp
allow mytype_t tmp_t:dir { getattr append read write lock create rename link rmdir open search };

# allow general context to run me
allow unconfined_t mytype_exec_t:file { read getattr open execute };

# allow general context to work with my process
allow unconfined_t mytype_t:process { siginh rlimitinh transition noatsecure };

# allow me to write to charcter device
allow mytype_t user_devpts_t:chr_file { read write getattr append ioctl };

# allow general context to switch into my context
type_transition unconfined_t mytype_exec_t : process mytype_t;
allow mytype_t mytype_exec_t:file entrypoint;

# allow me to send SIGCHLD to parent
allow mytype_t unconfined_t:process sigchld;

# allow general context to change to/from my context
allow unconfined_t mytype_exec_t:file { rename relabelfrom relabelto };


## access to pty in SSH session ##
allow mytype_t sshd_t:fd use;

При­мер би­нар­ни­ка для тес­тов:

#include <stdio.h>
#include <dirent.h>
#include <errno.h>

void ls (char *dir) {
  printf("\nNow will list %s\n", dir);

  DIR *dirp = opendir(dir);
  if (NULL == dirp) {
    printf("Error opening dir %i\n", errno);
  }

  struct dirent *dp;
  errno = 0;
  while (dirp) {
    if ((dp = readdir(dirp)) != NULL) {
      printf("%s\n", dp->d_name);
    } else {
      if (errno) {
        printf("Error readding dir %i\n", errno);
      }
      if (closedir(dirp)) {
        printf("Error closing dir %i\n", errno);
      }
      return;
    }
  }
  return;
}

int main() {
  ls ("/tmp");
  ls ("/etc");

  return 0;
}

Соб­ирать ко­неч­но на­до ста­ти­ком:

gcc -static a.c

После это­го не за­быть по­ме­нять context:

mv a.out /tmp
chcon -t mytype_t /tmp/a.out

Пра­ви­ло для policy “minimum”

Если уста­нов­лен minimum, то не­об­хо­ди­мо всем поль­зо­ва­те­лям ро­ли unconfined_r (а это в по­ли­ти­ке minumum все поль­зо­ва­те­ли) до­ступ к ти­пу mytype_t.

role unconfined_r types mytype_t;

Огра­ни­че­ние толь­ко од­но­го би­нар­ни­ка

  1. Поста­вить па­кет selinux-policy-minimum (CentOS)
  2. Про­пи­сать этот policy в /etc/selinux/config
  3. reboot (мо­жет быть дол­гим ес­ли на­чнет­ся relabeling)
  4. Соз­дать мо­дуль, не за­быть про Пра­ви­ло для policy “minimum”

Про­чее

Рас­прост­ра­не­ние с мо­ду­лем кон­текс­тов фа­йлов

В ди­рек­то­рии с мо­ду­лем до­ста­точ­но со­здать файл ви­да:

/usr/lib64/libmagic.* --      system_u:object_r:image_magick_lib_t:s0
...
...

Он точ­но так же со­бе­рет­ся и бу­дет уста­нов­лен с по­мощью make && make reload

Обно­вле­ние кон­текс­тов фа­йлов со­глас­но за­гру­жен­ным пра­ви­лам

Про­ве­рить что и как бу­дет об­но­вле­но:

fixfiles check /usr/lib64/

Обно­вить:

fixfiles relabel /usr/lib64/

Полез­ные ссыл­ки

»

Есть в яд­ре Linux от­лич­ная шту­ка: OProfile. Это ста­тис­ти­чес­кий ана­ли­за­тор на­груз­ки на си­сте­му. Рабо­та­ет так: не­сколь­ко раз в се­кун­ду оно оп­ра­ши­ва­ет яд­ро на пред­мет в ка­ком кон­тек­сте (т.е. в ка­кой функ­ции) ка­кие яд­ра CPU на­хо­дят­ся. Соб­ира­ет­ся ста­тис­ти­ка, за­тем мож­но по­смот­реть ли­де­ров. Это очень удоб­ный и прос­той спо­соб про­фи­ли­ро­ва­ния си­сте­мы.

И вот од­наж­ды при­шла в го­ло­ву мысль на­пи­сать ана­ло­гич­ный ана­ли­за­тор для MySQL. Реше­ние по­лу­чи­лось не­ожи­дан­но удач­ным и да­же при­обре­ло не­ко­то­рый ком­мер­чес­кий успех. Мне не жал­ко, по­это­му де­люсь: mysql_query_stats.

Рядом ле­жит вспо­мо­га­тель­ный скрипт, ко­то­рый ищ­ет по­хо­жие за­про­сы по ша­бло­ну: mysql_similar_queries

За ссыл­ку на ав­то­ра бу­ду ко­неч­но бла­го­да­рен, в скрипт ко­пи­ра­йты обе­щаю ко­гда-ни­будь до­ба­вить ;-) Всем до­бра.

»

Почти слу­чай­но на­пи­сал Perl-мо­дуль для рас­ста­нов­ки пе­ре­но­сов на ос­но­ве прос­той не­йро­се­ти с об­рат­ным рас­прост­ра­не­ни­ем. Обу­чить мож­но рас­став­лять пе­ре­но­сы в лю­бом язы­ке. При до­ста­точ­но боль­шом кор­пу­се вход­ных текс­тов на­вер­ня­ка мож­но на­учить ле­чить про­бле­мы ти­па е/ё, -ться/-тся1 и т.д. Взять мож­но на GitHub. В ком­пле­кте ми­ни­маль­ная POD-до­ку­мен­та­ция и при­мер для рус­ско­го язы­ка с уже обу­чен­ной сетью. Точ­ность с те­ку­щей сетью оце­ни­вать не бе­русь, но на глаз 90% — слиш­ком ма­ло вход­ных дан­ных. Обу­чал на кор­пу­се, по­лу­чен­ном с по­мощью алго­ри­тма Ляна-Кну­та2.


  1. С -тся яв­но лучше пе­рей­ти на свёрт­ки ^
  2. ис­поль­зу­ет­ся в \(\LaTeX\) ^
»

.

. :

[2018-08-17 Fri]

U. Meyer: Algorithms for Memory Hierarchies: Advanced Lectures

[2018-03-17 Sat]

                  ,
      .

. :

[2017-03-18 Sat]

utf8 “\xAB” does not map to Unicode at /home/evgenii/bin/hyphen/russian/russian-hyphen.pl line 64. utf8 “\xBB” does not map to Unicode at /home/evgenii/bin/hyphen/russian/russian-hyphen.pl line 64. . \xAB Linux 24 \xBB.

. : .

[2016-06-15 Wed]

. :

[2015-06-11 Thu]

. . : Haskell

[2010-10-04 Mon]