├── .gitignore ├── screenshots └── popover.png ├── dwt.gresources.xml ├── dwt.desktop ├── dwt-settings.h ├── meson.build ├── .ycm_extra_conf.py ├── COPYING ├── themes ├── thayer.h ├── wombat.h ├── molokai.h ├── monokai.h ├── zenburn.h ├── solarized.h ├── jellybeans.h └── linux.h ├── menus.xml ├── README.rst ├── tools └── convert-theme-xrdb ├── tests └── test-settings.c ├── dg-settings.h ├── dwt.rst ├── dwt-settings.c ├── dwt.1 ├── dg-settings.c └── dwt.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | dwt 3 | *.sw[op] 4 | *.py[co] 5 | *.gresources.[hc] 6 | tests/test-settings 7 | -------------------------------------------------------------------------------- /screenshots/popover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aperezdc/dwt/master/screenshots/popover.png -------------------------------------------------------------------------------- /dwt.gresources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | menus.xml 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /dwt.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Hidden=false 3 | Name=dwt 4 | Exec=dwt 5 | TryExec=dwt 6 | Icon=utilities-terminal 7 | Comment=Use the command line 8 | Type=Application 9 | NoDisplay=false 10 | StartupNotify=true 11 | StartupWMClass=Dwt 12 | Categories=GNOME;GTK;Utility;TerminalEmulator; 13 | Terminal=false 14 | Actions=New 15 | 16 | [Desktop Action New] 17 | Name=New Terminal 18 | Exec=dwt 19 | -------------------------------------------------------------------------------- /dwt-settings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * dwt-settings.h 3 | * Copyright (C) 2014 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef DWT_SETTINGS_H 9 | #define DWT_SETTINGS_H 10 | 11 | #include 12 | 13 | G_BEGIN_DECLS 14 | 15 | typedef struct _DwtSettings DwtSettings; 16 | 17 | DwtSettings* dwt_settings_get_instance (void); 18 | 19 | G_END_DECLS 20 | 21 | #endif /* !DWT_SETTINGS_H */ 22 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('dwt', 'c', 2 | version: '0.6.1', 3 | default_options: [ 4 | 'buildtype=debugoptimized', 5 | 'c_std=c11', 6 | 'b_lto=true', 7 | ] 8 | ) 9 | 10 | gnome = import('gnome') 11 | 12 | executable('dwt', 13 | 'dwt.c', 14 | 'dwt-settings.c', 15 | 'dg-settings.c', 16 | gnome.compile_resources('dwt.gresources', 'dwt.gresources.xml'), 17 | dependencies: dependency('vte-2.91', version: '>=0.50'), 18 | install: true, 19 | ) 20 | 21 | install_man('dwt.1') 22 | 23 | install_data('dwt.desktop', 24 | install_dir: join_paths(get_option('datadir'), 'applications')) 25 | -------------------------------------------------------------------------------- /.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 Adrian Perez 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | from subprocess import check_output 10 | from shlex import split as sh_split 11 | 12 | def FlagsForFile(path, **kwarg): 13 | flags = sh_split(check_output(["make", "print-flags"])) 14 | flags.extend(("-Qunused-arguments", 15 | "-DDWT_USE_POPOVER=TRUE", 16 | "-DDWT_USE_OVERLAY=TRUE")) 17 | return { 'flags': flags, 'do_cache': True } 18 | 19 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 by Adrian Perez 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /themes/thayer.h: -------------------------------------------------------------------------------- 1 | #ifndef THEME_THAYER_H 2 | #define THEME_THAYER_H 1 3 | { 4 | .name = "thayer", 5 | .fg = { 0.972549, 0.972549, 0.972549, 1.000000 }, /* #f8f8f8 */ 6 | .bg = { 0.105882, 0.113725, 0.117647, 1.000000 }, /* #1b1d1e */ 7 | .colors = { 8 | { 0.105882, 0.113725, 0.117647, 1.000000 }, /* #1b1d1e */ 9 | { 0.976471, 0.149020, 0.447059, 1.000000 }, /* #f92672 */ 10 | { 0.301961, 0.972549, 0.250980, 1.000000 }, /* #4df840 */ 11 | { 0.956863, 0.992157, 0.133333, 1.000000 }, /* #f4fd22 */ 12 | { 0.152941, 0.341176, 0.839216, 1.000000 }, /* #2757d6 */ 13 | { 0.549020, 0.329412, 0.996078, 1.000000 }, /* #8c54fe */ 14 | { 0.219608, 0.784314, 0.709804, 1.000000 }, /* #38c8b5 */ 15 | { 0.800000, 0.800000, 0.776471, 1.000000 }, /* #ccccc6 */ 16 | { 0.313725, 0.325490, 0.329412, 1.000000 }, /* #505354 */ 17 | { 1.000000, 0.349020, 0.584314, 1.000000 }, /* #ff5995 */ 18 | { 0.713725, 0.890196, 0.329412, 1.000000 }, /* #b6e354 */ 19 | { 0.996078, 0.929412, 0.423529, 1.000000 }, /* #feed6c */ 20 | { 0.247059, 0.470588, 1.000000, 1.000000 }, /* #3f78ff */ 21 | { 0.619608, 0.435294, 0.996078, 1.000000 }, /* #9e6ffe */ 22 | { 0.137255, 0.811765, 0.835294, 1.000000 }, /* #23cfd5 */ 23 | { 0.972549, 0.972549, 0.949020, 1.000000 }, /* #f8f8f2 */ 24 | }, 25 | }, 26 | #endif /* THEME_THAYER */ 27 | -------------------------------------------------------------------------------- /themes/wombat.h: -------------------------------------------------------------------------------- 1 | #ifndef THEME_WOMBAT_H 2 | #define THEME_WOMBAT_H 1 3 | { 4 | .name = "wombat", 5 | .fg = { 0.870588, 0.854902, 0.811765, 1.000000 }, /* #dedacf */ 6 | .bg = { 0.090196, 0.090196, 0.090196, 1.000000 }, /* #171717 */ 7 | .colors = { 8 | { 0.000000, 0.000000, 0.000000, 1.000000 }, /* #000000 */ 9 | { 1.000000, 0.380392, 0.352941, 1.000000 }, /* #ff615a */ 10 | { 0.694118, 0.913725, 0.411765, 1.000000 }, /* #b1e969 */ 11 | { 0.921569, 0.850980, 0.611765, 1.000000 }, /* #ebd99c */ 12 | { 0.364706, 0.662745, 0.964706, 1.000000 }, /* #5da9f6 */ 13 | { 0.909804, 0.415686, 1.000000, 1.000000 }, /* #e86aff */ 14 | { 0.509804, 1.000000, 0.968627, 1.000000 }, /* #82fff7 */ 15 | { 0.870588, 0.854902, 0.811765, 1.000000 }, /* #dedacf */ 16 | { 0.192157, 0.192157, 0.192157, 1.000000 }, /* #313131 */ 17 | { 0.960784, 0.549020, 0.501961, 1.000000 }, /* #f58c80 */ 18 | { 0.866667, 0.972549, 0.560784, 1.000000 }, /* #ddf88f */ 19 | { 0.933333, 0.898039, 0.698039, 1.000000 }, /* #eee5b2 */ 20 | { 0.647059, 0.780392, 1.000000, 1.000000 }, /* #a5c7ff */ 21 | { 0.866667, 0.666667, 1.000000, 1.000000 }, /* #ddaaff */ 22 | { 0.717647, 1.000000, 0.976471, 1.000000 }, /* #b7fff9 */ 23 | { 1.000000, 1.000000, 1.000000, 1.000000 }, /* #ffffff */ 24 | }, 25 | }, 26 | #endif /* THEME_WOMBAT */ 27 | -------------------------------------------------------------------------------- /themes/molokai.h: -------------------------------------------------------------------------------- 1 | #ifndef THEME_MOLOKAI_H 2 | #define THEME_MOLOKAI_H 1 3 | { 4 | .name = "molokai", 5 | .fg = { 0.733333, 0.733333, 0.733333, 1.000000 }, /* #bbbbbb */ 6 | .bg = { 0.070588, 0.070588, 0.070588, 1.000000 }, /* #121212 */ 7 | .colors = { 8 | { 0.070588, 0.070588, 0.070588, 1.000000 }, /* #121212 */ 9 | { 0.980392, 0.145098, 0.450980, 1.000000 }, /* #fa2573 */ 10 | { 0.596078, 0.882353, 0.137255, 1.000000 }, /* #98e123 */ 11 | { 0.874510, 0.831373, 0.376471, 1.000000 }, /* #dfd460 */ 12 | { 0.062745, 0.501961, 0.815686, 1.000000 }, /* #1080d0 */ 13 | { 0.529412, 0.000000, 1.000000, 1.000000 }, /* #8700ff */ 14 | { 0.262745, 0.658824, 0.815686, 1.000000 }, /* #43a8d0 */ 15 | { 0.733333, 0.733333, 0.733333, 1.000000 }, /* #bbbbbb */ 16 | { 0.333333, 0.333333, 0.333333, 1.000000 }, /* #555555 */ 17 | { 0.964706, 0.400000, 0.615686, 1.000000 }, /* #f6669d */ 18 | { 0.694118, 0.878431, 0.372549, 1.000000 }, /* #b1e05f */ 19 | { 1.000000, 0.949020, 0.427451, 1.000000 }, /* #fff26d */ 20 | { 0.000000, 0.686275, 1.000000, 1.000000 }, /* #00afff */ 21 | { 0.686275, 0.529412, 1.000000, 1.000000 }, /* #af87ff */ 22 | { 0.317647, 0.807843, 1.000000, 1.000000 }, /* #51ceff */ 23 | { 1.000000, 1.000000, 1.000000, 1.000000 }, /* #ffffff */ 24 | }, 25 | }, 26 | #endif /* THEME_MOLOKAI */ 27 | -------------------------------------------------------------------------------- /themes/monokai.h: -------------------------------------------------------------------------------- 1 | #ifndef THEME_MONOKAI_H 2 | #define THEME_MONOKAI_H 1 3 | { 4 | .name = "monokai", 5 | .fg = { 0.768627, 0.772549, 0.709804, 1.000000 }, /* #c4c5b5 */ 6 | .bg = { 0.101961, 0.101961, 0.101961, 1.000000 }, /* #1a1a1a */ 7 | .colors = { 8 | { 0.101961, 0.101961, 0.101961, 1.000000 }, /* #1a1a1a */ 9 | { 0.956863, 0.000000, 0.372549, 1.000000 }, /* #f4005f */ 10 | { 0.596078, 0.878431, 0.141176, 1.000000 }, /* #98e024 */ 11 | { 0.980392, 0.517647, 0.098039, 1.000000 }, /* #fa8419 */ 12 | { 0.615686, 0.396078, 1.000000, 1.000000 }, /* #9d65ff */ 13 | { 0.956863, 0.000000, 0.372549, 1.000000 }, /* #f4005f */ 14 | { 0.345098, 0.819608, 0.921569, 1.000000 }, /* #58d1eb */ 15 | { 0.768627, 0.772549, 0.709804, 1.000000 }, /* #c4c5b5 */ 16 | { 0.384314, 0.368627, 0.298039, 1.000000 }, /* #625e4c */ 17 | { 0.956863, 0.000000, 0.372549, 1.000000 }, /* #f4005f */ 18 | { 0.596078, 0.878431, 0.141176, 1.000000 }, /* #98e024 */ 19 | { 0.878431, 0.835294, 0.380392, 1.000000 }, /* #e0d561 */ 20 | { 0.615686, 0.396078, 1.000000, 1.000000 }, /* #9d65ff */ 21 | { 0.956863, 0.000000, 0.372549, 1.000000 }, /* #f4005f */ 22 | { 0.345098, 0.819608, 0.921569, 1.000000 }, /* #58d1eb */ 23 | { 0.964706, 0.964706, 0.937255, 1.000000 }, /* #f6f6ef */ 24 | }, 25 | }, 26 | #endif /* THEME_MONOKAI */ 27 | -------------------------------------------------------------------------------- /themes/zenburn.h: -------------------------------------------------------------------------------- 1 | #ifndef THEME_ZENBURN_H 2 | #define THEME_ZENBURN_H 1 3 | { 4 | .name = "zenburn", 5 | .fg = { 0.862745, 0.862745, 0.800000, 1.000000 }, /* #dcdccc */ 6 | .bg = { 0.247059, 0.247059, 0.247059, 1.000000 }, /* #3f3f3f */ 7 | .colors = { 8 | { 0.301961, 0.301961, 0.301961, 1.000000 }, /* #4d4d4d */ 9 | { 0.439216, 0.313725, 0.313725, 1.000000 }, /* #705050 */ 10 | { 0.376471, 0.705882, 0.541176, 1.000000 }, /* #60b48a */ 11 | { 0.941176, 0.874510, 0.686275, 1.000000 }, /* #f0dfaf */ 12 | { 0.313725, 0.376471, 0.439216, 1.000000 }, /* #506070 */ 13 | { 0.862745, 0.549020, 0.764706, 1.000000 }, /* #dc8cc3 */ 14 | { 0.549020, 0.815686, 0.827451, 1.000000 }, /* #8cd0d3 */ 15 | { 0.862745, 0.862745, 0.800000, 1.000000 }, /* #dcdccc */ 16 | { 0.439216, 0.564706, 0.501961, 1.000000 }, /* #709080 */ 17 | { 0.862745, 0.639216, 0.639216, 1.000000 }, /* #dca3a3 */ 18 | { 0.764706, 0.749020, 0.623529, 1.000000 }, /* #c3bf9f */ 19 | { 0.878431, 0.811765, 0.623529, 1.000000 }, /* #e0cf9f */ 20 | { 0.580392, 0.749020, 0.952941, 1.000000 }, /* #94bff3 */ 21 | { 0.925490, 0.576471, 0.827451, 1.000000 }, /* #ec93d3 */ 22 | { 0.576471, 0.878431, 0.890196, 1.000000 }, /* #93e0e3 */ 23 | { 1.000000, 1.000000, 1.000000, 1.000000 }, /* #ffffff */ 24 | }, 25 | }, 26 | #endif /* THEME_ZENBURN */ 27 | -------------------------------------------------------------------------------- /themes/solarized.h: -------------------------------------------------------------------------------- 1 | #ifndef THEME_SOLARIZED_H 2 | #define THEME_SOLARIZED_H 1 3 | { 4 | .name = "solarized", 5 | .fg = { 0.611765, 0.760784, 0.764706, 1.000000 }, /* #9cc2c3 */ 6 | .bg = { 0.000000, 0.117647, 0.152941, 1.000000 }, /* #001e27 */ 7 | .colors = { 8 | { 0.000000, 0.156863, 0.192157, 1.000000 }, /* #002831 */ 9 | { 0.819608, 0.109804, 0.141176, 1.000000 }, /* #d11c24 */ 10 | { 0.423529, 0.745098, 0.423529, 1.000000 }, /* #6cbe6c */ 11 | { 0.647059, 0.466667, 0.023529, 1.000000 }, /* #a57706 */ 12 | { 0.129412, 0.462745, 0.780392, 1.000000 }, /* #2176c7 */ 13 | { 0.776471, 0.109804, 0.435294, 1.000000 }, /* #c61c6f */ 14 | { 0.145098, 0.572549, 0.525490, 1.000000 }, /* #259286 */ 15 | { 0.917647, 0.890196, 0.796078, 1.000000 }, /* #eae3cb */ 16 | { 0.000000, 0.392157, 0.533333, 1.000000 }, /* #006488 */ 17 | { 0.960784, 0.086275, 0.231373, 1.000000 }, /* #f5163b */ 18 | { 0.317647, 0.937255, 0.517647, 1.000000 }, /* #51ef84 */ 19 | { 0.698039, 0.494118, 0.156863, 1.000000 }, /* #b27e28 */ 20 | { 0.090196, 0.556863, 0.784314, 1.000000 }, /* #178ec8 */ 21 | { 0.886275, 0.301961, 0.556863, 1.000000 }, /* #e24d8e */ 22 | { 0.000000, 0.701961, 0.619608, 1.000000 }, /* #00b39e */ 23 | { 0.988235, 0.956863, 0.862745, 1.000000 }, /* #fcf4dc */ 24 | }, 25 | }, 26 | #endif /* THEME_SOLARIZED */ 27 | -------------------------------------------------------------------------------- /themes/jellybeans.h: -------------------------------------------------------------------------------- 1 | #ifndef THEME_JELLYBEANS_H 2 | #define THEME_JELLYBEANS_H 1 3 | { 4 | .name = "jellybeans", 5 | .fg = { 0.870588, 0.870588, 0.870588, 1.000000 }, /* #dedede */ 6 | .bg = { 0.070588, 0.070588, 0.070588, 1.000000 }, /* #121212 */ 7 | .colors = { 8 | { 0.572549, 0.572549, 0.572549, 1.000000 }, /* #929292 */ 9 | { 0.886275, 0.450980, 0.450980, 1.000000 }, /* #e27373 */ 10 | { 0.580392, 0.725490, 0.474510, 1.000000 }, /* #94b979 */ 11 | { 1.000000, 0.729412, 0.482353, 1.000000 }, /* #ffba7b */ 12 | { 0.592157, 0.745098, 0.862745, 1.000000 }, /* #97bedc */ 13 | { 0.882353, 0.752941, 0.980392, 1.000000 }, /* #e1c0fa */ 14 | { 0.000000, 0.596078, 0.556863, 1.000000 }, /* #00988e */ 15 | { 0.870588, 0.870588, 0.870588, 1.000000 }, /* #dedede */ 16 | { 0.741176, 0.741176, 0.741176, 1.000000 }, /* #bdbdbd */ 17 | { 1.000000, 0.631373, 0.631373, 1.000000 }, /* #ffa1a1 */ 18 | { 0.741176, 0.870588, 0.670588, 1.000000 }, /* #bddeab */ 19 | { 1.000000, 0.862745, 0.627451, 1.000000 }, /* #ffdca0 */ 20 | { 0.694118, 0.847059, 0.964706, 1.000000 }, /* #b1d8f6 */ 21 | { 0.984314, 0.854902, 1.000000, 1.000000 }, /* #fbdaff */ 22 | { 0.101961, 0.698039, 0.658824, 1.000000 }, /* #1ab2a8 */ 23 | { 1.000000, 1.000000, 1.000000, 1.000000 }, /* #ffffff */ 24 | }, 25 | }, 26 | #endif /* THEME_JELLYBEANS */ 27 | -------------------------------------------------------------------------------- /themes/linux.h: -------------------------------------------------------------------------------- 1 | /* 2 | * themes/linux.h 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef THEMES_LINUX_H 9 | #define THEMES_LINUX_H 10 | 11 | /* 12 | * Set of colors as used by GNOME-Terminal for the “Linux” color scheme: 13 | * http://git.gnome.org/browse/gnome-terminal/tree/src/terminal-profile.c 14 | */ 15 | { 16 | .name = "linux", 17 | .fg = { 0.8, 0.8, 0.8, 1 }, 18 | .bg = { 0, 0, 0, 1 }, 19 | .colors = { 20 | { 0, 0, 0, 1 }, 21 | { 0.666667, 0, 0, 1 }, 22 | { 0, 0.666667, 0, 1 }, 23 | { 0.666667, 0.333333, 0, 1 }, 24 | { 0, 0, 0.666667, 1 }, 25 | { 0.666667, 0, 0.666667, 1 }, 26 | { 0, 0.666667, 0.666667, 1 }, 27 | { 0.666667, 0.666667, 0.666667, 1 }, 28 | { 0.333333, 0.333333, 0.333333, 1 }, 29 | { 1, 0.333333, 0.333333, 1 }, 30 | { 0.333333, 1, 0.333333, 1 }, 31 | { 1, 1, 0.333333, 1 }, 32 | { 0.333333, 0.333333, 1, 1 }, 33 | { 1, 0.333333, 1, 1 }, 34 | { 0.333333, 1, 1, 1 }, 35 | { 1, 1, 1, 1 }, 36 | } 37 | }, 38 | 39 | 40 | #endif /* !THEMES_LINUX_H */ 41 | -------------------------------------------------------------------------------- /menus.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | New _Terminal 7 | app.new-terminal 8 | 9 |
10 |
11 | 12 | _About 13 | app.about 14 | 15 |
16 |
17 | 18 | _Quit 19 | app.quit 20 | 21 |
22 |
23 | 24 |
25 | 26 | _Copy 27 | win.copy 28 | 29 | 30 | _Paste 31 | win.paste 32 | 33 | 34 | Copy _URL 35 | win.copy-url 36 | 37 | 38 | _Open URL… 39 | win.open-url 40 | 41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | DWT - Dumb Window Terminal 3 | ========================== 4 | 5 | **DWT is no longer maintained** — you might want to use my 6 | `Termite fork`__ instead. 7 | 8 | .. __: https://github.com/aperezdc/termite 9 | 10 | DWT is a simple, sleek emulator based on the VTE_ widget. Its main features 11 | are being nice-looking (according to my personal taste), not being (very) 12 | configurable, and being a good companion for tiling window managers like 13 | DWM_ (or my own XDWM_ fork). That being said, it works flawlessly and looks 14 | slick in any GTK+-based environment. 15 | 16 | Screenshot 17 | ========== 18 | 19 | .. image:: /screenshots/popover.png 20 | 21 | (Non-)Features 22 | ============== 23 | 24 | * Mostly-fixed configuration: 25 | 26 | - Grey on black color scheme with using Linux console color set. 27 | 28 | - Converts terminal bell beeps in “urgent” X window flags. No visible 29 | or audible terminal bell, besides from that. 30 | 31 | - Non-blinking cursor. 32 | 33 | - Scrollback buffer, but no scrollbar displayed, to save screen real 34 | estate. Use ``Shift-PageUp`` and ``Shift-PageDown`` to scroll. 35 | 36 | - Keybindings to change font size: Use ``Super-+`` and ``Super--`` 37 | to change sizes, ``Super-0`` to reset the font. 38 | 39 | - Mouse cursor auto-hide. 40 | 41 | * XTerm-style configurable window title. 42 | 43 | * Clickable URLs. Because on the Internet era being able to quickly open 44 | a browser is a must-have feature. 45 | 46 | * Single process, multiple terminal windows: the first time ``dwt`` is 47 | invoked, it will start a new process; in subsequent times, it will 48 | just create new windows in the existing process. 49 | 50 | * Uses current GTK+ widgets and code conventions. Apart from the popover 51 | and header bar widgets, modern facilities like ``GAction``, property 52 | bindings, and ``GtkApplication`` are used. 53 | 54 | 55 | .. _VTE: http://developer.gnome.org/vte/ 56 | .. _DWM: http://dwm.suckless.org/ 57 | .. _XDWM: https://github.com/aperezdc/xdwm 58 | -------------------------------------------------------------------------------- /tools/convert-theme-xrdb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env lua 2 | -- 3 | -- convert-theme-xrdb 4 | -- Copyright (C) 2015 Adrian Perez 5 | -- 6 | -- Distributed under terms of the MIT license. 7 | -- 8 | if #arg ~= 1 then 9 | io.stderr:write("convert-theme-xrdb: No theme name.\n", 10 | "Usage: convert-theme-xrdb theme-name < input\n") 11 | os.exit(1) 12 | end 13 | 14 | 15 | local line_pattern = "^%#define%s+([%a%_][%w_]*)%s+%#(%x%x)(%x%x)(%x%x)$" 16 | local ansi_nn_color_pattern = "^Ansi_(%d+)_Color$" 17 | 18 | local function prototype(init) 19 | local proto = {} 20 | proto.__index = proto 21 | if type(init) == "function" then 22 | setmetatable(proto, { __call = function (self, ...) 23 | local obj = setmetatable({}, self) 24 | init(obj, ...) 25 | return obj 26 | end }) 27 | end 28 | return proto 29 | end 30 | 31 | local Color = prototype(function(self, t) 32 | self.r = t.r or 0.0 33 | self.g = t.g or 0.0 34 | self.b = t.b or 0.0 35 | self.a = t.a or 1.0 36 | end) 37 | 38 | local color_gdk_rgba_template = "{ %.6f, %.6f, %.6f, %.6f }" 39 | function Color:as_gdk_rgba_string() 40 | return color_gdk_rgba_template:format(self.r, self.g, self.b, self.a) 41 | end 42 | 43 | local color_hex_template = "#%02x%02x%02x" 44 | function Color:as_hex_string() 45 | return color_hex_template:format(self.r * 255, self.g * 255, self.b * 255, self.a * 255) 46 | end 47 | 48 | 49 | local color_name_map = { 50 | Background_Color = "bg"; 51 | Foreground_Color = "fg"; 52 | } 53 | local colors = {} 54 | 55 | for line in io.lines() do 56 | local name, r, g, b = line:match(line_pattern) 57 | if name and r and g and b then 58 | local c = Color { 59 | r = tonumber(r, 16) / 255.0; 60 | g = tonumber(g, 16) / 255.0; 61 | b = tonumber(b, 16) / 255.0; 62 | a = 1.0; -- Always opaque. 63 | } 64 | local key = name:match(ansi_nn_color_pattern) 65 | key = key and (tonumber(key) + 1) or color_name_map[name] 66 | if key then 67 | colors[key] = c 68 | end 69 | end 70 | end 71 | 72 | local header_guard = arg[1]:upper():gsub("[^%a_]", "_") 73 | local theme_name = arg[1]:lower():gsub("[^%a_]", "-") 74 | 75 | io.write("#ifndef THEME_", header_guard, "_H\n") 76 | io.write("#define THEME_", header_guard, "_H 1\n") 77 | io.write("{\n") 78 | io.write(" .name = \"", theme_name, "\",\n") 79 | io.write(" .fg = ", colors.fg:as_gdk_rgba_string(), ", /* ", colors.fg:as_hex_string(), " */\n") 80 | io.write(" .bg = ", colors.bg:as_gdk_rgba_string(), ", /* ", colors.bg:as_hex_string(), " */\n") 81 | io.write(" .colors = {\n") 82 | for _, c in ipairs(colors) do 83 | io.write(" ", c:as_gdk_rgba_string(), ", /* ", c:as_hex_string(), " */\n") 84 | end 85 | io.write(" },\n") 86 | io.write("},\n") 87 | io.write("#endif /* THEME_", header_guard, " */\n") 88 | -------------------------------------------------------------------------------- /tests/test-settings.c: -------------------------------------------------------------------------------- 1 | /* 2 | * test-settings.c 3 | * Copyright (C) 2014 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "../dg-settings.h" 9 | #include "../dg-util.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | DG_SETTINGS_CLASS_DECLARE (TestSettings, test_settings) 18 | DG_SETTINGS_CLASS_DEFINE (TestSettings, test_settings) 19 | DG_SETTINGS_BOOLEAN ("foo", "Foo", "Foo", FALSE); 20 | DG_SETTINGS_UINT ("baz", "Baz", "Baz", 12345); 21 | DG_SETTINGS_STRING ("bar", "Bar", "Bar", "BAR"); 22 | DG_SETTINGS_CLASS_END 23 | 24 | 25 | static void 26 | delete_settings_dir (gpointer userdata) 27 | { 28 | dg_lmem gchar *dir_path = userdata; 29 | GDir *dir = g_dir_open (dir_path, 0, NULL); 30 | const gchar *name; 31 | 32 | while ((name = g_dir_read_name (dir))) { 33 | dg_lmem gchar *file_path = g_build_filename (dir_path, name, NULL); 34 | g_remove (file_path); 35 | } 36 | g_dir_close (dir); 37 | } 38 | 39 | 40 | static const gchar* 41 | temporary_settings_dir (void) 42 | { 43 | gchar *template = g_build_filename (g_get_tmp_dir (), 44 | "test-settings-XXXXXX", 45 | NULL); 46 | g_test_queue_destroy (delete_settings_dir, template); 47 | return g_mkdtemp (template); 48 | } 49 | 50 | 51 | static void 52 | test_settings_read_defaults (void) 53 | { 54 | TestSettings *settings = 55 | test_settings_new (temporary_settings_dir (), FALSE); 56 | g_test_queue_unref (settings); 57 | 58 | gboolean bool_value; 59 | gchar* string_value = NULL; 60 | guint uint_value = 0; 61 | 62 | g_object_get (G_OBJECT (settings), 63 | "foo", &bool_value, 64 | "bar", &string_value, 65 | "baz", &uint_value, 66 | NULL); 67 | g_test_queue_free (string_value); 68 | 69 | g_assert_false (bool_value); 70 | g_assert_cmpstr (string_value, ==, "BAR"); 71 | g_assert_cmpuint (uint_value, ==, 12345); 72 | } 73 | 74 | 75 | static void 76 | populate_setting (const gchar *settings_path, 77 | const gchar *setting_name, 78 | const gchar *value_as_string) 79 | { 80 | dg_lmem gchar *path = g_build_filename (settings_path, setting_name, NULL); 81 | dg_lobj GFile *file = g_file_new_for_path (path); 82 | g_file_replace_contents (file, 83 | value_as_string, 84 | strlen (value_as_string), 85 | NULL, 86 | FALSE, 87 | G_FILE_CREATE_NONE, 88 | NULL, 89 | NULL, 90 | NULL); 91 | } 92 | 93 | 94 | static void 95 | test_settings_read (void) 96 | { 97 | const gchar* settings_path = temporary_settings_dir (); 98 | populate_setting (settings_path, "foo", "TRUE"); 99 | populate_setting (settings_path, "bar", "Kitteh sez: meow!"); 100 | populate_setting (settings_path, "baz", "42"); 101 | 102 | TestSettings *settings = test_settings_new (settings_path, FALSE); 103 | g_test_queue_unref (settings); 104 | 105 | gboolean bool_value; 106 | gchar* string_value = NULL; 107 | guint uint_value = 0; 108 | 109 | g_object_get (G_OBJECT (settings), 110 | "foo", &bool_value, 111 | "bar", &string_value, 112 | "baz", &uint_value, 113 | NULL); 114 | g_test_queue_free (string_value); 115 | 116 | g_assert_true (bool_value); 117 | g_assert_cmpstr (string_value, ==, "Kitteh sez: meow!"); 118 | g_assert_cmpuint (uint_value, ==, 42); 119 | } 120 | 121 | 122 | int 123 | main (int argc, char *argv[]) 124 | { 125 | g_test_init (&argc, &argv, NULL); 126 | g_test_add_func ("/settings/read-defaults", test_settings_read_defaults); 127 | g_test_add_func ("/settings/read", test_settings_read); 128 | return g_test_run (); 129 | } 130 | 131 | -------------------------------------------------------------------------------- /dg-settings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * dg-settings.h 3 | * Copyright (C) 2014 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef DG_SETTINGS_H 9 | #define DG_SETTINGS_H 10 | 11 | #include 12 | 13 | G_BEGIN_DECLS 14 | 15 | #define DG_SETTINGS_TYPE (dg_settings_get_type ()) 16 | #define DG_SETTINGS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DG_SETTINGS_TYPE, DgSettings)) 17 | #define DG_IS_SETTINGS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DG_SETTINGS_TYPE)) 18 | 19 | typedef struct _DgSettingsClass DgSettingsClass; 20 | typedef struct _DgSettings DgSettings; 21 | 22 | struct _DgSettingsClass { 23 | GObjectClass parent_class; 24 | }; 25 | 26 | struct _DgSettings { 27 | GObject parent; 28 | }; 29 | 30 | GType dg_settings_get_type (void); 31 | 32 | void dg_settings__get_property__ (GObject *object, 33 | guint prop_id, 34 | GValue *value, 35 | GParamSpec *pspec); 36 | void dg_settings__set_property__ (GObject *object, 37 | guint prop_id, 38 | const GValue *value, 39 | GParamSpec *pspec); 40 | void dg_settings__constructed__ (GObject *object); 41 | 42 | #define DG_SETTING__FLAG \ 43 | (1 << G_PARAM_USER_SHIFT) 44 | #define DG_SETTING_FLAGS \ 45 | (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | DG_SETTING__FLAG) 46 | 47 | 48 | #define DG_SETTINGS_BOOLEAN(_name, _nick, _desc, _default) \ 49 | g_object_class_install_property (G_OBJECT_CLASS (klass), \ 50 | ++klass->prop_id, \ 51 | g_param_spec_boolean ((_name), \ 52 | (_nick), \ 53 | (_desc), \ 54 | (_default), \ 55 | DG_SETTING_FLAGS)) 56 | 57 | #define DG_SETTINGS_STRING(_name, _nick, _desc, _default) \ 58 | g_object_class_install_property (G_OBJECT_CLASS (klass), \ 59 | ++klass->prop_id, \ 60 | g_param_spec_string ((_name), \ 61 | (_nick), \ 62 | (_desc), \ 63 | (_default), \ 64 | DG_SETTING_FLAGS)) 65 | 66 | #define DG_SETTINGS_UINT_RANGE(_name, _nick, _desc, _default, _min, _max) \ 67 | g_object_class_install_property (G_OBJECT_CLASS (klass), \ 68 | ++klass->prop_id, \ 69 | g_param_spec_uint ((_name), \ 70 | (_nick), \ 71 | (_desc), \ 72 | (_min), \ 73 | (_max), \ 74 | (_default), \ 75 | DG_SETTING_FLAGS)) 76 | 77 | #define DG_SETTINGS_UINT(_name, _nick, _desc, _default) \ 78 | DG_SETTINGS_UINT_RANGE ((_name), (_nick), (_desc), (_default), 0, G_MAXUINT) 79 | 80 | #define DG_SETTINGS_CLASS_DECLARE(_T, _t) \ 81 | typedef struct _ ## _T ## Class _T ## Class; \ 82 | typedef struct _ ## _T _T; \ 83 | GType _t ## _get_type (void); \ 84 | static inline _T* _t ## _new(const gchar *path, gboolean monitor) { \ 85 | return g_object_new (_t ## _get_type(), "settings-path", path, \ 86 | "settings-monitoring-enabled", monitor, NULL); } 87 | 88 | #define DG_SETTINGS_CLASS_DEFINE(_T, _t) \ 89 | struct _ ## _T ## Class { DgSettingsClass parent_class; guint prop_id; }; \ 90 | struct _ ## _T { DgSettings parent; }; \ 91 | static void _t ## _init (_T *object) { } \ 92 | G_DEFINE_TYPE (_T, _t, DG_SETTINGS_TYPE) \ 93 | static void _t ## _class_init (_T ## Class *klass) { \ 94 | G_OBJECT_CLASS (klass)->get_property = dg_settings__get_property__; \ 95 | G_OBJECT_CLASS (klass)->set_property = dg_settings__set_property__; \ 96 | G_OBJECT_CLASS (klass)->constructed = dg_settings__constructed__; 97 | #define DG_SETTINGS_CLASS_END } 98 | 99 | G_END_DECLS 100 | 101 | #endif /* !DG_SETTINGS_H */ 102 | -------------------------------------------------------------------------------- /dwt.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | dwt 3 | ===== 4 | 5 | ------------------------------ 6 | Simple terminal emulator for X 7 | ------------------------------ 8 | 9 | :Author: Adrian Perez 10 | :Manual section: 1 11 | 12 | 13 | SYNOPSIS 14 | ======== 15 | 16 | ``dwt -e`` 17 | 18 | 19 | DESCRIPTION 20 | =========== 21 | 22 | The ``dwt`` program is a terminal emulator for the the X Window System. It 23 | provides DEC VT102/VT220 emulation. 24 | 25 | 26 | USAGE 27 | ===== 28 | 29 | Command line options: 30 | 31 | -e COMMAND, --command=COMMAND 32 | Run the given *COMMAND* in the terminal window, instead of the 33 | user shell. If the command has multple arguments, make sure to 34 | quote it properly: the whole command line has to be passed as 35 | a single command line option. 36 | 37 | Setting: ``command`` (*string*). 38 | 39 | -w PATH, --workdir=PATH 40 | Change the active directory to the given *PATH* before running 41 | the shell (or any other command) inside the terminal window. 42 | 43 | -f FONT, --font=FONT 44 | Sets the font used by the terminal emulator. The font name is 45 | interpreted using FontConfig. The default is "terminus 11". 46 | 47 | Setting: ``font`` (*string*). 48 | 49 | -T THEME, --theme=THEME 50 | Choose among one of the built-in themes. If *THEME* is ``list``, 51 | a list of available themes will be printed. 52 | 53 | Setting: ``theme`` (*string*). 54 | 55 | -t TITLE, --title=TITLE 56 | Defines the initial *TITLE* of the terminal window (which, by 57 | default, is "dwt"). Even when a title is specified, 58 | applications may override it using `xterm(1)`-compatible 59 | escape sequences. 60 | 61 | Setting: ``title`` (*string*). 62 | 63 | -U, --no-title-updates 64 | Do not update window titles with those provided by applications 65 | using escape sequences. 66 | 67 | Setting: ``update-title`` (*boolean*). 68 | 69 | -s BYTES, --scrollback=BYTES 70 | Sets the size of the scrollback buffer. The default is 1024 71 | bytes (1 kB). 72 | 73 | Setting: ``scrollback`` (*integer*). 74 | 75 | -b, --bold Allow usage of bold font variants. 76 | 77 | Setting: ``allow-bold`` (*boolean*). 78 | 79 | -N, --no-header-bar 80 | Disable using a custom header bar widget, and let the window 81 | manager use its own decorations instead. This may be useful 82 | to make ``dwt`` blend better with your desktop environment. 83 | 84 | Setting: ``no-header-bar`` (*boolean*). 85 | 86 | -H, --title-on-maximize 87 | Keep a title bar in maximized windows. The default is to 88 | disable the title bar. 89 | 90 | Setting: ``show-title`` (*boolean*). 91 | 92 | -h, --help Show a summary of available options. 93 | 94 | 95 | CONFIGURATION 96 | ============= 97 | 98 | ``dwt`` is configured by writing each setting to a configuration file under 99 | ``$XDG_CONFIG_HOME/dwt/`` (typically ``~/.config/dwt/``). In general, only the 100 | first line of each configuration file is read, and each file is used for one 101 | (and only one) setting. This allows to use shell commands to write the 102 | configuration files for ``dwt``. For example:: 103 | 104 | echo true > ~/.config/dwt/allow-bold 105 | echo 'Fira Mono 13' > ~/.config/dwt/font 106 | 107 | The following settings are not available as command line options, and are only 108 | settable using configuration files: 109 | 110 | * ``icon`` (*string*): Name of the icon used for application windows. 111 | * ``cursor-color`` (*string*): Cursor color for the terminal widget. 112 | * ``foreground-color`` (*string*): Foreground color of the terminal widget. 113 | If not set, the foreground color from the selected theme is used. 114 | * ``background-color`` (*string*): Background color of the terminal widget. 115 | If not set, the background color from the selected theme is used. 116 | * ``mouse-autohide`` (*boolean*): Automatically hide the mouse pointer on 117 | keypress when it is over a terminal. The mouse pointer will be shown again 118 | when the mouse is moved. 119 | * ``audible-bell`` (*boolean*): Whether the terminal bell is audible. 120 | 121 | 122 | EXAMPLES 123 | ======== 124 | 125 | Run Vim in a new terminal window, to edit a file with spaces in its file 126 | name:: 127 | 128 | dwt --command='vim "~/foo bar.txt" 129 | 130 | Launch a new terminal window, specifying an alternative title for it:: 131 | 132 | dwt --title='Dev Ops' 133 | 134 | 135 | ENVIRONMENT 136 | =========== 137 | 138 | If the ``SHELL`` environment variable is defined, it is assumed that it 139 | contains the path to the shell that will be run as default command in the 140 | terminal window. If not defined, `getpwuid(3)` will be used to determine the 141 | user shell. 142 | 143 | If present, the value of the ``DWT_APPLICATION_ID`` will be used as the 144 | unique identifier for the application. If a ``dwt`` process with the given 145 | identifier is already running, it will be instructed to create a new 146 | terminal window. This means that all the terminal windows created by 147 | launching ``dwt`` with the same identifier live in the same process. To 148 | disable this behaviour, use ``none`` as identifier. 149 | 150 | 151 | SEE ALSO 152 | ======== 153 | 154 | `xterm(1)` 155 | 156 | -------------------------------------------------------------------------------- /dwt-settings.c: -------------------------------------------------------------------------------- 1 | /* 2 | * dwt-settings.c 3 | * Copyright (C) 2014 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "dg-settings.h" 9 | 10 | #ifndef DWT_DEFAULT_FONT 11 | #define DWT_DEFAULT_FONT "monospace 11" 12 | #endif /* !DWT_DEFAULT_FONT */ 13 | 14 | 15 | DG_SETTINGS_CLASS_DECLARE (DwtSettings, dwt_settings) 16 | DG_SETTINGS_CLASS_DEFINE (DwtSettings, dwt_settings) 17 | 18 | DG_SETTINGS_BOOLEAN ("allow-bold", 19 | "Allow bold fonts", 20 | "Whether to allow usage of bold fonts." 21 | " Note that some type faces may not include" 22 | " a bold variant, and a substitute font which" 23 | " does not match well with the selected font" 24 | " might be chosen, or the base font may be" 25 | " emboldened to create a “fake bold” variant.", 26 | TRUE); 27 | 28 | DG_SETTINGS_BOOLEAN ("mouse-autohide", 29 | "Autohide mouse pointer", 30 | "Whether to automatically hide the mouse cursor" 31 | " on key press when the cursor is over a terminal" 32 | " window. To show the cursor again, move the mouse.", 33 | TRUE); 34 | 35 | DG_SETTINGS_BOOLEAN ("audible-bell", 36 | "Audible terminal bell", 37 | "Whether the terminal bell is audible (i.e. produces" 38 | " sound.", 39 | FALSE); 40 | 41 | DG_SETTINGS_BOOLEAN ("show-title", 42 | "Show window titles", 43 | "Show title bars for maximized terminal windows.", 44 | FALSE); 45 | 46 | DG_SETTINGS_BOOLEAN ("update-title", 47 | "Update window titles", 48 | "Allow updating the titles of windows using the" 49 | " same escape sequence used by XTerm.", 50 | TRUE); 51 | 52 | DG_SETTINGS_BOOLEAN ("no-header-bar", 53 | "Do not use header bars", 54 | "Let the window manager decorate windows instead." 55 | " of using client-side header bars.", 56 | FALSE); 57 | 58 | DG_SETTINGS_UINT_RANGE ("scrollback", 59 | "Scrollback size", 60 | "Number of lines saved as scrollback buffer.", 61 | 0, 0, 10000); 62 | 63 | DG_SETTINGS_STRING ("font", 64 | "Font name", 65 | "Name of the terminal font.", 66 | DWT_DEFAULT_FONT); 67 | 68 | DG_SETTINGS_STRING ("command", 69 | "Command to run", 70 | "Command to run instead of the user shell, if" 71 | " flag '-e' is not specified during invocation.", 72 | NULL); 73 | 74 | DG_SETTINGS_STRING ("title", 75 | "Default window title", 76 | "Initial title to show on terminal windows, if not" 77 | " changed using the XTerm title change escape" 78 | " sequence.", 79 | "dwt"); 80 | 81 | DG_SETTINGS_STRING ("icon", 82 | "Terminal window icon", 83 | "Name of the icon used for terminal windows, or" 84 | " path to the file containing the image to be used" 85 | " as icon.", 86 | "terminal"); 87 | 88 | DG_SETTINGS_STRING ("cursor-color", 89 | "Cursor color", 90 | "Color of the cursor in active terminal windows," 91 | " in #RRGGBB hex format.", 92 | "#AA0033"); 93 | 94 | DG_SETTINGS_STRING ("background-color", 95 | "Background color", 96 | "Terminal background color. Overrides the color" 97 | " defined in the active theme.", 98 | NULL); 99 | 100 | DG_SETTINGS_STRING ("foreground-color", 101 | "Foreground color", 102 | "Terminal foreground color. Overrides the color" 103 | " defined in the active theme.", 104 | NULL); 105 | 106 | DG_SETTINGS_STRING ("theme", 107 | "Color theme", 108 | "Name of the built-in color theme to be used for the" 109 | " foreground, background, and the 16 basic terminal" 110 | " colors.", 111 | NULL); 112 | 113 | DG_SETTINGS_CLASS_END 114 | 115 | 116 | static gpointer 117 | dwt_settings_create (gpointer dummy) 118 | { 119 | g_autofree char *path = g_build_filename (g_get_user_config_dir (), 120 | g_get_prgname (), 121 | NULL); 122 | return g_object_new (dwt_settings_get_type (), 123 | "settings-path", path, 124 | "settings-monitoring-enabled", TRUE, 125 | NULL); 126 | } 127 | 128 | 129 | DwtSettings* 130 | dwt_settings_get_instance (void) 131 | { 132 | static GOnce instance_once = G_ONCE_INIT; 133 | return g_once (&instance_once, dwt_settings_create, NULL); 134 | } 135 | -------------------------------------------------------------------------------- /dwt.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | .TH DWT 1 "" "" "" 4 | .SH NAME 5 | dwt \- Simple terminal emulator for X 6 | . 7 | .nr rst2man-indent-level 0 8 | . 9 | .de1 rstReportMargin 10 | \\$1 \\n[an-margin] 11 | level \\n[rst2man-indent-level] 12 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 13 | - 14 | \\n[rst2man-indent0] 15 | \\n[rst2man-indent1] 16 | \\n[rst2man-indent2] 17 | .. 18 | .de1 INDENT 19 | .\" .rstReportMargin pre: 20 | . RS \\$1 21 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 22 | . nr rst2man-indent-level +1 23 | .\" .rstReportMargin post: 24 | .. 25 | .de UNINDENT 26 | . RE 27 | .\" indent \\n[an-margin] 28 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 29 | .nr rst2man-indent-level -1 30 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 31 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 32 | .. 33 | .SH SYNOPSIS 34 | .sp 35 | \fBdwt \-e\fP 36 | .SH DESCRIPTION 37 | .sp 38 | The \fBdwt\fP program is a terminal emulator for the the X Window System. It 39 | provides DEC VT102/VT220 emulation. 40 | .SH USAGE 41 | .sp 42 | Command line options: 43 | .INDENT 0.0 44 | .TP 45 | .BI \-e \ COMMAND\fP,\fB \ \-\-command\fB= COMMAND 46 | Run the given \fICOMMAND\fP in the terminal window, instead of the 47 | user shell. If the command has multple arguments, make sure to 48 | quote it properly: the whole command line has to be passed as 49 | a single command line option. 50 | .sp 51 | Setting: \fBcommand\fP (\fIstring\fP). 52 | .TP 53 | .BI \-w \ PATH\fP,\fB \ \-\-workdir\fB= PATH 54 | Change the active directory to the given \fIPATH\fP before running 55 | the shell (or any other command) inside the terminal window. 56 | .TP 57 | .BI \-f \ FONT\fP,\fB \ \-\-font\fB= FONT 58 | Sets the font used by the terminal emulator. The font name is 59 | interpreted using FontConfig. The default is "terminus 11". 60 | .sp 61 | Setting: \fBfont\fP (\fIstring\fP). 62 | .TP 63 | .BI \-T \ THEME\fP,\fB \ \-\-theme\fB= THEME 64 | Choose among one of the built\-in themes. If \fITHEME\fP is \fBlist\fP, 65 | a list of available themes will be printed. 66 | .sp 67 | Setting: \fBtheme\fP (\fIstring\fP). 68 | .TP 69 | .BI \-t \ TITLE\fP,\fB \ \-\-title\fB= TITLE 70 | Defines the initial \fITITLE\fP of the terminal window (which, by 71 | default, is "dwt"). Even when a title is specified, 72 | applications may override it using \fIxterm(1)\fP\-compatible 73 | escape sequences. 74 | .sp 75 | Setting: \fBtitle\fP (\fIstring\fP). 76 | .TP 77 | .B \-U\fP,\fB \-\-no\-title\-updates 78 | Do not update window titles with those provided by applications 79 | using escape sequences. 80 | .sp 81 | Setting: \fBupdate\-title\fP (\fIboolean\fP). 82 | .TP 83 | .BI \-s \ BYTES\fP,\fB \ \-\-scrollback\fB= BYTES 84 | Sets the size of the scrollback buffer. The default is 1024 85 | bytes (1 kB). 86 | .sp 87 | Setting: \fBscrollback\fP (\fIinteger\fP). 88 | .TP 89 | .B \-b\fP,\fB \-\-bold 90 | Allow usage of bold font variants. 91 | .sp 92 | Setting: \fBallow\-bold\fP (\fIboolean\fP). 93 | .TP 94 | .B \-N\fP,\fB \-\-no\-header\-bar 95 | Disable using a custom header bar widget, and let the window 96 | manager use its own decorations instead. This may be useful 97 | to make \fBdwt\fP blend better with your desktop environment. 98 | .sp 99 | Setting: \fBno\-header\-bar\fP (\fIboolean\fP). 100 | .TP 101 | .B \-H\fP,\fB \-\-title\-on\-maximize 102 | Keep a title bar in maximized windows. The default is to 103 | disable the title bar. 104 | .sp 105 | Setting: \fBshow\-title\fP (\fIboolean\fP). 106 | .TP 107 | .B \-h\fP,\fB \-\-help 108 | Show a summary of available options. 109 | .UNINDENT 110 | .SH CONFIGURATION 111 | .sp 112 | \fBdwt\fP is configured by writing each setting to a configuration file under 113 | \fB$XDG_CONFIG_HOME/dwt/\fP (typically \fB~/.config/dwt/\fP). In general, only the 114 | first line of each configuration file is read, and each file is used for one 115 | (and only one) setting. This allows to use shell commands to write the 116 | configuration files for \fBdwt\fP\&. For example: 117 | .INDENT 0.0 118 | .INDENT 3.5 119 | .sp 120 | .nf 121 | .ft C 122 | echo true > ~/.config/dwt/allow\-bold 123 | echo \(aqFira Mono 13\(aq > ~/.config/dwt/font 124 | .ft P 125 | .fi 126 | .UNINDENT 127 | .UNINDENT 128 | .sp 129 | The following settings are not available as command line options, and are only 130 | settable using configuration files: 131 | .INDENT 0.0 132 | .IP \(bu 2 133 | \fBicon\fP (\fIstring\fP): Name of the icon used for application windows. 134 | .IP \(bu 2 135 | \fBcursor\-color\fP (\fIstring\fP): Cursor color for the terminal widget. 136 | .IP \(bu 2 137 | \fBforeground\-color\fP (\fIstring\fP): Foreground color of the terminal widget. 138 | If not set, the foreground color from the selected theme is used. 139 | .IP \(bu 2 140 | \fBbackground\-color\fP (\fIstring\fP): Background color of the terminal widget. 141 | If not set, the background color from the selected theme is used. 142 | .IP \(bu 2 143 | \fBmouse\-autohide\fP (\fIboolean\fP): Automatically hide the mouse pointer on 144 | keypress when it is over a terminal. The mouse pointer will be shown again 145 | when the mouse is moved. 146 | .IP \(bu 2 147 | \fBaudible\-bell\fP (\fIboolean\fP): Whether the terminal bell is audible. 148 | .UNINDENT 149 | .SH EXAMPLES 150 | .sp 151 | Run Vim in a new terminal window, to edit a file with spaces in its file 152 | name: 153 | .INDENT 0.0 154 | .INDENT 3.5 155 | .sp 156 | .nf 157 | .ft C 158 | dwt \-\-command=\(aqvim "~/foo bar.txt" 159 | .ft P 160 | .fi 161 | .UNINDENT 162 | .UNINDENT 163 | .sp 164 | Launch a new terminal window, specifying an alternative title for it: 165 | .INDENT 0.0 166 | .INDENT 3.5 167 | .sp 168 | .nf 169 | .ft C 170 | dwt \-\-title=\(aqDev Ops\(aq 171 | .ft P 172 | .fi 173 | .UNINDENT 174 | .UNINDENT 175 | .SH ENVIRONMENT 176 | .sp 177 | If the \fBSHELL\fP environment variable is defined, it is assumed that it 178 | contains the path to the shell that will be run as default command in the 179 | terminal window. If not defined, \fIgetpwuid(3)\fP will be used to determine the 180 | user shell. 181 | .sp 182 | If present, the value of the \fBDWT_APPLICATION_ID\fP will be used as the 183 | unique identifier for the application. If a \fBdwt\fP process with the given 184 | identifier is already running, it will be instructed to create a new 185 | terminal window. This means that all the terminal windows created by 186 | launching \fBdwt\fP with the same identifier live in the same process. To 187 | disable this behaviour, use \fBnone\fP as identifier. 188 | .SH SEE ALSO 189 | .sp 190 | \fIxterm(1)\fP 191 | .SH AUTHOR 192 | Adrian Perez 193 | .\" Generated by docutils manpage writer. 194 | . 195 | -------------------------------------------------------------------------------- /dg-settings.c: -------------------------------------------------------------------------------- 1 | /* 2 | * dg-settings.c 3 | * Copyright (C) 2014 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "dg-settings.h" 9 | #include 10 | 11 | 12 | enum { 13 | PROP_0, 14 | PROP_SETTINGS_PATH, 15 | PROP_SETTINGS_MONITORING_ENABLED, 16 | PROP_N 17 | }; 18 | 19 | typedef struct _DgSettingsPrivate DgSettingsPrivate; 20 | 21 | struct _DgSettingsPrivate { 22 | GFile *settings_path; 23 | gboolean monitor_enabled; 24 | GFileMonitor *monitor; 25 | GValue **values; 26 | }; 27 | 28 | G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (DgSettings, dg_settings, G_TYPE_OBJECT) 29 | 30 | 31 | static GParamSpec *properties[PROP_N] = { NULL, }; 32 | 33 | 34 | static gchar* 35 | read_line (GFile *file) 36 | { 37 | g_assert (file != NULL); 38 | 39 | g_autoptr(GError) error = NULL; 40 | g_autoptr(GInputStream) stream = G_INPUT_STREAM (g_file_read (file, NULL, &error)); 41 | 42 | if (!stream && error) 43 | return NULL; 44 | 45 | g_autoptr(GDataInputStream) datain = g_data_input_stream_new (stream); 46 | gsize read_length = 0; 47 | 48 | return g_data_input_stream_read_line_utf8 (datain, 49 | &read_length, 50 | NULL, 51 | &error); 52 | } 53 | 54 | 55 | static void 56 | read_boolean (GFile *file, GValue *value) 57 | { 58 | g_autofree char *line = read_line (file); 59 | if (line == NULL) 60 | return; 61 | 62 | g_value_set_boolean (value, g_ascii_strcasecmp ("false", line) != 0); 63 | } 64 | 65 | 66 | static void 67 | read_uint (GFile *file, GValue *value) 68 | { 69 | g_autofree char *line = read_line (file); 70 | if (line == NULL) 71 | return; 72 | 73 | guint64 uint_value = g_ascii_strtoull (line, NULL, 0); 74 | if (uint_value <= G_MAXUINT) 75 | g_value_set_uint (value, uint_value); 76 | } 77 | 78 | 79 | void 80 | dg_settings__get_property__ (GObject *object, 81 | guint prop_id, 82 | GValue *value, 83 | GParamSpec *pspec) 84 | { 85 | DgSettingsPrivate *priv = dg_settings_get_instance_private (DG_SETTINGS (object)); 86 | 87 | g_autoptr(GFile) setting_file = g_file_get_child (priv->settings_path, 88 | g_param_spec_get_name (pspec)); 89 | 90 | /* Use the default value when the file does not exist */ 91 | g_value_copy (g_param_spec_get_default_value (pspec), value); 92 | if (!g_file_query_exists (setting_file, NULL)) 93 | return; 94 | 95 | /* Try to read the value from the file, converting as appropriate. */ 96 | switch (G_PARAM_SPEC_VALUE_TYPE (pspec)) { 97 | case G_TYPE_BOOLEAN: 98 | read_boolean (setting_file, value); 99 | break; 100 | case G_TYPE_UINT: 101 | read_uint (setting_file, value); 102 | break; 103 | case G_TYPE_STRING: 104 | g_value_take_string (value, read_line (setting_file)); 105 | break; 106 | } 107 | } 108 | 109 | 110 | static void 111 | write_line (GFile *file, 112 | GParamSpec *pspec, 113 | const gchar *line) 114 | { 115 | g_assert (file != NULL); 116 | g_assert (pspec != NULL); 117 | g_assert (line != NULL); 118 | 119 | /* TODO: Handle errors */ 120 | g_autoptr(GOutputStream) stream = 121 | G_OUTPUT_STREAM (g_file_open_readwrite (file, NULL, NULL)); 122 | if (stream == NULL) 123 | return; 124 | 125 | gsize bytes_written; 126 | g_output_stream_printf (stream, 127 | &bytes_written, 128 | NULL, 129 | NULL, 130 | "%s\n\n%s\n", 131 | line, 132 | g_param_spec_get_blurb (pspec)); 133 | 134 | g_output_stream_close (stream, 135 | NULL, 136 | NULL); 137 | } 138 | 139 | 140 | void 141 | dg_settings__set_property__ (GObject *object, 142 | guint prop_id, 143 | const GValue *value, 144 | GParamSpec *pspec) 145 | { 146 | DgSettingsPrivate *priv = dg_settings_get_instance_private (DG_SETTINGS (object)); 147 | 148 | g_autoptr(GFile) setting_file = g_file_get_child (priv->settings_path, 149 | g_param_spec_get_name (pspec)); 150 | 151 | switch (G_PARAM_SPEC_VALUE_TYPE (pspec)) { 152 | case G_TYPE_BOOLEAN: 153 | write_line (setting_file, pspec, 154 | g_value_get_boolean (value) ? "true" : "false"); 155 | break; 156 | } 157 | } 158 | 159 | 160 | static void 161 | dg_settings_get_property (GObject *object, 162 | guint prop_id, 163 | GValue *value, 164 | GParamSpec *pspec) 165 | { 166 | DgSettingsPrivate *priv = dg_settings_get_instance_private (DG_SETTINGS (object)); 167 | 168 | switch (prop_id) { 169 | case PROP_SETTINGS_PATH: { 170 | g_value_take_string (value, g_file_get_path (priv->settings_path)); 171 | break; 172 | } 173 | case PROP_SETTINGS_MONITORING_ENABLED: 174 | g_value_set_boolean (value, priv->monitor_enabled); 175 | break; 176 | default: 177 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 178 | } 179 | } 180 | 181 | 182 | static void 183 | dg_settings_set_property (GObject *object, 184 | guint prop_id, 185 | const GValue *value, 186 | GParamSpec *pspec) 187 | { 188 | DgSettingsPrivate *priv = dg_settings_get_instance_private (DG_SETTINGS (object)); 189 | 190 | switch (prop_id) { 191 | case PROP_SETTINGS_PATH: 192 | g_assert (!priv->settings_path); 193 | priv->settings_path = g_file_new_for_path (g_value_get_string (value)); 194 | break; 195 | case PROP_SETTINGS_MONITORING_ENABLED: 196 | priv->monitor_enabled = g_value_get_boolean (value); 197 | break; 198 | default: 199 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 200 | } 201 | } 202 | 203 | 204 | static void 205 | dg_settings_finalize (GObject *object) 206 | { 207 | DgSettingsPrivate *priv = dg_settings_get_instance_private (DG_SETTINGS (object)); 208 | if (priv->monitor_enabled) 209 | g_object_unref (priv->monitor); 210 | g_object_unref (priv->settings_path); 211 | G_OBJECT_CLASS (dg_settings_parent_class)->finalize (object); 212 | } 213 | 214 | 215 | static void 216 | dg_settings_init (DgSettings *settings) 217 | { 218 | } 219 | 220 | 221 | static void 222 | dg_settings_monitor_changed (GFileMonitor *monitor, 223 | GFile *file, 224 | GFile *other_file, 225 | GFileMonitorEvent event, 226 | DgSettings *settings) 227 | { 228 | switch (event) { 229 | case G_FILE_MONITOR_EVENT_CHANGED: 230 | case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: 231 | case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: 232 | case G_FILE_MONITOR_EVENT_UNMOUNTED: 233 | case G_FILE_MONITOR_EVENT_MOVED: 234 | case G_FILE_MONITOR_EVENT_RENAMED: 235 | case G_FILE_MONITOR_EVENT_MOVED_IN: 236 | case G_FILE_MONITOR_EVENT_MOVED_OUT: 237 | case G_FILE_MONITOR_EVENT_CREATED: 238 | return; 239 | 240 | case G_FILE_MONITOR_EVENT_DELETED: 241 | case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: { 242 | g_autofree char *filename = g_file_get_basename (file); 243 | GParamSpec *pspec = 244 | g_object_class_find_property (G_OBJECT_GET_CLASS (settings), 245 | filename); 246 | if (pspec) g_object_notify_by_pspec (G_OBJECT (settings), pspec); 247 | } 248 | } 249 | } 250 | 251 | 252 | void 253 | dg_settings__constructed__ (GObject *object) 254 | { 255 | DgSettingsPrivate *priv = dg_settings_get_instance_private (DG_SETTINGS (object)); 256 | 257 | g_assert (priv->settings_path); 258 | if (priv->monitor_enabled) { 259 | g_autoptr(GError) error = NULL; 260 | priv->monitor = g_file_monitor_directory (priv->settings_path, 261 | G_FILE_MONITOR_NONE, 262 | NULL, 263 | &error); 264 | if (priv->monitor) { 265 | g_file_monitor_set_rate_limit (priv->monitor, 500); 266 | g_signal_connect (priv->monitor, 267 | "changed", 268 | G_CALLBACK (dg_settings_monitor_changed), 269 | object); 270 | } else { 271 | g_autofree char *path = g_file_get_path (priv->settings_path); 272 | g_warning ("DgSettings: cannot initialize monitoring for directory '%s'\n", path); 273 | if (error) g_warning ("DgSettings: %s\n", error->message); 274 | 275 | g_warning ("DgSettings: trying to continue anyway with monitoring disabled\n"); 276 | priv->monitor_enabled = FALSE; 277 | } 278 | } 279 | } 280 | 281 | 282 | static void 283 | dg_settings_class_init (DgSettingsClass *klass) 284 | { 285 | GObjectClass *g_object_class = G_OBJECT_CLASS (klass); 286 | g_object_class->get_property = dg_settings_get_property; 287 | g_object_class->set_property = dg_settings_set_property; 288 | g_object_class->finalize = dg_settings_finalize; 289 | 290 | properties[PROP_SETTINGS_PATH] = 291 | g_param_spec_string ("settings-path", 292 | "Settings path", 293 | "Directory where the configuration files reside", 294 | NULL, 295 | G_PARAM_READWRITE | 296 | G_PARAM_CONSTRUCT_ONLY | 297 | G_PARAM_STATIC_STRINGS); 298 | 299 | properties[PROP_SETTINGS_MONITORING_ENABLED] = 300 | g_param_spec_boolean ("settings-monitoring-enabled", 301 | "Settings monitoring enabled", 302 | "Whether changes to files backing up settings will be monitored", 303 | FALSE, 304 | G_PARAM_READWRITE | 305 | G_PARAM_CONSTRUCT_ONLY | 306 | G_PARAM_STATIC_STRINGS); 307 | 308 | g_object_class_install_properties (g_object_class, PROP_N, properties); 309 | } 310 | 311 | 312 | 313 | -------------------------------------------------------------------------------- /dwt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * dwt.c 3 | * Copyright (C) 2012-2014 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #define PCRE2_CODE_UNIT_WIDTH 8 9 | 10 | #define DWT_GRESOURCE(name) ("/org/perezdecastro/dwt/" name) 11 | 12 | #include "dwt-settings.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #ifdef GDK_WINDOWING_X11 23 | # include 24 | #endif 25 | 26 | #define CHECK_FLAGS(_v, _f) (((_v) & (_f)) == (_f)) 27 | #define NDIGITS10(_t) (sizeof (_t) * __CHAR_BIT__ * 3 / 10) 28 | 29 | /* Last matched text piece. */ 30 | static gchar *last_match_text = NULL; 31 | 32 | /* Default font size */ 33 | static gint default_font_size = 0; 34 | 35 | 36 | /* Forward declarations. */ 37 | static GtkWidget* 38 | create_new_window (GtkApplication *application, 39 | GVariantDict *options); 40 | 41 | 42 | static const GOptionEntry option_entries[] = 43 | { 44 | { 45 | "command", 'e', 46 | G_OPTION_FLAG_IN_MAIN, 47 | G_OPTION_ARG_STRING, 48 | NULL, 49 | "Execute the argument to this option inside the terminal", 50 | "COMMAND", 51 | }, { 52 | "workdir", 'w', 53 | G_OPTION_FLAG_IN_MAIN, 54 | G_OPTION_ARG_STRING, 55 | NULL, 56 | "Set working directory before running the command/shell", 57 | "PATH", 58 | }, { 59 | "font", 'f', 60 | G_OPTION_FLAG_IN_MAIN, 61 | G_OPTION_ARG_STRING, 62 | NULL, 63 | "Font used by the terminal, in FontConfig syntax", 64 | "FONT", 65 | }, { 66 | "theme", 'T', 67 | G_OPTION_FLAG_IN_MAIN, 68 | G_OPTION_ARG_STRING, 69 | NULL, 70 | "Choose a built-in color theme", 71 | "NAME", 72 | }, { 73 | "title", 't', 74 | G_OPTION_FLAG_IN_MAIN, 75 | G_OPTION_ARG_STRING, 76 | NULL, 77 | "Initial terminal window title", 78 | "TITLE", 79 | }, { 80 | "no-title-updates", 'U', 81 | G_OPTION_FLAG_IN_MAIN, 82 | G_OPTION_ARG_NONE, 83 | NULL, 84 | "Do not update window title automatically", 85 | NULL, 86 | }, { 87 | "scrollback", 's', 88 | G_OPTION_FLAG_IN_MAIN, 89 | G_OPTION_ARG_INT, 90 | NULL, 91 | "Scrollback buffer size, in bytes (default 1024)", 92 | "BYTES" 93 | }, { 94 | "bold", 'b', 95 | G_OPTION_FLAG_IN_MAIN, 96 | G_OPTION_ARG_NONE, 97 | NULL, 98 | "Allow using bold fonts", 99 | NULL, 100 | }, { 101 | "title-on-maximize", 'H', 102 | G_OPTION_FLAG_IN_MAIN, 103 | G_OPTION_ARG_NONE, 104 | NULL, 105 | "Always show title bar when window is maximized.", 106 | NULL, 107 | }, { 108 | "no-header-bar", 'N', 109 | G_OPTION_FLAG_IN_MAIN, 110 | G_OPTION_ARG_NONE, 111 | NULL, 112 | "Disable header bars in terminal windows (use window manager decorations)", 113 | NULL, 114 | }, 115 | { NULL } 116 | }; 117 | 118 | 119 | typedef struct { 120 | const gchar *name; 121 | GdkRGBA fg, bg; 122 | GdkRGBA colors[16]; 123 | } Theme; 124 | 125 | 126 | /* The first element is the "default" theme. */ 127 | static const Theme themes[] = { 128 | #include "themes/linux.h" 129 | #include "themes/jellybeans.h" 130 | #include "themes/monokai.h" 131 | #include "themes/molokai.h" 132 | #include "themes/solarized.h" 133 | #include "themes/thayer.h" 134 | #include "themes/wombat.h" 135 | #include "themes/zenburn.h" 136 | }; 137 | 138 | 139 | static GdkRGBA cursor_active = { 0, 0.75, 0, 1 }; 140 | static GdkRGBA cursor_inactive = { 0.6, 0.6, 0.6, 1 }; 141 | 142 | 143 | /* Regexp used to match URIs and allow clicking them */ 144 | static const gchar uri_regexp[] = "(ftp|http)s?://[-@a-zA-Z0-9.?$%&/=_~#.,:;+]*"; 145 | static const gchar image_regex_string[] = "/[^/]+\\.(png|jpg|jpeg|gif|webp)$"; 146 | 147 | static GRegex *image_regex = NULL; 148 | 149 | 150 | static const Theme* const 151 | find_theme (const gchar *name) 152 | { 153 | for (size_t i = 0; i < G_N_ELEMENTS (themes); i++) { 154 | if (g_str_equal (name, themes[i].name)) { 155 | return &themes[i]; 156 | } 157 | } 158 | return NULL; 159 | } 160 | 161 | 162 | #define SWAP(t, a, b) \ 163 | do { \ 164 | t tmp_ ## __LINE__ = (a); \ 165 | (a) = (b); \ 166 | (b) = tmp_ ## __LINE__; \ 167 | } while (0) 168 | 169 | static void 170 | configure_term_widget (VteTerminal *vtterm, 171 | GVariantDict *options) 172 | { 173 | /* Pick default settings from the settings... */ 174 | g_autofree char *opt_font = NULL; 175 | g_autofree char *opt_theme = NULL; 176 | g_autofree char *opt_fgcolor = NULL; 177 | g_autofree char *opt_bgcolor = NULL; 178 | gboolean opt_bold; 179 | guint opt_scroll; 180 | DwtSettings *settings = dwt_settings_get_instance (); 181 | 182 | g_object_get (settings, 183 | "font", &opt_font, 184 | "theme", &opt_theme, 185 | "allow-bold", &opt_bold, 186 | "scrollback", &opt_scroll, 187 | "foreground-color", &opt_fgcolor, 188 | "background-color", &opt_bgcolor, 189 | NULL); 190 | 191 | /* 192 | * This ensures that properties are updated for the terminal whenever they 193 | * change in the configuration files. 194 | * 195 | * TODO: For now this is done only for those properties which cannot be 196 | * overriden using command line flags. 197 | */ 198 | static const struct { 199 | const gchar *setting_name; 200 | const gchar *property_name; 201 | GBindingFlags bind_flags; 202 | } property_bind_map[] = { 203 | { "mouse-autohide", "pointer-autohide", G_BINDING_SYNC_CREATE }, 204 | { "audible-bell", "audible-bell", G_BINDING_SYNC_CREATE }, 205 | }; 206 | for (guint i = 0; i < G_N_ELEMENTS (property_bind_map); i++) { 207 | g_object_bind_property (settings, 208 | property_bind_map[i].setting_name, 209 | G_OBJECT (vtterm), 210 | property_bind_map[i].property_name, 211 | property_bind_map[i].bind_flags); 212 | } 213 | 214 | /* ...and allow command line options to override them. */ 215 | if (options) { 216 | g_autofree char *cmd_font = NULL; 217 | g_variant_dict_lookup (options, "font", "s", &cmd_font); 218 | if (cmd_font) SWAP (gchar*, cmd_font, opt_font); 219 | 220 | g_autofree char *cmd_theme = NULL; 221 | g_variant_dict_lookup (options, "theme", "s", &cmd_theme); 222 | if (cmd_theme) SWAP (gchar*, cmd_theme, opt_theme); 223 | 224 | g_variant_dict_lookup (options, "allow-bold", "b", &opt_bold); 225 | g_variant_dict_lookup (options, "scrollback", "u", &opt_scroll); 226 | } 227 | 228 | PangoFontDescription *fontd = pango_font_description_from_string (opt_font); 229 | if (fontd) { 230 | if (!pango_font_description_get_family (fontd)) 231 | pango_font_description_set_family_static (fontd, "monospace"); 232 | if (!pango_font_description_get_size (fontd)) 233 | pango_font_description_set_size (fontd, 12 * PANGO_SCALE); 234 | vte_terminal_set_font (vtterm, fontd); 235 | pango_font_description_free (fontd); 236 | fontd = NULL; 237 | } 238 | 239 | const Theme *theme = &themes[1]; 240 | if (opt_theme) { 241 | theme = find_theme (opt_theme); 242 | if (!theme) { 243 | g_printerr ("No such theme '%s', using default (linux)\n", opt_theme); 244 | theme = &themes[1]; 245 | } 246 | } 247 | 248 | GdkRGBA fgcolor, bgcolor; 249 | if (!(opt_fgcolor && gdk_rgba_parse (&fgcolor, opt_fgcolor))) 250 | fgcolor = theme->fg; 251 | if (!(opt_bgcolor && gdk_rgba_parse (&bgcolor, opt_bgcolor))) 252 | bgcolor = theme->bg; 253 | 254 | vte_terminal_set_rewrap_on_resize (vtterm, TRUE); 255 | vte_terminal_set_scroll_on_keystroke (vtterm, TRUE); 256 | vte_terminal_set_audible_bell (vtterm, FALSE); 257 | vte_terminal_set_scroll_on_output (vtterm, FALSE); 258 | vte_terminal_set_allow_bold (vtterm, opt_bold); 259 | vte_terminal_set_scrollback_lines (vtterm, opt_scroll); 260 | vte_terminal_set_cursor_blink_mode (vtterm, VTE_CURSOR_BLINK_OFF); 261 | vte_terminal_set_cursor_shape (vtterm, VTE_CURSOR_SHAPE_BLOCK); 262 | vte_terminal_set_colors (vtterm, 263 | &fgcolor, 264 | &bgcolor, 265 | theme->colors, 266 | G_N_ELEMENTS (theme->colors)); 267 | 268 | g_autoptr(GError) error = NULL; 269 | g_autoptr(VteRegex) regex = 270 | vte_regex_new_for_match (uri_regexp, -1, 271 | PCRE2_CASELESS | PCRE2_MULTILINE, 272 | &error); 273 | if (regex) { 274 | if (!vte_regex_jit (regex, PCRE2_JIT_COMPLETE, &error)) 275 | g_warning ("Could not JIT-compile URI regex: %s", error->message); 276 | int tag = vte_terminal_match_add_regex (vtterm, regex, PCRE2_NOTEMPTY); 277 | vte_terminal_match_set_cursor_type (vtterm, tag, GDK_HAND2); 278 | } else { 279 | g_critical ("Could not compile URI regex: %s", error->message); 280 | } 281 | } 282 | 283 | 284 | static void 285 | term_beeped (VteTerminal *vtterm, gpointer userdata) 286 | { 287 | /* 288 | * Only set the _URGENT hint when the window is not focused. If the 289 | * window is focused, the user is likely to notice what is happening 290 | * without the need to call for attention. 291 | */ 292 | if (!gtk_window_has_toplevel_focus (GTK_WINDOW (userdata))) 293 | gtk_window_set_urgency_hint (GTK_WINDOW (userdata), TRUE); 294 | } 295 | 296 | 297 | static void 298 | term_char_size_changed (VteTerminal *vtterm, 299 | guint width, 300 | guint height, 301 | gpointer userdata) 302 | { 303 | GdkGeometry geometry; 304 | geometry.height_inc = height; 305 | geometry.width_inc = width; 306 | 307 | GtkBorder padding; 308 | gtk_style_context_get_padding (gtk_widget_get_style_context (GTK_WIDGET (vtterm)), 309 | gtk_widget_get_state_flags (GTK_WIDGET (vtterm)), 310 | &padding); 311 | geometry.base_height = padding.top + padding.bottom; 312 | geometry.base_width = padding.left + padding.right; 313 | 314 | geometry.min_height = geometry.base_height + 3 * geometry.height_inc; 315 | geometry.min_width = geometry.base_width + 10 * geometry.width_inc; 316 | 317 | gtk_window_set_geometry_hints (GTK_WINDOW (userdata), 318 | GTK_WIDGET (vtterm), 319 | &geometry, 320 | GDK_HINT_MIN_SIZE | 321 | GDK_HINT_BASE_SIZE | 322 | GDK_HINT_RESIZE_INC); 323 | gtk_widget_queue_resize (GTK_WIDGET (vtterm)); 324 | } 325 | 326 | 327 | static char* 328 | guess_shell (void) 329 | { 330 | char *shell = getenv ("SHELL"); 331 | if (!shell) { 332 | struct passwd *pw = getpwuid (getuid ()); 333 | shell = (pw) ? pw->pw_shell : "/bin/sh"; 334 | } 335 | /* Return a copy */ 336 | return g_strdup (shell); 337 | } 338 | 339 | 340 | static gboolean 341 | popover_idle_closed_tick (gpointer userdata) 342 | { 343 | gtk_widget_grab_focus (GTK_WIDGET (userdata)); 344 | return FALSE; /* Do no re-arm (run once) */ 345 | } 346 | 347 | static void 348 | popover_closed (GtkPopover *popover, 349 | VteTerminal *vtterm) 350 | { 351 | /* XXX: Grabbing the focus right away does not work, must do later. */ 352 | g_idle_add (popover_idle_closed_tick, vtterm); 353 | } 354 | 355 | static GtkWidget* 356 | setup_popover (VteTerminal *vtterm) 357 | { 358 | GtkWidget *popover = gtk_popover_new (GTK_WIDGET (vtterm)); 359 | g_signal_connect (G_OBJECT (popover), "closed", 360 | G_CALLBACK (popover_closed), vtterm); 361 | 362 | g_autoptr(GtkBuilder) builder = gtk_builder_new_from_resource (DWT_GRESOURCE ("menus.xml")); 363 | gtk_popover_bind_model (GTK_POPOVER (popover), 364 | G_MENU_MODEL (gtk_builder_get_object (builder, "popover-menu")), 365 | NULL); 366 | return popover; 367 | } 368 | 369 | 370 | static void 371 | image_popover_closed (GtkWidget *popover, 372 | gpointer userdata) 373 | { 374 | gtk_widget_destroy (popover); 375 | } 376 | 377 | 378 | static void 379 | image_pixbuf_loaded (GInputStream *stream, 380 | GAsyncResult *result, 381 | GtkWidget *popover) 382 | { 383 | g_autoptr(GError) error = NULL; 384 | g_autoptr(GdkPixbuf) pixbuf = gdk_pixbuf_new_from_stream_finish (result, 385 | &error); 386 | if (!pixbuf || error) { 387 | g_printerr ("Could not decode image from pixbuf: %s", error->message); 388 | return; 389 | } 390 | 391 | gtk_container_add (GTK_CONTAINER (popover), 392 | gtk_image_new_from_pixbuf (pixbuf)); 393 | g_signal_connect (popover, "closed", 394 | G_CALLBACK (image_popover_closed), NULL); 395 | gtk_widget_show_all (popover); 396 | } 397 | 398 | 399 | static void 400 | image_file_opened (GFile *file, 401 | GAsyncResult *result, 402 | GtkWidget *popover) 403 | { 404 | g_autoptr(GError) error = NULL; 405 | g_autoptr(GFileInputStream) stream = g_file_read_finish (file, result, &error); 406 | if (!stream || error) { 407 | g_autofree char *uri = g_file_get_uri (file); 408 | g_printerr ("Could not open URL '%s': %s", uri, error->message); 409 | return; 410 | } 411 | 412 | gdk_pixbuf_new_from_stream_at_scale_async (G_INPUT_STREAM (stream), 413 | 500, 500, TRUE, 414 | NULL, 415 | (GAsyncReadyCallback) image_pixbuf_loaded, 416 | popover); 417 | } 418 | 419 | 420 | static GtkWidget* 421 | make_popover_for_image_url (VteTerminal *vtterm, 422 | const gchar *uri) 423 | { 424 | g_assert (vtterm); 425 | g_assert (uri); 426 | 427 | GtkWidget *popover = gtk_popover_new (GTK_WIDGET (vtterm)); 428 | g_autoptr(GVfs) gvfs = g_vfs_get_default (); 429 | g_autoptr(GFile) file = g_vfs_get_file_for_uri (gvfs, uri); 430 | g_file_read_async (file, 431 | G_PRIORITY_DEFAULT, 432 | NULL, 433 | (GAsyncReadyCallback) image_file_opened, 434 | popover); 435 | return popover; 436 | } 437 | 438 | 439 | static gboolean 440 | term_mouse_button_released (VteTerminal *vtterm, 441 | GdkEventButton *event, 442 | gpointer userdata) 443 | { 444 | g_clear_pointer (&last_match_text, g_free); 445 | 446 | int match_tag; 447 | g_autofree char *match = 448 | vte_terminal_match_check_event (vtterm, (GdkEvent*) event, &match_tag); 449 | 450 | const long col = event->x / vte_terminal_get_char_width (vtterm); 451 | const long row = event->y / vte_terminal_get_char_height (vtterm); 452 | 453 | GtkWindow *window = GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (vtterm), 454 | GTK_TYPE_WINDOW)); 455 | 456 | if (match && event->button == 1) { 457 | if (CHECK_FLAGS (event->state, GDK_CONTROL_MASK)) { 458 | g_autoptr(GError) error = NULL; 459 | if (!gtk_show_uri_on_window (window, match, event->time, &error)) 460 | g_printerr ("Could not open URL: %s\n", error->message); 461 | return FALSE; 462 | } else if (g_regex_match (image_regex, match, 0, NULL)) { 463 | /* Show picture in a popover */ 464 | GdkRectangle rect; 465 | rect.height = vte_terminal_get_char_height (vtterm); 466 | rect.width = vte_terminal_get_char_width (vtterm); 467 | rect.y = rect.height * row; 468 | rect.x = rect.width * col; 469 | 470 | GtkWidget* popover = make_popover_for_image_url (vtterm, match); 471 | gtk_popover_set_pointing_to (GTK_POPOVER (popover), &rect); 472 | return FALSE; 473 | } 474 | } 475 | 476 | 477 | if (event->button == 3 && userdata != NULL) { 478 | GdkRectangle rect; 479 | rect.height = vte_terminal_get_char_height (vtterm); 480 | rect.width = vte_terminal_get_char_width (vtterm); 481 | rect.y = rect.height * row; 482 | rect.x = rect.width * col; 483 | gtk_popover_set_pointing_to (GTK_POPOVER (userdata), &rect); 484 | 485 | GActionMap *actions = G_ACTION_MAP (window); 486 | g_simple_action_set_enabled (G_SIMPLE_ACTION (g_action_map_lookup_action (actions, 487 | "copy")), 488 | vte_terminal_get_has_selection (vtterm)); 489 | g_simple_action_set_enabled (G_SIMPLE_ACTION (g_action_map_lookup_action (actions, 490 | "open-url")), 491 | match != NULL); 492 | g_simple_action_set_enabled (G_SIMPLE_ACTION (g_action_map_lookup_action (actions, 493 | "copy-url")), 494 | match != NULL); 495 | if (match) { 496 | last_match_text = match; 497 | match = NULL; 498 | } 499 | 500 | gtk_widget_show_all (GTK_WIDGET (userdata)); 501 | 502 | return TRUE; 503 | } 504 | 505 | return FALSE; 506 | } 507 | 508 | static gboolean 509 | beeped_revealer_timeout (gpointer userdata) 510 | { 511 | GtkWindow *window = 512 | GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (userdata), 513 | GTK_TYPE_WINDOW)); 514 | if (!gtk_window_has_toplevel_focus (window)) 515 | return TRUE; 516 | 517 | gtk_revealer_set_reveal_child (GTK_REVEALER (userdata), FALSE); 518 | return FALSE; /* Do not re-arm timer, run only once */ 519 | } 520 | 521 | static void 522 | header_bar_term_beeped (VteTerminal *vtterm, 523 | GtkRevealer *revealer) 524 | { 525 | /* If already shown, do nothing */ 526 | if (gtk_revealer_get_reveal_child (revealer)) 527 | return; 528 | 529 | gtk_revealer_set_reveal_child (revealer, TRUE); 530 | g_timeout_add_seconds (3, beeped_revealer_timeout, revealer); 531 | } 532 | 533 | static void 534 | setup_header_bar (GtkWidget *window, 535 | VteTerminal *vtterm, 536 | gboolean show_maximized_title) 537 | { 538 | /* 539 | * Using the default GtkHeaderBar title/subtitle widget makes the bar 540 | * too thick to look nice for a terminal, so set a custom widget. 541 | */ 542 | const gchar *title = gtk_window_get_title (GTK_WINDOW (window)); 543 | GtkWidget *label = gtk_label_new (title ? title : "dwt"); 544 | g_object_bind_property (G_OBJECT (vtterm), "window-title", 545 | G_OBJECT (label), "label", 546 | G_BINDING_DEFAULT); 547 | 548 | GtkWidget *header = gtk_header_bar_new (); 549 | gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header), TRUE); 550 | gtk_header_bar_set_has_subtitle (GTK_HEADER_BAR (header), FALSE); 551 | gtk_header_bar_set_custom_title (GTK_HEADER_BAR (header), label); 552 | 553 | GtkWidget *button = gtk_button_new_from_icon_name ("tab-new-symbolic", 554 | GTK_ICON_SIZE_BUTTON); 555 | gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); 556 | gtk_actionable_set_action_name (GTK_ACTIONABLE (button), "app.new-terminal"); 557 | gtk_header_bar_pack_start (GTK_HEADER_BAR (header), button); 558 | 559 | GtkWidget *revealer = gtk_revealer_new (); 560 | gtk_container_add (GTK_CONTAINER (revealer), 561 | gtk_image_new_from_icon_name ("software-update-urgent-symbolic", 562 | GTK_ICON_SIZE_BUTTON)); 563 | gtk_revealer_set_transition_duration (GTK_REVEALER (revealer), 500); 564 | gtk_revealer_set_transition_type (GTK_REVEALER (revealer), 565 | GTK_REVEALER_TRANSITION_TYPE_CROSSFADE); 566 | gtk_header_bar_pack_end (GTK_HEADER_BAR (header), revealer); 567 | 568 | g_signal_connect (G_OBJECT (vtterm), "bell", 569 | G_CALLBACK (header_bar_term_beeped), revealer); 570 | g_object_bind_property (G_OBJECT (window), "urgency-hint", 571 | G_OBJECT (revealer), "reveal-child", 572 | G_BINDING_DEFAULT); 573 | 574 | gtk_window_set_titlebar (GTK_WINDOW (window), header); 575 | 576 | /* Hide the header bar when the window is maximized. */ 577 | if (!show_maximized_title) { 578 | g_object_bind_property (G_OBJECT (window), "is-maximized", 579 | G_OBJECT (header), "visible", 580 | G_BINDING_INVERT_BOOLEAN); 581 | } 582 | } 583 | 584 | 585 | static void 586 | window_has_toplevel_focus_notified (GObject *object, 587 | GParamSpec *pspec, 588 | gpointer userdata) 589 | { 590 | if (gtk_window_has_toplevel_focus (GTK_WINDOW (object))) { 591 | vte_terminal_set_color_cursor (VTE_TERMINAL (userdata), 592 | &cursor_active); 593 | /* Clear the _URGENT hint when the window gets activated. */ 594 | gtk_window_set_urgency_hint (GTK_WINDOW (object), FALSE); 595 | } else { 596 | vte_terminal_set_color_cursor (VTE_TERMINAL (userdata), 597 | &cursor_inactive); 598 | } 599 | } 600 | 601 | 602 | static void 603 | term_child_exited (VteTerminal *vtterm, 604 | gint status, 605 | gpointer userdata) 606 | { 607 | /* 608 | * Destroy the window when the terminal child is exited. Note that this 609 | * will fire the "delete-event" signal, and its handler already takes 610 | * care of deregistering the window in the GtkApplication. 611 | */ 612 | gtk_window_close (GTK_WINDOW (userdata)); 613 | } 614 | 615 | 616 | static VteTerminal* 617 | window_get_term_widget (GtkWindow *window) 618 | { 619 | GtkWidget *widget = gtk_bin_get_child (GTK_BIN (window)); 620 | if (GTK_IS_BIN (widget)) 621 | widget = gtk_bin_get_child (GTK_BIN (widget)); 622 | return VTE_TERMINAL (widget); 623 | } 624 | 625 | 626 | static void 627 | font_size_action_ativated (GSimpleAction *action, 628 | GVariant *parameter, 629 | gpointer userdata) 630 | { 631 | VteTerminal *vtterm = window_get_term_widget (GTK_WINDOW (userdata)); 632 | const gint modifier = g_variant_get_int32 (parameter); 633 | 634 | const PangoFontDescription *fontd = vte_terminal_get_font (vtterm); 635 | gint old_size = pango_font_description_get_size (fontd); 636 | if (default_font_size == 0) 637 | default_font_size = old_size; 638 | PangoFontDescription *new_fontd; 639 | gint new_size; 640 | 641 | switch (modifier) { 642 | case 0: 643 | old_size = default_font_size; 644 | /* fall-through */ 645 | case 1: 646 | case -1: 647 | new_size = old_size + modifier * PANGO_SCALE; 648 | new_fontd = pango_font_description_copy_static (fontd); 649 | pango_font_description_set_size (new_fontd, new_size); 650 | vte_terminal_set_font (vtterm, new_fontd); 651 | break; 652 | 653 | default: 654 | g_printerr ("%s: invalid modifier '%i'", __func__, modifier); 655 | return; 656 | } 657 | 658 | pango_font_description_free (new_fontd); 659 | } 660 | 661 | 662 | static void 663 | paste_action_activated (GSimpleAction *action, 664 | GVariant *parameter, 665 | gpointer userdata) 666 | { 667 | vte_terminal_paste_clipboard (window_get_term_widget (GTK_WINDOW (userdata))); 668 | } 669 | 670 | 671 | static void 672 | copy_action_activated (GSimpleAction *action, 673 | GVariant *parameter, 674 | gpointer userdata) 675 | { 676 | VteTerminal *vtterm = window_get_term_widget (GTK_WINDOW (userdata)); 677 | vte_terminal_copy_clipboard_format (vtterm, VTE_FORMAT_TEXT); 678 | vte_terminal_copy_primary (vtterm); 679 | } 680 | 681 | 682 | static void 683 | new_terminal_action_activated (GSimpleAction *action, 684 | GVariant *parameter, 685 | gpointer userdata) 686 | { 687 | create_new_window (GTK_APPLICATION (userdata), NULL); 688 | } 689 | 690 | 691 | static void 692 | about_action_activated (GSimpleAction *action, 693 | GVariant *parameter, 694 | gpointer userdata) 695 | { 696 | static const gchar* authors[] = { 697 | "Adrián Pérez de Castro", 698 | NULL, 699 | }; 700 | 701 | gtk_show_about_dialog (NULL, 702 | "authors", authors, 703 | "logo-icon-name", "terminal", 704 | "license-type", GTK_LICENSE_MIT_X11, 705 | "comments", "A sleek terminal emulator", 706 | "website", "https://github.com/aperezdc/dwt", 707 | NULL); 708 | } 709 | 710 | 711 | static void 712 | quit_action_activated (GSimpleAction *action, 713 | GVariant *parameter, 714 | gpointer userdata) 715 | { 716 | g_application_quit (G_APPLICATION (userdata)); 717 | } 718 | 719 | 720 | static void 721 | copy_url_action_activated (GSimpleAction *action, 722 | GVariant *parameter, 723 | gpointer userdata) 724 | { 725 | gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY), 726 | last_match_text, -1); 727 | gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), 728 | last_match_text, -1); 729 | 730 | g_free (last_match_text); 731 | last_match_text = NULL; 732 | } 733 | 734 | 735 | static void 736 | open_url_action_activated (GSimpleAction *action, 737 | GVariant *parameter, 738 | gpointer userdata) 739 | { 740 | g_autoptr(GError) error = NULL; 741 | if (!gtk_show_uri_on_window (GTK_WINDOW (userdata), 742 | last_match_text, 743 | GDK_CURRENT_TIME, 744 | &error)) 745 | g_warning ("Could not open URL: %s", error->message); 746 | g_clear_pointer (&last_match_text, g_free); 747 | } 748 | 749 | 750 | static const GActionEntry win_actions[] = { 751 | { "font-reset", font_size_action_ativated, "i", "0", NULL }, 752 | { "font-bigger", font_size_action_ativated, "i", "1", NULL }, 753 | { "font-smaller", font_size_action_ativated, "i", "-1", NULL }, 754 | { "copy", copy_action_activated, NULL, NULL, NULL }, 755 | { "paste", paste_action_activated, NULL, NULL, NULL }, 756 | { "copy-url", copy_url_action_activated, NULL, NULL, NULL }, 757 | { "open-url", open_url_action_activated, NULL, NULL, NULL }, 758 | }; 759 | 760 | static const GActionEntry app_actions[] = { 761 | { "new-terminal", new_terminal_action_activated, NULL, NULL, NULL }, 762 | { "about", about_action_activated, NULL, NULL, NULL }, 763 | { "quit", quit_action_activated, NULL, NULL, NULL }, 764 | }; 765 | 766 | 767 | static void 768 | on_child_spawned (G_GNUC_UNUSED VteTerminal *vtterm, 769 | GPid pid, GError *error, void *userdata) 770 | { 771 | if (pid == -1) { 772 | // Error: report and close window. 773 | g_assert_nonnull (error); 774 | gtk_window_close (GTK_WINDOW (userdata)); 775 | g_error ("Cannot spawn child process: %s", error->message); 776 | } else { 777 | g_assert_null (error); 778 | } 779 | } 780 | 781 | 782 | static GtkWidget* 783 | create_new_window (GtkApplication *application, 784 | GVariantDict *options) 785 | { 786 | g_autofree char *command = NULL; 787 | g_autofree char *title = NULL; 788 | gboolean opt_show_title; 789 | gboolean opt_update_title; 790 | gboolean opt_no_headerbar; 791 | 792 | g_object_get (dwt_settings_get_instance (), 793 | "show-title", &opt_show_title, 794 | "update-title", &opt_update_title, 795 | "no-header-bar", &opt_no_headerbar, 796 | "command", &command, 797 | "title", &title, 798 | NULL); 799 | 800 | const gchar *opt_command = command; 801 | const gchar *opt_title = title; 802 | const gchar *opt_workdir = NULL; 803 | 804 | if (options) { 805 | gboolean opt_no_auto_title = FALSE; 806 | g_variant_dict_lookup (options, "title-on-maximize", "b", &opt_show_title); 807 | g_variant_dict_lookup (options, "no-header-bar", "b", &opt_no_headerbar); 808 | g_variant_dict_lookup (options, "no-auto-title", "b", &opt_no_auto_title); 809 | g_variant_dict_lookup (options, "workdir", "&s", &opt_workdir); 810 | g_variant_dict_lookup (options, "command", "&s", &opt_command); 811 | g_variant_dict_lookup (options, "title", "&s", &opt_title); 812 | if (opt_no_auto_title) 813 | opt_update_title = FALSE; 814 | } 815 | if (!opt_workdir) opt_workdir = g_get_home_dir (); 816 | if (!opt_command) opt_command = guess_shell (); 817 | 818 | /* 819 | * Title either comes from the default value of the "title" setting, 820 | * or from the command line flag, but should never be NULL at this 821 | * point. 822 | */ 823 | g_assert (opt_title); 824 | 825 | g_autoptr(GError) error = NULL; 826 | gint command_argv_len = 0; 827 | gchar **command_argv = NULL; 828 | 829 | if (!g_shell_parse_argv (opt_command, 830 | &command_argv_len, 831 | &command_argv, 832 | &error)) 833 | { 834 | g_printerr ("%s: coult not parse command: %s\n", 835 | __func__, error->message); 836 | return NULL; 837 | } 838 | 839 | GtkWidget *window = gtk_application_window_new (application); 840 | gtk_widget_set_visual (window, 841 | gdk_screen_get_system_visual (gtk_widget_get_screen (window))); 842 | gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (window), 843 | FALSE); 844 | gtk_window_set_title (GTK_WINDOW (window), opt_title); 845 | gtk_window_set_hide_titlebar_when_maximized (GTK_WINDOW (window), 846 | !opt_show_title); 847 | 848 | g_action_map_add_action_entries (G_ACTION_MAP (window), win_actions, 849 | G_N_ELEMENTS (win_actions), window); 850 | 851 | VteTerminal *vtterm = VTE_TERMINAL (vte_terminal_new ()); 852 | configure_term_widget (vtterm, options); 853 | term_char_size_changed (vtterm, 854 | vte_terminal_get_char_width (vtterm), 855 | vte_terminal_get_char_height (vtterm), 856 | window); 857 | 858 | g_signal_connect (G_OBJECT (window), "notify::has-toplevel-focus", 859 | G_CALLBACK (window_has_toplevel_focus_notified), 860 | vtterm); 861 | 862 | g_signal_connect (G_OBJECT (vtterm), "char-size-changed", 863 | G_CALLBACK (term_char_size_changed), window); 864 | g_signal_connect (G_OBJECT (vtterm), "child-exited", 865 | G_CALLBACK (term_child_exited), window); 866 | g_signal_connect (G_OBJECT (vtterm), "bell", 867 | G_CALLBACK (term_beeped), window); 868 | g_signal_connect (G_OBJECT (vtterm), "button-release-event", 869 | G_CALLBACK (term_mouse_button_released), 870 | setup_popover (vtterm)); 871 | 872 | /* 873 | * Propagate title changes to the window. 874 | */ 875 | if (opt_update_title) 876 | g_object_bind_property (G_OBJECT (vtterm), "window-title", 877 | G_OBJECT (window), "title", 878 | G_BINDING_DEFAULT); 879 | 880 | if (!opt_no_headerbar) 881 | setup_header_bar (window, vtterm, opt_show_title); 882 | 883 | gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (vtterm)); 884 | gtk_widget_set_receives_default (GTK_WIDGET (vtterm), TRUE); 885 | 886 | /* We need to realize and show the window for it to have a valid XID */ 887 | gtk_widget_show_all (window); 888 | 889 | gchar **command_env = g_get_environ (); 890 | #ifdef GDK_WINDOWING_X11 891 | if (GDK_IS_X11_SCREEN (gtk_widget_get_screen (window))) { 892 | GdkWindow *gdk_window = gtk_widget_get_window (window); 893 | if (gdk_window) { 894 | gchar window_id[NDIGITS10(unsigned long)]; 895 | snprintf (window_id, 896 | sizeof (window_id), 897 | "%lu", 898 | GDK_WINDOW_XID (gdk_window)); 899 | command_env = g_environ_setenv (command_env, 900 | "WINDOWID", 901 | window_id, 902 | TRUE); 903 | } else { 904 | g_printerr ("No window, cannot set $WINDOWID!\n"); 905 | } 906 | } 907 | #endif /* GDK_WINDOWING_X11 */ 908 | 909 | vte_terminal_spawn_async (VTE_TERMINAL (vtterm), 910 | VTE_PTY_DEFAULT, 911 | opt_workdir, 912 | command_argv, 913 | command_env, 914 | G_SPAWN_SEARCH_PATH, 915 | NULL, 916 | NULL, 917 | NULL, 918 | -1, 919 | NULL, 920 | on_child_spawned, 921 | window); 922 | return window; 923 | } 924 | 925 | 926 | static void 927 | app_started (GApplication *application, gpointer userdata) 928 | { 929 | /* Set default icon, and preferred theme variant. */ 930 | g_autofree char *cursor_color = NULL; 931 | g_autofree char *icon = NULL; 932 | g_object_get (dwt_settings_get_instance (), 933 | "cursor-color", &cursor_color, 934 | "icon", &icon, 935 | NULL); 936 | gtk_window_set_default_icon_name (icon); 937 | g_object_set(gtk_settings_get_default(), 938 | "gtk-application-prefer-dark-theme", 939 | TRUE, NULL); 940 | 941 | if (cursor_color) { 942 | gdk_rgba_parse (&cursor_active, cursor_color); 943 | memcpy (&cursor_inactive, &cursor_active, sizeof (GdkRGBA)); 944 | cursor_inactive.red = 0.5 * cursor_active.red; 945 | cursor_inactive.green = 0.5 * cursor_active.green; 946 | cursor_inactive.blue = 0.5 * cursor_active.blue; 947 | cursor_inactive.alpha = 0.5 * cursor_active.alpha; 948 | } 949 | 950 | image_regex = g_regex_new (image_regex_string, G_REGEX_CASELESS | G_REGEX_OPTIMIZE, 0, NULL); 951 | g_assert (image_regex); 952 | 953 | g_action_map_add_action_entries (G_ACTION_MAP (application), app_actions, 954 | G_N_ELEMENTS (app_actions), application); 955 | 956 | g_autoptr(GtkBuilder) builder = gtk_builder_new_from_resource (DWT_GRESOURCE ("menus.xml")); 957 | gtk_application_set_app_menu (GTK_APPLICATION (application), 958 | G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu"))); 959 | 960 | const struct { 961 | const gchar *action; 962 | const gchar *accel; 963 | GVariant *param; 964 | } accel_map[] = { 965 | { "app.new-terminal", "n", NULL }, 966 | { "win.font-reset", "0", g_variant_new_int32 (+0) }, 967 | { "win.font-bigger", "plus", g_variant_new_int32 (+1) }, 968 | { "win.font-smaller", "minus", g_variant_new_int32 (-1) }, 969 | { "win.copy", "c", NULL }, 970 | { "win.paste", "p", NULL }, 971 | }; 972 | 973 | for (guint i = 0; i < G_N_ELEMENTS (accel_map); i++) { 974 | gtk_application_add_accelerator (GTK_APPLICATION (application), 975 | accel_map[i].accel, 976 | accel_map[i].action, 977 | accel_map[i].param); 978 | } 979 | } 980 | 981 | 982 | static void 983 | app_shutdown (GApplication *application, gpointer userdata) 984 | { 985 | g_regex_unref (image_regex); 986 | } 987 | 988 | 989 | static gint 990 | app_command_line_received (GApplication *application, 991 | GApplicationCommandLine *cmdline) 992 | { 993 | g_application_hold (G_APPLICATION (application)); 994 | GVariantDict *options = g_application_command_line_get_options_dict (cmdline); 995 | 996 | g_autofree char *opt_theme = NULL; 997 | if (g_variant_dict_lookup (options, "theme", "s", &opt_theme) && g_str_equal (opt_theme, "list")) { 998 | for (size_t i = 0; i < G_N_ELEMENTS (themes); i++) { 999 | g_print ("%s\n", themes[i].name); 1000 | } 1001 | } else { 1002 | create_new_window (GTK_APPLICATION (application), options); 1003 | } 1004 | g_variant_dict_unref (options); 1005 | g_application_release (application); 1006 | return 0; 1007 | } 1008 | 1009 | 1010 | static const gchar* 1011 | get_application_id (const gchar* argv0) { 1012 | static const gchar *app_id = NULL; 1013 | if (!app_id) { 1014 | if (!(app_id = g_getenv ("DWT_APPLICATION_ID"))) { 1015 | app_id = "org.perezdecastro.dwt"; 1016 | const gchar* slash_position = strrchr (argv0, '/'); 1017 | if (slash_position) 1018 | argv0 = slash_position + 1; 1019 | if (!g_str_equal ("dwt", argv0)) 1020 | app_id = g_strdup_printf ("%s.%s", app_id, argv0); 1021 | g_set_prgname (app_id + G_N_ELEMENTS ("org.perezdecastro.") - 1); 1022 | } 1023 | } 1024 | return (g_str_equal ("none", app_id) || 1025 | g_getenv ("DWT_SINGLE_WINDOW_PROCESS") != NULL) 1026 | ? NULL : app_id; 1027 | } 1028 | 1029 | 1030 | int 1031 | main (int argc, char *argv[]) 1032 | { 1033 | g_autoptr(GtkApplication) application = 1034 | gtk_application_new (get_application_id (argv[0]), 1035 | G_APPLICATION_HANDLES_COMMAND_LINE); 1036 | 1037 | g_application_add_main_option_entries (G_APPLICATION (application), 1038 | option_entries); 1039 | 1040 | g_signal_connect (G_OBJECT (application), "startup", 1041 | G_CALLBACK (app_started), NULL); 1042 | g_signal_connect (G_OBJECT (application), "shutdown", 1043 | G_CALLBACK (app_shutdown), NULL); 1044 | g_signal_connect (G_OBJECT (application), "command-line", 1045 | G_CALLBACK (app_command_line_received), NULL); 1046 | 1047 | return g_application_run (G_APPLICATION (application), argc, argv); 1048 | } 1049 | 1050 | --------------------------------------------------------------------------------