├── .editorconfig ├── .gitignore ├── Makefile ├── README.md ├── data └── moonterm.desktop ├── libraries ├── LIP.lua └── utils.lua ├── moonterm.lua └── src ├── moonterm-app.lua ├── moonterm-dialog.lua ├── moonterm-keybinds.lua └── moonterm-menu.lua /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = false 10 | charset = utf-8 11 | 12 | # Matches multiple files with brace expansion notation 13 | # Set default charset 14 | [*.{js,lua,css}] 15 | charset = utf-8 16 | 17 | # 4 space indentation 18 | [*.{js,lua,css,html}] 19 | indent_style = tab 20 | indent_size = 4 21 | #indent_style = space 22 | 23 | # Tab indentation (no size specified) 24 | [Makefile] 25 | indent_style = tab 26 | 27 | 28 | # Matches the exact files either package.json or .travis.yml 29 | [{package.json,.travis.yml}] 30 | indent_style = space 31 | indent_size = 2 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | moonterm.geany 2 | /moonterm 3 | /moonterm.luastatic.c -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LUASTATIC ?= luastatic 2 | LUA ?= lua5.1 3 | LUA_INCLUDE ?= /usr/include/$(LUA) 4 | 5 | PREFIX ?= /usr/local 6 | BINDIR ?= $(PREFIX)/bin 7 | DESKTOP_DIR ?= $(PREFIX)/share/applications 8 | 9 | SRC = moonterm.lua src/moonterm-dialog.lua src/moonterm-app.lua \ 10 | src/moonterm-menu.lua src/moonterm-keybinds.lua libraries/LIP.lua \ 11 | libraries/utils.lua 12 | 13 | moonterm: 14 | $(LUASTATIC) $(SRC) -l$(LUA) -I$(LUA_INCLUDE) 15 | @strip moonterm 16 | 17 | install: 18 | install -Dm775 moonterm $(DESTDIR)$(BINDIR)/moonterm 19 | install -Dm775 data/moonterm.desktop $(DESTDIR)$(DESKTOP_DIR)/moonterm.desktop 20 | 21 | uninstall: 22 | rm -rf $(DESTDIR)$(BINDIR)/moonterm 23 | rm -rf $(DESTDIR)$(DESKTOP_DIR)/moonterm.desktop 24 | 25 | clean: 26 | rm -rf moonterm.luastatic.c 27 | rm -rf moonterm 28 | 29 | .PHONY: moonterm -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MoonTerm 2 | Moonterm is a minimalist and customizable terminal in lua 3 | 4 | ## Screenshot 5 | 6 | ![screenshot](https://github.com/sodomon2/project-screenshot/blob/master/moonterm/screenshot.png?raw=true) 7 | 8 | ## Installation 9 | > Note: in order to install `moonterm` you need to have [luastatic](https://github.com/ers35/luastatic) installed 10 | 11 | ``` 12 | git clone https://github.com/sodomon2/moonterm.git 13 | make moonterm 14 | [sudo] make install 15 | ``` 16 | 17 | ## Dependencies 18 | 19 | - [Lua](https://www.lua.org/download.html) 20 | - [Lua-lgi](https://github.com/pavouk/lgi) 21 | - [VTE](https://github.com/GNOME/vte) 22 | - [Keybinder](https://github.com/kupferlauncher/keybinder/) **optional: required by quake-mode** 23 | -------------------------------------------------------------------------------- /data/moonterm.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Moonterm 3 | Comment=Moonterm is a minimalist and customizable terminal in lua 4 | Exec=moonterm 5 | Icon=terminal 6 | Terminal=false 7 | Type=Application 8 | Categories=GTK;System;Utility;TerminalEmulator; -------------------------------------------------------------------------------- /libraries/LIP.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2012 Carreras Nicolas 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | --]] 22 | --- Lua INI Parser. 23 | -- It has never been that simple to use INI files with Lua. 24 | --@author Dynodzzo 25 | 26 | local LIP = {}; 27 | 28 | --- Returns a table containing all the data from the INI file. 29 | --@param fileName The name of the INI file to parse. [string] 30 | --@return The table containing all data from the INI file. [table] 31 | function LIP:load(fileName) 32 | assert(type(fileName) == 'string', 'Parameter "fileName" must be a string.'); 33 | local file = assert(io.open(fileName, 'r'), 'Error loading file : ' .. fileName); 34 | local data = {}; 35 | local section; 36 | for line in file:lines() do 37 | local tempSection = line:match('^%[([^%[%]]+)%]$'); 38 | if(tempSection)then 39 | section = tonumber(tempSection) and tonumber(tempSection) or tempSection; 40 | data[section] = data[section] or {}; 41 | end 42 | local param, value = line:match('^([%w|_]+)%s-=%s-(.+)$'); 43 | if(param and value ~= nil)then 44 | if(tonumber(value))then 45 | value = tonumber(value); 46 | elseif(value == 'true')then 47 | value = true; 48 | elseif(value == 'false')then 49 | value = false; 50 | end 51 | if(tonumber(param))then 52 | param = tonumber(param); 53 | end 54 | data[section][param] = value; 55 | end 56 | end 57 | file:close(); 58 | return data; 59 | end 60 | 61 | --- Saves all the data from a table to an INI file. 62 | --@param fileName The name of the INI file to fill. [string] 63 | --@param data The table containing all the data to store. [table] 64 | function LIP:save(fileName, data) 65 | assert(type(fileName) == 'string', 'Parameter "fileName" must be a string.'); 66 | assert(type(data) == 'table', 'Parameter "data" must be a table.'); 67 | local file = assert(io.open(fileName, 'w+b'), 'Error loading file :' .. fileName); 68 | local contents = ''; 69 | for section, param in pairs(data) do 70 | contents = contents .. ('[%s]\n'):format(section); 71 | for key, value in pairs(param) do 72 | contents = contents .. ('%s=%s\n'):format(key, tostring(value)); 73 | end 74 | contents = contents .. '\n'; 75 | end 76 | file:write(contents); 77 | file:close(); 78 | end 79 | 80 | return LIP; -------------------------------------------------------------------------------- /libraries/utils.lua: -------------------------------------------------------------------------------- 1 | --[[-- 2 | @package Moonterm 3 | @filename utils.lua 4 | @version 1.0 5 | @author Diaz Urbaneja Victor Diego Alejandro 6 | @date 01.08.2020 19:22:11 -04 7 | --]] 8 | 9 | local utils = {} 10 | 11 | function utils:create_config(dir,filename) 12 | local config_dir = ('%s/%s'):format(GLib.get_user_config_dir(), dir) 13 | local filename = ('%s/%s'):format(config_dir, filename) 14 | if not utils:isfile(filename) then 15 | os.execute( ('mkdir -p %s'):format(config_dir) ) 16 | text = ("[moonterm]\ninterpreter=%s\nquake_mode=false\n"):format(shell) 17 | file = assert(io.open(filename,'w'), 'Error loading file : ' .. filename) 18 | file:write(text) 19 | file:close() 20 | end 21 | end 22 | 23 | function utils:split(str,sep) 24 | local sep, fields = sep or ":", {} 25 | local pattern = string.format("([^%s]+)", sep) 26 | str:gsub(pattern, function(c) fields[#fields+1] = c end) 27 | return fields 28 | end 29 | 30 | function utils:isfile(file) 31 | return (io.open(tostring(file), "r") ~= nil) 32 | end 33 | 34 | function utils:path_name(uri) 35 | local uri = uri or '' 36 | local _turi = utils:split(uri,'/') 37 | local _name = _turi[#_turi] 38 | local _path = '/' .. table.concat(_turi, '/') 39 | table.remove(_turi,(#_turi)) 40 | result = { name = _name, path = _path } 41 | return result 42 | end 43 | 44 | function utils:print_r(t, fd) 45 | fd = fd or io.stdout 46 | local function print(str) str = str or "" fd:write(str.."\n") end 47 | local print_r_cache={} 48 | local function sub_print_r(t,indent) 49 | if (print_r_cache[tostring(t)]) then 50 | print(indent.."*"..tostring(t)) 51 | else 52 | print_r_cache[tostring(t)]=true 53 | if (type(t)=="table") then 54 | for pos,val in pairs(t) do 55 | if (type(val)=="table") then 56 | print(indent.."["..pos.."] => "..tostring(t).." {") 57 | sub_print_r(val,indent..string.rep(" ",string.len(pos)+8)) 58 | print(indent..string.rep(" ",string.len(pos)+6).."}") 59 | elseif (type(val)=="string") then 60 | print(indent.."["..pos..'] => "'..val..'"') 61 | else 62 | print(indent.."["..pos.."] => "..tostring(val)) 63 | end 64 | end 65 | else 66 | print(indent..tostring(t)) 67 | end 68 | end 69 | end 70 | if (type(t)=="table") then 71 | print(tostring(t).." {") 72 | sub_print_r(t," ") 73 | print("}") 74 | else 75 | sub_print_r(t," ") 76 | end 77 | print() 78 | end 79 | 80 | return utils 81 | -------------------------------------------------------------------------------- /moonterm.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | --[[-- 4 | @package MoonTerm 5 | @filename moonterm.lua 6 | @version 1.0 7 | @author Diaz Urbaneja Victor Diego Alejandro 8 | @date 16.01.2021 23:52:45 -04 9 | --]] 10 | 11 | shell = os.getenv("SHELL") or "/bin/sh" 12 | inifile = require("libraries.LIP") 13 | utils = require("libraries.utils") 14 | 15 | lgi = require("lgi") 16 | Gtk = lgi.require('Gtk', '3.0') 17 | Gdk = lgi.require('Gdk', '3.0') 18 | Vte = lgi.require('Vte', '2.91') 19 | GLib = lgi.require('GLib', '2.0') 20 | 21 | app = Gtk.Application() 22 | term = Vte.Terminal() 23 | 24 | utils:create_config('moonterm','moonterm.ini') 25 | dir = ('%s/moonterm'):format(GLib.get_user_config_dir()) 26 | conf = inifile:load(('%s/moonterm.ini'):format(dir)) 27 | 28 | if conf.moonterm.quake_mode == true then 29 | Keybinder = lgi.require('Keybinder', '3.0') 30 | end 31 | 32 | -- MoonTerm 33 | require('src.moonterm-app') 34 | require('src.moonterm-menu') 35 | require('src.moonterm-dialog') 36 | require('src.moonterm-keybinds') 37 | 38 | app:run() 39 | -------------------------------------------------------------------------------- /src/moonterm-app.lua: -------------------------------------------------------------------------------- 1 | --[[-- 2 | @package MoonTerm 3 | @filename moonterm-app.lua 4 | @version 1.0 5 | @author Diaz Urbaneja Victor Diego Alejandro 6 | @date 22.01.2021 01:34:58 -04 7 | --]] 8 | 9 | about_window = Gtk.AboutDialog ({ 10 | program_name = 'Moonterm', 11 | version = '3.0', 12 | copyright = 'Díaz Urbaneja Víctor Diego Alejandro\n Copyright © 2021-2022', 13 | comments = 'a minimalist and customizable terminal in lua', 14 | website = 'https://github.com/moonsteal/moonterm', 15 | website_label = 'Github', 16 | logo_icon_name = 'Terminal', 17 | authors = {'Díaz Urbaneja Víctor Diego Alejandro'} 18 | }) 19 | 20 | main_window = Gtk.Window { 21 | width_request = 600, 22 | height_request = 400, 23 | Gtk.ScrolledWindow{id = 'scroll'} 24 | } 25 | 26 | headerbar = Gtk.HeaderBar { 27 | title = 'MoonTerm', 28 | subtitle = 'a minimalist and customizable terminal in lua', 29 | show_close_button = true 30 | } 31 | 32 | interpreter = utils:path_name(conf.moonterm.interpreter)['name'] 33 | if conf.moonterm.interpreter == shell then 34 | headerbar.title = 'Moonterm' 35 | else 36 | headerbar.title = ('Moonterm - %s'):format(interpreter) 37 | headerbar.subtitle = ('Moonterm using : %s interpreter'):format(interpreter) 38 | end 39 | 40 | function term:on_child_exited() 41 | app:quit() 42 | end 43 | 44 | function app:on_activate() 45 | font = term:get_font() 46 | --font:set_family("Camingo Code") -- Fix error when " Camingo Code " font is not available 47 | font:set_size(font:get_size() * 1.1) 48 | 49 | term:spawn_sync( 50 | Vte.PtyFlags.DEFAULT, 51 | nil, 52 | { conf.moonterm.interpreter }, 53 | nil, 54 | GLib.SpawnFlags.DEFAULT, 55 | function() end 56 | ) 57 | dialog_config.child.entry_interpreter.text = conf.moonterm.interpreter 58 | main_window:set_titlebar(headerbar) 59 | main_window.child.scroll:add(term) 60 | main_window:set_icon_name('terminal') 61 | if arg[1] then term:feed_child_binary(arg[1] .. "\n") end 62 | if conf.moonterm.quake_mode == true then 63 | main_window.decorated = false 64 | main_window:resize(Gdk.Screen.width(), Gdk.Screen.height()*(50/100)) 65 | main_window:set_position(0) 66 | Keybinder.init() 67 | else 68 | main_window:show_all() 69 | end 70 | self:add_window(main_window) 71 | end 72 | 73 | -------------------------------------------------------------------------------- /src/moonterm-dialog.lua: -------------------------------------------------------------------------------- 1 | --[[-- 2 | @package MoonTerm 3 | @filename moonterm-dialog.lua 4 | @version 1.0 5 | @author Diaz Urbaneja Victor Diego Alejandro 6 | @date 17.01.2021 00:52:45 -04 7 | --]] 8 | 9 | dialog_config = Gtk.Dialog { 10 | title = "Preferences", 11 | resizable = false 12 | } 13 | 14 | content = Gtk.Box { 15 | orientation = 'VERTICAL', 16 | spacing = 5, 17 | border_width = 5, 18 | Gtk.Box { 19 | orientation = 'HORIZONTAL', 20 | Gtk.Label { 21 | label = " Interpreter : ", 22 | use_markup = true, 23 | }, 24 | Gtk.Entry { 25 | id = 'entry_interpreter' 26 | } 27 | }, 28 | Gtk.Box { 29 | orientation = 'HORIZONTAL', 30 | spacing = 5, 31 | border_width = 5, 32 | Gtk.Label { 33 | label = " Quake Mode : " 34 | }, 35 | Gtk.Switch { 36 | id = 'quake_switch' 37 | } 38 | }, 39 | Gtk.Box { 40 | orientation = 'HORIZONTAL', 41 | homogeneous = true, 42 | spacing = 5, 43 | Gtk.Button { 44 | id = 'btn_apply', 45 | label = "Apply", 46 | on_clicked = function () 47 | conf.moonterm.interpreter = content.child.entry_interpreter.text 48 | conf.moonterm.quake_mode = content.child.quake_switch:get_active() 49 | inifile:save(('%s/moonterm.ini'):format(dir), conf) 50 | dialog_config:hide() 51 | end 52 | }, 53 | Gtk.Button { 54 | id = 'btn_cancel', 55 | label = "Cancel", 56 | on_clicked = function () dialog_config:hide() end, 57 | } 58 | } 59 | } 60 | 61 | dialog_config:get_content_area():add(content) 62 | content.child.entry_interpreter:grab_focus() 63 | content.child.quake_switch:set_active(conf.moonterm.quake_mode) 64 | -------------------------------------------------------------------------------- /src/moonterm-keybinds.lua: -------------------------------------------------------------------------------- 1 | --[[-- 2 | @package MoonTerm 3 | @filename moonterm-keybinds.lua 4 | @version 1.0 5 | @author Díaz Urbaneja Víctor Eduardo Diex 6 | @date 26.01.2021 00:40:09 -04 7 | --]] 8 | 9 | function toggle_fullscreen() 10 | fullscreen = not fullscreen 11 | if ( fullscreen ) then 12 | main_window:fullscreen() 13 | else 14 | main_window:unfullscreen() 15 | end 16 | end 17 | 18 | function quake() 19 | if conf.moonterm.quake_mode == true then 20 | visible = not visible 21 | if visible then 22 | main_window:show_all() 23 | main_window.skip_taskbar_hint = true 24 | else 25 | main_window:hide() 26 | main_window.skip_taskbar_hint = false 27 | end 28 | end 29 | end 30 | 31 | if conf.moonterm.quake_mode == true then 32 | Keybinder.bind("F12",quake) 33 | end 34 | 35 | keybindings = { 36 | -- alphanumeric keys 37 | { 38 | [Gdk.KEY_C] = function () term:copy_clipboard() end, 39 | [Gdk.KEY_V] = function () term:paste_clipboard() end, 40 | [Gdk.KEY_Q] = function () app:quit() end 41 | }, 42 | -- function keys 43 | { 44 | [Gdk.KEY_F11] = function () toggle_fullscreen() end 45 | } 46 | } 47 | 48 | function main_window:on_key_press_event(event) 49 | local ctrl_on = event.state.CONTROL_MASK 50 | local shift_on = event.state.SHIFT_MASK 51 | alphanumeric_keys = keybindings[1][event.keyval] 52 | function_keys = keybindings[2][event.keyval] 53 | 54 | if ( alphanumeric_keys and shift_on and ctrl_on ) then 55 | alphanumeric_keys() 56 | elseif ( function_keys and not shift_on and not ctrl_on ) then 57 | function_keys() 58 | else 59 | return false 60 | end 61 | return true 62 | end 63 | -------------------------------------------------------------------------------- /src/moonterm-menu.lua: -------------------------------------------------------------------------------- 1 | --[[-- 2 | @package MoonTerm 3 | @filename moonterm-menu.lua 4 | @version 1.0 5 | @autor Diaz Urbaneja Victor Diego Alejandro 6 | @date 30.01.2021 19:54:09 -04 7 | ]] 8 | 9 | function main_window:on_button_press_event(event) 10 | if (event.type == 'BUTTON_PRESS' and event.button == 3) then 11 | menu = Gtk.Menu { 12 | Gtk.ImageMenuItem { 13 | label = "Copy", 14 | image = Gtk.Image { 15 | stock = "gtk-copy" 16 | }, 17 | on_activate = function() 18 | term:copy_clipboard() 19 | end 20 | }, 21 | Gtk.ImageMenuItem { 22 | label = "Paste", 23 | image = Gtk.Image { 24 | stock = "gtk-paste" 25 | }, 26 | on_activate = function() 27 | term:paste_clipboard() 28 | end 29 | }, 30 | Gtk.SeparatorMenuItem {}, 31 | Gtk.ImageMenuItem { 32 | label = "About Moonterm", 33 | image = Gtk.Image { 34 | stock = "gtk-about" 35 | }, 36 | on_activate = function() 37 | about_window:run() 38 | about_window:hide() 39 | end 40 | }, 41 | Gtk.ImageMenuItem { 42 | label = "Preferences", 43 | image = Gtk.Image { 44 | stock = "gtk-preferences" 45 | }, 46 | on_activate = function() 47 | dialog_config:show_all() 48 | end 49 | }, 50 | Gtk.SeparatorMenuItem {}, 51 | Gtk.ImageMenuItem { 52 | label = "Quit", 53 | image = Gtk.Image { 54 | stock = "gtk-quit" 55 | }, 56 | on_activate = function() 57 | app:quit() 58 | end 59 | } 60 | } 61 | menu:attach_to_widget(main_window, null) 62 | menu:show_all() 63 | menu:popup(nil, nil, nil, event.button, event.time) 64 | end 65 | end 66 | --------------------------------------------------------------------------------