Make native library easier to build/use

Makes libtsm a static library rather than dynamic so that it doesn't
have to be installed as a system library on the user's operating system.

Also updates SConstruct to work on more operating systems other than
just NixOS. Adds a number of docker files for building the library on
various distributions (NixOS, Arch Linux, Ubuntu).

Uses a fork of godot-cpp with an updated godot_headers submodule that
includes godotengine/godot_headers#76. We should go back to tracking
https://github.com/godotengine/godot-cpp once the submodule has
been updated in that repo.

Former-commit-id: d8c8b5b272
This commit is contained in:
Leroy Hopson 2020-07-13 11:14:30 +07:00
parent 5cc2b2c718
commit f8412a03f5
30 changed files with 489 additions and 158 deletions

5
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "addons/godot_xterm_native/godot-cpp"] [submodule "addons/godot_xterm_native/godot-cpp"]
path = addons/godot_xterm_native/godot-cpp path = addons/godot_xterm_native/godot-cpp
url = https://github.com/GodotNativeTools/godot-cpp url = https://github.com/lihop/godot-cpp
[submodule "addons/godot_xterm_native/libtsm"]
path = addons/godot_xterm_native/libtsm
url = https://github.com/Aetf/libtsm

View file

@ -1,11 +1,17 @@
env: language: nix
- RUN_FULL_CODEPOINT_TESTS=true
services: services:
- docker - docker
install: jobs:
- docker pull barichello/godot-ci include:
- name: "Run tests"
env: SERVICE=tests
- name: "Build native on Arch Linux"
env: SERVICE=build-archlinux
- name: "Build native on NixOS"
env: SERVICE=build-nixos
- name: "Build native on Ubuntu"
env: SERVICE=build-ubuntu
script: script: docker-compose run $SERVICE
- docker run -v $(pwd):/src -e RUN_FULL_CODEPOINT_TESTS="${RUN_FULL_CODEPOINT_TESTS}" barichello/godot-ci godot --path /src -s addons/gut/gut_cmdln.gd

View file

@ -6,6 +6,14 @@
![Godot Version](https://img.shields.io/badge/godot-3.2+-blue.svg) ![Godot Version](https://img.shields.io/badge/godot-3.2+-blue.svg)
![License](https://img.shields.io/badge/license-MIT-green.svg) ![License](https://img.shields.io/badge/license-MIT-green.svg)
Native implementation of Godot Xterm using GDNative with [libtsm](https://github.com/Aetf/libtsm).
## Demo
Click the thumbnail to watch a demo video on youtube:
[![Demo video thumbnail](https://img.youtube.com/vi/_Tt4eQEBybo/0.jpg)](https://www.youtube.com/watch?v=_Tt4eQEBybo)
## License ## License
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license.
@ -14,4 +22,4 @@ You are also implicitly verifying that all code is your original work, or unorig
Copyright (c) 2020 Leroy Hopson (MIT License) Copyright (c) 2020 Leroy Hopson (MIT License)
The fonts used in this project are published under a seperate license. The fonts used in this project are published under a seperate license.
See: [addons/godot_xterm_native/fonts/LICENSE](addons/godot_xterm_native/fonts/LICENSE). See: [addons/godot_xterm_native/fonts/LICENSE.txt](addons/godot_xterm_native/fonts/LICENSE.txt).

359
addons/godot_xterm_native/.gitignore vendored Normal file
View file

@ -0,0 +1,359 @@
# Godot auto generated files
*.gen.*
.import/
# Documentation generated by doxygen or from classes.xml
doc/_build/
# Javascript specific
*.bc
# CLion
cmake-build-debug
# Android specific
.gradle
local.properties
*.iml
.idea
.gradletasknamecache
project.properties
platform/android/java/app/libs/*
platform/android/java/libs/*
platform/android/java/lib/.cxx/
# General c++ generated files
*.lib
*.o
*.ox
*.a
*.ax
*.d
*.so
*.os
*.Plo
*.lo
# Libs generated files
.deps/*
.dirstamp
# Gprof output
gmon.out
# Vim temp files
*.swo
*.swp
# Qt project files
*.config
*.creator
*.creator.*
*.files
*.includes
*.cflags
*.cxxflags
# Code::Blocks files
*.cbp
*.layout
*.depend
# Eclipse CDT files
.cproject
.settings/
*.pydevproject
*.launch
# Geany/geany-plugins files
*.geany
.geanyprj
# Jetbrains IDEs
.idea/
# Misc
.DS_Store
__MACOSX
logs/
# for projects that use SCons for building: http://http://www.scons.org/
.sconf_temp
.sconsign*.dblite
*.pyc
# https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
*.sln
*.vcxproj*
# Custom SCons configuration override
/custom.py
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
build/
bld/
[Oo]bj/
*.debug
*.dSYM
# Visual Studio cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# Hints for improving IntelliSense, created together with VS project
cpp.hint
#NUNIT
*.VisualState.xml
TestResult.xml
*.o
*.a
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.bak
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
*.nib
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.opendb
*.VC.VC.opendb
enc_temp_folder/
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# CodeLite project files
*.project
*.workspace
.codelite/
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
*.ncrunch*
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
#packages/*
## TODO: If the tool you use requires repositories.config, also uncomment the next line
#!packages/repositories.config
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
# This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented)
!packages/build/
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
__pycache__/
# KDE
.directory
#Kdevelop project files
*.kdev4
# Xcode
xcuserdata/
*.xcscmblueprint
*.xccheckout
*.xcodeproj/*
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
App_Data/*.mdf
App_Data/*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# =========================
# Windows detritus
# =========================
# Windows image file caches
[Tt]humbs.db
[Tt]humbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Windows stackdumps
*.stackdump
# Windows shortcuts
*.lnk
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
logo.h
*.autosave
# https://github.com/github/gitignore/blob/master/Global/Tags.gitignore
# Ignore tags created by etags, ctags, gtags (GNU global) and cscope
TAGS
!TAGS/
tags
*.tags
!tags/
gtags.files
GTAGS
GRTAGS
GPATH
cscope.files
cscope.out
cscope.in.out
cscope.po.out
godot.creator.*
projects/
platform/windows/godot_res.res
# Visual Studio 2017 and Visual Studio Code workspace folder
/.vs
/.vscode
# Visual Studio Code workspace file
*.code-workspace
# Scons construction environment dump
.scons_env.json
# Scons progress indicator
.scons_node_count
# ccls cache (https://github.com/MaskRay/ccls)
.ccls-cache/
# compile commands (https://clang.llvm.org/docs/JSONCompilationDatabase.html)
compile_commands.json
# Cppcheck
*.cppcheck

View file

@ -1,14 +0,0 @@
{
"configurations": [
{
"name": "Linux",
"includePath": ["${workspaceFolder}/**"],
"defines": [],
"compilerPath": "/nix/store/l2sqva7kzr8y68lji2aajk2s8017jyq4-gcc-wrapper-8.3.0/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}

View file

@ -1 +0,0 @@
/nix/store/c4l4iva7dm7fpfx8y3jb6hdzlsm1cvmj-libvterm-neovim-0.1.3/include/vterm.h

View file

@ -1 +0,0 @@
/nix/store/c4l4iva7dm7fpfx8y3jb6hdzlsm1cvmj-libvterm-neovim-0.1.3/include/vterm_keycodes.h

View file

@ -1,50 +0,0 @@
{
"files.associations": {
"array": "cpp",
"atomic": "cpp",
"*.tcc": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"string": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"new": "cpp",
"ostream": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"typeinfo": "cpp",
"filesystem": "cpp"
},
"nixEnvSelector.nixShellConfig": "${workspaceRoot}/default.nix"
}

View file

@ -1,16 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "echo",
"type": "shell",
"command": "nix-shell --run 'scons platform=linux -j12'",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View file

View file

@ -6,6 +6,9 @@ opts = Variables([], ARGUMENTS)
# Gets the standard flags CC, CCX, etc. # Gets the standard flags CC, CCX, etc.
env = DefaultEnvironment() env = DefaultEnvironment()
# Add PATH to environment so scons can find commands such as g++, etc.
env.AppendENVPath('PATH', os.getenv('PATH'))
# Define our options # Define our options
opts.Add(EnumVariable('target', "Compilation target", 'debug', ['d', 'debug', 'r', 'release'])) opts.Add(EnumVariable('target', "Compilation target", 'debug', ['d', 'debug', 'r', 'release']))
opts.Add(EnumVariable('platform', "Compilation platform", '', ['', 'windows', 'x11', 'linux', 'osx'])) opts.Add(EnumVariable('platform', "Compilation platform", '', ['', 'windows', 'x11', 'linux', 'osx']))
@ -18,6 +21,7 @@ opts.Add(PathVariable('target_name', 'The library name.', 'libgodotxtermnative',
godot_headers_path = "godot-cpp/godot_headers/" godot_headers_path = "godot-cpp/godot_headers/"
cpp_bindings_path = "godot-cpp/" cpp_bindings_path = "godot-cpp/"
cpp_library = "libgodot-cpp" cpp_library = "libgodot-cpp"
libtsm_path = "libtsm/"
# only support 64 at this time.. # only support 64 at this time..
bits = 64 bits = 64
@ -30,8 +34,8 @@ if env['use_llvm']:
env['CC'] = 'clang' env['CC'] = 'clang'
env['CXX'] = 'clang++' env['CXX'] = 'clang++'
else: else:
env['CC'] = '/home/leroy/.nix-profile/bin/gcc' env['CC'] = 'gcc'
env['CXX'] = '/home/leroy/.nix-profile/bin/g++' env['CXX'] = 'g++'
if env['p'] != '': if env['p'] != '':
env['platform'] = env['p'] env['platform'] = env['p']
@ -64,7 +68,6 @@ elif env['platform'] in ('x11', 'linux'):
env['target_path'] += 'x11/' env['target_path'] += 'x11/'
cpp_library += '.linux' cpp_library += '.linux'
env.Append(CCFLAGS=['-fPIC', '-shared']) env.Append(CCFLAGS=['-fPIC', '-shared'])
env.Append(CCFLAGS=os.environ['NIX_CFLAGS_COMPILE'])
env.Append(CXXFLAGS=['-std=c++17', '-shared', '-pthread']) env.Append(CXXFLAGS=['-std=c++17', '-shared', '-pthread'])
if env['target'] in ('debug', 'd'): if env['target'] in ('debug', 'd'):
env.Append(CCFLAGS=['-g3', '-Og']) env.Append(CCFLAGS=['-g3', '-Og'])
@ -96,8 +99,8 @@ else:
cpp_library += '.' + str(bits) cpp_library += '.' + str(bits)
# make sure our binding library is properly includes # make sure our binding library is properly includes
env.Append(CPPPATH=['.', godot_headers_path, cpp_bindings_path + 'include/', cpp_bindings_path + 'include/core/', cpp_bindings_path + 'include/gen/']) env.Append(CPPPATH=['.', godot_headers_path, cpp_bindings_path + 'include/', cpp_bindings_path + 'include/core/', cpp_bindings_path + 'include/gen/', libtsm_path + 'src/tsm'])
env.Append(LIBPATH=[cpp_bindings_path + 'bin/'] + os.environ['LD_LIBRARY_PATH'].split(':')) env.Append(LIBPATH=[cpp_bindings_path + 'bin/', libtsm_path + 'build/src/tsm'])
env.Append(LIBS=[cpp_library, 'tsm', 'util']) # Note util used by pseudoterminal, tsm used by terminal. env.Append(LIBS=[cpp_library, 'tsm', 'util']) # Note util used by pseudoterminal, tsm used by terminal.
# tweak this if you want to use different folders, or more folders, to store your source code in. # tweak this if you want to use different folders, or more folders, to store your source code in.

View file

@ -0,0 +1,2 @@
*
!.gitignore

View file

@ -1 +0,0 @@
da09712f58aeddfe9ec323c9fa07fba8be5ff7b5

View file

@ -0,0 +1,27 @@
#! /usr/bin/env nix-shell
#! nix-shell -i bash --pure -p binutils.bintools cmake scons
# Make sure we are in the addons/godot_xterm_native directory
cd ${BASH_SOURCE%/*}
# Initialize godot-cpp
if [ ! -d "godot-cpp/bin" ]
then
cd godot-cpp
scons platform=linux generate_bindings=yes -j12
cd ..
fi
# Build libtsm
if [ ! -f "libtsm/build/src/tsm/libtsm.a" ]
then
cd libtsm
mkdir -p build
cd build
cmake -DBUILD_SHARED_LIBS=n ..
make
cd ../..
fi
# Build godotxtermnative
scons platform=linux

View file

@ -1,20 +0,0 @@
with import /home/leroy/nixpkgs { overlays = [ (import ./overlay.nix) ]; };
stdenv.mkDerivation rec {
name = "systemnauts";
buildInputs = [
gcc
scons
libaudit
libvterm
libtsm
];
enablePrallelBuilding = true;
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
libaudit
libvterm
libtsm
];
}

@ -1 +1 @@
Subproject commit 9eceb16f0553884094d0f659461649be5d333866 Subproject commit 20d57e6cf5d0353de0905f4edd3697b6de32bc74

@ -0,0 +1 @@
Subproject commit f70e37982f382b03c6939dac3d5f814450bda253

View file

@ -1,19 +0,0 @@
self: super:
{
libtsm = super.libtsm.overrideAttrs (oldAttrs: {
src = super.fetchFromGitHub {
owner = "Aetf";
repo = "libtsm";
rev = "a5a9ea0c23b28aa2c9cf889105b75981fdebbc25";
sha256 = "0hqb0fjh5cwr2309sy2626ghhbnx7g64s8f3qqyvk7j8aysxgggh";
};
nativeBuildInputs = with super; [ cmake gtk3 cairo pango libxkbcommon pkgconfig ];
dontDisableStatic = true;
});
libaudit = super.libaudit.overrideAttrs (oldAttrs: { dontDisableStatic = true; });
libvterm = super.libvterm-neovim.overrideAttrs (oldAttrs: { dontDisableStatic = true; });
}

View file

@ -1,6 +1,7 @@
#include "pseudoterminal.h" #include "pseudoterminal.h"
#include <pty.h> #include <pty.h>
#include <unistd.h> #include <unistd.h>
#include <termios.h>
using namespace godot; using namespace godot;
@ -36,6 +37,14 @@ void Pseudoterminal::process_pty()
should_process_pty = true; should_process_pty = true;
struct termios termios = {};
termios.c_iflag = IGNPAR | ICRNL;
termios.c_oflag = 0;
termios.c_cflag = B38400 | CRTSCTS | CS8 | CLOCAL | CREAD;
termios.c_lflag = ICANON;
termios.c_cc[VMIN] = 1;
termios.c_cc[VTIME] = 0;
pid_t pty_pid = forkpty(&fd, NULL, NULL, NULL); pid_t pty_pid = forkpty(&fd, NULL, NULL, NULL);
if (pty_pid == -1) if (pty_pid == -1)
@ -71,13 +80,27 @@ void Pseudoterminal::process_pty()
FD_SET(fd, &write_fds); FD_SET(fd, &write_fds);
struct timeval timeout; struct timeval timeout;
timeout.tv_sec = 5; timeout.tv_sec = 10;
timeout.tv_usec = 0; timeout.tv_usec = 0;
ready = select(fd + 1, &read_fds, &write_fds, NULL, &timeout); ready = select(fd + 1, &read_fds, &write_fds, NULL, &timeout);
if (ready > 0) if (ready > 0)
{ {
if (FD_ISSET(fd, &write_fds))
{
std::lock_guard<std::mutex> guard(write_buffer_mutex);
if (bytes_to_write > 0)
{
write(fd, write_buffer, bytes_to_write);
Godot::print(String("wrote {0} bytes").format(Array::make(bytes_to_write)));
bytes_to_write = 0;
}
}
if (FD_ISSET(fd, &read_fds)) if (FD_ISSET(fd, &read_fds))
{ {
std::lock_guard<std::mutex> guard(read_buffer_mutex); std::lock_guard<std::mutex> guard(read_buffer_mutex);
@ -85,8 +108,9 @@ void Pseudoterminal::process_pty()
int ret; int ret;
int bytes_read = 0; int bytes_read = 0;
bytes_read = read(fd, read_buffer, 1); bytes_read = read(fd, read_buffer, MAX_READ_BUFFER_LENGTH);
// TODO: handle error (-1)
if (bytes_read <= 0) if (bytes_read <= 0)
continue; continue;
@ -112,19 +136,7 @@ void Pseudoterminal::process_pty()
if (bytes_read > 0) if (bytes_read > 0)
{ {
Godot::print(String("read {0} bytes").format(Array::make(bytes_read))); //Godot::print(String("read {0} bytes").format(Array::make(bytes_read)));
}
}
if (FD_ISSET(fd, &write_fds))
{
std::lock_guard<std::mutex> guard(write_buffer_mutex);
if (bytes_to_write > 0)
{
write(fd, write_buffer, bytes_to_write);
bytes_to_write = 0;
} }
} }
} }

View file

@ -13,15 +13,19 @@ class Pseudoterminal : public Node
{ {
GODOT_CLASS(Pseudoterminal, Node) GODOT_CLASS(Pseudoterminal, Node)
public:
static const int MAX_READ_BUFFER_LENGTH = 1024;
static const int MAX_WRITE_BUFFER_LENGTH = 1024;
private: private:
std::thread pty_thread; std::thread pty_thread;
bool should_process_pty; bool should_process_pty;
char write_buffer[4096]; char write_buffer[MAX_WRITE_BUFFER_LENGTH];
int bytes_to_write; int bytes_to_write;
std::mutex write_buffer_mutex; std::mutex write_buffer_mutex;
char read_buffer[4096]; char read_buffer[MAX_READ_BUFFER_LENGTH];
int bytes_to_read; int bytes_to_read;
std::mutex read_buffer_mutex; std::mutex read_buffer_mutex;

24
docker-compose.yml Normal file
View file

@ -0,0 +1,24 @@
version: "3.3"
services:
tests:
image: barichello/godot-ci:3.2.2
environment:
- RUN_FULL_CODEPOINT_TESTS=true
volumes:
- .:/src
command: godot --path /src -s addons/gut/gut_cmdln.gd
build-archlinux:
build:
context: .
dockerfile: ./dockerfiles/archlinux
command: bash /src/addons/godot_xterm_native/build.sh
build-nixos:
build:
context: .
dockerfile: ./dockerfiles/nixos
command: /src/addons/godot_xterm_native/build.sh
build-ubuntu:
build:
context: .
dockerfile: ./dockerfiles/ubuntu
command: bash /src/addons/godot_xterm_native/build.sh

3
dockerfiles/archlinux Normal file
View file

@ -0,0 +1,3 @@
FROM archlinux:20200908
RUN pacman -Sy --needed --noconfirm base-devel cmake scons
COPY . /src

2
dockerfiles/nixos Normal file
View file

@ -0,0 +1,2 @@
FROM lnl7/nix:2.3.6
COPY . /src

4
dockerfiles/ubuntu Normal file
View file

@ -0,0 +1,4 @@
FROM ubuntu:18.04
RUN apt-get update -y
RUN apt-get install -y build-essential cmake python3 scons
COPY . /src

View file

@ -15,7 +15,6 @@ onready var viewport = get_viewport()
func _ready(): func _ready():
$Pseudoterminal.connect("data_received", $Terminal, "write") $Pseudoterminal.connect("data_received", $Terminal, "write")
$Pseudoterminal.connect("data_received", self, "_on_data_received")
viewport.connect("size_changed", self, "_resize") viewport.connect("size_changed", self, "_resize")
_resize() _resize()
@ -51,10 +50,6 @@ func _input(event):
$Pseudoterminal.put_data(data) $Pseudoterminal.put_data(data)
func _on_data_received(data: PoolByteArray):
print("Got data: %s" % data.get_string_from_utf8())
func _resize(): func _resize():
rect_size = viewport.size rect_size = viewport.size
$Terminal.rect_size = rect_size $Terminal.rect_size = rect_size

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 4 KiB