From 4ccac59932f2ca3ebc9ba30fb0bc0aa8aac0d630 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Tue, 8 Nov 2016 21:31:34 +0100 Subject: [PATCH 001/180] debian: update changelog --- debian/changelog | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 3c88a21e..2efd65e8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,14 @@ -i3-wm (4.12.1-1) unstable; urgency=medium +i3-wm (4.13.1-1) unstable; urgency=medium * UNRELEASED - -- Michael Stapelberg <stapelberg@debian.org> Fri, 01 Apr 2016 16:34:35 +0200 + -- Michael Stapelberg <stapelberg@debian.org> Tue, 08 Nov 2016 21:31:13 +0100 + +i3-wm (4.13-1) unstable; urgency=medium + + * New upstream release. + + -- Michael Stapelberg <stapelberg@debian.org> Tue, 08 Nov 2016 19:02:16 +0100 i3-wm (4.12-2) unstable; urgency=medium From fff3f79da9a87a1f790c6328f6615422f2b69b47 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Tue, 8 Nov 2016 13:46:43 -0800 Subject: [PATCH 002/180] switch to clang-format-3.8 (#2547) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://llvm.org/bugs/show_bug.cgi?id=30353 was filed for the unintended line break between in e.g. “TAILQ_ENTRY(foo)\nbar;”. Until that’s fixed or a workaround is known, we’ll live with line breaks. To make it a bit easier for readers to see what’s going on, I added extra line breaks around each such struct member/variable definition, so that they at least visually are a single unit. fixes #2174 --- .clang-format | 1 + i3-input/keysym2ucs.c | 1548 +++++++++++----------- i3bar/include/common.h | 6 +- i3bar/include/configuration.h | 16 +- i3bar/include/outputs.h | 3 +- i3bar/include/trayclients.h | 3 +- i3bar/include/workspaces.h | 3 +- include/configuration.h | 21 +- include/data.h | 70 +- include/ipc.h | 3 +- include/queue.h | 5 +- src/commands.c | 4 +- src/con.c | 7 +- src/config_parser.c | 2 +- src/ipc.c | 3 +- src/load_layout.c | 4 +- src/match.c | 4 +- src/restore_layout.c | 3 +- src/x.c | 20 +- travis/check-formatting.sh | 2 +- travis/travis-base-386.Dockerfile | 4 +- travis/travis-base-ubuntu-386.Dockerfile | 4 +- travis/travis-base-ubuntu.Dockerfile | 4 +- travis/travis-base.Dockerfile | 4 +- 24 files changed, 907 insertions(+), 837 deletions(-) diff --git a/.clang-format b/.clang-format index 1d840132..6e49d835 100644 --- a/.clang-format +++ b/.clang-format @@ -8,3 +8,4 @@ IndentWidth: 4 PointerBindsToType: false ColumnLimit: 0 SpaceBeforeParens: ControlStatements +SortIncludes: false diff --git a/i3-input/keysym2ucs.c b/i3-input/keysym2ucs.c index b8e45a14..52bdc044 100644 --- a/i3-input/keysym2ucs.c +++ b/i3-input/keysym2ucs.c @@ -38,780 +38,780 @@ struct codepair { unsigned short keysym; unsigned short ucs; } keysymtab[] = { - {0x01a1, 0x0104}, /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */ - {0x01a2, 0x02d8}, /* breve ˘ BREVE */ - {0x01a3, 0x0141}, /* Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */ - {0x01a5, 0x013d}, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */ - {0x01a6, 0x015a}, /* Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */ - {0x01a9, 0x0160}, /* Scaron Š LATIN CAPITAL LETTER S WITH CARON */ - {0x01aa, 0x015e}, /* Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */ - {0x01ab, 0x0164}, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */ - {0x01ac, 0x0179}, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */ - {0x01ae, 0x017d}, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */ - {0x01af, 0x017b}, /* Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */ - {0x01b1, 0x0105}, /* aogonek ą LATIN SMALL LETTER A WITH OGONEK */ - {0x01b2, 0x02db}, /* ogonek ˛ OGONEK */ - {0x01b3, 0x0142}, /* lstroke ł LATIN SMALL LETTER L WITH STROKE */ - {0x01b5, 0x013e}, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */ - {0x01b6, 0x015b}, /* sacute ś LATIN SMALL LETTER S WITH ACUTE */ - {0x01b7, 0x02c7}, /* caron ˇ CARON */ - {0x01b9, 0x0161}, /* scaron š LATIN SMALL LETTER S WITH CARON */ - {0x01ba, 0x015f}, /* scedilla ş LATIN SMALL LETTER S WITH CEDILLA */ - {0x01bb, 0x0165}, /* tcaron ť LATIN SMALL LETTER T WITH CARON */ - {0x01bc, 0x017a}, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */ - {0x01bd, 0x02dd}, /* doubleacute ˝ DOUBLE ACUTE ACCENT */ - {0x01be, 0x017e}, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */ - {0x01bf, 0x017c}, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */ - {0x01c0, 0x0154}, /* Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */ - {0x01c3, 0x0102}, /* Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */ - {0x01c5, 0x0139}, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */ - {0x01c6, 0x0106}, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */ - {0x01c8, 0x010c}, /* Ccaron Č LATIN CAPITAL LETTER C WITH CARON */ - {0x01ca, 0x0118}, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */ - {0x01cc, 0x011a}, /* Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */ - {0x01cf, 0x010e}, /* Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */ - {0x01d0, 0x0110}, /* Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */ - {0x01d1, 0x0143}, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */ - {0x01d2, 0x0147}, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */ - {0x01d5, 0x0150}, /* Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ - {0x01d8, 0x0158}, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */ - {0x01d9, 0x016e}, /* Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */ - {0x01db, 0x0170}, /* Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ - {0x01de, 0x0162}, /* Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */ - {0x01e0, 0x0155}, /* racute ŕ LATIN SMALL LETTER R WITH ACUTE */ - {0x01e3, 0x0103}, /* abreve ă LATIN SMALL LETTER A WITH BREVE */ - {0x01e5, 0x013a}, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */ - {0x01e6, 0x0107}, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */ - {0x01e8, 0x010d}, /* ccaron č LATIN SMALL LETTER C WITH CARON */ - {0x01ea, 0x0119}, /* eogonek ę LATIN SMALL LETTER E WITH OGONEK */ - {0x01ec, 0x011b}, /* ecaron ě LATIN SMALL LETTER E WITH CARON */ - {0x01ef, 0x010f}, /* dcaron ď LATIN SMALL LETTER D WITH CARON */ - {0x01f0, 0x0111}, /* dstroke đ LATIN SMALL LETTER D WITH STROKE */ - {0x01f1, 0x0144}, /* nacute ń LATIN SMALL LETTER N WITH ACUTE */ - {0x01f2, 0x0148}, /* ncaron ň LATIN SMALL LETTER N WITH CARON */ - {0x01f5, 0x0151}, /* odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */ - {0x01f8, 0x0159}, /* rcaron ř LATIN SMALL LETTER R WITH CARON */ - {0x01f9, 0x016f}, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */ - {0x01fb, 0x0171}, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */ - {0x01fe, 0x0163}, /* tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */ - {0x01ff, 0x02d9}, /* abovedot ˙ DOT ABOVE */ - {0x02a1, 0x0126}, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */ - {0x02a6, 0x0124}, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ - {0x02a9, 0x0130}, /* Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */ - {0x02ab, 0x011e}, /* Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */ - {0x02ac, 0x0134}, /* Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ - {0x02b1, 0x0127}, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */ - {0x02b6, 0x0125}, /* hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */ - {0x02b9, 0x0131}, /* idotless ı LATIN SMALL LETTER DOTLESS I */ - {0x02bb, 0x011f}, /* gbreve ğ LATIN SMALL LETTER G WITH BREVE */ - {0x02bc, 0x0135}, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */ - {0x02c5, 0x010a}, /* Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */ - {0x02c6, 0x0108}, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ - {0x02d5, 0x0120}, /* Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */ - {0x02d8, 0x011c}, /* Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ - {0x02dd, 0x016c}, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */ - {0x02de, 0x015c}, /* Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ - {0x02e5, 0x010b}, /* cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */ - {0x02e6, 0x0109}, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */ - {0x02f5, 0x0121}, /* gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */ - {0x02f8, 0x011d}, /* gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */ - {0x02fd, 0x016d}, /* ubreve ŭ LATIN SMALL LETTER U WITH BREVE */ - {0x02fe, 0x015d}, /* scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */ - {0x03a2, 0x0138}, /* kra ĸ LATIN SMALL LETTER KRA */ - {0x03a3, 0x0156}, /* Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */ - {0x03a5, 0x0128}, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */ - {0x03a6, 0x013b}, /* Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */ - {0x03aa, 0x0112}, /* Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */ - {0x03ab, 0x0122}, /* Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */ - {0x03ac, 0x0166}, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */ - {0x03b3, 0x0157}, /* rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */ - {0x03b5, 0x0129}, /* itilde ĩ LATIN SMALL LETTER I WITH TILDE */ - {0x03b6, 0x013c}, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */ - {0x03ba, 0x0113}, /* emacron ē LATIN SMALL LETTER E WITH MACRON */ - {0x03bb, 0x0123}, /* gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */ - {0x03bc, 0x0167}, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */ - {0x03bd, 0x014a}, /* ENG Ŋ LATIN CAPITAL LETTER ENG */ - {0x03bf, 0x014b}, /* eng ŋ LATIN SMALL LETTER ENG */ - {0x03c0, 0x0100}, /* Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */ - {0x03c7, 0x012e}, /* Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */ - {0x03cc, 0x0116}, /* Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */ - {0x03cf, 0x012a}, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */ - {0x03d1, 0x0145}, /* Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */ - {0x03d2, 0x014c}, /* Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */ - {0x03d3, 0x0136}, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */ - {0x03d9, 0x0172}, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */ - {0x03dd, 0x0168}, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */ - {0x03de, 0x016a}, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */ - {0x03e0, 0x0101}, /* amacron ā LATIN SMALL LETTER A WITH MACRON */ - {0x03e7, 0x012f}, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */ - {0x03ec, 0x0117}, /* eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */ - {0x03ef, 0x012b}, /* imacron ī LATIN SMALL LETTER I WITH MACRON */ - {0x03f1, 0x0146}, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */ - {0x03f2, 0x014d}, /* omacron ō LATIN SMALL LETTER O WITH MACRON */ - {0x03f3, 0x0137}, /* kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */ - {0x03f9, 0x0173}, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */ - {0x03fd, 0x0169}, /* utilde ũ LATIN SMALL LETTER U WITH TILDE */ - {0x03fe, 0x016b}, /* umacron ū LATIN SMALL LETTER U WITH MACRON */ - {0x047e, 0x203e}, /* overline ‾ OVERLINE */ - {0x04a1, 0x3002}, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */ - {0x04a2, 0x300c}, /* kana_openingbracket 「 LEFT CORNER BRACKET */ - {0x04a3, 0x300d}, /* kana_closingbracket 」 RIGHT CORNER BRACKET */ - {0x04a4, 0x3001}, /* kana_comma 、 IDEOGRAPHIC COMMA */ - {0x04a5, 0x30fb}, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */ - {0x04a6, 0x30f2}, /* kana_WO ヲ KATAKANA LETTER WO */ - {0x04a7, 0x30a1}, /* kana_a ァ KATAKANA LETTER SMALL A */ - {0x04a8, 0x30a3}, /* kana_i ィ KATAKANA LETTER SMALL I */ - {0x04a9, 0x30a5}, /* kana_u ゥ KATAKANA LETTER SMALL U */ - {0x04aa, 0x30a7}, /* kana_e ェ KATAKANA LETTER SMALL E */ - {0x04ab, 0x30a9}, /* kana_o ォ KATAKANA LETTER SMALL O */ - {0x04ac, 0x30e3}, /* kana_ya ャ KATAKANA LETTER SMALL YA */ - {0x04ad, 0x30e5}, /* kana_yu ュ KATAKANA LETTER SMALL YU */ - {0x04ae, 0x30e7}, /* kana_yo ョ KATAKANA LETTER SMALL YO */ - {0x04af, 0x30c3}, /* kana_tsu ッ KATAKANA LETTER SMALL TU */ - {0x04b0, 0x30fc}, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */ - {0x04b1, 0x30a2}, /* kana_A ア KATAKANA LETTER A */ - {0x04b2, 0x30a4}, /* kana_I イ KATAKANA LETTER I */ - {0x04b3, 0x30a6}, /* kana_U ウ KATAKANA LETTER U */ - {0x04b4, 0x30a8}, /* kana_E エ KATAKANA LETTER E */ - {0x04b5, 0x30aa}, /* kana_O オ KATAKANA LETTER O */ - {0x04b6, 0x30ab}, /* kana_KA カ KATAKANA LETTER KA */ - {0x04b7, 0x30ad}, /* kana_KI キ KATAKANA LETTER KI */ - {0x04b8, 0x30af}, /* kana_KU ク KATAKANA LETTER KU */ - {0x04b9, 0x30b1}, /* kana_KE ケ KATAKANA LETTER KE */ - {0x04ba, 0x30b3}, /* kana_KO コ KATAKANA LETTER KO */ - {0x04bb, 0x30b5}, /* kana_SA サ KATAKANA LETTER SA */ - {0x04bc, 0x30b7}, /* kana_SHI シ KATAKANA LETTER SI */ - {0x04bd, 0x30b9}, /* kana_SU ス KATAKANA LETTER SU */ - {0x04be, 0x30bb}, /* kana_SE セ KATAKANA LETTER SE */ - {0x04bf, 0x30bd}, /* kana_SO ソ KATAKANA LETTER SO */ - {0x04c0, 0x30bf}, /* kana_TA タ KATAKANA LETTER TA */ - {0x04c1, 0x30c1}, /* kana_CHI チ KATAKANA LETTER TI */ - {0x04c2, 0x30c4}, /* kana_TSU ツ KATAKANA LETTER TU */ - {0x04c3, 0x30c6}, /* kana_TE テ KATAKANA LETTER TE */ - {0x04c4, 0x30c8}, /* kana_TO ト KATAKANA LETTER TO */ - {0x04c5, 0x30ca}, /* kana_NA ナ KATAKANA LETTER NA */ - {0x04c6, 0x30cb}, /* kana_NI ニ KATAKANA LETTER NI */ - {0x04c7, 0x30cc}, /* kana_NU ヌ KATAKANA LETTER NU */ - {0x04c8, 0x30cd}, /* kana_NE ネ KATAKANA LETTER NE */ - {0x04c9, 0x30ce}, /* kana_NO ノ KATAKANA LETTER NO */ - {0x04ca, 0x30cf}, /* kana_HA ハ KATAKANA LETTER HA */ - {0x04cb, 0x30d2}, /* kana_HI ヒ KATAKANA LETTER HI */ - {0x04cc, 0x30d5}, /* kana_FU フ KATAKANA LETTER HU */ - {0x04cd, 0x30d8}, /* kana_HE ヘ KATAKANA LETTER HE */ - {0x04ce, 0x30db}, /* kana_HO ホ KATAKANA LETTER HO */ - {0x04cf, 0x30de}, /* kana_MA マ KATAKANA LETTER MA */ - {0x04d0, 0x30df}, /* kana_MI ミ KATAKANA LETTER MI */ - {0x04d1, 0x30e0}, /* kana_MU ム KATAKANA LETTER MU */ - {0x04d2, 0x30e1}, /* kana_ME メ KATAKANA LETTER ME */ - {0x04d3, 0x30e2}, /* kana_MO モ KATAKANA LETTER MO */ - {0x04d4, 0x30e4}, /* kana_YA ヤ KATAKANA LETTER YA */ - {0x04d5, 0x30e6}, /* kana_YU ユ KATAKANA LETTER YU */ - {0x04d6, 0x30e8}, /* kana_YO ヨ KATAKANA LETTER YO */ - {0x04d7, 0x30e9}, /* kana_RA ラ KATAKANA LETTER RA */ - {0x04d8, 0x30ea}, /* kana_RI リ KATAKANA LETTER RI */ - {0x04d9, 0x30eb}, /* kana_RU ル KATAKANA LETTER RU */ - {0x04da, 0x30ec}, /* kana_RE レ KATAKANA LETTER RE */ - {0x04db, 0x30ed}, /* kana_RO ロ KATAKANA LETTER RO */ - {0x04dc, 0x30ef}, /* kana_WA ワ KATAKANA LETTER WA */ - {0x04dd, 0x30f3}, /* kana_N ン KATAKANA LETTER N */ - {0x04de, 0x309b}, /* voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */ - {0x04df, 0x309c}, /* semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ - {0x05ac, 0x060c}, /* Arabic_comma ، ARABIC COMMA */ - {0x05bb, 0x061b}, /* Arabic_semicolon ؛ ARABIC SEMICOLON */ - {0x05bf, 0x061f}, /* Arabic_question_mark ؟ ARABIC QUESTION MARK */ - {0x05c1, 0x0621}, /* Arabic_hamza ء ARABIC LETTER HAMZA */ - {0x05c2, 0x0622}, /* Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */ - {0x05c3, 0x0623}, /* Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */ - {0x05c4, 0x0624}, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */ - {0x05c5, 0x0625}, /* Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */ - {0x05c6, 0x0626}, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */ - {0x05c7, 0x0627}, /* Arabic_alef ا ARABIC LETTER ALEF */ - {0x05c8, 0x0628}, /* Arabic_beh ب ARABIC LETTER BEH */ - {0x05c9, 0x0629}, /* Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */ - {0x05ca, 0x062a}, /* Arabic_teh ت ARABIC LETTER TEH */ - {0x05cb, 0x062b}, /* Arabic_theh ث ARABIC LETTER THEH */ - {0x05cc, 0x062c}, /* Arabic_jeem ج ARABIC LETTER JEEM */ - {0x05cd, 0x062d}, /* Arabic_hah ح ARABIC LETTER HAH */ - {0x05ce, 0x062e}, /* Arabic_khah خ ARABIC LETTER KHAH */ - {0x05cf, 0x062f}, /* Arabic_dal د ARABIC LETTER DAL */ - {0x05d0, 0x0630}, /* Arabic_thal ذ ARABIC LETTER THAL */ - {0x05d1, 0x0631}, /* Arabic_ra ر ARABIC LETTER REH */ - {0x05d2, 0x0632}, /* Arabic_zain ز ARABIC LETTER ZAIN */ - {0x05d3, 0x0633}, /* Arabic_seen س ARABIC LETTER SEEN */ - {0x05d4, 0x0634}, /* Arabic_sheen ش ARABIC LETTER SHEEN */ - {0x05d5, 0x0635}, /* Arabic_sad ص ARABIC LETTER SAD */ - {0x05d6, 0x0636}, /* Arabic_dad ض ARABIC LETTER DAD */ - {0x05d7, 0x0637}, /* Arabic_tah ط ARABIC LETTER TAH */ - {0x05d8, 0x0638}, /* Arabic_zah ظ ARABIC LETTER ZAH */ - {0x05d9, 0x0639}, /* Arabic_ain ع ARABIC LETTER AIN */ - {0x05da, 0x063a}, /* Arabic_ghain غ ARABIC LETTER GHAIN */ - {0x05e0, 0x0640}, /* Arabic_tatweel ـ ARABIC TATWEEL */ - {0x05e1, 0x0641}, /* Arabic_feh ف ARABIC LETTER FEH */ - {0x05e2, 0x0642}, /* Arabic_qaf ق ARABIC LETTER QAF */ - {0x05e3, 0x0643}, /* Arabic_kaf ك ARABIC LETTER KAF */ - {0x05e4, 0x0644}, /* Arabic_lam ل ARABIC LETTER LAM */ - {0x05e5, 0x0645}, /* Arabic_meem م ARABIC LETTER MEEM */ - {0x05e6, 0x0646}, /* Arabic_noon ن ARABIC LETTER NOON */ - {0x05e7, 0x0647}, /* Arabic_ha ه ARABIC LETTER HEH */ - {0x05e8, 0x0648}, /* Arabic_waw و ARABIC LETTER WAW */ - {0x05e9, 0x0649}, /* Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */ - {0x05ea, 0x064a}, /* Arabic_yeh ي ARABIC LETTER YEH */ - {0x05eb, 0x064b}, /* Arabic_fathatan ً ARABIC FATHATAN */ - {0x05ec, 0x064c}, /* Arabic_dammatan ٌ ARABIC DAMMATAN */ - {0x05ed, 0x064d}, /* Arabic_kasratan ٍ ARABIC KASRATAN */ - {0x05ee, 0x064e}, /* Arabic_fatha َ ARABIC FATHA */ - {0x05ef, 0x064f}, /* Arabic_damma ُ ARABIC DAMMA */ - {0x05f0, 0x0650}, /* Arabic_kasra ِ ARABIC KASRA */ - {0x05f1, 0x0651}, /* Arabic_shadda ّ ARABIC SHADDA */ - {0x05f2, 0x0652}, /* Arabic_sukun ْ ARABIC SUKUN */ - {0x06a1, 0x0452}, /* Serbian_dje ђ CYRILLIC SMALL LETTER DJE */ - {0x06a2, 0x0453}, /* Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */ - {0x06a3, 0x0451}, /* Cyrillic_io ё CYRILLIC SMALL LETTER IO */ - {0x06a4, 0x0454}, /* Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */ - {0x06a5, 0x0455}, /* Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */ - {0x06a6, 0x0456}, /* Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ - {0x06a7, 0x0457}, /* Ukrainian_yi ї CYRILLIC SMALL LETTER YI */ - {0x06a8, 0x0458}, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */ - {0x06a9, 0x0459}, /* Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */ - {0x06aa, 0x045a}, /* Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */ - {0x06ab, 0x045b}, /* Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */ - {0x06ac, 0x045c}, /* Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */ - /* 0x06ad Ukrainian_ghe_with_upturn ? ??? */ - {0x06ae, 0x045e}, /* Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */ - {0x06af, 0x045f}, /* Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */ - {0x06b0, 0x2116}, /* numerosign № NUMERO SIGN */ - {0x06b1, 0x0402}, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */ - {0x06b2, 0x0403}, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */ - {0x06b3, 0x0401}, /* Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */ - {0x06b4, 0x0404}, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */ - {0x06b5, 0x0405}, /* Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */ - {0x06b6, 0x0406}, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ - {0x06b7, 0x0407}, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */ - {0x06b8, 0x0408}, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */ - {0x06b9, 0x0409}, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */ - {0x06ba, 0x040a}, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */ - {0x06bb, 0x040b}, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */ - {0x06bc, 0x040c}, /* Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */ - /* 0x06bd Ukrainian_GHE_WITH_UPTURN ? ??? */ - {0x06be, 0x040e}, /* Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */ - {0x06bf, 0x040f}, /* Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */ - {0x06c0, 0x044e}, /* Cyrillic_yu ю CYRILLIC SMALL LETTER YU */ - {0x06c1, 0x0430}, /* Cyrillic_a а CYRILLIC SMALL LETTER A */ - {0x06c2, 0x0431}, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */ - {0x06c3, 0x0446}, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */ - {0x06c4, 0x0434}, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */ - {0x06c5, 0x0435}, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */ - {0x06c6, 0x0444}, /* Cyrillic_ef ф CYRILLIC SMALL LETTER EF */ - {0x06c7, 0x0433}, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */ - {0x06c8, 0x0445}, /* Cyrillic_ha х CYRILLIC SMALL LETTER HA */ - {0x06c9, 0x0438}, /* Cyrillic_i и CYRILLIC SMALL LETTER I */ - {0x06ca, 0x0439}, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */ - {0x06cb, 0x043a}, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */ - {0x06cc, 0x043b}, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */ - {0x06cd, 0x043c}, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */ - {0x06ce, 0x043d}, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */ - {0x06cf, 0x043e}, /* Cyrillic_o о CYRILLIC SMALL LETTER O */ - {0x06d0, 0x043f}, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */ - {0x06d1, 0x044f}, /* Cyrillic_ya я CYRILLIC SMALL LETTER YA */ - {0x06d2, 0x0440}, /* Cyrillic_er р CYRILLIC SMALL LETTER ER */ - {0x06d3, 0x0441}, /* Cyrillic_es с CYRILLIC SMALL LETTER ES */ - {0x06d4, 0x0442}, /* Cyrillic_te т CYRILLIC SMALL LETTER TE */ - {0x06d5, 0x0443}, /* Cyrillic_u у CYRILLIC SMALL LETTER U */ - {0x06d6, 0x0436}, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */ - {0x06d7, 0x0432}, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */ - {0x06d8, 0x044c}, /* Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */ - {0x06d9, 0x044b}, /* Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */ - {0x06da, 0x0437}, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */ - {0x06db, 0x0448}, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */ - {0x06dc, 0x044d}, /* Cyrillic_e э CYRILLIC SMALL LETTER E */ - {0x06dd, 0x0449}, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */ - {0x06de, 0x0447}, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */ - {0x06df, 0x044a}, /* Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */ - {0x06e0, 0x042e}, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */ - {0x06e1, 0x0410}, /* Cyrillic_A А CYRILLIC CAPITAL LETTER A */ - {0x06e2, 0x0411}, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */ - {0x06e3, 0x0426}, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */ - {0x06e4, 0x0414}, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */ - {0x06e5, 0x0415}, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */ - {0x06e6, 0x0424}, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */ - {0x06e7, 0x0413}, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */ - {0x06e8, 0x0425}, /* Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */ - {0x06e9, 0x0418}, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */ - {0x06ea, 0x0419}, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */ - {0x06eb, 0x041a}, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */ - {0x06ec, 0x041b}, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */ - {0x06ed, 0x041c}, /* Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */ - {0x06ee, 0x041d}, /* Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */ - {0x06ef, 0x041e}, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */ - {0x06f0, 0x041f}, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */ - {0x06f1, 0x042f}, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */ - {0x06f2, 0x0420}, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */ - {0x06f3, 0x0421}, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */ - {0x06f4, 0x0422}, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */ - {0x06f5, 0x0423}, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */ - {0x06f6, 0x0416}, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */ - {0x06f7, 0x0412}, /* Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */ - {0x06f8, 0x042c}, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */ - {0x06f9, 0x042b}, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */ - {0x06fa, 0x0417}, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */ - {0x06fb, 0x0428}, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */ - {0x06fc, 0x042d}, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */ - {0x06fd, 0x0429}, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */ - {0x06fe, 0x0427}, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */ - {0x06ff, 0x042a}, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */ - {0x07a1, 0x0386}, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */ - {0x07a2, 0x0388}, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */ - {0x07a3, 0x0389}, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */ - {0x07a4, 0x038a}, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */ - {0x07a5, 0x03aa}, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ - {0x07a7, 0x038c}, /* Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */ - {0x07a8, 0x038e}, /* Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */ - {0x07a9, 0x03ab}, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ - {0x07ab, 0x038f}, /* Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */ - {0x07ae, 0x0385}, /* Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */ - {0x07af, 0x2015}, /* Greek_horizbar ― HORIZONTAL BAR */ - {0x07b1, 0x03ac}, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */ - {0x07b2, 0x03ad}, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */ - {0x07b3, 0x03ae}, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */ - {0x07b4, 0x03af}, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */ - {0x07b5, 0x03ca}, /* Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */ - {0x07b6, 0x0390}, /* Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ - {0x07b7, 0x03cc}, /* Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */ - {0x07b8, 0x03cd}, /* Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */ - {0x07b9, 0x03cb}, /* Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ - {0x07ba, 0x03b0}, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ - {0x07bb, 0x03ce}, /* Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */ - {0x07c1, 0x0391}, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */ - {0x07c2, 0x0392}, /* Greek_BETA Β GREEK CAPITAL LETTER BETA */ - {0x07c3, 0x0393}, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */ - {0x07c4, 0x0394}, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */ - {0x07c5, 0x0395}, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */ - {0x07c6, 0x0396}, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */ - {0x07c7, 0x0397}, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */ - {0x07c8, 0x0398}, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */ - {0x07c9, 0x0399}, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */ - {0x07ca, 0x039a}, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */ - {0x07cb, 0x039b}, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */ - {0x07cc, 0x039c}, /* Greek_MU Μ GREEK CAPITAL LETTER MU */ - {0x07cd, 0x039d}, /* Greek_NU Ν GREEK CAPITAL LETTER NU */ - {0x07ce, 0x039e}, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */ - {0x07cf, 0x039f}, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */ - {0x07d0, 0x03a0}, /* Greek_PI Π GREEK CAPITAL LETTER PI */ - {0x07d1, 0x03a1}, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */ - {0x07d2, 0x03a3}, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */ - {0x07d4, 0x03a4}, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */ - {0x07d5, 0x03a5}, /* Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */ - {0x07d6, 0x03a6}, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */ - {0x07d7, 0x03a7}, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */ - {0x07d8, 0x03a8}, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */ - {0x07d9, 0x03a9}, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */ - {0x07e1, 0x03b1}, /* Greek_alpha α GREEK SMALL LETTER ALPHA */ - {0x07e2, 0x03b2}, /* Greek_beta β GREEK SMALL LETTER BETA */ - {0x07e3, 0x03b3}, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */ - {0x07e4, 0x03b4}, /* Greek_delta δ GREEK SMALL LETTER DELTA */ - {0x07e5, 0x03b5}, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */ - {0x07e6, 0x03b6}, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */ - {0x07e7, 0x03b7}, /* Greek_eta η GREEK SMALL LETTER ETA */ - {0x07e8, 0x03b8}, /* Greek_theta θ GREEK SMALL LETTER THETA */ - {0x07e9, 0x03b9}, /* Greek_iota ι GREEK SMALL LETTER IOTA */ - {0x07ea, 0x03ba}, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */ - {0x07eb, 0x03bb}, /* Greek_lambda λ GREEK SMALL LETTER LAMDA */ - {0x07ec, 0x03bc}, /* Greek_mu μ GREEK SMALL LETTER MU */ - {0x07ed, 0x03bd}, /* Greek_nu ν GREEK SMALL LETTER NU */ - {0x07ee, 0x03be}, /* Greek_xi ξ GREEK SMALL LETTER XI */ - {0x07ef, 0x03bf}, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */ - {0x07f0, 0x03c0}, /* Greek_pi π GREEK SMALL LETTER PI */ - {0x07f1, 0x03c1}, /* Greek_rho ρ GREEK SMALL LETTER RHO */ - {0x07f2, 0x03c3}, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */ - {0x07f3, 0x03c2}, /* Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */ - {0x07f4, 0x03c4}, /* Greek_tau τ GREEK SMALL LETTER TAU */ - {0x07f5, 0x03c5}, /* Greek_upsilon υ GREEK SMALL LETTER UPSILON */ - {0x07f6, 0x03c6}, /* Greek_phi φ GREEK SMALL LETTER PHI */ - {0x07f7, 0x03c7}, /* Greek_chi χ GREEK SMALL LETTER CHI */ - {0x07f8, 0x03c8}, /* Greek_psi ψ GREEK SMALL LETTER PSI */ - {0x07f9, 0x03c9}, /* Greek_omega ω GREEK SMALL LETTER OMEGA */ - /* 0x08a1 leftradical ? ??? */ - /* 0x08a2 topleftradical ? ??? */ - /* 0x08a3 horizconnector ? ??? */ - {0x08a4, 0x2320}, /* topintegral ⌠ TOP HALF INTEGRAL */ - {0x08a5, 0x2321}, /* botintegral ⌡ BOTTOM HALF INTEGRAL */ - {0x08a6, 0x2502}, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */ - /* 0x08a7 topleftsqbracket ? ??? */ - /* 0x08a8 botleftsqbracket ? ??? */ - /* 0x08a9 toprightsqbracket ? ??? */ - /* 0x08aa botrightsqbracket ? ??? */ - /* 0x08ab topleftparens ? ??? */ - /* 0x08ac botleftparens ? ??? */ - /* 0x08ad toprightparens ? ??? */ - /* 0x08ae botrightparens ? ??? */ - /* 0x08af leftmiddlecurlybrace ? ??? */ - /* 0x08b0 rightmiddlecurlybrace ? ??? */ - /* 0x08b1 topleftsummation ? ??? */ - /* 0x08b2 botleftsummation ? ??? */ - /* 0x08b3 topvertsummationconnector ? ??? */ - /* 0x08b4 botvertsummationconnector ? ??? */ - /* 0x08b5 toprightsummation ? ??? */ - /* 0x08b6 botrightsummation ? ??? */ - /* 0x08b7 rightmiddlesummation ? ??? */ - {0x08bc, 0x2264}, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */ - {0x08bd, 0x2260}, /* notequal ≠ NOT EQUAL TO */ - {0x08be, 0x2265}, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */ - {0x08bf, 0x222b}, /* integral ∫ INTEGRAL */ - {0x08c0, 0x2234}, /* therefore ∴ THEREFORE */ - {0x08c1, 0x221d}, /* variation ∝ PROPORTIONAL TO */ - {0x08c2, 0x221e}, /* infinity ∞ INFINITY */ - {0x08c5, 0x2207}, /* nabla ∇ NABLA */ - {0x08c8, 0x2245}, /* approximate ≅ APPROXIMATELY EQUAL TO */ - /* 0x08c9 similarequal ? ??? */ - {0x08cd, 0x21d4}, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */ - {0x08ce, 0x21d2}, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */ - {0x08cf, 0x2261}, /* identical ≡ IDENTICAL TO */ - {0x08d6, 0x221a}, /* radical √ SQUARE ROOT */ - {0x08da, 0x2282}, /* includedin ⊂ SUBSET OF */ - {0x08db, 0x2283}, /* includes ⊃ SUPERSET OF */ - {0x08dc, 0x2229}, /* intersection ∩ INTERSECTION */ - {0x08dd, 0x222a}, /* union ∪ UNION */ - {0x08de, 0x2227}, /* logicaland ∧ LOGICAL AND */ - {0x08df, 0x2228}, /* logicalor ∨ LOGICAL OR */ - {0x08ef, 0x2202}, /* partialderivative ∂ PARTIAL DIFFERENTIAL */ - {0x08f6, 0x0192}, /* function ƒ LATIN SMALL LETTER F WITH HOOK */ - {0x08fb, 0x2190}, /* leftarrow ← LEFTWARDS ARROW */ - {0x08fc, 0x2191}, /* uparrow ↑ UPWARDS ARROW */ - {0x08fd, 0x2192}, /* rightarrow → RIGHTWARDS ARROW */ - {0x08fe, 0x2193}, /* downarrow ↓ DOWNWARDS ARROW */ - {0x09df, 0x2422}, /* blank ␢ BLANK SYMBOL */ - {0x09e0, 0x25c6}, /* soliddiamond ◆ BLACK DIAMOND */ - {0x09e1, 0x2592}, /* checkerboard ▒ MEDIUM SHADE */ - {0x09e2, 0x2409}, /* ht ␉ SYMBOL FOR HORIZONTAL TABULATION */ - {0x09e3, 0x240c}, /* ff ␌ SYMBOL FOR FORM FEED */ - {0x09e4, 0x240d}, /* cr ␍ SYMBOL FOR CARRIAGE RETURN */ - {0x09e5, 0x240a}, /* lf ␊ SYMBOL FOR LINE FEED */ - {0x09e8, 0x2424}, /* nl  SYMBOL FOR NEWLINE */ - {0x09e9, 0x240b}, /* vt ␋ SYMBOL FOR VERTICAL TABULATION */ - {0x09ea, 0x2518}, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */ - {0x09eb, 0x2510}, /* uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */ - {0x09ec, 0x250c}, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ - {0x09ed, 0x2514}, /* lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */ - {0x09ee, 0x253c}, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ - /* 0x09ef horizlinescan1 ? ??? */ - /* 0x09f0 horizlinescan3 ? ??? */ - {0x09f1, 0x2500}, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */ - /* 0x09f2 horizlinescan7 ? ??? */ - /* 0x09f3 horizlinescan9 ? ??? */ - {0x09f4, 0x251c}, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ - {0x09f5, 0x2524}, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */ - {0x09f6, 0x2534}, /* bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */ - {0x09f7, 0x252c}, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ - {0x09f8, 0x2502}, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */ - {0x0aa1, 0x2003}, /* emspace EM SPACE */ - {0x0aa2, 0x2002}, /* enspace EN SPACE */ - {0x0aa3, 0x2004}, /* em3space THREE-PER-EM SPACE */ - {0x0aa4, 0x2005}, /* em4space FOUR-PER-EM SPACE */ - {0x0aa5, 0x2007}, /* digitspace FIGURE SPACE */ - {0x0aa6, 0x2008}, /* punctspace PUNCTUATION SPACE */ - {0x0aa7, 0x2009}, /* thinspace THIN SPACE */ - {0x0aa8, 0x200a}, /* hairspace HAIR SPACE */ - {0x0aa9, 0x2014}, /* emdash — EM DASH */ - {0x0aaa, 0x2013}, /* endash – EN DASH */ - /* 0x0aac signifblank ? ??? */ - {0x0aae, 0x2026}, /* ellipsis … HORIZONTAL ELLIPSIS */ - /* 0x0aaf doubbaselinedot ? ??? */ - {0x0ab0, 0x2153}, /* onethird ⅓ VULGAR FRACTION ONE THIRD */ - {0x0ab1, 0x2154}, /* twothirds ⅔ VULGAR FRACTION TWO THIRDS */ - {0x0ab2, 0x2155}, /* onefifth ⅕ VULGAR FRACTION ONE FIFTH */ - {0x0ab3, 0x2156}, /* twofifths ⅖ VULGAR FRACTION TWO FIFTHS */ - {0x0ab4, 0x2157}, /* threefifths ⅗ VULGAR FRACTION THREE FIFTHS */ - {0x0ab5, 0x2158}, /* fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */ - {0x0ab6, 0x2159}, /* onesixth ⅙ VULGAR FRACTION ONE SIXTH */ - {0x0ab7, 0x215a}, /* fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */ - {0x0ab8, 0x2105}, /* careof ℅ CARE OF */ - {0x0abb, 0x2012}, /* figdash ‒ FIGURE DASH */ - {0x0abc, 0x2329}, /* leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */ - {0x0abd, 0x002e}, /* decimalpoint . FULL STOP */ - {0x0abe, 0x232a}, /* rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */ - /* 0x0abf marker ? ??? */ - {0x0ac3, 0x215b}, /* oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */ - {0x0ac4, 0x215c}, /* threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */ - {0x0ac5, 0x215d}, /* fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */ - {0x0ac6, 0x215e}, /* seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */ - {0x0ac9, 0x2122}, /* trademark ™ TRADE MARK SIGN */ - {0x0aca, 0x2613}, /* signaturemark ☓ SALTIRE */ - /* 0x0acb trademarkincircle ? ??? */ - {0x0acc, 0x25c1}, /* leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */ - {0x0acd, 0x25b7}, /* rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */ - {0x0ace, 0x25cb}, /* emopencircle ○ WHITE CIRCLE */ - {0x0acf, 0x25a1}, /* emopenrectangle □ WHITE SQUARE */ - {0x0ad0, 0x2018}, /* leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */ - {0x0ad1, 0x2019}, /* rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */ - {0x0ad2, 0x201c}, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */ - {0x0ad3, 0x201d}, /* rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */ - {0x0ad4, 0x211e}, /* prescription ℞ PRESCRIPTION TAKE */ - {0x0ad6, 0x2032}, /* minutes ′ PRIME */ - {0x0ad7, 0x2033}, /* seconds ″ DOUBLE PRIME */ - {0x0ad9, 0x271d}, /* latincross ✝ LATIN CROSS */ - /* 0x0ada hexagram ? ??? */ - {0x0adb, 0x25ac}, /* filledrectbullet ▬ BLACK RECTANGLE */ - {0x0adc, 0x25c0}, /* filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */ - {0x0add, 0x25b6}, /* filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */ - {0x0ade, 0x25cf}, /* emfilledcircle ● BLACK CIRCLE */ - {0x0adf, 0x25a0}, /* emfilledrect ■ BLACK SQUARE */ - {0x0ae0, 0x25e6}, /* enopencircbullet ◦ WHITE BULLET */ - {0x0ae1, 0x25ab}, /* enopensquarebullet ▫ WHITE SMALL SQUARE */ - {0x0ae2, 0x25ad}, /* openrectbullet ▭ WHITE RECTANGLE */ - {0x0ae3, 0x25b3}, /* opentribulletup △ WHITE UP-POINTING TRIANGLE */ - {0x0ae4, 0x25bd}, /* opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */ - {0x0ae5, 0x2606}, /* openstar ☆ WHITE STAR */ - {0x0ae6, 0x2022}, /* enfilledcircbullet • BULLET */ - {0x0ae7, 0x25aa}, /* enfilledsqbullet ▪ BLACK SMALL SQUARE */ - {0x0ae8, 0x25b2}, /* filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */ - {0x0ae9, 0x25bc}, /* filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */ - {0x0aea, 0x261c}, /* leftpointer ☜ WHITE LEFT POINTING INDEX */ - {0x0aeb, 0x261e}, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */ - {0x0aec, 0x2663}, /* club ♣ BLACK CLUB SUIT */ - {0x0aed, 0x2666}, /* diamond ♦ BLACK DIAMOND SUIT */ - {0x0aee, 0x2665}, /* heart ♥ BLACK HEART SUIT */ - {0x0af0, 0x2720}, /* maltesecross ✠ MALTESE CROSS */ - {0x0af1, 0x2020}, /* dagger † DAGGER */ - {0x0af2, 0x2021}, /* doubledagger ‡ DOUBLE DAGGER */ - {0x0af3, 0x2713}, /* checkmark ✓ CHECK MARK */ - {0x0af4, 0x2717}, /* ballotcross ✗ BALLOT X */ - {0x0af5, 0x266f}, /* musicalsharp ♯ MUSIC SHARP SIGN */ - {0x0af6, 0x266d}, /* musicalflat ♭ MUSIC FLAT SIGN */ - {0x0af7, 0x2642}, /* malesymbol ♂ MALE SIGN */ - {0x0af8, 0x2640}, /* femalesymbol ♀ FEMALE SIGN */ - {0x0af9, 0x260e}, /* telephone ☎ BLACK TELEPHONE */ - {0x0afa, 0x2315}, /* telephonerecorder ⌕ TELEPHONE RECORDER */ - {0x0afb, 0x2117}, /* phonographcopyright ℗ SOUND RECORDING COPYRIGHT */ - {0x0afc, 0x2038}, /* caret ‸ CARET */ - {0x0afd, 0x201a}, /* singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */ - {0x0afe, 0x201e}, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */ - /* 0x0aff cursor ? ??? */ - {0x0ba3, 0x003c}, /* leftcaret < LESS-THAN SIGN */ - {0x0ba6, 0x003e}, /* rightcaret > GREATER-THAN SIGN */ - {0x0ba8, 0x2228}, /* downcaret ∨ LOGICAL OR */ - {0x0ba9, 0x2227}, /* upcaret ∧ LOGICAL AND */ - {0x0bc0, 0x00af}, /* overbar ¯ MACRON */ - {0x0bc2, 0x22a4}, /* downtack ⊤ DOWN TACK */ - {0x0bc3, 0x2229}, /* upshoe ∩ INTERSECTION */ - {0x0bc4, 0x230a}, /* downstile ⌊ LEFT FLOOR */ - {0x0bc6, 0x005f}, /* underbar _ LOW LINE */ - {0x0bca, 0x2218}, /* jot ∘ RING OPERATOR */ - {0x0bcc, 0x2395}, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD */ - {0x0bce, 0x22a5}, /* uptack ⊥ UP TACK */ - {0x0bcf, 0x25cb}, /* circle ○ WHITE CIRCLE */ - {0x0bd3, 0x2308}, /* upstile ⌈ LEFT CEILING */ - {0x0bd6, 0x222a}, /* downshoe ∪ UNION */ - {0x0bd8, 0x2283}, /* rightshoe ⊃ SUPERSET OF */ - {0x0bda, 0x2282}, /* leftshoe ⊂ SUBSET OF */ - {0x0bdc, 0x22a3}, /* lefttack ⊣ LEFT TACK */ - {0x0bfc, 0x22a2}, /* righttack ⊢ RIGHT TACK */ - {0x0cdf, 0x2017}, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */ - {0x0ce0, 0x05d0}, /* hebrew_aleph א HEBREW LETTER ALEF */ - {0x0ce1, 0x05d1}, /* hebrew_bet ב HEBREW LETTER BET */ - {0x0ce2, 0x05d2}, /* hebrew_gimel ג HEBREW LETTER GIMEL */ - {0x0ce3, 0x05d3}, /* hebrew_dalet ד HEBREW LETTER DALET */ - {0x0ce4, 0x05d4}, /* hebrew_he ה HEBREW LETTER HE */ - {0x0ce5, 0x05d5}, /* hebrew_waw ו HEBREW LETTER VAV */ - {0x0ce6, 0x05d6}, /* hebrew_zain ז HEBREW LETTER ZAYIN */ - {0x0ce7, 0x05d7}, /* hebrew_chet ח HEBREW LETTER HET */ - {0x0ce8, 0x05d8}, /* hebrew_tet ט HEBREW LETTER TET */ - {0x0ce9, 0x05d9}, /* hebrew_yod י HEBREW LETTER YOD */ - {0x0cea, 0x05da}, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */ - {0x0ceb, 0x05db}, /* hebrew_kaph כ HEBREW LETTER KAF */ - {0x0cec, 0x05dc}, /* hebrew_lamed ל HEBREW LETTER LAMED */ - {0x0ced, 0x05dd}, /* hebrew_finalmem ם HEBREW LETTER FINAL MEM */ - {0x0cee, 0x05de}, /* hebrew_mem מ HEBREW LETTER MEM */ - {0x0cef, 0x05df}, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */ - {0x0cf0, 0x05e0}, /* hebrew_nun נ HEBREW LETTER NUN */ - {0x0cf1, 0x05e1}, /* hebrew_samech ס HEBREW LETTER SAMEKH */ - {0x0cf2, 0x05e2}, /* hebrew_ayin ע HEBREW LETTER AYIN */ - {0x0cf3, 0x05e3}, /* hebrew_finalpe ף HEBREW LETTER FINAL PE */ - {0x0cf4, 0x05e4}, /* hebrew_pe פ HEBREW LETTER PE */ - {0x0cf5, 0x05e5}, /* hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */ - {0x0cf6, 0x05e6}, /* hebrew_zade צ HEBREW LETTER TSADI */ - {0x0cf7, 0x05e7}, /* hebrew_qoph ק HEBREW LETTER QOF */ - {0x0cf8, 0x05e8}, /* hebrew_resh ר HEBREW LETTER RESH */ - {0x0cf9, 0x05e9}, /* hebrew_shin ש HEBREW LETTER SHIN */ - {0x0cfa, 0x05ea}, /* hebrew_taw ת HEBREW LETTER TAV */ - {0x0da1, 0x0e01}, /* Thai_kokai ก THAI CHARACTER KO KAI */ - {0x0da2, 0x0e02}, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */ - {0x0da3, 0x0e03}, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */ - {0x0da4, 0x0e04}, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */ - {0x0da5, 0x0e05}, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */ - {0x0da6, 0x0e06}, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */ - {0x0da7, 0x0e07}, /* Thai_ngongu ง THAI CHARACTER NGO NGU */ - {0x0da8, 0x0e08}, /* Thai_chochan จ THAI CHARACTER CHO CHAN */ - {0x0da9, 0x0e09}, /* Thai_choching ฉ THAI CHARACTER CHO CHING */ - {0x0daa, 0x0e0a}, /* Thai_chochang ช THAI CHARACTER CHO CHANG */ - {0x0dab, 0x0e0b}, /* Thai_soso ซ THAI CHARACTER SO SO */ - {0x0dac, 0x0e0c}, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */ - {0x0dad, 0x0e0d}, /* Thai_yoying ญ THAI CHARACTER YO YING */ - {0x0dae, 0x0e0e}, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */ - {0x0daf, 0x0e0f}, /* Thai_topatak ฏ THAI CHARACTER TO PATAK */ - {0x0db0, 0x0e10}, /* Thai_thothan ฐ THAI CHARACTER THO THAN */ - {0x0db1, 0x0e11}, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */ - {0x0db2, 0x0e12}, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */ - {0x0db3, 0x0e13}, /* Thai_nonen ณ THAI CHARACTER NO NEN */ - {0x0db4, 0x0e14}, /* Thai_dodek ด THAI CHARACTER DO DEK */ - {0x0db5, 0x0e15}, /* Thai_totao ต THAI CHARACTER TO TAO */ - {0x0db6, 0x0e16}, /* Thai_thothung ถ THAI CHARACTER THO THUNG */ - {0x0db7, 0x0e17}, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */ - {0x0db8, 0x0e18}, /* Thai_thothong ธ THAI CHARACTER THO THONG */ - {0x0db9, 0x0e19}, /* Thai_nonu น THAI CHARACTER NO NU */ - {0x0dba, 0x0e1a}, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */ - {0x0dbb, 0x0e1b}, /* Thai_popla ป THAI CHARACTER PO PLA */ - {0x0dbc, 0x0e1c}, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */ - {0x0dbd, 0x0e1d}, /* Thai_fofa ฝ THAI CHARACTER FO FA */ - {0x0dbe, 0x0e1e}, /* Thai_phophan พ THAI CHARACTER PHO PHAN */ - {0x0dbf, 0x0e1f}, /* Thai_fofan ฟ THAI CHARACTER FO FAN */ - {0x0dc0, 0x0e20}, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */ - {0x0dc1, 0x0e21}, /* Thai_moma ม THAI CHARACTER MO MA */ - {0x0dc2, 0x0e22}, /* Thai_yoyak ย THAI CHARACTER YO YAK */ - {0x0dc3, 0x0e23}, /* Thai_rorua ร THAI CHARACTER RO RUA */ - {0x0dc4, 0x0e24}, /* Thai_ru ฤ THAI CHARACTER RU */ - {0x0dc5, 0x0e25}, /* Thai_loling ล THAI CHARACTER LO LING */ - {0x0dc6, 0x0e26}, /* Thai_lu ฦ THAI CHARACTER LU */ - {0x0dc7, 0x0e27}, /* Thai_wowaen ว THAI CHARACTER WO WAEN */ - {0x0dc8, 0x0e28}, /* Thai_sosala ศ THAI CHARACTER SO SALA */ - {0x0dc9, 0x0e29}, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */ - {0x0dca, 0x0e2a}, /* Thai_sosua ส THAI CHARACTER SO SUA */ - {0x0dcb, 0x0e2b}, /* Thai_hohip ห THAI CHARACTER HO HIP */ - {0x0dcc, 0x0e2c}, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */ - {0x0dcd, 0x0e2d}, /* Thai_oang อ THAI CHARACTER O ANG */ - {0x0dce, 0x0e2e}, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */ - {0x0dcf, 0x0e2f}, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */ - {0x0dd0, 0x0e30}, /* Thai_saraa ะ THAI CHARACTER SARA A */ - {0x0dd1, 0x0e31}, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */ - {0x0dd2, 0x0e32}, /* Thai_saraaa า THAI CHARACTER SARA AA */ - {0x0dd3, 0x0e33}, /* Thai_saraam ำ THAI CHARACTER SARA AM */ - {0x0dd4, 0x0e34}, /* Thai_sarai ิ THAI CHARACTER SARA I */ - {0x0dd5, 0x0e35}, /* Thai_saraii ี THAI CHARACTER SARA II */ - {0x0dd6, 0x0e36}, /* Thai_saraue ึ THAI CHARACTER SARA UE */ - {0x0dd7, 0x0e37}, /* Thai_sarauee ื THAI CHARACTER SARA UEE */ - {0x0dd8, 0x0e38}, /* Thai_sarau ุ THAI CHARACTER SARA U */ - {0x0dd9, 0x0e39}, /* Thai_sarauu ู THAI CHARACTER SARA UU */ - {0x0dda, 0x0e3a}, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */ - {0x0dde, 0x0e3e}, /* Thai_maihanakat_maitho ??? */ - {0x0ddf, 0x0e3f}, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */ - {0x0de0, 0x0e40}, /* Thai_sarae เ THAI CHARACTER SARA E */ - {0x0de1, 0x0e41}, /* Thai_saraae แ THAI CHARACTER SARA AE */ - {0x0de2, 0x0e42}, /* Thai_sarao โ THAI CHARACTER SARA O */ - {0x0de3, 0x0e43}, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */ - {0x0de4, 0x0e44}, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */ - {0x0de5, 0x0e45}, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */ - {0x0de6, 0x0e46}, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */ - {0x0de7, 0x0e47}, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */ - {0x0de8, 0x0e48}, /* Thai_maiek ่ THAI CHARACTER MAI EK */ - {0x0de9, 0x0e49}, /* Thai_maitho ้ THAI CHARACTER MAI THO */ - {0x0dea, 0x0e4a}, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */ - {0x0deb, 0x0e4b}, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */ - {0x0dec, 0x0e4c}, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */ - {0x0ded, 0x0e4d}, /* Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */ - {0x0df0, 0x0e50}, /* Thai_leksun ๐ THAI DIGIT ZERO */ - {0x0df1, 0x0e51}, /* Thai_leknung ๑ THAI DIGIT ONE */ - {0x0df2, 0x0e52}, /* Thai_leksong ๒ THAI DIGIT TWO */ - {0x0df3, 0x0e53}, /* Thai_leksam ๓ THAI DIGIT THREE */ - {0x0df4, 0x0e54}, /* Thai_leksi ๔ THAI DIGIT FOUR */ - {0x0df5, 0x0e55}, /* Thai_lekha ๕ THAI DIGIT FIVE */ - {0x0df6, 0x0e56}, /* Thai_lekhok ๖ THAI DIGIT SIX */ - {0x0df7, 0x0e57}, /* Thai_lekchet ๗ THAI DIGIT SEVEN */ - {0x0df8, 0x0e58}, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */ - {0x0df9, 0x0e59}, /* Thai_lekkao ๙ THAI DIGIT NINE */ - {0x0ea1, 0x3131}, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */ - {0x0ea2, 0x3132}, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */ - {0x0ea3, 0x3133}, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */ - {0x0ea4, 0x3134}, /* Hangul_Nieun ㄴ HANGUL LETTER NIEUN */ - {0x0ea5, 0x3135}, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */ - {0x0ea6, 0x3136}, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */ - {0x0ea7, 0x3137}, /* Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */ - {0x0ea8, 0x3138}, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */ - {0x0ea9, 0x3139}, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */ - {0x0eaa, 0x313a}, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */ - {0x0eab, 0x313b}, /* Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */ - {0x0eac, 0x313c}, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */ - {0x0ead, 0x313d}, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */ - {0x0eae, 0x313e}, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */ - {0x0eaf, 0x313f}, /* Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */ - {0x0eb0, 0x3140}, /* Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */ - {0x0eb1, 0x3141}, /* Hangul_Mieum ㅁ HANGUL LETTER MIEUM */ - {0x0eb2, 0x3142}, /* Hangul_Pieub ㅂ HANGUL LETTER PIEUP */ - {0x0eb3, 0x3143}, /* Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */ - {0x0eb4, 0x3144}, /* Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */ - {0x0eb5, 0x3145}, /* Hangul_Sios ㅅ HANGUL LETTER SIOS */ - {0x0eb6, 0x3146}, /* Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */ - {0x0eb7, 0x3147}, /* Hangul_Ieung ㅇ HANGUL LETTER IEUNG */ - {0x0eb8, 0x3148}, /* Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */ - {0x0eb9, 0x3149}, /* Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */ - {0x0eba, 0x314a}, /* Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */ - {0x0ebb, 0x314b}, /* Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */ - {0x0ebc, 0x314c}, /* Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */ - {0x0ebd, 0x314d}, /* Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */ - {0x0ebe, 0x314e}, /* Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */ - {0x0ebf, 0x314f}, /* Hangul_A ㅏ HANGUL LETTER A */ - {0x0ec0, 0x3150}, /* Hangul_AE ㅐ HANGUL LETTER AE */ - {0x0ec1, 0x3151}, /* Hangul_YA ㅑ HANGUL LETTER YA */ - {0x0ec2, 0x3152}, /* Hangul_YAE ㅒ HANGUL LETTER YAE */ - {0x0ec3, 0x3153}, /* Hangul_EO ㅓ HANGUL LETTER EO */ - {0x0ec4, 0x3154}, /* Hangul_E ㅔ HANGUL LETTER E */ - {0x0ec5, 0x3155}, /* Hangul_YEO ㅕ HANGUL LETTER YEO */ - {0x0ec6, 0x3156}, /* Hangul_YE ㅖ HANGUL LETTER YE */ - {0x0ec7, 0x3157}, /* Hangul_O ㅗ HANGUL LETTER O */ - {0x0ec8, 0x3158}, /* Hangul_WA ㅘ HANGUL LETTER WA */ - {0x0ec9, 0x3159}, /* Hangul_WAE ㅙ HANGUL LETTER WAE */ - {0x0eca, 0x315a}, /* Hangul_OE ㅚ HANGUL LETTER OE */ - {0x0ecb, 0x315b}, /* Hangul_YO ㅛ HANGUL LETTER YO */ - {0x0ecc, 0x315c}, /* Hangul_U ㅜ HANGUL LETTER U */ - {0x0ecd, 0x315d}, /* Hangul_WEO ㅝ HANGUL LETTER WEO */ - {0x0ece, 0x315e}, /* Hangul_WE ㅞ HANGUL LETTER WE */ - {0x0ecf, 0x315f}, /* Hangul_WI ㅟ HANGUL LETTER WI */ - {0x0ed0, 0x3160}, /* Hangul_YU ㅠ HANGUL LETTER YU */ - {0x0ed1, 0x3161}, /* Hangul_EU ㅡ HANGUL LETTER EU */ - {0x0ed2, 0x3162}, /* Hangul_YI ㅢ HANGUL LETTER YI */ - {0x0ed3, 0x3163}, /* Hangul_I ㅣ HANGUL LETTER I */ - {0x0ed4, 0x11a8}, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */ - {0x0ed5, 0x11a9}, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */ - {0x0ed6, 0x11aa}, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */ - {0x0ed7, 0x11ab}, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */ - {0x0ed8, 0x11ac}, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */ - {0x0ed9, 0x11ad}, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */ - {0x0eda, 0x11ae}, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */ - {0x0edb, 0x11af}, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */ - {0x0edc, 0x11b0}, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */ - {0x0edd, 0x11b1}, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */ - {0x0ede, 0x11b2}, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */ - {0x0edf, 0x11b3}, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */ - {0x0ee0, 0x11b4}, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */ - {0x0ee1, 0x11b5}, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */ - {0x0ee2, 0x11b6}, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */ - {0x0ee3, 0x11b7}, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */ - {0x0ee4, 0x11b8}, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */ - {0x0ee5, 0x11b9}, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */ - {0x0ee6, 0x11ba}, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */ - {0x0ee7, 0x11bb}, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */ - {0x0ee8, 0x11bc}, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */ - {0x0ee9, 0x11bd}, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */ - {0x0eea, 0x11be}, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */ - {0x0eeb, 0x11bf}, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */ - {0x0eec, 0x11c0}, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */ - {0x0eed, 0x11c1}, /* Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */ - {0x0eee, 0x11c2}, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */ - {0x0eef, 0x316d}, /* Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */ - {0x0ef0, 0x3171}, /* Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */ - {0x0ef1, 0x3178}, /* Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */ - {0x0ef2, 0x317f}, /* Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */ - /* 0x0ef3 Hangul_KkogjiDalrinIeung ? ??? */ - {0x0ef4, 0x3184}, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */ - {0x0ef5, 0x3186}, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */ - {0x0ef6, 0x318d}, /* Hangul_AraeA ㆍ HANGUL LETTER ARAEA */ - {0x0ef7, 0x318e}, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */ - {0x0ef8, 0x11eb}, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */ - /* 0x0ef9 Hangul_J_KkogjiDalrinIeung ? ??? */ - {0x0efa, 0x11f9}, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */ - {0x0eff, 0x20a9}, /* Korean_Won ₩ WON SIGN */ - {0x13bc, 0x0152}, /* OE Œ LATIN CAPITAL LIGATURE OE */ - {0x13bd, 0x0153}, /* oe œ LATIN SMALL LIGATURE OE */ - {0x13be, 0x0178}, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */ - {0x20ac, 0x20ac}, /* EuroSign € EURO SIGN */ + {0x01a1, 0x0104}, /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */ + {0x01a2, 0x02d8}, /* breve ˘ BREVE */ + {0x01a3, 0x0141}, /* Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */ + {0x01a5, 0x013d}, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */ + {0x01a6, 0x015a}, /* Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */ + {0x01a9, 0x0160}, /* Scaron Š LATIN CAPITAL LETTER S WITH CARON */ + {0x01aa, 0x015e}, /* Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */ + {0x01ab, 0x0164}, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */ + {0x01ac, 0x0179}, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */ + {0x01ae, 0x017d}, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */ + {0x01af, 0x017b}, /* Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */ + {0x01b1, 0x0105}, /* aogonek ą LATIN SMALL LETTER A WITH OGONEK */ + {0x01b2, 0x02db}, /* ogonek ˛ OGONEK */ + {0x01b3, 0x0142}, /* lstroke ł LATIN SMALL LETTER L WITH STROKE */ + {0x01b5, 0x013e}, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */ + {0x01b6, 0x015b}, /* sacute ś LATIN SMALL LETTER S WITH ACUTE */ + {0x01b7, 0x02c7}, /* caron ˇ CARON */ + {0x01b9, 0x0161}, /* scaron š LATIN SMALL LETTER S WITH CARON */ + {0x01ba, 0x015f}, /* scedilla ş LATIN SMALL LETTER S WITH CEDILLA */ + {0x01bb, 0x0165}, /* tcaron ť LATIN SMALL LETTER T WITH CARON */ + {0x01bc, 0x017a}, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */ + {0x01bd, 0x02dd}, /* doubleacute ˝ DOUBLE ACUTE ACCENT */ + {0x01be, 0x017e}, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */ + {0x01bf, 0x017c}, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */ + {0x01c0, 0x0154}, /* Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */ + {0x01c3, 0x0102}, /* Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */ + {0x01c5, 0x0139}, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */ + {0x01c6, 0x0106}, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */ + {0x01c8, 0x010c}, /* Ccaron Č LATIN CAPITAL LETTER C WITH CARON */ + {0x01ca, 0x0118}, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */ + {0x01cc, 0x011a}, /* Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */ + {0x01cf, 0x010e}, /* Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */ + {0x01d0, 0x0110}, /* Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */ + {0x01d1, 0x0143}, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */ + {0x01d2, 0x0147}, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */ + {0x01d5, 0x0150}, /* Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ + {0x01d8, 0x0158}, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */ + {0x01d9, 0x016e}, /* Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */ + {0x01db, 0x0170}, /* Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ + {0x01de, 0x0162}, /* Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */ + {0x01e0, 0x0155}, /* racute ŕ LATIN SMALL LETTER R WITH ACUTE */ + {0x01e3, 0x0103}, /* abreve ă LATIN SMALL LETTER A WITH BREVE */ + {0x01e5, 0x013a}, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */ + {0x01e6, 0x0107}, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */ + {0x01e8, 0x010d}, /* ccaron č LATIN SMALL LETTER C WITH CARON */ + {0x01ea, 0x0119}, /* eogonek ę LATIN SMALL LETTER E WITH OGONEK */ + {0x01ec, 0x011b}, /* ecaron ě LATIN SMALL LETTER E WITH CARON */ + {0x01ef, 0x010f}, /* dcaron ď LATIN SMALL LETTER D WITH CARON */ + {0x01f0, 0x0111}, /* dstroke đ LATIN SMALL LETTER D WITH STROKE */ + {0x01f1, 0x0144}, /* nacute ń LATIN SMALL LETTER N WITH ACUTE */ + {0x01f2, 0x0148}, /* ncaron ň LATIN SMALL LETTER N WITH CARON */ + {0x01f5, 0x0151}, /* odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */ + {0x01f8, 0x0159}, /* rcaron ř LATIN SMALL LETTER R WITH CARON */ + {0x01f9, 0x016f}, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */ + {0x01fb, 0x0171}, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */ + {0x01fe, 0x0163}, /* tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */ + {0x01ff, 0x02d9}, /* abovedot ˙ DOT ABOVE */ + {0x02a1, 0x0126}, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */ + {0x02a6, 0x0124}, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ + {0x02a9, 0x0130}, /* Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */ + {0x02ab, 0x011e}, /* Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */ + {0x02ac, 0x0134}, /* Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ + {0x02b1, 0x0127}, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */ + {0x02b6, 0x0125}, /* hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */ + {0x02b9, 0x0131}, /* idotless ı LATIN SMALL LETTER DOTLESS I */ + {0x02bb, 0x011f}, /* gbreve ğ LATIN SMALL LETTER G WITH BREVE */ + {0x02bc, 0x0135}, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */ + {0x02c5, 0x010a}, /* Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */ + {0x02c6, 0x0108}, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ + {0x02d5, 0x0120}, /* Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */ + {0x02d8, 0x011c}, /* Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ + {0x02dd, 0x016c}, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */ + {0x02de, 0x015c}, /* Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ + {0x02e5, 0x010b}, /* cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */ + {0x02e6, 0x0109}, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */ + {0x02f5, 0x0121}, /* gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */ + {0x02f8, 0x011d}, /* gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */ + {0x02fd, 0x016d}, /* ubreve ŭ LATIN SMALL LETTER U WITH BREVE */ + {0x02fe, 0x015d}, /* scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */ + {0x03a2, 0x0138}, /* kra ĸ LATIN SMALL LETTER KRA */ + {0x03a3, 0x0156}, /* Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */ + {0x03a5, 0x0128}, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */ + {0x03a6, 0x013b}, /* Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */ + {0x03aa, 0x0112}, /* Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */ + {0x03ab, 0x0122}, /* Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */ + {0x03ac, 0x0166}, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */ + {0x03b3, 0x0157}, /* rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */ + {0x03b5, 0x0129}, /* itilde ĩ LATIN SMALL LETTER I WITH TILDE */ + {0x03b6, 0x013c}, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */ + {0x03ba, 0x0113}, /* emacron ē LATIN SMALL LETTER E WITH MACRON */ + {0x03bb, 0x0123}, /* gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */ + {0x03bc, 0x0167}, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */ + {0x03bd, 0x014a}, /* ENG Ŋ LATIN CAPITAL LETTER ENG */ + {0x03bf, 0x014b}, /* eng ŋ LATIN SMALL LETTER ENG */ + {0x03c0, 0x0100}, /* Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */ + {0x03c7, 0x012e}, /* Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */ + {0x03cc, 0x0116}, /* Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */ + {0x03cf, 0x012a}, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */ + {0x03d1, 0x0145}, /* Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */ + {0x03d2, 0x014c}, /* Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */ + {0x03d3, 0x0136}, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */ + {0x03d9, 0x0172}, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */ + {0x03dd, 0x0168}, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */ + {0x03de, 0x016a}, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */ + {0x03e0, 0x0101}, /* amacron ā LATIN SMALL LETTER A WITH MACRON */ + {0x03e7, 0x012f}, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */ + {0x03ec, 0x0117}, /* eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */ + {0x03ef, 0x012b}, /* imacron ī LATIN SMALL LETTER I WITH MACRON */ + {0x03f1, 0x0146}, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */ + {0x03f2, 0x014d}, /* omacron ō LATIN SMALL LETTER O WITH MACRON */ + {0x03f3, 0x0137}, /* kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */ + {0x03f9, 0x0173}, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */ + {0x03fd, 0x0169}, /* utilde ũ LATIN SMALL LETTER U WITH TILDE */ + {0x03fe, 0x016b}, /* umacron ū LATIN SMALL LETTER U WITH MACRON */ + {0x047e, 0x203e}, /* overline ‾ OVERLINE */ + {0x04a1, 0x3002}, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */ + {0x04a2, 0x300c}, /* kana_openingbracket 「 LEFT CORNER BRACKET */ + {0x04a3, 0x300d}, /* kana_closingbracket 」 RIGHT CORNER BRACKET */ + {0x04a4, 0x3001}, /* kana_comma 、 IDEOGRAPHIC COMMA */ + {0x04a5, 0x30fb}, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */ + {0x04a6, 0x30f2}, /* kana_WO ヲ KATAKANA LETTER WO */ + {0x04a7, 0x30a1}, /* kana_a ァ KATAKANA LETTER SMALL A */ + {0x04a8, 0x30a3}, /* kana_i ィ KATAKANA LETTER SMALL I */ + {0x04a9, 0x30a5}, /* kana_u ゥ KATAKANA LETTER SMALL U */ + {0x04aa, 0x30a7}, /* kana_e ェ KATAKANA LETTER SMALL E */ + {0x04ab, 0x30a9}, /* kana_o ォ KATAKANA LETTER SMALL O */ + {0x04ac, 0x30e3}, /* kana_ya ャ KATAKANA LETTER SMALL YA */ + {0x04ad, 0x30e5}, /* kana_yu ュ KATAKANA LETTER SMALL YU */ + {0x04ae, 0x30e7}, /* kana_yo ョ KATAKANA LETTER SMALL YO */ + {0x04af, 0x30c3}, /* kana_tsu ッ KATAKANA LETTER SMALL TU */ + {0x04b0, 0x30fc}, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */ + {0x04b1, 0x30a2}, /* kana_A ア KATAKANA LETTER A */ + {0x04b2, 0x30a4}, /* kana_I イ KATAKANA LETTER I */ + {0x04b3, 0x30a6}, /* kana_U ウ KATAKANA LETTER U */ + {0x04b4, 0x30a8}, /* kana_E エ KATAKANA LETTER E */ + {0x04b5, 0x30aa}, /* kana_O オ KATAKANA LETTER O */ + {0x04b6, 0x30ab}, /* kana_KA カ KATAKANA LETTER KA */ + {0x04b7, 0x30ad}, /* kana_KI キ KATAKANA LETTER KI */ + {0x04b8, 0x30af}, /* kana_KU ク KATAKANA LETTER KU */ + {0x04b9, 0x30b1}, /* kana_KE ケ KATAKANA LETTER KE */ + {0x04ba, 0x30b3}, /* kana_KO コ KATAKANA LETTER KO */ + {0x04bb, 0x30b5}, /* kana_SA サ KATAKANA LETTER SA */ + {0x04bc, 0x30b7}, /* kana_SHI シ KATAKANA LETTER SI */ + {0x04bd, 0x30b9}, /* kana_SU ス KATAKANA LETTER SU */ + {0x04be, 0x30bb}, /* kana_SE セ KATAKANA LETTER SE */ + {0x04bf, 0x30bd}, /* kana_SO ソ KATAKANA LETTER SO */ + {0x04c0, 0x30bf}, /* kana_TA タ KATAKANA LETTER TA */ + {0x04c1, 0x30c1}, /* kana_CHI チ KATAKANA LETTER TI */ + {0x04c2, 0x30c4}, /* kana_TSU ツ KATAKANA LETTER TU */ + {0x04c3, 0x30c6}, /* kana_TE テ KATAKANA LETTER TE */ + {0x04c4, 0x30c8}, /* kana_TO ト KATAKANA LETTER TO */ + {0x04c5, 0x30ca}, /* kana_NA ナ KATAKANA LETTER NA */ + {0x04c6, 0x30cb}, /* kana_NI ニ KATAKANA LETTER NI */ + {0x04c7, 0x30cc}, /* kana_NU ヌ KATAKANA LETTER NU */ + {0x04c8, 0x30cd}, /* kana_NE ネ KATAKANA LETTER NE */ + {0x04c9, 0x30ce}, /* kana_NO ノ KATAKANA LETTER NO */ + {0x04ca, 0x30cf}, /* kana_HA ハ KATAKANA LETTER HA */ + {0x04cb, 0x30d2}, /* kana_HI ヒ KATAKANA LETTER HI */ + {0x04cc, 0x30d5}, /* kana_FU フ KATAKANA LETTER HU */ + {0x04cd, 0x30d8}, /* kana_HE ヘ KATAKANA LETTER HE */ + {0x04ce, 0x30db}, /* kana_HO ホ KATAKANA LETTER HO */ + {0x04cf, 0x30de}, /* kana_MA マ KATAKANA LETTER MA */ + {0x04d0, 0x30df}, /* kana_MI ミ KATAKANA LETTER MI */ + {0x04d1, 0x30e0}, /* kana_MU ム KATAKANA LETTER MU */ + {0x04d2, 0x30e1}, /* kana_ME メ KATAKANA LETTER ME */ + {0x04d3, 0x30e2}, /* kana_MO モ KATAKANA LETTER MO */ + {0x04d4, 0x30e4}, /* kana_YA ヤ KATAKANA LETTER YA */ + {0x04d5, 0x30e6}, /* kana_YU ユ KATAKANA LETTER YU */ + {0x04d6, 0x30e8}, /* kana_YO ヨ KATAKANA LETTER YO */ + {0x04d7, 0x30e9}, /* kana_RA ラ KATAKANA LETTER RA */ + {0x04d8, 0x30ea}, /* kana_RI リ KATAKANA LETTER RI */ + {0x04d9, 0x30eb}, /* kana_RU ル KATAKANA LETTER RU */ + {0x04da, 0x30ec}, /* kana_RE レ KATAKANA LETTER RE */ + {0x04db, 0x30ed}, /* kana_RO ロ KATAKANA LETTER RO */ + {0x04dc, 0x30ef}, /* kana_WA ワ KATAKANA LETTER WA */ + {0x04dd, 0x30f3}, /* kana_N ン KATAKANA LETTER N */ + {0x04de, 0x309b}, /* voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */ + {0x04df, 0x309c}, /* semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ + {0x05ac, 0x060c}, /* Arabic_comma ، ARABIC COMMA */ + {0x05bb, 0x061b}, /* Arabic_semicolon ؛ ARABIC SEMICOLON */ + {0x05bf, 0x061f}, /* Arabic_question_mark ؟ ARABIC QUESTION MARK */ + {0x05c1, 0x0621}, /* Arabic_hamza ء ARABIC LETTER HAMZA */ + {0x05c2, 0x0622}, /* Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */ + {0x05c3, 0x0623}, /* Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */ + {0x05c4, 0x0624}, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */ + {0x05c5, 0x0625}, /* Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */ + {0x05c6, 0x0626}, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */ + {0x05c7, 0x0627}, /* Arabic_alef ا ARABIC LETTER ALEF */ + {0x05c8, 0x0628}, /* Arabic_beh ب ARABIC LETTER BEH */ + {0x05c9, 0x0629}, /* Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */ + {0x05ca, 0x062a}, /* Arabic_teh ت ARABIC LETTER TEH */ + {0x05cb, 0x062b}, /* Arabic_theh ث ARABIC LETTER THEH */ + {0x05cc, 0x062c}, /* Arabic_jeem ج ARABIC LETTER JEEM */ + {0x05cd, 0x062d}, /* Arabic_hah ح ARABIC LETTER HAH */ + {0x05ce, 0x062e}, /* Arabic_khah خ ARABIC LETTER KHAH */ + {0x05cf, 0x062f}, /* Arabic_dal د ARABIC LETTER DAL */ + {0x05d0, 0x0630}, /* Arabic_thal ذ ARABIC LETTER THAL */ + {0x05d1, 0x0631}, /* Arabic_ra ر ARABIC LETTER REH */ + {0x05d2, 0x0632}, /* Arabic_zain ز ARABIC LETTER ZAIN */ + {0x05d3, 0x0633}, /* Arabic_seen س ARABIC LETTER SEEN */ + {0x05d4, 0x0634}, /* Arabic_sheen ش ARABIC LETTER SHEEN */ + {0x05d5, 0x0635}, /* Arabic_sad ص ARABIC LETTER SAD */ + {0x05d6, 0x0636}, /* Arabic_dad ض ARABIC LETTER DAD */ + {0x05d7, 0x0637}, /* Arabic_tah ط ARABIC LETTER TAH */ + {0x05d8, 0x0638}, /* Arabic_zah ظ ARABIC LETTER ZAH */ + {0x05d9, 0x0639}, /* Arabic_ain ع ARABIC LETTER AIN */ + {0x05da, 0x063a}, /* Arabic_ghain غ ARABIC LETTER GHAIN */ + {0x05e0, 0x0640}, /* Arabic_tatweel ـ ARABIC TATWEEL */ + {0x05e1, 0x0641}, /* Arabic_feh ف ARABIC LETTER FEH */ + {0x05e2, 0x0642}, /* Arabic_qaf ق ARABIC LETTER QAF */ + {0x05e3, 0x0643}, /* Arabic_kaf ك ARABIC LETTER KAF */ + {0x05e4, 0x0644}, /* Arabic_lam ل ARABIC LETTER LAM */ + {0x05e5, 0x0645}, /* Arabic_meem م ARABIC LETTER MEEM */ + {0x05e6, 0x0646}, /* Arabic_noon ن ARABIC LETTER NOON */ + {0x05e7, 0x0647}, /* Arabic_ha ه ARABIC LETTER HEH */ + {0x05e8, 0x0648}, /* Arabic_waw و ARABIC LETTER WAW */ + {0x05e9, 0x0649}, /* Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */ + {0x05ea, 0x064a}, /* Arabic_yeh ي ARABIC LETTER YEH */ + {0x05eb, 0x064b}, /* Arabic_fathatan ً ARABIC FATHATAN */ + {0x05ec, 0x064c}, /* Arabic_dammatan ٌ ARABIC DAMMATAN */ + {0x05ed, 0x064d}, /* Arabic_kasratan ٍ ARABIC KASRATAN */ + {0x05ee, 0x064e}, /* Arabic_fatha َ ARABIC FATHA */ + {0x05ef, 0x064f}, /* Arabic_damma ُ ARABIC DAMMA */ + {0x05f0, 0x0650}, /* Arabic_kasra ِ ARABIC KASRA */ + {0x05f1, 0x0651}, /* Arabic_shadda ّ ARABIC SHADDA */ + {0x05f2, 0x0652}, /* Arabic_sukun ْ ARABIC SUKUN */ + {0x06a1, 0x0452}, /* Serbian_dje ђ CYRILLIC SMALL LETTER DJE */ + {0x06a2, 0x0453}, /* Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */ + {0x06a3, 0x0451}, /* Cyrillic_io ё CYRILLIC SMALL LETTER IO */ + {0x06a4, 0x0454}, /* Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */ + {0x06a5, 0x0455}, /* Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */ + {0x06a6, 0x0456}, /* Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ + {0x06a7, 0x0457}, /* Ukrainian_yi ї CYRILLIC SMALL LETTER YI */ + {0x06a8, 0x0458}, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */ + {0x06a9, 0x0459}, /* Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */ + {0x06aa, 0x045a}, /* Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */ + {0x06ab, 0x045b}, /* Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */ + {0x06ac, 0x045c}, /* Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */ + /* 0x06ad Ukrainian_ghe_with_upturn ? ??? */ + {0x06ae, 0x045e}, /* Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */ + {0x06af, 0x045f}, /* Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */ + {0x06b0, 0x2116}, /* numerosign № NUMERO SIGN */ + {0x06b1, 0x0402}, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */ + {0x06b2, 0x0403}, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */ + {0x06b3, 0x0401}, /* Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */ + {0x06b4, 0x0404}, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */ + {0x06b5, 0x0405}, /* Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */ + {0x06b6, 0x0406}, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ + {0x06b7, 0x0407}, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */ + {0x06b8, 0x0408}, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */ + {0x06b9, 0x0409}, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */ + {0x06ba, 0x040a}, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */ + {0x06bb, 0x040b}, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */ + {0x06bc, 0x040c}, /* Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */ + /* 0x06bd Ukrainian_GHE_WITH_UPTURN ? ??? */ + {0x06be, 0x040e}, /* Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */ + {0x06bf, 0x040f}, /* Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */ + {0x06c0, 0x044e}, /* Cyrillic_yu ю CYRILLIC SMALL LETTER YU */ + {0x06c1, 0x0430}, /* Cyrillic_a а CYRILLIC SMALL LETTER A */ + {0x06c2, 0x0431}, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */ + {0x06c3, 0x0446}, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */ + {0x06c4, 0x0434}, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */ + {0x06c5, 0x0435}, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */ + {0x06c6, 0x0444}, /* Cyrillic_ef ф CYRILLIC SMALL LETTER EF */ + {0x06c7, 0x0433}, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */ + {0x06c8, 0x0445}, /* Cyrillic_ha х CYRILLIC SMALL LETTER HA */ + {0x06c9, 0x0438}, /* Cyrillic_i и CYRILLIC SMALL LETTER I */ + {0x06ca, 0x0439}, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */ + {0x06cb, 0x043a}, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */ + {0x06cc, 0x043b}, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */ + {0x06cd, 0x043c}, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */ + {0x06ce, 0x043d}, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */ + {0x06cf, 0x043e}, /* Cyrillic_o о CYRILLIC SMALL LETTER O */ + {0x06d0, 0x043f}, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */ + {0x06d1, 0x044f}, /* Cyrillic_ya я CYRILLIC SMALL LETTER YA */ + {0x06d2, 0x0440}, /* Cyrillic_er р CYRILLIC SMALL LETTER ER */ + {0x06d3, 0x0441}, /* Cyrillic_es с CYRILLIC SMALL LETTER ES */ + {0x06d4, 0x0442}, /* Cyrillic_te т CYRILLIC SMALL LETTER TE */ + {0x06d5, 0x0443}, /* Cyrillic_u у CYRILLIC SMALL LETTER U */ + {0x06d6, 0x0436}, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */ + {0x06d7, 0x0432}, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */ + {0x06d8, 0x044c}, /* Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */ + {0x06d9, 0x044b}, /* Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */ + {0x06da, 0x0437}, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */ + {0x06db, 0x0448}, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */ + {0x06dc, 0x044d}, /* Cyrillic_e э CYRILLIC SMALL LETTER E */ + {0x06dd, 0x0449}, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */ + {0x06de, 0x0447}, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */ + {0x06df, 0x044a}, /* Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */ + {0x06e0, 0x042e}, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */ + {0x06e1, 0x0410}, /* Cyrillic_A А CYRILLIC CAPITAL LETTER A */ + {0x06e2, 0x0411}, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */ + {0x06e3, 0x0426}, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */ + {0x06e4, 0x0414}, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */ + {0x06e5, 0x0415}, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */ + {0x06e6, 0x0424}, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */ + {0x06e7, 0x0413}, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */ + {0x06e8, 0x0425}, /* Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */ + {0x06e9, 0x0418}, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */ + {0x06ea, 0x0419}, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */ + {0x06eb, 0x041a}, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */ + {0x06ec, 0x041b}, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */ + {0x06ed, 0x041c}, /* Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */ + {0x06ee, 0x041d}, /* Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */ + {0x06ef, 0x041e}, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */ + {0x06f0, 0x041f}, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */ + {0x06f1, 0x042f}, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */ + {0x06f2, 0x0420}, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */ + {0x06f3, 0x0421}, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */ + {0x06f4, 0x0422}, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */ + {0x06f5, 0x0423}, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */ + {0x06f6, 0x0416}, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */ + {0x06f7, 0x0412}, /* Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */ + {0x06f8, 0x042c}, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */ + {0x06f9, 0x042b}, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */ + {0x06fa, 0x0417}, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */ + {0x06fb, 0x0428}, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */ + {0x06fc, 0x042d}, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */ + {0x06fd, 0x0429}, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */ + {0x06fe, 0x0427}, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */ + {0x06ff, 0x042a}, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */ + {0x07a1, 0x0386}, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */ + {0x07a2, 0x0388}, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */ + {0x07a3, 0x0389}, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */ + {0x07a4, 0x038a}, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */ + {0x07a5, 0x03aa}, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ + {0x07a7, 0x038c}, /* Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */ + {0x07a8, 0x038e}, /* Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */ + {0x07a9, 0x03ab}, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ + {0x07ab, 0x038f}, /* Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */ + {0x07ae, 0x0385}, /* Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */ + {0x07af, 0x2015}, /* Greek_horizbar ― HORIZONTAL BAR */ + {0x07b1, 0x03ac}, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */ + {0x07b2, 0x03ad}, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */ + {0x07b3, 0x03ae}, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */ + {0x07b4, 0x03af}, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */ + {0x07b5, 0x03ca}, /* Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */ + {0x07b6, 0x0390}, /* Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ + {0x07b7, 0x03cc}, /* Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */ + {0x07b8, 0x03cd}, /* Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */ + {0x07b9, 0x03cb}, /* Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ + {0x07ba, 0x03b0}, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ + {0x07bb, 0x03ce}, /* Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */ + {0x07c1, 0x0391}, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */ + {0x07c2, 0x0392}, /* Greek_BETA Β GREEK CAPITAL LETTER BETA */ + {0x07c3, 0x0393}, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */ + {0x07c4, 0x0394}, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */ + {0x07c5, 0x0395}, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */ + {0x07c6, 0x0396}, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */ + {0x07c7, 0x0397}, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */ + {0x07c8, 0x0398}, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */ + {0x07c9, 0x0399}, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */ + {0x07ca, 0x039a}, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */ + {0x07cb, 0x039b}, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */ + {0x07cc, 0x039c}, /* Greek_MU Μ GREEK CAPITAL LETTER MU */ + {0x07cd, 0x039d}, /* Greek_NU Ν GREEK CAPITAL LETTER NU */ + {0x07ce, 0x039e}, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */ + {0x07cf, 0x039f}, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */ + {0x07d0, 0x03a0}, /* Greek_PI Π GREEK CAPITAL LETTER PI */ + {0x07d1, 0x03a1}, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */ + {0x07d2, 0x03a3}, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */ + {0x07d4, 0x03a4}, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */ + {0x07d5, 0x03a5}, /* Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */ + {0x07d6, 0x03a6}, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */ + {0x07d7, 0x03a7}, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */ + {0x07d8, 0x03a8}, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */ + {0x07d9, 0x03a9}, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */ + {0x07e1, 0x03b1}, /* Greek_alpha α GREEK SMALL LETTER ALPHA */ + {0x07e2, 0x03b2}, /* Greek_beta β GREEK SMALL LETTER BETA */ + {0x07e3, 0x03b3}, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */ + {0x07e4, 0x03b4}, /* Greek_delta δ GREEK SMALL LETTER DELTA */ + {0x07e5, 0x03b5}, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */ + {0x07e6, 0x03b6}, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */ + {0x07e7, 0x03b7}, /* Greek_eta η GREEK SMALL LETTER ETA */ + {0x07e8, 0x03b8}, /* Greek_theta θ GREEK SMALL LETTER THETA */ + {0x07e9, 0x03b9}, /* Greek_iota ι GREEK SMALL LETTER IOTA */ + {0x07ea, 0x03ba}, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */ + {0x07eb, 0x03bb}, /* Greek_lambda λ GREEK SMALL LETTER LAMDA */ + {0x07ec, 0x03bc}, /* Greek_mu μ GREEK SMALL LETTER MU */ + {0x07ed, 0x03bd}, /* Greek_nu ν GREEK SMALL LETTER NU */ + {0x07ee, 0x03be}, /* Greek_xi ξ GREEK SMALL LETTER XI */ + {0x07ef, 0x03bf}, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */ + {0x07f0, 0x03c0}, /* Greek_pi π GREEK SMALL LETTER PI */ + {0x07f1, 0x03c1}, /* Greek_rho ρ GREEK SMALL LETTER RHO */ + {0x07f2, 0x03c3}, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */ + {0x07f3, 0x03c2}, /* Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */ + {0x07f4, 0x03c4}, /* Greek_tau τ GREEK SMALL LETTER TAU */ + {0x07f5, 0x03c5}, /* Greek_upsilon υ GREEK SMALL LETTER UPSILON */ + {0x07f6, 0x03c6}, /* Greek_phi φ GREEK SMALL LETTER PHI */ + {0x07f7, 0x03c7}, /* Greek_chi χ GREEK SMALL LETTER CHI */ + {0x07f8, 0x03c8}, /* Greek_psi ψ GREEK SMALL LETTER PSI */ + {0x07f9, 0x03c9}, /* Greek_omega ω GREEK SMALL LETTER OMEGA */ + /* 0x08a1 leftradical ? ??? */ + /* 0x08a2 topleftradical ? ??? */ + /* 0x08a3 horizconnector ? ??? */ + {0x08a4, 0x2320}, /* topintegral ⌠ TOP HALF INTEGRAL */ + {0x08a5, 0x2321}, /* botintegral ⌡ BOTTOM HALF INTEGRAL */ + {0x08a6, 0x2502}, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */ + /* 0x08a7 topleftsqbracket ? ??? */ + /* 0x08a8 botleftsqbracket ? ??? */ + /* 0x08a9 toprightsqbracket ? ??? */ + /* 0x08aa botrightsqbracket ? ??? */ + /* 0x08ab topleftparens ? ??? */ + /* 0x08ac botleftparens ? ??? */ + /* 0x08ad toprightparens ? ??? */ + /* 0x08ae botrightparens ? ??? */ + /* 0x08af leftmiddlecurlybrace ? ??? */ + /* 0x08b0 rightmiddlecurlybrace ? ??? */ + /* 0x08b1 topleftsummation ? ??? */ + /* 0x08b2 botleftsummation ? ??? */ + /* 0x08b3 topvertsummationconnector ? ??? */ + /* 0x08b4 botvertsummationconnector ? ??? */ + /* 0x08b5 toprightsummation ? ??? */ + /* 0x08b6 botrightsummation ? ??? */ + /* 0x08b7 rightmiddlesummation ? ??? */ + {0x08bc, 0x2264}, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */ + {0x08bd, 0x2260}, /* notequal ≠ NOT EQUAL TO */ + {0x08be, 0x2265}, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */ + {0x08bf, 0x222b}, /* integral ∫ INTEGRAL */ + {0x08c0, 0x2234}, /* therefore ∴ THEREFORE */ + {0x08c1, 0x221d}, /* variation ∝ PROPORTIONAL TO */ + {0x08c2, 0x221e}, /* infinity ∞ INFINITY */ + {0x08c5, 0x2207}, /* nabla ∇ NABLA */ + {0x08c8, 0x2245}, /* approximate ≅ APPROXIMATELY EQUAL TO */ + /* 0x08c9 similarequal ? ??? */ + {0x08cd, 0x21d4}, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */ + {0x08ce, 0x21d2}, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */ + {0x08cf, 0x2261}, /* identical ≡ IDENTICAL TO */ + {0x08d6, 0x221a}, /* radical √ SQUARE ROOT */ + {0x08da, 0x2282}, /* includedin ⊂ SUBSET OF */ + {0x08db, 0x2283}, /* includes ⊃ SUPERSET OF */ + {0x08dc, 0x2229}, /* intersection ∩ INTERSECTION */ + {0x08dd, 0x222a}, /* union ∪ UNION */ + {0x08de, 0x2227}, /* logicaland ∧ LOGICAL AND */ + {0x08df, 0x2228}, /* logicalor ∨ LOGICAL OR */ + {0x08ef, 0x2202}, /* partialderivative ∂ PARTIAL DIFFERENTIAL */ + {0x08f6, 0x0192}, /* function ƒ LATIN SMALL LETTER F WITH HOOK */ + {0x08fb, 0x2190}, /* leftarrow ← LEFTWARDS ARROW */ + {0x08fc, 0x2191}, /* uparrow ↑ UPWARDS ARROW */ + {0x08fd, 0x2192}, /* rightarrow → RIGHTWARDS ARROW */ + {0x08fe, 0x2193}, /* downarrow ↓ DOWNWARDS ARROW */ + {0x09df, 0x2422}, /* blank ␢ BLANK SYMBOL */ + {0x09e0, 0x25c6}, /* soliddiamond ◆ BLACK DIAMOND */ + {0x09e1, 0x2592}, /* checkerboard ▒ MEDIUM SHADE */ + {0x09e2, 0x2409}, /* ht ␉ SYMBOL FOR HORIZONTAL TABULATION */ + {0x09e3, 0x240c}, /* ff ␌ SYMBOL FOR FORM FEED */ + {0x09e4, 0x240d}, /* cr ␍ SYMBOL FOR CARRIAGE RETURN */ + {0x09e5, 0x240a}, /* lf ␊ SYMBOL FOR LINE FEED */ + {0x09e8, 0x2424}, /* nl  SYMBOL FOR NEWLINE */ + {0x09e9, 0x240b}, /* vt ␋ SYMBOL FOR VERTICAL TABULATION */ + {0x09ea, 0x2518}, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */ + {0x09eb, 0x2510}, /* uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */ + {0x09ec, 0x250c}, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ + {0x09ed, 0x2514}, /* lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */ + {0x09ee, 0x253c}, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ + /* 0x09ef horizlinescan1 ? ??? */ + /* 0x09f0 horizlinescan3 ? ??? */ + {0x09f1, 0x2500}, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */ + /* 0x09f2 horizlinescan7 ? ??? */ + /* 0x09f3 horizlinescan9 ? ??? */ + {0x09f4, 0x251c}, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ + {0x09f5, 0x2524}, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */ + {0x09f6, 0x2534}, /* bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */ + {0x09f7, 0x252c}, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ + {0x09f8, 0x2502}, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */ + {0x0aa1, 0x2003}, /* emspace EM SPACE */ + {0x0aa2, 0x2002}, /* enspace EN SPACE */ + {0x0aa3, 0x2004}, /* em3space THREE-PER-EM SPACE */ + {0x0aa4, 0x2005}, /* em4space FOUR-PER-EM SPACE */ + {0x0aa5, 0x2007}, /* digitspace FIGURE SPACE */ + {0x0aa6, 0x2008}, /* punctspace PUNCTUATION SPACE */ + {0x0aa7, 0x2009}, /* thinspace THIN SPACE */ + {0x0aa8, 0x200a}, /* hairspace HAIR SPACE */ + {0x0aa9, 0x2014}, /* emdash — EM DASH */ + {0x0aaa, 0x2013}, /* endash – EN DASH */ + /* 0x0aac signifblank ? ??? */ + {0x0aae, 0x2026}, /* ellipsis … HORIZONTAL ELLIPSIS */ + /* 0x0aaf doubbaselinedot ? ??? */ + {0x0ab0, 0x2153}, /* onethird ⅓ VULGAR FRACTION ONE THIRD */ + {0x0ab1, 0x2154}, /* twothirds ⅔ VULGAR FRACTION TWO THIRDS */ + {0x0ab2, 0x2155}, /* onefifth ⅕ VULGAR FRACTION ONE FIFTH */ + {0x0ab3, 0x2156}, /* twofifths ⅖ VULGAR FRACTION TWO FIFTHS */ + {0x0ab4, 0x2157}, /* threefifths ⅗ VULGAR FRACTION THREE FIFTHS */ + {0x0ab5, 0x2158}, /* fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */ + {0x0ab6, 0x2159}, /* onesixth ⅙ VULGAR FRACTION ONE SIXTH */ + {0x0ab7, 0x215a}, /* fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */ + {0x0ab8, 0x2105}, /* careof ℅ CARE OF */ + {0x0abb, 0x2012}, /* figdash ‒ FIGURE DASH */ + {0x0abc, 0x2329}, /* leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */ + {0x0abd, 0x002e}, /* decimalpoint . FULL STOP */ + {0x0abe, 0x232a}, /* rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */ + /* 0x0abf marker ? ??? */ + {0x0ac3, 0x215b}, /* oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */ + {0x0ac4, 0x215c}, /* threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */ + {0x0ac5, 0x215d}, /* fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */ + {0x0ac6, 0x215e}, /* seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */ + {0x0ac9, 0x2122}, /* trademark ™ TRADE MARK SIGN */ + {0x0aca, 0x2613}, /* signaturemark ☓ SALTIRE */ + /* 0x0acb trademarkincircle ? ??? */ + {0x0acc, 0x25c1}, /* leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */ + {0x0acd, 0x25b7}, /* rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */ + {0x0ace, 0x25cb}, /* emopencircle ○ WHITE CIRCLE */ + {0x0acf, 0x25a1}, /* emopenrectangle □ WHITE SQUARE */ + {0x0ad0, 0x2018}, /* leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */ + {0x0ad1, 0x2019}, /* rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */ + {0x0ad2, 0x201c}, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */ + {0x0ad3, 0x201d}, /* rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */ + {0x0ad4, 0x211e}, /* prescription ℞ PRESCRIPTION TAKE */ + {0x0ad6, 0x2032}, /* minutes ′ PRIME */ + {0x0ad7, 0x2033}, /* seconds ″ DOUBLE PRIME */ + {0x0ad9, 0x271d}, /* latincross ✝ LATIN CROSS */ + /* 0x0ada hexagram ? ??? */ + {0x0adb, 0x25ac}, /* filledrectbullet ▬ BLACK RECTANGLE */ + {0x0adc, 0x25c0}, /* filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */ + {0x0add, 0x25b6}, /* filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */ + {0x0ade, 0x25cf}, /* emfilledcircle ● BLACK CIRCLE */ + {0x0adf, 0x25a0}, /* emfilledrect ■ BLACK SQUARE */ + {0x0ae0, 0x25e6}, /* enopencircbullet ◦ WHITE BULLET */ + {0x0ae1, 0x25ab}, /* enopensquarebullet ▫ WHITE SMALL SQUARE */ + {0x0ae2, 0x25ad}, /* openrectbullet ▭ WHITE RECTANGLE */ + {0x0ae3, 0x25b3}, /* opentribulletup △ WHITE UP-POINTING TRIANGLE */ + {0x0ae4, 0x25bd}, /* opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */ + {0x0ae5, 0x2606}, /* openstar ☆ WHITE STAR */ + {0x0ae6, 0x2022}, /* enfilledcircbullet • BULLET */ + {0x0ae7, 0x25aa}, /* enfilledsqbullet ▪ BLACK SMALL SQUARE */ + {0x0ae8, 0x25b2}, /* filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */ + {0x0ae9, 0x25bc}, /* filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */ + {0x0aea, 0x261c}, /* leftpointer ☜ WHITE LEFT POINTING INDEX */ + {0x0aeb, 0x261e}, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */ + {0x0aec, 0x2663}, /* club ♣ BLACK CLUB SUIT */ + {0x0aed, 0x2666}, /* diamond ♦ BLACK DIAMOND SUIT */ + {0x0aee, 0x2665}, /* heart ♥ BLACK HEART SUIT */ + {0x0af0, 0x2720}, /* maltesecross ✠ MALTESE CROSS */ + {0x0af1, 0x2020}, /* dagger † DAGGER */ + {0x0af2, 0x2021}, /* doubledagger ‡ DOUBLE DAGGER */ + {0x0af3, 0x2713}, /* checkmark ✓ CHECK MARK */ + {0x0af4, 0x2717}, /* ballotcross ✗ BALLOT X */ + {0x0af5, 0x266f}, /* musicalsharp ♯ MUSIC SHARP SIGN */ + {0x0af6, 0x266d}, /* musicalflat ♭ MUSIC FLAT SIGN */ + {0x0af7, 0x2642}, /* malesymbol ♂ MALE SIGN */ + {0x0af8, 0x2640}, /* femalesymbol ♀ FEMALE SIGN */ + {0x0af9, 0x260e}, /* telephone ☎ BLACK TELEPHONE */ + {0x0afa, 0x2315}, /* telephonerecorder ⌕ TELEPHONE RECORDER */ + {0x0afb, 0x2117}, /* phonographcopyright ℗ SOUND RECORDING COPYRIGHT */ + {0x0afc, 0x2038}, /* caret ‸ CARET */ + {0x0afd, 0x201a}, /* singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */ + {0x0afe, 0x201e}, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */ + /* 0x0aff cursor ? ??? */ + {0x0ba3, 0x003c}, /* leftcaret < LESS-THAN SIGN */ + {0x0ba6, 0x003e}, /* rightcaret > GREATER-THAN SIGN */ + {0x0ba8, 0x2228}, /* downcaret ∨ LOGICAL OR */ + {0x0ba9, 0x2227}, /* upcaret ∧ LOGICAL AND */ + {0x0bc0, 0x00af}, /* overbar ¯ MACRON */ + {0x0bc2, 0x22a4}, /* downtack ⊤ DOWN TACK */ + {0x0bc3, 0x2229}, /* upshoe ∩ INTERSECTION */ + {0x0bc4, 0x230a}, /* downstile ⌊ LEFT FLOOR */ + {0x0bc6, 0x005f}, /* underbar _ LOW LINE */ + {0x0bca, 0x2218}, /* jot ∘ RING OPERATOR */ + {0x0bcc, 0x2395}, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD */ + {0x0bce, 0x22a5}, /* uptack ⊥ UP TACK */ + {0x0bcf, 0x25cb}, /* circle ○ WHITE CIRCLE */ + {0x0bd3, 0x2308}, /* upstile ⌈ LEFT CEILING */ + {0x0bd6, 0x222a}, /* downshoe ∪ UNION */ + {0x0bd8, 0x2283}, /* rightshoe ⊃ SUPERSET OF */ + {0x0bda, 0x2282}, /* leftshoe ⊂ SUBSET OF */ + {0x0bdc, 0x22a3}, /* lefttack ⊣ LEFT TACK */ + {0x0bfc, 0x22a2}, /* righttack ⊢ RIGHT TACK */ + {0x0cdf, 0x2017}, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */ + {0x0ce0, 0x05d0}, /* hebrew_aleph א HEBREW LETTER ALEF */ + {0x0ce1, 0x05d1}, /* hebrew_bet ב HEBREW LETTER BET */ + {0x0ce2, 0x05d2}, /* hebrew_gimel ג HEBREW LETTER GIMEL */ + {0x0ce3, 0x05d3}, /* hebrew_dalet ד HEBREW LETTER DALET */ + {0x0ce4, 0x05d4}, /* hebrew_he ה HEBREW LETTER HE */ + {0x0ce5, 0x05d5}, /* hebrew_waw ו HEBREW LETTER VAV */ + {0x0ce6, 0x05d6}, /* hebrew_zain ז HEBREW LETTER ZAYIN */ + {0x0ce7, 0x05d7}, /* hebrew_chet ח HEBREW LETTER HET */ + {0x0ce8, 0x05d8}, /* hebrew_tet ט HEBREW LETTER TET */ + {0x0ce9, 0x05d9}, /* hebrew_yod י HEBREW LETTER YOD */ + {0x0cea, 0x05da}, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */ + {0x0ceb, 0x05db}, /* hebrew_kaph כ HEBREW LETTER KAF */ + {0x0cec, 0x05dc}, /* hebrew_lamed ל HEBREW LETTER LAMED */ + {0x0ced, 0x05dd}, /* hebrew_finalmem ם HEBREW LETTER FINAL MEM */ + {0x0cee, 0x05de}, /* hebrew_mem מ HEBREW LETTER MEM */ + {0x0cef, 0x05df}, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */ + {0x0cf0, 0x05e0}, /* hebrew_nun נ HEBREW LETTER NUN */ + {0x0cf1, 0x05e1}, /* hebrew_samech ס HEBREW LETTER SAMEKH */ + {0x0cf2, 0x05e2}, /* hebrew_ayin ע HEBREW LETTER AYIN */ + {0x0cf3, 0x05e3}, /* hebrew_finalpe ף HEBREW LETTER FINAL PE */ + {0x0cf4, 0x05e4}, /* hebrew_pe פ HEBREW LETTER PE */ + {0x0cf5, 0x05e5}, /* hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */ + {0x0cf6, 0x05e6}, /* hebrew_zade צ HEBREW LETTER TSADI */ + {0x0cf7, 0x05e7}, /* hebrew_qoph ק HEBREW LETTER QOF */ + {0x0cf8, 0x05e8}, /* hebrew_resh ר HEBREW LETTER RESH */ + {0x0cf9, 0x05e9}, /* hebrew_shin ש HEBREW LETTER SHIN */ + {0x0cfa, 0x05ea}, /* hebrew_taw ת HEBREW LETTER TAV */ + {0x0da1, 0x0e01}, /* Thai_kokai ก THAI CHARACTER KO KAI */ + {0x0da2, 0x0e02}, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */ + {0x0da3, 0x0e03}, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */ + {0x0da4, 0x0e04}, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */ + {0x0da5, 0x0e05}, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */ + {0x0da6, 0x0e06}, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */ + {0x0da7, 0x0e07}, /* Thai_ngongu ง THAI CHARACTER NGO NGU */ + {0x0da8, 0x0e08}, /* Thai_chochan จ THAI CHARACTER CHO CHAN */ + {0x0da9, 0x0e09}, /* Thai_choching ฉ THAI CHARACTER CHO CHING */ + {0x0daa, 0x0e0a}, /* Thai_chochang ช THAI CHARACTER CHO CHANG */ + {0x0dab, 0x0e0b}, /* Thai_soso ซ THAI CHARACTER SO SO */ + {0x0dac, 0x0e0c}, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */ + {0x0dad, 0x0e0d}, /* Thai_yoying ญ THAI CHARACTER YO YING */ + {0x0dae, 0x0e0e}, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */ + {0x0daf, 0x0e0f}, /* Thai_topatak ฏ THAI CHARACTER TO PATAK */ + {0x0db0, 0x0e10}, /* Thai_thothan ฐ THAI CHARACTER THO THAN */ + {0x0db1, 0x0e11}, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */ + {0x0db2, 0x0e12}, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */ + {0x0db3, 0x0e13}, /* Thai_nonen ณ THAI CHARACTER NO NEN */ + {0x0db4, 0x0e14}, /* Thai_dodek ด THAI CHARACTER DO DEK */ + {0x0db5, 0x0e15}, /* Thai_totao ต THAI CHARACTER TO TAO */ + {0x0db6, 0x0e16}, /* Thai_thothung ถ THAI CHARACTER THO THUNG */ + {0x0db7, 0x0e17}, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */ + {0x0db8, 0x0e18}, /* Thai_thothong ธ THAI CHARACTER THO THONG */ + {0x0db9, 0x0e19}, /* Thai_nonu น THAI CHARACTER NO NU */ + {0x0dba, 0x0e1a}, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */ + {0x0dbb, 0x0e1b}, /* Thai_popla ป THAI CHARACTER PO PLA */ + {0x0dbc, 0x0e1c}, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */ + {0x0dbd, 0x0e1d}, /* Thai_fofa ฝ THAI CHARACTER FO FA */ + {0x0dbe, 0x0e1e}, /* Thai_phophan พ THAI CHARACTER PHO PHAN */ + {0x0dbf, 0x0e1f}, /* Thai_fofan ฟ THAI CHARACTER FO FAN */ + {0x0dc0, 0x0e20}, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */ + {0x0dc1, 0x0e21}, /* Thai_moma ม THAI CHARACTER MO MA */ + {0x0dc2, 0x0e22}, /* Thai_yoyak ย THAI CHARACTER YO YAK */ + {0x0dc3, 0x0e23}, /* Thai_rorua ร THAI CHARACTER RO RUA */ + {0x0dc4, 0x0e24}, /* Thai_ru ฤ THAI CHARACTER RU */ + {0x0dc5, 0x0e25}, /* Thai_loling ล THAI CHARACTER LO LING */ + {0x0dc6, 0x0e26}, /* Thai_lu ฦ THAI CHARACTER LU */ + {0x0dc7, 0x0e27}, /* Thai_wowaen ว THAI CHARACTER WO WAEN */ + {0x0dc8, 0x0e28}, /* Thai_sosala ศ THAI CHARACTER SO SALA */ + {0x0dc9, 0x0e29}, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */ + {0x0dca, 0x0e2a}, /* Thai_sosua ส THAI CHARACTER SO SUA */ + {0x0dcb, 0x0e2b}, /* Thai_hohip ห THAI CHARACTER HO HIP */ + {0x0dcc, 0x0e2c}, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */ + {0x0dcd, 0x0e2d}, /* Thai_oang อ THAI CHARACTER O ANG */ + {0x0dce, 0x0e2e}, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */ + {0x0dcf, 0x0e2f}, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */ + {0x0dd0, 0x0e30}, /* Thai_saraa ะ THAI CHARACTER SARA A */ + {0x0dd1, 0x0e31}, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */ + {0x0dd2, 0x0e32}, /* Thai_saraaa า THAI CHARACTER SARA AA */ + {0x0dd3, 0x0e33}, /* Thai_saraam ำ THAI CHARACTER SARA AM */ + {0x0dd4, 0x0e34}, /* Thai_sarai ิ THAI CHARACTER SARA I */ + {0x0dd5, 0x0e35}, /* Thai_saraii ี THAI CHARACTER SARA II */ + {0x0dd6, 0x0e36}, /* Thai_saraue ึ THAI CHARACTER SARA UE */ + {0x0dd7, 0x0e37}, /* Thai_sarauee ื THAI CHARACTER SARA UEE */ + {0x0dd8, 0x0e38}, /* Thai_sarau ุ THAI CHARACTER SARA U */ + {0x0dd9, 0x0e39}, /* Thai_sarauu ู THAI CHARACTER SARA UU */ + {0x0dda, 0x0e3a}, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */ + {0x0dde, 0x0e3e}, /* Thai_maihanakat_maitho ??? */ + {0x0ddf, 0x0e3f}, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */ + {0x0de0, 0x0e40}, /* Thai_sarae เ THAI CHARACTER SARA E */ + {0x0de1, 0x0e41}, /* Thai_saraae แ THAI CHARACTER SARA AE */ + {0x0de2, 0x0e42}, /* Thai_sarao โ THAI CHARACTER SARA O */ + {0x0de3, 0x0e43}, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */ + {0x0de4, 0x0e44}, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */ + {0x0de5, 0x0e45}, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */ + {0x0de6, 0x0e46}, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */ + {0x0de7, 0x0e47}, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */ + {0x0de8, 0x0e48}, /* Thai_maiek ่ THAI CHARACTER MAI EK */ + {0x0de9, 0x0e49}, /* Thai_maitho ้ THAI CHARACTER MAI THO */ + {0x0dea, 0x0e4a}, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */ + {0x0deb, 0x0e4b}, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */ + {0x0dec, 0x0e4c}, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */ + {0x0ded, 0x0e4d}, /* Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */ + {0x0df0, 0x0e50}, /* Thai_leksun ๐ THAI DIGIT ZERO */ + {0x0df1, 0x0e51}, /* Thai_leknung ๑ THAI DIGIT ONE */ + {0x0df2, 0x0e52}, /* Thai_leksong ๒ THAI DIGIT TWO */ + {0x0df3, 0x0e53}, /* Thai_leksam ๓ THAI DIGIT THREE */ + {0x0df4, 0x0e54}, /* Thai_leksi ๔ THAI DIGIT FOUR */ + {0x0df5, 0x0e55}, /* Thai_lekha ๕ THAI DIGIT FIVE */ + {0x0df6, 0x0e56}, /* Thai_lekhok ๖ THAI DIGIT SIX */ + {0x0df7, 0x0e57}, /* Thai_lekchet ๗ THAI DIGIT SEVEN */ + {0x0df8, 0x0e58}, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */ + {0x0df9, 0x0e59}, /* Thai_lekkao ๙ THAI DIGIT NINE */ + {0x0ea1, 0x3131}, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */ + {0x0ea2, 0x3132}, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */ + {0x0ea3, 0x3133}, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */ + {0x0ea4, 0x3134}, /* Hangul_Nieun ㄴ HANGUL LETTER NIEUN */ + {0x0ea5, 0x3135}, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */ + {0x0ea6, 0x3136}, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */ + {0x0ea7, 0x3137}, /* Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */ + {0x0ea8, 0x3138}, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */ + {0x0ea9, 0x3139}, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */ + {0x0eaa, 0x313a}, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */ + {0x0eab, 0x313b}, /* Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */ + {0x0eac, 0x313c}, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */ + {0x0ead, 0x313d}, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */ + {0x0eae, 0x313e}, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */ + {0x0eaf, 0x313f}, /* Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */ + {0x0eb0, 0x3140}, /* Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */ + {0x0eb1, 0x3141}, /* Hangul_Mieum ㅁ HANGUL LETTER MIEUM */ + {0x0eb2, 0x3142}, /* Hangul_Pieub ㅂ HANGUL LETTER PIEUP */ + {0x0eb3, 0x3143}, /* Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */ + {0x0eb4, 0x3144}, /* Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */ + {0x0eb5, 0x3145}, /* Hangul_Sios ㅅ HANGUL LETTER SIOS */ + {0x0eb6, 0x3146}, /* Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */ + {0x0eb7, 0x3147}, /* Hangul_Ieung ㅇ HANGUL LETTER IEUNG */ + {0x0eb8, 0x3148}, /* Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */ + {0x0eb9, 0x3149}, /* Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */ + {0x0eba, 0x314a}, /* Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */ + {0x0ebb, 0x314b}, /* Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */ + {0x0ebc, 0x314c}, /* Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */ + {0x0ebd, 0x314d}, /* Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */ + {0x0ebe, 0x314e}, /* Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */ + {0x0ebf, 0x314f}, /* Hangul_A ㅏ HANGUL LETTER A */ + {0x0ec0, 0x3150}, /* Hangul_AE ㅐ HANGUL LETTER AE */ + {0x0ec1, 0x3151}, /* Hangul_YA ㅑ HANGUL LETTER YA */ + {0x0ec2, 0x3152}, /* Hangul_YAE ㅒ HANGUL LETTER YAE */ + {0x0ec3, 0x3153}, /* Hangul_EO ㅓ HANGUL LETTER EO */ + {0x0ec4, 0x3154}, /* Hangul_E ㅔ HANGUL LETTER E */ + {0x0ec5, 0x3155}, /* Hangul_YEO ㅕ HANGUL LETTER YEO */ + {0x0ec6, 0x3156}, /* Hangul_YE ㅖ HANGUL LETTER YE */ + {0x0ec7, 0x3157}, /* Hangul_O ㅗ HANGUL LETTER O */ + {0x0ec8, 0x3158}, /* Hangul_WA ㅘ HANGUL LETTER WA */ + {0x0ec9, 0x3159}, /* Hangul_WAE ㅙ HANGUL LETTER WAE */ + {0x0eca, 0x315a}, /* Hangul_OE ㅚ HANGUL LETTER OE */ + {0x0ecb, 0x315b}, /* Hangul_YO ㅛ HANGUL LETTER YO */ + {0x0ecc, 0x315c}, /* Hangul_U ㅜ HANGUL LETTER U */ + {0x0ecd, 0x315d}, /* Hangul_WEO ㅝ HANGUL LETTER WEO */ + {0x0ece, 0x315e}, /* Hangul_WE ㅞ HANGUL LETTER WE */ + {0x0ecf, 0x315f}, /* Hangul_WI ㅟ HANGUL LETTER WI */ + {0x0ed0, 0x3160}, /* Hangul_YU ㅠ HANGUL LETTER YU */ + {0x0ed1, 0x3161}, /* Hangul_EU ㅡ HANGUL LETTER EU */ + {0x0ed2, 0x3162}, /* Hangul_YI ㅢ HANGUL LETTER YI */ + {0x0ed3, 0x3163}, /* Hangul_I ㅣ HANGUL LETTER I */ + {0x0ed4, 0x11a8}, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */ + {0x0ed5, 0x11a9}, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */ + {0x0ed6, 0x11aa}, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */ + {0x0ed7, 0x11ab}, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */ + {0x0ed8, 0x11ac}, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */ + {0x0ed9, 0x11ad}, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */ + {0x0eda, 0x11ae}, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */ + {0x0edb, 0x11af}, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */ + {0x0edc, 0x11b0}, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */ + {0x0edd, 0x11b1}, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */ + {0x0ede, 0x11b2}, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */ + {0x0edf, 0x11b3}, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */ + {0x0ee0, 0x11b4}, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */ + {0x0ee1, 0x11b5}, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */ + {0x0ee2, 0x11b6}, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */ + {0x0ee3, 0x11b7}, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */ + {0x0ee4, 0x11b8}, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */ + {0x0ee5, 0x11b9}, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */ + {0x0ee6, 0x11ba}, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */ + {0x0ee7, 0x11bb}, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */ + {0x0ee8, 0x11bc}, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */ + {0x0ee9, 0x11bd}, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */ + {0x0eea, 0x11be}, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */ + {0x0eeb, 0x11bf}, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */ + {0x0eec, 0x11c0}, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */ + {0x0eed, 0x11c1}, /* Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */ + {0x0eee, 0x11c2}, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */ + {0x0eef, 0x316d}, /* Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */ + {0x0ef0, 0x3171}, /* Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */ + {0x0ef1, 0x3178}, /* Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */ + {0x0ef2, 0x317f}, /* Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */ + /* 0x0ef3 Hangul_KkogjiDalrinIeung ? ??? */ + {0x0ef4, 0x3184}, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */ + {0x0ef5, 0x3186}, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */ + {0x0ef6, 0x318d}, /* Hangul_AraeA ㆍ HANGUL LETTER ARAEA */ + {0x0ef7, 0x318e}, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */ + {0x0ef8, 0x11eb}, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */ + /* 0x0ef9 Hangul_J_KkogjiDalrinIeung ? ??? */ + {0x0efa, 0x11f9}, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */ + {0x0eff, 0x20a9}, /* Korean_Won ₩ WON SIGN */ + {0x13bc, 0x0152}, /* OE Œ LATIN CAPITAL LIGATURE OE */ + {0x13bd, 0x0153}, /* oe œ LATIN SMALL LIGATURE OE */ + {0x13be, 0x0178}, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */ + {0x20ac, 0x20ac}, /* EuroSign € EURO SIGN */ }; long keysym2ucs(xcb_keysym_t keysym) { diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 0929e408..77be3182 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -74,10 +74,12 @@ struct status_block { char *name; char *instance; - TAILQ_ENTRY(status_block) blocks; + TAILQ_ENTRY(status_block) + blocks; }; -TAILQ_HEAD(statusline_head, status_block) statusline_head; +TAILQ_HEAD(statusline_head, status_block) +statusline_head; #include "child.h" #include "ipc.h" diff --git a/i3bar/include/configuration.h b/i3bar/include/configuration.h index c7c1f5e2..e77e891b 100644 --- a/i3bar/include/configuration.h +++ b/i3bar/include/configuration.h @@ -28,18 +28,23 @@ typedef struct binding_t { int input_code; char *command; - TAILQ_ENTRY(binding_t) bindings; + TAILQ_ENTRY(binding_t) + bindings; } binding_t; typedef struct tray_output_t { char *output; - TAILQ_ENTRY(tray_output_t) tray_outputs; + TAILQ_ENTRY(tray_output_t) + tray_outputs; } tray_output_t; typedef struct config_t { int modifier; - TAILQ_HEAD(bindings_head, binding_t) bindings; + + TAILQ_HEAD(bindings_head, binding_t) + bindings; + position_t position; int verbose; struct xcb_color_strings_t colors; @@ -50,7 +55,10 @@ typedef struct config_t { char *command; char *fontname; i3String *separator_symbol; - TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs; + + TAILQ_HEAD(tray_outputs_head, tray_output_t) + tray_outputs; + int tray_padding; int num_outputs; char **outputs; diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index 3067581d..de960270 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -67,5 +67,6 @@ struct i3_output { struct ws_head* workspaces; /* The workspaces on this output */ struct tc_head* trayclients; /* The tray clients on this output */ - SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */ + SLIST_ENTRY(i3_output) + slist; /* Pointer for the SLIST-Macro */ }; diff --git a/i3bar/include/trayclients.h b/i3bar/include/trayclients.h index 694faa48..db954bb1 100644 --- a/i3bar/include/trayclients.h +++ b/i3bar/include/trayclients.h @@ -18,5 +18,6 @@ struct trayclient { bool mapped; /* Whether this window is mapped */ int xe_version; /* The XEMBED version supported by the client */ - TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */ + TAILQ_ENTRY(trayclient) + tailq; /* Pointer for the TAILQ-Macro */ }; diff --git a/i3bar/include/workspaces.h b/i3bar/include/workspaces.h index dde8b6e1..e1f9e887 100644 --- a/i3bar/include/workspaces.h +++ b/i3bar/include/workspaces.h @@ -40,5 +40,6 @@ struct i3_ws { rect rect; /* The rect of the ws (not used (yet)) */ struct i3_output *output; /* The current output of the ws */ - TAILQ_ENTRY(i3_ws) tailq; /* Pointer for the TAILQ-Macro */ + TAILQ_ENTRY(i3_ws) + tailq; /* Pointer for the TAILQ-Macro */ }; diff --git a/include/configuration.h b/include/configuration.h index b7cdc804..f93afde7 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -68,7 +68,8 @@ struct Variable { char *value; char *next_match; - SLIST_ENTRY(Variable) variables; + SLIST_ENTRY(Variable) + variables; }; /** @@ -82,7 +83,8 @@ struct Mode { bool pango_markup; struct bindings_head *bindings; - SLIST_ENTRY(Mode) modes; + SLIST_ENTRY(Mode) + modes; }; /** @@ -253,7 +255,8 @@ struct Barconfig { /* List of outputs on which the tray is allowed to be shown, in order. * The special value "none" disables it (per default, it will be shown) and * the special value "primary" enabled it on the primary output. */ - TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs; + TAILQ_HEAD(tray_outputs_head, tray_output_t) + tray_outputs; /* Padding around the tray icons. */ int tray_padding; @@ -284,7 +287,8 @@ struct Barconfig { M_MOD5 = 7 } modifier; - TAILQ_HEAD(bar_bindings_head, Barbinding) bar_bindings; + TAILQ_HEAD(bar_bindings_head, Barbinding) + bar_bindings; /** Bar position (bottom by default). */ enum { P_BOTTOM = 0, @@ -351,7 +355,8 @@ struct Barconfig { char *binding_mode_text; } colors; - TAILQ_ENTRY(Barconfig) configs; + TAILQ_ENTRY(Barconfig) + configs; }; /** @@ -366,13 +371,15 @@ struct Barbinding { /** The command which is to be executed for this button. */ char *command; - TAILQ_ENTRY(Barbinding) bindings; + TAILQ_ENTRY(Barbinding) + bindings; }; struct tray_output_t { char *output; - TAILQ_ENTRY(tray_output_t) tray_outputs; + TAILQ_ENTRY(tray_output_t) + tray_outputs; }; /** diff --git a/include/data.h b/include/data.h index a729b21e..410f2e0d 100644 --- a/include/data.h +++ b/include/data.h @@ -199,7 +199,8 @@ struct Workspace_Assignment { char *name; char *output; - TAILQ_ENTRY(Workspace_Assignment) ws_assignments; + TAILQ_ENTRY(Workspace_Assignment) + ws_assignments; }; struct Ignore_Event { @@ -207,7 +208,8 @@ struct Ignore_Event { int response_type; time_t added; - SLIST_ENTRY(Ignore_Event) ignore_events; + SLIST_ENTRY(Ignore_Event) + ignore_events; }; /** @@ -226,7 +228,8 @@ struct Startup_Sequence { * completed) */ time_t delete_at; - TAILQ_ENTRY(Startup_Sequence) sequences; + TAILQ_ENTRY(Startup_Sequence) + sequences; }; /** @@ -252,7 +255,9 @@ struct regex { struct Binding_Keycode { xcb_keycode_t keycode; i3_event_state_mask_t modifiers; - TAILQ_ENTRY(Binding_Keycode) keycodes; + + TAILQ_ENTRY(Binding_Keycode) + keycodes; }; /****************************************************************************** @@ -309,12 +314,14 @@ struct Binding { /** Only in use if symbol != NULL. Contains keycodes which generate the * specified symbol. Useful for unbinding and checking which binding was * used when a key press event comes in. */ - TAILQ_HEAD(keycodes_head, Binding_Keycode) keycodes_head; + TAILQ_HEAD(keycodes_head, Binding_Keycode) + keycodes_head; /** Command, like in command mode */ char *command; - TAILQ_ENTRY(Binding) bindings; + TAILQ_ENTRY(Binding) + bindings; }; /** @@ -330,8 +337,12 @@ struct Autostart { /** no_startup_id flag for start_application(). Determines whether a * startup notification context/ID should be created. */ bool no_startup_id; - TAILQ_ENTRY(Autostart) autostarts; - TAILQ_ENTRY(Autostart) autostarts_always; + + TAILQ_ENTRY(Autostart) + autostarts; + + TAILQ_ENTRY(Autostart) + autostarts_always; }; /** @@ -364,7 +375,8 @@ struct xoutput { /** x, y, width, height */ Rect rect; - TAILQ_ENTRY(xoutput) outputs; + TAILQ_ENTRY(xoutput) + outputs; }; /** @@ -493,7 +505,8 @@ struct Match { M_ASSIGN_WS, M_BELOW } insert_where; - TAILQ_ENTRY(Match) matches; + TAILQ_ENTRY(Match) + matches; /* Whether this match was generated when restarting i3 inplace. * Leads to not setting focus when managing a new window, because the old @@ -537,7 +550,8 @@ struct Assignment { char *workspace; } dest; - TAILQ_ENTRY(Assignment) assignments; + TAILQ_ENTRY(Assignment) + assignments; }; /** Fullscreen modes. Used by Con.fullscreen_mode. */ @@ -548,7 +562,8 @@ typedef enum { CF_NONE = 0, struct mark_t { char *name; - TAILQ_ENTRY(mark_t) marks; + TAILQ_ENTRY(mark_t) + marks; }; /** @@ -612,7 +627,8 @@ struct Con { char *sticky_group; /* user-definable marks to jump to this container later */ - TAILQ_HEAD(marks_head, mark_t) marks_head; + TAILQ_HEAD(marks_head, mark_t) + marks_head; /* cached to decide whether a redraw is needed */ bool mark_changed; @@ -631,12 +647,17 @@ struct Con { struct deco_render_params *deco_render_params; /* Only workspace-containers can have floating clients */ - TAILQ_HEAD(floating_head, Con) floating_head; + TAILQ_HEAD(floating_head, Con) + floating_head; - TAILQ_HEAD(nodes_head, Con) nodes_head; - TAILQ_HEAD(focus_head, Con) focus_head; + TAILQ_HEAD(nodes_head, Con) + nodes_head; - TAILQ_HEAD(swallow_head, Match) swallow_head; + TAILQ_HEAD(focus_head, Con) + focus_head; + + TAILQ_HEAD(swallow_head, Match) + swallow_head; fullscreen_mode_t fullscreen_mode; @@ -674,10 +695,17 @@ struct Con { FLOATING_USER_ON = 3 } floating; - TAILQ_ENTRY(Con) nodes; - TAILQ_ENTRY(Con) focused; - TAILQ_ENTRY(Con) all_cons; - TAILQ_ENTRY(Con) floating_windows; + TAILQ_ENTRY(Con) + nodes; + + TAILQ_ENTRY(Con) + focused; + + TAILQ_ENTRY(Con) + all_cons; + + TAILQ_ENTRY(Con) + floating_windows; /** callbacks */ void (*on_remove_child)(Con *); diff --git a/include/ipc.h b/include/ipc.h index 5c528a6d..7ff4704c 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -31,7 +31,8 @@ typedef struct ipc_client { int num_events; char **events; - TAILQ_ENTRY(ipc_client) clients; + TAILQ_ENTRY(ipc_client) + clients; } ipc_client; /* diff --git a/include/queue.h b/include/queue.h index 9fb9ba5e..9b410449 100644 --- a/include/queue.h +++ b/include/queue.h @@ -446,7 +446,10 @@ } #define CIRCLEQ_HEAD_INITIALIZER(head) \ - { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } + { \ + CIRCLEQ_END(&head) \ + , CIRCLEQ_END(&head) \ + } #define CIRCLEQ_ENTRY(type) \ struct { \ diff --git a/src/commands.c b/src/commands.c index b91c71a4..70622b7d 100644 --- a/src/commands.c +++ b/src/commands.c @@ -142,7 +142,9 @@ static Con *maybe_auto_back_and_forth_workspace(Con *workspace) { */ typedef struct owindow { Con *con; - TAILQ_ENTRY(owindow) owindows; + + TAILQ_ENTRY(owindow) + owindows; } owindow; typedef TAILQ_HEAD(owindows_head, owindow) owindows_head; diff --git a/src/con.c b/src/con.c index 36225415..40924a73 100644 --- a/src/con.c +++ b/src/con.c @@ -410,7 +410,8 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation) { struct bfs_entry { Con *con; - TAILQ_ENTRY(bfs_entry) entries; + TAILQ_ENTRY(bfs_entry) + entries; }; /* @@ -422,7 +423,9 @@ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) { /* TODO: is breadth-first-search really appropriate? (check as soon as * fullscreen levels and fullscreen for containers is implemented) */ - TAILQ_HEAD(bfs_head, bfs_entry) bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head); + TAILQ_HEAD(bfs_head, bfs_entry) + bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head); + struct bfs_entry *entry = smalloc(sizeof(struct bfs_entry)); entry->con = con; TAILQ_INSERT_TAIL(&bfs_head, entry, entries); diff --git a/src/config_parser.c b/src/config_parser.c index a8265869..60b27815 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -1002,7 +1002,7 @@ bool parse_file(const char *f, bool use_nagbar) { char *next; for (next = bufcopy; next < (bufcopy + stbuf.st_size) && - (next = strcasestr(next, current->key)) != NULL; + (next = strcasestr(next, current->key)) != NULL; next += strlen(current->key)) { *next = '_'; extra_bytes += extra; diff --git a/src/ipc.c b/src/ipc.c index c0dfb1ec..db2fa362 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -22,7 +22,8 @@ char *current_socketpath = NULL; -TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients); +TAILQ_HEAD(ipc_client_head, ipc_client) +all_clients = TAILQ_HEAD_INITIALIZER(all_clients); /* * Puts the given socket file descriptor into non-blocking mode or dies if diff --git a/src/load_layout.c b/src/load_layout.c index 7004a859..f6f045d2 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -34,7 +34,9 @@ static bool swallow_is_empty; * array. */ struct focus_mapping { int old_id; - TAILQ_ENTRY(focus_mapping) focus_mappings; + + TAILQ_ENTRY(focus_mapping) + focus_mappings; }; static TAILQ_HEAD(focus_mappings_head, focus_mapping) focus_mappings = diff --git a/src/match.c b/src/match.c index 4d87c560..9185537b 100644 --- a/src/match.c +++ b/src/match.c @@ -167,7 +167,7 @@ bool match_matches_window(Match *match, i3Window *window) { /* if we find a window that is newer than this one, bail */ TAILQ_FOREACH(con, &all_cons, all_cons) { if ((con->window != NULL) && - _i3_timercmp(con->window->urgent, window->urgent, > )) { + _i3_timercmp(con->window->urgent, window->urgent, >)) { return false; } } @@ -183,7 +183,7 @@ bool match_matches_window(Match *match, i3Window *window) { TAILQ_FOREACH(con, &all_cons, all_cons) { if ((con->window != NULL) && (con->window->urgent.tv_sec != 0) && - _i3_timercmp(con->window->urgent, window->urgent, < )) { + _i3_timercmp(con->window->urgent, window->urgent, <)) { return false; } } diff --git a/src/restore_layout.c b/src/restore_layout.c index 131196e9..d48e5c6e 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -29,7 +29,8 @@ typedef struct placeholder_state { /** The graphics context for “pixmap”. */ xcb_gcontext_t gc; - TAILQ_ENTRY(placeholder_state) state; + TAILQ_ENTRY(placeholder_state) + state; } placeholder_state; static TAILQ_HEAD(state_head, placeholder_state) state_head = diff --git a/src/x.c b/src/x.c index 8d7d3dd8..c2153d5c 100644 --- a/src/x.c +++ b/src/x.c @@ -58,18 +58,26 @@ typedef struct con_state { char *name; - CIRCLEQ_ENTRY(con_state) state; - CIRCLEQ_ENTRY(con_state) old_state; - TAILQ_ENTRY(con_state) initial_mapping_order; + CIRCLEQ_ENTRY(con_state) + state; + + CIRCLEQ_ENTRY(con_state) + old_state; + + TAILQ_ENTRY(con_state) + initial_mapping_order; } con_state; -CIRCLEQ_HEAD(state_head, con_state) state_head = +CIRCLEQ_HEAD(state_head, con_state) +state_head = CIRCLEQ_HEAD_INITIALIZER(state_head); -CIRCLEQ_HEAD(old_state_head, con_state) old_state_head = +CIRCLEQ_HEAD(old_state_head, con_state) +old_state_head = CIRCLEQ_HEAD_INITIALIZER(old_state_head); -TAILQ_HEAD(initial_mapping_head, con_state) initial_mapping_head = +TAILQ_HEAD(initial_mapping_head, con_state) +initial_mapping_head = TAILQ_HEAD_INITIALIZER(initial_mapping_head); /* diff --git a/travis/check-formatting.sh b/travis/check-formatting.sh index ead25157..ff406bea 100755 --- a/travis/check-formatting.sh +++ b/travis/check-formatting.sh @@ -3,4 +3,4 @@ set -e set -x -clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false) +clang-format-3.8 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false) diff --git a/travis/travis-base-386.Dockerfile b/travis/travis-base-386.Dockerfile index 9f2b8129..ddb3874e 100644 --- a/travis/travis-base-386.Dockerfile +++ b/travis/travis-base-386.Dockerfile @@ -13,12 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. # Install mk-build-deps (for installing the i3 build dependencies), -# clang and clang-format-3.5 (for checking formatting and building with clang), +# clang and clang-format-3.8 (for checking formatting and building with clang), # lintian (for checking spelling errors), RUN linux32 apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - clang clang-format-3.5 \ + clang clang-format-3.8 \ lintian && \ rm -rf /var/lib/apt/lists/* diff --git a/travis/travis-base-ubuntu-386.Dockerfile b/travis/travis-base-ubuntu-386.Dockerfile index 65f123c8..1014407a 100644 --- a/travis/travis-base-ubuntu-386.Dockerfile +++ b/travis/travis-base-ubuntu-386.Dockerfile @@ -13,12 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. # Install mk-build-deps (for installing the i3 build dependencies), -# clang and clang-format-3.5 (for checking formatting and building with clang), +# clang and clang-format-3.8 (for checking formatting and building with clang), # lintian (for checking spelling errors), RUN linux32 apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - clang clang-format-3.5 \ + clang clang-format-3.8 \ lintian && \ rm -rf /var/lib/apt/lists/* diff --git a/travis/travis-base-ubuntu.Dockerfile b/travis/travis-base-ubuntu.Dockerfile index ab474691..0b4ec206 100644 --- a/travis/travis-base-ubuntu.Dockerfile +++ b/travis/travis-base-ubuntu.Dockerfile @@ -13,13 +13,13 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. # Install mk-build-deps (for installing the i3 build dependencies), -# clang and clang-format-3.5 (for checking formatting and building with clang), +# clang and clang-format-3.8 (for checking formatting and building with clang), # lintian (for checking spelling errors), # test suite dependencies (for running tests) RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - clang clang-format-3.5 \ + clang clang-format-3.8 \ lintian && \ rm -rf /var/lib/apt/lists/* diff --git a/travis/travis-base.Dockerfile b/travis/travis-base.Dockerfile index 85fa2752..a415f549 100644 --- a/travis/travis-base.Dockerfile +++ b/travis/travis-base.Dockerfile @@ -11,13 +11,13 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. # Install mk-build-deps (for installing the i3 build dependencies), -# clang and clang-format-3.5 (for checking formatting and building with clang), +# clang and clang-format-3.8 (for checking formatting and building with clang), # lintian (for checking spelling errors), # test suite dependencies (for running tests) RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - clang clang-format-3.5 \ + clang clang-format-3.8 \ lintian \ libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libanyevent-i3-perl && \ rm -rf /var/lib/apt/lists/* From 86ad867277b0de63537720ec49ca6523707660cb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Tue, 8 Nov 2016 14:04:46 -0800 Subject: [PATCH 003/180] travis: switch to container-based trusty (#2550) fixes #2546 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f9744335..63f69ac8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -sudo: required +sudo: false dist: trusty services: - docker From c602ec7cc2203c03c62439bb3f35a3acfe9e2ed8 Mon Sep 17 00:00:00 2001 From: Baptiste Daroussin <bapt@FreeBSD.org> Date: Wed, 9 Nov 2016 22:34:39 +0100 Subject: [PATCH 004/180] Respect SYSCONFDIR when looking for defaut 'xdg' directory --- libi3/get_config_path.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi3/get_config_path.c b/libi3/get_config_path.c index c47e6dd8..efece5cd 100644 --- a/libi3/get_config_path.c +++ b/libi3/get_config_path.c @@ -71,7 +71,7 @@ char *get_config_path(const char *override_configpath, bool use_system_paths) { /* 4: check for $XDG_CONFIG_DIRS/i3/config */ if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL) - xdg_config_dirs = "/etc/xdg"; + xdg_config_dirs = SYSCONFDIR "/xdg"; char *buf = sstrdup(xdg_config_dirs); char *tok = strtok(buf, ":"); From b494d27848beb166fae6503813ed1593f02e6f1e Mon Sep 17 00:00:00 2001 From: Baptiste Daroussin <bapt@FreeBSD.org> Date: Wed, 9 Nov 2016 22:37:21 +0100 Subject: [PATCH 005/180] Accept calling absolute path when building outsource --- m4/ax_extend_srcdir.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m4/ax_extend_srcdir.m4 b/m4/ax_extend_srcdir.m4 index a308d67d..40f37878 100644 --- a/m4/ax_extend_srcdir.m4 +++ b/m4/ax_extend_srcdir.m4 @@ -74,7 +74,7 @@ AC_DEFUN([AX_EXTEND_SRCDIR], [dnl AS_CASE([$srcdir], - [.|.*], + [.|.*|/*], [ # pwd -P is specified in IEEE 1003.1 from 2004 as_dir=`cd "$srcdir" && pwd -P` From 6e519e18e14b5509ad4bcd28d7b41e6d3350f782 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Thu, 10 Nov 2016 00:25:30 -0800 Subject: [PATCH 006/180] release.sh: add reminder to announce on reddit (#2553) --- release.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/release.sh b/release.sh index 9101332c..33cb4f53 100755 --- a/release.sh +++ b/release.sh @@ -236,3 +236,4 @@ echo "Announce on:" echo " twitter" echo " google+" echo " #i3 topic" +echo " reddit /r/i3wm" From ad7dec31d5785ce9006bbb8498529bead652d19c Mon Sep 17 00:00:00 2001 From: mihaicmn <mihai.cmn@gmail.com> Date: Sat, 12 Nov 2016 16:34:54 +0200 Subject: [PATCH 007/180] Use the DPI setting within the i3bar (#2556) --- i3bar/src/main.c | 2 ++ include/libi3.h | 6 ++++++ libi3/dpi.c | 8 ++++++++ libi3/font.c | 14 +------------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/i3bar/src/main.c b/i3bar/src/main.c index be684fc5..910e9524 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -149,6 +149,8 @@ int main(int argc, char **argv) { socket_path = expand_path(i3_default_sock_path); } + init_dpi(); + init_outputs(); if (init_connection(socket_path)) { /* Request the bar configuration. When it arrives, we fill the config array. */ diff --git a/include/libi3.h b/include/libi3.h index 11ca3127..94e1d78b 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -473,6 +473,12 @@ char *get_exe_path(const char *argv0); */ void init_dpi(void); +/** + * This function returns the value of the DPI setting. + * + */ +long get_dpi_value(void); + /** * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI * screen) to a corresponding amount of physical pixels on a standard or retina diff --git a/libi3/dpi.c b/libi3/dpi.c index a832a689..d0d1bc68 100644 --- a/libi3/dpi.c +++ b/libi3/dpi.c @@ -64,6 +64,14 @@ init_dpi_end: } } +/* + * This function returns the value of the DPI setting. + * + */ +long get_dpi_value(void) { + return dpi; +} + /* * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI * screen) to a corresponding amount of physical pixels on a standard or retina diff --git a/libi3/font.c b/libi3/font.c index fa848481..81091ea7 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -24,24 +24,12 @@ static double pango_font_red; static double pango_font_green; static double pango_font_blue; -/* Necessary to track whether the dpi changes and trigger a LOG() message, - * which is more easily visible to users. */ -static double logged_dpi = 0.0; - static PangoLayout *create_layout_with_dpi(cairo_t *cr) { PangoLayout *layout; PangoContext *context; context = pango_cairo_create_context(cr); - const double dpi = (double)root_screen->height_in_pixels * 25.4 / - (double)root_screen->height_in_millimeters; - if (logged_dpi != dpi) { - logged_dpi = dpi; - LOG("X11 root window dictates %f DPI\n", dpi); - } else { - DLOG("X11 root window dictates %f DPI\n", dpi); - } - pango_cairo_context_set_resolution(context, dpi); + pango_cairo_context_set_resolution(context, get_dpi_value()); layout = pango_layout_new(context); g_object_unref(context); From 9108f3214ce18b82ed3d21cee49adb48354360c6 Mon Sep 17 00:00:00 2001 From: Chih-Chyuan Hwang <hwangcc@csie.nctu.edu.tw> Date: Mon, 14 Nov 2016 02:45:39 +0800 Subject: [PATCH 008/180] Fix memory leaks (#2560) Fix memory leaks when executing 'i3 --moreversion'. ================================================================= ==14852==ERROR: LeakSanitizer: detected memory leaks Direct leak of 159 byte(s) in 1 object(s) allocated from: #0 0x7fea40855602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602) #1 0x4c4c4a in smalloc ../../i3/libi3/safewrappers.c:24 #2 0x4c3aee in ipc_recv_message ../../i3/libi3/ipc_recv_message.c:61 #3 0x44dc2e in display_running_version ../../i3/src/display_version.c:94 #4 0x472947 in main ../../i3/src/main.c:269 #5 0x7fea3d0c982f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f) Direct leak of 39 byte(s) in 2 object(s) allocated from: #0 0x7fea40855602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602) #1 0x7fea3d11f7d7 in vasprintf (/lib/x86_64-linux-gnu/libc.so.6+0x767d7) SUMMARY: AddressSanitizer: 198 byte(s) leaked in 3 allocation(s). --- src/display_version.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/display_version.c b/src/display_version.c index 0e650e81..764ee753 100644 --- a/src/display_version.c +++ b/src/display_version.c @@ -182,4 +182,7 @@ void display_running_version(void) { #endif yajl_free(handle); + free(reply); + free(pid_from_atom); + free(socket_path); } From da5fe3b93463b70887c20f79cf0c6054e69cdb04 Mon Sep 17 00:00:00 2001 From: mihaicmn <mihai.cmn@gmail.com> Date: Tue, 15 Nov 2016 19:26:53 +0200 Subject: [PATCH 009/180] fix incorrect reply to ipc command (#2567) --- src/commands.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/commands.c b/src/commands.c index 70622b7d..2387ddd7 100644 --- a/src/commands.c +++ b/src/commands.c @@ -893,8 +893,10 @@ void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_a cmd_output->needs_tree_render = true; return; } - if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name)) + if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name)) { + ysuccess(true); return; + } workspace_show(workspace); cmd_output->needs_tree_render = true; @@ -940,8 +942,10 @@ void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_ } DLOG("should switch to workspace %s\n", name); - if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name)) + if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name)) { + ysuccess(true); return; + } workspace_show_by_name(name); cmd_output->needs_tree_render = true; From aa20c416c38426e790543dca2652b083a52d8a58 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Sat, 19 Nov 2016 14:45:22 -0800 Subject: [PATCH 010/180] configure.ac: verify macros in m4/ are being replaced (Thanks sur5r) (#2571) See the comment for more details, and see the motivating blog post: https://blogs.noname-ev.de/sur5r/index.php?/archives/7-Another-instance-of-AC_DEFINE-being-undefined.html --- configure.ac | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/configure.ac b/configure.ac index 1a91b3b1..b57f9efa 100644 --- a/configure.ac +++ b/configure.ac @@ -15,6 +15,12 @@ AC_CONFIG_SRCDIR([libi3/ipc_recv_message.c]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIR([m4]) +dnl Verify macros defined in m4/ such as AX_SANITIZERS are not present in the +dnl output, i.e. are replaced as expected. This line results in a better error +dnl message when using aclocal < 1.13 (which does not understand +dnl AC_CONFIG_MACRO_DIR) without passing the -I m4 parameter. +m4_pattern_forbid([AX_SANITIZERS]) + # Verify we are using GNU make because we use '%'-style pattern rules in # Makefile.am, which are a GNU make extension. Pull requests to replace # '%'-style pattern rules with a more portable alternative are welcome. From 3e7a07e48d3c8e32b39c54000e8ebe0d02d47112 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Sun, 20 Nov 2016 23:20:14 -0800 Subject: [PATCH 011/180] tests: add inject_randr15 (#2573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This tool is similar to xtrace in usage in that it intercepts traffic to the X server. The motivating feature for writing the tool is its ability to inject prepared reply messages instead of the server’s reply. In this particular case, we’ll inject a RRGetMonitors reply to test i3’s RandR 1.5 code paths. The added testcase is a noop for now, but with the code that’s lingering in the randr15 branch, i3 does actually detect monitors as per the injected reply: 2016-11-20 21:10:05 - randr.c:__randr_query_outputs:618 - RandR 1.5 available, querying monitors 2016-11-20 21:10:05 - randr.c:__randr_query_outputs:628 - 1 RandR monitors found (timestamp 0) 2016-11-20 21:10:05 - randr.c:__randr_query_outputs:646 - name DP3, x 0, y 0, width 3840 px, height 2160 px, width 520 mm, height 290 mm, primary 1, automatic 1 This is preparation work for issue #1799 --- Makefile.am | 18 +- testcases/inject_randr1.5.c | 441 ++++++++++++++++++++++++++++++ testcases/lib/SocketActivation.pm | 12 +- testcases/lib/i3test.pm.in | 1 + testcases/t/533-randr15.t | 73 +++++ 5 files changed, 543 insertions(+), 2 deletions(-) create mode 100644 testcases/inject_randr1.5.c create mode 100644 testcases/t/533-randr15.t diff --git a/Makefile.am b/Makefile.am index c90e26c7..aea4256b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -45,7 +45,10 @@ dist_xsessions_DATA = \ noinst_LIBRARIES = libi3.a -check_PROGRAMS = test.commands_parser test.config_parser +check_PROGRAMS = \ + test.commands_parser \ + test.config_parser \ + test.inject_randr15 check_SCRIPTS = \ testcases/complete-run.pl @@ -401,6 +404,19 @@ i3_config_wizard_i3_config_wizard_SOURCES = \ i3-config-wizard/main.c \ i3-config-wizard/xcb.h +test_inject_randr15_CPPFLAGS = \ + $(AM_CPPFLAGS) + +test_inject_randr15_CFLAGS = \ + $(AM_CFLAGS) \ + $(i3_CFLAGS) + +test_inject_randr15_SOURCES = \ + testcases/inject_randr1.5.c + +test_inject_randr15_LDADD = \ + $(i3_LDADD) + test_commands_parser_CPPFLAGS = \ $(AM_CPPFLAGS) \ -DTEST_PARSER diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c new file mode 100644 index 00000000..bd0df399 --- /dev/null +++ b/testcases/inject_randr1.5.c @@ -0,0 +1,441 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + * inject_randr1.5.c: An X11 proxy which interprets RandR 1.5 GetMonitors + * requests and overwrites their reply with a custom reply. + * + * This tool can be refactored as necessary in order to perform the same + * purpose for other request types. The RandR 1.5 specific portions of the code + * have been marked as such to make such a refactoring easier. + * + */ +#include "all.h" + +#include <ev.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <libgen.h> + +static void uds_connection_cb(EV_P_ ev_io *w, int revents); +static void read_client_setup_request_cb(EV_P_ ev_io *w, int revents); +static void read_server_setup_reply_cb(EV_P_ ev_io *w, int revents); +static void read_client_x11_packet_cb(EV_P_ ev_io *w, int revents); +static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents); + +static char *sun_path = NULL; + +void cleanup_socket(void) { + if (sun_path != NULL) { + unlink(sun_path); + free(sun_path); + sun_path = NULL; + } +} + +/* BEGIN RandR 1.5 specific */ +static void *injected_reply = NULL; +static off_t injected_reply_len = 0; +/* END RandR 1.5 specific */ + +#define XCB_PAD(i) (-(i)&3) + +struct connstate { + /* clientw is a libev watcher for the connection which we accept()ed. */ + ev_io *clientw; + + /* serverw is a libev watcher for the connection to X11 which we initiated + * on behalf of the client. */ + ev_io *serverw; + + /* sequence is the client-side sequence number counter. In X11’s wire + * encoding, sequence counters are not included in requests, only in + * replies. */ + int sequence; + + /* BEGIN RandR 1.5 specific */ + /* sequence number of the most recent GetExtension request for RANDR */ + int getext_randr; + /* sequence number of the most recent RRGetMonitors request */ + int getmonitors; + + int randr_major_opcode; + /* END RandR 1.5 specific */ +}; + +/* + * Returns 0 on EOF + * Returns -1 on error (with errno from read() untouched) + * + */ +static size_t readall_into(void *buffer, const size_t len, int fd) { + size_t read_bytes = 0; + while (read_bytes < len) { + ssize_t n = read(fd, buffer + read_bytes, len - read_bytes); + if (n <= 0) { + return n; + } + read_bytes += (size_t)n; + } + return read_bytes; +} + +/* + * Exits the program with an error if the read failed. + * + */ +static void must_read(int n) { + if (n == -1) { + err(EXIT_FAILURE, "read()"); + } + if (n == 0) { + errx(EXIT_FAILURE, "EOF"); + } +} + +/* + * Exits the program with an error if the write failed. + * + */ +static void must_write(int n) { + if (n == -1) { + err(EXIT_FAILURE, "write()"); + } +} + +static void uds_connection_cb(EV_P_ ev_io *w, int revents) { + struct sockaddr_un addr; + socklen_t addrlen = sizeof(addr); + const int clientfd = accept(w->fd, (struct sockaddr *)&addr, &addrlen); + if (clientfd == -1) { + if (errno == EINTR) { + return; + } + err(EXIT_FAILURE, "accept()"); + } + + struct connstate *connstate = scalloc(1, sizeof(struct connstate)); + + ev_io *clientw = scalloc(1, sizeof(ev_io)); + connstate->clientw = clientw; + clientw->data = connstate; + ev_io_init(clientw, read_client_setup_request_cb, clientfd, EV_READ); + ev_io_start(EV_A_ clientw); +} + +// https://www.x.org/releases/current/doc/xproto/x11protocol.html#Encoding::Connection_Setup +static void read_client_setup_request_cb(EV_P_ ev_io *w, int revents) { + ev_io_stop(EV_A_ w); + struct connstate *connstate = (struct connstate *)w->data; + + /* Read X11 setup request in its entirety. */ + xcb_setup_request_t setup_request; + must_read(readall_into(&setup_request, sizeof(setup_request), w->fd)); + + /* Establish a connection to X11. */ + int fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (fd == -1) { + err(EXIT_FAILURE, "socket()"); + } + + char *host; + int displayp; + if (xcb_parse_display(getenv("DISPLAY"), &host, &displayp, NULL) == 0) { + errx(EXIT_FAILURE, "Could not parse DISPLAY=%s", getenv("DISPLAY")); + } + free(host); + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_LOCAL; + snprintf(addr.sun_path, sizeof(addr.sun_path), "/tmp/.X11-unix/X%d", displayp); + if (connect(fd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) { + err(EXIT_FAILURE, "connect(%s)", addr.sun_path); + } + + /* Relay setup request. */ + must_write(writeall(fd, &setup_request, sizeof(setup_request))); + + if (setup_request.authorization_protocol_name_len > 0 || + setup_request.authorization_protocol_data_len > 0) { + const size_t authlen = setup_request.authorization_protocol_name_len + + XCB_PAD(setup_request.authorization_protocol_name_len) + + setup_request.authorization_protocol_data_len + + XCB_PAD(setup_request.authorization_protocol_data_len); + void *buf = smalloc(authlen); + must_read(readall_into(buf, authlen, w->fd)); + must_write(writeall(fd, buf, authlen)); + free(buf); + } + + /* Wait for a response from the X11 server. */ + ev_io *serverw = scalloc(1, sizeof(ev_io)); + connstate->serverw = serverw; + serverw->data = connstate; + ev_io_init(serverw, read_server_setup_reply_cb, fd, EV_READ); + ev_io_start(EV_A_ serverw); +} + +static void read_server_setup_reply_cb(EV_P_ ev_io *w, int revents) { + struct connstate *connstate = (struct connstate *)w->data; + xcb_setup_failed_t setup_failed; + must_read(readall_into(&setup_failed, sizeof(setup_failed), w->fd)); + + switch (setup_failed.status) { + case 0: + errx(EXIT_FAILURE, "error authenticating at the X11 server"); + + case 2: + errx(EXIT_FAILURE, "two-factor auth not implemented"); + + case 1: + must_write(writeall(connstate->clientw->fd, &setup_failed, sizeof(xcb_setup_failed_t))); + const size_t len = (setup_failed.length * 4); + void *buf = smalloc(len); + must_read(readall_into(buf, len, w->fd)); + must_write(writeall(connstate->clientw->fd, buf, len)); + free(buf); + + ev_set_cb(connstate->clientw, read_client_x11_packet_cb); + ev_set_cb(connstate->serverw, read_server_x11_packet_cb); + ev_io_start(EV_A_ connstate->clientw); + break; + + default: + errx(EXIT_FAILURE, "X11 protocol error: expected setup_failed.status in [0..2], got %d", setup_failed.status); + } +} + +// https://www.x.org/releases/current/doc/xproto/x11protocol.html#request_format +typedef struct { + uint8_t opcode; + uint8_t pad0; + uint16_t length; +} generic_x11_request_t; + +// https://www.x.org/releases/current/doc/xproto/x11protocol.html#reply_format +typedef struct { + uint8_t code; /* if 1, this is a reply. if 0, this is an error. else, an event */ + uint8_t pad0; + uint16_t sequence; + uint32_t length; +} generic_x11_reply_t; + +static void read_client_x11_packet_cb(EV_P_ ev_io *w, int revents) { + struct connstate *connstate = (struct connstate *)w->data; + + void *request = smalloc(sizeof(generic_x11_request_t)); + must_read(readall_into(request, sizeof(generic_x11_request_t), connstate->clientw->fd)); + const size_t len = (((generic_x11_request_t *)request)->length * 4); + if (len > sizeof(generic_x11_request_t)) { + request = srealloc(request, len); + must_read(readall_into(request + sizeof(generic_x11_request_t), + len - sizeof(generic_x11_request_t), + connstate->clientw->fd)); + } + + // XXX: sequence counter wrapping is not implemented, but should not be + // necessary given that this tool is scoped for test cases. + connstate->sequence++; + + /* BEGIN RandR 1.5 specific */ + const uint8_t opcode = ((generic_x11_request_t *)request)->opcode; + if (opcode == XCB_QUERY_EXTENSION) { + xcb_query_extension_request_t *req = request; + const char *name = request + sizeof(xcb_query_extension_request_t); + if (req->name_len == strlen("RANDR") && + strncmp(name, "RANDR", strlen("RANDR")) == 0) { + connstate->getext_randr = connstate->sequence; + } + } else if (opcode == connstate->randr_major_opcode) { + const uint8_t randr_opcode = ((generic_x11_request_t *)request)->pad0; + if (randr_opcode == XCB_RANDR_GET_MONITORS) { + connstate->getmonitors = connstate->sequence; + } + } + /* END RandR 1.5 specific */ + + must_write(writeall(connstate->serverw->fd, request, len)); + free(request); +} + +static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) { + struct connstate *connstate = (struct connstate *)w->data; + // all packets from the server are at least 32 bytes in length + size_t len = 32; + void *packet = smalloc(len); + must_read(readall_into(packet, len, connstate->serverw->fd)); + switch (((generic_x11_reply_t *)packet)->code) { + case 0: // error + break; + + case 1: // reply + len += ((generic_x11_reply_t *)packet)->length * 4; + if (len > 32) { + packet = srealloc(packet, len); + must_read(readall_into(packet + 32, len - 32, connstate->serverw->fd)); + } + + /* BEGIN RandR 1.5 specific */ + const uint16_t sequence = ((generic_x11_reply_t *)packet)->sequence; + + if (sequence == connstate->getext_randr) { + xcb_query_extension_reply_t *reply = packet; + connstate->randr_major_opcode = reply->major_opcode; + } + + if (sequence == connstate->getmonitors) { + printf("RRGetMonitors reply!\n"); + xcb_randr_get_monitors_reply_t *reply = packet; + if (injected_reply != NULL) { + printf("injecting reply\n"); + ((generic_x11_reply_t *)injected_reply)->sequence = sequence; + must_write(writeall(connstate->clientw->fd, injected_reply, injected_reply_len)); + free(packet); + return; + } + } + /* END RandR 1.5 specific */ + + break; + + default: // event + break; + } + must_write(writeall(connstate->clientw->fd, packet, len)); + free(packet); +} + +static void child_cb(EV_P_ ev_child *w, int revents) { + ev_child_stop(EV_A_ w); + if (WIFEXITED(w->rstatus)) { + exit(WEXITSTATUS(w->rstatus)); + } else { + exit(WTERMSIG(w->rstatus) + 128); + } +} + +static void must_read_reply(const char *filename) { + FILE *f; + if ((f = fopen(filename, "r")) == NULL) { + err(EXIT_FAILURE, "fopen(%s)", filename); + } + struct stat stbuf; + if (fstat(fileno(f), &stbuf) != 0) { + err(EXIT_FAILURE, "fstat(%s)", filename); + } + /* BEGIN RandR 1.5 specific */ + injected_reply_len = stbuf.st_size; + injected_reply = smalloc(stbuf.st_size); + int n = fread(injected_reply, 1, stbuf.st_size, f); + /* END RandR 1.5 specific */ + if (n != stbuf.st_size) { + err(EXIT_FAILURE, "fread(%s)", filename); + } + fclose(f); +} + +int main(int argc, char *argv[]) { + static struct option long_options[] = { + {"getmonitors_reply", required_argument, 0, 0}, + {0, 0, 0, 0}, + }; + char *options_string = ""; + int opt; + int option_index = 0; + + while ((opt = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { + switch (opt) { + case 0: + if (strcmp(long_options[option_index].name, "getmonitors_reply") == 0) { + must_read_reply(optarg); + } + break; + default: + exit(EXIT_FAILURE); + } + } + + if (optind >= argc) { + errx(EXIT_FAILURE, "syntax: %s [options] <command>\n", argv[0]); + } + + int fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (fd == -1) { + err(EXIT_FAILURE, "socket(AF_UNIX)"); + } + + if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { + warn("Could not set FD_CLOEXEC"); + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + int i; + bool bound = false; + for (i = 0; i < 100; i++) { + /* XXX: The path to X11 sockets differs on some platforms (e.g. Trusted + * Solaris, HPUX), but since libxcb doesn’t provide a function to + * generate the path, we’ll just have to hard-code it for now. */ + snprintf(addr.sun_path, sizeof(addr.sun_path), "/tmp/.X11-unix/X%d", i); + + if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) { + warn("bind(%s)", addr.sun_path); + } else { + bound = true; + /* Let the user know bind() was successful, so that they know the + * error messages can be disregarded. */ + fprintf(stderr, "Successfuly bound to %s\n", addr.sun_path); + sun_path = sstrdup(addr.sun_path); + break; + } + } + + if (!bound) { + err(EXIT_FAILURE, "bind()"); + } + + atexit(cleanup_socket); + + /* This program will be started for each testcase which requires it, so we + * expect precisely one connection. */ + if (listen(fd, 1) == -1) { + err(EXIT_FAILURE, "listen()"); + } + + pid_t child = fork(); + if (child == -1) { + err(EXIT_FAILURE, "fork()"); + } + if (child == 0) { + char *display; + sasprintf(&display, ":%d", i); + setenv("DISPLAY", display, 1); + free(display); + + char **child_args = argv + optind; + execvp(child_args[0], child_args); + err(EXIT_FAILURE, "exec()"); + } + + struct ev_loop *loop = ev_default_loop(0); + + ev_child cw; + ev_child_init(&cw, child_cb, child, 0); + ev_child_start(loop, &cw); + + ev_io watcher; + ev_io_init(&watcher, uds_connection_cb, fd, EV_READ); + ev_io_start(loop, &watcher); + + ev_run(loop, 0); +} diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index 53dbb3b6..0f307eb3 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -88,7 +88,10 @@ sub activate_i3 { # the interactive signalhandler to make it crash immediately instead. # Also disable logging to SHM since we redirect the logs anyways. # Force Xinerama because we use Xdmx for multi-monitor tests. - my $i3cmd = q|i3 --shmlog-size=0 --disable-signalhandler --force-xinerama|; + my $i3cmd = q|i3 --shmlog-size=0 --disable-signalhandler|; + if (!defined($args{inject_randr15})) { + $i3cmd .= q| --force-xinerama|; + } if (!$args{validate_config}) { # We only set logging if i3 is actually started, but not if we only # validate the config file. This is to keep logging to a minimum as @@ -139,6 +142,13 @@ sub activate_i3 { 'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"'; } + if ($args{inject_randr15}) { + # See comment in $args{strace} branch. + $cmd = 'test.inject_randr15 --getmonitors_reply="' . + $args{inject_randr15} . '" -- ' . + 'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"'; + } + # We need to use the shell due to using output redirections. exec '/bin/sh', '-c', $cmd; diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index f9f6e821..f7e1515d 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -861,6 +861,7 @@ sub launch_with_config { cv => $cv, dont_create_temp_dir => $args{dont_create_temp_dir}, validate_config => $args{validate_config}, + inject_randr15 => $args{inject_randr15}, ); # If we called i3 with -C, we wait for it to exit and then return as diff --git a/testcases/t/533-randr15.t b/testcases/t/533-randr15.t new file mode 100644 index 00000000..f520806c --- /dev/null +++ b/testcases/t/533-randr15.t @@ -0,0 +1,73 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# TODO: Description of this file. +# Ticket: #999 +# Bug still in: 4.13-12-g2ff3d9d +use File::Temp qw(tempfile); +use i3test i3_autostart => 0; + +my $config = <<EOT; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +EOT + +my ($outfh, $outname) = tempfile('i3-randr15reply-XXXXXX', UNLINK => 1); + +# Prepare a RRGetMonitors reply, see A.2.4 in +# https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt +my $reply = pack('cxSLLLLx[LLL]', + 1, # reply + 0, # sequence (will be filled in by inject_randr15) + # 56 = length($reply) + length($monitor1) + # 32 = minimum X11 reply length + (56-32) / 4, # length in words + 0, # timestamp TODO + 1, # nmonitors + 0); # noutputs + +# Manually intern _NET_CURRENT_DESKTOP as $x->atom will not create atoms if +# they are not yet interned. +my $atom_cookie = $x->intern_atom(0, length("DP3"), "DP3"); +my $DP3 = $x->intern_atom_reply($atom_cookie->{sequence})->{atom}; + +# MONITORINFO is defined in A.1.1 in +# https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt +my $monitor1 = pack('LccSssSSLL', + $DP3, # name (ATOM) + 1, # primary + 1, # automatic + 0, # ncrtcs + 0, # x + 0, # y + 3840, # width in pixels + 2160, # height in pixels + 520, # width in millimeters + 290); # height in millimeters + +print $outfh $reply; +print $outfh $monitor1; + +close($outfh); + +my $pid = launch_with_config($config, inject_randr15 => $outname); + +cmd 'nop'; + +exit_gracefully($pid); + +done_testing; From 70e7f0e39a462d55617cda735339a2861d4e69ca Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Mon, 21 Nov 2016 00:37:10 -0800 Subject: [PATCH 012/180] Remove unused src/debug.c (#2575) --- Makefile.am | 1 - docs/hacking-howto | 3 - include/debug.h | 15 --- src/debug.c | 245 --------------------------------------------- 4 files changed, 264 deletions(-) delete mode 100644 include/debug.h delete mode 100644 src/debug.c diff --git a/Makefile.am b/Makefile.am index aea4256b..f35a2f2f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -518,7 +518,6 @@ i3_SOURCES = \ src/config.c \ src/config_directives.c \ src/config_parser.c \ - src/debug.c \ src/display_version.c \ src/ewmh.c \ src/fake_outputs.c \ diff --git a/docs/hacking-howto b/docs/hacking-howto index 74a690e7..2ba74917 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -119,9 +119,6 @@ src/config.c:: Contains all functions handling the configuration file (calling the parser src/config_parser.c) with the correct path, switching key bindings mode). -src/debug.c:: -Contains debugging functions to print unhandled X events. - src/ewmh.c:: Functions to get/set certain EWMH properties easily. diff --git a/include/debug.h b/include/debug.h deleted file mode 100644 index ab5f3808..00000000 --- a/include/debug.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * vim:ts=4:sw=4:expandtab - * - * i3 - an improved dynamic tiling window manager - * © 2009 Michael Stapelberg and contributors (see also: LICENSE) - * - * debug.c: Debugging functions, especially FormatEvent, which prints unhandled - * events. This code is from xcb-util. - * - */ -#pragma once - -#include <config.h> - -int handle_event(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e); diff --git a/src/debug.c b/src/debug.c deleted file mode 100644 index ea4ca997..00000000 --- a/src/debug.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * vim:ts=4:sw=4:expandtab - * - * i3 - an improved dynamic tiling window manager - * © 2009 Michael Stapelberg and contributors (see also: LICENSE) - * - * debug.c: Debugging functions, especially FormatEvent, which prints unhandled - * events. This code is from xcb-util. - * - */ -#include <config.h> - -#include <stdio.h> -#include <xcb/xcb.h> - -#include "log.h" - -static const char *labelError[] = { - "Success", - "BadRequest", - "BadValue", - "BadWindow", - "BadPixmap", - "BadAtom", - "BadCursor", - "BadFont", - "BadMatch", - "BadDrawable", - "BadAccess", - "BadAlloc", - "BadColor", - "BadGC", - "BadIDChoice", - "BadName", - "BadLength", - "BadImplementation", -}; - -static const char *labelRequest[] = { - "no request", - "CreateWindow", - "ChangeWindowAttributes", - "GetWindowAttributes", - "DestroyWindow", - "DestroySubwindows", - "ChangeSaveSet", - "ReparentWindow", - "MapWindow", - "MapSubwindows", - "UnmapWindow", - "UnmapSubwindows", - "ConfigureWindow", - "CirculateWindow", - "GetGeometry", - "QueryTree", - "InternAtom", - "GetAtomName", - "ChangeProperty", - "DeleteProperty", - "GetProperty", - "ListProperties", - "SetSelectionOwner", - "GetSelectionOwner", - "ConvertSelection", - "SendEvent", - "GrabPointer", - "UngrabPointer", - "GrabButton", - "UngrabButton", - "ChangeActivePointerGrab", - "GrabKeyboard", - "UngrabKeyboard", - "GrabKey", - "UngrabKey", - "AllowEvents", - "GrabServer", - "UngrabServer", - "QueryPointer", - "GetMotionEvents", - "TranslateCoords", - "WarpPointer", - "SetInputFocus", - "GetInputFocus", - "QueryKeymap", - "OpenFont", - "CloseFont", - "QueryFont", - "QueryTextExtents", - "ListFonts", - "ListFontsWithInfo", - "SetFontPath", - "GetFontPath", - "CreatePixmap", - "FreePixmap", - "CreateGC", - "ChangeGC", - "CopyGC", - "SetDashes", - "SetClipRectangles", - "FreeGC", - "ClearArea", - "CopyArea", - "CopyPlane", - "PolyPoint", - "PolyLine", - "PolySegment", - "PolyRectangle", - "PolyArc", - "FillPoly", - "PolyFillRectangle", - "PolyFillArc", - "PutImage", - "GetImage", - "PolyText", - "PolyText", - "ImageText", - "ImageText", - "CreateColormap", - "FreeColormap", - "CopyColormapAndFree", - "InstallColormap", - "UninstallColormap", - "ListInstalledColormaps", - "AllocColor", - "AllocNamedColor", - "AllocColorCells", - "AllocColorPlanes", - "FreeColors", - "StoreColors", - "StoreNamedColor", - "QueryColors", - "LookupColor", - "CreateCursor", - "CreateGlyphCursor", - "FreeCursor", - "RecolorCursor", - "QueryBestSize", - "QueryExtension", - "ListExtensions", - "ChangeKeyboardMapping", - "GetKeyboardMapping", - "ChangeKeyboardControl", - "GetKeyboardControl", - "Bell", - "ChangePointerControl", - "GetPointerControl", - "SetScreenSaver", - "GetScreenSaver", - "ChangeHosts", - "ListHosts", - "SetAccessControl", - "SetCloseDownMode", - "KillClient", - "RotateProperties", - "ForceScreenSaver", - "SetPointerMapping", - "GetPointerMapping", - "SetModifierMapping", - "GetModifierMapping", - "major 120", - "major 121", - "major 122", - "major 123", - "major 124", - "major 125", - "major 126", - "NoOperation", -}; - -static const char *labelEvent[] = { - "error", - "reply", - "KeyPress", - "KeyRelease", - "ButtonPress", - "ButtonRelease", - "MotionNotify", - "EnterNotify", - "LeaveNotify", - "FocusIn", - "FocusOut", - "KeymapNotify", - "Expose", - "GraphicsExpose", - "NoExpose", - "VisibilityNotify", - "CreateNotify", - "DestroyNotify", - "UnmapNotify", - "MapNotify", - "MapRequest", - "ReparentNotify", - "ConfigureNotify", - "ConfigureRequest", - "GravityNotify", - "ResizeRequest", - "CirculateNotify", - "CirculateRequest", - "PropertyNotify", - "SelectionClear", - "SelectionRequest", - "SelectionNotify", - "ColormapNotify", - "ClientMessage", - "MappingNotify", -}; - -static const char *labelSendEvent[] = { - "", - " (from SendEvent)", -}; - -int format_event(xcb_generic_event_t *e) { - uint8_t sendEvent; - uint16_t seqnum; - - sendEvent = (e->response_type & 0x80) ? 1 : 0; - e->response_type &= ~0x80; - seqnum = *((uint16_t *)e + 1); - - switch (e->response_type) { - case 0: - DLOG("Error %s on seqnum %d (%s).\n", - labelError[*((uint8_t *)e + 1)], - seqnum, - labelRequest[*((uint8_t *)e + 10)]); - break; - default: - if (e->response_type > sizeof(labelEvent) / sizeof(char *)) - break; - DLOG("Event %s following seqnum %d%s.\n", - labelEvent[e->response_type], - seqnum, - labelSendEvent[sendEvent]); - break; - case XCB_KEYMAP_NOTIFY: - DLOG("Event %s%s.\n", - labelEvent[e->response_type], - labelSendEvent[sendEvent]); - break; - } - - fflush(stdout); - return 1; -} From 90d68d7ea0f25b579f91f26ca3c27c9536a3a44d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Mon, 21 Nov 2016 00:37:17 -0800 Subject: [PATCH 013/180] Remove some now-unused functions from xcb.[ch] (#2574) xcb_draw_line is unused since commit d7f9700ba41db61788a7b0f22350cdd9d008a907 xcb_draw_rect is unused since commit a79d33fc7fd41ab6e9b853f5356eeec64aa66ef5 xcb_raise_window is unused since commit 7208d010489ba9ebd79b20aa830ae7fb176f05dc xcb_warp_pointer is unused since commit 755c618cd41815c72d30fd0d3c4770557e952df2 --- include/xcb.h | 28 ---------------------------- src/xcb.c | 43 ------------------------------------------- 2 files changed, 71 deletions(-) diff --git a/include/xcb.h b/include/xcb.h index 94f2945d..92be7b89 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -69,22 +69,6 @@ extern unsigned int xcb_numlock_mask; xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t depth, xcb_visualid_t visual, uint16_t window_class, enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values); -/** - * Draws a line from x,y to to_x,to_y using the given color - * - */ -void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, - xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x, - uint32_t y, uint32_t to_x, uint32_t to_y); - -/** - * Draws a rectangle from x,y with width,height using the given color - * - */ -void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, - xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x, - uint32_t y, uint32_t width, uint32_t height); - /** * Generates a configure_notify_event with absolute coordinates (relative to * the X root window, not to the client’s frame) for the given client. @@ -98,12 +82,6 @@ void fake_absolute_configure_notify(Con *con); */ void send_take_focus(xcb_window_t window, xcb_timestamp_t timestamp); -/** - * Raises the given window (typically client->frame) above all other windows - * - */ -void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window); - /** * Configures the given window to have the size/position specified by given rect * @@ -122,12 +100,6 @@ xcb_atom_t xcb_get_preferred_window_type(xcb_get_property_reply_t *reply); */ bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom); -/** - * Moves the mouse pointer into the middle of rect. - * - */ -void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect); - /** * Set the cursor of the root window to the given cursor id. * This function should only be used if xcursor_supported == false. diff --git a/src/xcb.c b/src/xcb.c index 900840a2..726d1e89 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -62,28 +62,6 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, return result; } -/* - * Draws a line from x,y to to_x,to_y using the given color - * - */ -void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc, - uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y) { - xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]){colorpixel}); - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2, - (xcb_point_t[]){{x, y}, {to_x, to_y}}); -} - -/* - * Draws a rectangle from x,y with width,height using the given color - * - */ -void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc, - uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { - xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]){colorpixel}); - xcb_rectangle_t rect = {x, y, width, height}; - xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); -} - /* * Generates a configure_notify_event with absolute coordinates (relative to the X root * window, not to the client’s frame) for the given client. @@ -127,15 +105,6 @@ void send_take_focus(xcb_window_t window, xcb_timestamp_t timestamp) { free(event); } -/* - * Raises the given window (typically client->frame) above all other windows - * - */ -void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) { - uint32_t values[] = {XCB_STACK_MODE_ABOVE}; - xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values); -} - /* * Configures the given window to have the size/position specified by given rect * @@ -201,18 +170,6 @@ bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom) { return false; } -/** - * Moves the mouse pointer into the middle of rect. - * - */ -void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect) { - int mid_x = rect->x + (rect->width / 2); - int mid_y = rect->y + (rect->height / 2); - - LOG("warp pointer to: %d %d\n", mid_x, mid_y); - xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y); -} - /* * Set the cursor of the root window to the given cursor id. * This function should only be used if xcursor_supported == false. From 2f9bb7dd5a296f002219fbbdcb6169410d529814 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Mon, 21 Nov 2016 02:41:15 -0800 Subject: [PATCH 014/180] Fix memory leak: free marks when destroying containers (#2578) --- src/tree.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tree.c b/src/tree.c index 6e128363..d2fe4e07 100644 --- a/src/tree.c +++ b/src/tree.c @@ -329,6 +329,12 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par match_free(match); free(match); } + while (!TAILQ_EMPTY(&(con->marks_head))) { + mark_t *mark = TAILQ_FIRST(&(con->marks_head)); + TAILQ_REMOVE(&(con->marks_head), mark, marks); + FREE(mark->name); + FREE(mark); + } free(con); /* in the case of floating windows, we already focused another container From fd3403d96e40648c420c3734410fdb727507937a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Mon, 21 Nov 2016 03:02:43 -0800 Subject: [PATCH 015/180] remove debug.h from Makefile.am (#2581) --- Makefile.am | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index f35a2f2f..0368ecca 100644 --- a/Makefile.am +++ b/Makefile.am @@ -473,7 +473,6 @@ i3_SOURCES = \ include/config_parser.h \ include/con.h \ include/data.h \ - include/debug.h \ include/display_version.h \ include/ewmh.h \ include/fake_outputs.h \ From 0dd9d2202f1810526bf1d833a4b21f9394abf4ba Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Mon, 21 Nov 2016 03:16:06 -0800 Subject: [PATCH 016/180] release.sh: update for v4.13 release (#2582) --- release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release.sh b/release.sh index 33cb4f53..22d903c4 100755 --- a/release.sh +++ b/release.sh @@ -1,8 +1,8 @@ #!/bin/zsh # This script is used to prepare a new release of i3. -export RELEASE_VERSION="4.12" -export PREVIOUS_VERSION="4.11" +export RELEASE_VERSION="4.13" +export PREVIOUS_VERSION="4.12" export RELEASE_BRANCH="next" if [ ! -e "../i3.github.io" ] From f2ffd8d8644c6c663f9da90cff42ac31acdc462e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Mon, 21 Nov 2016 21:41:43 +0100 Subject: [PATCH 017/180] Added instructions to update Github milestones after release. (#2561) --- release.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release.sh b/release.sh index 22d903c4..8f537c0e 100755 --- a/release.sh +++ b/release.sh @@ -232,6 +232,10 @@ echo "" echo " cd ${TMPDIR}" echo " sendmail -t < email.txt" echo "" +echo "Update milestones on GitHub:" +echo " Set due date of ${RELEASE_VERSION} to $(date +'%Y-$m-%d') and close the milestone" +echo " Create milestone for the next version with unset due date" +echo "" echo "Announce on:" echo " twitter" echo " google+" From 633a9f7b1475ffd5c72489bc6bd2d224c441c9a9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Mon, 28 Nov 2016 18:20:46 +0100 Subject: [PATCH 018/180] Implement RandR 1.5 support (#2580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This comes with the intentionally undocumented --disable-randr15 command line flag and disable-randr15 configuration directive. We will add documentation before the release if and only if it turns out that users actually need to use this flag in their setups. Ideally, nobody would need to use the flag and everything would just keep working, but it’s better to be safe than sorry. fixes #1799 --- generate-command-parser.pl | 10 +- include/config_directives.h | 1 + include/configuration.h | 3 + include/randr.h | 2 +- parser-specs/config.spec | 6 + src/config_directives.c | 4 + src/handlers.c | 19 +++ src/main.c | 9 +- src/randr.c | 218 +++++++++++++++++++++++++------- testcases/inject_randr1.5.c | 1 - testcases/t/201-config-parser.t | 2 + testcases/t/533-randr15.t | 37 +++++- 12 files changed, 259 insertions(+), 53 deletions(-) diff --git a/generate-command-parser.pl b/generate-command-parser.pl index 6208945d..a7687c7b 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -65,7 +65,7 @@ for my $line (@raw_lines) { my $current_state; for my $line (@lines) { - if (my ($state) = ($line =~ /^state ([A-Z_]+):$/)) { + if (my ($state) = ($line =~ /^state ([A-Z0-9_]+):$/)) { #say "got a new state: $state"; $current_state = $state; } else { @@ -155,12 +155,20 @@ for my $state (@keys) { # to generate a format string. The format uses %d for <number>s, # literal numbers or state IDs and %s for NULL, <string>s and literal # strings. + + # remove the function name temporarily, so that the following + # replacements only apply to the arguments. + my ($funcname) = ($fmt =~ /^(.+)\(/); + $fmt =~ s/^$funcname//; + $fmt =~ s/$_/%d/g for @keys; $fmt =~ s/\$([a-z_]+)/%s/g; $fmt =~ s/\&([a-z_]+)/%ld/g; $fmt =~ s/"([a-z0-9_]+)"/%s/g; $fmt =~ s/(?:-?|\b)[0-9]+\b/%d/g; + $fmt = $funcname . $fmt; + say $callfh " case $call_id:"; say $callfh " result->next_state = $next_state;"; say $callfh '#ifndef TEST_PARSER'; diff --git a/include/config_directives.h b/include/config_directives.h index 7f1bfe4a..0bf52168 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -51,6 +51,7 @@ CFGFUN(focus_follows_mouse, const char *value); CFGFUN(mouse_warping, const char *value); CFGFUN(force_focus_wrapping, const char *value); CFGFUN(force_xinerama, const char *value); +CFGFUN(disable_randr15, const char *value); CFGFUN(fake_outputs, const char *outputs); CFGFUN(force_display_urgency_hint, const long duration_ms); CFGFUN(focus_on_window_activation, const char *mode); diff --git a/include/configuration.h b/include/configuration.h index f93afde7..66628eeb 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -156,6 +156,9 @@ struct Config { * is fetched once and never updated. */ bool force_xinerama; + /** Don’t use RandR 1.5 for querying outputs. */ + bool disable_randr15; + /** Overwrites output detection (for testing), see src/fake_outputs.c */ char *fake_outputs; diff --git a/include/randr.h b/include/randr.h index 55068316..8cbfc842 100644 --- a/include/randr.h +++ b/include/randr.h @@ -29,7 +29,7 @@ typedef enum { * XRandR information to setup workspaces for each screen. * */ -void randr_init(int *event_base); +void randr_init(int *event_base, const bool disable_randr15); /** * Initializes a CT_OUTPUT Con (searches existing ones from inplace restart diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 90296819..19e2d21a 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -37,6 +37,7 @@ state INITIAL: 'mouse_warping' -> MOUSE_WARPING 'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING 'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA + 'disable_randr15', 'disable-randr15' -> DISABLE_RANDR15 'workspace_auto_back_and_forth' -> WORKSPACE_BACK_AND_FORTH 'fake_outputs', 'fake-outputs' -> FAKE_OUTPUTS 'force_display_urgency_hint' -> FORCE_DISPLAY_URGENCY_HINT @@ -205,6 +206,11 @@ state FORCE_XINERAMA: value = word -> call cfg_force_xinerama($value) +# disable_randr15 +state DISABLE_RANDR15: + value = word + -> call cfg_disable_randr15($value) + # workspace_back_and_forth state WORKSPACE_BACK_AND_FORTH: value = word diff --git a/src/config_directives.c b/src/config_directives.c index 6b5464f1..a260518c 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -252,6 +252,10 @@ CFGFUN(force_xinerama, const char *value) { config.force_xinerama = eval_boolstr(value); } +CFGFUN(disable_randr15, const char *value) { + config.disable_randr15 = eval_boolstr(value); +} + CFGFUN(force_focus_wrapping, const char *value) { config.force_focus_wrapping = eval_boolstr(value); } diff --git a/src/handlers.c b/src/handlers.c index 7dfacef7..5e589e9c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1150,6 +1150,21 @@ static void handle_focus_in(xcb_focus_in_event_t *event) { return; } +/* + * Handles ConfigureNotify events for the root window, which are generated when + * the monitor configuration changed. + * + */ +static void handle_configure_notify(xcb_configure_notify_event_t *event) { + if (event->event != root) { + DLOG("ConfigureNotify for non-root window 0x%08x, ignoring\n", event->event); + return; + } + DLOG("ConfigureNotify for root window 0x%08x\n", event->event); + + randr_query_outputs(); +} + /* * Handles the WM_CLASS property for assignments and criteria selection. * @@ -1476,6 +1491,10 @@ void handle_event(int type, xcb_generic_event_t *event) { break; } + case XCB_CONFIGURE_NOTIFY: + handle_configure_notify((xcb_configure_notify_event_t *)event); + break; + default: //DLOG("Unhandled event of type %d\n", type); break; diff --git a/src/main.c b/src/main.c index 4737175b..43efb3c2 100644 --- a/src/main.c +++ b/src/main.c @@ -194,6 +194,7 @@ int main(int argc, char *argv[]) { char *layout_path = NULL; bool delete_layout_path = false; bool force_xinerama = false; + bool disable_randr15 = false; char *fake_outputs = NULL; bool disable_signalhandler = false; bool only_check_config = false; @@ -209,6 +210,8 @@ int main(int argc, char *argv[]) { {"restart", required_argument, 0, 0}, {"force-xinerama", no_argument, 0, 0}, {"force_xinerama", no_argument, 0, 0}, + {"disable-randr15", no_argument, 0, 0}, + {"disable_randr15", no_argument, 0, 0}, {"disable-signalhandler", no_argument, 0, 0}, {"shmlog-size", required_argument, 0, 0}, {"shmlog_size", required_argument, 0, 0}, @@ -289,6 +292,10 @@ int main(int argc, char *argv[]) { "Please check if your driver really does not support RandR " "and disable this option as soon as you can.\n"); break; + } else if (strcmp(long_options[option_index].name, "disable-randr15") == 0 || + strcmp(long_options[option_index].name, "disable_randr15") == 0) { + disable_randr15 = true; + break; } else if (strcmp(long_options[option_index].name, "disable-signalhandler") == 0) { disable_signalhandler = true; break; @@ -661,7 +668,7 @@ int main(int argc, char *argv[]) { xinerama_init(); } else { DLOG("Checking for XRandR...\n"); - randr_init(&randr_base); + randr_init(&randr_base, disable_randr15 || config.disable_randr15); } /* We need to force disabling outputs which have been loaded from the diff --git a/src/randr.c b/src/randr.c index e5dcddfc..16ef62b8 100644 --- a/src/randr.c +++ b/src/randr.c @@ -14,11 +14,6 @@ #include <time.h> #include <xcb/randr.h> -/* While a clean namespace is usually a pretty good thing, we really need - * to use shorter names than the whole xcb_randr_* default names. */ -typedef xcb_randr_get_crtc_info_reply_t crtc_info; -typedef xcb_randr_get_screen_resources_current_reply_t resources_reply; - /* Pointer to the result of the query for primary output */ xcb_randr_get_output_primary_reply_t *primary; @@ -27,6 +22,7 @@ struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs); /* This is the output covering the root window */ static Output *root_output; +static bool has_randr_1_5 = false; /* * Get a specific output by its internal X11 id. Used by randr_query_outputs @@ -534,18 +530,112 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { } /* - * Gets called by randr_query_outputs() for each output. The function adds new - * outputs to the list of outputs, checks if the mode of existing outputs has - * been changed or if an existing output has been disabled. It will then change - * either the "changed" or the "to_be_deleted" flag of the output, if + * randr_query_outputs_15 uses RandR ≥ 1.5 to update outputs. + * + */ +static bool randr_query_outputs_15(void) { +#if XCB_RANDR_MINOR_VERSION < 5 + return false; +#else + /* RandR 1.5 available at compile-time, i.e. libxcb is new enough */ + if (!has_randr_1_5) { + return false; + } + /* RandR 1.5 available at run-time (supported by the server and not + * disabled by the user) */ + DLOG("Querying outputs using RandR 1.5\n"); + xcb_generic_error_t *err; + xcb_randr_get_monitors_reply_t *monitors = + xcb_randr_get_monitors_reply( + conn, xcb_randr_get_monitors(conn, root, true), &err); + if (err != NULL) { + ELOG("Could not get RandR monitors: X11 error code %d\n", err->error_code); + free(err); + /* Fall back to RandR ≤ 1.4 */ + return false; + } + + /* Mark all outputs as to_be_disabled, since xcb_randr_get_monitors() will + * only return active outputs. */ + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) { + if (output != root_output) { + output->to_be_disabled = true; + } + } + + DLOG("%d RandR monitors found (timestamp %d)\n", + xcb_randr_get_monitors_monitors_length(monitors), + monitors->timestamp); + + xcb_randr_monitor_info_iterator_t iter; + for (iter = xcb_randr_get_monitors_monitors_iterator(monitors); + iter.rem; + xcb_randr_monitor_info_next(&iter)) { + const xcb_randr_monitor_info_t *monitor_info = iter.data; + xcb_get_atom_name_reply_t *atom_reply = + xcb_get_atom_name_reply( + conn, xcb_get_atom_name(conn, monitor_info->name), &err); + if (err != NULL) { + ELOG("Could not get RandR monitor name: X11 error code %d\n", err->error_code); + free(err); + continue; + } + char *name; + sasprintf(&name, "%.*s", + xcb_get_atom_name_name_length(atom_reply), + xcb_get_atom_name_name(atom_reply)); + free(atom_reply); + + Output *new = get_output_by_name(name); + if (new == NULL) { + new = scalloc(1, sizeof(Output)); + new->name = sstrdup(name); + if (monitor_info->primary) { + TAILQ_INSERT_HEAD(&outputs, new, outputs); + } else { + TAILQ_INSERT_TAIL(&outputs, new, outputs); + } + } + /* We specified get_active == true in xcb_randr_get_monitors(), so we + * will only receive active outputs. */ + new->active = true; + new->to_be_disabled = false; + + new->primary = monitor_info->primary; + + new->changed = + update_if_necessary(&(new->rect.x), monitor_info->x) | + update_if_necessary(&(new->rect.y), monitor_info->y) | + update_if_necessary(&(new->rect.width), monitor_info->width) | + update_if_necessary(&(new->rect.height), monitor_info->height); + + DLOG("name %s, x %d, y %d, width %d px, height %d px, width %d mm, height %d mm, primary %d, automatic %d\n", + name, + monitor_info->x, monitor_info->y, monitor_info->width, monitor_info->height, + monitor_info->width_in_millimeters, monitor_info->height_in_millimeters, + monitor_info->primary, monitor_info->automatic); + free(name); + } + free(monitors); + return true; +#endif +} + +/* + * Gets called by randr_query_outputs_14() for each output. The function adds + * new outputs to the list of outputs, checks if the mode of existing outputs + * has been changed or if an existing output has been disabled. It will then + * change either the "changed" or the "to_be_deleted" flag of the output, if * appropriate. * */ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, xcb_randr_get_output_info_reply_t *output, - xcb_timestamp_t cts, resources_reply *res) { + xcb_timestamp_t cts, + xcb_randr_get_screen_resources_current_reply_t *res) { /* each CRT controller has a position in which we are interested in */ - crtc_info *crtc; + xcb_randr_get_crtc_info_reply_t *crtc; Output *new = get_output_by_id(id); bool existing = (new != NULL); @@ -614,25 +704,16 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, } /* - * (Re-)queries the outputs via RandR and stores them in the list of outputs. - * - * If no outputs are found use the root window. + * randr_query_outputs_14 uses RandR ≤ 1.4 to update outputs. * */ -void randr_query_outputs(void) { - Output *output, *other; - xcb_randr_get_output_primary_cookie_t pcookie; - xcb_randr_get_screen_resources_current_cookie_t rcookie; - - /* timestamp of the configuration so that we get consistent replies to all - * requests (if the configuration changes between our different calls) */ - xcb_timestamp_t cts; - - /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */ - xcb_randr_output_t *randr_outputs; +static void randr_query_outputs_14(void) { + DLOG("Querying outputs using RandR ≤ 1.4\n"); /* Get screen resources (primary output, crtcs, outputs, modes) */ + xcb_randr_get_screen_resources_current_cookie_t rcookie; rcookie = xcb_randr_get_screen_resources_current(conn, root); + xcb_randr_get_output_primary_cookie_t pcookie; pcookie = xcb_randr_get_output_primary(conn, root); if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL) @@ -640,30 +721,52 @@ void randr_query_outputs(void) { else DLOG("primary output is %08x\n", primary->output); - resources_reply *res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL); + xcb_randr_get_screen_resources_current_reply_t *res = + xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL); if (res == NULL) { ELOG("Could not query screen resources.\n"); - } else { - cts = res->config_timestamp; + return; + } - int len = xcb_randr_get_screen_resources_current_outputs_length(res); - randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); + /* timestamp of the configuration so that we get consistent replies to all + * requests (if the configuration changes between our different calls) */ + const xcb_timestamp_t cts = res->config_timestamp; - /* Request information for each output */ - xcb_randr_get_output_info_cookie_t ocookie[len]; - for (int i = 0; i < len; i++) - ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); + const int len = xcb_randr_get_screen_resources_current_outputs_length(res); - /* Loop through all outputs available for this X11 screen */ - for (int i = 0; i < len; i++) { - xcb_randr_get_output_info_reply_t *output; + /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */ + xcb_randr_output_t *randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); - if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) - continue; + /* Request information for each output */ + xcb_randr_get_output_info_cookie_t ocookie[len]; + for (int i = 0; i < len; i++) + ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); - handle_output(conn, randr_outputs[i], output, cts, res); - free(output); - } + /* Loop through all outputs available for this X11 screen */ + for (int i = 0; i < len; i++) { + xcb_randr_get_output_info_reply_t *output; + + if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) + continue; + + handle_output(conn, randr_outputs[i], output, cts, res); + free(output); + } + + FREE(res); +} + +/* + * (Re-)queries the outputs via RandR and stores them in the list of outputs. + * + * If no outputs are found use the root window. + * + */ +void randr_query_outputs(void) { + Output *output, *other; + + if (!randr_query_outputs_15()) { + randr_query_outputs_14(); } /* If there's no randr output, enable the output covering the root window. */ @@ -763,7 +866,6 @@ void randr_query_outputs(void) { /* render_layout flushes */ tree_render(); - FREE(res); FREE(primary); } @@ -857,12 +959,18 @@ void randr_disable_output(Output *output) { output->changed = false; } +static void fallback_to_root_output(void) { + root_output->active = true; + output_init_con(root_output); + init_ws_for_output(root_output, output_get_content(root_output->con)); +} + /* * We have just established a connection to the X server and need the initial * XRandR information to setup workspaces for each screen. * */ -void randr_init(int *event_base) { +void randr_init(int *event_base, const bool disable_randr15) { const xcb_query_extension_reply_t *extreply; root_output = create_root_output(conn); @@ -871,13 +979,27 @@ void randr_init(int *event_base) { extreply = xcb_get_extension_data(conn, &xcb_randr_id); if (!extreply->present) { DLOG("RandR is not present, activating root output.\n"); - root_output->active = true; - output_init_con(root_output); - init_ws_for_output(root_output, output_get_content(root_output->con)); - + fallback_to_root_output(); return; } + xcb_generic_error_t *err; + xcb_randr_query_version_reply_t *randr_version = + xcb_randr_query_version_reply( + conn, xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &err); + if (err != NULL) { + free(err); + ELOG("Could not query RandR version: X11 error code %d\n", err->error_code); + fallback_to_root_output(); + return; + } + + has_randr_1_5 = (randr_version->major_version >= 1) && + (randr_version->minor_version >= 5) && + !disable_randr15; + + free(randr_version); + randr_query_outputs(); if (event_base != NULL) diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c index bd0df399..5796ef05 100644 --- a/testcases/inject_randr1.5.c +++ b/testcases/inject_randr1.5.c @@ -294,7 +294,6 @@ static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) { if (sequence == connstate->getmonitors) { printf("RRGetMonitors reply!\n"); - xcb_randr_get_monitors_reply_t *reply = packet; if (injected_reply != NULL) { printf("injecting reply\n"); ((generic_x11_reply_t *)injected_reply)->sequence = sequence; diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 6cd84b6f..1de86c65 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -467,6 +467,8 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, ' force_focus_wrapping force_xinerama force-xinerama + disable_randr15 + disable-randr15 workspace_auto_back_and_forth fake_outputs fake-outputs diff --git a/testcases/t/533-randr15.t b/testcases/t/533-randr15.t index f520806c..08fa88cc 100644 --- a/testcases/t/533-randr15.t +++ b/testcases/t/533-randr15.t @@ -66,7 +66,42 @@ close($outfh); my $pid = launch_with_config($config, inject_randr15 => $outname); -cmd 'nop'; +my $tree = i3->get_tree->recv; +my @outputs = map { $_->{name} } @{$tree->{nodes}}; +is_deeply(\@outputs, [ '__i3', 'DP3' ], 'outputs are __i3 and DP3'); + +my ($dp3) = grep { $_->{name} eq 'DP3' } @{$tree->{nodes}}; +is_deeply($dp3->{rect}, { + width => 3840, + height => 2160, + x => 0, + y => 0, + }, 'Output DP3 at 3840x2160+0+0'); + +exit_gracefully($pid); + +################################################################################ +# Verify that adding monitors with RandR 1.5 results in i3 outputs. +################################################################################ + +# When inject_randr15 is defined but false, fake-xinerama will be turned off, +# but inject_randr15 will not actually be used. +my $pid = launch_with_config($config, inject_randr15 => ''); + +$tree = i3->get_tree->recv; +@outputs = map { $_->{name} } @{$tree->{nodes}}; +is_deeply(\@outputs, [ '__i3', 'default' ], 'outputs are __i3 and default'); + +SKIP: { + skip 'xrandr --setmonitor failed (xrandr too old?)', 1 unless + system(q|xrandr --setmonitor up2414q 3840/527x2160/296+1280+0 none|) == 0; + + sync_with_i3; + + $tree = i3->get_tree->recv; + @outputs = map { $_->{name} } @{$tree->{nodes}}; + is_deeply(\@outputs, [ '__i3', 'default', 'up2414q' ], 'outputs are __i3, default and up2414q'); +} exit_gracefully($pid); From f25c3d5e77f410305efeed97da5ac9adb25491f3 Mon Sep 17 00:00:00 2001 From: mihaicmn <mihai.cmn@gmail.com> Date: Mon, 28 Nov 2016 23:07:45 +0200 Subject: [PATCH 019/180] Use the DPI setting within the i3-config-wizard and i3-nagbar (#2585) --- i3-config-wizard/main.c | 1 + i3-nagbar/main.c | 1 + 2 files changed, 2 insertions(+) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index 2bb43270..9e851c06 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -854,6 +854,7 @@ int main(int argc, char *argv[]) { xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply); + init_dpi(); font = load_font(pattern, true); bold_font = load_font(patternbold, true); diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 2e9e77d4..b9f27a87 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -468,6 +468,7 @@ int main(int argc, char *argv[]) { color_border_bottom = draw_util_hex_to_color("#ab7100"); } + init_dpi(); font = load_font(pattern, true); set_font(&font); From a3013969976c0f2572e6137f8a6b0ed306287b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Mon, 28 Nov 2016 22:09:39 +0100 Subject: [PATCH 020/180] Respect minimum size hints for floating windows. (#2508) This commit introduces proper support for the minimum size on floating windows by ensuring that it is respected during mapping, later changes as well as resizes. Furthermore, this commit fixes minor issues with how the hints are handled during calculations. fixes #2436 --- include/data.h | 4 ++ src/floating.c | 96 +++++++++++++++++---------- src/handlers.c | 70 +++++++++++++------ src/manage.c | 6 ++ testcases/t/141-resize.t | 4 +- testcases/t/221-floating-type-hints.t | 12 ++-- 6 files changed, 128 insertions(+), 64 deletions(-) diff --git a/include/data.h b/include/data.h index 410f2e0d..3d67e315 100644 --- a/include/data.h +++ b/include/data.h @@ -450,6 +450,10 @@ struct Window { int width_increment; int height_increment; + /* Minimum size specified for the window. */ + int min_width; + int min_height; + /* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */ double aspect_ratio; }; diff --git a/src/floating.c b/src/floating.c index 0a8b6957..f2994339 100644 --- a/src/floating.c +++ b/src/floating.c @@ -72,18 +72,29 @@ void floating_check_size(Con *floating_con) { Rect floating_sane_max_dimensions; Con *focused_con = con_descend_focused(floating_con); - /* obey size increments */ - if (focused_con->window != NULL && (focused_con->window->height_increment || focused_con->window->width_increment)) { - Rect border_rect = con_border_style_rect(focused_con); + Rect border_rect = con_border_style_rect(focused_con); + /* We have to do the opposite calculations that render_con() do + * to get the exact size we want. */ + border_rect.width = -border_rect.width; + border_rect.width += 2 * focused_con->border_width; + border_rect.height = -border_rect.height; + border_rect.height += 2 * focused_con->border_width; + if (con_border_style(focused_con) == BS_NORMAL) { + border_rect.height += render_deco_height(); + } - /* We have to do the opposite calculations that render_con() do - * to get the exact size we want. */ - border_rect.width = -border_rect.width; - border_rect.width += 2 * focused_con->border_width; - border_rect.height = -border_rect.height; - border_rect.height += 2 * focused_con->border_width; - if (con_border_style(focused_con) == BS_NORMAL) - border_rect.height += render_deco_height(); + if (focused_con->window != NULL) { + if (focused_con->window->min_width) { + floating_con->rect.width -= border_rect.width; + floating_con->rect.width = max(floating_con->rect.width, focused_con->window->min_width); + floating_con->rect.width += border_rect.width; + } + + if (focused_con->window->min_height) { + floating_con->rect.height -= border_rect.height; + floating_con->rect.height = max(floating_con->rect.height, focused_con->window->min_height); + floating_con->rect.height += border_rect.height; + } if (focused_con->window->height_increment && floating_con->rect.height >= focused_con->window->base_height + border_rect.height) { @@ -100,36 +111,50 @@ void floating_check_size(Con *floating_con) { } } + /* Unless user requests otherwise (-1), raise the width/height to + * reasonable minimum dimensions */ + if (config.floating_minimum_height != -1) { + floating_con->rect.height -= border_rect.height; + if (config.floating_minimum_height == 0) { + floating_con->rect.height = max(floating_con->rect.height, floating_sane_min_height); + } else { + floating_con->rect.height = max(floating_con->rect.height, config.floating_minimum_height); + } + floating_con->rect.height += border_rect.height; + } + + if (config.floating_minimum_width != -1) { + floating_con->rect.width -= border_rect.width; + if (config.floating_minimum_width == 0) { + floating_con->rect.width = max(floating_con->rect.width, floating_sane_min_width); + } else { + floating_con->rect.width = max(floating_con->rect.width, config.floating_minimum_width); + } + floating_con->rect.width += border_rect.width; + } + /* Unless user requests otherwise (-1), ensure width/height do not exceed * configured maxima or, if unconfigured, limit to combined width of all * outputs */ - if (config.floating_minimum_height != -1) { - if (config.floating_minimum_height == 0) - floating_con->rect.height = max(floating_con->rect.height, floating_sane_min_height); - else - floating_con->rect.height = max(floating_con->rect.height, config.floating_minimum_height); - } - if (config.floating_minimum_width != -1) { - if (config.floating_minimum_width == 0) - floating_con->rect.width = max(floating_con->rect.width, floating_sane_min_width); - else - floating_con->rect.width = max(floating_con->rect.width, config.floating_minimum_width); - } - - /* Unless user requests otherwise (-1), raise the width/height to - * reasonable minimum dimensions */ floating_sane_max_dimensions = total_outputs_dimensions(); if (config.floating_maximum_height != -1) { - if (config.floating_maximum_height == 0) + floating_con->rect.height -= border_rect.height; + if (config.floating_maximum_height == 0) { floating_con->rect.height = min(floating_con->rect.height, floating_sane_max_dimensions.height); - else + } else { floating_con->rect.height = min(floating_con->rect.height, config.floating_maximum_height); + } + floating_con->rect.height += border_rect.height; } + if (config.floating_maximum_width != -1) { - if (config.floating_maximum_width == 0) + floating_con->rect.width -= border_rect.width; + if (config.floating_maximum_width == 0) { floating_con->rect.width = min(floating_con->rect.width, floating_sane_max_dimensions.width); - else + } else { floating_con->rect.width = min(floating_con->rect.width, config.floating_maximum_width); + } + floating_con->rect.width += border_rect.width; } } @@ -208,7 +233,8 @@ void floating_enable(Con *con, bool automatic) { } } - floating_check_size(nc); + TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); + TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); /* 3: attach the child to the new parent container. We need to do this * because con_border_style_rect() needs to access con->parent. */ @@ -227,13 +253,16 @@ void floating_enable(Con *con, bool automatic) { nc->rect.width -= border_style_rect.width; /* Add some more pixels for the title bar */ - if (con_border_style(con) == BS_NORMAL) + if (con_border_style(con) == BS_NORMAL) { nc->rect.height += deco_height; + } /* Honor the X11 border */ nc->rect.height += con->border_width * 2; nc->rect.width += con->border_width * 2; + floating_check_size(nc); + /* Some clients (like GIMP’s color picker window) get mapped * to (0, 0), so we push them to a reasonable position * (centered over their leader) */ @@ -280,9 +309,6 @@ void floating_enable(Con *con, bool automatic) { DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height); - TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); - TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); - /* render the cons to get initial window_rect correct */ render_con(nc, false); render_con(con, false); diff --git a/src/handlers.c b/src/handlers.c index 5e589e9c..9b248058 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -929,54 +929,72 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat xcb_size_hints_t size_hints; - //CLIENT_LOG(client); - /* If the hints were already in this event, use them, if not, request them */ - if (reply != NULL) + if (reply != NULL) { xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply); - else + } else { xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL); + } + + int win_width = con->window_rect.width; + int win_height = con->window_rect.height; if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) { - // TODO: Minimum size is not yet implemented DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height); + + con->window->min_width = size_hints.min_width; + con->window->min_height = size_hints.min_height; + } + + if (con_is_floating(con)) { + win_width = MAX(win_width, con->window->min_width); + win_height = MAX(win_height, con->window->min_height); } bool changed = false; if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) { - if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) + if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) { if (con->window->width_increment != size_hints.width_inc) { con->window->width_increment = size_hints.width_inc; changed = true; } - if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) + } + + if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) { if (con->window->height_increment != size_hints.height_inc) { con->window->height_increment = size_hints.height_inc; changed = true; } + } - if (changed) + if (changed) { DLOG("resize increments changed\n"); + } } - int base_width = 0, base_height = 0; + bool has_base_size = false; + int base_width = 0; + int base_height = 0; - /* base_width/height are the desired size of the window. - We check if either the program-specified size or the program-specified - min-size is available */ + /* The base width / height is the desired size of the window. */ if (size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) { base_width = size_hints.base_width; base_height = size_hints.base_height; - } else if (size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) { - /* TODO: is this right? icccm says not */ + has_base_size = true; + } + + /* If the window didn't specify a base size, the ICCCM tells us to fall + * back to the minimum size instead, if available. */ + if (!has_base_size && size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) { base_width = size_hints.min_width; base_height = size_hints.min_height; } - if (base_width != con->window->base_width || - base_height != con->window->base_height) { + // TODO XXX Should we only do this is the base size is > 0? + if (base_width != con->window->base_width || base_height != con->window->base_height) { con->window->base_width = base_width; con->window->base_height = base_height; + DLOG("client's base_height changed to %d\n", base_height); DLOG("client's base_width changed to %d\n", base_width); changed = true; @@ -989,9 +1007,13 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat goto render_and_return; } - /* XXX: do we really use rect here, not window_rect? */ - double width = con->rect.width - base_width; - double height = con->rect.height - base_height; + /* The ICCCM says to subtract the base size from the window size for aspect + * ratio calculations. However, unlike determining the base size itself we + * must not fall back to using the minimum size in this case according to + * the ICCCM. */ + double width = win_width - base_width * has_base_size; + double height = win_height - base_height * has_base_size; + /* Convert numerator/denominator to a double */ double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den; double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den; @@ -1000,8 +1022,9 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat DLOG("width = %f, height = %f\n", width, height); /* Sanity checks, this is user-input, in a way */ - if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0) + if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0) { goto render_and_return; + } /* Check if we need to set proportional_* variables using the correct ratio */ double aspect_ratio = 0.0; @@ -1009,8 +1032,9 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat aspect_ratio = min_aspect; } else if ((width / height) > max_aspect) { aspect_ratio = max_aspect; - } else + } else { goto render_and_return; + } if (fabs(con->window->aspect_ratio - aspect_ratio) > DBL_EPSILON) { con->window->aspect_ratio = aspect_ratio; @@ -1018,8 +1042,10 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat } render_and_return: - if (changed) + if (changed) { tree_render(); + } + FREE(reply); return true; } diff --git a/src/manage.c b/src/manage.c index 81ee16fd..86a361c3 100644 --- a/src/manage.c +++ b/src/manage.c @@ -491,6 +491,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki geom->height = wm_size_hints.height; } + if (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) { + DLOG("Window specifies minimum size %d x %d\n", wm_size_hints.min_width, wm_size_hints.min_height); + nc->window->min_width = wm_size_hints.min_width; + nc->window->min_height = wm_size_hints.min_height; + } + /* Store the requested geometry. The width/height gets raised to at least * 75x50 when entering floating mode, which is the minimum size for a * window to be useful (smaller windows are usually overlays/toolbars/… diff --git a/testcases/t/141-resize.t b/testcases/t/141-resize.t index 44ad9bbf..64e33bc3 100644 --- a/testcases/t/141-resize.t +++ b/testcases/t/141-resize.t @@ -298,11 +298,11 @@ sub get_floating_rect { # focus is on the right window, so we resize the left one using criteria my $leftold = get_floating_rect($left->id); my $rightold = get_floating_rect($right->id); -cmd '[id="' . $left->id . '"] resize shrink height 10px or 10ppt'; +cmd '[id="' . $left->id . '"] resize grow height 10px or 10ppt'; my $leftnew = get_floating_rect($left->id); my $rightnew = get_floating_rect($right->id); is($rightnew->{height}, $rightold->{height}, 'height of right container unchanged'); -is($leftnew->{height}, $leftold->{height} - 10, 'height of left container changed'); +is($leftnew->{height}, $leftold->{height} + 10, 'height of left container changed'); done_testing; diff --git a/testcases/t/221-floating-type-hints.t b/testcases/t/221-floating-type-hints.t index 01c73a75..60590904 100644 --- a/testcases/t/221-floating-type-hints.t +++ b/testcases/t/221-floating-type-hints.t @@ -62,10 +62,10 @@ sub open_with_fixed_size { my $flags = $XCB_ICCCM_SIZE_HINT_P_MIN_SIZE | $XCB_ICCCM_SIZE_HINT_P_MAX_SIZE; - my $min_width = 55; - my $max_width = 55; - my $min_height = 77; - my $max_height = 77; + my $min_width = 150; + my $max_width = 150; + my $min_height = 100; + my $max_height = 100; my $pad = 0x00; @@ -82,7 +82,7 @@ sub open_with_fixed_size { $atomtype->id, 32, 12, - pack('C5N8', $flags, $pad, $pad, $pad, $pad, 0, 0, 0, $min_width, $min_height, $max_width, $max_height), + pack('C5N7', $flags, $pad, $pad, $pad, $pad, 0, 0, 0, $min_width, $min_height, $max_width, $max_height), ); }, ); @@ -114,6 +114,8 @@ $window->unmap; $window = open_with_fixed_size; is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window}, $window->id, 'Fixed size window opened floating'); +is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window_rect}->{width}, 150, 'Fixed size window opened with minimum width'); +is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window_rect}->{height}, 100, 'Fixed size window opened with minimum height'); $window->unmap; done_testing; From d7dcef61d70bea4d3f0b6de5b59435c26aea9b50 Mon Sep 17 00:00:00 2001 From: Johannes Lange <jolange@users.noreply.github.com> Date: Wed, 30 Nov 2016 08:19:36 +0100 Subject: [PATCH 021/180] `move` syntax clarification: (#2591) both x and y position need to be specified --- docs/userguide | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index acdc0a58..e1e65c24 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1921,7 +1921,8 @@ move <left|right|down|up> [<px> px] # Moves the container either to a specific location # or to the center of the screen. If 'absolute' is # used, it is moved to the center of all outputs. -move [absolute] position [[<px> px] [<px> px]|center] +move [absolute] position <pos_x> [px] <pos_y> [px] +move [absolute] position center # Moves the container to the current position of the # mouse cursor. Only affects floating containers. From 05a2270eb7aff19b060ebac7830a29c5278b7cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbyn=C4=9Bk=20Moravec?= <moraveczbynek@gmail.com> Date: Fri, 2 Dec 2016 18:53:59 +0100 Subject: [PATCH 022/180] Fix read of uninitialized memory (#2596) Previous code was reading whole array, it was slower and it read uninitialized memory --- src/bindings.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings.c b/src/bindings.c index eec821b6..c76e7779 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -981,7 +981,7 @@ int *bindings_get_buttons_to_grab(void) { } /* Avoid duplicates. */ - for (int i = 0; i < num_max; i++) { + for (int i = 0; i < num; i++) { if (buffer[i] == button) continue; } From 6b9b12c3038a766b68ea9c5e8a4b815163cd9049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Fri, 2 Dec 2016 19:05:43 +0100 Subject: [PATCH 023/180] Do not set input focus in i3-input. (#2598) This commit removes all traces of setting and reverting the input focus in i3-input. We don't need to do this because grabbing the keyboard is sufficient to have the attention we need. Changing the input focus and reverting it can cause situations where i3 executes the IPC command before processing the FocusIn events. This leads to i3's input focus change to be rejected due to the timing, leading to an inconsistent focus state. fixes #2597 --- i3-input/main.c | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/i3-input/main.c b/i3-input/main.c index 97d574a2..0f07c845 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -54,7 +54,6 @@ static int limit; xcb_window_t root; xcb_connection_t *conn; xcb_screen_t *root_screen; -static xcb_get_input_focus_cookie_t focus_cookie; /* * Having verboselog(), errorlog() and debuglog() is necessary when using libi3. @@ -79,24 +78,6 @@ void errorlog(char *fmt, ...) { void debuglog(char *fmt, ...) { } -/* - * Restores the X11 input focus to wherever it was before. - * This is necessary because i3-input’s window has override_redirect=1 - * (→ unmanaged by the window manager) and thus i3-input changes focus itself. - * This function is called on exit(). - * - */ -static void restore_input_focus(void) { - xcb_generic_error_t *error; - xcb_get_input_focus_reply_t *reply = xcb_get_input_focus_reply(conn, focus_cookie, &error); - if (error != NULL) { - fprintf(stderr, "[i3-input] ERROR: Could not restore input focus (X error %d)\n", error->error_code); - return; - } - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, reply->focus, XCB_CURRENT_TIME); - xcb_flush(conn); -} - /* * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for * rendering it (UCS-2) or sending it to i3 (UTF-8). @@ -208,10 +189,6 @@ static void finish_input() { /* prefix the command if a prefix was specified on commandline */ printf("command = %s\n", full); - restore_input_focus(); - - xcb_aux_sync(conn); - ipc_send_message(sockfd, strlen(full), 0, (uint8_t *)full); free(full); @@ -265,7 +242,6 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press return 1; } if (sym == XK_Escape) { - restore_input_focus(); exit(0); } @@ -467,9 +443,6 @@ int main(int argc, char *argv[]) { sockfd = ipc_connect(socket_path); - /* Request the current InputFocus to restore when i3-input exits. */ - focus_cookie = xcb_get_input_focus(conn); - root_screen = xcb_aux_get_screen(conn, screen); root = root_screen->root; @@ -509,10 +482,6 @@ int main(int argc, char *argv[]) { xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), font.height + logical_px(8)); xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); - /* Set input focus (we have override_redirect=1, so the wm will not do - * this for us) */ - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME); - /* Grab the keyboard to get all input */ xcb_flush(conn); @@ -531,7 +500,6 @@ int main(int argc, char *argv[]) { if (reply->status != XCB_GRAB_STATUS_SUCCESS) { fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status); - restore_input_focus(); exit(-1); } From fbf58a67bb0bc74d5fa6da14aafb9a0ac649df7c Mon Sep 17 00:00:00 2001 From: cresh <marcus@crestani.de> Date: Fri, 2 Dec 2016 19:06:39 +0100 Subject: [PATCH 024/180] Some systems need to link to libiconv explicitly, thus add a check for it. (#2586) --- configure.ac | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configure.ac b/configure.ac index b57f9efa..c0f3258d 100644 --- a/configure.ac +++ b/configure.ac @@ -85,6 +85,8 @@ AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_ru AC_SEARCH_LIBS([shm_open], [rt]) +AC_SEARCH_LIBS([iconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])]) + AX_PTHREAD dnl Each prefix corresponds to a source tarball which users might have From 8d739b7fe15fd351b5d703f922db01be46699a92 Mon Sep 17 00:00:00 2001 From: Jakub Wilk <jwilk@jwilk.net> Date: Fri, 9 Dec 2016 08:37:49 +0100 Subject: [PATCH 025/180] Fix name of mcedit (#2524) It's "mcedit", not "mc-edit". --- i3-sensible-editor | 2 +- man/i3-sensible-editor.man | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/i3-sensible-editor b/i3-sensible-editor index b93893a1..ad3f6bdd 100755 --- a/i3-sensible-editor +++ b/i3-sensible-editor @@ -9,7 +9,7 @@ # mechanism to find the preferred editor # Hopefully one of these is installed (no flamewars about preference please!): -for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mc-edit; do +for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mcedit; do if command -v "$editor" > /dev/null 2>&1; then exec "$editor" "$@" fi diff --git a/man/i3-sensible-editor.man b/man/i3-sensible-editor.man index bfc5c5c6..effae6c0 100644 --- a/man/i3-sensible-editor.man +++ b/man/i3-sensible-editor.man @@ -29,7 +29,7 @@ It tries to start one of the following (in that order): * mg * jed * gedit -* mc-edit +* mcedit Please don’t complain about the order: If the user has any preference, they will have $VISUAL or $EDITOR set. From 25d27c5b2e0050e4bcc6bcdf8c03d7ab949b77db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Mon, 2 Jan 2017 15:56:28 +0100 Subject: [PATCH 026/180] Free allocated X resource value. (#2620) This fixes a little memory leak. --- libi3/dpi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libi3/dpi.c b/libi3/dpi.c index d0d1bc68..ce85cacc 100644 --- a/libi3/dpi.c +++ b/libi3/dpi.c @@ -53,6 +53,10 @@ void init_dpi(void) { DLOG("Found Xft.dpi = %ld.\n", dpi); init_dpi_end: + if (resource != NULL) { + free(resource); + } + if (database != NULL) { xcb_xrm_database_free(database); } From 46fcb1188f15b5e9410b7ce6287886639765c04d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Tue, 3 Jan 2017 09:38:45 +0100 Subject: [PATCH 027/180] .github/CONTRIBUTING.md: explain that compositors are unsupported (#2621) --- .github/CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 474355ad..c19ac81e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -14,6 +14,9 @@ Note that bug reports and feature requests for related projects should be filed having access to the source code is too time-consuming. Additionally, experience has shown that often, the software in question is responsible for the issue. Please raise an issue with the software in question, not i3. +5. Please note that i3 does not support compositors (e.g. compton). If you + encountered the issue you are about to report while using a compositor, + please try reproducing it without a compositor. ## Pull requests From 9cb5df42d2bab6a5bbce5073efd458de147233a7 Mon Sep 17 00:00:00 2001 From: Nathan Schulte <nmschulte@gmail.com> Date: Mon, 9 Jan 2017 18:47:16 -0600 Subject: [PATCH 028/180] properly detect version when building out of tree --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index c0f3258d..9d4b2f40 100644 --- a/configure.ac +++ b/configure.ac @@ -31,8 +31,8 @@ AX_EXTEND_SRCDIR AS_IF([test -d ${srcdir}/.git], [ - VERSION="$(git describe --tags --abbrev=0)" - I3_VERSION="$(git describe --tags --always) ($(git log --pretty=format:%cd --date=short -n1), branch \\\"$(git describe --tags --always --all | sed s:heads/::)\\\")" + VERSION="$(git -C ${srcdir} describe --tags --abbrev=0)" + I3_VERSION="$(git -C ${srcdir} describe --tags --always) ($(git -C ${srcdir} log --pretty=format:%cd --date=short -n1), branch \\\"$(git -C ${srcdir} describe --tags --always --all | sed s:heads/::)\\\")" # Mirrors what libi3/is_debug_build.c does: is_release=$(test $(echo "${I3_VERSION}" | cut -d '(' -f 1 | wc -m) -lt 10 && echo yes || echo no) ], From 934b23fa52dd404d29970cd083d411f5e7b52263 Mon Sep 17 00:00:00 2001 From: Nathan Schulte <nmschulte@gmail.com> Date: Mon, 9 Jan 2017 18:43:38 -0600 Subject: [PATCH 029/180] fix auto exclude in-work-tree build dirs updates: -- configure: add build directory to gitignore #2543 -- https://github.com/i3/i3/pull/2543 --- configure.ac | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 9d4b2f40..4f67d5ea 100644 --- a/configure.ac +++ b/configure.ac @@ -154,8 +154,9 @@ else print_BUILD_MANS=no fi -git_dir=`git rev-parse --git-dir 2>/dev/null` -if test -n "$git_dir"; then +in_git_worktree=`git rev-parse --is-inside-work-tree 2>/dev/null` +if "$in_git_worktree" == "true"; then + git_dir=`git rev-parse --git-dir 2>/dev/null` srcdir=`dirname "$git_dir"` exclude_dir=`pwd | sed "s,^$srcdir,,g"` if ! grep -q "^$exclude_dir" "$git_dir/info/exclude"; then From 6da187b27f8e398366b50a04b3a09528e4453688 Mon Sep 17 00:00:00 2001 From: Johannes Lange <jolange@users.noreply.github.com> Date: Tue, 10 Jan 2017 09:22:22 +0100 Subject: [PATCH 030/180] linking vim_like_marks from show_marks documentation (first occurrence (#2626) of marks in userguide) --- docs/userguide | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/userguide b/docs/userguide index e1e65c24..6dc2241b 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1127,9 +1127,9 @@ none:: [[show_marks]] === Drawing marks on window decoration -If activated, marks on windows are drawn in their window decoration. However, -any mark starting with an underscore in its name (+_+) will not be drawn even if -this option is activated. +If activated, marks (see <<vim_like_marks>>) on windows are drawn in their window +decoration. However, any mark starting with an underscore in its name (+_+) will +not be drawn even if this option is activated. The default for this option is +yes+. From 584263b1b3393015d7c77a63e21bd5c50583e5ce Mon Sep 17 00:00:00 2001 From: Jens-Wolfhard Schicke-Uffmann <drahflow@gmx.de> Date: Tue, 10 Jan 2017 09:29:06 +0100 Subject: [PATCH 031/180] Report error during error log creation (#2625) --- src/log.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/log.c b/src/log.c index 1c33649a..916085f4 100644 --- a/src/log.c +++ b/src/log.c @@ -88,8 +88,13 @@ void init_logging(void) { fprintf(stderr, "Could not initialize errorlog\n"); else { errorfile = fopen(errorfilename, "w"); - if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) { - fprintf(stderr, "Could not set close-on-exec flag\n"); + if (!errorfile) { + fprintf(stderr, "Could not initialize errorlog on %s: %s\n", + errorfilename, strerror(errno)); + } else { + if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) { + fprintf(stderr, "Could not set close-on-exec flag\n"); + } } } } From abf830079230c3806e0fdf8b97c4c12832bec7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <ingo.buerk@tngtech.com> Date: Wed, 4 Jan 2017 10:41:47 +0100 Subject: [PATCH 032/180] Rewrite the signal handler dialogs. This commit is a rewrite of the popup dialogs used when i3 crashes. We now use our draw_util suite and both properly react to EXPOSE events and clean up the windows when the handler exits. As a side-effect, this fixes #2422 --- include/sighandler.h | 7 +- src/sighandler.c | 343 +++++++++++++++++++++++-------------------- 2 files changed, 186 insertions(+), 164 deletions(-) diff --git a/include/sighandler.h b/include/sighandler.h index 20ede4eb..2cc20cd2 100644 --- a/include/sighandler.h +++ b/include/sighandler.h @@ -3,10 +3,6 @@ * * i3 - an improved dynamic tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) - * © 2009 Jan-Erik Rediger - * - * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers - * to restart inplace). * */ #pragma once @@ -14,7 +10,8 @@ #include <config.h> /** - * Setup signal handlers to safely handle SIGSEGV and SIGFPE + * Configured a signal handler to gracefully handle crashes and allow the user + * to generate a backtrace and rescue their session. * */ void setup_signal_handler(void); diff --git a/src/sighandler.c b/src/sighandler.c index 79f90d9a..8ce0d9d7 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -3,10 +3,6 @@ * * i3 - an improved dynamic tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) - * © 2009 Jan-Erik Rediger - * - * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers - * to restart inplace). * */ #include "all.h" @@ -20,28 +16,44 @@ #include <X11/keysym.h> -static void open_popups(void); +typedef struct dialog_t { + xcb_window_t id; + xcb_colormap_t colormap; + Rect dims; + surface_t surface; -static xcb_gcontext_t pixmap_gc; -static xcb_pixmap_t pixmap; + TAILQ_ENTRY(dialog_t) + dialogs; +} dialog_t; + +static TAILQ_HEAD(dialogs_head, dialog_t) dialogs = TAILQ_HEAD_INITIALIZER(dialogs); static int raised_signal; - -static char *crash_text[] = { - "i3 just crashed.", - "To debug this problem, either attach gdb now", - "or press", - "- 'b' to save a backtrace (needs GDB),", - "- 'r' to restart i3 in-place or", - "- 'f' to forget the current layout and restart"}; -static int crash_text_longest = 5; -static int backtrace_string_index = 3; static int backtrace_done = 0; +static int sighandler_backtrace(void); +static void sighandler_setup(void); +static void sighandler_create_dialogs(void); +static void sighandler_destroy_dialogs(void); +static void sighandler_handle_expose(void); +static void sighandler_draw_dialog(dialog_t *dialog); +static void sighandler_handle_key_press(xcb_key_press_event_t *event); + +static i3String *message_intro; +static i3String *message_intro2; +static i3String *message_option_backtrace; +static i3String *message_option_restart; +static i3String *message_option_forget; +static int dialog_width; +static int dialog_height; + +static int border_width = 2; +static int margin = 4; + /* * Attach gdb to pid_parent and dump a backtrace to i3-backtrace.$pid in the * tmpdir */ -static int backtrace(void) { +static int sighandler_backtrace(void) { char *tmpdir = getenv("TMPDIR"); if (tmpdir == NULL) tmpdir = "/tmp"; @@ -125,53 +137,144 @@ static int backtrace(void) { return 1; } -/* - * Draw the window containing the info text - * - */ -static int sig_draw_window(xcb_window_t win, int width, int height, int font_height, i3String **crash_text_i3strings) { - /* re-draw the background */ - xcb_rectangle_t border = {0, 0, width, height}, - inner = {2, 2, width - 4, height - 4}; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); +static void sighandler_setup(void) { + border_width = logical_px(border_width); + margin = logical_px(margin); - /* restore font color */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); + int num_lines = 5; + message_intro = i3string_from_utf8("i3 has just crashed. Please report a bug for this."); + message_intro2 = i3string_from_utf8("To debug this problem, you can either attach gdb or choose from the following options:"); + message_option_backtrace = i3string_from_utf8("- 'b' to save a backtrace (requires gdb)"); + message_option_restart = i3string_from_utf8("- 'r' to restart i3 in-place"); + message_option_forget = i3string_from_utf8("- 'f' to forget the previous layout and restart i3"); - char *bt_colour = "#FFFFFF"; - if (backtrace_done < 0) - bt_colour = "#AA0000"; - else if (backtrace_done > 0) - bt_colour = "#00AA00"; + int width_longest_message = predict_text_width(message_intro2); - for (int i = 0; crash_text_i3strings[i] != NULL; ++i) { - /* fix the colour for the backtrace line when it finished */ - if (i == backtrace_string_index) - set_font_colors(pixmap_gc, draw_util_hex_to_color(bt_colour), draw_util_hex_to_color("#000000")); - - draw_text(crash_text_i3strings[i], pixmap, pixmap_gc, NULL, - 8, 5 + i * font_height, width - 16); - - /* and reset the colour again for other lines */ - if (i == backtrace_string_index) - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); - } - - /* Copy the contents of the pixmap to the real window */ - xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height); - xcb_flush(conn); - - return 1; + dialog_width = width_longest_message + 2 * border_width + 2 * margin; + dialog_height = num_lines * config.font.height + 2 * border_width + 2 * margin; } -/* - * Handles keypresses of 'b', 'r' and 'f' to get a backtrace or restart i3 - * - */ -static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { +static void sighandler_create_dialogs(void) { + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) { + continue; + } + + dialog_t *dialog = scalloc(1, sizeof(struct dialog_t)); + TAILQ_INSERT_TAIL(&dialogs, dialog, dialogs); + + xcb_visualid_t visual = get_visualid_by_depth(root_depth); + dialog->colormap = xcb_generate_id(conn); + xcb_create_colormap(conn, XCB_COLORMAP_ALLOC_NONE, dialog->colormap, root, visual); + + uint32_t mask = 0; + uint32_t values[4]; + int i = 0; + + /* Needs to be set in the case of a 32-bit root depth. */ + mask |= XCB_CW_BACK_PIXEL; + values[i++] = root_screen->black_pixel; + + /* Needs to be set in the case of a 32-bit root depth. */ + mask |= XCB_CW_BORDER_PIXEL; + values[i++] = root_screen->black_pixel; + + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[i++] = 1; + + /* Needs to be set in the case of a 32-bit root depth. */ + mask |= XCB_CW_COLORMAP; + values[i++] = dialog->colormap; + + dialog->dims.x = output->rect.x + (output->rect.width / 2); + dialog->dims.y = output->rect.y + (output->rect.height / 2); + dialog->dims.width = dialog_width; + dialog->dims.height = dialog_height; + + /* Make sure the dialog is centered. */ + dialog->dims.x -= dialog->dims.width / 2; + dialog->dims.y -= dialog->dims.height / 2; + + dialog->id = create_window(conn, dialog->dims, root_depth, visual, + XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, + true, mask, values); + + draw_util_surface_init(conn, &(dialog->surface), dialog->id, get_visualtype_by_id(visual), + dialog->dims.width, dialog->dims.height); + + xcb_grab_keyboard(conn, false, dialog->id, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + + /* Confine the pointer to the crash dialog. */ + xcb_grab_pointer(conn, false, dialog->id, XCB_NONE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, dialog->id, + XCB_NONE, XCB_CURRENT_TIME); + } + + sighandler_handle_expose(); + xcb_flush(conn); +} + +static void sighandler_destroy_dialogs(void) { + while (!TAILQ_EMPTY(&dialogs)) { + dialog_t *dialog = TAILQ_FIRST(&dialogs); + + xcb_free_colormap(conn, dialog->colormap); + draw_util_surface_free(conn, &(dialog->surface)); + xcb_destroy_window(conn, dialog->id); + + TAILQ_REMOVE(&dialogs, dialog, dialogs); + free(dialog); + } + + xcb_flush(conn); +} + +static void sighandler_handle_expose(void) { + dialog_t *current; + TAILQ_FOREACH(current, &dialogs, dialogs) { + sighandler_draw_dialog(current); + } + + xcb_flush(conn); +} + +static void sighandler_draw_dialog(dialog_t *dialog) { + const color_t black = draw_util_hex_to_color("#000000"); + const color_t white = draw_util_hex_to_color("#FFFFFF"); + const color_t red = draw_util_hex_to_color("#FF0000"); + + /* Start with a clean slate and draw a red border. */ + draw_util_clear_surface(conn, &(dialog->surface), red); + draw_util_rectangle(conn, &(dialog->surface), black, border_width, border_width, + dialog->dims.width - 2 * border_width, dialog->dims.height - 2 * border_width); + + int y = border_width + margin; + const int x = border_width + margin; + const int max_width = dialog->dims.width - 2 * x; + + draw_util_text(message_intro, &(dialog->surface), white, black, x, y, max_width); + y += config.font.height; + + draw_util_text(message_intro2, &(dialog->surface), white, black, x, y, max_width); + y += config.font.height; + + char *bt_color = "#FFFFFF"; + if (backtrace_done < 0) { + bt_color = "#AA0000"; + } else if (backtrace_done > 0) { + bt_color = "#00AA00"; + } + draw_util_text(message_option_backtrace, &(dialog->surface), draw_util_hex_to_color(bt_color), black, x, y, max_width); + y += config.font.height; + + draw_util_text(message_option_restart, &(dialog->surface), white, black, x, y, max_width); + y += config.font.height; + + draw_util_text(message_option_forget, &(dialog->surface), white, black, x, y, max_width); + y += config.font.height; +} + +static void sighandler_handle_key_press(xcb_key_press_event_t *event) { uint16_t state = event->state; /* Apparently, after activating numlock once, the numlock modifier @@ -186,106 +289,17 @@ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_p /* fork and exec/attach GDB to the parent to get a backtrace in the * tmpdir */ - backtrace_done = backtrace(); - - /* re-open the windows to indicate that it's finished */ - open_popups(); - } - - if (sym == 'r') + backtrace_done = sighandler_backtrace(); + sighandler_handle_expose(); + } else if (sym == 'r') { + sighandler_destroy_dialogs(); i3_restart(false); - - if (sym == 'f') + } else if (sym == 'f') { + sighandler_destroy_dialogs(); i3_restart(true); - - return 1; -} - -/* - * Opens the window we use for input/output and maps it - * - */ -static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, uint32_t width, uint32_t height) { - xcb_window_t win = xcb_generate_id(conn); - - uint32_t mask = 0; - uint32_t values[2]; - - mask |= XCB_CW_BACK_PIXEL; - values[0] = 0; - - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[1] = 1; - - /* center each popup on the specified screen */ - uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width / 2)), - y = screen_rect.y + ((screen_rect.height / 2) - (height / 2)); - - xcb_create_window(conn, - XCB_COPY_FROM_PARENT, - win, /* the window id */ - root, /* parent == root */ - x, y, width, height, /* dimensions */ - 0, /* border = 0, we draw our own */ - XCB_WINDOW_CLASS_INPUT_OUTPUT, - XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ - mask, - values); - - /* Map the window (= make it visible) */ - xcb_map_window(conn, win); - - return win; -} - -static void open_popups() { - /* width and height of the popup window, so that the text fits in */ - int crash_text_num = sizeof(crash_text) / sizeof(char *); - int height = 13 + (crash_text_num * config.font.height); - - int crash_text_length = sizeof(crash_text) / sizeof(char *); - i3String **crash_text_i3strings = smalloc(sizeof(i3String *) * (crash_text_length + 1)); - /* Pre-compute i3Strings for our text */ - for (int i = 0; i < crash_text_length; ++i) { - crash_text_i3strings[i] = i3string_from_utf8(crash_text[i]); - } - crash_text_i3strings[crash_text_length] = NULL; - /* calculate width for longest text */ - int font_width = predict_text_width(crash_text_i3strings[crash_text_longest]); - int width = font_width + 20; - - /* Open a popup window on each virtual screen */ - Output *screen; - xcb_window_t win; - TAILQ_FOREACH(screen, &outputs, outputs) { - if (!screen->active) - continue; - win = open_input_window(conn, screen->rect, width, height); - - /* Create pixmap */ - pixmap = xcb_generate_id(conn); - pixmap_gc = xcb_generate_id(conn); - xcb_create_pixmap(conn, root_depth, pixmap, win, width, height); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); - - /* Grab the keyboard to get all input */ - xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); - - /* Grab the cursor inside the popup */ - xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC, - XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME); - - sig_draw_window(win, width, height, config.font.height, crash_text_i3strings); - xcb_flush(conn); } } -/* - * Handle signals - * It creates a window asking the user to restart in-place - * or exit to generate a core dump - * - */ void handle_signal(int sig, siginfo_t *info, void *data) { DLOG("i3 crashed. SIG: %d\n", sig); @@ -294,22 +308,33 @@ void handle_signal(int sig, siginfo_t *info, void *data) { sigaction(sig, &action, NULL); raised_signal = sig; - open_popups(); + sighandler_setup(); + sighandler_create_dialogs(); xcb_generic_event_t *event; /* Yay, more own eventhandlers… */ while ((event = xcb_wait_for_event(conn))) { /* Strip off the highest bit (set if the event is generated) */ int type = (event->response_type & 0x7F); - if (type == XCB_KEY_PRESS) { - sig_handle_key_press(NULL, conn, (xcb_key_press_event_t *)event); + switch (type) { + case XCB_KEY_PRESS: + sighandler_handle_key_press((xcb_key_press_event_t *)event); + break; + case XCB_EXPOSE: + if (((xcb_expose_event_t *)event)->count == 0) { + sighandler_handle_expose(); + } + + break; } + free(event); } } /* - * Setup signal handlers to safely handle SIGSEGV and SIGFPE + * Configured a signal handler to gracefully handle crashes and allow the user + * to generate a backtrace and rescue their session. * */ void setup_signal_handler(void) { From 0a6e2865e0389e6ab346d22ba2caf4153e825907 Mon Sep 17 00:00:00 2001 From: Nathan Schulte <nmschulte@gmail.com> Date: Tue, 10 Jan 2017 20:48:59 -0600 Subject: [PATCH 033/180] fix conditional in configure script --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 4f67d5ea..87ffbbe7 100644 --- a/configure.ac +++ b/configure.ac @@ -155,7 +155,7 @@ else fi in_git_worktree=`git rev-parse --is-inside-work-tree 2>/dev/null` -if "$in_git_worktree" == "true"; then +if [[ "$in_git_worktree" = "true" ]]; then git_dir=`git rev-parse --git-dir 2>/dev/null` srcdir=`dirname "$git_dir"` exclude_dir=`pwd | sed "s,^$srcdir,,g"` From f7a0453543a43b3f5b0f603c7dfdf6cc1fd33808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Fri, 13 Jan 2017 18:28:29 +0100 Subject: [PATCH 034/180] Free trayclient when removing it. (#2632) fixes #2619 --- i3bar/src/xcb.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 2715e447..49c8de1c 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -870,11 +870,13 @@ static void handle_destroy_notify(xcb_destroy_notify_event_t *event) { DLOG("checking output %s\n", walk->name); trayclient *trayclient; TAILQ_FOREACH(trayclient, walk->trayclients, tailq) { - if (trayclient->win != event->window) + if (trayclient->win != event->window) { continue; + } DLOG("Removing tray client with window ID %08x\n", event->window); TAILQ_REMOVE(walk->trayclients, trayclient, tailq); + FREE(trayclient); /* Trigger an update, we now have more space for the statusline */ configure_trayclients(); @@ -1558,6 +1560,7 @@ void kick_tray_clients(i3_output *output) { /* We remove the trayclient right here. We might receive an UnmapNotify * event afterwards, but better safe than sorry. */ TAILQ_REMOVE(output->trayclients, trayclient, tailq); + FREE(trayclient); } /* Fake a DestroyNotify so that Qt re-adds tray icons. From 14eea7fce571ed6b1592af382aa4440b87c62b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Fri, 13 Jan 2017 18:30:50 +0100 Subject: [PATCH 035/180] Added support for _NET_MOVERESIZE_WINDOW. (#2634) fixes #2603 --- include/atoms_NET_SUPPORTED.xmacro | 1 + src/handlers.c | 34 +++++++ testcases/t/266-net-moveresize-window.t | 117 ++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 testcases/t/266-net-moveresize-window.t diff --git a/include/atoms_NET_SUPPORTED.xmacro b/include/atoms_NET_SUPPORTED.xmacro index 1358e0f1..a7b9676d 100644 --- a/include/atoms_NET_SUPPORTED.xmacro +++ b/include/atoms_NET_SUPPORTED.xmacro @@ -31,3 +31,4 @@ xmacro(_NET_DESKTOP_NAMES) xmacro(_NET_DESKTOP_VIEWPORT) xmacro(_NET_ACTIVE_WINDOW) xmacro(_NET_CLOSE_WINDOW) +xmacro(_NET_MOVERESIZE_WINDOW) diff --git a/src/handlers.c b/src/handlers.c index 9b248058..e02a1ee5 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -637,6 +637,11 @@ static void handle_expose_event(xcb_expose_event_t *event) { #define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ #define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ +#define _NET_MOVERESIZE_WINDOW_X (1 << 8) +#define _NET_MOVERESIZE_WINDOW_Y (1 << 9) +#define _NET_MOVERESIZE_WINDOW_WIDTH (1 << 10) +#define _NET_MOVERESIZE_WINDOW_HEIGHT (1 << 11) + /* * Handle client messages (EWMH) * @@ -897,6 +902,35 @@ static void handle_client_message(xcb_client_message_event_t *event) { DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction); break; } + } else if (event->type == A__NET_MOVERESIZE_WINDOW) { + DLOG("Received _NET_MOVE_RESIZE_WINDOW. Handling by faking a configure request.\n"); + + void *_generated_event = scalloc(32, 1); + xcb_configure_request_event_t *generated_event = _generated_event; + + generated_event->window = event->window; + generated_event->response_type = XCB_CONFIGURE_REQUEST; + + generated_event->value_mask = 0; + if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_X) { + generated_event->value_mask |= XCB_CONFIG_WINDOW_X; + generated_event->x = event->data.data32[1]; + } + if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_Y) { + generated_event->value_mask |= XCB_CONFIG_WINDOW_Y; + generated_event->y = event->data.data32[2]; + } + if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_WIDTH) { + generated_event->value_mask |= XCB_CONFIG_WINDOW_WIDTH; + generated_event->width = event->data.data32[3]; + } + if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_HEIGHT) { + generated_event->value_mask |= XCB_CONFIG_WINDOW_HEIGHT; + generated_event->height = event->data.data32[4]; + } + + handle_configure_request(generated_event); + FREE(generated_event); } else { DLOG("Skipping client message for unhandled type %d\n", event->type); } diff --git a/testcases/t/266-net-moveresize-window.t b/testcases/t/266-net-moveresize-window.t new file mode 100644 index 00000000..69542f9c --- /dev/null +++ b/testcases/t/266-net-moveresize-window.t @@ -0,0 +1,117 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests for _NET_MOVERESIZE_WINDOW. +# Ticket: #2603 +use i3test i3_autostart => 0; + +sub moveresize_window { + my ($win, $pos_x, $pos_y, $width, $height) = @_; + + my $flags = 0; + $flags |= (1 << 8) if $pos_x >= 0; + $flags |= (1 << 9) if $pos_y >= 0; + $flags |= (1 << 10) if $width >= 0; + $flags |= (1 << 11) if $height >= 0; + + my $msg = pack "CCSLLLLLLL", + X11::XCB::CLIENT_MESSAGE, # response_type + 32, # format + 0, # sequence + $win->id, # window + $x->atom(name => '_NET_MOVERESIZE_WINDOW')->id, # message type + $flags, # data32[0] (flags) + $pos_x, # data32[1] (x) + $pos_y, # data32[2] (y) + $width, # data32[3] (width) + $height; # data32[4] (height) + + $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); + sync_with_i3; +} + +my $config = <<EOT; +# i3 config file (v4) +font font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +new_window none +new_float none +EOT + +my ($pid, $ws, $window, $content); + +############################################################################### + +############################################################################### +# A _NET_MOVERESIZE_WINDOW client message can change the position and size +# of a floating window. +############################################################################### + +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$window = open_floating_window(rect => [50, 50, 100, 100]); +moveresize_window($window, 0, 0, 555, 666); + +$content = get_ws($ws); +is($content->{floating_nodes}->[0]->{rect}->{x}, 0, 'the x coordinate is correct'); +is($content->{floating_nodes}->[0]->{rect}->{y}, 0, 'the y coordinate is correct'); +is($content->{floating_nodes}->[0]->{rect}->{width}, 555, 'the width is correct'); +is($content->{floating_nodes}->[0]->{rect}->{height}, 666, 'the height is correct'); + +exit_gracefully($pid); + +############################################################################### +# A _NET_MOVERESIZE_WINDOW client message can change only the position of a +# window. +############################################################################### + +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$window = open_floating_window(rect => [50, 50, 100, 100]); +moveresize_window($window, 100, 100, -1, -1); + +$content = get_ws($ws); +is($content->{floating_nodes}->[0]->{rect}->{x}, 100, 'the x coordinate is correct'); +is($content->{floating_nodes}->[0]->{rect}->{y}, 100, 'the y coordinate is correct'); +is($content->{floating_nodes}->[0]->{rect}->{width}, 100, 'the width is unchanged'); +is($content->{floating_nodes}->[0]->{rect}->{height}, 100, 'the height is unchanged'); + +exit_gracefully($pid); + +############################################################################### +# A _NET_MOVERESIZE_WINDOW client message can change only the size of a +# window. +############################################################################### + +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$window = open_floating_window(rect => [50, 50, 100, 100]); +moveresize_window($window, -1, -1, 200, 200); + +$content = get_ws($ws); +is($content->{floating_nodes}->[0]->{rect}->{x}, 50, 'the x coordinate is unchanged'); +is($content->{floating_nodes}->[0]->{rect}->{y}, 50, 'the y coordinate is unchanged'); +is($content->{floating_nodes}->[0]->{rect}->{width}, 200, 'the width is correct'); +is($content->{floating_nodes}->[0]->{rect}->{height}, 200, 'the height is correct'); + +exit_gracefully($pid); + +############################################################################### + +done_testing; From 33d6a4e829cf6b67f285541a86499b89d44b8a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Fri, 13 Jan 2017 18:33:29 +0100 Subject: [PATCH 036/180] Validate that a binding mode is not defined more than once. (#2633) While defining the same mode usually wouldn't hurt and, in fact, the old behavior allows to split the definition of a binding mode into several blocks, this can lead to user errors where they accidentally define a mode twice and don't understand why the mode behaves a certain way (this has been observed in real life :-)). There's no good usecase for splitting a single binding mode into multiple blocks, thus the new behavior is better. fixes #2615 --- src/bindings.c | 3 ++- src/config_directives.c | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/bindings.c b/src/bindings.c index c76e7779..bfec27e1 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -32,8 +32,9 @@ static struct Mode *mode_from_name(const char *name, bool pango_markup) { /* Try to find the mode in the list of modes and return it */ SLIST_FOREACH(mode, &modes, modes) { - if (strcmp(mode->name, name) == 0) + if (strcmp(mode->name, name) == 0) { return mode; + } } /* If the mode was not found, create a new one */ diff --git a/src/config_directives.c b/src/config_directives.c index a260518c..82e1a346 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -126,6 +126,15 @@ CFGFUN(enter_mode, const char *pango_markup, const char *modename) { ELOG("You cannot use the name %s for your mode\n", DEFAULT_BINDING_MODE); exit(1); } + + struct Mode *mode; + SLIST_FOREACH(mode, &modes, modes) { + if (strcmp(mode->name, modename) == 0) { + ELOG("The binding mode with name \"%s\" is defined at least twice.\n", modename); + exit(1); + } + } + DLOG("\t now in mode %s\n", modename); FREE(current_mode); current_mode = sstrdup(modename); From e0582aa5ebf7f4497c55d9350f503124bc0271c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Fri, 13 Jan 2017 18:34:09 +0100 Subject: [PATCH 037/180] Remove unused function arguments. (#2635) The connection is no longer necessary since the non-cairo paths have been removed. --- i3bar/src/xcb.c | 23 +++++++++++------------ include/libi3.h | 6 +++--- libi3/draw_util.c | 14 +++++++------- src/handlers.c | 2 +- src/sighandler.c | 4 ++-- src/x.c | 42 +++++++++++++++++++++--------------------- 6 files changed, 45 insertions(+), 46 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 49c8de1c..d3bfca69 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -181,7 +181,7 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b uint32_t center_x = x - sep_offset; if (config.separator_symbol == NULL) { /* Draw a classic one pixel, vertical separator. */ - draw_util_rectangle(xcb_connection, &output->statusline_buffer, sep_fg, + draw_util_rectangle(&output->statusline_buffer, sep_fg, center_x, logical_px(sep_voff_px), logical_px(1), @@ -250,7 +250,7 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color struct status_block *block; color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg); - draw_util_clear_surface(xcb_connection, &output->statusline_buffer, bar_color); + draw_util_clear_surface(&output->statusline_buffer, bar_color); /* Use unsigned integer wraparound to clip off the left side. * For example, if clip_left is 75, then x will start at the very large @@ -301,13 +301,13 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color } /* Draw the border. */ - draw_util_rectangle(xcb_connection, &output->statusline_buffer, border_color, + draw_util_rectangle(&output->statusline_buffer, border_color, x, logical_px(1), full_render_width, bar_height - logical_px(2)); /* Draw the background. */ - draw_util_rectangle(xcb_connection, &output->statusline_buffer, bg_color, + draw_util_rectangle(&output->statusline_buffer, bg_color, x + border_width, logical_px(1) + border_width, full_render_width - 2 * border_width, @@ -1946,8 +1946,7 @@ void draw_bars(bool unhide) { bool use_focus_colors = output_has_focus(outputs_walk); /* First things first: clear the backbuffer */ - draw_util_clear_surface(xcb_connection, &(outputs_walk->buffer), - (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg)); + draw_util_clear_surface(&(outputs_walk->buffer), (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg)); if (!config.disable_ws) { i3_ws *ws_walk; @@ -1977,14 +1976,14 @@ void draw_bars(bool unhide) { } /* Draw the border of the button. */ - draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), border_color, + draw_util_rectangle(&(outputs_walk->buffer), border_color, workspace_width, logical_px(1), ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1), font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1)); /* Draw the inside of the button. */ - draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color, + draw_util_rectangle(&(outputs_walk->buffer), bg_color, workspace_width + logical_px(1), 2 * logical_px(1), ws_walk->name_width + 2 * logical_px(ws_hoff_px), @@ -2007,13 +2006,13 @@ void draw_bars(bool unhide) { color_t fg_color = colors.binding_mode_fg; color_t bg_color = colors.binding_mode_bg; - draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), colors.binding_mode_border, + draw_util_rectangle(&(outputs_walk->buffer), colors.binding_mode_border, workspace_width, logical_px(1), binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1), font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1)); - draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color, + draw_util_rectangle(&(outputs_walk->buffer), bg_color, workspace_width + logical_px(1), 2 * logical_px(1), binding.width + 2 * logical_px(ws_hoff_px), @@ -2049,7 +2048,7 @@ void draw_bars(bool unhide) { int x_dest = outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width; draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text); - draw_util_copy_surface(xcb_connection, &outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0, + draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0, x_dest, 0, visible_statusline_width, (int16_t)bar_height); outputs_walk->statusline_width = statusline_width; @@ -2080,7 +2079,7 @@ void redraw_bars(void) { continue; } - draw_util_copy_surface(xcb_connection, &(outputs_walk->buffer), &(outputs_walk->bar), 0, 0, + draw_util_copy_surface(&(outputs_walk->buffer), &(outputs_walk->bar), 0, 0, 0, 0, outputs_walk->rect.w, outputs_walk->rect.h); xcb_flush(xcb_connection); } diff --git a/include/libi3.h b/include/libi3.h index 94e1d78b..d33d6c71 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -597,17 +597,17 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_ * surface as well as restoring the cairo state. * */ -void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h); +void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h); /** * Clears a surface with the given color. * */ -void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color); +void draw_util_clear_surface(surface_t *surface, color_t color); /** * Copies a surface onto another surface. * */ -void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y, +void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, double dest_x, double dest_y, double width, double height); diff --git a/libi3/draw_util.c b/libi3/draw_util.c index e471405b..e4f0d065 100644 --- a/libi3/draw_util.c +++ b/libi3/draw_util.c @@ -19,7 +19,7 @@ xcb_visualtype_t *visual_type; /* Forward declarations */ -static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color); +static void draw_util_set_source_color(surface_t *surface, color_t color); #define RETURN_UNLESS_SURFACE_INITIALIZED(surface) \ do { \ @@ -110,7 +110,7 @@ color_t draw_util_hex_to_color(const char *color) { * Set the given color as the source color on the surface. * */ -static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color) { +static void draw_util_set_source_color(surface_t *surface, color_t color) { RETURN_UNLESS_SURFACE_INITIALIZED(surface); cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha); @@ -141,7 +141,7 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_ * surface as well as restoring the cairo state. * */ -void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h) { +void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) { RETURN_UNLESS_SURFACE_INITIALIZED(surface); cairo_save(surface->cr); @@ -150,7 +150,7 @@ void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t col * onto the surface rather than blending it. This is a bit more efficient and * allows better color control for the user when using opacity. */ cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE); - draw_util_set_source_color(conn, surface, color); + draw_util_set_source_color(surface, color); cairo_rectangle(surface->cr, x, y, w, h); cairo_fill(surface->cr); @@ -166,7 +166,7 @@ void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t col * Clears a surface with the given color. * */ -void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color) { +void draw_util_clear_surface(surface_t *surface, color_t color) { RETURN_UNLESS_SURFACE_INITIALIZED(surface); cairo_save(surface->cr); @@ -175,7 +175,7 @@ void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t * onto the surface rather than blending it. This is a bit more efficient and * allows better color control for the user when using opacity. */ cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE); - draw_util_set_source_color(conn, surface, color); + draw_util_set_source_color(surface, color); cairo_paint(surface->cr); @@ -190,7 +190,7 @@ void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t * Copies a surface onto another surface. * */ -void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y, +void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, double dest_x, double dest_y, double width, double height) { RETURN_UNLESS_SURFACE_INITIALIZED(src); RETURN_UNLESS_SURFACE_INITIALIZED(dest); diff --git a/src/handlers.c b/src/handlers.c index e02a1ee5..6d7be465 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -617,7 +617,7 @@ static void handle_expose_event(xcb_expose_event_t *event) { * only tell us that the X server lost (parts of) the window contents. We * can handle that by copying the appropriate part from our surface to the * window. */ - draw_util_copy_surface(conn, &(parent->frame_buffer), &(parent->frame), + draw_util_copy_surface(&(parent->frame_buffer), &(parent->frame), event->x, event->y, event->x, event->y, event->width, event->height); xcb_flush(conn); diff --git a/src/sighandler.c b/src/sighandler.c index 8ce0d9d7..b1e7d166 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -244,8 +244,8 @@ static void sighandler_draw_dialog(dialog_t *dialog) { const color_t red = draw_util_hex_to_color("#FF0000"); /* Start with a clean slate and draw a red border. */ - draw_util_clear_surface(conn, &(dialog->surface), red); - draw_util_rectangle(conn, &(dialog->surface), black, border_width, border_width, + draw_util_clear_surface(&(dialog->surface), red); + draw_util_rectangle(&(dialog->surface), black, border_width, border_width, dialog->dims.width - 2 * border_width, dialog->dims.height - 2 * border_width); int y = border_width + margin; diff --git a/src/x.c b/src/x.c index c2153d5c..f3dbc237 100644 --- a/src/x.c +++ b/src/x.c @@ -330,10 +330,10 @@ static void x_draw_title_border(Con *con, struct deco_render_params *p) { deco_diff_r = 0; } - draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, + draw_util_rectangle(&(con->parent->frame_buffer), p->color->border, dr->x, dr->y, dr->width, 1); - draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, + draw_util_rectangle(&(con->parent->frame_buffer), p->color->border, dr->x + deco_diff_l, dr->y + dr->height - 1, dr->width - (deco_diff_l + deco_diff_r), 1); } @@ -349,7 +349,7 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p /* We actually only redraw the far right two pixels as that is the * distance we keep from the edge (not the entire border width). * Redrawing the entire border would cause text to be cut off. */ - draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->background, + draw_util_rectangle(&(con->parent->frame_buffer), p->color->background, dr->x + dr->width - 2 * logical_px(1), dr->y, 2 * logical_px(1), @@ -360,11 +360,11 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p * be easily distinguished. */ if (con->parent->layout == L_TABBED) { /* Left side */ - draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, + draw_util_rectangle(&(con->parent->frame_buffer), p->color->border, dr->x, dr->y, 1, dr->height); /* Right side */ - draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, + draw_util_rectangle(&(con->parent->frame_buffer), p->color->border, dr->x + dr->width - 1, dr->y, 1, dr->height); } @@ -458,16 +458,16 @@ void x_draw_decoration(Con *con) { /* 2: draw the client.background, but only for the parts around the window_rect */ if (con->window != NULL) { /* top area */ - draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, + draw_util_rectangle(&(con->frame_buffer), config.client.background, 0, 0, r->width, w->y); /* bottom area */ - draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, + draw_util_rectangle(&(con->frame_buffer), config.client.background, 0, w->y + w->height, r->width, r->height - (w->y + w->height)); /* left area */ - draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, + draw_util_rectangle(&(con->frame_buffer), config.client.background, 0, 0, w->x, r->height); /* right area */ - draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, + draw_util_rectangle(&(con->frame_buffer), config.client.background, w->x + w->width, 0, r->width - (w->x + w->width), r->height); } @@ -484,21 +484,21 @@ void x_draw_decoration(Con *con) { * rectangle because some childs are not freely resizable and we want * their background color to "shine through". */ if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height); + draw_util_rectangle(&(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height); } if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), + draw_util_rectangle(&(con->frame_buffer), p->color->child_border, r->width + (br.width + br.x), 0, -(br.width + br.x), r->height); } if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), + draw_util_rectangle(&(con->frame_buffer), p->color->child_border, br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)); } /* pixel border needs an additional line at the top */ if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), + draw_util_rectangle(&(con->frame_buffer), p->color->child_border, br.x, 0, r->width + br.width, br.y); } @@ -510,10 +510,10 @@ void x_draw_decoration(Con *con) { TAILQ_PREV(con, nodes_head, nodes) == NULL && con->parent->type != CT_FLOATING_CON) { if (p->parent_layout == L_SPLITH) { - draw_util_rectangle(conn, &(con->frame_buffer), p->color->indicator, + draw_util_rectangle(&(con->frame_buffer), p->color->indicator, r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height); } else if (p->parent_layout == L_SPLITV) { - draw_util_rectangle(conn, &(con->frame_buffer), p->color->indicator, + draw_util_rectangle(&(con->frame_buffer), p->color->indicator, br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)); } } @@ -533,12 +533,12 @@ void x_draw_decoration(Con *con) { * garbage left on there. This is important to avoid tearing when using * transparency. */ if (con == TAILQ_FIRST(&(con->parent->nodes_head))) { - draw_util_clear_surface(conn, &(con->parent->frame_buffer), COLOR_TRANSPARENT); + draw_util_clear_surface(&(con->parent->frame_buffer), COLOR_TRANSPARENT); FREE(con->parent->deco_render_params); } /* 4: paint the bar */ - draw_util_rectangle(conn, &(parent->frame_buffer), p->color->background, + draw_util_rectangle(&(parent->frame_buffer), p->color->background, con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height); /* 5: draw two unconnected horizontal lines in border color */ @@ -619,7 +619,7 @@ void x_draw_decoration(Con *con) { after_title: x_draw_decoration_after_title(con, p); copy_pixmaps: - draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); } /* @@ -642,7 +642,7 @@ void x_deco_recurse(Con *con) { x_deco_recurse(current); if (state->mapped) { - draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); } } @@ -829,7 +829,7 @@ void x_push_node(Con *con) { xcb_flush(conn); xcb_set_window_rect(conn, con->frame.id, rect); if (con->frame_buffer.id != XCB_NONE) { - draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); } xcb_flush(conn); @@ -881,7 +881,7 @@ void x_push_node(Con *con) { /* copy the pixmap contents to the frame window immediately after mapping */ if (con->frame_buffer.id != XCB_NONE) { - draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); } xcb_flush(conn); From f80cbf7872bc9ced15ff0428d8c1edf79713791a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Fri, 13 Jan 2017 18:34:28 +0100 Subject: [PATCH 038/180] Fix blinking test. (#2637) Thanks to @sandsmark for figuring this out. --- testcases/t/221-floating-type-hints.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcases/t/221-floating-type-hints.t b/testcases/t/221-floating-type-hints.t index 60590904..ae0f05c3 100644 --- a/testcases/t/221-floating-type-hints.t +++ b/testcases/t/221-floating-type-hints.t @@ -81,8 +81,8 @@ sub open_with_fixed_size { $atomname->id, $atomtype->id, 32, - 12, - pack('C5N7', $flags, $pad, $pad, $pad, $pad, 0, 0, 0, $min_width, $min_height, $max_width, $max_height), + 13, + pack('C5N8', $flags, $pad, $pad, $pad, $pad, 0, 0, 0, $min_width, $min_height, $max_width, $max_height), ); }, ); From d58dbc3a77a27624e70fb4ffd149af2716502863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Fri, 13 Jan 2017 18:34:58 +0100 Subject: [PATCH 039/180] Only react on the last Expose event in a series of events. (#2636) Thanks to @psychon for pointing this out during the review of PR #2624. This commit extends this change to all other occurences of Expose events within i3. --- i3-config-wizard/main.c | 5 ++++- i3-input/main.c | 5 ++++- i3-nagbar/main.c | 5 ++++- i3bar/src/xcb.c | 7 +++++-- src/handlers.c | 5 ++++- src/restore_layout.c | 5 ++++- 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index 9e851c06..8eec941c 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -966,7 +966,10 @@ int main(int argc, char *argv[]) { break; case XCB_EXPOSE: - handle_expose(); + if (((xcb_expose_event_t *)event)->count == 0) { + handle_expose(); + } + break; } diff --git a/i3-input/main.c b/i3-input/main.c index 0f07c845..6d1e3378 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -525,7 +525,10 @@ int main(int argc, char *argv[]) { break; case XCB_EXPOSE: - handle_expose(NULL, conn, (xcb_expose_event_t *)event); + if (((xcb_expose_event_t *)event)->count == 0) { + handle_expose(NULL, conn, (xcb_expose_event_t *)event); + } + break; } diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index b9f27a87..eb25e9cb 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -596,7 +596,10 @@ int main(int argc, char *argv[]) { switch (type) { case XCB_EXPOSE: - handle_expose(conn, (xcb_expose_event_t *)event); + if (((xcb_expose_event_t *)event)->count == 0) { + handle_expose(conn, (xcb_expose_event_t *)event); + } + break; case XCB_BUTTON_PRESS: diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index d3bfca69..edef9b7e 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1149,8 +1149,11 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { handle_visibility_notify((xcb_visibility_notify_event_t *)event); break; case XCB_EXPOSE: - /* Expose-events happen, when the window needs to be redrawn */ - redraw_bars(); + if (((xcb_expose_event_t *)event)->count == 0) { + /* Expose-events happen, when the window needs to be redrawn */ + redraw_bars(); + } + break; case XCB_BUTTON_PRESS: /* Button press events are mouse buttons clicked on one of our bars */ diff --git a/src/handlers.c b/src/handlers.c index 6d7be465..315688c4 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1511,7 +1511,10 @@ void handle_event(int type, xcb_generic_event_t *event) { break; case XCB_EXPOSE: - handle_expose_event((xcb_expose_event_t *)event); + if (((xcb_expose_event_t *)event)->count == 0) { + handle_expose_event((xcb_expose_event_t *)event); + } + break; case XCB_MOTION_NOTIFY: diff --git a/src/restore_layout.c b/src/restore_layout.c index d48e5c6e..9edf4b11 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -360,7 +360,10 @@ static void configure_notify(xcb_configure_notify_event_t *event) { static void restore_handle_event(int type, xcb_generic_event_t *event) { switch (type) { case XCB_EXPOSE: - expose_event((xcb_expose_event_t *)event); + if (((xcb_expose_event_t *)event)->count == 0) { + expose_event((xcb_expose_event_t *)event); + } + break; case XCB_CONFIGURE_NOTIFY: configure_notify((xcb_configure_notify_event_t *)event); From 7f84f498463d908108de05297fa0b4740909c696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Fri, 13 Jan 2017 19:01:36 +0100 Subject: [PATCH 040/180] Don't exit() on redefined binding mode. (#2638) Doing a hard exit() is a rather harsh action for something i3 can handle perfectly fine and is only meant to be a check to make debugging easier for users in certain situations. --- src/config_directives.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/config_directives.c b/src/config_directives.c index 82e1a346..879d225e 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -124,14 +124,13 @@ CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *ke CFGFUN(enter_mode, const char *pango_markup, const char *modename) { if (strcasecmp(modename, DEFAULT_BINDING_MODE) == 0) { ELOG("You cannot use the name %s for your mode\n", DEFAULT_BINDING_MODE); - exit(1); + return; } struct Mode *mode; SLIST_FOREACH(mode, &modes, modes) { if (strcmp(mode->name, modename) == 0) { ELOG("The binding mode with name \"%s\" is defined at least twice.\n", modename); - exit(1); } } From 367811be2d285452bbeecd94bf69bae20ee8221d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Mon, 16 Jan 2017 23:00:01 +0100 Subject: [PATCH 041/180] Ensure that marks and the title are displayed even if the window title is empty. (#2639) Previously rendering marks and the title were skipped if the title is empty. With marks this is obviously wrong, with the title it is also wrong because title_format might be set. --- src/x.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/x.c b/src/x.c index f3dbc237..d9a70a92 100644 --- a/src/x.c +++ b/src/x.c @@ -572,9 +572,6 @@ void x_draw_decoration(Con *con) { goto after_title; } - if (win->name == NULL) - goto copy_pixmaps; - int mark_width = 0; if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) { char *formatted_mark = sstrdup(""); @@ -608,13 +605,19 @@ void x_draw_decoration(Con *con) { } i3String *title = con->title_format == NULL ? win->name : con_parse_title_format(con); + if (title == NULL) { + goto copy_pixmaps; + } + draw_util_text(title, &(parent->frame_buffer), p->color->text, p->color->background, con->deco_rect.x + logical_px(2), con->deco_rect.y + text_offset_y, con->deco_rect.width - mark_width - 2 * logical_px(2)); - if (con->title_format != NULL) + + if (con->title_format != NULL) { I3STRING_FREE(title); + } after_title: x_draw_decoration_after_title(con, p); From f356439a8d5bf96d1226e478d99c1561b94bd76c Mon Sep 17 00:00:00 2001 From: fred777 <a-v-a-t-a-r@gmx.net> Date: Sat, 21 Jan 2017 13:21:32 +0100 Subject: [PATCH 042/180] Update userguide (#2647) key identifier Esc is invalid, use Escape instead --- docs/userguide | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index 6dc2241b..d15aed73 100644 --- a/docs/userguide +++ b/docs/userguide @@ -479,7 +479,7 @@ mode <name> *Example*: ------------------------------------------------------------------------ -# Press $mod+o followed by either f, t, Esc or Return to launch firefox, +# Press $mod+o followed by either f, t, Escape or Return to launch firefox, # thunderbird or return to the default mode, respectively. set $mode_launcher Launch: [f]irefox [t]hunderbird bindsym $mod+o mode "$mode_launcher" @@ -488,7 +488,7 @@ mode "$mode_launcher" { bindsym f exec firefox bindsym t exec thunderbird - bindsym Esc mode "default" + bindsym Escape mode "default" bindsym Return mode "default" } ------------------------------------------------------------------------ From 8edb4a1f0cbda0127996d5ee85a98e5d1c416515 Mon Sep 17 00:00:00 2001 From: mihaicmn <mihai.cmn@gmail.com> Date: Sat, 21 Jan 2017 17:25:21 +0200 Subject: [PATCH 043/180] migrate i3-nagbar to draw_util (#2644) --- i3-nagbar/main.c | 169 +++++++++++++++++------------------------------ 1 file changed, 61 insertions(+), 108 deletions(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index eb25e9cb..7d38f731 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -38,6 +38,13 @@ * constant for that. */ #define XCB_CURSOR_LEFT_PTR 68 +#define MSG_PADDING logical_px(8) +#define BTN_PADDING logical_px(3) +#define BTN_BORDER logical_px(3) +#define BTN_GAP logical_px(20) +#define CLOSE_BTN_GAP logical_px(15) +#define BAR_BORDER logical_px(2) + static char *argv0 = NULL; typedef struct { @@ -48,11 +55,12 @@ typedef struct { } button_t; static xcb_window_t win; -static xcb_pixmap_t pixmap; -static xcb_gcontext_t pixmap_gc; -static xcb_rectangle_t rect = {0, 0, 600, 20}; +static surface_t bar; + static i3Font font; static i3String *prompt; + +static button_t btn_close; static button_t *buttons; static int buttoncnt; @@ -138,7 +146,7 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve printf("button released on x = %d, y = %d\n", event->event_x, event->event_y); /* If the user hits the close button, we exit(0) */ - if (event->event_x >= (rect.width - logical_px(32))) + if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width) exit(0); button_t *button = get_button_at(event->event_x, event->event_y); if (!button) @@ -190,108 +198,64 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve /* TODO: unset flag, re-render */ } +/* + * Draws a button and returns its width + * + */ +static int button_draw(button_t *button, int position) { + int text_width = predict_text_width(button->label); + button->width = text_width + 2 * BTN_PADDING + 2 * BTN_BORDER; + button->x = position - button->width; + + /* draw border */ + draw_util_rectangle(&bar, color_border, + position - button->width, + MSG_PADDING - BTN_PADDING - BTN_BORDER, + button->width, + font.height + 2 * BTN_PADDING + 2 * BTN_BORDER); + /* draw background */ + draw_util_rectangle(&bar, color_button_background, + position - button->width + BTN_BORDER, + MSG_PADDING - BTN_PADDING, + text_width + 2 * BTN_PADDING, + font.height + 2 * BTN_PADDING); + /* draw label */ + draw_util_text(button->label, &bar, color_text, color_button_background, + position - button->width + BTN_BORDER + BTN_PADDING, + MSG_PADDING, + 200); + return button->width; +} + /* * Handles expose events (redraws of the window) and rendering in general. Will * be called from the code with event == NULL or from X with event != NULL. * */ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { - /* re-draw the background */ - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_background.colorpixel}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect); + /* draw background */ + draw_util_clear_surface(&bar, color_background); + /* draw message */ + draw_util_text(prompt, &bar, color_text, color_background, + MSG_PADDING, MSG_PADDING, + bar.width - 2 * MSG_PADDING); - /* restore font color */ - set_font_colors(pixmap_gc, color_text, color_background); - draw_text(prompt, pixmap, pixmap_gc, NULL, - logical_px(4) + logical_px(4), - logical_px(4) + logical_px(4), - rect.width - logical_px(4) - logical_px(4)); + int position = bar.width - (MSG_PADDING - BTN_BORDER - BTN_PADDING); /* render close button */ - const char *close_button_label = "X"; - int line_width = logical_px(4); - /* set width to the width of the label */ - int w = predict_text_width(i3string_from_utf8(close_button_label)); - /* account for left/right padding, which seems to be set to 8px (total) below */ - w += logical_px(8); - int y = rect.width; - uint32_t values[3]; - values[0] = color_button_background.colorpixel; - values[1] = line_width; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); - - xcb_rectangle_t close = {y - w - (2 * line_width), 0, w + (2 * line_width), rect.height}; - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); - - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel}); - xcb_point_t points[] = { - {y - w - (2 * line_width), line_width / 2}, - {y - (line_width / 2), line_width / 2}, - {y - (line_width / 2), (rect.height - (line_width / 2)) - logical_px(2)}, - {y - w - (2 * line_width), (rect.height - (line_width / 2)) - logical_px(2)}, - {y - w - (2 * line_width), line_width / 2}}; - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points); - - values[0] = 1; - set_font_colors(pixmap_gc, color_text, color_button_background); - /* the x term here seems to set left/right padding */ - draw_text_ascii(close_button_label, pixmap, pixmap_gc, - y - w - line_width + w / 2 - logical_px(4), - logical_px(4) + logical_px(3), - rect.width - y + w + line_width - w / 2 + logical_px(4)); - y -= w; - - y -= logical_px(20); + position -= button_draw(&btn_close, position); + position -= CLOSE_BTN_GAP; /* render custom buttons */ - line_width = 1; - for (int c = 0; c < buttoncnt; c++) { - /* set w to the width of the label */ - w = predict_text_width(buttons[c].label); - /* account for left/right padding, which seems to be set to 12px (total) below */ - w += logical_px(12); - y -= logical_px(30); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_button_background.colorpixel}); - close = (xcb_rectangle_t){y - w - (2 * line_width), logical_px(2), w + (2 * line_width), rect.height - logical_px(6)}; - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); - - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel}); - buttons[c].x = y - w - (2 * line_width); - buttons[c].width = w; - xcb_point_t points2[] = { - {y - w - (2 * line_width), (line_width / 2) + logical_px(2)}, - {y - (line_width / 2), (line_width / 2) + logical_px(2)}, - {y - (line_width / 2), (rect.height - logical_px(4) - (line_width / 2))}, - {y - w - (2 * line_width), (rect.height - logical_px(4) - (line_width / 2))}, - {y - w - (2 * line_width), (line_width / 2) + logical_px(2)}}; - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2); - - values[0] = color_text.colorpixel; - values[1] = color_button_background.colorpixel; - set_font_colors(pixmap_gc, color_text, color_button_background); - /* the x term seems to set left/right padding */ - draw_text(buttons[c].label, pixmap, pixmap_gc, NULL, - y - w - line_width + logical_px(6), - logical_px(4) + logical_px(3), - rect.width - y + w + line_width - logical_px(6)); - - y -= w; + for (int i = 0; i < buttoncnt; i++) { + position -= BTN_GAP; + position -= button_draw(&buttons[i], position); } /* border line at the bottom */ - line_width = logical_px(2); - values[0] = color_border_bottom.colorpixel; - values[1] = line_width; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); - xcb_point_t bottom[] = { - {0, rect.height - 0}, - {rect.width, rect.height - 0}}; - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 2, bottom); + draw_util_rectangle(&bar, color_border_bottom, 0, bar.height - BAR_BORDER, bar.width, BAR_BORDER); - /* Copy the contents of the pixmap to the real window */ - xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, rect.width, rect.height); xcb_flush(conn); - return 1; } @@ -301,7 +265,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { */ static xcb_rectangle_t get_window_position(void) { /* Default values if we cannot determine the primary output or its CRTC info. */ - xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + logical_px(8) + logical_px(8)}; + xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER}; xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root); xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root); @@ -438,6 +402,8 @@ int main(int argc, char *argv[]) { } } + btn_close.label = i3string_from_utf8("X"); + int screens; if ((conn = xcb_connect(NULL, &screens)) == NULL || xcb_connection_has_error(conn)) @@ -575,11 +541,8 @@ int main(int argc, char *argv[]) { 12, &strut_partial); - /* Create pixmap */ - pixmap = xcb_generate_id(conn); - pixmap_gc = xcb_generate_id(conn); - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + logical_px(8)); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + /* Initialize the drawable bar */ + draw_util_surface_init(conn, &bar, win, get_visualtype(root_screen), win_pos.width, win_pos.height); /* Grab the keyboard to get all input */ xcb_flush(conn); @@ -612,18 +575,7 @@ int main(int argc, char *argv[]) { case XCB_CONFIGURE_NOTIFY: { xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event; - rect = (xcb_rectangle_t){ - configure_notify->x, - configure_notify->y, - configure_notify->width, - configure_notify->height}; - - /* Recreate the pixmap / gc */ - xcb_free_pixmap(conn, pixmap); - xcb_free_gc(conn, pixmap_gc); - - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, rect.width, rect.height); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height); break; } } @@ -632,6 +584,7 @@ int main(int argc, char *argv[]) { } FREE(pattern); + draw_util_surface_free(conn, &bar); return 0; } From c78afab5f8b4dd7045846e8dd7663ac75335eeb6 Mon Sep 17 00:00:00 2001 From: mihaicmn <mihai.cmn@gmail.com> Date: Sat, 21 Jan 2017 17:30:31 +0200 Subject: [PATCH 044/180] migrate i3-input to draw_util (#2645) --- i3-input/main.c | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/i3-input/main.c b/i3-input/main.c index 6d1e3378..785a133f 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -33,6 +33,10 @@ #include "i3-input.h" +#define MAX_WIDTH logical_px(500) +#define BORDER logical_px(2) +#define PADDING logical_px(2) + /* IPC format string. %s will be replaced with what the user entered, then * the command will be sent to i3 */ static char *format; @@ -42,8 +46,7 @@ static int sockfd; static xcb_key_symbols_t *symbols; static bool modeswitch_active = false; static xcb_window_t win; -static xcb_pixmap_t pixmap; -static xcb_gcontext_t pixmap_gc; +static surface_t surface; static xcb_char2b_t glyphs_ucs[512]; static char *glyphs_utf8[512]; static int input_position; @@ -109,30 +112,30 @@ static uint8_t *concat_strings(char **glyphs, int max) { static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) { printf("expose!\n"); - /* re-draw the background */ - xcb_rectangle_t border = {0, 0, logical_px(500), font.height + logical_px(8)}, - inner = {logical_px(2), logical_px(2), logical_px(496), font.height + logical_px(8) - logical_px(4)}; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); + color_t border_color = draw_util_hex_to_color("#FF0000"); + color_t fg_color = draw_util_hex_to_color("#FFFFFF"); + color_t bg_color = draw_util_hex_to_color("#000000"); - /* restore font color */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); + int text_offset = BORDER + PADDING; + + /* draw border */ + draw_util_rectangle(&surface, border_color, 0, 0, surface.width, surface.height); + + /* draw background */ + draw_util_rectangle(&surface, bg_color, BORDER, BORDER, surface.width - 2 * BORDER, surface.height - 2 * BORDER); /* draw the prompt … */ if (prompt != NULL) { - draw_text(prompt, pixmap, pixmap_gc, NULL, logical_px(4), logical_px(4), logical_px(492)); + draw_util_text(prompt, &surface, fg_color, bg_color, text_offset, text_offset, MAX_WIDTH - text_offset); } + /* … and the text */ if (input_position > 0) { i3String *input = i3string_from_ucs2(glyphs_ucs, input_position); - draw_text(input, pixmap, pixmap_gc, NULL, prompt_offset + logical_px(4), logical_px(4), logical_px(492)); + draw_util_text(input, &surface, fg_color, bg_color, text_offset + prompt_offset, text_offset, MAX_WIDTH - text_offset); i3string_free(input); } - /* Copy the contents of the pixmap to the real window */ - xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), font.height + logical_px(8)); xcb_flush(conn); return 1; @@ -287,7 +290,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press } static xcb_rectangle_t get_window_position(void) { - xcb_rectangle_t result = (xcb_rectangle_t){logical_px(50), logical_px(50), logical_px(500), font.height + logical_px(8)}; + xcb_rectangle_t result = (xcb_rectangle_t){logical_px(50), logical_px(50), MAX_WIDTH, font.height + 2 * BORDER + 2 * PADDING}; xcb_get_property_reply_t *supporting_wm_reply = NULL; xcb_get_input_focus_reply_t *input_focus = NULL; @@ -448,6 +451,7 @@ int main(int argc, char *argv[]) { symbols = xcb_key_symbols_alloc(conn); + init_dpi(); font = load_font(pattern, true); set_font(&font); @@ -476,11 +480,8 @@ int main(int argc, char *argv[]) { /* Map the window (make it visible) */ xcb_map_window(conn, win); - /* Create pixmap */ - pixmap = xcb_generate_id(conn); - pixmap_gc = xcb_generate_id(conn); - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), font.height + logical_px(8)); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + /* Initialize the drawable surface */ + draw_util_surface_init(conn, &surface, win, get_visualtype(root_screen), win_pos.width, win_pos.height); /* Grab the keyboard to get all input */ xcb_flush(conn); @@ -535,5 +536,6 @@ int main(int argc, char *argv[]) { free(event); } + draw_util_surface_free(conn, &surface); return 0; } From 564945bc14b060bddb6437db6cbb8553f78e2d6a Mon Sep 17 00:00:00 2001 From: mihaicmn <mihai.cmn@gmail.com> Date: Sat, 21 Jan 2017 17:53:09 +0200 Subject: [PATCH 045/180] migrate placeholder windows to draw_util (#2646) --- src/restore_layout.c | 58 +++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/src/restore_layout.c b/src/restore_layout.c index 9edf4b11..d567e971 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -15,6 +15,8 @@ #include <sanitizer/lsan_interface.h> #endif +#define TEXT_PADDING logical_px(2) + typedef struct placeholder_state { /** The X11 placeholder window. */ xcb_window_t window; @@ -24,10 +26,8 @@ typedef struct placeholder_state { /** Current size of the placeholder window (to detect size changes). */ Rect rect; - /** The pixmap to render on (back buffer). */ - xcb_pixmap_t pixmap; - /** The graphics context for “pixmap”. */ - xcb_gcontext_t gc; + /** The drawable surface */ + surface_t surface; TAILQ_ENTRY(placeholder_state) state; @@ -138,17 +138,15 @@ void restore_connect(void) { } static void update_placeholder_contents(placeholder_state *state) { - xcb_change_gc(restore_conn, state->gc, XCB_GC_FOREGROUND, - (uint32_t[]){config.client.placeholder.background.colorpixel}); - xcb_poly_fill_rectangle(restore_conn, state->pixmap, state->gc, 1, - (xcb_rectangle_t[]){{0, 0, state->rect.width, state->rect.height}}); + const color_t foreground = config.client.placeholder.text; + const color_t background = config.client.placeholder.background; + + draw_util_clear_surface(&(state->surface), background); // TODO: make i3font functions per-connection, at least these two for now…? xcb_flush(restore_conn); xcb_aux_sync(restore_conn); - set_font_colors(state->gc, config.client.placeholder.text, config.client.placeholder.background); - Match *swallows; int n = 0; TAILQ_FOREACH(swallows, &(state->con->swallow_head), matches) { @@ -175,7 +173,10 @@ static void update_placeholder_contents(placeholder_state *state) { DLOG("con %p (placeholder 0x%08x) line %d: %s\n", state->con, state->window, n, serialized); i3String *str = i3string_from_utf8(serialized); - draw_text(str, state->pixmap, state->gc, NULL, 2, (n * (config.font.height + 2)) + 2, state->rect.width - 2); + draw_util_text(str, &(state->surface), foreground, background, + TEXT_PADDING, + (n * (config.font.height + TEXT_PADDING)) + TEXT_PADDING, + state->rect.width - 2 * TEXT_PADDING); i3string_free(str); n++; free(serialized); @@ -186,7 +187,7 @@ static void update_placeholder_contents(placeholder_state *state) { int text_width = predict_text_width(line); int x = (state->rect.width / 2) - (text_width / 2); int y = (state->rect.height / 2) - (config.font.height / 2); - draw_text(line, state->pixmap, state->gc, NULL, x, y, text_width); + draw_util_text(line, &(state->surface), foreground, background, x, y, text_width); i3string_free(line); xcb_flush(conn); xcb_aux_sync(conn); @@ -228,11 +229,8 @@ static void open_placeholder_window(Con *con) { state->window = placeholder; state->con = con; state->rect = con->rect; - state->pixmap = xcb_generate_id(restore_conn); - xcb_create_pixmap(restore_conn, root_depth, state->pixmap, - state->window, state->rect.width, state->rect.height); - state->gc = xcb_generate_id(restore_conn); - xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0}); + + draw_util_surface_init(conn, &(state->surface), placeholder, get_visualtype(root_screen), state->rect.width, state->rect.height); update_placeholder_contents(state); TAILQ_INSERT_TAIL(&state_head, state, state); @@ -286,8 +284,7 @@ bool restore_kill_placeholder(xcb_window_t placeholder) { continue; xcb_destroy_window(restore_conn, state->window); - xcb_free_pixmap(restore_conn, state->pixmap); - xcb_free_gc(restore_conn, state->gc); + draw_util_surface_free(restore_conn, &(state->surface)); TAILQ_REMOVE(&state_head, state, state); free(state); DLOG("placeholder window 0x%08x destroyed.\n", placeholder); @@ -306,14 +303,8 @@ static void expose_event(xcb_expose_event_t *event) { DLOG("refreshing window 0x%08x contents (con %p)\n", state->window, state->con); - /* Since we render to our pixmap on every change anyways, expose events - * only tell us that the X server lost (parts of) the window contents. We - * can handle that by copying the appropriate part from our pixmap to the - * window. */ - xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc, - event->x, event->y, event->x, event->y, - event->width, event->height); - xcb_flush(restore_conn); + update_placeholder_contents(state); + return; } @@ -338,19 +329,10 @@ static void configure_notify(xcb_configure_notify_event_t *event) { state->rect.width = event->width; state->rect.height = event->height; - xcb_free_pixmap(restore_conn, state->pixmap); - xcb_free_gc(restore_conn, state->gc); - - state->pixmap = xcb_generate_id(restore_conn); - xcb_create_pixmap(restore_conn, root_depth, state->pixmap, - state->window, state->rect.width, state->rect.height); - state->gc = xcb_generate_id(restore_conn); - xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0}); + draw_util_surface_set_size(&(state->surface), state->rect.width, state->rect.height); update_placeholder_contents(state); - xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc, - 0, 0, 0, 0, state->rect.width, state->rect.height); - xcb_flush(restore_conn); + return; } From 04dcf42397a7cf0e1284a2c6b16fb40c15af1f3e Mon Sep 17 00:00:00 2001 From: Tony Crisci <tony@dubstepdish.com> Date: Sun, 22 Jan 2017 17:08:32 -0500 Subject: [PATCH 046/180] Add the ipc shutdown event (#2652) This event is triggered when the connection to the ipc is about to shutdown because of a user action such as with a `restart` or `exit` command. The `change` field indicates why the ipc is shutting down. It can be either "restart" or "exit". fixes #2318 --- docs/ipc | 16 +++++++ include/i3/ipc.h | 3 ++ include/ipc.h | 13 +++-- src/commands.c | 4 +- src/ipc.c | 32 ++++++++++++- src/util.c | 2 +- testcases/t/264-ipc-shutdown-event.t | 71 ++++++++++++++++++++++++++++ 7 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 testcases/t/264-ipc-shutdown-event.t diff --git a/docs/ipc b/docs/ipc index fda289a0..5d47bcbd 100644 --- a/docs/ipc +++ b/docs/ipc @@ -671,6 +671,8 @@ barconfig_update (4):: binding (5):: Sent when a configured command binding is triggered with the keyboard or mouse +shutdown (6):: + Sent when the ipc shuts down because of a restart or exit by user command *Example:* -------------------------------------------------------------------- @@ -829,6 +831,20 @@ input_type (string):: } --------------------------- +=== shutdown event + +This event is triggered when the connection to the ipc is about to shutdown +because of a user action such as a +restart+ or +exit+ command. The +change +(string)+ field indicates why the ipc is shutting down. It can be either ++"restart"+ or +"exit"+. + +*Example:* +--------------------------- +{ + "change": "restart" +} +--------------------------- + == See also (existing libraries) [[libraries]] diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 98ac35b0..249cc32e 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -91,3 +91,6 @@ typedef struct i3_ipc_header { /** The binding event will be triggered when bindings run */ #define I3_IPC_EVENT_BINDING (I3_IPC_EVENT_MASK | 5) + +/** The shutdown event will be triggered when the ipc shuts down */ +#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6) diff --git a/include/ipc.h b/include/ipc.h index 7ff4704c..7ffbf7a8 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -77,11 +77,18 @@ int ipc_create_socket(const char *filename); void ipc_send_event(const char *event, uint32_t message_type, const char *payload); /** - * Calls shutdown() on each socket and closes it. This function to be called - * when exiting or restarting only! + * Calls to ipc_shutdown() should provide a reason for the shutdown. + */ +typedef enum { + SHUTDOWN_REASON_RESTART, + SHUTDOWN_REASON_EXIT +} shutdown_reason_t; + +/** + * Calls shutdown() on each socket and closes it. * */ -void ipc_shutdown(void); +void ipc_shutdown(shutdown_reason_t reason); void dump_node(yajl_gen gen, Con *con, bool inplace_restart); diff --git a/src/commands.c b/src/commands.c index 2387ddd7..56c07b6a 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1562,7 +1562,7 @@ void cmd_exit(I3_CMD) { #ifdef I3_ASAN_ENABLED __lsan_do_leak_check(); #endif - ipc_shutdown(); + ipc_shutdown(SHUTDOWN_REASON_EXIT); unlink(config.ipc_socket_path); xcb_disconnect(conn); exit(0); @@ -1595,7 +1595,7 @@ void cmd_reload(I3_CMD) { */ void cmd_restart(I3_CMD) { LOG("restarting i3\n"); - ipc_shutdown(); + ipc_shutdown(SHUTDOWN_REASON_RESTART); unlink(config.ipc_socket_path); /* We need to call this manually since atexit handlers don’t get called * when exec()ing */ diff --git a/src/ipc.c b/src/ipc.c index db2fa362..bb20b340 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -62,11 +62,39 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa } /* - * Calls shutdown() on each socket and closes it. This function to be called + * For shutdown events, we send the reason for the shutdown. + */ +static void ipc_send_shutdown_event(shutdown_reason_t reason) { + yajl_gen gen = ygenalloc(); + y(map_open); + + ystr("change"); + + if (reason == SHUTDOWN_REASON_RESTART) { + ystr("restart"); + } else if (reason == SHUTDOWN_REASON_EXIT) { + ystr("exit"); + } + + y(map_close); + + const unsigned char *payload; + ylength length; + + y(get_buf, &payload, &length); + ipc_send_event("shutdown", I3_IPC_EVENT_SHUTDOWN, (const char *)payload); + + y(free); +} + +/* + * Calls shutdown() on each socket and closes it. This function is to be called * when exiting or restarting only! * */ -void ipc_shutdown(void) { +void ipc_shutdown(shutdown_reason_t reason) { + ipc_send_shutdown_event(reason); + ipc_client *current; while (!TAILQ_EMPTY(&all_clients)) { current = TAILQ_FIRST(&all_clients); diff --git a/src/util.c b/src/util.c index cfe4c953..5c8fc774 100644 --- a/src/util.c +++ b/src/util.c @@ -259,7 +259,7 @@ void i3_restart(bool forget_layout) { restore_geometry(); - ipc_shutdown(); + ipc_shutdown(SHUTDOWN_REASON_RESTART); LOG("restarting \"%s\"...\n", start_argv[0]); /* make sure -a is in the argument list or add it */ diff --git a/testcases/t/264-ipc-shutdown-event.t b/testcases/t/264-ipc-shutdown-event.t new file mode 100644 index 00000000..379b9bf2 --- /dev/null +++ b/testcases/t/264-ipc-shutdown-event.t @@ -0,0 +1,71 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test the ipc shutdown event. This event is triggered when the connection to +# the ipc is about to shutdown because of a user action such as with a +# `restart` or `exit` command. The `change` field indicates why the ipc is +# shutting down. It can be either "restart" or "exit". +# +# Ticket: #2318 +# Bug still in: 4.12-46-g2123888 +use i3test; + +SKIP: { + skip "AnyEvent::I3 too old (need >= 0.17)", 1 if $AnyEvent::I3::VERSION < 0.17; + +my $i3 = i3(get_socket_path()); +$i3->connect->recv; + +my $cv = AE::cv; +my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; + +$i3->subscribe({ + shutdown => sub { + $cv->send(shift); + } + })->recv; + +cmd 'restart'; + +my $e = $cv->recv; + +diag "Event:\n", Dumper($e); +ok($e, 'the shutdown event should emit when the ipc is restarted by command'); +is($e->{change}, 'restart', 'the `change` field should tell the reason for the shutdown'); + +# restarting kills the ipc client so we have to make a new one +$i3 = i3(get_socket_path()); +$i3->connect->recv; + +$cv = AE::cv; +$timer = AE::timer 0.5, 0, sub { $cv->send(0); }; + +$i3->subscribe({ + shutdown => sub { + $cv->send(shift); + } + })->recv; + +cmd 'exit'; + +$e = $cv->recv; + +diag "Event:\n", Dumper($e); +ok($e, 'the shutdown event should emit when the ipc is exited by command'); +is($e->{change}, 'exit', 'the `change` field should tell the reason for the shutdown'); +} + +done_testing; From a55733f719c146438d9665445bfcadec9f69c1e2 Mon Sep 17 00:00:00 2001 From: Tony Crisci <tony@dubstepdish.com> Date: Mon, 23 Jan 2017 02:40:08 -0500 Subject: [PATCH 047/180] Testcases: Check for required binaries (#2655) Fail fast in case test binaries are not built and provide instructions on how to build them. --- testcases/complete-run.pl.in | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/testcases/complete-run.pl.in b/testcases/complete-run.pl.in index d872bda1..2019253c 100755 --- a/testcases/complete-run.pl.in +++ b/testcases/complete-run.pl.in @@ -87,6 +87,17 @@ foreach my $binary (@binaries) { die "$binary is not an executable" unless -x $binary; } +my @test_binaries = qw( + @abs_top_builddir@/test.commands_parser + @abs_top_builddir@/test.config_parser + @abs_top_builddir@/test.inject_randr15 + ); + +foreach my $binary (@test_binaries) { + die "$binary executable not found, did you run “make check”?" unless -e $binary; + die "$binary is not an executable" unless -x $binary; +} + $ENV{PATH} = join(':', '@abs_top_builddir@/i3-nagbar', '@abs_top_builddir@/i3-msg', From 348d0d4622857caa6539f5a259e6cfca183dfe0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=A4nel?= <contact@tobias-haenel.de> Date: Tue, 24 Jan 2017 17:40:32 +0100 Subject: [PATCH 048/180] Added missing cases for workspace event (#2656) The possible values "rename", "reload" and "restored" of the property 'change' from the workspace event were missing. Because no events of those types contain an old workspace, this was trivial. --- docs/ipc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ipc b/docs/ipc index 5d47bcbd..466b6596 100644 --- a/docs/ipc +++ b/docs/ipc @@ -696,9 +696,9 @@ if ($is_event) { This event consists of a single serialized map containing a property +change (string)+ which indicates the type of the change ("focus", "init", -"empty", "urgent"). A +current (object)+ property will be present with the -affected workspace whenever the type of event affects a workspace (otherwise, -it will be +null). +"empty", "urgent", "reload", "rename", "restored"). A +current (object)+ +property will be present with the affected workspace whenever the type of event +affects a workspace (otherwise, it will be +null). When the change is "focus", an +old (object)+ property will be present with the previous workspace. When the first switch occurs (when i3 focuses the From fa488d721dfd1e1abe849faadc12b1f9ec93ac76 Mon Sep 17 00:00:00 2001 From: mihaicmn <mihai.cmn@gmail.com> Date: Wed, 25 Jan 2017 09:18:13 +0200 Subject: [PATCH 049/180] migrate i3-config-wizard to draw_util (#2654) --- i3-config-wizard/main.c | 113 +++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 59 deletions(-) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index 8eec941c..dd58fd12 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -69,10 +69,16 @@ #include "xcb.h" #include "libi3.h" +#define TEXT_PADDING logical_px(4) +#define WIN_POS_X logical_px(490) +#define WIN_POS_Y logical_px(297) +#define WIN_WIDTH logical_px(300) +#define WIN_HEIGHT (15 * font.height + TEXT_PADDING) + +#define col_x(col) \ + (((col)-1) * char_width + TEXT_PADDING) #define row_y(row) \ - (((row)-1) * font.height + logical_px(4)) -#define window_height() \ - (row_y(15) + font.height) + (((row)-1) * font.height + TEXT_PADDING) enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME; @@ -90,8 +96,7 @@ static i3Font bold_font; static int char_width; static char *socket_path; static xcb_window_t win; -static xcb_pixmap_t pixmap; -static xcb_gcontext_t pixmap_gc; +static surface_t surface; static xcb_key_symbols_t *symbols; xcb_window_t root; static struct xkb_keymap *xkb_keymap; @@ -463,82 +468,73 @@ void errorlog(char *fmt, ...) { void debuglog(char *fmt, ...) { } +static void txt(int col, int row, char *text, color_t fg, color_t bg) { + int x = col_x(col); + int y = row_y(row); + i3String *string = i3string_from_utf8(text); + draw_util_text(string, &surface, fg, bg, x, y, WIN_WIDTH - x - TEXT_PADDING); + i3string_free(string); +} + /* * Handles expose events, that is, draws the window contents. * */ static int handle_expose() { - /* re-draw the background */ - xcb_rectangle_t border = {0, 0, logical_px(300), window_height()}; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); + const color_t black = draw_util_hex_to_color("#000000"); + const color_t white = draw_util_hex_to_color("#FFFFFF"); + const color_t green = draw_util_hex_to_color("#00FF00"); + const color_t red = draw_util_hex_to_color("#FF0000"); + + /* draw background */ + draw_util_clear_surface(&surface, black); set_font(&font); -#define txt(x, row, text) \ - draw_text_ascii(text, pixmap, pixmap_gc, \ - x, row_y(row), logical_px(500) - x * 2) - if (current_step == STEP_WELCOME) { - /* restore font color */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); - - txt(logical_px(10), 2, "You have not configured i3 yet."); - txt(logical_px(10), 3, "Do you want me to generate a config at"); + txt(2, 2, "You have not configured i3 yet.", white, black); + txt(2, 3, "Do you want me to generate a config at", white, black); char *msg; sasprintf(&msg, "%s?", config_path); - txt(logical_px(10), 4, msg); + txt(2, 4, msg, white, black); free(msg); - txt(logical_px(85), 6, "Yes, generate the config"); - txt(logical_px(85), 8, "No, I will use the defaults"); + txt(13, 6, "Yes, generate the config", white, black); + txt(13, 8, "No, I will use the defaults", white, black); - /* green */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000")); - txt(logical_px(25), 6, "<Enter>"); + txt(4, 6, "<Enter>", green, black); - /* red */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000")); - txt(logical_px(31), 8, "<ESC>"); + txt(5, 8, "<ESC>", red, black); } if (current_step == STEP_GENERATE) { - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); - - txt(logical_px(10), 2, "Please choose either:"); - txt(logical_px(85), 4, "Win as default modifier"); - txt(logical_px(85), 5, "Alt as default modifier"); - txt(logical_px(10), 7, "Afterwards, press"); - txt(logical_px(85), 9, "to write the config"); - txt(logical_px(85), 10, "to abort"); + txt(2, 2, "Please choose either:", white, black); + txt(13, 4, "Win as default modifier", white, black); + txt(13, 5, "Alt as default modifier", white, black); + txt(2, 7, "Afterwards, press", white, black); + txt(13, 9, "to write the config", white, black); + txt(13, 10, "to abort", white, black); /* the not-selected modifier */ if (modifier == MOD_Mod4) - txt(logical_px(31), 5, "<Alt>"); + txt(5, 5, "<Alt>", white, black); else - txt(logical_px(31), 4, "<Win>"); + txt(5, 4, "<Win>", white, black); /* the selected modifier */ set_font(&bold_font); - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); if (modifier == MOD_Mod4) - txt(logical_px(10), 4, "-> <Win>"); + txt(2, 4, "-> <Win>", white, black); else - txt(logical_px(10), 5, "-> <Alt>"); + txt(2, 5, "-> <Alt>", white, black); - /* green */ set_font(&font); - set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000")); - txt(logical_px(25), 9, "<Enter>"); + txt(4, 9, "<Enter>", green, black); - /* red */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000")); - txt(logical_px(31), 10, "<ESC>"); + txt(5, 10, "<ESC>", red, black); } - /* Copy the contents of the pixmap to the real window */ - xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), logical_px(500)); xcb_flush(conn); return 1; @@ -625,8 +621,7 @@ static void handle_button_press(xcb_button_press_event_t *event) { if (current_step != STEP_GENERATE) return; - if (event->event_x < logical_px(32) || - event->event_x > (logical_px(32) + char_width * 5)) + if (event->event_x < col_x(5) || event->event_x > col_x(10)) return; if (event->event_y >= row_y(4) && event->event_y <= (row_y(4) + font.height)) { @@ -867,10 +862,10 @@ int main(int argc, char *argv[]) { xcb_create_window( conn, XCB_COPY_FROM_PARENT, - win, /* the window id */ - root, /* parent == root */ - logical_px(490), logical_px(297), logical_px(300), window_height(), /* dimensions */ - 0, /* X11 border = 0, we draw our own */ + win, /* the window id */ + root, /* parent == root */ + WIN_POS_X, WIN_POS_Y, WIN_WIDTH, WIN_HEIGHT, /* dimensions */ + 0, /* X11 border = 0, we draw our own */ XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, @@ -915,11 +910,8 @@ int main(int argc, char *argv[]) { strlen("i3: first configuration"), "i3: first configuration"); - /* Create pixmap */ - pixmap = xcb_generate_id(conn); - pixmap_gc = xcb_generate_id(conn); - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), logical_px(500)); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + /* Initialize drawable surface */ + draw_util_surface_init(conn, &surface, win, get_visualtype(root_screen), WIN_WIDTH, WIN_HEIGHT); /* Grab the keyboard to get all input */ xcb_flush(conn); @@ -976,5 +968,8 @@ int main(int argc, char *argv[]) { free(event); } + /* Dismiss drawable surface */ + draw_util_surface_free(conn, &surface); + return 0; } From 98f202dd1b2782d11a713513f5dcca2f52daab73 Mon Sep 17 00:00:00 2001 From: Johannes Lange <jolange@users.noreply.github.com> Date: Sun, 5 Feb 2017 10:04:35 +0100 Subject: [PATCH 050/180] restart bar status command on config reload (#2668) Closes #2651 --- i3bar/src/ipc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 4a090ad7..cc5074e5 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -178,6 +178,11 @@ void got_bar_config_update(char *event) { init_xcb_late(config.fontname); init_colors(&(config.colors)); + /* restart status command process */ + kill_child(); + start_child(config.command); + FREE(config.command); + draw_bars(false); } From 492da1c062d7758e162f0edb5262b47b55b48d89 Mon Sep 17 00:00:00 2001 From: Manuel Mendez <mmendez534@gmail.com> Date: Sun, 5 Feb 2017 04:07:21 -0500 Subject: [PATCH 051/180] docs: focus_follow_mouse only happens at window border crossings (#2669) see #2666 --- docs/userguide | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/userguide b/docs/userguide index d15aed73..584edc80 100644 --- a/docs/userguide +++ b/docs/userguide @@ -946,12 +946,12 @@ the next section. === Focus follows mouse -By default, window focus follows your mouse movements. However, if you have a -setup where your mouse usually is in your way (like a touchpad on your laptop -which you do not want to disable completely), you might want to disable 'focus -follows mouse' and control focus only by using your keyboard. The mouse will -still be useful inside the currently active window (for example to click on -links in your browser window). +By default, window focus follows your mouse movements as the mouse crosses +window borders. However, if you have a setup where your mouse usually is in your +way (like a touchpad on your laptop which you do not want to disable +completely), you might want to disable 'focus follows mouse' and control focus +only by using your keyboard. The mouse will still be useful inside the +currently active window (for example to click on links in your browser window). *Syntax*: -------------------------- From 55692c166795142419ffc2ce57cbdc0a84a61be7 Mon Sep 17 00:00:00 2001 From: Stefan Hagen <github@textmail.me> Date: Sun, 5 Feb 2017 18:57:44 +0100 Subject: [PATCH 052/180] Explaing the workspace number "1: www" behavior (#2674) The documentation did not explain how workspace number `"1: www"` is working. Related to #2663 Rephrase to cover the creation case --- docs/userguide | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/userguide b/docs/userguide index 584edc80..69eeda35 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2090,6 +2090,23 @@ i3-msg 'rename workspace to "2: mail"' bindsym $mod+r exec i3-input -F 'rename workspace to "%s"' -P 'New name: ' -------------------------------------------------------------------------- +If you want to rename workspaces on demand while keeping the navigation stable, +you can use a setup like this: + +*Example*: +------------------------- +bindsym $mod+1 workspace number "1: www" +bindsym $mod+2 workspace number "2: mail" +... +------------------------- + +If a workspace does not exist, the command +workspace number "1: mail"+ will +create workspace "1: mail". + +If a workspace with number 1 does already exist, the command will switch to this +workspace and ignore the text part. So even when the workspace has been renamed +to "1: web", the above command will still switch to it. + === Moving workspaces to a different screen See <<move_to_outputs>> for how to move a container/workspace to a different From 297e6be5bd3c223c307ac4b5951163e35924bdae Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Mon, 6 Feb 2017 10:10:41 -0800 Subject: [PATCH 053/180] document our project governance model (#2675) --- .github/GOVERNANCE.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/GOVERNANCE.md diff --git a/.github/GOVERNANCE.md b/.github/GOVERNANCE.md new file mode 100644 index 00000000..44e13345 --- /dev/null +++ b/.github/GOVERNANCE.md @@ -0,0 +1,30 @@ +# i3 project governance + +## Overview + +The i3 project uses a governance model commonly described as Benevolent +Dictator For Life (BDFL). This document outlines our understanding of what this +means. + +## Roles + +* user: anyone who interacts with the i3 project +* core contributor: a handful of people who have contributed significantly to + the project by any means (issue triage, support, documentation, code, etc.). + Core contributors are recognizable via GitHub’s “Member” badge. +* BDFL: a single individual who makes decisions when consensus cannot be + reached. i3’s current BDFL is [@stapelberg](https://github.com/stapelberg). + +## Decision making process + +In general, we try to reach consensus in discussions. In case consensus cannot +be reached, the BDFL makes a decision. + +For feature requests and code contributions specifically, the values with which +we consider them can be found on the bottom of https://i3wm.org/. These values +are not set in stone and are to be treated as guiding principles, not absolute +rules that must be followed in every case. + +## Contribution process + +Please see [CONTRIBUTING](CONTRIBUTING.md). From ad9c879cbd5a9cc7f41484ea4651cf02c17335f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Sun, 12 Feb 2017 20:24:35 +0100 Subject: [PATCH 054/180] Add troubleshooting for title attributes during layout restoring. (#2679) --- docs/layout-saving | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/layout-saving b/docs/layout-saving index 5897036e..6ca08fa2 100644 --- a/docs/layout-saving +++ b/docs/layout-saving @@ -259,3 +259,27 @@ container: ] } -------------------------------------------------------------------------------- + +=== Placeholders using window title matches don't swallow the window + +If you use the +title+ attribute to match a window and find that it doesn't +work or only works sometimes, the reason might be that the application sets the +title only after making the window visible. This will be especially true for +programs running inside terminal emulators, e.g., +urxvt -e irssi+ when +matching on +title: "irssi"+. + +One way to deal with this is to not rely on the title, but instead use, e.g., +the +instance+ attribute and running the program to set this window instance to +that value: + +-------------------------------------------------------------------------------- +# Run irssi via +# urxvt -name "irssi-container" -e irssi + +"swallows": [ + { + "class": "URxvt", + "instance": "irssi-container" + } +] +-------------------------------------------------------------------------------- From 7732971ad86433cc232e5d951564acff448e08a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=83=C2=83=C3=82=C2=BCrk?= <ingo.buerk@tngtech.com> Date: Sun, 12 Feb 2017 20:36:42 +0100 Subject: [PATCH 055/180] Introduce named aliases for mouse buttons. This increases readability and allows us to cover up the fact that XCB doesn't define constants for left/right scrolling. --- i3bar/src/xcb.c | 4 ++-- include/libi3.h | 11 +++++++++++ src/click.c | 22 +++++++++++----------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index edef9b7e..0b9d6f81 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -531,7 +531,7 @@ void handle_button(xcb_button_press_event_t *event) { return; } switch (event->detail) { - case 4: + case XCB_BUTTON_SCROLL_UP: /* Mouse wheel up. We select the previous ws, if any. * If there is no more workspace, don’t even send the workspace * command, otherwise (with workspace auto_back_and_forth) we’d end @@ -541,7 +541,7 @@ void handle_button(xcb_button_press_event_t *event) { cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq); break; - case 5: + case XCB_BUTTON_SCROLL_DOWN: /* Mouse wheel down. We select the next ws, if any. * If there is no more workspace, don’t even send the workspace * command, otherwise (with workspace auto_back_and_forth) we’d end diff --git a/include/libi3.h b/include/libi3.h index d33d6c71..67094534 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -24,6 +24,17 @@ #define DEFAULT_DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) +/** Mouse buttons */ +#define XCB_BUTTON_CLICK_LEFT XCB_BUTTON_INDEX_1 +#define XCB_BUTTON_CLICK_MIDDLE XCB_BUTTON_INDEX_2 +#define XCB_BUTTON_CLICK_RIGHT XCB_BUTTON_INDEX_3 +#define XCB_BUTTON_SCROLL_UP XCB_BUTTON_INDEX_4 +#define XCB_BUTTON_SCROLL_DOWN XCB_BUTTON_INDEX_5 +/* xcb doesn't define constants for these. */ +#define XCB_BUTTON_SCROLL_LEFT 6 +#define XCB_BUTTON_SCROLL_RIGHT 7 + + /** * XCB connection and root screen * diff --git a/src/click.c b/src/click.c index 913741b4..5717b9b0 100644 --- a/src/click.c +++ b/src/click.c @@ -178,8 +178,8 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod if (con->parent->type == CT_DOCKAREA) goto done; - const bool is_left_or_right_click = (event->detail == XCB_BUTTON_INDEX_1 || - event->detail == XCB_BUTTON_INDEX_3); + const bool is_left_or_right_click = (event->detail == XCB_BUTTON_CLICK_LEFT || + event->detail == XCB_BUTTON_CLICK_RIGHT); /* if the user has bound an action to this click, it should override the * default behavior. */ @@ -228,8 +228,8 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */ if (in_stacked && dest == CLICK_DECORATION && - (event->detail == XCB_BUTTON_INDEX_4 || - event->detail == XCB_BUTTON_INDEX_5)) { + (event->detail == XCB_BUTTON_SCROLL_UP || + event->detail == XCB_BUTTON_SCROLL_DOWN)) { DLOG("Scrolling on a window decoration\n"); orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ); /* Focus the currently focused container on the same level that the @@ -244,9 +244,9 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod * #557), we first check if scrolling is possible at all. */ bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL); bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL); - if (event->detail == XCB_BUTTON_INDEX_4 && scroll_prev_possible) + if (event->detail == XCB_BUTTON_SCROLL_UP && scroll_prev_possible) tree_next('p', orientation); - else if (event->detail == XCB_BUTTON_INDEX_5 && scroll_next_possible) + else if (event->detail == XCB_BUTTON_SCROLL_DOWN && scroll_next_possible) tree_next('n', orientation); goto done; } @@ -261,7 +261,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod floating_raise_con(floatingcon); /* 4: floating_modifier plus left mouse button drags */ - if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1) { + if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) { floating_drag_window(floatingcon, event); return 1; } @@ -269,7 +269,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod /* 5: resize (floating) if this was a (left or right) click on the * left/right/bottom border, or a right click on the decoration. * also try resizing (tiling) if it was a click on the top */ - if (mod_pressed && event->detail == XCB_BUTTON_INDEX_3) { + if (mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) { DLOG("floating resize due to floatingmodifier\n"); floating_resize_window(floatingcon, proportional, event); return 1; @@ -283,7 +283,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod goto done; } - if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_INDEX_3) { + if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_CLICK_RIGHT) { DLOG("floating resize due to decoration right click\n"); floating_resize_window(floatingcon, proportional, event); return 1; @@ -298,7 +298,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod /* 6: dragging, if this was a click on a decoration (which did not lead * to a resize) */ if (!in_stacked && dest == CLICK_DECORATION && - (event->detail == XCB_BUTTON_INDEX_1)) { + (event->detail == XCB_BUTTON_CLICK_LEFT)) { floating_drag_window(floatingcon, event); return 1; } @@ -313,7 +313,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod } /* 7: floating modifier pressed, initiate a resize */ - if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_INDEX_3) { + if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) { if (floating_mod_on_tiled_client(con, event)) return 1; } From 432c4211ea422608645ecb8ef02ac88687adeced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <ingo.buerk@tngtech.com> Date: Sun, 12 Feb 2017 20:48:44 +0100 Subject: [PATCH 056/180] Allow using left/right scrolling like up/down scrolling. This commit makes left/right scrolling synonyms for up/down scrolling for * scrolling on window decoration * scrolling on i3bar workspaces fixes #2677 --- i3bar/src/xcb.c | 2 ++ include/libi3.h | 1 - src/click.c | 10 +++++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 0b9d6f81..24f91642 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -532,6 +532,7 @@ void handle_button(xcb_button_press_event_t *event) { } switch (event->detail) { case XCB_BUTTON_SCROLL_UP: + case XCB_BUTTON_SCROLL_LEFT: /* Mouse wheel up. We select the previous ws, if any. * If there is no more workspace, don’t even send the workspace * command, otherwise (with workspace auto_back_and_forth) we’d end @@ -542,6 +543,7 @@ void handle_button(xcb_button_press_event_t *event) { cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq); break; case XCB_BUTTON_SCROLL_DOWN: + case XCB_BUTTON_SCROLL_RIGHT: /* Mouse wheel down. We select the next ws, if any. * If there is no more workspace, don’t even send the workspace * command, otherwise (with workspace auto_back_and_forth) we’d end diff --git a/include/libi3.h b/include/libi3.h index 67094534..dbb29e1f 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -34,7 +34,6 @@ #define XCB_BUTTON_SCROLL_LEFT 6 #define XCB_BUTTON_SCROLL_RIGHT 7 - /** * XCB connection and root screen * diff --git a/src/click.c b/src/click.c index 5717b9b0..e989b88d 100644 --- a/src/click.c +++ b/src/click.c @@ -229,7 +229,9 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod if (in_stacked && dest == CLICK_DECORATION && (event->detail == XCB_BUTTON_SCROLL_UP || - event->detail == XCB_BUTTON_SCROLL_DOWN)) { + event->detail == XCB_BUTTON_SCROLL_DOWN || + event->detail == XCB_BUTTON_SCROLL_LEFT || + event->detail == XCB_BUTTON_SCROLL_RIGHT)) { DLOG("Scrolling on a window decoration\n"); orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ); /* Focus the currently focused container on the same level that the @@ -244,10 +246,12 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod * #557), we first check if scrolling is possible at all. */ bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL); bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL); - if (event->detail == XCB_BUTTON_SCROLL_UP && scroll_prev_possible) + if ((event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) && scroll_prev_possible) { tree_next('p', orientation); - else if (event->detail == XCB_BUTTON_SCROLL_DOWN && scroll_next_possible) + } else if ((event->detail == XCB_BUTTON_SCROLL_DOWN || event->detail == XCB_BUTTON_SCROLL_RIGHT) && scroll_next_possible) { tree_next('n', orientation); + } + goto done; } From 8158e4c4155e15ea9442acd960a194376400788b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Fri, 17 Feb 2017 09:06:40 +0100 Subject: [PATCH 057/180] Copy the entire window content on Expose events. (#2685) With commit d58dbc3 we started ignoring Expose events in a sequence except for the last one. Since we only copied the affected part of the window in the Expose event handler, this caused incorrectly rendered window decorations. Instead of reverting to the old behavior, we now copy the entire window content on this single, last event with the following rationale: - It's cheaper to copy a larger chunk once than multiple smaller chunks doing one server roundtrip each. - That's how we do it when rendering out decoration on decoration changes as well. fixes #2683 --- src/handlers.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 315688c4..9fb9040e 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -614,12 +614,9 @@ static void handle_expose_event(xcb_expose_event_t *event) { } /* Since we render to our surface on every change anyways, expose events - * only tell us that the X server lost (parts of) the window contents. We - * can handle that by copying the appropriate part from our surface to the - * window. */ + * only tell us that the X server lost (parts of) the window contents. */ draw_util_copy_surface(&(parent->frame_buffer), &(parent->frame), - event->x, event->y, event->x, event->y, - event->width, event->height); + 0, 0, 0, 0, parent->rect.width, parent->rect.height); xcb_flush(conn); return; } From 3410cb256d3b033c2ca61c78766eb58652dce49f Mon Sep 17 00:00:00 2001 From: s3rb31 <s3rb31@mail.ru> Date: Tue, 21 Feb 2017 02:12:39 +0100 Subject: [PATCH 058/180] Implement mapping from string to layout as extra function --- include/util.h | 8 ++++++++ src/commands.c | 15 +-------------- src/util.c | 25 +++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/include/util.h b/include/util.h index e5ba3341..cbe9778c 100644 --- a/include/util.h +++ b/include/util.h @@ -69,6 +69,14 @@ Rect rect_sub(Rect a, Rect b); */ __attribute__((pure)) bool name_is_digits(const char *name); +/** + * Set 'out' to the layout_t value for the given layout. The function + * returns true on success or false if the passed string is not a valid + * layout name. + * + */ +bool layout_from_name(const char *layout_str, layout_t *out); + /** * Parses the workspace name as a number. Returns -1 if the workspace should be * interpreted as a "named workspace". diff --git a/src/commands.c b/src/commands.c index 2387ddd7..44a5d8a4 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1489,21 +1489,8 @@ void cmd_move_direction(I3_CMD, const char *direction, long move_px) { void cmd_layout(I3_CMD, const char *layout_str) { HANDLE_EMPTY_MATCH; - if (strcmp(layout_str, "stacking") == 0) - layout_str = "stacked"; layout_t layout; - /* default is a special case which will be handled in con_set_layout(). */ - if (strcmp(layout_str, "default") == 0) - layout = L_DEFAULT; - else if (strcmp(layout_str, "stacked") == 0) - layout = L_STACKED; - else if (strcmp(layout_str, "tabbed") == 0) - layout = L_TABBED; - else if (strcmp(layout_str, "splitv") == 0) - layout = L_SPLITV; - else if (strcmp(layout_str, "splith") == 0) - layout = L_SPLITH; - else { + if (!layout_from_name(layout_str, &layout)) { ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str); return; } diff --git a/src/util.c b/src/util.c index cfe4c953..b6f45fde 100644 --- a/src/util.c +++ b/src/util.c @@ -66,6 +66,31 @@ __attribute__((pure)) bool name_is_digits(const char *name) { return true; } +/** + * Set 'out' to the layout_t value for the given layout. The function + * returns true on success or false if the passed string is not a valid + * layout name. + * + */ +bool layout_from_name(const char *layout_str, layout_t *out) { + if (strcmp(layout_str, "default") == 0) { + *out = L_DEFAULT; + } else if (strcasecmp(layout_str, "stacked") == 0 || + strcasecmp(layout_str, "stacking") == 0) { + *out = L_STACKED; + } else if (strcasecmp(layout_str, "tabbed") == 0) { + *out = L_TABBED; + } else if (strcasecmp(layout_str, "splitv") == 0) { + *out = L_SPLITV; + } else if (strcasecmp(layout_str, "splith") == 0) { + *out = L_SPLITH; + } else { + return false; + } + + return true; +} + /* * Parses the workspace name as a number. Returns -1 if the workspace should be * interpreted as a "named workspace". From 37658bd6d7066489cef38dfbb8797975d2024b2a Mon Sep 17 00:00:00 2001 From: s3rb31 <s3rb31@mail.ru> Date: Thu, 19 Jan 2017 20:49:56 +0100 Subject: [PATCH 059/180] layout toggle: take any combination of layouts as arguments (#2649) With this PR the 'layout toggle' command can be passed any combination of valid layout keywords as arguments. They will be activated one after another each time you issue the command, advancing from left to right always selecting the layout after the currently active layout or the leftmost layout if the active layout is not in the argument list. This PR also incorporates the feature request from #2476. --- docs/userguide | 18 +++++++++- parser-specs/commands.spec | 2 +- src/con.c | 64 +++++++++++++++++++++++++++-------- src/util.c | 11 +++--- testcases/t/192-layout.t | 68 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 20 deletions(-) diff --git a/docs/userguide b/docs/userguide index 6dc2241b..21d08625 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1802,7 +1802,8 @@ The +toggle+ option will toggle the orientation of the split container if it contains a single window. Otherwise it makes the current window a split container with opposite orientation compared to the parent container. Use +layout toggle split+ to change the layout of any split container from -splitv to splith or vice-versa. +splitv to splith or vice-versa. You can also define a custom sequence of layouts +to cycle through with +layout toggle+, see <<manipulating_layout>>. *Syntax*: -------------------------------- @@ -1822,6 +1823,11 @@ Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+ or +layout splith+ to change the current container layout to splith/splitv, stacking, tabbed layout, splitv or splith, respectively. +Specify up to four layouts after +layout toggle+ to cycle through them. Every +time the command is executed, the layout specified after the currently active +one will be applied. If the currently active layout is not in the list, the +first layout in the list will be activated. + To make the current window (!) fullscreen, use +fullscreen enable+ (or +fullscreen enable global+ for the global mode), to leave either fullscreen mode use +fullscreen disable+, and to toggle between these two states use @@ -1834,6 +1840,7 @@ enable+ respectively +floating disable+ (or +floating toggle+): -------------------------------------------- layout default|tabbed|stacking|splitv|splith layout toggle [split|all] +layout toggle [split|tabbed|stacking|splitv|splith] [split|tabbed|stacking|splitv|splith]… -------------------------------------------- *Examples*: @@ -1848,6 +1855,15 @@ bindsym $mod+x layout toggle # Toggle between stacking/tabbed/splith/splitv: bindsym $mod+x layout toggle all +# Toggle between stacking/tabbed/splith: +bindsym $mod+x layout toggle stacking tabbed splith + +# Toggle between splitv/tabbed +bindsym $mod+x layout toggle splitv tabbed + +# Toggle between last split layout/tabbed/stacking +bindsym $mod+x layout toggle split tabbed stacking + # Toggle fullscreen bindsym $mod+f fullscreen toggle diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index d4b3dbc6..542ff798 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -110,7 +110,7 @@ state LAYOUT: state LAYOUT_TOGGLE: end -> call cmd_layout_toggle($toggle_mode) - toggle_mode = 'split', 'all' + toggle_mode = string -> call cmd_layout_toggle($toggle_mode) # append_layout <path> diff --git a/src/con.c b/src/con.c index 40924a73..b3f193e6 100644 --- a/src/con.c +++ b/src/con.c @@ -1719,28 +1719,64 @@ void con_toggle_layout(Con *con, const char *toggle_mode) { parent = con->parent; DLOG("con_toggle_layout(%p, %s), parent = %p\n", con, toggle_mode, parent); - if (strcmp(toggle_mode, "split") == 0) { - /* Toggle between splits. When the current layout is not a split - * layout, we just switch back to last_split_layout. Otherwise, we - * change to the opposite split layout. */ - if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) - con_set_layout(con, parent->last_split_layout); - else { - if (parent->layout == L_SPLITH) - con_set_layout(con, L_SPLITV); - else - con_set_layout(con, L_SPLITH); + const char delim[] = " "; + + if (strcasecmp(toggle_mode, "split") == 0 || strstr(toggle_mode, delim)) { + /* L_DEFAULT is used as a placeholder value to distinguish if + * the first layout has already been saved. (it can never be L_DEFAULT) */ + layout_t new_layout = L_DEFAULT; + bool current_layout_found = false; + char *tm_dup = sstrdup(toggle_mode); + char *cur_tok = strtok(tm_dup, delim); + + for (layout_t layout; cur_tok != NULL; cur_tok = strtok(NULL, delim)) { + if (strcasecmp(cur_tok, "split") == 0) { + /* Toggle between splits. When the current layout is not a split + * layout, we just switch back to last_split_layout. Otherwise, we + * change to the opposite split layout. */ + if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) { + layout = parent->last_split_layout; + } else { + layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH; + } + } else { + bool success = layout_from_name(cur_tok, &layout); + if (!success || layout == L_DEFAULT) { + ELOG("The token '%s' was not recognized and has been skipped.\n", cur_tok); + continue; + } + } + + /* If none of the specified layouts match the current, + * fall back to the first layout in the list */ + if (new_layout == L_DEFAULT) { + new_layout = layout; + } + + /* We found the active layout in the last iteration, so + * now let's activate the current layout (next in list) */ + if (current_layout_found) { + new_layout = layout; + free(tm_dup); + break; + } + + if (parent->layout == layout) { + current_layout_found = true; + } } - } else { + + con_set_layout(con, new_layout); + } else if (strcasecmp(toggle_mode, "all") == 0 || strcasecmp(toggle_mode, "default") == 0) { if (parent->layout == L_STACKED) con_set_layout(con, L_TABBED); else if (parent->layout == L_TABBED) { - if (strcmp(toggle_mode, "all") == 0) + if (strcasecmp(toggle_mode, "all") == 0) con_set_layout(con, L_SPLITH); else con_set_layout(con, parent->last_split_layout); } else if (parent->layout == L_SPLITH || parent->layout == L_SPLITV) { - if (strcmp(toggle_mode, "all") == 0) { + if (strcasecmp(toggle_mode, "all") == 0) { /* When toggling through all modes, we toggle between * splith/splitv, whereas normally we just directly jump to * stacked. */ diff --git a/src/util.c b/src/util.c index b6f45fde..0289ded9 100644 --- a/src/util.c +++ b/src/util.c @@ -66,7 +66,7 @@ __attribute__((pure)) bool name_is_digits(const char *name) { return true; } -/** +/* * Set 'out' to the layout_t value for the given layout. The function * returns true on success or false if the passed string is not a valid * layout name. @@ -75,20 +75,23 @@ __attribute__((pure)) bool name_is_digits(const char *name) { bool layout_from_name(const char *layout_str, layout_t *out) { if (strcmp(layout_str, "default") == 0) { *out = L_DEFAULT; + return true; } else if (strcasecmp(layout_str, "stacked") == 0 || strcasecmp(layout_str, "stacking") == 0) { *out = L_STACKED; + return true; } else if (strcasecmp(layout_str, "tabbed") == 0) { *out = L_TABBED; + return true; } else if (strcasecmp(layout_str, "splitv") == 0) { *out = L_SPLITV; + return true; } else if (strcasecmp(layout_str, "splith") == 0) { *out = L_SPLITH; - } else { - return false; + return true; } - return true; + return false; } /* diff --git a/testcases/t/192-layout.t b/testcases/t/192-layout.t index 6fd6eae8..1d406fc6 100644 --- a/testcases/t/192-layout.t +++ b/testcases/t/192-layout.t @@ -95,4 +95,72 @@ cmd 'layout toggle all'; ($nodes, $focus) = get_ws_content($tmp); is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); +cmd 'layout toggle splith splitv'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splith', 'layout now splith'); + +cmd 'layout toggle splith splitv'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +cmd 'layout toggle stacked splitv tabbed'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed'); + +cmd 'layout toggle stacking splitv tabbed'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); + +cmd 'layout toggle stacking splitv tabbed'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +cmd 'layout toggle splitv i stacking tabbed'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); + +cmd 'layout toggle stacked'; +($nodes, $focus) = get_ws_content($tmp); +# this is correct if it does nothing +is($nodes->[1]->{layout}, 'stacked', 'layout now tabbed'); + +cmd 'layout toggle tabbed stacked'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'tabbed', 'layout now stacked'); + +# obsoletes 'split' ;) +cmd 'layout toggle splith splitv'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splith', 'layout now splith'); + +# nonsense but works expectedly +cmd 'layout toggle split split'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +cmd 'layout toggle split split'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splith', 'layout now splith'); + +# testing with arbitrary length and garbage +cmd 'layout toggle stacking splith tabbed splitv stacking'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed'); + +cmd 'layout toggle stacking splith garbage tabbed splitv stacking'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +cmd 'layout toggle stacking splith garbage tabbed splitv stacking'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); + +cmd 'layout toggle splitv splith garbage splitv tabbed stacking splitv'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +cmd 'layout toggle splitv garbage tabbed'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed'); + done_testing; From dda2ef9716c6025fdfac93d1f5a6e4fe7e252086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Fri, 24 Feb 2017 08:36:31 +0100 Subject: [PATCH 060/180] Fixes a small mixup in the assertion description. (#2692) relates to PR #2649 --- testcases/t/192-layout.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcases/t/192-layout.t b/testcases/t/192-layout.t index 1d406fc6..c54f27a2 100644 --- a/testcases/t/192-layout.t +++ b/testcases/t/192-layout.t @@ -122,11 +122,11 @@ is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); cmd 'layout toggle stacked'; ($nodes, $focus) = get_ws_content($tmp); # this is correct if it does nothing -is($nodes->[1]->{layout}, 'stacked', 'layout now tabbed'); +is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); cmd 'layout toggle tabbed stacked'; ($nodes, $focus) = get_ws_content($tmp); -is($nodes->[1]->{layout}, 'tabbed', 'layout now stacked'); +is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed'); # obsoletes 'split' ;) cmd 'layout toggle splith splitv'; From 1e1da5a6595dafa479adedf8924c8d81513bbaa3 Mon Sep 17 00:00:00 2001 From: Trevor Merrifield <trevorm42@gmail.com> Date: Mon, 6 Mar 2017 01:20:47 -0500 Subject: [PATCH 061/180] docs/ipc: Document the 'primary' flag Resolves #2697 --- docs/ipc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/ipc b/docs/ipc index 466b6596..026e434d 100644 --- a/docs/ipc +++ b/docs/ipc @@ -232,6 +232,8 @@ name (string):: The name of this output (as seen in +xrandr(1)+). Encoded in UTF-8. active (boolean):: Whether this output is currently active (has a valid mode). +primary (boolean):: + Whether this output is currently the primary output. current_workspace (string):: The name of the current workspace that is visible on this output. +null+ if the output is not active. From 2f0f8b16c2755165fc79c5801aedff0428ef03ad Mon Sep 17 00:00:00 2001 From: lebenlechzer <lebenlechzer@gmail.com> Date: Sun, 12 Mar 2017 21:17:12 +0100 Subject: [PATCH 062/180] i3bar: accept 'primary' for output config option --- docs/userguide | 14 +++++++++++++- i3bar/src/ipc.c | 8 +++++++- i3bar/src/outputs.c | 11 ++++++----- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/docs/userguide b/docs/userguide index 40e9e3b9..4946a1ba 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1378,7 +1378,7 @@ directive multiple times. *Syntax*: --------------- -output <output> +output primary|<output> --------------- *Example*: @@ -1400,7 +1400,19 @@ bar { statusline #ffffff } } + +# show bar on the primary monitor and on HDMI2 +bar { + output primary + output HDMI2 + status_command i3status +} + ------------------------------- +Note that you might not have a primary output configured yet. To do so, run: +------------------------- +xrandr --output <output> --primary +------------------------- === Tray output diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index cc5074e5..459684ef 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -63,12 +63,18 @@ void got_subscribe_reply(char *reply) { * */ void got_output_reply(char *reply) { + DLOG("Clearing old output configuration...\n"); + i3_output *o_walk; + SLIST_FOREACH(o_walk, outputs, slist) { + destroy_window(o_walk); + } + FREE_SLIST(outputs, i3_output); + DLOG("Parsing outputs JSON...\n"); parse_outputs_json(reply); DLOG("Reconfiguring windows...\n"); reconfig_windows(false); - i3_output *o_walk; SLIST_FOREACH(o_walk, outputs, slist) { kick_tray_clients(o_walk); } diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index 480c00be..e7cd6eeb 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -189,11 +189,12 @@ static int outputs_end_map_cb(void *params_) { if (config.num_outputs > 0) { bool handle_output = false; for (int c = 0; c < config.num_outputs; c++) { - if (strcasecmp(params->outputs_walk->name, config.outputs[c]) != 0) - continue; - - handle_output = true; - break; + if (strcasecmp(params->outputs_walk->name, config.outputs[c]) == 0 || + (strcasecmp(config.outputs[c], "primary") == 0 && + params->outputs_walk->primary)) { + handle_output = true; + break; + } } if (!handle_output) { DLOG("Ignoring output \"%s\", not configured to handle it.\n", From f475b37a3c0bbb46f93ece8806882fcbef6f2109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20K=C3=B6nig?= <Arlon1@users.noreply.github.com> Date: Sun, 26 Mar 2017 16:05:56 +0200 Subject: [PATCH 063/180] Adding new terminals to i3-sensible-terminal --- i3-sensible-terminal | 2 +- man/i3-sensible-terminal.man | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/i3-sensible-terminal b/i3-sensible-terminal index 4ef1c75f..7ec0f878 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -8,7 +8,7 @@ # We welcome patches that add distribution-specific mechanisms to find the # preferred terminal emulator. On Debian, there is the x-terminal-emulator # symlink for example. -for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal; do +for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix; do if command -v "$terminal" > /dev/null 2>&1; then exec "$terminal" "$@" fi diff --git a/man/i3-sensible-terminal.man b/man/i3-sensible-terminal.man index b830cd09..5facbac9 100644 --- a/man/i3-sensible-terminal.man +++ b/man/i3-sensible-terminal.man @@ -40,6 +40,9 @@ It tries to start one of the following (in that order): * terminology * st * qterminal +* lilyterm +* tilix +* terminix Please don’t complain about the order: If the user has any preference, they will have $TERMINAL set or modified their i3 configuration file. From 4fa87a0c43eb1d1dbee03ddb4de1017f0068e331 Mon Sep 17 00:00:00 2001 From: Nathan Schulte <nmschulte@gmail.com> Date: Sun, 2 Apr 2017 19:26:55 -0500 Subject: [PATCH 064/180] format i3bar src/outputs.c w/ clang-format --- i3bar/src/outputs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index e7cd6eeb..bd056a70 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -190,8 +190,8 @@ static int outputs_end_map_cb(void *params_) { bool handle_output = false; for (int c = 0; c < config.num_outputs; c++) { if (strcasecmp(params->outputs_walk->name, config.outputs[c]) == 0 || - (strcasecmp(config.outputs[c], "primary") == 0 && - params->outputs_walk->primary)) { + (strcasecmp(config.outputs[c], "primary") == 0 && + params->outputs_walk->primary)) { handle_output = true; break; } From 454578b331a8e97cd34df26f753817a6c151a935 Mon Sep 17 00:00:00 2001 From: Nathan Schulte <nmschulte@gmail.com> Date: Fri, 7 Apr 2017 09:59:24 -0500 Subject: [PATCH 065/180] add error check and log for xcb_create_window --- src/xcb.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/xcb.c b/src/xcb.c index 726d1e89..bdfb08bc 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -28,16 +28,21 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, visual = XCB_COPY_FROM_PARENT; } - xcb_create_window(conn, - depth, - result, /* the window id */ - root, /* parent == root */ - dims.x, dims.y, dims.width, dims.height, /* dimensions */ - 0, /* border = 0, we draw our own */ - window_class, - visual, - mask, - values); + xcb_void_cookie_t gc_cookie = xcb_create_window(conn, + depth, + result, /* the window id */ + root, /* parent == root */ + dims.x, dims.y, dims.width, dims.height, /* dimensions */ + 0, /* border = 0, we draw our own */ + window_class, + visual, + mask, + values); + + xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie); + if (error != NULL) { + ELOG("Could not create window. Error code: %d.\n", error->error_code); + } /* Set the cursor */ if (xcursor_supported) { From 52ce8c803d424f395874fa87992eea0cc9f061d3 Mon Sep 17 00:00:00 2001 From: Mihai Coman <mihai.cmn@gmail.com> Date: Thu, 27 Apr 2017 17:50:55 +0300 Subject: [PATCH 066/180] Fix changing of root workspace layout from stacked/tabbed --- src/con.c | 12 +- src/tree.c | 6 +- testcases/t/167-workspace_layout.t | 231 +++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+), 6 deletions(-) diff --git a/src/con.c b/src/con.c index b3f193e6..7b831b36 100644 --- a/src/con.c +++ b/src/con.c @@ -1639,12 +1639,14 @@ void con_set_layout(Con *con, layout_t layout) { * whole workspace into stacked/tabbed mode. To do this and still allow * intuitive operations (like level-up and then opening a new window), we * need to create a new split container. */ - if (con->type == CT_WORKSPACE && - (layout == L_STACKED || layout == L_TABBED)) { + if (con->type == CT_WORKSPACE) { if (con_num_children(con) == 0) { - DLOG("Setting workspace_layout to %d\n", layout); - con->workspace_layout = layout; - } else { + layout_t ws_layout = (layout == L_STACKED || layout == L_TABBED) ? layout : L_DEFAULT; + DLOG("Setting workspace_layout to %d\n", ws_layout); + con->workspace_layout = ws_layout; + DLOG("Setting layout to %d\n", layout); + con->layout = layout; + } else if (layout == L_STACKED || layout == L_TABBED) { DLOG("Creating new split container\n"); /* 1: create a new split container */ Con *new = con_new(NULL, NULL); diff --git a/src/tree.c b/src/tree.c index d2fe4e07..2d4647f8 100644 --- a/src/tree.c +++ b/src/tree.c @@ -383,7 +383,11 @@ void tree_split(Con *con, orientation_t orientation) { if (con->type == CT_WORKSPACE) { if (con_num_children(con) < 2) { - DLOG("Just changing orientation of workspace\n"); + if (con_num_children(con) == 0) { + DLOG("Changing workspace_layout to L_DEFAULT\n"); + con->workspace_layout = L_DEFAULT; + } + DLOG("Changing orientation of workspace\n"); con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV; return; } else { diff --git a/testcases/t/167-workspace_layout.t b/testcases/t/167-workspace_layout.t index 033a31f2..1ed09990 100644 --- a/testcases/t/167-workspace_layout.t +++ b/testcases/t/167-workspace_layout.t @@ -145,6 +145,237 @@ is($x->input_focus, $second->id, 'second window focused'); ok(@content == 1, 'one con at workspace level'); is($content[0]->{layout}, 'stacked', 'layout stacked'); +##################################################################### +# 8: when the workspace is empty check that its layout can be changed +# from stacked to horizontal split using the 'layout splith' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout stacked'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'stacked', 'layout stacked'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'layout splith'; +$first = open_window; +$second = open_window; +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'stacked', 'layout not stacked'); +isnt($content[1]->{layout}, 'stacked', 'layout not stacked'); + +##################################################################### +# 9: when the workspace is empty check that its layout can be changed +# from stacked to vertical split using the 'layout splitv' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout stacked'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'stacked', 'layout stacked'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'layout splitv'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'stacked', 'layout not stacked'); +isnt($content[1]->{layout}, 'stacked', 'layout not stacked'); + +##################################################################### +# 10: when the workspace is empty check that its layout can be changed +# from tabbed to horizontal split using the 'layout splith' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout tabbed'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'tabbed', 'layout tabbed'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'layout splith'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed'); +isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed'); + +##################################################################### +# 11: when the workspace is empty check that its layout can be changed +# from tabbed to vertical split using the 'layout splitv' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout tabbed'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'tabbed', 'layout tabbed'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'layout splitv'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed'); +isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed'); + +##################################################################### +# 12: when the workspace is empty check that its layout can be changed +# from stacked to horizontal split using the 'split horizontal' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout stacked'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'stacked', 'layout stacked'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'split horizontal'; +$first = open_window; +$second = open_window; +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'stacked', 'layout not stacked'); +isnt($content[1]->{layout}, 'stacked', 'layout not stacked'); + +##################################################################### +# 13: when the workspace is empty check that its layout can be changed +# from stacked to vertical split using the 'split vertical' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout stacked'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'stacked', 'layout stacked'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'split vertical'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'stacked', 'layout not stacked'); +isnt($content[1]->{layout}, 'stacked', 'layout not stacked'); + +##################################################################### +# 14: when the workspace is empty check that its layout can be changed +# from tabbed to horizontal split using the 'split horizontal' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout tabbed'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'tabbed', 'layout tabbed'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'split horizontal'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed'); +isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed'); + +##################################################################### +# 15: when the workspace is empty check that its layout can be changed +# from tabbed to vertical split using the 'split vertical' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout tabbed'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'tabbed', 'layout tabbed'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'split vertical'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed'); +isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed'); + + exit_gracefully($pid); done_testing; From 32175b0a1f2db722a0adc149f1ddc3cbef1f9553 Mon Sep 17 00:00:00 2001 From: Sebastian Larsson <sjereq@gmail.com> Date: Sun, 30 Apr 2017 14:40:04 +0200 Subject: [PATCH 067/180] Fix i3-dmenu-desktop quoted command name According to the Desktop Entry Specification https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables the executable name or path of the executable may be quoted. This is not properly respected when i3-dmenu-desktop extracts the command name from the Exec entry. Examples of values that fail and what they currently result in: - "bar" -> "bar" - "foo/bar" -> bar" - "foo foobar/bar" -> "foo - "foo\sbar" -> "foo\sbar" - foo\sbar -> foo\sbar - "foo\\\\bar" -> "foo\\\\bar" --- i3-dmenu-desktop | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/i3-dmenu-desktop b/i3-dmenu-desktop index 3b81cb20..1d29a69f 100755 --- a/i3-dmenu-desktop +++ b/i3-dmenu-desktop @@ -282,7 +282,31 @@ for my $app (keys %apps) { } if ((scalar grep { $_ eq 'command' } @entry_types) > 0) { - my ($command) = split(' ', $apps{$app}->{Exec}); + my $command = $apps{$app}->{Exec}; + + # Handle escape sequences (should be done for all string values, but does + # matter here). + my %escapes = ( + '\\s' => ' ', + '\\n' => '\n', + '\\t' => '\t', + '\\r' => '\r', + '\\\\' => '\\', + ); + $command =~ s/(\\[sntr\\])/$escapes{$1}/go; + + # Extract executable + if ($command =~ m/^\s*([^\s\"]+)(?:\s|$)/) { + # No quotes + $command = $1; + } elsif ($command =~ m/^\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"(?:\s|$)/) { + # Quoted, remove quotes and fix escaped characters + $command = $1; + $command =~ s/\\([\"\`\$\\])/$1/g; + } else { + # Invalid quotes, fallback to whitespace + ($command) = split(' ', $command); + } # Don’t add “geany” if “Geany” is already present. my @keys = map { lc } keys %choices; From d78fd8d91fa714dec5288322098dec6410927f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Tue, 2 May 2017 09:08:42 +0200 Subject: [PATCH 068/180] Introduce --exclude-titlebar flag for mouse bindings. (#2703) This introduces the flag --exclude-titlebar for mouse bindings which allows bindings like bindsym --whole-window --border --exclude-titlebar button3 focus fixes #2347 --- docs/userguide | 9 ++++++--- include/bindings.h | 3 ++- include/config_directives.h | 4 ++-- include/data.h | 4 ++++ parser-specs/config.spec | 12 ++++++++++-- src/bindings.c | 4 +++- src/click.c | 2 +- src/config_directives.c | 8 ++++---- testcases/t/201-config-parser.t | 20 ++++++++++++-------- 9 files changed, 44 insertions(+), 22 deletions(-) diff --git a/docs/userguide b/docs/userguide index 4946a1ba..d2ad7d41 100644 --- a/docs/userguide +++ b/docs/userguide @@ -412,9 +412,9 @@ button in the scope of the clicked container (see <<command_criteria>>). You can configure mouse bindings in a similar way to key bindings. *Syntax*: -------------------------------------------------------------------------------- -bindsym [--release] [--border] [--whole-window] [<Modifiers>+]button<n> command -------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------------------- +bindsym [--release] [--border] [--whole-window] [--exclude-titlebar] [<Modifiers>+]button<n> command +---------------------------------------------------------------------------------------------------- By default, the binding will only run when you click on the titlebar of the window. If the +--release+ flag is given, it will run when the mouse button @@ -424,6 +424,9 @@ If the +--whole-window+ flag is given, the binding will also run when any part of the window is clicked, with the exception of the border. To have a bind run when the border is clicked, specify the +--border+ flag. +If the +--exclude-titlebar+ flag is given, the titlebar will not be considered +for the keybinding. + *Examples*: -------------------------------- # The middle button over a titlebar kills the window diff --git a/include/bindings.h b/include/bindings.h index 0fcc4df4..df3c32a5 100644 --- a/include/bindings.h +++ b/include/bindings.h @@ -27,7 +27,8 @@ extern const char *DEFAULT_BINDING_MODE; */ Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code, const char *release, const char *border, const char *whole_window, - const char *command, const char *mode, bool pango_markup); + const char *exclude_titlebar, const char *command, const char *mode, + bool pango_markup); /** * Grab the bound keys (tell X to send us keypress events for those keycodes) diff --git a/include/config_directives.h b/include/config_directives.h index 0bf52168..f35666f3 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -67,10 +67,10 @@ CFGFUN(color_single, const char *colorclass, const char *color); CFGFUN(floating_modifier, const char *modifiers); CFGFUN(new_window, const char *windowtype, const char *border, const long width); CFGFUN(workspace, const char *workspace, const char *output); -CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command); +CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command); CFGFUN(enter_mode, const char *pango_markup, const char *mode); -CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command); +CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command); CFGFUN(bar_font, const char *font); CFGFUN(bar_separator_symbol, const char *separator); diff --git a/include/data.h b/include/data.h index 3d67e315..69a79ade 100644 --- a/include/data.h +++ b/include/data.h @@ -298,6 +298,10 @@ struct Binding { * title bar (default). */ bool whole_window; + /** If this is true for a mouse binding, the binding should only be + * executed if the button press was not on the titlebar. */ + bool exclude_titlebar; + /** Keycode to bind */ uint32_t keycode; diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 19e2d21a..53828221 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -321,6 +321,8 @@ state BINDING: -> whole_window = '--whole-window' -> + exclude_titlebar = '--exclude-titlebar' + -> modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod' -> '+' @@ -335,8 +337,10 @@ state BINDCOMMAND: -> whole_window = '--whole-window' -> + exclude_titlebar = '--exclude-titlebar' + -> command = string - -> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $command) + -> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command) ################################################################################ # Mode configuration @@ -376,6 +380,8 @@ state MODE_BINDING: -> whole_window = '--whole-window' -> + exclude_titlebar = '--exclude-titlebar' + -> modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod' -> '+' @@ -390,8 +396,10 @@ state MODE_BINDCOMMAND: -> whole_window = '--whole-window' -> + exclude_titlebar = '--exclude-titlebar' + -> command = string - -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $command); MODE + -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command); MODE ################################################################################ # Bar configuration (i3bar) diff --git a/src/bindings.c b/src/bindings.c index bfec27e1..36bcc5d1 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -56,12 +56,14 @@ static struct Mode *mode_from_name(const char *name, bool pango_markup) { */ Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code, const char *release, const char *border, const char *whole_window, - const char *command, const char *modename, bool pango_markup) { + const char *exclude_titlebar, const char *command, const char *modename, + bool pango_markup) { Binding *new_binding = scalloc(1, sizeof(Binding)); DLOG("Binding %p bindtype %s, modifiers %s, input code %s, release %s\n", new_binding, bindtype, modifiers, input_code, release); new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS); new_binding->border = (border != NULL); new_binding->whole_window = (whole_window != NULL); + new_binding->exclude_titlebar = (exclude_titlebar != NULL); if (strcmp(bindtype, "bindsym") == 0) { new_binding->input_type = (strncasecmp(input_code, "button", (sizeof("button") - 1)) == 0 ? B_MOUSE diff --git a/src/click.c b/src/click.c index e989b88d..e5cdc8b2 100644 --- a/src/click.c +++ b/src/click.c @@ -186,7 +186,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod if (dest == CLICK_DECORATION || dest == CLICK_INSIDE || dest == CLICK_BORDER) { Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event); - if (bind != NULL && (dest == CLICK_DECORATION || + if (bind != NULL && ((dest == CLICK_DECORATION && !bind->exclude_titlebar) || (dest == CLICK_INSIDE && bind->whole_window) || (dest == CLICK_BORDER && bind->border))) { CommandResult *result = run_binding(bind, con); diff --git a/src/config_directives.c b/src/config_directives.c index 879d225e..7ca6e102 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -106,8 +106,8 @@ CFGFUN(font, const char *font) { font_pattern = sstrdup(font); } -CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) { - configure_binding(bindtype, modifiers, key, release, border, whole_window, command, DEFAULT_BINDING_MODE, false); +CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) { + configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, DEFAULT_BINDING_MODE, false); } /******************************************************************************* @@ -117,8 +117,8 @@ CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, co static char *current_mode; static bool current_mode_pango_markup; -CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) { - configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode, current_mode_pango_markup); +CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) { + configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, current_mode, current_mode_pango_markup); } CFGFUN(enter_mode, const char *pango_markup, const char *modename) { diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 1de86c65..159de046 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -49,18 +49,22 @@ mode "meh" { bindsym --release --whole-window button3 nop bindsym --border button3 nop bindsym --release --border button3 nop + bindsym --exclude-titlebar button3 nop + bindsym --whole-window --border --exclude-titlebar button3 nop } EOT my $expected = <<'EOT'; cfg_enter_mode((null), meh) -cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), (null), resize grow) -cfg_mode_binding(bindcode, Mod1, 44, (null), (null), (null), resize shrink) -cfg_mode_binding(bindsym, Mod1, x, --release, (null), (null), exec foo) -cfg_mode_binding(bindsym, (null), button3, (null), (null), --whole-window, nop) -cfg_mode_binding(bindsym, (null), button3, --release, (null), --whole-window, nop) -cfg_mode_binding(bindsym, (null), button3, (null), --border, (null), nop) -cfg_mode_binding(bindsym, (null), button3, --release, --border, (null), nop) +cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), (null), (null), resize grow) +cfg_mode_binding(bindcode, Mod1, 44, (null), (null), (null), (null), resize shrink) +cfg_mode_binding(bindsym, Mod1, x, --release, (null), (null), (null), exec foo) +cfg_mode_binding(bindsym, (null), button3, (null), (null), --whole-window, (null), nop) +cfg_mode_binding(bindsym, (null), button3, --release, (null), --whole-window, (null), nop) +cfg_mode_binding(bindsym, (null), button3, (null), --border, (null), (null), nop) +cfg_mode_binding(bindsym, (null), button3, --release, --border, (null), (null), nop) +cfg_mode_binding(bindsym, (null), button3, (null), (null), (null), --exclude-titlebar, nop) +cfg_mode_binding(bindsym, (null), button3, (null), --border, --whole-window, --exclude-titlebar, nop) EOT is(parser_calls($config), @@ -674,7 +678,7 @@ EOT $expected = <<'EOT'; cfg_enter_mode((null), yo) -cfg_mode_binding(bindsym, (null), x, (null), (null), (null), resize shrink left) +cfg_mode_binding(bindsym, (null), x, (null), (null), (null), (null), resize shrink left) ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', '}' ERROR: CONFIG: (in file <stdin>) ERROR: CONFIG: Line 1: mode "yo" { From c826fc0e44c2fccb5ffc4c70c761cdf6807e9920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Tue, 2 May 2017 09:11:35 +0200 Subject: [PATCH 069/180] Query workspaces again in i3bar when an output change occured. (#2760) As of 2f0f8b1, i3bar will properly clean up on output change events. However, this requires us to query the workspaces again to avoid a display error in i3bar. fixes #2740 fixes #2743 --- i3bar/src/ipc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 459684ef..042e230a 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -79,6 +79,10 @@ void got_output_reply(char *reply) { kick_tray_clients(o_walk); } + if (!config.disable_ws) { + i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); + } + draw_bars(false); } From a3c6a89e6e1d7b14d8df80d19fe4d1246d8dc447 Mon Sep 17 00:00:00 2001 From: Maarten Dirkse <mdirkse@bol.com> Date: Tue, 2 May 2017 13:12:25 +0200 Subject: [PATCH 070/180] Change golang ipc lib reference to one that is maintained. --- docs/ipc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ipc b/docs/ipc index 026e434d..7268c2ed 100644 --- a/docs/ipc +++ b/docs/ipc @@ -861,7 +861,7 @@ C:: C++:: * https://github.com/drmgc/i3ipcpp Go:: - * https://github.com/proxypoke/i3ipc + * https://github.com/mdirkse/i3ipc-go JavaScript:: * https://github.com/acrisci/i3ipc-gjs Lua:: From e428bf02fb071b74df41adf8b7ac463afabca2fd Mon Sep 17 00:00:00 2001 From: Maarten Dirkse <mdirkse@bol.com> Date: Thu, 4 May 2017 00:36:44 +0200 Subject: [PATCH 071/180] Fix invalid JSON --- docs/ipc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ipc b/docs/ipc index 026e434d..2e223801 100644 --- a/docs/ipc +++ b/docs/ipc @@ -264,7 +264,7 @@ rect (map):: "y": 0, "width": 1280, "height": 1024 - }, + } } ] ------------------- @@ -392,7 +392,7 @@ JSON dump: "y": 0, "width": 1280, "height": 0 - }, + } }, { From b56cb84e16a342766d9a0b43e74bdadca22931bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Thu, 4 May 2017 23:08:51 +0200 Subject: [PATCH 072/180] Added a hint about the required config v4 version hint. (#2759) fixes #2751 --- docs/userguide | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/userguide b/docs/userguide index d2ad7d41..068ecd87 100644 --- a/docs/userguide +++ b/docs/userguide @@ -297,6 +297,15 @@ keyboard layout. To start the wizard, use the command +i3-config-wizard+. Please note that you must not have +~/.i3/config+, otherwise the wizard will exit. +Since i3 4.0, a new configuration format is used. i3 will try to automatically +detect the format version of a config file based on a few different keywords, +but if you want to make sure that your config is read with the new format, +include the following line in your config file: + +--------------------- +# i3 config file (v4) +--------------------- + === Comments It is possible and recommended to use comments in your configuration file to From 9bc504ebdb92e56e1a34047f8ab7c70f6b67eb90 Mon Sep 17 00:00:00 2001 From: Nathan Schulte <nmschulte@gmail.com> Date: Fri, 7 Apr 2017 09:59:24 -0500 Subject: [PATCH 073/180] add error check and log for xcb_create_window --- src/xcb.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/xcb.c b/src/xcb.c index 726d1e89..bdfb08bc 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -28,16 +28,21 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, visual = XCB_COPY_FROM_PARENT; } - xcb_create_window(conn, - depth, - result, /* the window id */ - root, /* parent == root */ - dims.x, dims.y, dims.width, dims.height, /* dimensions */ - 0, /* border = 0, we draw our own */ - window_class, - visual, - mask, - values); + xcb_void_cookie_t gc_cookie = xcb_create_window(conn, + depth, + result, /* the window id */ + root, /* parent == root */ + dims.x, dims.y, dims.width, dims.height, /* dimensions */ + 0, /* border = 0, we draw our own */ + window_class, + visual, + mask, + values); + + xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie); + if (error != NULL) { + ELOG("Could not create window. Error code: %d.\n", error->error_code); + } /* Set the cursor */ if (xcursor_supported) { From 2fe9d7bbd262f663e91f27b1876d26224950b2b2 Mon Sep 17 00:00:00 2001 From: Max Fisher <f.mach4@gmail.com> Date: Sun, 7 May 2017 23:40:19 +1000 Subject: [PATCH 074/180] i3-nagbar: add button flag to execute action with /bin/sh directly. Fixes #2765. --- i3-nagbar/main.c | 116 +++++++++++++++++++++++++++------------------- man/i3-nagbar.man | 14 ++++-- 2 files changed, 78 insertions(+), 52 deletions(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 7d38f731..6ec4294f 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -50,6 +50,7 @@ static char *argv0 = NULL; typedef struct { i3String *label; char *action; + bool exec_in_terminal; int16_t x; uint16_t width; } button_t; @@ -123,6 +124,51 @@ static void start_application(const char *command) { wait(0); } +static void execute_in_terminal(const char *command) { + /* We need to create a custom script containing our actual command + * since not every terminal emulator which is contained in + * i3-sensible-terminal supports -e with multiple arguments (and not + * all of them support -e with one quoted argument either). + * + * NB: The paths need to be unique, that is, don’t assume users close + * their nagbars at any point in time (and they still need to work). + * */ + char *script_path = get_process_filename("nagbar-cmd"); + + int fd = open(script_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd == -1) { + warn("Could not create temporary script to store the nagbar command"); + return; + } + FILE *script = fdopen(fd, "w"); + if (script == NULL) { + warn("Could not fdopen() temporary script to store the nagbar command"); + return; + } + fprintf(script, "#!/bin/sh\nrm %s\n%s", script_path, command); + /* Also closes fd */ + fclose(script); + + char *link_path; + char *exe_path = get_exe_path(argv0); + sasprintf(&link_path, "%s.nagbar_cmd", script_path); + if (symlink(exe_path, link_path) == -1) { + err(EXIT_FAILURE, "Failed to symlink %s to %s", link_path, exe_path); + } + + char *terminal_cmd; + sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path); + printf("argv0 = %s\n", argv0); + printf("terminal_cmd = %s\n", terminal_cmd); + + start_application(terminal_cmd); + + free(link_path); + free(terminal_cmd); + free(script_path); + free(exe_path); +} + static button_t *get_button_at(int16_t x, int16_t y) { for (int c = 0; c < buttoncnt; c++) if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width)) @@ -149,51 +195,15 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width) exit(0); button_t *button = get_button_at(event->event_x, event->event_y); - if (!button) - return; - - /* We need to create a custom script containing our actual command - * since not every terminal emulator which is contained in - * i3-sensible-terminal supports -e with multiple arguments (and not - * all of them support -e with one quoted argument either). - * - * NB: The paths need to be unique, that is, don’t assume users close - * their nagbars at any point in time (and they still need to work). - * */ - char *script_path = get_process_filename("nagbar-cmd"); - - int fd = open(script_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); - if (fd == -1) { - warn("Could not create temporary script to store the nagbar command"); + if (!button) { return; } - FILE *script = fdopen(fd, "w"); - if (script == NULL) { - warn("Could not fdopen() temporary script to store the nagbar command"); - return; + + if (button->exec_in_terminal) { + execute_in_terminal(button->action); + } else { + start_application(button->action); } - fprintf(script, "#!/bin/sh\nrm %s\n%s", script_path, button->action); - /* Also closes fd */ - fclose(script); - - char *link_path; - char *exe_path = get_exe_path(argv0); - sasprintf(&link_path, "%s.nagbar_cmd", script_path); - if (symlink(exe_path, link_path) == -1) { - err(EXIT_FAILURE, "Failed to symlink %s to %s", link_path, exe_path); - } - - char *terminal_cmd; - sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path); - printf("argv0 = %s\n", argv0); - printf("terminal_cmd = %s\n", terminal_cmd); - - start_application(terminal_cmd); - - free(link_path); - free(terminal_cmd); - free(script_path); - free(exe_path); /* TODO: unset flag, re-render */ } @@ -358,12 +368,13 @@ int main(int argc, char *argv[]) { {"version", no_argument, 0, 'v'}, {"font", required_argument, 0, 'f'}, {"button", required_argument, 0, 'b'}, + {"button-sh", required_argument, 0, 'B'}, {"help", no_argument, 0, 'h'}, {"message", required_argument, 0, 'm'}, {"type", required_argument, 0, 't'}, {0, 0, 0, 0}}; - char *options_string = "b:f:m:t:vh"; + char *options_string = "B:b:f:m:t:vh"; prompt = i3string_from_utf8("Please do not run this program."); @@ -385,15 +396,26 @@ int main(int argc, char *argv[]) { break; case 'h': printf("i3-nagbar " I3_VERSION "\n"); - printf("i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v]\n"); + printf("i3-nagbar [-m <message>] [-b <button> <terminal-action>] " + "[-B <button> <shell-action> [-t warning|error] [-f <font>] [-v]\n"); return 0; + /* falls through */ + case 'B': case 'b': buttons = srealloc(buttons, sizeof(button_t) * (buttoncnt + 1)); buttons[buttoncnt].label = i3string_from_utf8(optarg); buttons[buttoncnt].action = argv[optind]; - printf("button with label *%s* and action *%s*\n", - i3string_as_utf8(buttons[buttoncnt].label), - buttons[buttoncnt].action); + if (o == 'b') { + buttons[buttoncnt].exec_in_terminal = true; + printf("button with label *%s* and terminal action *%s*\n", + i3string_as_utf8(buttons[buttoncnt].label), + buttons[buttoncnt].action); + } else { + buttons[buttoncnt].exec_in_terminal = false; + printf("button with label *%s* and shell action *%s*\n", + i3string_as_utf8(buttons[buttoncnt].label), + buttons[buttoncnt].action); + } buttoncnt++; printf("now %d buttons\n", buttoncnt); if (optind < argc) diff --git a/man/i3-nagbar.man b/man/i3-nagbar.man index 77fdd80b..7dd3a238 100644 --- a/man/i3-nagbar.man +++ b/man/i3-nagbar.man @@ -9,7 +9,7 @@ i3-nagbar - displays an error bar on top of your screen == SYNOPSIS -i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v] +i3-nagbar [-m <message>] [-b <button> <terminal-action>] [-B <button> <shell-action>] [-t warning|error] [-f <font>] [-v] == OPTIONS @@ -29,9 +29,13 @@ Display 'message' as text on the left of the i3-nagbar. *-f, --font* 'font':: Select font that is being used. -*-b, --button* 'button' 'action':: -Create a button with text 'button'. The 'action' are the shell commands that -will be executed by this button. Multiple buttons can be defined. +*-b, --button* 'button' 'terminal-action':: +Adds a button labelled 'button' to the bar. When pressed, the command given +in 'terminal-action' is executed inside a terminal emulator, via i3-sensible-terminal(1). +Multiple buttons can be defined. + +*-B, --button-sh* 'button' 'shell-action':: +Same as *--button*, except that the command given in 'shell-action' is executed directly by the shell. == DESCRIPTION @@ -49,7 +53,7 @@ i3-nagbar -m 'You have an error in your i3 config file!' \ == SEE ALSO -i3(1) +i3(1), i3-sensible-terminal(1) == AUTHOR From 9178c5b8ca2b5c09ba96397ce3303d552125ede4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <admin@airblader.de> Date: Fri, 12 May 2017 19:26:35 +0200 Subject: [PATCH 075/180] Force container to be redrawn upon moving to another container. (#2769) This is necessary as otherwise urgent containers can be broken after moving them. --- src/con.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/con.c b/src/con.c index 7b831b36..86412596 100644 --- a/src/con.c +++ b/src/con.c @@ -1127,6 +1127,9 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi con_set_urgency(con, true); } + /* Ensure the container will be redrawn. */ + FREE(con->deco_render_params); + CALL(parent, on_remove_child); ipc_send_window_event("move", con); From 838616a1657ebe0906444307dc42fc5063d800d6 Mon Sep 17 00:00:00 2001 From: loungecube <cube@hannen.at> Date: Mon, 15 May 2017 05:02:09 +0200 Subject: [PATCH 076/180] prevent multiple menu items per .desktop entry If a .desktop entry is being added to the list of Menu items by its "Name" field, it should not be added again by its command or filename. If it is being added by its command, it should not be added again by its filename. --- i3-dmenu-desktop | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/i3-dmenu-desktop b/i3-dmenu-desktop index 1d29a69f..aee5dc2f 100755 --- a/i3-dmenu-desktop +++ b/i3-dmenu-desktop @@ -279,6 +279,7 @@ for my $app (keys %apps) { } $choices{$name} = $app; + next; } if ((scalar grep { $_ eq 'command' } @entry_types) > 0) { @@ -310,9 +311,10 @@ for my $app (keys %apps) { # Don’t add “geany” if “Geany” is already present. my @keys = map { lc } keys %choices; - next if (scalar grep { $_ eq lc(basename($command)) } @keys) > 0; - - $choices{basename($command)} = $app; + if (!(scalar grep { $_ eq lc(basename($command)) } @keys) > 0) { + $choices{basename($command)} = $app; + } + next; } if ((scalar grep { $_ eq 'filename' } @entry_types) > 0) { From 5f9a5e8d7d0eb3123fd7068452090b72b9096bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= <ingo.buerk@tngtech.com> Date: Fri, 26 Feb 2016 22:39:44 +0100 Subject: [PATCH 077/180] Implement 'swap' command. This patch introduces a new command 'swap' that swaps two containers so that they assume each other's position and geometry. fixes #917 --- docs/userguide | 33 +++ include/commands.h | 6 + include/con.h | 12 + include/util.h | 7 + parser-specs/commands.spec | 16 ++ src/bindings.c | 24 +- src/commands.c | 59 +++++ src/con.c | 232 +++++++++++++++-- src/match.c | 16 +- src/util.c | 17 ++ src/x.c | 5 +- testcases/t/187-commands-parser.t | 1 + testcases/t/265-swap.t | 412 ++++++++++++++++++++++++++++++ 13 files changed, 794 insertions(+), 46 deletions(-) create mode 100644 testcases/t/265-swap.t diff --git a/docs/userguide b/docs/userguide index 4946a1ba..9e0a6f00 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1976,6 +1976,39 @@ bindsym $mod+c move absolute position center bindsym $mod+m move position mouse ------------------------------------------------------- +=== Swapping containers + +Two containers can be swapped (i.e., move to each other's position) by using +the +swap+ command. They will assume the position and geometry of the container +they are swapped with. + +The first container to participate in the swapping can be selected through the +normal command criteria process with the focused window being the usual +fallback if no criteria are specified. The second container can be selected +using one of the following methods: + ++id+:: The X11 window ID of a client window. ++con_id+:: The i3 container ID of a container. ++mark+:: A container with the specified mark, see <<vim_like_marks>>. + +Note that swapping does not work with all containers. Most notably, swapping +floating containers or containers that have a parent-child relationship to one +another does not work. + +*Syntax*: +---------------------------------------- +swap container with id|con_id|mark <arg> +---------------------------------------- + +*Examples*: +----------------------------------------------------------------- +# Swaps the focused container with the container marked »swapee«. +swap container with mark swapee + +# Swaps container marked »A« and »B« +[con_mark="^A$"] swap container with mark B +----------------------------------------------------------------- + === Sticky floating windows If you want a window to stick to the glass, i.e., have it stay on screen even diff --git a/include/commands.h b/include/commands.h index a57b2925..9780f788 100644 --- a/include/commands.h +++ b/include/commands.h @@ -290,6 +290,12 @@ void cmd_move_scratchpad(I3_CMD); */ void cmd_scratchpad_show(I3_CMD); +/** + * Implementation of 'swap [container] [with] id|con_id|mark <arg>'. + * + */ +void cmd_swap(I3_CMD, const char *mode, const char *arg); + /** * Implementation of 'title_format <format>' * diff --git a/include/con.h b/include/con.h index 0c532207..0fa660a1 100644 --- a/include/con.h +++ b/include/con.h @@ -139,6 +139,12 @@ Con *con_inside_floating(Con *con); */ bool con_inside_focused(Con *con); +/** + * Checks if the container has the given parent as an actual parent. + * + */ +bool con_has_parent(Con *con, Con *parent); + /** * Returns the container with the given client window ID or NULL if no such * container exists. @@ -461,3 +467,9 @@ void con_force_split_parents_redraw(Con *con); * */ i3String *con_parse_title_format(Con *con); + +/** + * Swaps the two containers. + * + */ +bool con_swap(Con *first, Con *second); diff --git a/include/util.h b/include/util.h index cbe9778c..6c5fc4c6 100644 --- a/include/util.h +++ b/include/util.h @@ -157,3 +157,10 @@ void start_nagbar(pid_t *nagbar_pid, char *argv[]); * */ void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it); + +/** + * Converts a string into a long using strtol(). + * This is a convenience wrapper checking the parsing result. It returns true + * if the number could be parsed. + */ +bool parse_long(const char *str, long *out, int base); diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 542ff798..0858322b 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -38,6 +38,7 @@ state INITIAL: 'rename' -> RENAME 'nop' -> NOP 'scratchpad' -> SCRATCHPAD + 'swap' -> SWAP 'title_format' -> TITLE_FORMAT 'mode' -> MODE 'bar' -> BAR @@ -406,6 +407,21 @@ state SCRATCHPAD: 'show' -> call cmd_scratchpad_show() +# swap [container] [with] id <window> +# swap [container] [with] con_id <con_id> +# swap [container] [with] mark <mark> +state SWAP: + 'container' + -> + 'with' + -> + mode = 'id', 'con_id', 'mark' + -> SWAP_ARGUMENT + +state SWAP_ARGUMENT: + arg = string + -> call cmd_swap($mode, $arg) + state TITLE_FORMAT: format = string -> call cmd_title_format($format) diff --git a/src/bindings.c b/src/bindings.c index bfec27e1..f91f7959 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -69,15 +69,15 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch new_binding->symbol = sstrdup(input_code); } else { - char *endptr; - long keycode = strtol(input_code, &endptr, 10); - new_binding->keycode = keycode; - new_binding->input_type = B_KEYBOARD; - if (keycode == LONG_MAX || keycode == LONG_MIN || keycode < 0 || *endptr != '\0' || endptr == input_code) { + long keycode; + if (!parse_long(input_code, &keycode, 10)) { ELOG("Could not parse \"%s\" as an input code, ignoring this binding.\n", input_code); FREE(new_binding); return NULL; } + + new_binding->keycode = keycode; + new_binding->input_type = B_KEYBOARD; } new_binding->command = sstrdup(command); new_binding->event_state_mask = event_state_from_str(modifiers); @@ -459,13 +459,12 @@ void translate_keysyms(void) { Binding *bind; TAILQ_FOREACH(bind, bindings, bindings) { if (bind->input_type == B_MOUSE) { - char *endptr; - long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10); - bind->keycode = button; - - if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol) + long button; + if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) { ELOG("Could not translate string to button: \"%s\"\n", bind->symbol); + } + bind->keycode = button; continue; } @@ -974,9 +973,8 @@ int *bindings_get_buttons_to_grab(void) { if (bind->input_type != B_MOUSE || !bind->whole_window) continue; - char *endptr; - long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10); - if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol) { + long button; + if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) { ELOG("Could not parse button number, skipping this binding. Please report this bug in i3.\n"); continue; } diff --git a/src/commands.c b/src/commands.c index 33737f71..393d7018 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1812,6 +1812,65 @@ void cmd_scratchpad_show(I3_CMD) { ysuccess(true); } +/* + * Implementation of 'swap [container] [with] id|con_id|mark <arg>'. + * + */ +void cmd_swap(I3_CMD, const char *mode, const char *arg) { + HANDLE_EMPTY_MATCH; + + owindow *match = TAILQ_FIRST(&owindows); + if (match == NULL) { + DLOG("No match found for swapping.\n"); + return; + } + + Con *con; + if (strcmp(mode, "id") == 0) { + long target; + if (!parse_long(arg, &target, 0)) { + yerror("Failed to parse %s into a window id.\n", arg); + return; + } + + con = con_by_window_id(target); + } else if (strcmp(mode, "con_id") == 0) { + long target; + if (!parse_long(arg, &target, 0)) { + yerror("Failed to parse %s into a container id.\n", arg); + return; + } + + con = (Con *)target; + } else if (strcmp(mode, "mark") == 0) { + con = con_by_mark(arg); + } else { + yerror("Unhandled swap mode \"%s\". This is a bug.\n", mode); + return; + } + + if (con == NULL) { + yerror("Could not find container for %s = %s\n", mode, arg); + return; + } + + if (match == TAILQ_LAST(&owindows, owindows_head)) { + DLOG("More than one container matched the swap command, only using the first one."); + } + + if (match->con == NULL) { + DLOG("Match %p has no container.\n", match); + ysuccess(false); + return; + } + + DLOG("Swapping %p with %p.\n", match->con, con); + bool result = con_swap(match->con, con); + + cmd_output->needs_tree_render = true; + ysuccess(result); +} + /* * Implementation of 'title_format <format>' * diff --git a/src/con.c b/src/con.c index b3f193e6..cec3a29b 100644 --- a/src/con.c +++ b/src/con.c @@ -22,9 +22,11 @@ static void con_on_remove_child(Con *con); void con_force_split_parents_redraw(Con *con) { Con *parent = con; - while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { - if (!con_is_leaf(parent)) + while (parent != NULL && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { + if (!con_is_leaf(parent)) { FREE(parent->deco_render_params); + } + parent = parent->parent; } } @@ -145,7 +147,7 @@ static void _con_attach(Con *con, Con *parent, Con *previous, bool ignore_focus) /* Insert the container after the tiling container, if found. * When adding to a CT_OUTPUT, just append one after another. */ - if (current && parent->type != CT_OUTPUT) { + if (current != NULL && parent->type != CT_OUTPUT) { DLOG("Inserting con = %p after con %p\n", con, current); TAILQ_INSERT_AFTER(nodes_head, current, con, nodes); } else @@ -525,6 +527,23 @@ bool con_inside_focused(Con *con) { return con_inside_focused(con->parent); } +/* + * Checks if the container has the given parent as an actual parent. + * + */ +bool con_has_parent(Con *con, Con *parent) { + Con *current = con->parent; + if (current == NULL) { + return false; + } + + if (current == parent) { + return true; + } + + return con_has_parent(current, parent); +} + /* * Returns the container with the given client window ID or NULL if no such * container exists. @@ -803,10 +822,11 @@ void con_fix_percent(Con *con) { if (children_with_percent != children) { TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (child->percent <= 0.0) { - if (children_with_percent == 0) + if (children_with_percent == 0) { total += (child->percent = 1.0); - else + } else { total += (child->percent = total / children_with_percent); + } } } } @@ -814,11 +834,13 @@ void con_fix_percent(Con *con) { // if we got a zero, just distribute the space equally, otherwise // distribute according to the proportions we got if (total == 0.0) { - TAILQ_FOREACH(child, &(con->nodes_head), nodes) - child->percent = 1.0 / children; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + child->percent = 1.0 / children; + } } else if (total != 1.0) { - TAILQ_FOREACH(child, &(con->nodes_head), nodes) - child->percent /= total; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + child->percent /= total; + } } } @@ -944,7 +966,7 @@ void con_disable_fullscreen(Con *con) { con_set_fullscreen_mode(con, CF_NONE); } -static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fix_coordinates, bool dont_warp, bool ignore_focus) { +static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fix_coordinates, bool dont_warp, bool ignore_focus, bool fix_percentage) { Con *orig_target = target; /* Prevent moving if this would violate the fullscreen focus restrictions. */ @@ -1053,9 +1075,11 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi _con_attach(con, target, behind_focused ? NULL : orig_target, !behind_focused); /* 5: fix the percentages */ - con_fix_percent(parent); - con->percent = 0.0; - con_fix_percent(target); + if (fix_percentage) { + con_fix_percent(parent); + con->percent = 0.0; + con_fix_percent(target); + } /* 6: focus the con on the target workspace, but only within that * workspace, that is, don’t move focus away if the target workspace is @@ -1127,6 +1151,9 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi con_set_urgency(con, true); } + /* Ensure the container will be redrawn. */ + FREE(con->deco_render_params); + CALL(parent, on_remove_child); ipc_send_window_event("move", con); @@ -1166,12 +1193,12 @@ bool con_move_to_mark(Con *con, const char *mark) { target = TAILQ_FIRST(&(target->focus_head)); } - if (con == target || con == target->parent) { + if (con == target || con_has_parent(target, con)) { DLOG("cannot move the container to or inside itself, aborting.\n"); return false; } - return _con_move_to_con(con, target, false, true, false, false); + return _con_move_to_con(con, target, false, true, false, false, true); } /* @@ -1204,7 +1231,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool } Con *target = con_descend_focused(workspace); - _con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus); + _con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus, true); } /* @@ -1311,15 +1338,16 @@ Con *con_next_focused(Con *con) { } else { /* try to focus the next container on the same level as this one or fall * back to its parent */ - if (!(next = TAILQ_NEXT(con, focused))) + if (!(next = TAILQ_NEXT(con, focused))) { next = con->parent; + } } /* now go down the focus stack as far as * possible, excluding the current container */ - while (!TAILQ_EMPTY(&(next->focus_head)) && - TAILQ_FIRST(&(next->focus_head)) != con) + while (!TAILQ_EMPTY(&(next->focus_head)) && TAILQ_FIRST(&(next->focus_head)) != con) { next = TAILQ_FIRST(&(next->focus_head)); + } return next; } @@ -2159,3 +2187,169 @@ i3String *con_parse_title_format(Con *con) { return formatted; } + +/* + * Swaps the two containers. + * + */ +bool con_swap(Con *first, Con *second) { + assert(first != NULL); + assert(second != NULL); + DLOG("Swapping containers %p / %p\n", first, second); + + if (first->type != CT_CON) { + ELOG("Only regular containers can be swapped, but found con = %p with type = %d.\n", first, first->type); + return false; + } + + if (second->type != CT_CON) { + ELOG("Only regular containers can be swapped, but found con = %p with type = %d.\n", second, second->type); + return false; + } + + if (con_is_floating(first) || con_is_floating(second)) { + ELOG("Floating windows cannot be swapped.\n"); + return false; + } + + if (first == second) { + DLOG("Swapping container %p with itself, nothing to do.\n", first); + return false; + } + + if (con_has_parent(first, second) || con_has_parent(second, first)) { + ELOG("Cannot swap containers %p and %p because they are in a parent-child relationship.\n", first, second); + return false; + } + + Con *old_focus = focused; + + Con *first_ws = con_get_workspace(first); + Con *second_ws = con_get_workspace(second); + Con *current_ws = con_get_workspace(old_focus); + const bool focused_within_first = (first == old_focus || con_has_parent(old_focus, first)); + const bool focused_within_second = (second == old_focus || con_has_parent(old_focus, second)); + + if (!con_fullscreen_permits_focusing(first_ws)) { + DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", first_ws->name); + return false; + } + + if (!con_fullscreen_permits_focusing(second_ws)) { + DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", second_ws->name); + return false; + } + + double first_percent = first->percent; + double second_percent = second->percent; + + /* De- and reattaching the containers will insert them at the tail of the + * focus_heads. We will need to fix this. But we need to make sure first + * and second don't get in each other's way if they share the same parent, + * so we select the closest previous focus_head that isn't involved. */ + Con *first_prev_focus_head = first; + while (first_prev_focus_head == first || first_prev_focus_head == second) { + first_prev_focus_head = TAILQ_PREV(first_prev_focus_head, focus_head, focused); + } + + Con *second_prev_focus_head = second; + while (second_prev_focus_head == second || second_prev_focus_head == first) { + second_prev_focus_head = TAILQ_PREV(second_prev_focus_head, focus_head, focused); + } + + /* We use a fake container to mark the spot of where the second container needs to go. */ + Con *fake = con_new(NULL, NULL); + fake->layout = L_SPLITH; + _con_attach(fake, first->parent, first, true); + + bool result = true; + /* Swap the containers. We set the ignore_focus flag here because after the + * container is attached, the focus order is not yet correct and would + * result in wrong windows being focused. */ + + /* Move first to second. */ + result &= _con_move_to_con(first, second, false, false, false, true, false); + + /* If we moved the container holding the focused window to another + * workspace we need to ensure the visible workspace has the focused + * container. + * We don't need to check this for the second container because we've only + * moved the first one at this point.*/ + if (first_ws != second_ws && focused_within_first) { + con_focus(con_descend_focused(current_ws)); + } + + /* Move second to where first has been originally. */ + result &= _con_move_to_con(second, fake, false, false, false, true, false); + + /* If swapping the containers didn't work we don't need to mess with the focus. */ + if (!result) { + goto swap_end; + } + + /* Swapping will have inserted the containers at the tail of their parents' + * focus head. We fix this now by putting them in the position of the focus + * head the container they swapped with was in. */ + TAILQ_REMOVE(&(first->parent->focus_head), first, focused); + TAILQ_REMOVE(&(second->parent->focus_head), second, focused); + + if (second_prev_focus_head == NULL) { + TAILQ_INSERT_HEAD(&(first->parent->focus_head), first, focused); + } else { + TAILQ_INSERT_AFTER(&(first->parent->focus_head), second_prev_focus_head, first, focused); + } + + if (first_prev_focus_head == NULL) { + TAILQ_INSERT_HEAD(&(second->parent->focus_head), second, focused); + } else { + TAILQ_INSERT_AFTER(&(second->parent->focus_head), first_prev_focus_head, second, focused); + } + + /* If the focus was within any of the swapped containers, do the following: + * - If swapping took place within a workspace, ensure the previously + * focused container stays focused. + * - Otherwise, focus the container that has been swapped in. + * + * To understand why fixing the focus_head previously wasn't enough, + * consider the scenario + * H[ V[ A X ] V[ Y B ] ] + * with B being focused, but X being the focus_head within its parent. If + * we swap A and B now, fixing the focus_head would focus X, but since B + * was the focused container before it should stay focused. + */ + if (focused_within_first) { + if (first_ws == second_ws) { + con_focus(old_focus); + } else { + con_focus(con_descend_focused(second)); + } + } else if (focused_within_second) { + if (first_ws == second_ws) { + con_focus(old_focus); + } else { + con_focus(con_descend_focused(first)); + } + } + + /* We need to copy each other's percentages to ensure that the geometry + * doesn't change during the swap. This needs to happen _before_ we close + * the fake container as closing the tree will recalculate percentages. */ + first->percent = second_percent; + second->percent = first_percent; + fake->percent = 0.0; + +swap_end: + /* We don't actually need this since percentages-wise we haven't changed + * anything, but we'll better be safe than sorry and just make sure as we'd + * otherwise crash i3. */ + con_fix_percent(first->parent); + con_fix_percent(second->parent); + + /* We can get rid of the fake container again now. */ + con_close(fake, DONT_KILL_WINDOW); + + con_force_split_parents_redraw(first); + con_force_split_parents_redraw(second); + + return result; +} diff --git a/src/match.c b/src/match.c index 9185537b..b3136ab9 100644 --- a/src/match.c +++ b/src/match.c @@ -307,12 +307,8 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { return; } - char *end; - long parsed = strtol(cvalue, &end, 0); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { + long parsed; + if (!parse_long(cvalue, &parsed, 0)) { ELOG("Could not parse con id \"%s\"\n", cvalue); match->error = sstrdup("invalid con_id"); } else { @@ -323,12 +319,8 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { } if (strcmp(ctype, "id") == 0) { - char *end; - long parsed = strtol(cvalue, &end, 0); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { + long parsed; + if (!parse_long(cvalue, &parsed, 0)) { ELOG("Could not parse window id \"%s\"\n", cvalue); match->error = sstrdup("invalid id"); } else { diff --git a/src/util.c b/src/util.c index 06fbea2a..32c3c57e 100644 --- a/src/util.c +++ b/src/util.c @@ -458,3 +458,20 @@ void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it) { * waitpid() here. */ waitpid(*nagbar_pid, NULL, 0); } + +/* + * Converts a string into a long using strtol(). + * This is a convenience wrapper checking the parsing result. It returns true + * if the number could be parsed. + */ +bool parse_long(const char *str, long *out, int base) { + char *end; + long result = strtol(str, &end, base); + if (result == LONG_MIN || result == LONG_MAX || result < 0 || (end != NULL && *end != '\0')) { + *out = result; + return false; + } + + *out = result; + return true; +} diff --git a/src/x.c b/src/x.c index d9a70a92..ee5ed2ce 100644 --- a/src/x.c +++ b/src/x.c @@ -904,8 +904,9 @@ void x_push_node(Con *con) { /* Handle all children and floating windows of this node. We recurse * in focus order to display the focused client in a stack first when * switching workspaces (reduces flickering). */ - TAILQ_FOREACH(current, &(con->focus_head), focused) - x_push_node(current); + TAILQ_FOREACH(current, &(con->focus_head), focused) { + x_push_node(current); + } } /* diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index dc5b67ed..5d3a0ff5 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -169,6 +169,7 @@ is(parser_calls('unknown_literal'), rename nop scratchpad + swap title_format mode bar diff --git a/testcases/t/265-swap.t b/testcases/t/265-swap.t new file mode 100644 index 00000000..f86bba71 --- /dev/null +++ b/testcases/t/265-swap.t @@ -0,0 +1,412 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests the swap command. +# Ticket: #917 +use i3test i3_autostart => 0; + +my $config = <<EOT; +# i3 config file (v4) +font font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +for_window[class="mark_A"] mark A +for_window[class="mark_B"] mark B +EOT + +my ($pid); +my ($ws, $ws1, $ws2, $ws3); +my ($nodes, $expected_focus, $A, $B, $F); +my ($result); +my @urgent; + +############################################################################### +# Swap two containers next to each other. +# Focus should stay on B because both windows are on the focused workspace. +# The focused container is B. +# +# +---+---+ Layout: H1[ A B ] +# | A | B | Focus Stacks: +# +---+---+ H1: B, A +############################################################################### +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$A = open_window(wm_class => 'mark_A'); +$B = open_window(wm_class => 'mark_B'); +$expected_focus = get_focused($ws); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws); +is($nodes->[0]->{window}, $B->{id}, 'B is on the left'); +is($nodes->[1]->{window}, $A->{id}, 'A is on the right'); +is(get_focused($ws), $expected_focus, 'B is still focused'); + +exit_gracefully($pid); + +############################################################################### +# Swap two containers with different parents. +# In this test, the focus head of the left v-split container is A. +# The focused container is B. +# +# +---+---+ Layout: H1[ V1[ A Y ] V2[ X B ] ] +# | A | X | Focus Stacks: +# +---+---+ H1: V2, V1 +# | Y | B | V1: A, Y +# +---+---+ V2: B, X +############################################################################### +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$A = open_window(wm_class => 'mark_A'); +$B = open_window(wm_class => 'mark_B'); +cmd 'split v'; +open_window; +cmd 'move up, focus left'; +cmd 'split v'; +open_window; +cmd 'focus up, focus right, focus down'; +$expected_focus = get_focused($ws); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws); +is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left'); +is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right'); +is(get_focused($ws), $expected_focus, 'B is still focused'); + +exit_gracefully($pid); + +############################################################################### +# Swap two containers with different parents. +# In this test, the focus head of the left v-split container is _not_ A. +# The focused container is B. +# +# +---+---+ Layout: H1[ V1[ A Y ] V2[ X B ] ] +# | A | X | Focus Stacks: +# +---+---+ H1: V2, V1 +# | Y | B | V1: Y, A +# +---+---+ V2: B, X +############################################################################### +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$A = open_window(wm_class => 'mark_A'); +$B = open_window(wm_class => 'mark_B'); +cmd 'split v'; +open_window; +cmd 'move up, focus left'; +cmd 'split v'; +open_window; +cmd 'focus right, focus down'; +$expected_focus = get_focused($ws); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws); +is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left'); +is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right'); +is(get_focused($ws), $expected_focus, 'B is still focused'); + +exit_gracefully($pid); + +############################################################################### +# Swap two containers with one being on a different workspace. +# The focused container is B. +# +# Layout: O1[ W1[ H1 ] W2[ H2 ] ] +# Focus Stacks: +# O1: W2, W1 +# +# +---+---+ Layout: H1[ A X ] +# | A | X | Focus Stacks: +# +---+---+ H1: A, X +# +# +---+---+ Layout: H2[ Y, B ] +# | Y | B | Focus Stacks: +# +---+---+ H2: B, Y +############################################################################### +$pid = launch_with_config($config); + +$ws1 = fresh_workspace; +$A = open_window(wm_class => 'mark_A'); +$expected_focus = get_focused($ws1); +open_window; +cmd 'focus left'; + +$ws2 = fresh_workspace; +open_window; +$B = open_window(wm_class => 'mark_B'); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws1); +is($nodes->[0]->{window}, $B->{id}, 'B is on ws1:left'); + +$nodes = get_ws_content($ws2); +is($nodes->[1]->{window}, $A->{id}, 'A is on ws1:right'); +is(get_focused($ws2), $expected_focus, 'A is focused'); + +exit_gracefully($pid); + +############################################################################### +# Swap two non-focused containers within the same workspace. +# +# +---+---+ Layout: H1[ V1[ A X ] V2[ F B ] ] +# | A | F | Focus Stacks: +# +---+---+ H1: V2, V1 +# | X | B | V1: A, X +# +---+---+ V2: F, B +############################################################################### +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$A = open_window(wm_class => 'mark_A'); +$B = open_window(wm_class => 'mark_B'); +cmd 'split v'; +open_window; +cmd 'move up, focus left'; +cmd 'split v'; +open_window; +cmd 'focus up, focus right'; +$expected_focus = get_focused($ws); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws); +is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left'); +is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right'); +is(get_focused($ws), $expected_focus, 'F is still focused'); + +exit_gracefully($pid); + +############################################################################### +# Swap two non-focused containers which are both on different workspaces. +# +# Layout: O1[ W1[ A ] W2[ B ] W3[ F ] ] +# Focus Stacks: +# O1: W3, W2, W1 +# +# +---+ +# | A | +# +---+ +# +# +---+ +# | B | +# +---+ +# +# +---+ +# | F | +# +---+ +############################################################################### +$pid = launch_with_config($config); + +$ws1 = fresh_workspace; +$A = open_window(wm_class => 'mark_A'); + +$ws2 = fresh_workspace; +$B = open_window(wm_class => 'mark_B'); + +$ws3 = fresh_workspace; +open_window; +$expected_focus = get_focused($ws3); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws1); +is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace'); + +$nodes = get_ws_content($ws2); +is($nodes->[0]->{window}, $A->{id}, 'A is on the second workspace'); + +is(get_focused($ws3), $expected_focus, 'F is still focused'); + +exit_gracefully($pid); + +############################################################################### +# Swap two non-focused containers with one being on a different workspace. +# +# Layout: O1[ W1[ A ] W2[ H2 ] ] +# Focus Stacks: +# O1: W2, W1 +# +# +---+ +# | A | +# +---+ +# +# +---+---+ Layout: H2[ B, F ] +# | B | F | Focus Stacks: +# +---+---+ H2: F, B +############################################################################### +$pid = launch_with_config($config); + +$ws1 = fresh_workspace; +$A = open_window(wm_class => 'mark_A'); + +$ws2 = fresh_workspace; +$B = open_window(wm_class => 'mark_B'); +open_window; +$expected_focus = get_focused($ws2); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws1); +is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace'); + +$nodes = get_ws_content($ws2); +is($nodes->[0]->{window}, $A->{id}, 'A is on the left of the second workspace'); +is(get_focused($ws2), $expected_focus, 'F is still focused'); + +exit_gracefully($pid); + +############################################################################### +# 1. A container cannot be swapped with its parent. +# 2. A container cannot be swapped with one of its children. +# +# ↓A↓ +# +---+---+ Layout: H1[ X V1[ Y B ] ] +# | | Y | (with A := V1) +# | X +---+ +# | | B | +# +---+---+ +############################################################################### +$pid = launch_with_config($config); + +$ws = fresh_workspace; +open_window; +open_window; +cmd 'split v'; +$B = open_window(wm_class => 'mark_B'); +cmd 'focus parent, mark A, focus child'; + +$result = cmd '[con_mark=B] swap container with mark A'; +is($result->[0]->{success}, 0, 'B cannot be swappd with its parent'); + +$result = cmd '[con_mark=A] swap container with mark B'; +is($result->[0]->{success}, 0, 'A cannot be swappd with one of its children'); + +exit_gracefully($pid); + +############################################################################### +# Swapping two containers preserves the geometry of the container they are +# being swapped with. +# +# Before: +# +---+-------+ +# | A | B | +# +---+-------+ +# +# After: +# +---+-------+ +# | B | A | +# +---+-------+ +############################################################################### +$pid = launch_with_config($config); + +$ws = fresh_workspace; +$A = open_window(wm_class => 'mark_A'); +$B = open_window(wm_class => 'mark_B'); +cmd 'resize grow width 0 or 25 ppt'; + +# sanity checks +$nodes = get_ws_content($ws); +cmp_float($nodes->[0]->{percent}, 0.25, 'A has 25% width'); +cmp_float($nodes->[1]->{percent}, 0.75, 'B has 75% width'); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws); +cmp_float($nodes->[0]->{percent}, 0.25, 'B has 25% width'); +cmp_float($nodes->[1]->{percent}, 0.75, 'A has 75% width'); + +exit_gracefully($pid); + +############################################################################### +# Swapping containers not sharing the same parent preserves the geometry of +# the container they are swapped with. +# +# Before: +# +---+-----+ +# | A | | +# +---+ B | +# | | | +# | Y +-----+ +# | | X | +# +---+-----+ +# +# After: +# +---+-----+ +# | B | | +# +---+ A | +# | | | +# | Y +-----+ +# | | X | +# +---+-----+ +############################################################################### +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$A = open_window(wm_class => 'mark_A'); +$B = open_window(wm_class => 'mark_B'); +cmd 'split v'; +open_window; +cmd 'focus up, resize grow height 0 or 25 ppt'; +cmd 'focus left, split v'; +open_window; +cmd 'resize grow height 0 or 25 ppt'; + +# sanity checks +$nodes = get_ws_content($ws); +cmp_float($nodes->[0]->{nodes}->[0]->{percent}, 0.25, 'A has 25% height'); +cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.75, 'B has 75% height'); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws); +cmp_float($nodes->[0]->{nodes}->[0]->{percent}, 0.25, 'B has 25% height'); +cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.75, 'A has 75% height'); + +exit_gracefully($pid); + +############################################################################### +# Swapping containers moves the urgency hint correctly. +############################################################################### +$pid = launch_with_config($config); + +$ws1 = fresh_workspace; +$A = open_window(wm_class => 'mark_A'); +$ws2 = fresh_workspace; +$B = open_window(wm_class => 'mark_B'); +open_window; + +$B->add_hint('urgency'); +sync_with_i3; + +cmd '[con_mark=B] swap container with mark A'; + +@urgent = grep { $_->{urgent} } @{get_ws_content($ws1)}; +is(@urgent, 1, 'B is marked urgent'); +is(get_ws($ws1)->{urgent}, 1, 'the first workspace is marked urgent'); + +@urgent = grep { $_->{urgent} } @{get_ws_content($ws2)}; +is(@urgent, 0, 'A is not marked urgent'); +is(get_ws($ws2)->{urgent}, 0, 'the second workspace is not marked urgent'); + +exit_gracefully($pid); + +############################################################################### + +done_testing; From f99727b518968659ca71750a8d19ee5dbca483e3 Mon Sep 17 00:00:00 2001 From: hwangcc23 <hwangcc@csie.nctu.edu.tw> Date: Sun, 14 May 2017 16:05:29 +0800 Subject: [PATCH 078/180] Support to get the primary output This makes `primary` output available for assign or move commands. Fix the issue #2764(https://github.com/i3/i3/issues/2764). --- docs/userguide | 24 +++++++++++++++++++++--- src/randr.c | 11 +++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/docs/userguide b/docs/userguide index 068ecd87..985ce09b 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1922,7 +1922,7 @@ output:: ---------------------------------------------- focus left|right|down|up focus parent|child|floating|tiling|mode_toggle -focus output left|right|up|down|<output> +focus output left|right|up|down|primary|<output> ---------------------------------------------- *Examples*: @@ -1944,8 +1944,17 @@ bindsym $mod+x focus output right # Focus the big output bindsym $mod+x focus output HDMI-2 + +# Focus the primary output +bindsym $mod+x focus output primary ------------------------------------------------- +------------------------------- +Note that you might not have a primary output configured yet. To do so, run: +------------------------- +xrandr --output <output> --primary +------------------------- + === Moving containers Use the +move+ command to move a container. @@ -2162,8 +2171,8 @@ To move a container to another RandR output (addressed by names like +LVDS1+ or *Syntax*: ------------------------------------------------------------ -move container to output left|right|down|up|current|<output> -move workspace to output left|right|down|up|current|<output> +move container to output left|right|down|up|current|primary|<output> +move workspace to output left|right|down|up|current|primary|<output> ------------------------------------------------------------ *Examples*: @@ -2174,8 +2183,17 @@ bindsym $mod+x move workspace to output right # Put this window on the presentation output. bindsym $mod+x move container to output VGA1 + +# Put this window on the primary output. +bindsym $mod+x move container to output primary -------------------------------------------------------- +------------------------------- +Note that you might not have a primary output configured yet. To do so, run: +------------------------- +xrandr --output <output> --primary +------------------------- + === Moving containers/windows to marks To move a container to another container with a specific mark (see <<vim_like_marks>>), diff --git a/src/randr.c b/src/randr.c index 16ef62b8..8496fd03 100644 --- a/src/randr.c +++ b/src/randr.c @@ -45,10 +45,13 @@ static Output *get_output_by_id(xcb_randr_output_t id) { */ Output *get_output_by_name(const char *name) { Output *output; - TAILQ_FOREACH(output, &outputs, outputs) - if (output->active && - strcasecmp(output->name, name) == 0) - return output; + bool get_primary = (strcasecmp("primary", name) == 0); + TAILQ_FOREACH(output, &outputs, outputs) { + if ((output->primary && get_primary) || + (output->active && strcasecmp(output->name, name) == 0)) { + return output; + } + } return NULL; } From e4c2eb12aed6dfc554db18afa2e754187f8cc08a Mon Sep 17 00:00:00 2001 From: lasers <lasers@users.noreply.github.com> Date: Wed, 17 May 2017 18:48:56 -0500 Subject: [PATCH 079/180] docs/ipc: Fix typo --- docs/ipc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ipc b/docs/ipc index 25961f78..fd00ba4b 100644 --- a/docs/ipc +++ b/docs/ipc @@ -795,7 +795,7 @@ same as a +GET_BAR_CONFIG+ reply for the bar with the given id. === binding event This event consists of a single serialized map reporting on the details of a -binding that ran a command because of user input. The +change (sring)+ field +binding that ran a command because of user input. The +change (string)+ field indicates what sort of binding event was triggered (right now it will always be +"run"+ but may be expanded in the future). From bbfdcfddfeffded9c9f38797d923e11e1278aa79 Mon Sep 17 00:00:00 2001 From: hwangcc23 <hwangcc@csie.nctu.edu.tw> Date: Sun, 21 May 2017 23:08:27 +0800 Subject: [PATCH 080/180] Update testsuite docs After moved to autotools, the method to run the i3 testsuite is changed. Update the testsuite document to reflect the change. --- docs/testsuite | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/docs/testsuite b/docs/testsuite index 71c6a427..db98da13 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -114,7 +114,18 @@ machines without a powerful video card. .Example invocation of complete-run.pl+ --------------------------------------- -$ cd ~/i3/testcases +$ cd ~/i3 + +$ autoreconf -fi + +$ mkdir -p build && cd build + +$ ../configure + +$ make -j8 +# output omitted because it is very long + +$ cd testcases $ ./complete-run.pl # output omitted because it is very long @@ -160,6 +171,41 @@ $ ./complete-run.pl --parallel=1 --keep-xserver-output This will show the output of Xephyr, which is the X server implementation we use for testing. +===== make command: make check +Make check runs the i3 testsuite. +You can still use ./testcases/complete-run.pl to get the interactive progress output. + +.Example invocation of make check+ +--------------------------------------- +$ cd ~/i3 + +$ autoreconf -fi + +$ mkdir -p build && cd build + +$ ../configure + +$ make -j8 +# output omitted because it is very long + +$ make check +# output omitted because it is very long +PASS: testcases/complete-run.pl +============================================================================ +Testsuite summary for i3 4.13 +============================================================================ +# TOTAL: 1 +# PASS: 1 +# SKIP: 0 +# XFAIL: 0 +# FAIL: 0 +# XPASS: 0 +# ERROR: 0 +============================================================================ + +$ less test-suite.log +---------------------------------------- + ==== Coverage testing Coverage testing is possible with +lcov+, the front-end for GCC's coverage From 36f80f2de80bc22d75f40e446c4ead2248f0b7fb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Mon, 22 May 2017 21:32:05 +0200 Subject: [PATCH 081/180] Apply numlock fallback to bindcode where necessary (#2781) This was broken with commit d77d40173aae250d2e5ba86a8bce7a6e857cbc67 fixes #2559 --- src/bindings.c | 108 ++++++++++++++++++++++------- testcases/t/264-keypress-numlock.t | 53 +++++++++++++- 2 files changed, 132 insertions(+), 29 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index bf04c432..8d54c6eb 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -224,8 +224,9 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas const uint32_t xkb_group_state = (state_filtered & 0xFFFF0000); const uint32_t modifiers_state = (state_filtered & 0x0000FFFF); TAILQ_FOREACH(bind, bindings, bindings) { - if (bind->input_type != input_type) + if (bind->input_type != input_type) { continue; + } const uint32_t xkb_group_mask = (bind->event_state_mask & 0xFFFF0000); const bool groups_match = ((xkb_group_state & xkb_group_mask) == xkb_group_mask); @@ -251,23 +252,31 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas break; } } - if (!found_keycode) + if (!found_keycode) { continue; + } } else { - const uint32_t modifiers_mask = (bind->event_state_mask & 0x0000FFFF); - const bool mods_match = modifiers_match(modifiers_mask, modifiers_state); - DLOG("binding mods_match = %s\n", (mods_match ? "yes" : "no")); - /* First compare the state_filtered (unless this is a - * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease - * event) */ - if (!mods_match && - (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS || - !is_release)) - continue; - /* This case is easier: The user specified a keycode */ - if (bind->keycode != input_code) + if (bind->keycode != input_code) { continue; + } + + xcb_keycode_t input_keycode = (xcb_keycode_t)input_code; + bool found_keycode = false; + struct Binding_Keycode *binding_keycode; + TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) { + const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF); + const bool mods_match = modifiers_match(modifiers_mask, modifiers_state); + DLOG("binding_keycode->modifiers = %d, modifiers_mask = %d, modifiers_state = %d, mods_match = %s\n", + binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no")); + if (binding_keycode->keycode == input_keycode && mods_match) { + found_keycode = true; + break; + } + } + if (!found_keycode) { + continue; + } } /* If this binding is a release binding, it matches the key which the @@ -286,8 +295,9 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas /* Check if the binding is for a press or a release event */ if ((bind->release == B_UPON_KEYPRESS && is_release) || - (bind->release >= B_UPON_KEYRELEASE && !is_release)) + (bind->release >= B_UPON_KEYRELEASE && !is_release)) { continue; + } break; } @@ -460,6 +470,14 @@ void translate_keysyms(void) { bool has_errors = false; Binding *bind; TAILQ_FOREACH(bind, bindings, bindings) { +#define ADD_TRANSLATED_KEY(code, mods) \ + do { \ + struct Binding_Keycode *binding_keycode = smalloc(sizeof(struct Binding_Keycode)); \ + binding_keycode->modifiers = (mods); \ + binding_keycode->keycode = (code); \ + TAILQ_INSERT_TAIL(&(bind->keycodes_head), binding_keycode, keycodes); \ + } while (0) + if (bind->input_type == B_MOUSE) { long button; if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) { @@ -467,17 +485,7 @@ void translate_keysyms(void) { } bind->keycode = button; - continue; - } - - if (bind->keycode > 0) - continue; - - /* We need to translate the symbol to a keycode */ - const xkb_keysym_t keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS); - if (keysym == XKB_KEY_NoSymbol) { - ELOG("Could not translate string to key symbol: \"%s\"\n", - bind->symbol); + ADD_TRANSLATED_KEY(button, bind->event_state_mask); continue; } @@ -532,6 +540,52 @@ void translate_keysyms(void) { 0 /* xkb_layout_index_t latched_group, */, group /* xkb_layout_index_t locked_group, */); + if (bind->keycode > 0) { + /* We need to specify modifiers for the keycode binding (numlock + * fallback). */ + while (!TAILQ_EMPTY(&(bind->keycodes_head))) { + struct Binding_Keycode *first = TAILQ_FIRST(&(bind->keycodes_head)); + TAILQ_REMOVE(&(bind->keycodes_head), first, keycodes); + FREE(first); + } + + ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask); + + /* Also bind the key with active CapsLock */ + ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask | XCB_MOD_MASK_LOCK); + + /* If this binding is not explicitly for NumLock, check whether we need to + * add a fallback. */ + if ((bind->event_state_mask & xcb_numlock_mask) != xcb_numlock_mask) { + /* Check whether the keycode results in the same keysym when NumLock is + * active. If so, grab the key with NumLock as well, so that users don’t + * need to duplicate every key binding with an additional Mod2 specified. + */ + xkb_keysym_t sym = xkb_state_key_get_one_sym(dummy_state, bind->keycode); + xkb_keysym_t sym_numlock = xkb_state_key_get_one_sym(dummy_state_numlock, bind->keycode); + if (sym == sym_numlock) { + /* Also bind the key with active NumLock */ + ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask | xcb_numlock_mask); + + /* Also bind the key with active NumLock+CapsLock */ + ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask | xcb_numlock_mask | XCB_MOD_MASK_LOCK); + } else { + DLOG("Skipping automatic numlock fallback, key %d resolves to 0x%x with numlock\n", + bind->keycode, sym_numlock); + } + } + + continue; + } + + /* We need to translate the symbol to a keycode */ + const xkb_keysym_t keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS); + if (keysym == XKB_KEY_NoSymbol) { + ELOG("Could not translate string to key symbol: \"%s\"\n", + bind->symbol); + continue; + } + struct resolve resolving = { .bind = bind, .keysym = keysym, @@ -574,6 +628,8 @@ void translate_keysyms(void) { DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n", bind->event_state_mask, bind->symbol, keysym, keycodes, num_keycodes); free(keycodes); + +#undef ADD_TRANSLATED_KEY } xkb_state_unref(dummy_state); diff --git a/testcases/t/264-keypress-numlock.t b/testcases/t/264-keypress-numlock.t index 45ec7e88..464fe748 100644 --- a/testcases/t/264-keypress-numlock.t +++ b/testcases/t/264-keypress-numlock.t @@ -44,6 +44,9 @@ bindsym Shift+Escape nop Shift+Escape # Binding which should work with numlock and without, see issue #2418. bindsym Mod1+Shift+q nop Mod1+Shift+q + +# Binding which should work with numlock and without, see issue #2559. +bindcode 39 nop s EOT my $pid = launch_with_config($config); @@ -159,7 +162,6 @@ is(listen_for_binding( 'Mod1+Shift+q', 'triggered the "Mod1+Shift+q" keybinding'); - is(listen_for_binding( sub { xtest_key_press(77); # enable Num_Lock @@ -177,8 +179,30 @@ is(listen_for_binding( 'Mod1+Shift+q', 'triggered the "Mod1+Shift+q" keybinding'); +is(listen_for_binding( + sub { + xtest_key_press(39); # s + xtest_key_release(39); # s + }, + ), + 's', + 'triggered the "s" keybinding without Num_Lock'); + +is(listen_for_binding( + sub { + xtest_key_press(77); # enable Num_Lock + xtest_key_release(77); # enable Num_Lock + xtest_key_press(39); # s + xtest_key_release(39); # s + xtest_key_press(77); # disable Num_Lock + xtest_key_release(77); # disable Num_Lock + }, + ), + 's', + 'triggered the "s" keybinding with Num_Lock'); + sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 10, 'Received exactly 10 binding events'); +is(scalar @i3test::XTEST::binding_events, 12, 'Received exactly 12 binding events'); exit_gracefully($pid); @@ -191,6 +215,7 @@ $config = <<EOT; font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 bindsym KP_End nop KP_End +bindcode 88 nop KP_Down EOT $pid = launch_with_config($config); @@ -206,6 +231,15 @@ is(listen_for_binding( 'KP_End', 'triggered the "KP_End" keybinding'); +is(listen_for_binding( + sub { + xtest_key_press(88); # KP_Down + xtest_key_release(88); # KP_Down + }, + ), + 'KP_Down', + 'triggered the "KP_Down" keybinding'); + is(listen_for_binding( sub { xtest_key_press(77); # enable Num_Lock @@ -219,10 +253,23 @@ is(listen_for_binding( 'timeout', 'Did not trigger the KP_End keybinding with KP_1'); +is(listen_for_binding( + sub { + xtest_key_press(77); # enable Num_Lock + xtest_key_release(77); # enable Num_Lock + xtest_key_press(88); # KP_2 + xtest_key_release(88); # KP_2 + xtest_key_press(77); # disable Num_Lock + xtest_key_release(77); # disable Num_Lock + }, + ), + 'timeout', + 'Did not trigger the KP_Down keybinding with KP_2'); + # TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2. sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 11, 'Received exactly 11 binding events'); +is(scalar @i3test::XTEST::binding_events, 14, 'Received exactly 14 binding events'); exit_gracefully($pid); From 2bd3639fbb58e92d4d3babde0956bb9f003c0335 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Tue, 23 May 2017 08:28:56 +0200 Subject: [PATCH 082/180] Fix --release bindings broken by previous commit fixes #2559 --- src/bindings.c | 4 ++- testcases/t/264-keypress-numlock.t | 45 +++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 8d54c6eb..a8b897af 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -274,7 +274,9 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas break; } } - if (!found_keycode) { + if (!found_keycode && + (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS || + !is_release)) { continue; } } diff --git a/testcases/t/264-keypress-numlock.t b/testcases/t/264-keypress-numlock.t index 464fe748..90a403af 100644 --- a/testcases/t/264-keypress-numlock.t +++ b/testcases/t/264-keypress-numlock.t @@ -206,6 +206,49 @@ is(scalar @i3test::XTEST::binding_events, 12, 'Received exactly 12 binding event exit_gracefully($pid); +################################################################################ +# Verify bindings for modifiers work +################################################################################ + +$config = <<EOT; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +# Binding which should work with numlock and without, see issue #2559. +bindcode --release 133 nop Super_L +EOT + +$pid = launch_with_config($config); + +start_binding_capture; + +is(listen_for_binding( + sub { + xtest_key_press(133); # Super_L + xtest_key_release(133); # Super_L + }, + ), + 'Super_L', + 'triggered the "Super_L" keybinding without Num_Lock'); + +is(listen_for_binding( + sub { + xtest_key_press(77); # enable Num_Lock + xtest_key_release(77); # enable Num_Lock + xtest_key_press(133); # Super_L + xtest_key_release(133); # Super_L + xtest_key_press(77); # disable Num_Lock + xtest_key_release(77); # disable Num_Lock + }, + ), + 'Super_L', + 'triggered the "Super_L" keybinding with Num_Lock'); + +sync_with_i3; +is(scalar @i3test::XTEST::binding_events, 14, 'Received exactly 14 binding events'); + +exit_gracefully($pid); + ################################################################################ # Verify the binding is only triggered for KP_End, not KP_1 ################################################################################ @@ -269,7 +312,7 @@ is(listen_for_binding( # TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2. sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 14, 'Received exactly 14 binding events'); +is(scalar @i3test::XTEST::binding_events, 16, 'Received exactly 16 binding events'); exit_gracefully($pid); From 13372d511aa926c018161a4d7287e9b9ce6ea9c3 Mon Sep 17 00:00:00 2001 From: Chih-Chyuan Hwang <hwangcc@csie.nctu.edu.tw> Date: Tue, 23 May 2017 14:47:11 +0800 Subject: [PATCH 083/180] Fix the i3 crash caused by mark + restart commands (#2779) This patch fixes the issue #2511(https://github.com/i3/i3/issues/2511). 1). Memorize the marks, but only call con_mark once the container has finished parsing. (Credit: This is @Airblader's patch.) 2). Add a test case 267-regress-mark-restart.t for regression test to check if mark and restart command crash i3. --- src/load_layout.c | 19 ++++++++++++++-- testcases/t/267-regress-mark-restart.t | 30 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 testcases/t/267-regress-mark-restart.t diff --git a/src/load_layout.c b/src/load_layout.c index f6f045d2..632c6ec7 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -29,6 +29,8 @@ static bool parsing_focus; static bool parsing_marks; struct Match *current_swallow; static bool swallow_is_empty; +static int num_marks; +static char **marks; /* This list is used for reordering the focus stack after parsing the 'focus' * array. */ @@ -148,6 +150,16 @@ static int json_end_map(void *ctx) { floating_check_size(json_node); } + if (num_marks > 0) { + for (int i = 0; i < num_marks; i++) { + con_mark(json_node, marks[i], MM_ADD); + free(marks[i]); + } + + free(marks); + num_marks = 0; + } + LOG("attaching\n"); con_attach(json_node, json_node->parent, true); LOG("Creating window\n"); @@ -230,8 +242,10 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) { if (strcasecmp(last_key, "focus") == 0) parsing_focus = true; - if (strcasecmp(last_key, "marks") == 0) + if (strcasecmp(last_key, "marks") == 0) { + num_marks = 0; parsing_marks = true; + } return 1; } @@ -261,7 +275,8 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { char *mark; sasprintf(&mark, "%.*s", (int)len, val); - con_mark(json_node, mark, MM_ADD); + marks = srealloc(marks, (++num_marks) * sizeof(char *)); + marks[num_marks - 1] = sstrdup(mark); } else { if (strcasecmp(last_key, "name") == 0) { json_node->name = scalloc(len + 1, 1); diff --git a/testcases/t/267-regress-mark-restart.t b/testcases/t/267-regress-mark-restart.t new file mode 100644 index 00000000..220d765b --- /dev/null +++ b/testcases/t/267-regress-mark-restart.t @@ -0,0 +1,30 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Regression test to check if mark and restart commands crash i3 +# +use i3test; + +cmd 'open'; +cmd 'mark foo'; + +cmd 'restart'; + +diag('Checking if i3 still lives'); + +does_i3_live; + +done_testing; From 6d9b165fb442003375c6de3a6c2d03b10d487ba9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Wed, 24 May 2017 20:40:17 +0200 Subject: [PATCH 084/180] =?UTF-8?q?no-op=20change:=20don=E2=80=99t=20compa?= =?UTF-8?q?re=20keycode=20for=20every=20modifier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bindings.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index a8b897af..0a994f55 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -261,7 +261,6 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas continue; } - xcb_keycode_t input_keycode = (xcb_keycode_t)input_code; bool found_keycode = false; struct Binding_Keycode *binding_keycode; TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) { @@ -269,7 +268,7 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas const bool mods_match = modifiers_match(modifiers_mask, modifiers_state); DLOG("binding_keycode->modifiers = %d, modifiers_mask = %d, modifiers_state = %d, mods_match = %s\n", binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no")); - if (binding_keycode->keycode == input_keycode && mods_match) { + if (mods_match) { found_keycode = true; break; } From 0acd11a8d71a120edf3d2cfe327ec678bb1b63af Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Wed, 24 May 2017 20:41:17 +0200 Subject: [PATCH 085/180] no-op change: move bind->release check into loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don’t have to negate the check anymore, making it more readable. --- src/bindings.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 0a994f55..e9aec2c6 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -268,14 +268,12 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas const bool mods_match = modifiers_match(modifiers_mask, modifiers_state); DLOG("binding_keycode->modifiers = %d, modifiers_mask = %d, modifiers_state = %d, mods_match = %s\n", binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no")); - if (mods_match) { + if (mods_match || (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS && is_release)) { found_keycode = true; break; } } - if (!found_keycode && - (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS || - !is_release)) { + if (!found_keycode) { continue; } } From 26f5edb97fb3dcc0659c3ae5fff5b816f80d57e5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Wed, 24 May 2017 20:41:55 +0200 Subject: [PATCH 086/180] no-op change: store |button| in the correct data type --- src/bindings.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index e9aec2c6..7cba556e 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -483,8 +483,9 @@ void translate_keysyms(void) { ELOG("Could not translate string to button: \"%s\"\n", bind->symbol); } - bind->keycode = button; - ADD_TRANSLATED_KEY(button, bind->event_state_mask); + xcb_keycode_t key = button; + bind->keycode = key; + ADD_TRANSLATED_KEY(key, bind->event_state_mask); continue; } From a4f6387911b4b79ca864052012c5cbace584eb2e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Wed, 24 May 2017 20:42:27 +0200 Subject: [PATCH 087/180] compare modifiers for equality, not subset (+test) Subset comparison was introduced with the rather large commit bf3cd41b5ddf1e757515ab5fbf811be56e5f69cc, but I now think we should use equality. In other words, the following key binding: bindsym Mod4+x nop Mod4+x previously would have been triggered when pressing Mod3+Mod4+x. Strictly speaking, this is a change of behavior, but it breaks none of our tests, and using equality instead of subset comparison enables more use-cases. fixes #2002 --- src/bindings.c | 15 ++------------ testcases/t/264-keypress-numlock.t | 32 ++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 7cba556e..14eaaf5e 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -191,17 +191,6 @@ void regrab_all_buttons(xcb_connection_t *conn) { xcb_ungrab_server(conn); } -static bool modifiers_match(const uint32_t modifiers_mask, const uint32_t modifiers_state) { - /* modifiers_mask is a special case: a value of 0 does not mean “match - * all”, but rather “match exactly when no modifiers are present”. */ - if (modifiers_mask == 0) { - /* Verify no modifiers are pressed. A bitwise AND would lead to - * false positives, see issue #2002. */ - return (modifiers_state == 0); - } - return ((modifiers_state & modifiers_mask) == modifiers_mask); -} - /* * Returns a pointer to the Binding with the specified modifiers and * keycode or NULL if no such binding exists. @@ -244,7 +233,7 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas struct Binding_Keycode *binding_keycode; TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) { const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF); - const bool mods_match = modifiers_match(modifiers_mask, modifiers_state); + const bool mods_match = (modifiers_mask == modifiers_state); DLOG("binding_keycode->modifiers = %d, modifiers_mask = %d, modifiers_state = %d, mods_match = %s\n", binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no")); if (binding_keycode->keycode == input_keycode && mods_match) { @@ -265,7 +254,7 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas struct Binding_Keycode *binding_keycode; TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) { const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF); - const bool mods_match = modifiers_match(modifiers_mask, modifiers_state); + const bool mods_match = (modifiers_mask == modifiers_state); DLOG("binding_keycode->modifiers = %d, modifiers_mask = %d, modifiers_state = %d, mods_match = %s\n", binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no")); if (mods_match || (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS && is_release)) { diff --git a/testcases/t/264-keypress-numlock.t b/testcases/t/264-keypress-numlock.t index 90a403af..2fdafb83 100644 --- a/testcases/t/264-keypress-numlock.t +++ b/testcases/t/264-keypress-numlock.t @@ -214,6 +214,8 @@ $config = <<EOT; # i3 config file (v4) font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 +bindsym Mod4+Return nop Return + # Binding which should work with numlock and without, see issue #2559. bindcode --release 133 nop Super_L EOT @@ -244,8 +246,34 @@ is(listen_for_binding( 'Super_L', 'triggered the "Super_L" keybinding with Num_Lock'); +is(listen_for_binding( + sub { + xtest_key_press(133); # Super_L + xtest_key_press(36); # Return + xtest_key_release(36); # Return + xtest_key_release(133); # Super_L + }, + ), + 'Return', + 'triggered the "Return" keybinding without Num_Lock'); + +is(listen_for_binding( + sub { + xtest_key_press(77); # enable Num_Lock + xtest_key_release(77); # enable Num_Lock + xtest_key_press(133); # Super_L + xtest_key_press(36); # Return + xtest_key_release(36); # Return + xtest_key_release(133); # Super_L + xtest_key_press(77); # disable Num_Lock + xtest_key_release(77); # disable Num_Lock + }, + ), + 'Return', + 'triggered the "Return" keybinding with Num_Lock'); + sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 14, 'Received exactly 14 binding events'); +is(scalar @i3test::XTEST::binding_events, 16, 'Received exactly 16 binding events'); exit_gracefully($pid); @@ -312,7 +340,7 @@ is(listen_for_binding( # TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2. sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 16, 'Received exactly 16 binding events'); +is(scalar @i3test::XTEST::binding_events, 18, 'Received exactly 18 binding events'); exit_gracefully($pid); From ca1b0afca167711be79a1556671edd50c274bfdc Mon Sep 17 00:00:00 2001 From: Adaephon-GH <adaephon@gmx.net> Date: Wed, 31 May 2017 11:26:07 +0200 Subject: [PATCH 088/180] Fix manpage on configuration lookup order The lookup order stated in the i3 manpage was is contratiction to the actual lookup order, which was introduced with commit https://github.com/i3/i3/commit/bfa12a581915d6a3de182fa6025fce108cac8eab. Since that commit (6.5 years ago) the "traditional" paths took precedence over XDG_CONFIG paths. --- man/i3.man | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/man/i3.man b/man/i3.man index 16302e08..c1f7eca2 100644 --- a/man/i3.man +++ b/man/i3.man @@ -170,10 +170,10 @@ Exits i3. When starting, i3 looks for configuration files in the following order: -1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set) -2. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set) -3. ~/.i3/config -4. /etc/i3/config +1. ~/.i3/config +2. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set) +3. /etc/i3/config +4. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set) You can specify a custom path using the -c option. From b2ee718f56afc334bbfee7dd3dda4306a94df1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C3=ABl=20Gu=C3=A9neau?= <armael.gueneau@ens-lyon.fr> Date: Wed, 31 May 2017 16:52:17 +0200 Subject: [PATCH 089/180] Add a link to the ocaml-i3ipc library --- docs/ipc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/ipc b/docs/ipc index fd00ba4b..0fc5774a 100644 --- a/docs/ipc +++ b/docs/ipc @@ -877,3 +877,5 @@ Ruby:: * https://github.com/badboy/i3-ipc (not maintained) Rust:: * https://github.com/tmerr/i3ipc-rs +OCaml:: + * https://github.com/Armael/ocaml-i3ipc \ No newline at end of file From e63070607fa6f63948d28f4c642e2eb3aa758ef3 Mon Sep 17 00:00:00 2001 From: hwangcc23 <hwangcc@csie.nctu.edu.tw> Date: Fri, 2 Jun 2017 22:26:09 +0800 Subject: [PATCH 090/180] Add compilation instructions to the hacking howto --- docs/hacking-howto | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docs/hacking-howto b/docs/hacking-howto index 2ba74917..52436da6 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -990,6 +990,47 @@ New features are only found in the “next” branch. Therefore, if you are work on a new feature, use the “next” branch. If you are working on a bugfix, use the “next” branch, too, but make sure your code also works on “master”. +=== How to build? + +You can build i3 like you build any other software package which uses autotools. +Here’s a memory refresher: + + $ autoreconf -fi + $ mkdir -p build && cd build + $ ../configure + $ make -j8 + +(The autoreconf -fi step is unnecessary if you are building from a release tarball, + but shouldn’t hurt either.) + +==== Build system features + +* We use the AX_ENABLE_BUILDDIR macro to enforce builds happening in a separate + directory. This is a prerequisite for the AX_EXTEND_SRCDIR macro and building + in a separate directory is common practice anyway. In case this causes any + trouble when packaging i3 for your distribution, please open an issue. + +* “make check” runs the i3 testsuite. See docs/testsuite for details. + +* “make distcheck” (runs testsuite on “make dist” result, tiny bit quicker + feedback cycle than waiting for the travis build to catch the issue). + +* “make uninstall” (occasionally requested by users who compile from source) + +* “make” will build manpages/docs by default if the tools are installed. + Conversely, manpages/docs are not tried to be built for users who don’t want + to install all these dependencies to get started hacking on i3. + +* non-release builds will enable address sanitizer by default. Use the + --disable-sanitizers configure option to turn off all sanitizers, and see + --help for available sanitizers. + +* Support for pre-compiled headers (PCH) has been dropped for now in the + interest of simplicity. If you need support for PCH, please open an issue. + +* Coverage reports are now generated using “make check-code-coverage”, which + requires specifying --enable-code-coverage when calling configure. + == Thought experiments In this section, we collect thought experiments, so that we don’t forget our From cc4be4167422644c5789b94ec6292e5c77d2df45 Mon Sep 17 00:00:00 2001 From: hwangcc23 <hwangcc@csie.nctu.edu.tw> Date: Sun, 11 Jun 2017 23:48:55 +0800 Subject: [PATCH 091/180] Fix 'rename workspace to tosomething' This patch fixes the issue #2802 (https://github.com/i3/i3/issues/2802). 1). Revise the state machine for the 'rename workspace' command. These scenarios are considered: a). 'rename workspace to to bla' state transitions: RENAME -> RENAME_WORKSPACE -> RENAME_WORKSPACE_LIKELY_TO -> RENAME_WORKSPACE_LIKELY_TO_NEW_NAME b). 'rename workspace to tosomething' state transitions: RENAME -> RENAME_WORKSPACE -> RENAME_WORKSPACE_LIKELY_TO c). 'rename workspace to to' state transitions: RENAME -> RENAME_WORKSPACE -> RENAME_WORKSPACE_LIKELY_TO d). 'rename workspace to bla' state transitions: RENAME -> RENAME_WORKSPACE -> RENAME_WORKSPACE_LIKELY_TO e). 'rename workspace bla to foo' state transitions: RENAME -> RENAME_WORKSPACE -> RENAME_WORKSPACE_TO -> RENAME_WORKSPACE_TO_NEW_NAME 2). Add a test case in 117-workspace.t for the scenario b. --- parser-specs/commands.spec | 20 ++++++++++++-------- testcases/t/117-workspace.t | 6 ++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 0858322b..a5873328 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -274,24 +274,28 @@ state RENAME: -> RENAME_WORKSPACE state RENAME_WORKSPACE: - old_name = 'to' + 'to' -> RENAME_WORKSPACE_LIKELY_TO old_name = word -> RENAME_WORKSPACE_TO state RENAME_WORKSPACE_LIKELY_TO: - 'to' - -> RENAME_WORKSPACE_NEW_NAME + 'to ' + -> RENAME_WORKSPACE_LIKELY_TO_NEW_NAME new_name = word -> call cmd_rename_workspace(NULL, $new_name) -state RENAME_WORKSPACE_TO: - 'to' - -> RENAME_WORKSPACE_NEW_NAME - -state RENAME_WORKSPACE_NEW_NAME: +state RENAME_WORKSPACE_LIKELY_TO_NEW_NAME: + new_name = string + -> call cmd_rename_workspace("to", $new_name) end -> call cmd_rename_workspace(NULL, "to") + +state RENAME_WORKSPACE_TO: + 'to' + -> RENAME_WORKSPACE_TO_NEW_NAME + +state RENAME_WORKSPACE_TO_NEW_NAME: new_name = string -> call cmd_rename_workspace($old_name, $new_name) diff --git a/testcases/t/117-workspace.t b/testcases/t/117-workspace.t index 01d51cc0..40b2cbb6 100644 --- a/testcases/t/117-workspace.t +++ b/testcases/t/117-workspace.t @@ -279,6 +279,12 @@ is(focused_ws(), 'bla', 'now on workspace bla'); cmd 'rename workspace to to'; ok(!workspace_exists('bla'), 'workspace bla does not exist anymore'); is(focused_ws(), 'to', 'now on workspace to'); +cmd 'rename workspace to bla'; +ok(!workspace_exists('to'), 'workspace to does not exist anymore'); +is(focused_ws(), 'bla', 'now on workspace bla'); +cmd 'rename workspace to tosomething'; +ok(!workspace_exists('bla'), 'workspace bla does not exist anymore'); +is(focused_ws(), 'tosomething', 'now on workspace tosomething'); # 6: already existing workspace my $result = cmd 'rename workspace qux to 11: bar'; From 5362876b60ddecda43de07affc77ec8f13335512 Mon Sep 17 00:00:00 2001 From: hwangcc23 <hwangcc@csie.nctu.edu.tw> Date: Thu, 22 Jun 2017 22:53:08 +0800 Subject: [PATCH 092/180] i3-msg: Return an exit code upon missing -t arg If getopt() returns '?' (option with a missing argument), exit the program with an error code rather than continuing the execution. Fix the issue #2804 (https://github.com/i3/i3/issues/2804). --- i3-msg/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/i3-msg/main.c b/i3-msg/main.c index 02e156a1..4bc3c149 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -180,6 +180,8 @@ int main(int argc, char *argv[]) { printf("i3-msg " I3_VERSION "\n"); printf("i3-msg [-s <socket>] [-t <type>] <message>\n"); return 0; + } else if (o == '?') { + exit(EXIT_FAILURE); } } From ec3ffdd7a073422f563a099d919d4ea5e7db8b40 Mon Sep 17 00:00:00 2001 From: akash akya <akashh246@gmail.com> Date: Fri, 7 Jul 2017 01:03:36 +0530 Subject: [PATCH 093/180] Fix 'Mouse binding don't work' --- src/bindings.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 14eaaf5e..3e2bb965 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -474,8 +474,7 @@ void translate_keysyms(void) { xcb_keycode_t key = button; bind->keycode = key; - ADD_TRANSLATED_KEY(key, bind->event_state_mask); - continue; + DLOG("Binding Mouse button, Keycode = %d\n", key); } xkb_layout_index_t group = XCB_XKB_GROUP_1; From cacd2ae600c5c52a7bd01dc5b4b7123feb5fe64a Mon Sep 17 00:00:00 2001 From: David Jimenez Sequero <david.jimenez-sequero@infinityworks.com> Date: Mon, 10 Jul 2017 11:59:37 +0100 Subject: [PATCH 094/180] Add KDE konsole terminal fallback to i3-sensible-terminal --- i3-sensible-terminal | 2 +- man/i3-sensible-terminal.man | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/i3-sensible-terminal b/i3-sensible-terminal index 7ec0f878..f92ff224 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -8,7 +8,7 @@ # We welcome patches that add distribution-specific mechanisms to find the # preferred terminal emulator. On Debian, there is the x-terminal-emulator # symlink for example. -for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix; do +for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole; do if command -v "$terminal" > /dev/null 2>&1; then exec "$terminal" "$@" fi diff --git a/man/i3-sensible-terminal.man b/man/i3-sensible-terminal.man index 5facbac9..20a6810c 100644 --- a/man/i3-sensible-terminal.man +++ b/man/i3-sensible-terminal.man @@ -43,6 +43,7 @@ It tries to start one of the following (in that order): * lilyterm * tilix * terminix +* konsole Please don’t complain about the order: If the user has any preference, they will have $TERMINAL set or modified their i3 configuration file. From c534a3ea9502b9e9794f97555c8a8a821b9999e1 Mon Sep 17 00:00:00 2001 From: Tony Crisci <tony@dubstepdish.com> Date: Wed, 12 Jul 2017 10:57:23 -0400 Subject: [PATCH 095/180] Move SYSCONFDIR definition to makefile Official autoconf docs contraindicate the use of the $sysconfdir variable in configure.ac. For reference: https://www.gnu.org/software/autoconf/manual/autoconf-2.63/html_node/Installation-Directory-Variables.html The macro SYSCONFDIR (ordinarilly set to "/etc") is required to find a system configuration file installed with i3. In some build setups, this may not be defined properly in when the configure script is compiled. Instead, define this variable in AM_CPPFLAGS as the documentation indicates. fixes #2832 --- Makefile.am | 1 + configure.ac | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index 0368ecca..188e9e82 100644 --- a/Makefile.am +++ b/Makefile.am @@ -215,6 +215,7 @@ asciidoc_MANS = endif AM_CPPFLAGS = \ + -DSYSCONFDIR="\"$(sysconfdir)\"" \ -I$(top_builddir)/parser \ -I$(top_srcdir)/include \ @AX_EXTEND_SRCDIR_CPPFLAGS@ diff --git a/configure.ac b/configure.ac index 87ffbbe7..f2bf26f8 100644 --- a/configure.ac +++ b/configure.ac @@ -59,8 +59,6 @@ AX_CHECK_ENABLE_DEBUG([yes], , [UNUSED_NDEBUG], [$is_release]) AC_PROG_CC_C99 -AC_DEFINE_UNQUOTED(SYSCONFDIR, "`eval echo $sysconfdir`", [Location of system configuration files]) - # For strnlen() and vasprintf(). AC_USE_SYSTEM_EXTENSIONS From 1c06f8b797bcd4cb142dbb7169aa5cce1ae1f50e Mon Sep 17 00:00:00 2001 From: Tony Crisci <tony@dubstepdish.com> Date: Wed, 12 Jul 2017 17:50:10 -0400 Subject: [PATCH 096/180] i3bar: change error block color to hex Named colors are not supported by the i3bar protocol so give the error block color in hex. --- i3bar/src/child.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 60ab462a..814f0411 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -112,13 +112,13 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha struct status_block *err_block = scalloc(1, sizeof(struct status_block)); err_block->full_text = i3string_from_utf8("Error: "); err_block->name = sstrdup("error"); - err_block->color = sstrdup("red"); + err_block->color = sstrdup("#ff0000"); err_block->no_separator = true; struct status_block *message_block = scalloc(1, sizeof(struct status_block)); message_block->full_text = i3string_from_utf8(message); message_block->name = sstrdup("error_message"); - message_block->color = sstrdup("red"); + message_block->color = sstrdup("#ff0000"); message_block->no_separator = true; TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks); From 3a914396df50441377f1d0f17912653dd4bba4cf Mon Sep 17 00:00:00 2001 From: Tony Crisci <tony@dubstepdish.com> Date: Wed, 12 Jul 2017 17:50:19 -0400 Subject: [PATCH 097/180] libi3: Add basic validation to hex color conversion Make sure a given hex color is the expected length and begins with a hash in draw_util_hex_to_color() to avoid memory errors. fixes #2829 --- libi3/draw_util.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libi3/draw_util.c b/libi3/draw_util.c index e4f0d065..6a2e93dc 100644 --- a/libi3/draw_util.c +++ b/libi3/draw_util.c @@ -84,6 +84,11 @@ void draw_util_surface_set_size(surface_t *surface, int width, int height) { * */ color_t draw_util_hex_to_color(const char *color) { + if (strlen(color) < 6 || color[0] != '#') { + ELOG("Could not parse color: %s\n", color); + return draw_util_hex_to_color("#A9A9A9"); + } + char alpha[2]; if (strlen(color) == strlen("#rrggbbaa")) { alpha[0] = color[7]; From 6a8a4266f3b15aa983619ee2adf895c54c217608 Mon Sep 17 00:00:00 2001 From: Trevor Merrifield <trevorm42@gmail.com> Date: Sun, 30 Jul 2017 00:42:11 -0400 Subject: [PATCH 098/180] Add missing newline to end of file --- docs/ipc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ipc b/docs/ipc index 0fc5774a..1d103931 100644 --- a/docs/ipc +++ b/docs/ipc @@ -878,4 +878,4 @@ Ruby:: Rust:: * https://github.com/tmerr/i3ipc-rs OCaml:: - * https://github.com/Armael/ocaml-i3ipc \ No newline at end of file + * https://github.com/Armael/ocaml-i3ipc From a805676197a7f4c85e706ddf5b039f1e724d40e3 Mon Sep 17 00:00:00 2001 From: Trevor Merrifield <trevorm42@gmail.com> Date: Sun, 30 Jul 2017 00:47:40 -0400 Subject: [PATCH 099/180] Document missing case of workspace event The docs are missing the "move" workspace event emitted from workspace.c's workspace_move_to_output function. --- docs/ipc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ipc b/docs/ipc index 1d103931..65723577 100644 --- a/docs/ipc +++ b/docs/ipc @@ -698,9 +698,9 @@ if ($is_event) { This event consists of a single serialized map containing a property +change (string)+ which indicates the type of the change ("focus", "init", -"empty", "urgent", "reload", "rename", "restored"). A +current (object)+ -property will be present with the affected workspace whenever the type of event -affects a workspace (otherwise, it will be +null). +"empty", "urgent", "reload", "rename", "restored", "move"). A ++current (object)+ property will be present with the affected workspace +whenever the type of event affects a workspace (otherwise, it will be +null). When the change is "focus", an +old (object)+ property will be present with the previous workspace. When the first switch occurs (when i3 focuses the From 69efe0176eedbd9165b6973eae26a4d71017f9ba Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 19 Aug 2017 17:29:31 +0200 Subject: [PATCH 100/180] Prevent accidentally introduced fall-through This was added in commit https://github.com/i3/i3/commit/e82e26a24d22e2f92cd90d9547c213493e94ea63 --- i3bar/src/xcb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 24f91642..3bd5677a 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1184,6 +1184,7 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { case XCB_CONFIGURE_REQUEST: /* ConfigureRequest, sent by a tray child */ handle_configure_request((xcb_configure_request_event_t *)event); + break; case XCB_RESIZE_REQUEST: /* ResizeRequest sent by a tray child using override_redirect. */ handle_resize_request((xcb_resize_request_event_t *)event); From 5738ea10bbe57daaa259612772fe288e29c67b76 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 19 Aug 2017 17:44:35 +0200 Subject: [PATCH 101/180] t/171-config-migrate: escape braces for newer Perl versions --- testcases/t/171-config-migrate.t | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testcases/t/171-config-migrate.t b/testcases/t/171-config-migrate.t index 5bd21128..d855e818 100644 --- a/testcases/t/171-config-migrate.t +++ b/testcases/t/171-config-migrate.t @@ -153,7 +153,7 @@ ok(line_exists($output, qr|^bindsym Mod1\+s restart$|), 'restart unchanged'); ok(line_exists($output, qr|^bindsym Mod1\+s reload$|), 'reload unchanged'); ok(line_exists($output, qr|^bindsym Mod1\+s exit$|), 'exit unchanged'); ok(line_exists($output, qr|^bindcode Mod1\+c exec /usr/bin/urxvt$|), 'bind changed to bindcode'); -ok(line_exists($output, qr|^mode "asdf" {$|), 'mode asdf unchanged'); +ok(line_exists($output, qr|^mode "asdf" \{$|), 'mode asdf unchanged'); ok(line_exists($output, qr|^bindcode 36 mode \"default\"$|), 'mode default unchanged'); ok(line_exists($output, qr|^}$|), 'closing mode bracket still there'); @@ -336,13 +336,13 @@ ok(line_exists($output, qr|^bindsym Mod1\+3 move container to workspace work|), ##################################################################### $output = migrate_config(''); -ok(line_exists($output, qr|bar {|), 'i3bar added'); +ok(line_exists($output, qr|bar \{|), 'i3bar added'); $output = migrate_config('workspace_bar enable'); -ok(line_exists($output, qr|bar {|), 'i3bar added'); +ok(line_exists($output, qr|bar \{|), 'i3bar added'); $output = migrate_config('workspace_bar no'); -ok(!line_exists($output, qr|bar {|), 'no i3bar added'); +ok(!line_exists($output, qr|bar \{|), 'no i3bar added'); ##################################################################### # check whether the mode command gets quotes From 632bdb7d2a3ab9b779cc7e3e33b88dff419265ee Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 13 Mar 2010 17:11:09 +0100 Subject: [PATCH 102/180] Initial commit --- Changes | 5 + MANIFEST | 9 ++ Makefile.PL | 12 +++ README | 55 ++++++++++ ignore.txt | 12 +++ lib/AnyEvent/I3.pm | 257 +++++++++++++++++++++++++++++++++++++++++++++ t/00-load.t | 10 ++ t/01-workspaces.t | 12 +++ t/boilerplate.t | 55 ++++++++++ t/manifest.t | 13 +++ t/pod-coverage.t | 18 ++++ t/pod.t | 12 +++ 12 files changed, 470 insertions(+) create mode 100644 Changes create mode 100644 MANIFEST create mode 100644 Makefile.PL create mode 100644 README create mode 100644 ignore.txt create mode 100644 lib/AnyEvent/I3.pm create mode 100644 t/00-load.t create mode 100644 t/01-workspaces.t create mode 100644 t/boilerplate.t create mode 100644 t/manifest.t create mode 100644 t/pod-coverage.t create mode 100644 t/pod.t diff --git a/Changes b/Changes new file mode 100644 index 00000000..7071c737 --- /dev/null +++ b/Changes @@ -0,0 +1,5 @@ +Revision history for AnyEvent-I3 + +0.01 Date/time + First version, released on an unsuspecting world. + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 00000000..7087a287 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,9 @@ +Changes +MANIFEST +Makefile.PL +README +lib/AnyEvent/I3.pm +t/00-load.t +t/manifest.t +t/pod-coverage.t +t/pod.t diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 00000000..2082ce24 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,12 @@ +use inc::Module::Install; + +name 'AnyEvent-I3'; +all_from 'lib/AnyEvent/I3.pm'; +author 'Michael Stapelberg'; + +requires 'AnyEvent'; +requires 'AnyEvent::Handle'; +requires 'AnyEvent::Socket'; +requires 'JSON::XS'; + +WriteAll; diff --git a/README b/README new file mode 100644 index 00000000..3aff4b15 --- /dev/null +++ b/README @@ -0,0 +1,55 @@ +AnyEvent-I3 + +The README is used to introduce the module and provide instructions on +how to install the module, any machine dependencies it may have (for +example C compilers and installed libraries) and any other information +that should be provided before the module is installed. + +A README file is required for CPAN modules since CPAN extracts the README +file from a module distribution so that people browsing the archive +can use it to get an idea of the module's uses. It is usually a good idea +to provide version information here so that people can decide whether +fixes for the module are worth downloading. + + +INSTALLATION + +To install this module, run the following commands: + + perl Makefile.PL + make + make test + make install + +SUPPORT AND DOCUMENTATION + +After installing, you can find documentation for this module with the +perldoc command. + + perldoc AnyEvent::I3 + +You can also look for information at: + + RT, CPAN's request tracker + http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3 + + AnnoCPAN, Annotated CPAN documentation + http://annocpan.org/dist/AnyEvent-I3 + + CPAN Ratings + http://cpanratings.perl.org/d/AnyEvent-I3 + + Search CPAN + http://search.cpan.org/dist/AnyEvent-I3/ + + +LICENSE AND COPYRIGHT + +Copyright (C) 2010 Michael Stapelberg + +This program is free software; you can redistribute it and/or modify it +under the terms of either: the GNU General Public License as published +by the Free Software Foundation; or the Artistic License. + +See http://dev.perl.org/licenses/ for more information. + diff --git a/ignore.txt b/ignore.txt new file mode 100644 index 00000000..01d97f8a --- /dev/null +++ b/ignore.txt @@ -0,0 +1,12 @@ +blib* +Makefile +Makefile.old +Build +Build.bat +_build* +pm_to_blib* +*.tar.gz +.lwpcookies +cover_db +pod2htm*.tmp +AnyEvent-I3-* diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm new file mode 100644 index 00000000..f365d394 --- /dev/null +++ b/lib/AnyEvent/I3.pm @@ -0,0 +1,257 @@ +package AnyEvent::I3; +# vim:ts=4:sw=4:expandtab + +use strict; +use warnings; +use JSON::XS; +use AnyEvent::Handle; +use AnyEvent::Socket; +use AnyEvent; + +=head1 NAME + +AnyEvent::I3 - communicate with the i3 window manager + +=cut + +our $VERSION = '0.01'; + +=head1 VERSION + +Version 0.01 + +=head1 SYNOPSIS + +This module connects to the i3 window manager using the UNIX socket based +IPC interface it provides (if enabled in the configuration file). You can +then subscribe to events or send messages and receive their replies. + +Note that as soon as you subscribe to some kind of event, you should B<NOT> +send any more messages as race conditions might occur. Instead, open another +connection for that. + + use AnyEvent::I3; + + my $i3 = i3("/tmp/i3-ipc.sock"); + + $i3->connect->recv; + say "Connected to i3"; + + my $workspaces = $i3->message(1)->recv; + say "Currently, you use " . @{$workspaces} . " workspaces"; + +=head1 EXPORT + +=head2 $i3 = i3([ $path ]); + +Creates a new C<AnyEvent::I3> object and returns it. C<path> is the path of +the UNIX socket to connect to. + +=head1 SUBROUTINES/METHODS + +=cut + + +use Exporter; +use base 'Exporter'; + +our @EXPORT = qw(i3); + + +my $magic = "i3-ipc"; + +# TODO: auto-generate this from the header file? (i3/ipc.h) +my $event_mask = (1 << 31); +my %events = ( + workspace => ($event_mask | 0), +); + +sub bytelength { + my ($scalar) = @_; + use bytes; + length($scalar) +} + +sub i3 { + AnyEvent::I3->new(@_) +} + +=head2 $i3 = AnyEvent::I3->new([ $path ]) + +Creates a new C<AnyEvent::I3> object and returns it. C<path> is the path of +the UNIX socket to connect to. + +=cut +sub new { + my ($class, $path) = @_; + + $path ||= '/tmp/i3-ipc.sock'; + + bless { path => $path } => $class; +} + +=head2 $i3->connect + +Establishes the connection to i3. Returns an C<AnyEvent::CondVar> which will +be triggered as soon as the connection has been established. + +=cut +sub connect { + my ($self) = @_; + my $hdl; + my $cv = AnyEvent->condvar; + + tcp_connect "unix/", $self->{path}, sub { + my ($fh) = @_; + + $self->{ipchdl} = AnyEvent::Handle->new( + fh => $fh, + on_read => sub { my ($hdl) = @_; $self->data_available($hdl) } + ); + + $cv->send + }; + + $cv +} + +sub data_available { + my ($self, $hdl) = @_; + + $hdl->unshift_read( + chunk => length($magic) + 4 + 4, + sub { + my $header = $_[1]; + # Unpack message length and read the payload + my ($len, $type) = unpack("LL", substr($header, length($magic))); + $hdl->unshift_read( + chunk => $len, + sub { $self->handle_i3_message($type, $_[1]) } + ); + } + ); +} + +sub handle_i3_message { + my ($self, $type, $payload) = @_; + + return unless defined($self->{callbacks}->{$type}); + + my $cb = $self->{callbacks}->{$type}; + $cb->(decode_json $payload); +} + +=head2 $i3->subscribe(\%callbacks) + +Subscribes to the given event types. This function awaits a hashref with the +key being the name of the event and the value being a callback. + + $i3->subscribe({ + workspace => sub { say "Workspaces changed" } + }); + +=cut +sub subscribe { + my ($self, $callbacks) = @_; + + my $payload = encode_json [ keys %{$callbacks} ]; + my $message = $magic . pack("LL", bytelength($payload), 2) . $payload; + $self->{ipchdl}->push_write($message); + + # Register callbacks for each message type + for my $key (keys %{$callbacks}) { + my $type = $events{$key}; + $self->{callbacks}->{$type} = $callbacks->{$key}; + } +} + +=head2 $i3->message($type, $content) + +Sends a message of the specified C<type> to i3, possibly containing the data +structure C<payload>, if specified. + + my $cv = $i3->message(0, "reload"); + my $reply = $cv->recv; + if ($reply->{success}) { + say "Configuration successfully reloaded"; + } + +=cut +sub message { + my ($self, $type, $content) = @_; + + die "No message type specified" unless $type; + + my $payload = ""; + if ($content) { + if (ref($content) eq "SCALAR") { + $payload = $content; + } else { + $payload = encode_json $content; + } + } + my $message = $magic . pack("LL", bytelength($payload), $type) . $payload; + $self->{ipchdl}->push_write($message); + + my $cv = AnyEvent->condvar; + + # We don’t preserve the old callback as it makes no sense to + # have a callback on message reply types (only on events) + $self->{callbacks}->{$type} = + sub { + my ($reply) = @_; + $cv->send($reply); + undef $self->{callbacks}->{$type}; + }; + + $cv +} + +=head1 AUTHOR + +Michael Stapelberg, C<< <michael at stapelberg.de> >> + +=head1 BUGS + +Please report any bugs or feature requests to C<bug-anyevent-i3 at rt.cpan.org>, or through +the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=AnyEvent-I3>. I will be notified, and then you'll +automatically be notified of progress on your bug as I make changes. + +=head1 SUPPORT + +You can find documentation for this module with the perldoc command. + + perldoc AnyEvent::I3 + +You can also look for information at: + +=over 2 + +=item * RT: CPAN's request tracker + +L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3> + +=item * The i3 window manager website + +L<http://i3.zekjur.net/> + +=back + + +=head1 ACKNOWLEDGEMENTS + + +=head1 LICENSE AND COPYRIGHT + +Copyright 2010 Michael Stapelberg. + +This program is free software; you can redistribute it and/or modify it +under the terms of either: the GNU General Public License as published +by the Free Software Foundation; or the Artistic License. + +See http://dev.perl.org/licenses/ for more information. + + +=cut + +1; # End of AnyEvent::I3 diff --git a/t/00-load.t b/t/00-load.t new file mode 100644 index 00000000..4bf6151e --- /dev/null +++ b/t/00-load.t @@ -0,0 +1,10 @@ +#!perl -T + +use Test::More tests => 1; + +BEGIN { + use_ok( 'AnyEvent::I3' ) || print "Bail out! +"; +} + +diag( "Testing AnyEvent::I3 $AnyEvent::I3::VERSION, Perl $], $^X" ); diff --git a/t/01-workspaces.t b/t/01-workspaces.t new file mode 100644 index 00000000..5e708d13 --- /dev/null +++ b/t/01-workspaces.t @@ -0,0 +1,12 @@ +#!perl -T + +use Test::More tests => 1; +use AnyEvent::I3; + +my $i3 = i3(); +my $cv = $i3->connect; +$cv->recv; + +ok(1, "connected"); + +diag( "Testing AnyEvent::I3 $AnyEvent::I3::VERSION, Perl $], $^X" ); diff --git a/t/boilerplate.t b/t/boilerplate.t new file mode 100644 index 00000000..effb65b6 --- /dev/null +++ b/t/boilerplate.t @@ -0,0 +1,55 @@ +#!perl -T + +use strict; +use warnings; +use Test::More tests => 3; + +sub not_in_file_ok { + my ($filename, %regex) = @_; + open( my $fh, '<', $filename ) + or die "couldn't open $filename for reading: $!"; + + my %violated; + + while (my $line = <$fh>) { + while (my ($desc, $regex) = each %regex) { + if ($line =~ $regex) { + push @{$violated{$desc}||=[]}, $.; + } + } + } + + if (%violated) { + fail("$filename contains boilerplate text"); + diag "$_ appears on lines @{$violated{$_}}" for keys %violated; + } else { + pass("$filename contains no boilerplate text"); + } +} + +sub module_boilerplate_ok { + my ($module) = @_; + not_in_file_ok($module => + 'the great new $MODULENAME' => qr/ - The great new /, + 'boilerplate description' => qr/Quick summary of what the module/, + 'stub function definition' => qr/function[12]/, + ); +} + +TODO: { + local $TODO = "Need to replace the boilerplate text"; + + not_in_file_ok(README => + "The README is used..." => qr/The README is used/, + "'version information here'" => qr/to provide version information/, + ); + + not_in_file_ok(Changes => + "placeholder date/time" => qr(Date/time) + ); + + module_boilerplate_ok('lib/AnyEvent/I3.pm'); + + +} + diff --git a/t/manifest.t b/t/manifest.t new file mode 100644 index 00000000..45eb83fd --- /dev/null +++ b/t/manifest.t @@ -0,0 +1,13 @@ +#!perl -T + +use strict; +use warnings; +use Test::More; + +unless ( $ENV{RELEASE_TESTING} ) { + plan( skip_all => "Author tests not required for installation" ); +} + +eval "use Test::CheckManifest 0.9"; +plan skip_all => "Test::CheckManifest 0.9 required" if $@; +ok_manifest(); diff --git a/t/pod-coverage.t b/t/pod-coverage.t new file mode 100644 index 00000000..fc40a57c --- /dev/null +++ b/t/pod-coverage.t @@ -0,0 +1,18 @@ +use strict; +use warnings; +use Test::More; + +# Ensure a recent version of Test::Pod::Coverage +my $min_tpc = 1.08; +eval "use Test::Pod::Coverage $min_tpc"; +plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage" + if $@; + +# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, +# but older versions don't recognize some common documentation styles +my $min_pc = 0.18; +eval "use Pod::Coverage $min_pc"; +plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" + if $@; + +all_pod_coverage_ok(); diff --git a/t/pod.t b/t/pod.t new file mode 100644 index 00000000..ee8b18ad --- /dev/null +++ b/t/pod.t @@ -0,0 +1,12 @@ +#!perl -T + +use strict; +use warnings; +use Test::More; + +# Ensure a recent version of Test::Pod +my $min_tp = 1.22; +eval "use Test::Pod $min_tp"; +plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; + +all_pod_files_ok(); From 5c9e2833b6d8f3128d3c94ebb7113a7a7e6f50d3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 13 Mar 2010 17:19:38 +0100 Subject: [PATCH 103/180] prefix internal subs with _ --- lib/AnyEvent/I3.pm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index f365d394..c11acf01 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -66,7 +66,7 @@ my %events = ( workspace => ($event_mask | 0), ); -sub bytelength { +sub _bytelength { my ($scalar) = @_; use bytes; length($scalar) @@ -106,7 +106,7 @@ sub connect { $self->{ipchdl} = AnyEvent::Handle->new( fh => $fh, - on_read => sub { my ($hdl) = @_; $self->data_available($hdl) } + on_read => sub { my ($hdl) = @_; $self->_data_available($hdl) } ); $cv->send @@ -115,7 +115,7 @@ sub connect { $cv } -sub data_available { +sub _data_available { my ($self, $hdl) = @_; $hdl->unshift_read( @@ -126,13 +126,13 @@ sub data_available { my ($len, $type) = unpack("LL", substr($header, length($magic))); $hdl->unshift_read( chunk => $len, - sub { $self->handle_i3_message($type, $_[1]) } + sub { $self->_handle_i3_message($type, $_[1]) } ); } ); } -sub handle_i3_message { +sub _handle_i3_message { my ($self, $type, $payload) = @_; return unless defined($self->{callbacks}->{$type}); @@ -155,7 +155,7 @@ sub subscribe { my ($self, $callbacks) = @_; my $payload = encode_json [ keys %{$callbacks} ]; - my $message = $magic . pack("LL", bytelength($payload), 2) . $payload; + my $message = $magic . pack("LL", _bytelength($payload), 2) . $payload; $self->{ipchdl}->push_write($message); # Register callbacks for each message type @@ -190,7 +190,7 @@ sub message { $payload = encode_json $content; } } - my $message = $magic . pack("LL", bytelength($payload), $type) . $payload; + my $message = $magic . pack("LL", _bytelength($payload), $type) . $payload; $self->{ipchdl}->push_write($message); my $cv = AnyEvent->condvar; From 7d92e2c3e2589560632fc49446466f5a6aa0a27d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 13 Mar 2010 17:21:52 +0100 Subject: [PATCH 104/180] kill boilerplate --- Changes | 2 +- README | 25 +++++-------------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/Changes b/Changes index 7071c737..0dc96033 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,5 @@ Revision history for AnyEvent-I3 -0.01 Date/time +0.01 2010-03-13 First version, released on an unsuspecting world. diff --git a/README b/README index 3aff4b15..4658ba16 100644 --- a/README +++ b/README @@ -1,16 +1,8 @@ AnyEvent-I3 -The README is used to introduce the module and provide instructions on -how to install the module, any machine dependencies it may have (for -example C compilers and installed libraries) and any other information -that should be provided before the module is installed. - -A README file is required for CPAN modules since CPAN extracts the README -file from a module distribution so that people browsing the archive -can use it to get an idea of the module's uses. It is usually a good idea -to provide version information here so that people can decide whether -fixes for the module are worth downloading. - +This module connects to the i3 window manager using the UNIX socket based +IPC interface it provides (if enabled in the configuration file). You can +then subscribe to events or send messages and receive their replies. INSTALLATION @@ -33,14 +25,8 @@ You can also look for information at: RT, CPAN's request tracker http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3 - AnnoCPAN, Annotated CPAN documentation - http://annocpan.org/dist/AnyEvent-I3 - - CPAN Ratings - http://cpanratings.perl.org/d/AnyEvent-I3 - - Search CPAN - http://search.cpan.org/dist/AnyEvent-I3/ + The i3 window manager website + http://i3.zekjur.net/ LICENSE AND COPYRIGHT @@ -52,4 +38,3 @@ under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. - From 279528a863906638b677a319bacf3a362d168419 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 13 Mar 2010 17:38:32 +0100 Subject: [PATCH 105/180] Return connection status in condvar in $i3->connect --- lib/AnyEvent/I3.pm | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index c11acf01..bf25bda4 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -60,6 +60,7 @@ our @EXPORT = qw(i3); my $magic = "i3-ipc"; +# TODO: export constants for message types # TODO: auto-generate this from the header file? (i3/ipc.h) my $event_mask = (1 << 31); my %events = ( @@ -93,7 +94,12 @@ sub new { =head2 $i3->connect Establishes the connection to i3. Returns an C<AnyEvent::CondVar> which will -be triggered as soon as the connection has been established. +be triggered with a boolean (true if the connection was established) as soon as +the connection has been established. + + if ($i3->connect->recv) { + say "Connected to i3"; + } =cut sub connect { @@ -104,12 +110,14 @@ sub connect { tcp_connect "unix/", $self->{path}, sub { my ($fh) = @_; + return $cv->send(0) unless $fh; + $self->{ipchdl} = AnyEvent::Handle->new( fh => $fh, on_read => sub { my ($hdl) = @_; $self->_data_available($hdl) } ); - $cv->send + $cv->send(1) }; $cv From 1044c9814b0fa3b49bf81b33a39dac7ec2fc1273 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 13 Mar 2010 17:38:59 +0100 Subject: [PATCH 106/180] Expand testcase for the get_workspaces message (needs i3 to be running) --- t/01-workspaces.t | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/t/01-workspaces.t b/t/01-workspaces.t index 5e708d13..f3206d89 100644 --- a/t/01-workspaces.t +++ b/t/01-workspaces.t @@ -1,12 +1,29 @@ #!perl -T +# vim:ts=4:sw=4:expandtab -use Test::More tests => 1; +use Test::More tests => 3; use AnyEvent::I3; +use AnyEvent; my $i3 = i3(); -my $cv = $i3->connect; -$cv->recv; +my $cv = AnyEvent->condvar; -ok(1, "connected"); +# Try to connect to i3 +$i3->connect->cb(sub { my ($v) = @_; $cv->send($v->recv) }); + +# But cancel if we are not connected after 0.5 seconds +my $t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0) }); +my $connected = $cv->recv; + +SKIP: { + skip 'No connection to i3', 3 unless $connected; + + my $workspaces = $i3->message(1)->recv; + isa_ok($workspaces, 'ARRAY'); + + ok(@{$workspaces} > 0, 'More than zero workspaces found'); + + ok(defined(@{$workspaces}[0]->{num}), 'JSON deserialized'); +} diag( "Testing AnyEvent::I3 $AnyEvent::I3::VERSION, Perl $], $^X" ); From e35d6039d75cde4d0002db9f4b4d97f1ec4522f5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 13 Mar 2010 18:17:12 +0100 Subject: [PATCH 107/180] Provide constants for message types --- lib/AnyEvent/I3.pm | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index bf25bda4..d2fae1c7 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -30,14 +30,14 @@ Note that as soon as you subscribe to some kind of event, you should B<NOT> send any more messages as race conditions might occur. Instead, open another connection for that. - use AnyEvent::I3; + use AnyEvent::I3 qw(:all); my $i3 = i3("/tmp/i3-ipc.sock"); $i3->connect->recv; say "Connected to i3"; - my $workspaces = $i3->message(1)->recv; + my $workspaces = $i3->message(TYPE_GET_WORKSPACES)->recv; say "Currently, you use " . @{$workspaces} . " workspaces"; =head1 EXPORT @@ -51,16 +51,23 @@ the UNIX socket to connect to. =cut - use Exporter; use base 'Exporter'; our @EXPORT = qw(i3); +use constant TYPE_COMMAND => 0; +use constant TYPE_GET_WORKSPACES => 1; +use constant TYPE_SUBSCRIBE => 2; + +our %EXPORT_TAGS = ( 'all' => [ + qw(TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE) +] ); + +our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); my $magic = "i3-ipc"; -# TODO: export constants for message types # TODO: auto-generate this from the header file? (i3/ipc.h) my $event_mask = (1 << 31); my %events = ( @@ -178,8 +185,7 @@ sub subscribe { Sends a message of the specified C<type> to i3, possibly containing the data structure C<payload>, if specified. - my $cv = $i3->message(0, "reload"); - my $reply = $cv->recv; + my $reply = $i3->message(TYPE_COMMAND, "reload")->recv; if ($reply->{success}) { say "Configuration successfully reloaded"; } From 4c6b8f91e02de962edbfea7278419329ad48aa08 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 13 Mar 2010 18:27:32 +0100 Subject: [PATCH 108/180] Update MANIFEST --- MANIFEST | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/MANIFEST b/MANIFEST index 7087a287..61ca8577 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,9 +1,22 @@ Changes -MANIFEST -Makefile.PL -README +ignore.txt +inc/Module/Install.pm +inc/Module/Install/Base.pm +inc/Module/Install/Can.pm +inc/Module/Install/Fetch.pm +inc/Module/Install/Makefile.pm +inc/Module/Install/Metadata.pm +inc/Module/Install/Win32.pm +inc/Module/Install/WriteAll.pm lib/AnyEvent/I3.pm +Makefile.PL +MANIFEST +META.yml +README t/00-load.t +t/01-workspaces.t +t/02-constants.t +t/boilerplate.t t/manifest.t t/pod-coverage.t t/pod.t From f6a26056104ea31f273c492ae13b4f534ec58c3a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 13 Mar 2010 18:30:46 +0100 Subject: [PATCH 109/180] update MANIFEST correctly (use MANIFEST.SKIP instead of ignore.txt) --- MANIFEST | 5 +---- MANIFEST.SKIP | 11 +++++++++++ ignore.txt | 12 ------------ 3 files changed, 12 insertions(+), 16 deletions(-) create mode 100644 MANIFEST.SKIP delete mode 100644 ignore.txt diff --git a/MANIFEST b/MANIFEST index 61ca8577..c9c9c27c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,21 +1,18 @@ Changes -ignore.txt inc/Module/Install.pm inc/Module/Install/Base.pm inc/Module/Install/Can.pm inc/Module/Install/Fetch.pm -inc/Module/Install/Makefile.pm inc/Module/Install/Metadata.pm inc/Module/Install/Win32.pm inc/Module/Install/WriteAll.pm lib/AnyEvent/I3.pm -Makefile.PL MANIFEST +MANIFEST.SKIP META.yml README t/00-load.t t/01-workspaces.t -t/02-constants.t t/boilerplate.t t/manifest.t t/pod-coverage.t diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP new file mode 100644 index 00000000..f58f7ef8 --- /dev/null +++ b/MANIFEST.SKIP @@ -0,0 +1,11 @@ +^\.git/ +\.bak$ +blib/ +Makefile +Makefile.old +Build +Build.bat +^pm_to_blib +\.tar\.gz$ +^pod2htm(.*).tmp$ +^AnyEvent-I3- diff --git a/ignore.txt b/ignore.txt deleted file mode 100644 index 01d97f8a..00000000 --- a/ignore.txt +++ /dev/null @@ -1,12 +0,0 @@ -blib* -Makefile -Makefile.old -Build -Build.bat -_build* -pm_to_blib* -*.tar.gz -.lwpcookies -cover_db -pod2htm*.tmp -AnyEvent-I3-* From e3f0e5b01ca13080a6b89d6f3c7da16e70782230 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Fri, 19 Mar 2010 22:35:19 +0100 Subject: [PATCH 110/180] Add new constants --- lib/AnyEvent/I3.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index d2fae1c7..9c8cbbdc 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -59,9 +59,10 @@ our @EXPORT = qw(i3); use constant TYPE_COMMAND => 0; use constant TYPE_GET_WORKSPACES => 1; use constant TYPE_SUBSCRIBE => 2; +use constant TYPE_GET_OUTPUTS => 3; our %EXPORT_TAGS = ( 'all' => [ - qw(TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE) + qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -72,6 +73,7 @@ my $magic = "i3-ipc"; my $event_mask = (1 << 31); my %events = ( workspace => ($event_mask | 0), + output => ($event_mask | 1), ); sub _bytelength { From 8b2db9a238ae2c37968646af74ca97b1f2fb8d82 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 22 Mar 2010 21:37:33 +0100 Subject: [PATCH 111/180] Bugfix: Handle message_type == 0 --- lib/AnyEvent/I3.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 9c8cbbdc..7296a9c5 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -196,7 +196,7 @@ structure C<payload>, if specified. sub message { my ($self, $type, $content) = @_; - die "No message type specified" unless $type; + die "No message type specified" unless defined($type); my $payload = ""; if ($content) { From 460f09915f61d4af44eec3c4ef9b729e6efac501 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 22 Mar 2010 21:37:44 +0100 Subject: [PATCH 112/180] Get rid of _bytelength, use encode_utf8 and length instead. Correctly check for scalar --- lib/AnyEvent/I3.pm | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 7296a9c5..2addeb34 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -7,6 +7,7 @@ use JSON::XS; use AnyEvent::Handle; use AnyEvent::Socket; use AnyEvent; +use Encode; =head1 NAME @@ -76,12 +77,6 @@ my %events = ( output => ($event_mask | 1), ); -sub _bytelength { - my ($scalar) = @_; - use bytes; - length($scalar) -} - sub i3 { AnyEvent::I3->new(@_) } @@ -172,7 +167,7 @@ sub subscribe { my ($self, $callbacks) = @_; my $payload = encode_json [ keys %{$callbacks} ]; - my $message = $magic . pack("LL", _bytelength($payload), 2) . $payload; + my $message = $magic . pack("LL", length($payload), 2) . $payload; $self->{ipchdl}->push_write($message); # Register callbacks for each message type @@ -185,7 +180,8 @@ sub subscribe { =head2 $i3->message($type, $content) Sends a message of the specified C<type> to i3, possibly containing the data -structure C<payload>, if specified. +structure C<content> (or C<content>, encoded as utf8, if C<content> is a +scalar), if specified. my $reply = $i3->message(TYPE_COMMAND, "reload")->recv; if ($reply->{success}) { @@ -200,13 +196,14 @@ sub message { my $payload = ""; if ($content) { - if (ref($content) eq "SCALAR") { - $payload = $content; + if (not ref($content)) { + # Convert from Perl’s internal encoding to UTF8 octets + $payload = encode_utf8($content); } else { $payload = encode_json $content; } } - my $message = $magic . pack("LL", _bytelength($payload), $type) . $payload; + my $message = $magic . pack("LL", length($payload), $type) . $payload; $self->{ipchdl}->push_write($message); my $cv = AnyEvent->condvar; From 4ba7259f6a60be3ba350d14b5682b9d99807aa57 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 22 Mar 2010 21:42:35 +0100 Subject: [PATCH 113/180] use constant instead of magic number --- lib/AnyEvent/I3.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 2addeb34..e69604a8 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -167,7 +167,8 @@ sub subscribe { my ($self, $callbacks) = @_; my $payload = encode_json [ keys %{$callbacks} ]; - my $message = $magic . pack("LL", length($payload), 2) . $payload; + my $len = length($payload); + my $message = $magic . pack("LL", $len, TYPE_SUBSCRIBE) . $payload; $self->{ipchdl}->push_write($message); # Register callbacks for each message type From 98e32d39a561aefc39ee2c676a8b332c36b50765 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 22 Mar 2010 21:43:05 +0100 Subject: [PATCH 114/180] reformat perldoc paragraph --- lib/AnyEvent/I3.pm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index e69604a8..3a82552d 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -227,9 +227,11 @@ Michael Stapelberg, C<< <michael at stapelberg.de> >> =head1 BUGS -Please report any bugs or feature requests to C<bug-anyevent-i3 at rt.cpan.org>, or through -the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=AnyEvent-I3>. I will be notified, and then you'll -automatically be notified of progress on your bug as I make changes. +Please report any bugs or feature requests to C<bug-anyevent-i3 at +rt.cpan.org>, or through the web interface at +L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=AnyEvent-I3>. I will be +notified, and then you'll automatically be notified of progress on your bug as +I make changes. =head1 SUPPORT From 7ffa4bea31a11ea79f89a27bd7d6897182590701 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 22 Mar 2010 21:50:41 +0100 Subject: [PATCH 115/180] Use only 'import' of 'Exporter' --- lib/AnyEvent/I3.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 3a82552d..bb7c3d92 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -52,7 +52,7 @@ the UNIX socket to connect to. =cut -use Exporter; +use Exporter qw(import); use base 'Exporter'; our @EXPORT = qw(i3); From b57fca1ef9b61b9cd6bbb519adf59f146cabe5fb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 22 Mar 2010 22:22:46 +0100 Subject: [PATCH 116/180] kill left-over variable --- lib/AnyEvent/I3.pm | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index bb7c3d92..282f24a5 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -108,7 +108,6 @@ the connection has been established. =cut sub connect { my ($self) = @_; - my $hdl; my $cv = AnyEvent->condvar; tcp_connect "unix/", $self->{path}, sub { From 1aac4d3f14864b9c08f50e3852c59d9677f12a0e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 22 Mar 2010 22:33:26 +0100 Subject: [PATCH 117/180] Use $self->message in subscribe --- lib/AnyEvent/I3.pm | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 282f24a5..310fa5ef 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -157,24 +157,25 @@ sub _handle_i3_message { Subscribes to the given event types. This function awaits a hashref with the key being the name of the event and the value being a callback. - $i3->subscribe({ + my %callbacks = ( workspace => sub { say "Workspaces changed" } - }); + ); + + if ($i3->subscribe(\%callbacks)->recv->{success}) + say "Successfully subscribed"; + } =cut sub subscribe { my ($self, $callbacks) = @_; - my $payload = encode_json [ keys %{$callbacks} ]; - my $len = length($payload); - my $message = $magic . pack("LL", $len, TYPE_SUBSCRIBE) . $payload; - $self->{ipchdl}->push_write($message); - # Register callbacks for each message type for my $key (keys %{$callbacks}) { my $type = $events{$key}; $self->{callbacks}->{$type} = $callbacks->{$key}; } + + $self->message(TYPE_SUBSCRIBE, [ keys %{$callbacks} ]) } =head2 $i3->message($type, $content) From 45eef6bdf7e2718a2e0cf3e4ba3b247b2eb5199f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 22 Mar 2010 23:22:42 +0100 Subject: [PATCH 118/180] Remove obsolete paragraph about the need for a second connection Due to the event-based handling of incoming data from i3 we do not suffer from this problem. --- lib/AnyEvent/I3.pm | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 310fa5ef..d763357c 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -27,10 +27,6 @@ This module connects to the i3 window manager using the UNIX socket based IPC interface it provides (if enabled in the configuration file). You can then subscribe to events or send messages and receive their replies. -Note that as soon as you subscribe to some kind of event, you should B<NOT> -send any more messages as race conditions might occur. Instead, open another -connection for that. - use AnyEvent::I3 qw(:all); my $i3 = i3("/tmp/i3-ipc.sock"); From d137f834528f99fcb91adeda73bd29a27e5c506f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 22 Mar 2010 23:23:07 +0100 Subject: [PATCH 119/180] Add sugar methods for easier usage --- lib/AnyEvent/I3.pm | 74 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index d763357c..5df58257 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -31,12 +31,20 @@ then subscribe to events or send messages and receive their replies. my $i3 = i3("/tmp/i3-ipc.sock"); - $i3->connect->recv; + $i3->connect->recv or die "Error connecting"; say "Connected to i3"; my $workspaces = $i3->message(TYPE_GET_WORKSPACES)->recv; say "Currently, you use " . @{$workspaces} . " workspaces"; +...or, using the sugar methods: + + use AnyEvent::I3; + + my $i3 = i3; + my $workspaces = $i3->workspaces->recv; + say "Currently, you use " . @{$workspaces} . " workspaces"; + =head1 EXPORT =head2 $i3 = i3([ $path ]); @@ -217,6 +225,70 @@ sub message { $cv } +=head1 SUGAR METHODS + +These methods intend to make your scripts as beautiful as possible. All of +them automatically establish a connection to i3 blockingly (if it does not +already exist). + +=cut + +sub _ensure_connection { + my ($self) = @_; + + return if defined($self->{ipchdl}); + + $self->connect->recv or die "Unable to connect to i3" +} + +=head2 get_workspaces + +Gets the current workspaces from i3. + + my $ws = i3->get_workspaces->recv; + say Dumper($ws); + +=cut +sub get_workspaces { + my ($self) = @_; + + $self->_ensure_connection; + + $self->message(TYPE_GET_WORKSPACES) +} + +=head2 get_outputs + +Gets the current outputs from i3. + + my $outs = i3->get_outputs->recv; + say Dumper($outs); + +=cut +sub get_outputs { + my ($self) = @_; + + $self->_ensure_connection; + + $self->message(TYPE_GET_OUTPUTS) +} + +=head2 command($content) + +Makes i3 execute the given command + + my $reply = i3->command("reload")->recv; + die "command failed" unless $reply->{success}; + +=cut +sub command { + my ($self, $content) = @_; + + $self->_ensure_connection; + + $self->message(TYPE_COMMAND, $content) +} + =head1 AUTHOR Michael Stapelberg, C<< <michael at stapelberg.de> >> From e34675c3c7ee62a8d77e20627c1c824272ca16ab Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Tue, 23 Mar 2010 02:04:28 +0100 Subject: [PATCH 120/180] Implement _error callback --- lib/AnyEvent/I3.pm | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 5df58257..a09c7512 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -41,8 +41,7 @@ then subscribe to events or send messages and receive their replies. use AnyEvent::I3; - my $i3 = i3; - my $workspaces = $i3->workspaces->recv; + my $workspaces = i3->workspaces->recv; say "Currently, you use " . @{$workspaces} . " workspaces"; =head1 EXPORT @@ -79,6 +78,7 @@ my $event_mask = (1 << 31); my %events = ( workspace => ($event_mask | 0), output => ($event_mask | 1), + _error => 0xFFFFFFFF, ); sub i3 { @@ -121,7 +121,25 @@ sub connect { $self->{ipchdl} = AnyEvent::Handle->new( fh => $fh, - on_read => sub { my ($hdl) = @_; $self->_data_available($hdl) } + on_read => sub { my ($hdl) = @_; $self->_data_available($hdl) }, + on_error => sub { + my ($hdl, $fatal, $msg) = @_; + delete $self->{ipchdl}; + $hdl->destroy; + + my $cb = $self->{callbacks}; + + # Trigger all one-time callbacks with undef + for my $type (keys %{$cb}) { + next if ($type & $event_mask) == $event_mask; + $cb->{$type}->(); + } + + # Trigger _error callback, if set + my $type = $events{_error}; + return unless defined($cb->{$type}); + $cb->{$type}->($msg); + } ); $cv->send(1) @@ -154,6 +172,12 @@ sub _handle_i3_message { my $cb = $self->{callbacks}->{$type}; $cb->(decode_json $payload); + + return if ($type & $event_mask) == $event_mask; + + # If this was a one-time callback, we delete it + # (when connection is lost, all one-time callbacks get triggered) + delete $self->{callbacks}->{$type}; } =head2 $i3->subscribe(\%callbacks) @@ -169,6 +193,20 @@ key being the name of the event and the value being a callback. say "Successfully subscribed"; } +The special callback with name C<_error> is called when the connection to i3 +is killed (because of a crash, exit or restart of i3 most likely). You can +use it to print an appropriate message and exit cleanly or to try to reconnect. + + my %callbacks = ( + _error => sub { + my ($msg) = @_; + say "I am sorry. I am so sorry: $msg"; + exit 1; + } + ); + + $i3->subscribe(\%callbacks)->recv; + =cut sub subscribe { my ($self, $callbacks) = @_; @@ -199,6 +237,8 @@ sub message { die "No message type specified" unless defined($type); + die "No connection to i3" unless defined($self->{ipchdl}); + my $payload = ""; if ($content) { if (not ref($content)) { From b9c83fbd2623e62a637326470827372f05c1eddc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Tue, 23 Mar 2010 02:05:33 +0100 Subject: [PATCH 121/180] add testcase for sugar methods --- t/02-sugar.t | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 t/02-sugar.t diff --git a/t/02-sugar.t b/t/02-sugar.t new file mode 100644 index 00000000..a3e2cc79 --- /dev/null +++ b/t/02-sugar.t @@ -0,0 +1,29 @@ +#!perl -T +# vim:ts=4:sw=4:expandtab + +use Test::More tests => 3; +use AnyEvent::I3; +use AnyEvent; + +my $i3 = i3(); +my $cv = AnyEvent->condvar; + +# Try to connect to i3 +$i3->connect->cb(sub { my ($v) = @_; $cv->send($v->recv) }); + +# But cancel if we are not connected after 0.5 seconds +my $t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0) }); +my $connected = $cv->recv; + +SKIP: { + skip 'No connection to i3', 3 unless $connected; + + my $workspaces = i3->get_workspaces->recv; + isa_ok($workspaces, 'ARRAY'); + + ok(@{$workspaces} > 0, 'More than zero workspaces found'); + + ok(defined(@{$workspaces}[0]->{num}), 'JSON deserialized'); +} + +diag( "Testing AnyEvent::I3 $AnyEvent::I3::VERSION, Perl $], $^X" ); From f12facc184650fb84f66632968a850929edd51da Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Tue, 23 Mar 2010 02:07:02 +0100 Subject: [PATCH 122/180] bump version to 0.02, update MANIFEST --- MANIFEST | 1 + lib/AnyEvent/I3.pm | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/MANIFEST b/MANIFEST index c9c9c27c..99d318ff 100644 --- a/MANIFEST +++ b/MANIFEST @@ -13,6 +13,7 @@ META.yml README t/00-load.t t/01-workspaces.t +t/02-sugar.t t/boilerplate.t t/manifest.t t/pod-coverage.t diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index a09c7512..36852623 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -15,11 +15,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.01'; +our $VERSION = '0.02'; =head1 VERSION -Version 0.01 +Version 0.02 =head1 SYNOPSIS From 5d1bb0b0ce0dcce063c5e63e0533e7805ec856df Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Fri, 26 Mar 2010 19:48:59 +0100 Subject: [PATCH 123/180] Bump version for new CPAN upload, fix MANIFEST --- MANIFEST | 2 ++ MANIFEST.SKIP | 4 ++-- lib/AnyEvent/I3.pm | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/MANIFEST b/MANIFEST index 99d318ff..34c8a8fb 100644 --- a/MANIFEST +++ b/MANIFEST @@ -3,10 +3,12 @@ inc/Module/Install.pm inc/Module/Install/Base.pm inc/Module/Install/Can.pm inc/Module/Install/Fetch.pm +inc/Module/Install/Makefile.pm inc/Module/Install/Metadata.pm inc/Module/Install/Win32.pm inc/Module/Install/WriteAll.pm lib/AnyEvent/I3.pm +Makefile.PL MANIFEST MANIFEST.SKIP META.yml diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index f58f7ef8..01bee91f 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -1,8 +1,8 @@ ^\.git/ \.bak$ blib/ -Makefile -Makefile.old +^Makefile$ +^Makefile.old$ Build Build.bat ^pm_to_blib diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 36852623..a2951767 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -15,11 +15,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.02'; +our $VERSION = '0.03'; =head1 VERSION -Version 0.02 +Version 0.03 =head1 SYNOPSIS From e6568648386348ba29f0f36654163eeb0db1d89e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 27 Mar 2010 14:54:30 +0100 Subject: [PATCH 124/180] use new default ipc-socket path, glob() path, bump version --- lib/AnyEvent/I3.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index a2951767..d9a55b2b 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -15,11 +15,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.03'; +our $VERSION = '0.04'; =head1 VERSION -Version 0.03 +Version 0.04 =head1 SYNOPSIS @@ -29,7 +29,7 @@ then subscribe to events or send messages and receive their replies. use AnyEvent::I3 qw(:all); - my $i3 = i3("/tmp/i3-ipc.sock"); + my $i3 = i3("~/.i3/ipc.sock"); $i3->connect->recv or die "Error connecting"; say "Connected to i3"; @@ -94,9 +94,9 @@ the UNIX socket to connect to. sub new { my ($class, $path) = @_; - $path ||= '/tmp/i3-ipc.sock'; + $path ||= '~/.i3/ipc.sock'; - bless { path => $path } => $class; + bless { path => glob($path) } => $class; } =head2 $i3->connect From 192ef6a827fb1f5385c82d8ada4cee602ae848dc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Thu, 10 Jun 2010 00:18:50 +0200 Subject: [PATCH 125/180] use getpwuid() to resolve ~ in socket paths instead of glob() This fixes a warning about using a tainted variable (broke the tests with newer perl versions). --- lib/AnyEvent/I3.pm | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index d9a55b2b..944d5f02 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -96,7 +96,16 @@ sub new { $path ||= '~/.i3/ipc.sock'; - bless { path => glob($path) } => $class; + # Check if we need to resolve ~ + if ($path =~ /~/) { + # We use getpwuid() instead of $ENV{HOME} because the latter is tainted + # and thus produces warnings when running tests with perl -T + my $home = (getpwuid($<))[7]; + die "Could not get home directory" unless $home and -d $home; + $path =~ s/~/$home/g; + } + + bless { path => $path } => $class; } =head2 $i3->connect From da94674f8fdd98ae324b79b90a1698721a1c6bb2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Thu, 10 Jun 2010 00:20:11 +0200 Subject: [PATCH 126/180] bump version --- lib/AnyEvent/I3.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 944d5f02..953de81e 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -15,11 +15,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.04'; +our $VERSION = '0.05'; =head1 VERSION -Version 0.04 +Version 0.05 =head1 SYNOPSIS From a3a42f30e100ecc84165381a71e140bf32284b5a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Wed, 16 Jun 2010 19:40:55 +0200 Subject: [PATCH 127/180] Add check to Makefile to abort in a Windows environment (neither i3 nor unix sockets available) Hopefully, this will stop CPAN Testers emails about failing tests on windows. --- Makefile.PL | 4 ++++ lib/AnyEvent/I3.pm | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile.PL b/Makefile.PL index 2082ce24..8b81a065 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -9,4 +9,8 @@ requires 'AnyEvent::Handle'; requires 'AnyEvent::Socket'; requires 'JSON::XS'; +if ($^O eq 'MSWin32') { + die "AnyEvent::I3 cannot be used on win32 (unix sockets are missing)"; +} + WriteAll; diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 953de81e..61486488 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -15,11 +15,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.05'; +our $VERSION = '0.06'; =head1 VERSION -Version 0.05 +Version 0.06 =head1 SYNOPSIS From 7176a7074b9a1c94b9f22a2532826bd01586cf3d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 21 Nov 2010 21:52:43 +0100 Subject: [PATCH 128/180] introduce get_tree request (tree branch only) --- lib/AnyEvent/I3.pm | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 61486488..d191241c 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -15,11 +15,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.06'; +our $VERSION = '0.07'; =head1 VERSION -Version 0.06 +Version 0.07 =head1 SYNOPSIS @@ -64,9 +64,10 @@ use constant TYPE_COMMAND => 0; use constant TYPE_GET_WORKSPACES => 1; use constant TYPE_SUBSCRIBE => 2; use constant TYPE_GET_OUTPUTS => 3; +use constant TYPE_GET_TREE => 4; our %EXPORT_TAGS = ( 'all' => [ - qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS) + qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS TYPE_GET_TREE) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -322,6 +323,23 @@ sub get_outputs { $self->message(TYPE_GET_OUTPUTS) } +=head2 get_tree + +Gets the layout tree from i3 (tree branch only). + + my $tree = i3->get_tree->recv; + say Dumper($tree); + +=cut +sub get_tree { + my ($self) = @_; + + $self->_ensure_connection; + + $self->message(TYPE_GET_TREE) +} + + =head2 command($content) Makes i3 execute the given command From 62749590294edb97cdfb5f2316b90f9e13b9e113 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 26 Sep 2011 19:25:38 +0100 Subject: [PATCH 129/180] =?UTF-8?q?Bugfix:=20The=20synopsis=20mentioned=20?= =?UTF-8?q?->workspaces,=20but=20it=E2=80=99s=20->get=5Fworkspaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/AnyEvent/I3.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index d191241c..36642643 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -41,7 +41,7 @@ then subscribe to events or send messages and receive their replies. use AnyEvent::I3; - my $workspaces = i3->workspaces->recv; + my $workspaces = i3->get_workspaces->recv; say "Currently, you use " . @{$workspaces} . " workspaces"; =head1 EXPORT From 1a272f96a58bde317621bb3fd13db5548313b1e9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 26 Sep 2011 19:25:59 +0100 Subject: [PATCH 130/180] Implement support for the TYPE_GET_MARKS request, add ->get_marks sugar method --- lib/AnyEvent/I3.pm | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 36642643..979e0867 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -65,9 +65,10 @@ use constant TYPE_GET_WORKSPACES => 1; use constant TYPE_SUBSCRIBE => 2; use constant TYPE_GET_OUTPUTS => 3; use constant TYPE_GET_TREE => 4; +use constant TYPE_GET_MARKS => 5; our %EXPORT_TAGS = ( 'all' => [ - qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS TYPE_GET_TREE) + qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS TYPE_GET_TREE TYPE_GET_MARKS) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -325,7 +326,7 @@ sub get_outputs { =head2 get_tree -Gets the layout tree from i3 (tree branch only). +Gets the layout tree from i3 (>= v4.0). my $tree = i3->get_tree->recv; say Dumper($tree); @@ -339,6 +340,21 @@ sub get_tree { $self->message(TYPE_GET_TREE) } +=head2 get_marks + +Gets all the window identifier marks from i3 (>= v4.1). + + my $marks = i3->get_marks->recv; + say Dumper($tree); + +=cut +sub get_marks { + my ($self) = @_; + + $self->_ensure_connection; + + $self->message(TYPE_GET_MARKS) +} =head2 command($content) From 9054711650a02fb7b4113a2186bb19208151c5da Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 26 Sep 2011 19:26:18 +0100 Subject: [PATCH 131/180] Bump version to 0.08 --- lib/AnyEvent/I3.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 979e0867..cf43ab0c 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -15,11 +15,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.07'; +our $VERSION = '0.08'; =head1 VERSION -Version 0.07 +Version 0.08 =head1 SYNOPSIS From d7bd64586319846d7da50c66304cb49fff9fbff4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Wed, 12 Oct 2011 23:25:56 +0100 Subject: [PATCH 132/180] Fix documentation for get_marks (s/tree/marks) --- lib/AnyEvent/I3.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index cf43ab0c..3977e652 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -345,7 +345,7 @@ sub get_tree { Gets all the window identifier marks from i3 (>= v4.1). my $marks = i3->get_marks->recv; - say Dumper($tree); + say Dumper($marks); =cut sub get_marks { From 7a934b94dd62bd05d2df59518d66410ccbac1492 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Wed, 12 Oct 2011 23:26:04 +0100 Subject: [PATCH 133/180] Add TYPE_GET_MARKS and accompanying sugar method --- lib/AnyEvent/I3.pm | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 3977e652..4a5e8c24 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -66,9 +66,11 @@ use constant TYPE_SUBSCRIBE => 2; use constant TYPE_GET_OUTPUTS => 3; use constant TYPE_GET_TREE => 4; use constant TYPE_GET_MARKS => 5; +use constant TYPE_GET_BAR_CONFIG => 6; our %EXPORT_TAGS = ( 'all' => [ - qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS TYPE_GET_TREE TYPE_GET_MARKS) + qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS + TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -356,6 +358,22 @@ sub get_marks { $self->message(TYPE_GET_MARKS) } +=head2 get_bar_config + +Gets the bar configuration for the specific bar id from i3 (>= v4.1). + + my $config = i3->get_bar_config($id)->recv; + say Dumper($config); + +=cut +sub get_bar_config { + my ($self, $id) = @_; + + $self->_ensure_connection; + + $self->message(TYPE_GET_BAR_CONFIG, $id) +} + =head2 command($content) Makes i3 execute the given command From 23beaa83ea00c3b6475e91e49150863eefc7a6c9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Thu, 20 Oct 2011 20:28:04 +0100 Subject: [PATCH 134/180] Bump version to 0.09 --- lib/AnyEvent/I3.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 4a5e8c24..fea470bc 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -15,11 +15,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.08'; +our $VERSION = '0.09'; =head1 VERSION -Version 0.08 +Version 0.09 =head1 SYNOPSIS From ead15574680e34144142f203ec01c8d4689e908d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 10 Dec 2011 11:37:43 +0000 Subject: [PATCH 135/180] implement the GET_LOG_MARKERS request Requires i3 version 966c654112561b21fca076a8e967033510da9981 or later. --- lib/AnyEvent/I3.pm | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index fea470bc..6b8f905b 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -67,10 +67,11 @@ use constant TYPE_GET_OUTPUTS => 3; use constant TYPE_GET_TREE => 4; use constant TYPE_GET_MARKS => 5; use constant TYPE_GET_BAR_CONFIG => 6; +use constant TYPE_GET_LOG_MARKERS => 7; our %EXPORT_TAGS = ( 'all' => [ qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS - TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG) + TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_LOG_MARKERS) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -374,6 +375,23 @@ sub get_bar_config { $self->message(TYPE_GET_BAR_CONFIG, $id) } +=head2 get_log_markers + +Gets the bar configuration for the specific bar id from i3 (>= v4.1). + + my $markers = i3->get_log_markers()->recv; + say Dumper($markers); + +=cut +sub get_log_markers { + my ($self) = @_; + + $self->_ensure_connection; + + $self->message(TYPE_GET_LOG_MARKERS) +} + + =head2 command($content) Makes i3 execute the given command From 3a9024de63f92066e74c11684511e7ecc7538fc7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 21 Jan 2012 21:59:12 +0000 Subject: [PATCH 136/180] Bugfix: Also delete callbacks which are triggered due to an error --- lib/AnyEvent/I3.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 6b8f905b..6c7dbd12 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -147,6 +147,7 @@ sub connect { for my $type (keys %{$cb}) { next if ($type & $event_mask) == $event_mask; $cb->{$type}->(); + delete $cb->{$type}; } # Trigger _error callback, if set From 7021bb43154a46114509ed0dffcbe4429968be13 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 21 Jan 2012 22:00:04 +0000 Subject: [PATCH 137/180] Revert "implement the GET_LOG_MARKERS request" (no longer available) This reverts commit 7e2ed06447af5e3b49af69dd24e2a5dec373ad9c. --- lib/AnyEvent/I3.pm | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 6c7dbd12..398040cb 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -67,11 +67,10 @@ use constant TYPE_GET_OUTPUTS => 3; use constant TYPE_GET_TREE => 4; use constant TYPE_GET_MARKS => 5; use constant TYPE_GET_BAR_CONFIG => 6; -use constant TYPE_GET_LOG_MARKERS => 7; our %EXPORT_TAGS = ( 'all' => [ qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS - TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_LOG_MARKERS) + TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -376,23 +375,6 @@ sub get_bar_config { $self->message(TYPE_GET_BAR_CONFIG, $id) } -=head2 get_log_markers - -Gets the bar configuration for the specific bar id from i3 (>= v4.1). - - my $markers = i3->get_log_markers()->recv; - say Dumper($markers); - -=cut -sub get_log_markers { - my ($self) = @_; - - $self->_ensure_connection; - - $self->message(TYPE_GET_LOG_MARKERS) -} - - =head2 command($content) Makes i3 execute the given command From 476e41ddc3aac7c938b7ebefa146e77b3723d80f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 9 Jul 2012 15:49:16 +0200 Subject: [PATCH 138/180] use i3 --get-socketpath by default for determining the socket path This was introduced in i3 v4.1 (released 2011-11-11, so should be widespread enough by now). --- lib/AnyEvent/I3.pm | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 398040cb..33347638 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -29,7 +29,7 @@ then subscribe to events or send messages and receive their replies. use AnyEvent::I3 qw(:all); - my $i3 = i3("~/.i3/ipc.sock"); + my $i3 = i3(); $i3->connect->recv or die "Error connecting"; say "Connected to i3"; @@ -48,8 +48,12 @@ then subscribe to events or send messages and receive their replies. =head2 $i3 = i3([ $path ]); -Creates a new C<AnyEvent::I3> object and returns it. C<path> is the path of -the UNIX socket to connect to. +Creates a new C<AnyEvent::I3> object and returns it. + +C<path> is an optional path of the UNIX socket to connect to. It is strongly +advised to NOT specify this unless you're absolutely sure you need it. +C<AnyEvent::I3> will automatically figure it out by querying the running i3 +instance on the current DISPLAY which is almost always what you want. =head1 SUBROUTINES/METHODS @@ -91,13 +95,37 @@ sub i3 { =head2 $i3 = AnyEvent::I3->new([ $path ]) -Creates a new C<AnyEvent::I3> object and returns it. C<path> is the path of -the UNIX socket to connect to. +Creates a new C<AnyEvent::I3> object and returns it. + +C<path> is an optional path of the UNIX socket to connect to. It is strongly +advised to NOT specify this unless you're absolutely sure you need it. +C<AnyEvent::I3> will automatically figure it out by querying the running i3 +instance on the current DISPLAY which is almost always what you want. =cut sub new { my ($class, $path) = @_; + if (!$path) { + # This effectively circumvents taint mode checking for $ENV{PATH}. We + # do this because users might specify PATH explicitly to call i3 in a + # custom location (think ~/.bin/). + my $paths = $ENV{PATH}; + if ($paths =~ /^(.*)$/) { + $ENV{PATH} = $1; + } + chomp($path = qx(i3 --get-socketpath)); + # Circumventing taint mode again: the socket can be anywhere on the + # system and that’s okay. + if ($path =~ /^([^\0]+)$/) { + $path = $1; + } else { + warn "Asking i3 for the socket path failed. Is DISPLAY set and is i3 in your PATH?"; + } + } + + # This is the old default path (v3.*). This fallback line can be removed in + # a year from now. -- Michael, 2012-07-09 $path ||= '~/.i3/ipc.sock'; # Check if we need to resolve ~ @@ -292,7 +320,7 @@ sub _ensure_connection { return if defined($self->{ipchdl}); - $self->connect->recv or die "Unable to connect to i3" + $self->connect->recv or die "Unable to connect to i3 (socket path " . $self->{path} . ")"; } =head2 get_workspaces From a6a0e11718a7346d40a98dd33110be2dcc784ce2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 9 Jul 2012 15:51:37 +0200 Subject: [PATCH 139/180] update copyright and URL/email --- lib/AnyEvent/I3.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 33347638..deadd9fb 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -421,7 +421,7 @@ sub command { =head1 AUTHOR -Michael Stapelberg, C<< <michael at stapelberg.de> >> +Michael Stapelberg, C<< <michael at i3wm.org> >> =head1 BUGS @@ -447,7 +447,7 @@ L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3> =item * The i3 window manager website -L<http://i3.zekjur.net/> +L<http://i3wm.org> =back @@ -457,7 +457,7 @@ L<http://i3.zekjur.net/> =head1 LICENSE AND COPYRIGHT -Copyright 2010 Michael Stapelberg. +Copyright 2010-2012 Michael Stapelberg. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published From 2ec58b40f8095f7845012342022f4cf62b756113 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 9 Jul 2012 15:54:26 +0200 Subject: [PATCH 140/180] bump version to 0.10 --- lib/AnyEvent/I3.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index deadd9fb..831f350d 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -15,11 +15,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.09'; +our $VERSION = '0.10'; =head1 VERSION -Version 0.09 +Version 0.10 =head1 SYNOPSIS From 4c97c94a0a29e0b775974c780d574639372e76c2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Tue, 10 Jul 2012 18:55:05 +0200 Subject: [PATCH 141/180] taint mode fix for FreeBSD --- lib/AnyEvent/I3.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 831f350d..b08fdf63 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -114,6 +114,8 @@ sub new { if ($paths =~ /^(.*)$/) { $ENV{PATH} = $1; } + # Otherwise the qx() operator wont work: + delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; chomp($path = qx(i3 --get-socketpath)); # Circumventing taint mode again: the socket can be anywhere on the # system and that’s okay. From af6f180c35c3024ce4265a69a14ccc3d474131dc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Tue, 10 Jul 2012 18:55:30 +0200 Subject: [PATCH 142/180] bump version to 0.11 --- lib/AnyEvent/I3.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index b08fdf63..4128870f 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -15,11 +15,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.10'; +our $VERSION = '0.11'; =head1 VERSION -Version 0.10 +Version 0.11 =head1 SYNOPSIS From b4058790cc47d147d3448795da679ea4ccfe4055 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Wed, 11 Jul 2012 08:58:59 +0200 Subject: [PATCH 143/180] remove relative directories from $ENV{PATH} (for taint mode) Otherwise, the module will die when you use it with PATH=$PATH:. (as is the case on the OpenBSD cpan testers). --- lib/AnyEvent/I3.pm | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 4128870f..2170b267 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -8,6 +8,7 @@ use AnyEvent::Handle; use AnyEvent::Socket; use AnyEvent; use Encode; +use Scalar::Util qw(tainted); =head1 NAME @@ -107,12 +108,25 @@ sub new { my ($class, $path) = @_; if (!$path) { + my $path_tainted = tainted($ENV{PATH}); # This effectively circumvents taint mode checking for $ENV{PATH}. We # do this because users might specify PATH explicitly to call i3 in a # custom location (think ~/.bin/). - my $paths = $ENV{PATH}; - if ($paths =~ /^(.*)$/) { - $ENV{PATH} = $1; + (local $ENV{PATH}) = ($ENV{PATH} =~ /(.*)/); + + # In taint mode, we also need to remove all relative directories from + # PATH (like . or ../bin). We only do this in taint mode and warn the + # user, since this might break a real-world use case for some people. + if ($path_tainted) { + my @dirs = split /:/, $ENV{PATH}; + my @filtered = grep !/^\./, @dirs; + if (scalar @dirs != scalar @filtered) { + $ENV{PATH} = join ':', @filtered; + warn qq|Removed relative directories from PATH because you | . + qq|are running Perl with taint mode enabled. Remove -T | . + qq|to be able to use relative directories in PATH. | . + qq|New PATH is "$ENV{PATH}"|; + } } # Otherwise the qx() operator wont work: delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; From 3a7f5d7912ca2d812c9513c5898571ec39906618 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Wed, 11 Jul 2012 08:59:51 +0200 Subject: [PATCH 144/180] bump version to 0.12 --- lib/AnyEvent/I3.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 2170b267..c338878a 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -16,11 +16,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.11'; +our $VERSION = '0.12'; =head1 VERSION -Version 0.11 +Version 0.12 =head1 SYNOPSIS From d8ad62c36fb1d481f399a7292349dfaa1041ee96 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 5 Aug 2012 17:31:07 +0200 Subject: [PATCH 145/180] add a more involved example to the SYNOPSIS --- lib/AnyEvent/I3.pm | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index c338878a..63f063f4 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -45,6 +45,28 @@ then subscribe to events or send messages and receive their replies. my $workspaces = i3->get_workspaces->recv; say "Currently, you use " . @{$workspaces} . " workspaces"; +A somewhat more involved example which dumps the i3 layout tree whenever there +is a workspace event: + + use Data::Dumper; + use AnyEvent; + use AnyEvent::I3; + + my $i3 = i3(); + + $i3->connect->recv or die "Error connecting to i3"; + + $i3->subscribe({ + workspace => sub { + $i3->get_tree->cb(sub { + my ($tree) = @_; + say "tree: " . Dumper($tree); + }); + } + })->recv->{success} or die "Error subscribing to events"; + + AE::cv->recv + =head1 EXPORT =head2 $i3 = i3([ $path ]); From 64fddbe41bf8d50eda121d822cdc71399fc0a2ec Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 5 Aug 2012 17:31:22 +0200 Subject: [PATCH 146/180] Implement the GET_VERSION request (with a fallback to i3 --version) --- lib/AnyEvent/I3.pm | 121 ++++++++++++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 33 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 63f063f4..b46f2c62 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -94,10 +94,11 @@ use constant TYPE_GET_OUTPUTS => 3; use constant TYPE_GET_TREE => 4; use constant TYPE_GET_MARKS => 5; use constant TYPE_GET_BAR_CONFIG => 6; +use constant TYPE_GET_VERSION => 7; our %EXPORT_TAGS = ( 'all' => [ qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS - TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG) + TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -116,6 +117,43 @@ sub i3 { AnyEvent::I3->new(@_) } +# Calls i3, even when running in taint mode. +sub _call_i3 { + my ($args) = @_; + + my $path_tainted = tainted($ENV{PATH}); + # This effectively circumvents taint mode checking for $ENV{PATH}. We + # do this because users might specify PATH explicitly to call i3 in a + # custom location (think ~/.bin/). + (local $ENV{PATH}) = ($ENV{PATH} =~ /(.*)/); + + # In taint mode, we also need to remove all relative directories from + # PATH (like . or ../bin). We only do this in taint mode and warn the + # user, since this might break a real-world use case for some people. + if ($path_tainted) { + my @dirs = split /:/, $ENV{PATH}; + my @filtered = grep !/^\./, @dirs; + if (scalar @dirs != scalar @filtered) { + $ENV{PATH} = join ':', @filtered; + warn qq|Removed relative directories from PATH because you | . + qq|are running Perl with taint mode enabled. Remove -T | . + qq|to be able to use relative directories in PATH. | . + qq|New PATH is "$ENV{PATH}"|; + } + } + # Otherwise the qx() operator wont work: + delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; + chomp(my $result = qx(i3 $args)); + # Circumventing taint mode again: the socket can be anywhere on the + # system and that’s okay. + if ($result =~ /^([^\0]+)$/) { + return $1; + } + + warn "Calling i3 $args failed. Is DISPLAY set and is i3 in your PATH?"; + return undef; +} + =head2 $i3 = AnyEvent::I3->new([ $path ]) Creates a new C<AnyEvent::I3> object and returns it. @@ -129,38 +167,7 @@ instance on the current DISPLAY which is almost always what you want. sub new { my ($class, $path) = @_; - if (!$path) { - my $path_tainted = tainted($ENV{PATH}); - # This effectively circumvents taint mode checking for $ENV{PATH}. We - # do this because users might specify PATH explicitly to call i3 in a - # custom location (think ~/.bin/). - (local $ENV{PATH}) = ($ENV{PATH} =~ /(.*)/); - - # In taint mode, we also need to remove all relative directories from - # PATH (like . or ../bin). We only do this in taint mode and warn the - # user, since this might break a real-world use case for some people. - if ($path_tainted) { - my @dirs = split /:/, $ENV{PATH}; - my @filtered = grep !/^\./, @dirs; - if (scalar @dirs != scalar @filtered) { - $ENV{PATH} = join ':', @filtered; - warn qq|Removed relative directories from PATH because you | . - qq|are running Perl with taint mode enabled. Remove -T | . - qq|to be able to use relative directories in PATH. | . - qq|New PATH is "$ENV{PATH}"|; - } - } - # Otherwise the qx() operator wont work: - delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; - chomp($path = qx(i3 --get-socketpath)); - # Circumventing taint mode again: the socket can be anywhere on the - # system and that’s okay. - if ($path =~ /^([^\0]+)$/) { - $path = $1; - } else { - warn "Asking i3 for the socket path failed. Is DISPLAY set and is i3 in your PATH?"; - } - } + $path = _call_i3('--get-socketpath') unless $path; # This is the old default path (v3.*). This fallback line can be removed in # a year from now. -- Michael, 2012-07-09 @@ -441,6 +448,54 @@ sub get_bar_config { $self->message(TYPE_GET_BAR_CONFIG, $id) } +=head2 get_version + +Gets the i3 version via IPC, with a fall-back that parses the output of i3 +--version (for i3 < v4.3). + + my $version = i3->get_version()->recv; + say "major: " . $version->{major} . ", minor = " . $version->{minor}; + +=cut +sub get_version { + my ($self) = @_; + + $self->_ensure_connection; + + my $cv = AnyEvent->condvar; + + my $version_cv = $self->message(TYPE_GET_VERSION); + my $timeout; + $timeout = AnyEvent->timer( + after => 1, + cb => sub { + warn "Falling back to i3 --version since the running i3 doesn’t support GET_VERSION yet."; + my $version = _call_i3('--version'); + $version =~ s/^i3 version //; + my $patch = 0; + my ($major, $minor) = ($version =~ /^([0-9]+)\.([0-9]+)/); + if ($version =~ /^[0-9]+\.[0-9]+\.([0-9]+)/) { + $patch = $1; + } + # Strip everything from the © sign on. + $version =~ s/ ©.*$//g; + $cv->send({ + major => int($major), + minor => int($minor), + patch => int($patch), + human_readable => $version, + }); + undef $timeout; + }, + ); + $version_cv->cb(sub { + undef $timeout; + $cv->send($version_cv->recv); + }); + + return $cv; +} + =head2 command($content) Makes i3 execute the given command From c3538c4cad327dc966e3ec66eea2a6fa4aeb8208 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 5 Aug 2012 17:31:57 +0200 Subject: [PATCH 147/180] bump version to 0.13 --- lib/AnyEvent/I3.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index b46f2c62..5254e22d 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -16,11 +16,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.12'; +our $VERSION = '0.13'; =head1 VERSION -Version 0.12 +Version 0.13 =head1 SYNOPSIS From 879266c40b71d137f55256a920024fc1ecb1b594 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 5 Aug 2012 17:41:16 +0200 Subject: [PATCH 148/180] update Changes file --- Changes | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 0dc96033..5915a90d 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,52 @@ Revision history for AnyEvent-I3 -0.01 2010-03-13 - First version, released on an unsuspecting world. +0.13 2012-08-05 + * support the GET_VERSION request with a fall-back to i3 --version + +0.12 2012-07-11 + + * taint mode fix: remove relative directories from $ENV{PATH} + +0.11 2012-07-10 + + * taint mode fix for FreeBSD + +0.10 2012-07-09 + + * Use i3 --get-socketpath by default for determining the socket path + * Bugfix: Also delete callbacks which are triggered due to an error + +0.09 2011-10-12 + + * Implement GET_BAR_CONFIG request + +0.08 2011-09-26 + + * Implement GET_MARKS request + * The synopsis mentioned ->workspaces, but it’s ->get_workspaces + +0.07 2010-11-21 + + * Implement GET_TREE request + +0.06 2010-06-16 + + * Add check to Makefile to abort in a Windows environment (neither i3 nor + unix sockets available) + +0.05 2010-06-09 + + * use getpwuid() to resolve ~ in socket paths instead of glob() + +0.04 2010-03-27 + + * use new default ipc-socket path, glob() path, bump version + +0.03 2010-03-26 + + * fix MANIFEST + +0.02 2010-03-23 + + * first upload to CPAN From b008d8b2e96cecb33347527fdf640a8e87a3d4fa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 22 Sep 2012 13:11:08 +0200 Subject: [PATCH 149/180] 0.14: add support for the mode event --- Changes | 4 ++++ lib/AnyEvent/I3.pm | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 5915a90d..0a62838b 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Revision history for AnyEvent-I3 +0.14 2012-09-22 + + * support the mode event + 0.13 2012-08-05 * support the GET_VERSION request with a fall-back to i3 --version diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 5254e22d..b0bf2a09 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -16,11 +16,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.13'; +our $VERSION = '0.14'; =head1 VERSION -Version 0.13 +Version 0.14 =head1 SYNOPSIS @@ -110,6 +110,7 @@ my $event_mask = (1 << 31); my %events = ( workspace => ($event_mask | 0), output => ($event_mask | 1), + mode => ($event_mask | 2), _error => 0xFFFFFFFF, ); From 85e98d8d7d579cb02ae1a6810f39b2d5713a0bfc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 3 Nov 2012 11:47:27 +0100 Subject: [PATCH 150/180] fix doc error: missing opening brace (Thanks bitonic) --- lib/AnyEvent/I3.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index b0bf2a09..309a11c0 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -277,7 +277,7 @@ key being the name of the event and the value being a callback. workspace => sub { say "Workspaces changed" } ); - if ($i3->subscribe(\%callbacks)->recv->{success}) + if ($i3->subscribe(\%callbacks)->recv->{success}) { say "Successfully subscribed"; } From bd1c33588d7b9b8788e58ecbd6fab154ee554d7c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 18 Feb 2013 11:01:04 +0100 Subject: [PATCH 151/180] support the window event, tag 0.15 --- Changes | 4 ++++ lib/AnyEvent/I3.pm | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 0a62838b..ab63dd06 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Revision history for AnyEvent-I3 +0.15 2013-02-18 + + * support the window event + 0.14 2012-09-22 * support the mode event diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 309a11c0..e472ccc6 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -16,11 +16,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.14'; +our $VERSION = '0.15'; =head1 VERSION -Version 0.14 +Version 0.15 =head1 SYNOPSIS @@ -111,6 +111,7 @@ my %events = ( workspace => ($event_mask | 0), output => ($event_mask | 1), mode => ($event_mask | 2), + window => ($event_mask | 3), _error => 0xFFFFFFFF, ); From 538c50c8ef0cdcce8825aa28d4e7f44c8434964a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Fri, 3 Oct 2014 09:39:10 +0200 Subject: [PATCH 152/180] support the barconfig_update and binding event, tag 0.16 --- Changes | 4 ++++ lib/AnyEvent/I3.pm | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index ab63dd06..dd68f4cb 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Revision history for AnyEvent-I3 +0.16 2014-10-03 + + * support the barconfig_update and binding event + 0.15 2013-02-18 * support the window event diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index e472ccc6..9f93519e 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -16,11 +16,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.15'; +our $VERSION = '0.16'; =head1 VERSION -Version 0.15 +Version 0.16 =head1 SYNOPSIS @@ -112,6 +112,8 @@ my %events = ( output => ($event_mask | 1), mode => ($event_mask | 2), window => ($event_mask | 3), + barconfig_update => ($event_mask | 4), + binding => ($event_mask | 5), _error => 0xFFFFFFFF, ); From 0940f7b9d19ec45f0b20e9cd585c889d4ba22e81 Mon Sep 17 00:00:00 2001 From: Tony Crisci <tony@dubstepdish.com> Date: Fri, 6 May 2016 02:44:28 -0400 Subject: [PATCH 153/180] Add the shutdown event (#2) The shutdown event is triggered when the ipc shuts down because of either a restart or when i3 exits. --- lib/AnyEvent/I3.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index 9f93519e..a8872d5d 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -114,6 +114,7 @@ my %events = ( window => ($event_mask | 3), barconfig_update => ($event_mask | 4), binding => ($event_mask | 5), + shutdown => ($event_mask | 6), _error => 0xFFFFFFFF, ); From f7645336cb499e208df66037ea1ffc269cae94d6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 9 Apr 2017 15:32:04 +0200 Subject: [PATCH 154/180] use lib '.' for Perl 5.25.11+ see also https://rt.cpan.org/Ticket/Display.html?id=120943 --- Makefile.PL | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.PL b/Makefile.PL index 8b81a065..5d2ab32e 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -1,3 +1,4 @@ +use lib '.'; use inc::Module::Install; name 'AnyEvent-I3'; From db35244ee766869a1b1b832cddcb311d1f8ab11c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 9 Apr 2017 15:34:36 +0200 Subject: [PATCH 155/180] tag 0.17 --- Changes | 5 +++++ lib/AnyEvent/I3.pm | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index dd68f4cb..713f7d1c 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,10 @@ Revision history for AnyEvent-I3 +0.17 2017-04-09 + + * support the shutdown event + * use lib '.' for Perl 5.25.11+ + 0.16 2014-10-03 * support the barconfig_update and binding event diff --git a/lib/AnyEvent/I3.pm b/lib/AnyEvent/I3.pm index a8872d5d..875f3790 100644 --- a/lib/AnyEvent/I3.pm +++ b/lib/AnyEvent/I3.pm @@ -16,11 +16,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.16'; +our $VERSION = '0.17'; =head1 VERSION -Version 0.16 +Version 0.17 =head1 SYNOPSIS From 062ecdb0b5e97448c24b7aaf07e9260906d22fbe Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 19 Aug 2017 16:32:39 +0200 Subject: [PATCH 156/180] Move to AnyEvent-I3 --- Changes => AnyEvent-I3/Changes | 0 MANIFEST => AnyEvent-I3/MANIFEST | 0 MANIFEST.SKIP => AnyEvent-I3/MANIFEST.SKIP | 0 Makefile.PL => AnyEvent-I3/Makefile.PL | 0 README => AnyEvent-I3/README | 0 {lib => AnyEvent-I3/lib}/AnyEvent/I3.pm | 0 {t => AnyEvent-I3/t}/00-load.t | 0 {t => AnyEvent-I3/t}/01-workspaces.t | 0 {t => AnyEvent-I3/t}/02-sugar.t | 0 {t => AnyEvent-I3/t}/boilerplate.t | 0 {t => AnyEvent-I3/t}/manifest.t | 0 {t => AnyEvent-I3/t}/pod-coverage.t | 0 {t => AnyEvent-I3/t}/pod.t | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename Changes => AnyEvent-I3/Changes (100%) rename MANIFEST => AnyEvent-I3/MANIFEST (100%) rename MANIFEST.SKIP => AnyEvent-I3/MANIFEST.SKIP (100%) rename Makefile.PL => AnyEvent-I3/Makefile.PL (100%) rename README => AnyEvent-I3/README (100%) rename {lib => AnyEvent-I3/lib}/AnyEvent/I3.pm (100%) rename {t => AnyEvent-I3/t}/00-load.t (100%) rename {t => AnyEvent-I3/t}/01-workspaces.t (100%) rename {t => AnyEvent-I3/t}/02-sugar.t (100%) rename {t => AnyEvent-I3/t}/boilerplate.t (100%) rename {t => AnyEvent-I3/t}/manifest.t (100%) rename {t => AnyEvent-I3/t}/pod-coverage.t (100%) rename {t => AnyEvent-I3/t}/pod.t (100%) diff --git a/Changes b/AnyEvent-I3/Changes similarity index 100% rename from Changes rename to AnyEvent-I3/Changes diff --git a/MANIFEST b/AnyEvent-I3/MANIFEST similarity index 100% rename from MANIFEST rename to AnyEvent-I3/MANIFEST diff --git a/MANIFEST.SKIP b/AnyEvent-I3/MANIFEST.SKIP similarity index 100% rename from MANIFEST.SKIP rename to AnyEvent-I3/MANIFEST.SKIP diff --git a/Makefile.PL b/AnyEvent-I3/Makefile.PL similarity index 100% rename from Makefile.PL rename to AnyEvent-I3/Makefile.PL diff --git a/README b/AnyEvent-I3/README similarity index 100% rename from README rename to AnyEvent-I3/README diff --git a/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm similarity index 100% rename from lib/AnyEvent/I3.pm rename to AnyEvent-I3/lib/AnyEvent/I3.pm diff --git a/t/00-load.t b/AnyEvent-I3/t/00-load.t similarity index 100% rename from t/00-load.t rename to AnyEvent-I3/t/00-load.t diff --git a/t/01-workspaces.t b/AnyEvent-I3/t/01-workspaces.t similarity index 100% rename from t/01-workspaces.t rename to AnyEvent-I3/t/01-workspaces.t diff --git a/t/02-sugar.t b/AnyEvent-I3/t/02-sugar.t similarity index 100% rename from t/02-sugar.t rename to AnyEvent-I3/t/02-sugar.t diff --git a/t/boilerplate.t b/AnyEvent-I3/t/boilerplate.t similarity index 100% rename from t/boilerplate.t rename to AnyEvent-I3/t/boilerplate.t diff --git a/t/manifest.t b/AnyEvent-I3/t/manifest.t similarity index 100% rename from t/manifest.t rename to AnyEvent-I3/t/manifest.t diff --git a/t/pod-coverage.t b/AnyEvent-I3/t/pod-coverage.t similarity index 100% rename from t/pod-coverage.t rename to AnyEvent-I3/t/pod-coverage.t diff --git a/t/pod.t b/AnyEvent-I3/t/pod.t similarity index 100% rename from t/pod.t rename to AnyEvent-I3/t/pod.t From a91544b5b39396b7b6491fa18a61de092ea5e1f7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 19 Aug 2017 16:51:51 +0200 Subject: [PATCH 157/180] testcases: remove external AnyEvent::I3 dependency --- Makefile.am | 14 +++++++++++++- testcases/Makefile.PL | 1 - testcases/complete-run.pl.in | 2 +- testcases/lib/i3test.pm.in | 1 + testcases/lib/i3test/XTEST.pm | 1 + testcases/t/000-load-deps.t | 1 - travis/travis-base.Dockerfile | 2 +- 7 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Makefile.am b/Makefile.am index 188e9e82..ec28991c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -53,6 +53,9 @@ check_PROGRAMS = \ check_SCRIPTS = \ testcases/complete-run.pl +check_DATA = \ + anyevent-i3.stamp + clean-check: rm -rf testsuite-* latest i3-cfg-for-* _Inline clean-local: clean-check @@ -573,6 +576,15 @@ i3-config-parser.stamp: parser/$(dirstamp) generate-command-parser.pl parser-spe $(AM_V_at) mv GENERATED_config_* $(top_builddir)/parser $(AM_V_at) touch $@ +################################################################################ +# AnyEvent-I3 build process +################################################################################ + +anyevent-i3.stamp: generate-command-parser.pl parser-specs/config.spec + $(AM_V_BUILD) (cd $(top_srcdir)/AnyEvent-I3 && perl Makefile.PL && make) + $(AM_V_at) touch $@ + CLEANFILES = \ i3-command-parser.stamp \ - i3-config-parser.stamp + i3-config-parser.stamp \ + anyevent-i3.stamp diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL index 3c2a26f9..0b1f3055 100755 --- a/testcases/Makefile.PL +++ b/testcases/Makefile.PL @@ -8,7 +8,6 @@ WriteMakefile( MIN_PERL_VERSION => '5.010000', # 5.10.0 PREREQ_PM => { 'AnyEvent' => 0, - 'AnyEvent::I3' => '0.16', 'X11::XCB' => '0.12', 'Inline' => 0, 'Inline::C' => 0, diff --git a/testcases/complete-run.pl.in b/testcases/complete-run.pl.in index 2019253c..ba192469 100755 --- a/testcases/complete-run.pl.in +++ b/testcases/complete-run.pl.in @@ -18,7 +18,7 @@ use Time::HiRes qw(time); use IO::Handle; # these are shipped with the testsuite -use lib qw(@abs_top_builddir@/testcases/lib @abs_top_srcdir@/testcases/lib); +use lib qw(@abs_top_builddir@/testcases/lib @abs_top_srcdir@/testcases/lib @abs_top_srcdir@/AnyEvent-I3/blib/lib); use i3test::Util qw(slurp); use StartXServer; use StatusLine; diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index f7e1515d..18bebb52 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -7,6 +7,7 @@ use Test::Builder; use X11::XCB::Rect; use X11::XCB::Window; use X11::XCB qw(:all); +use lib qw(@abs_top_srcdir@/AnyEvent-I3/blib/lib); use AnyEvent::I3; use List::Util qw(first); use Time::HiRes qw(sleep); diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm index 92adde42..3937b70a 100644 --- a/testcases/lib/i3test/XTEST.pm +++ b/testcases/lib/i3test/XTEST.pm @@ -6,6 +6,7 @@ use warnings; use v5.10; use i3test i3_autostart => 0; +use lib qw(@abs_top_srcdir@/AnyEvent-I3/blib/lib); use AnyEvent::I3; use ExtUtils::PkgConfig; diff --git a/testcases/t/000-load-deps.t b/testcases/t/000-load-deps.t index ab93233a..e0408338 100644 --- a/testcases/t/000-load-deps.t +++ b/testcases/t/000-load-deps.t @@ -8,7 +8,6 @@ BEGIN { X11::XCB::Connection X11::XCB::Window AnyEvent - AnyEvent::I3 IPC::Run ExtUtils::PkgConfig Inline diff --git a/travis/travis-base.Dockerfile b/travis/travis-base.Dockerfile index a415f549..5704d8e4 100644 --- a/travis/travis-base.Dockerfile +++ b/travis/travis-base.Dockerfile @@ -19,7 +19,7 @@ RUN apt-get update && \ dpkg-dev devscripts git equivs \ clang clang-format-3.8 \ lintian \ - libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libanyevent-i3-perl && \ + libmodule-install-perl libanyevent-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libjson-xs-perl && \ rm -rf /var/lib/apt/lists/* # Install i3 build dependencies. From 6bb9c9e7088aaf5142b26fb297c86343b02dad76 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 19 Aug 2017 19:05:25 +0200 Subject: [PATCH 158/180] Makefile.am: fix anyevent-i3.stamp dependencies --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index ec28991c..0da98732 100644 --- a/Makefile.am +++ b/Makefile.am @@ -580,7 +580,7 @@ i3-config-parser.stamp: parser/$(dirstamp) generate-command-parser.pl parser-spe # AnyEvent-I3 build process ################################################################################ -anyevent-i3.stamp: generate-command-parser.pl parser-specs/config.spec +anyevent-i3.stamp: AnyEvent-I3/lib/AnyEvent/I3.pm $(AM_V_BUILD) (cd $(top_srcdir)/AnyEvent-I3 && perl Makefile.PL && make) $(AM_V_at) touch $@ From a6d8ed9b1ac6efa507d65b752758522bcfcc5213 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 19 Aug 2017 17:29:03 +0200 Subject: [PATCH 159/180] Introduce the GET_CONFIG IPC request This introduces memory usage by one copy of the config file, which is an acceptable trade-off for being able to easily revert data loss. The default config is 6KB, user configs will be in the same ballpark. fixes #2856 --- AnyEvent-I3/lib/AnyEvent/I3.pm | 19 ++++++++- i3-msg/main.c | 77 +++++++++++++++++++++++++++++----- include/configuration.h | 1 + include/i3/ipc.h | 4 ++ src/config.c | 1 + src/config_parser.c | 5 +++ src/ipc.c | 24 ++++++++++- testcases/t/268-ipc-config.t | 55 ++++++++++++++++++++++++ 8 files changed, 173 insertions(+), 13 deletions(-) create mode 100644 testcases/t/268-ipc-config.t diff --git a/AnyEvent-I3/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm index 875f3790..5ce3c016 100644 --- a/AnyEvent-I3/lib/AnyEvent/I3.pm +++ b/AnyEvent-I3/lib/AnyEvent/I3.pm @@ -95,10 +95,13 @@ use constant TYPE_GET_TREE => 4; use constant TYPE_GET_MARKS => 5; use constant TYPE_GET_BAR_CONFIG => 6; use constant TYPE_GET_VERSION => 7; +use constant TYPE_GET_BINDING_MODES => 8; +use constant TYPE_GET_CONFIG => 9; our %EXPORT_TAGS = ( 'all' => [ qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS - TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION) + TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION + TYPE_GET_BINDING_MODES TYPE_GET_CONFIG) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -501,6 +504,20 @@ sub get_version { return $cv; } +=head2 get_config + +Gets the raw last read config from i3. Requires i3 >= 4.14 + +=cut +sub get_config { + my ($self) = @_; + + $self->_ensure_connection; + + $self->message(TYPE_GET_CONFIG); +} + + =head2 command($content) Makes i3 execute the given command diff --git a/i3-msg/main.c b/i3-msg/main.c index 4bc3c149..1a172789 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -119,6 +119,43 @@ static yajl_callbacks reply_callbacks = { .yajl_end_map = reply_end_map_cb, }; +/******************************************************************************* + * Config reply callbacks + *******************************************************************************/ + +static char *config_last_key = NULL; + +static int config_string_cb(void *params, const unsigned char *val, size_t len) { + char *str = scalloc(len + 1, 1); + strncpy(str, (const char *)val, len); + if (strcmp(config_last_key, "config") == 0) { + fprintf(stdout, "%s", str); + } + free(str); + return 1; +} + +static int config_start_map_cb(void *params) { + return 1; +} + +static int config_end_map_cb(void *params) { + return 1; +} + +static int config_map_key_cb(void *params, const unsigned char *keyVal, size_t keyLen) { + config_last_key = scalloc(keyLen + 1, 1); + strncpy(config_last_key, (const char *)keyVal, keyLen); + return 1; +} + +static yajl_callbacks config_callbacks = { + .yajl_string = config_string_cb, + .yajl_start_map = config_start_map_cb, + .yajl_map_key = config_map_key_cb, + .yajl_end_map = config_end_map_cb, +}; + int main(int argc, char *argv[]) { #if defined(__OpenBSD__) if (pledge("stdio rpath unix", NULL) == -1) @@ -150,25 +187,27 @@ int main(int argc, char *argv[]) { free(socket_path); socket_path = sstrdup(optarg); } else if (o == 't') { - if (strcasecmp(optarg, "command") == 0) + if (strcasecmp(optarg, "command") == 0) { message_type = I3_IPC_MESSAGE_TYPE_COMMAND; - else if (strcasecmp(optarg, "get_workspaces") == 0) + } else if (strcasecmp(optarg, "get_workspaces") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES; - else if (strcasecmp(optarg, "get_outputs") == 0) + } else if (strcasecmp(optarg, "get_outputs") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS; - else if (strcasecmp(optarg, "get_tree") == 0) + } else if (strcasecmp(optarg, "get_tree") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_TREE; - else if (strcasecmp(optarg, "get_marks") == 0) + } else if (strcasecmp(optarg, "get_marks") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS; - else if (strcasecmp(optarg, "get_bar_config") == 0) + } else if (strcasecmp(optarg, "get_bar_config") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG; - else if (strcasecmp(optarg, "get_binding_modes") == 0) + } else if (strcasecmp(optarg, "get_binding_modes") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES; - else if (strcasecmp(optarg, "get_version") == 0) + } else if (strcasecmp(optarg, "get_version") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION; - else { + } else if (strcasecmp(optarg, "get_config") == 0) { + message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG; + } else { printf("Unknown message type\n"); - printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version\n"); + printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n"); exit(EXIT_FAILURE); } } else if (o == 'q') { @@ -241,7 +280,7 @@ int main(int argc, char *argv[]) { errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected %d", reply_type, message_type); /* For the reply of commands, have a look if that command was successful. * If not, nicely format the error message. */ - if (reply_type == I3_IPC_MESSAGE_TYPE_COMMAND) { + if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) { yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL); yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length); yajl_free(handle); @@ -256,8 +295,24 @@ int main(int argc, char *argv[]) { /* NB: We still fall-through and print the reply, because even if one * command failed, that doesn’t mean that all commands failed. */ + } else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) { + yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL); + yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length); + yajl_free(handle); + + switch (state) { + case yajl_status_ok: + break; + case yajl_status_client_canceled: + case yajl_status_error: + errx(EXIT_FAILURE, "IPC: Could not parse JSON reply."); + } + + goto exit; } printf("%.*s\n", reply_length, reply); + +exit: free(reply); close(sockfd); diff --git a/include/configuration.h b/include/configuration.h index 66628eeb..4f6e5ce8 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -21,6 +21,7 @@ typedef struct Config Config; typedef struct Barconfig Barconfig; extern char *current_configpath; +extern char *current_config; extern Config config; extern SLIST_HEAD(modes_head, Mode) modes; extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs; diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 249cc32e..e3891454 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -54,6 +54,9 @@ typedef struct i3_ipc_header { /** Request a list of configured binding modes. */ #define I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES 8 +/** Request the raw last loaded i3 config. */ +#define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9 + /* * Messages from i3 to clients * @@ -67,6 +70,7 @@ typedef struct i3_ipc_header { #define I3_IPC_REPLY_TYPE_BAR_CONFIG 6 #define I3_IPC_REPLY_TYPE_VERSION 7 #define I3_IPC_REPLY_TYPE_BINDING_MODES 8 +#define I3_IPC_REPLY_TYPE_CONFIG 9 /* * Events from i3 to clients. Events have the first bit set high. diff --git a/src/config.c b/src/config.c index d4441d5d..7e08b520 100644 --- a/src/config.c +++ b/src/config.c @@ -13,6 +13,7 @@ #include <xkbcommon/xkbcommon.h> char *current_configpath = NULL; +char *current_config = NULL; Config config; struct modes_head modes; struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs); diff --git a/src/config_parser.c b/src/config_parser.c index 60b27815..e72923d6 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -898,6 +898,11 @@ bool parse_file(const char *f, bool use_nagbar) { if ((fstr = fdopen(fd, "r")) == NULL) die("Could not fdopen: %s\n", strerror(errno)); + FREE(current_config); + current_config = scalloc(stbuf.st_size + 1, 1); + fread(current_config, 1, stbuf.st_size, fstr); + rewind(fstr); + while (!feof(fstr)) { if (!continuation) continuation = buffer; diff --git a/src/ipc.c b/src/ipc.c index bb20b340..18d6075d 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -1087,9 +1087,30 @@ IPC_HANDLER(subscribe) { ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply); } +/* + * Returns the raw last loaded i3 configuration file contents. + */ +IPC_HANDLER(get_config) { + yajl_gen gen = ygenalloc(); + + y(map_open); + + ystr("config"); + ystr(current_config); + + y(map_close); + + const unsigned char *payload; + ylength length; + y(get_buf, &payload, &length); + + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_CONFIG, payload); + y(free); +} + /* The index of each callback function corresponds to the numeric * value of the message type (see include/i3/ipc.h) */ -handler_t handlers[9] = { +handler_t handlers[10] = { handle_command, handle_get_workspaces, handle_subscribe, @@ -1099,6 +1120,7 @@ handler_t handlers[9] = { handle_get_bar_config, handle_get_version, handle_get_binding_modes, + handle_get_config, }; /* diff --git a/testcases/t/268-ipc-config.t b/testcases/t/268-ipc-config.t new file mode 100644 index 00000000..bb578e13 --- /dev/null +++ b/testcases/t/268-ipc-config.t @@ -0,0 +1,55 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that the config file is returned raw via the IPC interface. +# Ticket: #2856 +# Bug still in: 4.13-133-ge4da07e7 +use i3test i3_autostart => 0; +use File::Temp qw(tempdir); + +my $tmpdir = tempdir(CLEANUP => 1); +my $socketpath = $tmpdir . "/config.sock"; +ok(! -e $socketpath, "$socketpath does not exist yet"); + +my $config = <<'EOT'; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +nop foo \ +continued + +set $var normal title +for_window [title="$vartest"] border none +EOT + +$config .= "ipc-socket $socketpath"; + +my $pid = launch_with_config($config, dont_add_socket_path => 1, dont_create_temp_dir => 1); +get_socket_path(0); +my $i3 = i3(get_socket_path()); +$i3->connect->recv; + +my $cv = AE::cv; +my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; + +my $last_config = $i3->get_config()->recv; +chomp($last_config->{config}); +is($last_config->{config}, $config, + 'received config is not equal to written config'); + +exit_gracefully($pid); + +done_testing; From d14c9bd6af765758d64d0e84ae12f645a9066481 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sat, 19 Aug 2017 19:06:23 +0200 Subject: [PATCH 160/180] Bump AnyEvent-I3 to 0.18 --- AnyEvent-I3/Changes | 4 ++++ AnyEvent-I3/lib/AnyEvent/I3.pm | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/AnyEvent-I3/Changes b/AnyEvent-I3/Changes index 713f7d1c..d763437f 100644 --- a/AnyEvent-I3/Changes +++ b/AnyEvent-I3/Changes @@ -1,5 +1,9 @@ Revision history for AnyEvent-I3 +0.18 2017-08-19 + + * support the GET_CONFIG command + 0.17 2017-04-09 * support the shutdown event diff --git a/AnyEvent-I3/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm index 5ce3c016..75845ccd 100644 --- a/AnyEvent-I3/lib/AnyEvent/I3.pm +++ b/AnyEvent-I3/lib/AnyEvent/I3.pm @@ -16,11 +16,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.17'; +our $VERSION = '0.18'; =head1 VERSION -Version 0.17 +Version 0.18 =head1 SYNOPSIS From 4d93d264849165713ecd05f4265d76f217789a95 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Sun, 20 Aug 2017 00:19:45 +0200 Subject: [PATCH 161/180] Bugfix: consider inactive monitors when querying (#2862) fixes #2815 fixes #2594 --- include/randr.h | 5 +++-- src/output.c | 4 ++-- src/randr.c | 11 ++++++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/include/randr.h b/include/randr.h index 8cbfc842..568b52f0 100644 --- a/include/randr.h +++ b/include/randr.h @@ -75,10 +75,11 @@ void randr_disable_output(Output *output); Output *get_first_output(void); /** - * Returns the output with the given name if it is active (!) or NULL. + * Returns the output with the given name or NULL. + * If require_active is true, only active outputs are considered. * */ -Output *get_output_by_name(const char *name); +Output *get_output_by_name(const char *name, const bool require_active); /** * Returns the active (!) output which contains the coordinates x, y or NULL diff --git a/src/output.c b/src/output.c index 0f2bd617..e3c54a67 100644 --- a/src/output.c +++ b/src/output.c @@ -41,7 +41,7 @@ Output *get_output_from_string(Output *current_output, const char *output_str) { return get_output_next_wrap(D_DOWN, current_output); } - return get_output_by_name(output_str); + return get_output_by_name(output_str, true); } Output *get_output_for_con(Con *con) { @@ -51,7 +51,7 @@ Output *get_output_for_con(Con *con) { return NULL; } - Output *output = get_output_by_name(output_con->name); + Output *output = get_output_by_name(output_con->name, true); if (output == NULL) { ELOG("Could not get output from name \"%s\".\n", output_con->name); return NULL; diff --git a/src/randr.c b/src/randr.c index 8496fd03..48bffb46 100644 --- a/src/randr.c +++ b/src/randr.c @@ -40,15 +40,16 @@ static Output *get_output_by_id(xcb_randr_output_t id) { } /* - * Returns the output with the given name if it is active (!) or NULL. + * Returns the output with the given name or NULL. + * If require_active is true, only active outputs are considered. * */ -Output *get_output_by_name(const char *name) { +Output *get_output_by_name(const char *name, const bool require_active) { Output *output; bool get_primary = (strcasecmp("primary", name) == 0); TAILQ_FOREACH(output, &outputs, outputs) { if ((output->primary && get_primary) || - (output->active && strcasecmp(output->name, name) == 0)) { + ((!require_active || output->active) && strcasecmp(output->name, name) == 0)) { return output; } } @@ -442,7 +443,7 @@ void init_ws_for_output(Output *output, Con *content) { if (visible && previous == NULL) { LOG("There is no workspace left on \"%s\", re-initializing\n", workspace_out->name); - init_ws_for_output(get_output_by_name(workspace_out->name), + init_ws_for_output(get_output_by_name(workspace_out->name, true), output_get_content(workspace_out)); DLOG("Done re-initializing, continuing with \"%s\"\n", output->name); } @@ -590,7 +591,7 @@ static bool randr_query_outputs_15(void) { xcb_get_atom_name_name(atom_reply)); free(atom_reply); - Output *new = get_output_by_name(name); + Output *new = get_output_by_name(name, false); if (new == NULL) { new = scalloc(1, sizeof(Output)); new->name = sstrdup(name); From 8106ae7923a4a94949435b65aca0aad303215432 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 20 Aug 2017 12:14:12 +0200 Subject: [PATCH 162/180] i3bar: Bugfix: avoid freeze after VisibilityNotify fixes #2790 --- i3bar/src/xcb.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 3bd5677a..2ba446b1 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1255,6 +1255,12 @@ char *init_xcb_early() { ev_prepare_init(xcb_prep, &xcb_prep_cb); ev_check_init(xcb_chk, &xcb_chk_cb); + /* Within an event loop iteration, run the xcb_chk watcher last: other + * watchers might call xcb_flush(), which, unexpectedly, can also read + * events into the queue (see _xcb_conn_wait). Hence, we need to drain xcb’s + * queue last, otherwise we risk dead-locking. */ + ev_set_priority(xcb_chk, EV_MINPRI); + ev_io_start(main_loop, xcb_io); ev_prepare_start(main_loop, xcb_prep); ev_check_start(main_loop, xcb_chk); From c25bee0ffca38a70d032712b973bd863de9157b2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 20 Aug 2017 12:54:49 +0200 Subject: [PATCH 163/180] Bugfix: check bounds before accessing memory This fixes the following issue when having an error early in the config file: ==1562==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6220000180ff at pc 0x55c837edb1d3 bp 0x7ffee7534650 sp 0x7ffee7534648 READ of size 1 at 0x6220000180ff thread T0 #0 0x55c837edb1d2 in start_of_line ../../i3/src/config_parser.c:238 #1 0x55c837edc96f in parse_config ../../i3/src/config_parser.c:493 #2 0x55c837edf527 in parse_file ../../i3/src/config_parser.c:1091 #3 0x55c837ecf14b in parse_configuration ../../i3/src/config.c:65 #4 0x55c837ed1ef4 in load_configuration ../../i3/src/config.c:230 #5 0x55c837f0a8d0 in main ../../i3/src/main.c:539 #6 0x7fb63ae042b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0) #7 0x55c837e95eb9 in _start (/home/michael/i3/build/i3+0x4beb9) 0x6220000180ff is located 1 bytes to the left of 5165-byte region [0x622000018100,0x62200001952d) allocated by thread T0 here: #0 0x7fb63e590cf8 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.3+0xc1cf8) #1 0x55c837f59aa6 in smalloc ../../i3/libi3/safewrappers.c:24 #2 0x55c837edef45 in parse_file ../../i3/src/config_parser.c:1029 #3 0x55c837ecf14b in parse_configuration ../../i3/src/config.c:65 #4 0x55c837ed1ef4 in load_configuration ../../i3/src/config.c:230 #5 0x55c837f0a8d0 in main ../../i3/src/main.c:539 #6 0x7fb63ae042b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0) --- src/config_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config_parser.c b/src/config_parser.c index e72923d6..c3684737 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -235,7 +235,7 @@ static void next_state(const cmdp_token *token) { * */ static const char *start_of_line(const char *walk, const char *beginning) { - while (*walk != '\n' && *walk != '\r' && walk >= beginning) { + while (walk >= beginning && *walk != '\n' && *walk != '\r') { walk--; } From dd019f59fa3e2935a29d7ad4c24020eb589e12c4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 20 Aug 2017 12:55:55 +0200 Subject: [PATCH 164/180] (Re-)initialize optional fields to empty strings --- src/config_parser.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/config_parser.c b/src/config_parser.c index c3684737..0fcdeb6e 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -916,6 +916,7 @@ bool parse_file(const char *f, bool use_nagbar) { } /* sscanf implicitly strips whitespace. */ + value[0] = '\0'; const bool skip_line = (sscanf(buffer, "%511s %4095[^\n]", key, value) < 1 || strlen(key) < 3); const bool comment = (key[0] == '#'); value[4095] = '\n'; @@ -938,7 +939,7 @@ bool parse_file(const char *f, bool use_nagbar) { if (strcasecmp(key, "set") == 0) { char v_key[512]; - char v_value[4096]; + char v_value[4096] = {'\0'}; if (sscanf(value, "%511s %4095[^\n]", v_key, v_value) < 1) { ELOG("Failed to parse variable specification '%s', skipping it.\n", value); @@ -953,9 +954,9 @@ bool parse_file(const char *f, bool use_nagbar) { upsert_variable(&variables, v_key, v_value); continue; } else if (strcasecmp(key, "set_from_resource") == 0) { - char res_name[512]; + char res_name[512] = {'\0'}; char v_key[512]; - char fallback[4096]; + char fallback[4096] = {'\0'}; /* Ensure that this string is terminated. For example, a user might * want a variable to be empty if the resource can't be found and From 8cc11dcb08985b980d5be41d4da5c31c652b6737 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 20 Aug 2017 12:57:04 +0200 Subject: [PATCH 165/180] =?UTF-8?q?Skip=20lines=20consisting=20only=20of?= =?UTF-8?q?=20=E2=80=9Cset=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config_parser.c b/src/config_parser.c index 0fcdeb6e..24a9bbfb 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -937,7 +937,7 @@ bool parse_file(const char *f, bool use_nagbar) { continue; } - if (strcasecmp(key, "set") == 0) { + if (strcasecmp(key, "set") == 0 && *value != '\0') { char v_key[512]; char v_value[4096] = {'\0'}; From bb0aac6e3916061c2325f31d57f7b83dc27eb285 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 20 Aug 2017 12:57:26 +0200 Subject: [PATCH 166/180] start nagbar when encountering invalid set statements related to #2564 --- src/config_parser.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/config_parser.c b/src/config_parser.c index 24a9bbfb..c88e9d1e 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -903,6 +903,8 @@ bool parse_file(const char *f, bool use_nagbar) { fread(current_config, 1, stbuf.st_size, fstr); rewind(fstr); + bool invalid_sets = false; + while (!feof(fstr)) { if (!continuation) continuation = buffer; @@ -943,11 +945,13 @@ bool parse_file(const char *f, bool use_nagbar) { if (sscanf(value, "%511s %4095[^\n]", v_key, v_value) < 1) { ELOG("Failed to parse variable specification '%s', skipping it.\n", value); + invalid_sets = true; continue; } if (v_key[0] != '$') { ELOG("Malformed variable assignment, name has to start with $\n"); + invalid_sets = true; continue; } @@ -968,11 +972,13 @@ bool parse_file(const char *f, bool use_nagbar) { if (sscanf(value, "%511s %511s %4095[^\n]", v_key, res_name, fallback) < 1) { ELOG("Failed to parse resource specification '%s', skipping it.\n", value); + invalid_sets = true; continue; } if (v_key[0] != '$') { ELOG("Malformed variable assignment, name has to start with $\n"); + invalid_sets = true; continue; } @@ -1088,12 +1094,12 @@ bool parse_file(const char *f, bool use_nagbar) { check_for_duplicate_bindings(context); reorder_bindings(); - if (use_nagbar && (context->has_errors || context->has_warnings)) { + if (use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) { ELOG("FYI: You are using i3 version %s\n", i3_version); if (version == 3) ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); - start_config_error_nagbar(f, context->has_errors); + start_config_error_nagbar(f, context->has_errors || invalid_sets); } bool has_errors = context->has_errors; From c04b8592fd40461b869fa1e9306bde9ade5d95c0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 20 Aug 2017 12:58:13 +0200 Subject: [PATCH 167/180] parser: only skip set[\s], not set.* fixes #2564 --- parser-specs/config.spec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 53828221..4aa320bf 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -17,7 +17,8 @@ state INITIAL: end -> error -> '#' -> IGNORE_LINE - 'set' -> IGNORE_LINE + 'set ' -> IGNORE_LINE + 'set ' -> IGNORE_LINE 'set_from_resource' -> IGNORE_LINE bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING 'bar' -> BARBRACE From 1e349ae3e1656de3357628de09517f18a453a6e3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 20 Aug 2017 13:12:06 +0200 Subject: [PATCH 168/180] t/201-config-parser: update expected token list --- testcases/t/201-config-parser.t | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 159de046..fb3130d5 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -446,8 +446,7 @@ hide_edge_border both client.focused #4c7899 #285577 #ffffff #2e9ef4 EOT -my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '#', '" . join("', '", qw( - set +my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '#', '" . join("', '", 'set ', 'set ', qw( set_from_resource bindsym bindcode From c70fa8078fd98ac68499330a78b8f9f67a48b07b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Sun, 20 Aug 2017 14:56:44 +0200 Subject: [PATCH 169/180] Focus windows upon ConfigureWindow with stack-mode=Above (#2865) fixes #2708 fixes #2745 --- src/handlers.c | 21 ++++++++++++++-- testcases/t/269-focus-stack-above.t | 39 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 testcases/t/269-focus-stack-above.t diff --git a/src/handlers.c b/src/handlers.c index 9fb9040e..7d83f541 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -400,11 +400,28 @@ static void handle_configure_request(xcb_configure_request_event_t *event) { DLOG("Dock client will not be moved, we only support moving it to another output.\n"); } } + fake_absolute_configure_notify(con); + return; + } + + if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) { + DLOG("window 0x%08x wants to be stacked %d\n", event->window, event->stack_mode); + if (event->stack_mode == XCB_STACK_MODE_ABOVE) { + /* Emacs and IntelliJ Idea “request focus” by stacking their window + * above all others. */ + if (!fullscreen && con_is_leaf(con)) { + if (strcmp(con_get_workspace(con)->name, "__i3_scratch") == 0) { + DLOG("This is a scratchpad container, ignoring ConfigureRequest\n"); + return; + } + + con_focus(con); + tree_render(); + } + } } fake_absolute_configure_notify(con); - - return; } /* diff --git a/testcases/t/269-focus-stack-above.t b/testcases/t/269-focus-stack-above.t new file mode 100644 index 00000000..e1785260 --- /dev/null +++ b/testcases/t/269-focus-stack-above.t @@ -0,0 +1,39 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies a ConfigureWindow request with stack-mode=Above is translated into +# focusing the target window by i3. +# Ticket: #2708 +# Bug still in: 4.13-207-gafdf6792 +use i3test; +use X11::XCB qw(CONFIG_WINDOW_STACK_MODE STACK_MODE_ABOVE); + +my $ws = fresh_workspace; +my $left_window = open_window; +my $right_window = open_window; + +is($x->input_focus, $right_window->id, 'right window has focus'); +my $old_focus = get_focused($ws); + +$x->configure_window($left_window->id, CONFIG_WINDOW_STACK_MODE, (STACK_MODE_ABOVE)); +$x->flush; + +sync_with_i3; + +is($x->input_focus, $left_window->id, 'left window has focus'); +isnt(get_focused($ws), $old_focus, 'right window is no longer focused'); + +done_testing; From fdb551f9d55c28d60f4270cccd0c058b799ca200 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Sun, 20 Aug 2017 15:30:27 +0200 Subject: [PATCH 170/180] i3bar: only restart child when command changed (#2866) this is a follow-up to https://github.com/i3/i3/commit/98f202dd1b2782d11a713513f5dcca2f52daab73 fixes #2689 --- i3bar/src/config.c | 1 + i3bar/src/ipc.c | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/i3bar/src/config.c b/i3bar/src/config.c index d065ff4c..cbe84d50 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -185,6 +185,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len if (!strcmp(cur_key, "status_command")) { DLOG("command = %.*s\n", len, val); + FREE(config.command); sasprintf(&config.command, "%.*s", len, val); return 1; } diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 042e230a..c932aaf7 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -113,7 +113,6 @@ void got_bar_config(char *reply) { init_colors(&(config.colors)); start_child(config.command); - FREE(config.command); } /* Data structure to easily call the reply handlers later */ @@ -178,6 +177,7 @@ void got_bar_config_update(char *event) { /* update the configuration with the received settings */ DLOG("Received bar config update \"%s\"\n", event); + char *old_command = sstrdup(config.command); bar_display_mode_t old_mode = config.hide_on_modifier; parse_config_json(event); if (old_mode != config.hide_on_modifier) { @@ -189,9 +189,11 @@ void got_bar_config_update(char *event) { init_colors(&(config.colors)); /* restart status command process */ - kill_child(); - start_child(config.command); - FREE(config.command); + if (strcmp(old_command, config.command) != 0) { + kill_child(); + start_child(config.command); + } + free(old_command); draw_bars(false); } From 260bcf283f58f24cc808a42707e3212cf7f429fd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Sun, 20 Aug 2017 17:07:23 +0200 Subject: [PATCH 171/180] Respect dont_warp flag when moving containers (#2867) fixes #2681 fixes #2592 --- src/con.c | 7 ++++- testcases/t/534-dont-warp.t | 61 +++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 testcases/t/534-dont-warp.t diff --git a/src/con.c b/src/con.c index 136ce5d2..cf923ec8 100644 --- a/src/con.c +++ b/src/con.c @@ -1105,8 +1105,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* Descend focus stack in case focus_next is a workspace which can * occur if we move to the same workspace. Also show current workspace * to ensure it is focused. */ - if (!ignore_focus) + if (!ignore_focus) { workspace_show(current_ws); + if (dont_warp) { + DLOG("x_set_warp_to(NULL) because dont_warp is set\n"); + x_set_warp_to(NULL); + } + } /* Set focus only if con was on current workspace before moving. * Otherwise we would give focus to some window on different workspace. */ diff --git a/testcases/t/534-dont-warp.t b/testcases/t/534-dont-warp.t new file mode 100644 index 00000000..8f84f9ae --- /dev/null +++ b/testcases/t/534-dont-warp.t @@ -0,0 +1,61 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies i3 doesn’t warp when a new floating window is opened under the cursor +# over an unfocused workspace. +# Ticket: #2681 +# Bug still in: 4.13-210-g80c23afa +use i3test i3_autostart => 0; + +# Ensure the pointer is at (0, 0) so that we really start on the first +# (the left) workspace. +$x->root->warp_pointer(0, 0); + +my $config = <<EOT; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +fake-outputs 1024x768+0+0,1024x768+1024+0 + +focus_follows_mouse no +EOT + +my $pid = launch_with_config($config); + +cmd 'focus output fake-0'; +my $s0_ws = fresh_workspace; + +cmd 'focus output fake-1'; +my $s1_ws = fresh_workspace; +open_window; + +# Move mouse to fake-0 +sync_with_i3; +$x->root->warp_pointer(500, 0); +sync_with_i3; + +my $dropdown = open_floating_window; +$dropdown->rect(X11::XCB::Rect->new(x => 1, y => 1, width => 100, height => 100)); +sync_with_i3; + +my $cookie = $x->query_pointer($dropdown->{id}); +my $reply = $x->query_pointer_reply($cookie->{sequence}); +cmp_ok($reply->{root_x}, '<', 1024, 'pointer still on fake-0'); +cmp_ok($reply->{root_y}, '<', 768, 'pointer still on fake-0'); + +exit_gracefully($pid); + +done_testing; From c9676e0cdb18133053c02e9d651cad89e42a442c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Sun, 20 Aug 2017 18:00:10 +0200 Subject: [PATCH 172/180] t/264-keypress-numlock: add mouse binding test fixes #2523 --- testcases/t/264-keypress-numlock.t | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/testcases/t/264-keypress-numlock.t b/testcases/t/264-keypress-numlock.t index 2fdafb83..fcc39ead 100644 --- a/testcases/t/264-keypress-numlock.t +++ b/testcases/t/264-keypress-numlock.t @@ -344,6 +344,47 @@ is(scalar @i3test::XTEST::binding_events, 18, 'Received exactly 18 binding event exit_gracefully($pid); +################################################################################ +# Verify mouse bindings are unaffected by NumLock +################################################################################ + +$config = <<EOT; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +bindsym --whole-window button4 nop button4 +EOT + +$pid = launch_with_config($config); + +my $win = open_window; + +start_binding_capture; + +is(listen_for_binding( + sub { + xtest_key_press(77); # enable Num_Lock + xtest_key_release(77); # enable Num_Lock + xtest_button_press(4, 50, 50); + xtest_button_release(4, 50, 50); + xtest_key_press(77); # disable Num_Lock + xtest_key_release(77); # disable Num_Lock + }, + ), + 'button4', + 'triggered the button4 keybinding with NumLock'); + +is(listen_for_binding( + sub { + xtest_button_press(4, 50, 50); + xtest_button_release(4, 50, 50); + }, + ), + 'button4', + 'triggered the button4 keybinding without NumLock'); + +exit_gracefully($pid); + } done_testing; From 1b0c9958d06ecfc4dfa16be831a5bbd3cd432b14 Mon Sep 17 00:00:00 2001 From: Johannes Lange <johannes.lange@rwth-aachen.de> Date: Mon, 5 Jun 2017 14:30:49 +0200 Subject: [PATCH 173/180] docs markup fixes --- docs/testsuite | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/testsuite b/docs/testsuite index db98da13..00beacb1 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -112,7 +112,7 @@ the tests without an X session with Xvfb, such as with +xvfb-run ./complete-run+. This will also speed up the tests significantly especially on machines without a powerful video card. -.Example invocation of complete-run.pl+ +.Example invocation of +complete-run.pl+ --------------------------------------- $ cd ~/i3 @@ -171,11 +171,11 @@ $ ./complete-run.pl --parallel=1 --keep-xserver-output This will show the output of Xephyr, which is the X server implementation we use for testing. -===== make command: make check +===== make command: +make check+ Make check runs the i3 testsuite. You can still use ./testcases/complete-run.pl to get the interactive progress output. -.Example invocation of make check+ +.Example invocation of +make check+ --------------------------------------- $ cd ~/i3 From c4474adfd671703968977264a20cc0fe3f6e7e86 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 28 Aug 2017 09:22:38 +0200 Subject: [PATCH 174/180] Fix memleak: free regex when parsing fails fixes #2541 --- src/regex.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/regex.c b/src/regex.c index 296b91dd..8f039157 100644 --- a/src/regex.c +++ b/src/regex.c @@ -38,6 +38,7 @@ struct regex *regex_new(const char *pattern) { } ELOG("PCRE regular expression compilation failed at %d: %s\n", offset, error); + regex_free(re); return NULL; } re->extra = pcre_study(re->regex, 0, &error); From d7e6cba17e1ecf69acf5424c828089143bed7eeb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 28 Aug 2017 09:23:42 +0200 Subject: [PATCH 175/180] do leak check before exiting related to #2541 --- src/main.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main.c b/src/main.c index 43efb3c2..21265446 100644 --- a/src/main.c +++ b/src/main.c @@ -21,6 +21,10 @@ #include <libgen.h> #include "shmlog.h" +#ifdef I3_ASAN_ENABLED +#include <sanitizer/lsan_interface.h> +#endif + #include "sd-daemon.h" /* The original value of RLIMIT_CORE when i3 was started. We need to restore @@ -551,6 +555,9 @@ int main(int argc, char *argv[]) { xcb_generic_error_t *error = xcb_request_check(conn, cookie); if (error != NULL) { ELOG("Another window manager seems to be running (X error %d)\n", error->error_code); +#ifdef I3_ASAN_ENABLED + __lsan_do_leak_check(); +#endif return 1; } From 044f03d5c77df80e03deaeec735e0d7f69060182 Mon Sep 17 00:00:00 2001 From: Chih-Chyuan Hwang <hwangcc@csie.nctu.edu.tw> Date: Mon, 28 Aug 2017 15:43:42 +0800 Subject: [PATCH 176/180] Update doc for dependency Module::Install (#2877) After moving to AnyEvent-I3, a new testsuite dependency is introduced: Module::Install. Update the doc for this. See the issue #2876. --- docs/testsuite | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/testsuite b/docs/testsuite index 00beacb1..795be042 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -82,6 +82,8 @@ The tests additionally require +Xephyr(1)+ to run a nested X server. Install $ cd ~/i3/testcases $ sudo apt-get install cpanminus $ sudo cpanm . +$ cd ~/i3/AnyEvent-I3 +$ sudo cpanm . -------------------------------------------------------------------------------- If you don’t want to use cpanminus for some reason, the same works with cpan: @@ -90,6 +92,8 @@ If you don’t want to use cpanminus for some reason, the same works with cpan: -------------------------------------------------------------------------------- $ cd ~/i3/testcases $ sudo cpan . +$ cd ~/i3/AnyEvent-I3 +$ sudo cpan . -------------------------------------------------------------------------------- In case you don’t have root permissions, you can also install into your home From 4dca8e6e0b7d061f6beedbac622019f2efaa960c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Mon, 28 Aug 2017 12:07:56 +0200 Subject: [PATCH 177/180] Respect focus_on_window_activation for ConfigureRequests (#2889) fixes #2873 --- src/handlers.c | 46 ++++++++++++++++++++++------- testcases/t/269-focus-stack-above.t | 16 ++++++++++ 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 7d83f541..c273e116 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -406,21 +406,45 @@ static void handle_configure_request(xcb_configure_request_event_t *event) { if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) { DLOG("window 0x%08x wants to be stacked %d\n", event->window, event->stack_mode); - if (event->stack_mode == XCB_STACK_MODE_ABOVE) { - /* Emacs and IntelliJ Idea “request focus” by stacking their window - * above all others. */ - if (!fullscreen && con_is_leaf(con)) { - if (strcmp(con_get_workspace(con)->name, "__i3_scratch") == 0) { - DLOG("This is a scratchpad container, ignoring ConfigureRequest\n"); - return; - } - con_focus(con); - tree_render(); - } + /* Emacs and IntelliJ Idea “request focus” by stacking their window + * above all others. */ + if (event->stack_mode != XCB_STACK_MODE_ABOVE) { + DLOG("stack_mode != XCB_STACK_MODE_ABOVE, ignoring ConfigureRequest\n"); + goto out; + } + + if (fullscreen || !con_is_leaf(con)) { + DLOG("fullscreen or not a leaf, ignoring ConfigureRequest\n"); + goto out; + } + + Con *ws = con_get_workspace(con); + if (ws == NULL) { + DLOG("Window is not being managed, ignoring ConfigureRequest\n"); + goto out; + } + + if (strcmp(ws->name, "__i3_scratch") == 0) { + DLOG("This is a scratchpad container, ignoring ConfigureRequest\n"); + goto out; + } + + if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) { + DLOG("Focusing con = %p\n", con); + workspace_show(ws); + con_focus(con); + tree_render(); + } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) { + DLOG("Marking con = %p urgent\n", con); + con_set_urgency(con, true); + tree_render(); + } else { + DLOG("Ignoring request for con = %p.\n", con); } } +out: fake_absolute_configure_notify(con); } diff --git a/testcases/t/269-focus-stack-above.t b/testcases/t/269-focus-stack-above.t index e1785260..a902d968 100644 --- a/testcases/t/269-focus-stack-above.t +++ b/testcases/t/269-focus-stack-above.t @@ -36,4 +36,20 @@ sync_with_i3; is($x->input_focus, $left_window->id, 'left window has focus'); isnt(get_focused($ws), $old_focus, 'right window is no longer focused'); +################################################################################ +# Verify the ConfigureWindow request is only applied when on the active +# workspace. +################################################################################ + +$ws = fresh_workspace; +my $new_window = open_window; + +is($x->input_focus, $new_window->id, 'new window has focus'); +$x->configure_window($left_window->id, CONFIG_WINDOW_STACK_MODE, (STACK_MODE_ABOVE)); +$x->flush; + +sync_with_i3; + +is($x->input_focus, $new_window->id, 'new window still has focus'); + done_testing; From 38b777c5fce81bde93437ccc6fa9b7d7f49ffcf3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <stapelberg@users.noreply.github.com> Date: Thu, 31 Aug 2017 22:50:00 +0200 Subject: [PATCH 178/180] Revert "i3-nagbar: add button flag to execute action with /bin/sh directly" (#2893) --- i3-nagbar/main.c | 116 +++++++++++++++++++--------------------------- man/i3-nagbar.man | 14 ++---- 2 files changed, 52 insertions(+), 78 deletions(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 6ec4294f..7d38f731 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -50,7 +50,6 @@ static char *argv0 = NULL; typedef struct { i3String *label; char *action; - bool exec_in_terminal; int16_t x; uint16_t width; } button_t; @@ -124,51 +123,6 @@ static void start_application(const char *command) { wait(0); } -static void execute_in_terminal(const char *command) { - /* We need to create a custom script containing our actual command - * since not every terminal emulator which is contained in - * i3-sensible-terminal supports -e with multiple arguments (and not - * all of them support -e with one quoted argument either). - * - * NB: The paths need to be unique, that is, don’t assume users close - * their nagbars at any point in time (and they still need to work). - * */ - char *script_path = get_process_filename("nagbar-cmd"); - - int fd = open(script_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); - if (fd == -1) { - warn("Could not create temporary script to store the nagbar command"); - return; - } - FILE *script = fdopen(fd, "w"); - if (script == NULL) { - warn("Could not fdopen() temporary script to store the nagbar command"); - return; - } - fprintf(script, "#!/bin/sh\nrm %s\n%s", script_path, command); - /* Also closes fd */ - fclose(script); - - char *link_path; - char *exe_path = get_exe_path(argv0); - sasprintf(&link_path, "%s.nagbar_cmd", script_path); - if (symlink(exe_path, link_path) == -1) { - err(EXIT_FAILURE, "Failed to symlink %s to %s", link_path, exe_path); - } - - char *terminal_cmd; - sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path); - printf("argv0 = %s\n", argv0); - printf("terminal_cmd = %s\n", terminal_cmd); - - start_application(terminal_cmd); - - free(link_path); - free(terminal_cmd); - free(script_path); - free(exe_path); -} - static button_t *get_button_at(int16_t x, int16_t y) { for (int c = 0; c < buttoncnt; c++) if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width)) @@ -195,15 +149,51 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width) exit(0); button_t *button = get_button_at(event->event_x, event->event_y); - if (!button) { + if (!button) + return; + + /* We need to create a custom script containing our actual command + * since not every terminal emulator which is contained in + * i3-sensible-terminal supports -e with multiple arguments (and not + * all of them support -e with one quoted argument either). + * + * NB: The paths need to be unique, that is, don’t assume users close + * their nagbars at any point in time (and they still need to work). + * */ + char *script_path = get_process_filename("nagbar-cmd"); + + int fd = open(script_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd == -1) { + warn("Could not create temporary script to store the nagbar command"); return; } - - if (button->exec_in_terminal) { - execute_in_terminal(button->action); - } else { - start_application(button->action); + FILE *script = fdopen(fd, "w"); + if (script == NULL) { + warn("Could not fdopen() temporary script to store the nagbar command"); + return; } + fprintf(script, "#!/bin/sh\nrm %s\n%s", script_path, button->action); + /* Also closes fd */ + fclose(script); + + char *link_path; + char *exe_path = get_exe_path(argv0); + sasprintf(&link_path, "%s.nagbar_cmd", script_path); + if (symlink(exe_path, link_path) == -1) { + err(EXIT_FAILURE, "Failed to symlink %s to %s", link_path, exe_path); + } + + char *terminal_cmd; + sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path); + printf("argv0 = %s\n", argv0); + printf("terminal_cmd = %s\n", terminal_cmd); + + start_application(terminal_cmd); + + free(link_path); + free(terminal_cmd); + free(script_path); + free(exe_path); /* TODO: unset flag, re-render */ } @@ -368,13 +358,12 @@ int main(int argc, char *argv[]) { {"version", no_argument, 0, 'v'}, {"font", required_argument, 0, 'f'}, {"button", required_argument, 0, 'b'}, - {"button-sh", required_argument, 0, 'B'}, {"help", no_argument, 0, 'h'}, {"message", required_argument, 0, 'm'}, {"type", required_argument, 0, 't'}, {0, 0, 0, 0}}; - char *options_string = "B:b:f:m:t:vh"; + char *options_string = "b:f:m:t:vh"; prompt = i3string_from_utf8("Please do not run this program."); @@ -396,26 +385,15 @@ int main(int argc, char *argv[]) { break; case 'h': printf("i3-nagbar " I3_VERSION "\n"); - printf("i3-nagbar [-m <message>] [-b <button> <terminal-action>] " - "[-B <button> <shell-action> [-t warning|error] [-f <font>] [-v]\n"); + printf("i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v]\n"); return 0; - /* falls through */ - case 'B': case 'b': buttons = srealloc(buttons, sizeof(button_t) * (buttoncnt + 1)); buttons[buttoncnt].label = i3string_from_utf8(optarg); buttons[buttoncnt].action = argv[optind]; - if (o == 'b') { - buttons[buttoncnt].exec_in_terminal = true; - printf("button with label *%s* and terminal action *%s*\n", - i3string_as_utf8(buttons[buttoncnt].label), - buttons[buttoncnt].action); - } else { - buttons[buttoncnt].exec_in_terminal = false; - printf("button with label *%s* and shell action *%s*\n", - i3string_as_utf8(buttons[buttoncnt].label), - buttons[buttoncnt].action); - } + printf("button with label *%s* and action *%s*\n", + i3string_as_utf8(buttons[buttoncnt].label), + buttons[buttoncnt].action); buttoncnt++; printf("now %d buttons\n", buttoncnt); if (optind < argc) diff --git a/man/i3-nagbar.man b/man/i3-nagbar.man index 7dd3a238..77fdd80b 100644 --- a/man/i3-nagbar.man +++ b/man/i3-nagbar.man @@ -9,7 +9,7 @@ i3-nagbar - displays an error bar on top of your screen == SYNOPSIS -i3-nagbar [-m <message>] [-b <button> <terminal-action>] [-B <button> <shell-action>] [-t warning|error] [-f <font>] [-v] +i3-nagbar [-m <message>] [-b <button> <action>] [-t warning|error] [-f <font>] [-v] == OPTIONS @@ -29,13 +29,9 @@ Display 'message' as text on the left of the i3-nagbar. *-f, --font* 'font':: Select font that is being used. -*-b, --button* 'button' 'terminal-action':: -Adds a button labelled 'button' to the bar. When pressed, the command given -in 'terminal-action' is executed inside a terminal emulator, via i3-sensible-terminal(1). -Multiple buttons can be defined. - -*-B, --button-sh* 'button' 'shell-action':: -Same as *--button*, except that the command given in 'shell-action' is executed directly by the shell. +*-b, --button* 'button' 'action':: +Create a button with text 'button'. The 'action' are the shell commands that +will be executed by this button. Multiple buttons can be defined. == DESCRIPTION @@ -53,7 +49,7 @@ i3-nagbar -m 'You have an error in your i3 config file!' \ == SEE ALSO -i3(1), i3-sensible-terminal(1) +i3(1) == AUTHOR From abe725cf6547c749f7f997610554dec7bbba7728 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 4 Sep 2017 07:53:25 +0200 Subject: [PATCH 179/180] release i3 4.14 --- I3_VERSION | 2 +- Makefile.am | 2 +- RELEASE-NOTES-4.13 | 114 --------------------------------------------- RELEASE-NOTES-4.14 | 94 +++++++++++++++++++++++++++++++++++++ configure.ac | 2 +- 5 files changed, 97 insertions(+), 117 deletions(-) delete mode 100644 RELEASE-NOTES-4.13 create mode 100644 RELEASE-NOTES-4.14 diff --git a/I3_VERSION b/I3_VERSION index d4cfa42a..6776cd68 100644 --- a/I3_VERSION +++ b/I3_VERSION @@ -1 +1 @@ -4.13-non-git +4.14 (2017-09-04) diff --git a/Makefile.am b/Makefile.am index 0da98732..3ea300e5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -100,7 +100,7 @@ EXTRA_DIST = \ I3_VERSION \ LICENSE \ PACKAGE-MAINTAINER \ - RELEASE-NOTES-4.13 \ + RELEASE-NOTES-4.14 \ generate-command-parser.pl \ parser-specs/commands.spec \ parser-specs/config.spec \ diff --git a/RELEASE-NOTES-4.13 b/RELEASE-NOTES-4.13 deleted file mode 100644 index 0e854569..00000000 --- a/RELEASE-NOTES-4.13 +++ /dev/null @@ -1,114 +0,0 @@ - - ┌────────────────────────────┐ - │ Release notes for i3 v4.13 │ - └────────────────────────────┘ - -This is i3 v4.13. This version is considered stable. All users of i3 are -strongly encouraged to upgrade. - -For users, there are two changes to be aware of: - -1. The X server DPI is read from the Xft.dpi X resource (if available). - Previously, i3 used to directly look at the X server’s DPI (based on screen - resolution and physical size). Looking at Xft.dpi is more consistent with - other software, more likely to be correct (because it’s user-specified and - not read from possibly broken hardware information) and allows users to - override the value. - -2. It is now possible to set config file variables from X resources using the - “set_from_resource” directive. This allows users to have a single source of - truth for e.g. theming X11 applications (specify “*color0: #121212” and have - it apply to URxvt and your i3 config). - -For packagers, there are three changes that likely require action: - -1. cairo/pango are now required dependencies, as announced in the i3 v4.12 - release notes. - -2. The aforementioned “set_from_resource” feature requires the new dependency - libxcb-util-xrm. - -3. i3 now uses the GNU build system (autotools). Please see - https://github.com/i3/i3/commit/4a52a7e9fb6fb2e1f0256b2e086cfa313f411cd8 for - a lot more details about the rationale and what this means for your package. - Bottomline, things should get simpler for you, though :). - - ┌────────────────────────────┐ - │ Changes in i3 v4.13 │ - └────────────────────────────┘ - - • build: wire up version handling for non-release tarballs (as opposed to git - checkouts) - • build: switch to the GNU build system - • i3bar: disable pango markup for plain-text input - • man/i3-msg: point out default ipc message type - • config: introduce support for specifying variables from X resources - • config: ensure variables match on longest-length, eliminating problems - where one variable was a prefix of another - • config: do not count '\' in comment lines as line continuation - • ipc: introduce a new GET_BINDING_MODES command - • ipc: implement new window::mark event - • ipc: add “output” to IPC events referencing a container - • make fullscreen windows open on the output which is indicated by their - geometry (fixes LibreOffice Impress multi-monitor presentations) - • focus newly managed windows only if they don’t use the globally active - input mode (fixes issues with RubyMine) - • remove title indentation in nested containers (rationale was unclear, - nobody spoke up when we asked about the feature on i3-discuss) - • use the last known timestamp when calling xcb_set_input_focus (might fix - rare race conditions in focus handling) - • introduce the “smart” option for hide_edge_borders, which will hide borders - when there is precisely one window on the workspace - • handle _MOTIF_WM_HINTS changes (_MOTIF_WM_HINTS were previously only - considered when managing a new window) - • don’t change border style if BS_NORMAL is requested in _MOTIF_WM_HINTS - • only add numlock fallback for keybindings where necessary (allows users to - correctly bind keys on the numpad) - • do not match docks in config and command criteria - • get DPI from the Xft.dpi resource instead of directly looking at the screen - resolution/size - • handle _NET_ACTIVE_WINDOW for scratchpad windows (for pagers) - • set _NET_WM_DESKTOP to sticky for scratchpad windows - • add new criteria “tiling” and “floating” - • implement special output name “current” for commands - • handle ResizeRequests for tray clients (fixes VLC tray icon) - - ┌────────────────────────────┐ - │ Bugfixes │ - └────────────────────────────┘ - - • i3bar: fix crash when the I3SOCK environment variable is present - • i3-dmenu-desktop: do not die on failed open - • i3-input: properly position in non-standard cases (fixes an issue where - i3-input would launch off-screen) - • i3-save-tree: rename “mark” to “marks” to reflect our recent change to - allow multiple marks - • mouse bindings: only grab the mouse buttons that need to be grabbed - • no_focus: correctly count the number of windows (makes no_focus work with - tabbed/stacked workspace layouts). - • properly close disabled outputs restored during a restart (this fixes state - handling when RandR changes happen during i3 restarts) - • don’t trigger bindings on window border clicks unless --border was - specified for the binding - • traverse numbered workspaces in correct order - • fix transition from named to numbered workspaces in “workspace next|prev” - • avoid setting urgency hint on content containers and above (fixes crashes) - • don’t trigger unrelated key bindings for --release bindings - • fix colormap handling for containers (fixes taking screenshots using xwd) - • check output crossing on ENTER_NOTIFY to dockarea (fixes pointer jumping) - • fix a use-after-free bug (fixes “floating enable” on single split windows) - - ┌────────────────────────────┐ - │ Thanks! │ - └────────────────────────────┘ - -Thanks for testing, bugfixes, discussions and everything I forgot go out to: - - Benedikt Heine, Cedric Buissart, Chih-Chyuan Hwang, Denton Liu, eplanet, Eric - Engeström, EvilPudding, Ferdinand Bachmann, Hong, Ingo Bürk, Jakob Schnell, - Jakub Wilk, johannes karoff, Johannes Lange, joshrosso, Julien Lequertier, - Kacper Kowalik, Kenneth Lyons, Kyle Kneitinger, madroach, Michael Vetter, - Nathan Schulte, Øsse, Peder Stray, Tony Crisci, Trevor Merrifield, wentasah, - yshui, Zamarin Arthur - --- Michael Stapelberg, 2016-11-08 diff --git a/RELEASE-NOTES-4.14 b/RELEASE-NOTES-4.14 new file mode 100644 index 00000000..2ab058f2 --- /dev/null +++ b/RELEASE-NOTES-4.14 @@ -0,0 +1,94 @@ + + ┌────────────────────────────┐ + │ Release notes for i3 v4.14 │ + └────────────────────────────┘ + +This is i3 v4.14. This version is considered stable. All users of i3 are +strongly encouraged to upgrade. + +Aside from many bug and documentation fixes, the “swap” command is a notable +addition of this release. As is almost tradition at this point, keybinding +handling has seen some fixes as well. A noticeable change for users with such +monitors is i3’s support for RandR 1.5, which transparently supports the TILE +property of first-gen 4K monitors and current 5K or 8K monitors. + + ┌────────────────────────────┐ + │ Changes in i3 v4.14 │ + └────────────────────────────┘ + + • build: link libiconv explicitly for systems which need it + • build: move AnyEvent-I3 into the i3 repository + • docs/hacking-howto: add compilation instructions + • docs/ipc: add missing cases to the workspace event + • docs/ipc: document the “primary” field of the OUTPUTS reply + • docs/ipc: replace Go IPC library with a maintained one + • docs/ipc: add link to the ocaml-i3ipc library + • docs/ipc: fix invalid trailing commas in JSON examples + • docs/layout-saving: add section about troubleshooting window titles + • docs/testsuite: update for the move to autotools + • docs/userguide: clarify the move command syntax + • docs/userguide: correct “Esc” to “Escape” + • docs/userguide: clarify focus_follows_mouse behavior + • docs/userguide: expand on combining “workspace number” with a name + • docs/userguide: mention the magic v4 config marker + • man/i3.man: correct configuration lookup order + • i3bar, i3-config-wizard, i3-nagbar: use the Xft.dpi setting (see 4.13 notes) + • i3bar: restart bar status command on reload if it changed + • i3bar: treat left/right scrolling like up/down scrolling + • i3bar: accept “primary” in the “output” configuration directive + • i3-input: do not set input focus, grabbing the keyboard suffices + • i3-msg: return an exit code when missing the -t argument + • i3-sensible-editor: correct “mc-edit” to “mcedit” + • i3-sensible-terminal: add lilyterm, tilix, terminix, konsole + • respect SYSCONFDIR when looking for the default xdg directory + • use RandR 1.5 to query screens, supporting the TILE property commonly used + by multi-stream transport (MST) monitors, such as first-gen 4K monitors, or + current 5K and 8K monitors + • respect minimum size hints for floating windows + • support the _NET_MOVERESIZE_WINDOW client message (for e.g. wmctrl) + • validate binding modes are not defined more than once + • only react to the last ExposeEvent in a series of events + • add the shutdown IPC event (upon “restart” or “exit”) + • treat left/right scrolling like up/down scrolling (on window titles) + • make the “layout toggle” command optionally take a sequence of layouts + • introduce --exclude-titlebar flag for mouse bindings + • introduce the “swap” command + • support the primary output in the “focus” and “move” commands + • compare keybinding modifiers for equality, not subset + • introduce the GET_CONFIG ipc request (i3-msg -t get_config) + • start i3-nagbar when encountering invalid set statements + • focus windows upon ConfigureWindow requests with stack-mode=Above + + ┌────────────────────────────┐ + │ Bugfixes │ + └────────────────────────────┘ + + • i3bar: correct the color codes used for statusline errors + • i3bar: avoid freeze after VisibilityNotify + • i3-dmenu-desktop: fix quoted command names + • i3-dmenu-desktop: avoid adding items multiple times + • fix various X11 resource leaks, memory leaks and memory errors + • fix IPC success reply for the workspace command + • report errors during logfile creation + • fix the signal handler being blank + • display marks and the title even if the title is empty (for title_format) + • fix changing workspace layout from stacked/tabbed for empty workspaces + • add numlock fallback to “bindcode” where necessary + • fix a crash on restart when using marks + • fix renaming workspaces when the new name starts with “to” + • respect dont_warp flag when moving containers + + ┌────────────────────────────┐ + │ Thanks! │ + └────────────────────────────┘ + +Thanks for testing, bugfixes, discussions and everything I forgot go out to: + + akash akya, Armaël Guéneau, Baptiste Daroussin, Chih-Chyuan Hwang, cresh, + David Jimenez Sequero, Franz König, fred777, Ingo Bürk, Jakub Wilk, + Jens-Wolfhard Schicke-Uffmann, Johannes Lange, lasers, lebenlechzer, + loungecube, Maarten Dirkse, Manuel Mendez, Max Fisher, Mihai Coman, Nathan + Schulte, s3rb31, Sebastian Larsson, Stefan Hagen, Tobias Hänel, Tony Crisci, + Trevor Merrifield, Zbyněk Moravec + +-- Michael Stapelberg, 2017-09-04 diff --git a/configure.ac b/configure.ac index f2bf26f8..7d274e36 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Run autoreconf -fi to generate a configure script from this file. AC_PREREQ([2.69]) -AC_INIT([i3], [4.13], [https://github.com/i3/i3/issues]) +AC_INIT([i3], [4.14], [https://github.com/i3/i3/issues]) # For AX_EXTEND_SRCDIR AX_ENABLE_BUILDDIR AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2]) From 9e2e4881ab4672070a505ae522b21cad6985c86e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg <michael@stapelberg.de> Date: Mon, 4 Sep 2017 07:53:39 +0200 Subject: [PATCH 180/180] Set non-git version to 4.14-non-git. --- I3_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I3_VERSION b/I3_VERSION index 6776cd68..af1432e3 100644 --- a/I3_VERSION +++ b/I3_VERSION @@ -1 +1 @@ -4.14 (2017-09-04) +4.14-non-git