├── .gitpod.yml
├── .gitpod
├── Dockerfile
├── novnc-index.html
└── start-vnc-session.sh
├── GtkApplication.lua
├── GtkApplicationWindow.lua
├── GtkBox1.lua
├── GtkBuilder-02.ui
├── GtkBuilder.ui
├── GtkBuilder1.lua
├── GtkBuilder2.lua
├── GtkButton1.lua
├── GtkButton2.lua
├── GtkButton3.lua
├── GtkComboBox.lua
├── GtkComboBoxText.lua
├── GtkCssProvider1.lua
├── GtkDialog1.lua
├── GtkDialog2.lua
├── GtkEntry.lua
├── GtkEntryCompletion.lua
├── GtkExpander.lua
├── GtkFileChooserButton.lua
├── GtkFileChooserDialog.lua
├── GtkFixed.lua
├── GtkFlowBox.lua
├── GtkGrid-Explained.png
├── GtkGrid1.lua
├── GtkGrid2.lua
├── GtkGrid3.lua
├── GtkHeaderBar.lua
├── GtkImage1.lua
├── GtkImage2.lua
├── GtkInfoBar.lua
├── GtkLabel1.lua
├── GtkLabel2.lua
├── GtkListBox1.lua
├── GtkMessageDialog.lua
├── GtkMessageDialog2.lua
├── GtkNotebook.lua
├── GtkOverlay.lua
├── GtkPaned.lua
├── GtkRevealer.lua
├── GtkScrolledWindow.lua
├── GtkSpinner.lua
├── GtkStack1.lua
├── GtkStack2.lua
├── GtkStatusbar.lua
├── GtkWindow.lua
├── LICENSE
├── Moonsteal-Logo.jpg
├── README.md
├── action-bar.lua
├── mobdebug.lua
└── styles
└── GtkCssProvider1.css
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | image:
2 | file: .gitpod/Dockerfile
3 |
4 | tasks:
5 | - name: Setup workspace
6 | command: |
7 | sudo apt update -q
8 | sudo apt upgrade -yq
9 | sudo apt install -yq libgtk-3-dev lua5.1 lua-lgi
10 |
11 | vscode:
12 | extensions:
13 | - sumneko.lua
--------------------------------------------------------------------------------
/.gitpod/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM gitpod/workspace-base:latest
2 |
3 | # Install Xvfb, JavaFX-helpers, Openbox window manager and emacs GTK
4 | RUN sudo install-packages xvfb x11vnc xterm openjfx libopenjfx-java openbox
5 |
6 | # Overwrite this env variable to use a different window manager
7 | ENV WINDOW_MANAGER="openbox"
8 |
9 | USER root
10 |
11 | # Install novnc
12 | RUN git clone --depth 1 https://github.com/novnc/noVNC.git /opt/novnc \
13 | && git clone --depth 1 https://github.com/novnc/websockify /opt/novnc/utils/websockify
14 | COPY .gitpod/novnc-index.html /opt/novnc/index.html
15 |
16 | # Add VNC startup script
17 | COPY .gitpod/start-vnc-session.sh /usr/bin/
18 | RUN chmod +x /usr/bin/start-vnc-session.sh
19 |
20 | # This is a bit of a hack. At the moment we have no means of starting background
21 | # tasks from a Dockerfile. This workaround checks, on each bashrc eval, if the X
22 | # server is running on screen 0, and if not starts Xvfb, x11vnc and novnc.
23 | RUN echo "export DISPLAY=:0" >> ~/.bashrc
24 | RUN echo "[ ! -e /tmp/.X0-lock ] && (/usr/bin/start-vnc-session.sh &> /tmp/display-\${DISPLAY}.log)" >> ~/.bashrc
25 |
26 | ### checks ###
27 | # no root-owned files in the home directory
28 | RUN notOwnedFile=$(find . -not "(" -user gitpod -and -group gitpod ")" -print -quit) \
29 | && { [ -z "$notOwnedFile" ] \
30 | || { echo "Error: not all files/dirs in $HOME are owned by 'gitpod' user & group"; exit 1; } }
31 |
32 | USER gitpod
--------------------------------------------------------------------------------
/.gitpod/novnc-index.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/.gitpod/start-vnc-session.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # see https://en.wikipedia.org/wiki/Xvfb#Remote_control_over_SSH
4 |
5 | DISP=${DISPLAY:1}
6 |
7 | Xvfb -screen "$DISP" "${CUSTOM_XVFB_WxHxD:=1280x720x16}" -ac -pn -noreset &
8 |
9 | $WINDOW_MANAGER &
10 |
11 | VNC_PORT=$((5900 + "$DISP"))
12 | NOVNC_PORT=$((6080 + "$DISP"))
13 |
14 | x11vnc -localhost -shared -display :"$DISP" -forever -rfbport ${VNC_PORT} -bg -o "/tmp/x11vnc-${DISP}.log"
15 | cd /opt/novnc/utils && ./novnc_proxy --vnc "localhost:${VNC_PORT}" --listen "${NOVNC_PORT}" &
--------------------------------------------------------------------------------
/GtkApplication.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | --[[ GtkApplication:
5 |
6 | Provides an application interface. I recommend you to read the
7 | info in the links below.
8 |
9 | ]]
10 | local App = Gtk.Application({
11 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkApplication"
12 | })
13 |
14 | -- For more info about these signals, see this:
15 | -- https://wiki.gnome.org/HowDoI/GtkApplication
16 | -- https://developer.gnome.org/gio/stable/GApplication.html
17 | -- https://developer.gnome.org/gtk3/stable/GtkApplication.html
18 | function App:on_startup()
19 | print("Application::startup called")
20 | end
21 |
22 | function App:on_activate()
23 | print("Application::activate called")
24 | end
25 |
26 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkApplicationWindow.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkApplicationWindow"
6 | })
7 |
8 | function App:on_startup()
9 | --[[ GtkApplicationWindow:
10 |
11 | A GtkWindow thats has a better integration with GtkApplication
12 |
13 | ]]
14 | Gtk.ApplicationWindow({
15 | application = self,
16 | default_width = 400,
17 | default_height = 400
18 | })
19 | end
20 |
21 | function App:on_activate()
22 | self.active_window:present()
23 | end
24 |
25 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkBox1.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkBox1"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400
13 | })
14 | end
15 |
16 | function App:on_activate()
17 | self.active_window:set_titlebar(Gtk.HeaderBar({
18 | visible = true,
19 | show_close_button = true,
20 | title = "GtkBox",
21 | subtitle = "Example 1"
22 | }))
23 |
24 | --[[ GtkBox:
25 |
26 | A container thats organize widgets lineal in one of these directions:
27 | - Vertical
28 | - Horizontal
29 |
30 | ]]
31 | local Box = Gtk.Box({
32 | visible = true,
33 | orientation = Gtk.Orientation.VERTICAL,
34 | spacing = 10,
35 | valign = Gtk.Align.CENTER
36 | })
37 |
38 | Box:pack_start(
39 | Gtk.Label({ visible = true, label = "Label 1" }),
40 | false,
41 | true,
42 | 0
43 | )
44 |
45 | Box:pack_start(
46 | Gtk.Label({ visible = true, label = "Label 2" }),
47 | false,
48 | true,
49 | 0
50 | )
51 |
52 | self.active_window:add(Box)
53 | self.active_window:present()
54 | end
55 |
56 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkBuilder-02.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
220 |
221 |
--------------------------------------------------------------------------------
/GtkBuilder.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | center
8 | 400
9 | 400
10 | center
11 |
12 |
13 |
14 |
15 | True
16 | True
17 | Here is the title
18 | And here is the subtitle
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | True
27 | Hello, world!
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/GtkBuilder1.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | --[[ GtkBuilder:
5 |
6 | A mechanism provided by Gtk that allows build user interfaces
7 | using a XML definition. That XML definition can be in a file
8 | or an string of the programming language that you use.
9 |
10 | This can be very helpful, because you don't need create the
11 | entire UI from code, instead, you can use a XML definition
12 | of your app UI and in the code side, you write the whatever
13 | your app will do.
14 |
15 | ]]
16 | local Builder = Gtk.Builder.new_from_file("GtkBuilder.ui")
17 |
18 | local App = Gtk.Application({
19 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkBuilder1"
20 | })
21 |
22 | function App:on_startup()
23 | self:add_window(Builder:get_object("Window"))
24 | end
25 |
26 | function App:on_activate()
27 | self.active_window:present()
28 | end
29 |
30 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkBuilder2.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local Builder = Gtk.Builder.new_from_file("GtkBuilder.ui")
5 |
6 | -- Another way to use GtkBuilder in LGI
7 | local UI = Builder.objects
8 | local App = Gtk.Application({
9 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkBuilder2"
10 | })
11 |
12 | function App:on_startup()
13 | self:add_window(UI.Window)
14 | end
15 |
16 | function App:on_activate()
17 | self.active_window:present()
18 | end
19 |
20 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkButton1.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 | local Gio = lgi.require("Gio", "2.0")
4 | local GLib = lgi.require("GLib", "2.0")
5 |
6 | local App = Gtk.Application({
7 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkButton1"
8 | })
9 |
10 | function App:on_startup()
11 | Gtk.ApplicationWindow({
12 | application = self,
13 | default_width = 400,
14 | default_height = 400
15 | })
16 | end
17 |
18 | function App:on_activate()
19 | self.active_window:set_titlebar(Gtk.HeaderBar({
20 | visible = true,
21 | show_close_button = true,
22 | title = "GtkButton",
23 | subtitle = "Example 1"
24 | }))
25 |
26 | --[[ GtkButton:
27 |
28 | Just a button lol
29 |
30 | ]]
31 | local Button = Gtk.Button({
32 | visible = true,
33 | label = "Click me!",
34 | valign = Gtk.Align.CENTER,
35 | halign = Gtk.Align.CENTER
36 | })
37 |
38 | local Notification_ID = 0
39 |
40 | --for k, v in pairs(GLib:_resolve(true)._struct) do print(k, v) end
41 |
42 | -- The "clicked" signal is emited when the user clicks
43 | -- the button
44 | function Button:on_clicked()
45 | -- Notifications are not yet supported on Windows
46 | if GLib.get_os_info("ID") == "windows" then
47 | print(("Clicked %d times!"):format(Notification_ID))
48 | else
49 | local Notification = Gio.Notification()
50 | Notification:set_title("GtkButton example 1")
51 | Notification:set_body("You cliked the button!")
52 |
53 | -- 'self' can't be used here, because 'self' here
54 | -- reffers to the GtkButton that's clicked
55 | App:send_notification(Notification_ID, Notification)
56 | end
57 |
58 | -- Incrementing this value makes the app spawn various
59 | -- notifications
60 | Notification_ID = Notification_ID + 1
61 | end
62 |
63 | self.active_window:add(Button)
64 | self.active_window:present()
65 | end
66 |
67 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkButton2.lua:
--------------------------------------------------------------------------------
1 |
2 | if arg[#arg] == "-debug" then require"mobdebug".start() end
3 |
4 | local lgi = require"lgi"
5 | local Gtk = lgi.require("Gtk", "3.0")
6 |
7 | local App = Gtk.Application{
8 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkButton2"
9 | }
10 |
11 | function App:on_startup()
12 | Gtk.ApplicationWindow{
13 | application = self,
14 | default_width = 400,
15 | default_height = 400
16 | }
17 | end
18 |
19 | function App:on_activate()
20 | local window = self.active_window
21 |
22 | window:set_titlebar(Gtk.HeaderBar{
23 | visible = true,
24 | show_close_button = true,
25 | title = "GtkButton",
26 | subtitle = "Example 2"
27 | })
28 |
29 | local Button = Gtk.Button{
30 | visible = true,
31 | label = "Click me!",
32 | valign = Gtk.Align.CENTER,
33 | halign = Gtk.Align.CENTER
34 | }
35 |
36 | local Notification_ID = 1
37 |
38 | function Button:on_clicked()
39 |
40 | local label = ("Clicked %d times!"):format(Notification_ID)
41 | self.label = label
42 | print(label)
43 |
44 | if Notification_ID >= 3 then window:destroy() end
45 |
46 | Notification_ID = Notification_ID + 1
47 |
48 | end
49 |
50 | window:add(Button)
51 | window:present()
52 | end
53 |
54 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkButton3.lua:
--------------------------------------------------------------------------------
1 |
2 | if arg[#arg] == "-debug" then require"mobdebug".start() end
3 |
4 | local lgi = require"lgi"
5 | local Gtk = lgi.require("Gtk", "3.0")
6 |
7 | local Builder = Gtk.Builder.new_from_file("GtkBuilder-02.ui")
8 | local UI = Builder.objects
9 |
10 | local App = Gtk.Application{
11 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkButton3"
12 | }
13 |
14 | function App:on_startup()
15 | self:add_window(UI.Window)
16 | end
17 |
18 | function grid_click(button)
19 | local counter = 1
20 | function button:on_clicked()
21 | local label_parent = self.child[1] or self
22 | label_parent.label = counter
23 | counter = counter + 1
24 | end
25 | end
26 |
27 | function App:on_activate()
28 | local window = self.active_window
29 | for row=1,3 do
30 | for col=1,3 do
31 | local name="button"..row..col
32 | grid_click(UI[name])
33 | end
34 | end
35 | window:present()
36 | end
37 |
38 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkComboBox.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 | local GObject = lgi.require("GObject", "2.0")
4 |
5 | local App = Gtk.Application({
6 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkComboBox"
7 | })
8 |
9 | function App:on_startup()
10 | Gtk.ApplicationWindow({
11 | application = self,
12 | default_width = 400,
13 | default_height = 400,
14 | border_width = 10
15 | })
16 | end
17 |
18 | function App:on_activate()
19 | self.active_window:set_titlebar(Gtk.HeaderBar({
20 | visible = true,
21 | show_close_button = true,
22 | title = "GtkComboBox"
23 | }))
24 |
25 | -- Model for the combo box
26 | local Model = Gtk.ListStore.new({ GObject.Type.STRING })
27 | local Items = { "GNOME", "KDE Plasma", "XFCE", "MATE", "Cinnamon", "Pantheon", "LXDE", "LXQT" }
28 |
29 | -- Add the items to the model
30 | for _, Name in ipairs(Items) do
31 | Model:append({ Name })
32 | end
33 |
34 | -- Label to be updated
35 | local Label = Gtk.Label({ visible = true, label = "Default option: 0" })
36 |
37 | --[[ GtkComboBox:
38 |
39 | A container that allows the user to select "something" among several
40 | valid options.
41 |
42 | ]]
43 | local Combo = Gtk.ComboBox({
44 | visible = true,
45 | model = Model,
46 | active = 0,
47 | cells = {
48 | {
49 | Gtk.CellRendererText(),
50 | { text = 1 },
51 | align = Gtk.Align.START
52 | }
53 | }
54 | })
55 |
56 | -- Changes the 'Label' text when user change the combo box value
57 | function Combo:on_changed()
58 | local n = self:get_active()
59 | Label.label = "Option "..n.." selected ("..Items[n + 1]..")"
60 | end
61 |
62 | local Box = Gtk.Box({
63 | visible = true,
64 | orientation = Gtk.Orientation.VERTICAL,
65 | spacing = 10,
66 | halign = Gtk.Align.CENTER,
67 | valign = Gtk.Align.CENTER,
68 |
69 | Gtk.Label({ visible = true, label = "Select an option" }),
70 | Combo,
71 | Label
72 | })
73 |
74 | self.active_window:add(Box)
75 | self.active_window:present()
76 | end
77 |
78 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkComboBoxText.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 | local GObject = lgi.require("GObject", "2.0")
4 |
5 | local App = Gtk.Application({
6 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkComboBoxText"
7 | })
8 |
9 | function App:on_startup()
10 | Gtk.ApplicationWindow({
11 | application = self,
12 | default_width = 400,
13 | default_height = 400,
14 | border_width = 10
15 | })
16 | end
17 |
18 | function App:on_activate()
19 | self.active_window:set_titlebar(Gtk.HeaderBar({
20 | visible = true,
21 | show_close_button = true,
22 | title = "GtkComboBoxText"
23 | }))
24 |
25 | -- Label to be updated
26 | local Label = Gtk.Label({ visible = true, label = "Default id: gnome" })
27 |
28 | --[[ GtkComboBoxText:
29 |
30 | Just a text-only GtkComboBox
31 |
32 | ]]
33 | local Combo = Gtk.ComboBoxText({ visible = true })
34 |
35 | local Items = {
36 | gnome = "GNOME",
37 | plasma = "KDE Plasma",
38 | xfce = "XFCE",
39 | mate = "MATE",
40 | cinnamon = "Cinnamon",
41 | pantheon = "Pantheon",
42 | lxde = "LXDE",
43 | lxqt = "LXQT"
44 | }
45 |
46 | for ID, Value in pairs(Items) do
47 | Combo:append(ID, Value)
48 | end
49 |
50 | Combo.active_id = "gnome"
51 |
52 | -- Changes the 'Label' text when user change the combo box value
53 | function Combo:on_changed()
54 | Label.label = "Option id: " .. self:get_active_id()
55 | end
56 |
57 | local Box = Gtk.Box({
58 | visible = true,
59 | orientation = Gtk.Orientation.VERTICAL,
60 | spacing = 10,
61 | halign = Gtk.Align.CENTER,
62 | valign = Gtk.Align.CENTER,
63 |
64 | Gtk.Label({ visible = true, label = "Select an option" }),
65 | Combo,
66 | Label
67 | })
68 |
69 | self.active_window:add(Box)
70 | self.active_window:present()
71 | end
72 |
73 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkCssProvider1.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | @author Díaz Urbaneja Víctor Eduardo Diex
3 | @date 28.02.2021 02:26:39 -04
4 | ]]
5 |
6 | local lgi = require('lgi')
7 | local Gtk = lgi.require('Gtk', '3.0')
8 | local Gdk = lgi.require('Gdk', '3.0')
9 | assert = lgi.assert -- With this function I will confirm if a file (in this case custom.css) exists.
10 |
11 | --- I load my css
12 | local Provider = Gtk.CssProvider()
13 |
14 | -- Show a message if custom.css does not exist
15 | assert(Provider:load_from_path('styles/GtkCssProvider1.css'), 'ERROR: file.css not found')
16 |
17 | --- I add my CSS to the current window
18 | local Screen = Gdk.Display.get_default_screen(Gdk.Display:get_default())
19 | local GTK_STYLE_PROVIDER_PRIORITY_APPLICATION = 600
20 | Gtk.StyleContext.add_provider_for_screen(
21 | Screen, Provider,
22 | GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
23 | )
24 |
25 | local Window = Gtk.Window {
26 | title = 'Linking CSS Styles',
27 | width = 200,
28 | height = 200,
29 | window_position = Gtk.WindowPosition.CENTER,
30 | {
31 | Gtk.Button {
32 | halign = Gtk.Align.CENTER,
33 | valign = Gtk.Align.CENTER,
34 | label = 'Example of the button with css',
35 | on_clicked = function (self)
36 | self:get_style_context():add_class('red')
37 | end
38 | }
39 | },
40 | on_destroy = function()
41 | Gtk.main_quit()
42 | end
43 | }
44 |
45 | Window:show_all()
46 | Gtk.main()
47 |
--------------------------------------------------------------------------------
/GtkDialog1.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 | local Pango = lgi.require("Pango", "1.0")
4 |
5 | local App = Gtk.Application({
6 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkDialog1"
7 | })
8 |
9 | function App:on_startup()
10 | --[[ GtkDialog:
11 |
12 | A (generic) popup window
13 |
14 | ]]
15 | local Dialog = Gtk.Dialog({
16 | application = self,
17 | default_width = 400,
18 | border_width = 10,
19 | title = "GtkDialog - Example 1"
20 | })
21 |
22 | Dialog:add_button("Yes 👍", Gtk.ResponseType.OK)
23 | Dialog:add_button("No 🛑", Gtk.ResponseType.CANCEL)
24 |
25 | local TitleText = [[Universe destruction]]
26 | local Title = Gtk.Label({ visible = true, label = TitleText, use_markup = true })
27 |
28 | local SummaryText = "Our universe has a lot of problems and the only way to fix it is destroying the entire universe and this important decision is now in your hands."
29 | local Summary = Gtk.Label({
30 | visible = true,
31 | label = SummaryText,
32 | xalign = 0,
33 | wrap = true,
34 | wrap_mode = Pango.WrapMode.CHAR
35 | })
36 |
37 | local EpilogText = [[Do you accept?]]
38 | local Epilog = Gtk.Label({ visible = true, label = EpilogText, use_markup = true })
39 |
40 | local Content = Dialog:get_content_area()
41 | Content.spacing = 10
42 | Content:pack_start(Title, false, true, 0)
43 | Content:pack_start(Summary, false, true, 0)
44 | Content:pack_start(Epilog, false, true, 10)
45 |
46 | self:add_window(Dialog)
47 | end
48 |
49 | function App:on_activate()
50 | -- When you work with dialogs, use this instead of 'present()'
51 | local Response = self.active_window:run()
52 |
53 | if Response == Gtk.ResponseType.OK then
54 | self.active_window:destroy()
55 | print("Universe destroyed! 💥")
56 | elseif Response == Gtk.ResponseType.CANCEL then
57 | self.active_window:destroy()
58 | print("Universe is in peace now! 🙏")
59 | else
60 | self.active_window:destroy()
61 | print("Nothing happens! 🤔")
62 | end
63 | end
64 |
65 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkDialog2.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 | local Pango = lgi.require("Pango", "1.0")
4 |
5 | local App = Gtk.Application({
6 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkDialog2"
7 | })
8 |
9 | function App:on_startup()
10 | --[[ GtkDialog:
11 |
12 | A (generic) popup window
13 |
14 | ]]
15 | local Dialog = Gtk.Dialog({
16 | application = self,
17 | use_header_bar = 1,
18 | default_width = 400,
19 | border_width = 10
20 | })
21 |
22 | Dialog:add_button("Yes 👍", Gtk.ResponseType.OK)
23 | Dialog:add_button("No 🛑", Gtk.ResponseType.CANCEL)
24 |
25 | local Header = Dialog:get_header_bar()
26 | Header.title = "GtkDialog"
27 | Header.subtitle = "Example 2"
28 |
29 | local TitleText = [[Universe destruction]]
30 | local Title = Gtk.Label({ visible = true, label = TitleText, use_markup = true })
31 |
32 | local SummaryText = "Our universe has a lot of problems and the only way to fix it is destroying the entire universe and this important decision is now in your hands."
33 | local Summary = Gtk.Label({
34 | visible = true,
35 | label = SummaryText,
36 | xalign = 0,
37 | wrap = true,
38 | wrap_mode = Pango.WrapMode.CHAR
39 | })
40 |
41 | local EpilogText = [[Do you accept?]]
42 | local Epilog = Gtk.Label({ visible = true, label = EpilogText, use_markup = true })
43 |
44 | local Content = Dialog:get_content_area()
45 | Content.spacing = 10
46 | Content:pack_start(Title, false, true, 0)
47 | Content:pack_start(Summary, false, true, 0)
48 | Content:pack_start(Epilog, false, true, 10)
49 |
50 | self:add_window(Dialog)
51 | end
52 |
53 | function App:on_activate()
54 | -- When you work with dialogs, use this instead of 'present()'
55 | local Response = self.active_window:run()
56 |
57 | if Response == Gtk.ResponseType.OK then
58 | self.active_window:destroy()
59 | print("Universe destroyed! 💥")
60 | elseif Response == Gtk.ResponseType.CANCEL then
61 | self.active_window:destroy()
62 | print("Universe is in peace now! 🙏")
63 | else
64 | self.active_window:destroy()
65 | print("Nothing happens! 🤔")
66 | end
67 | end
68 |
69 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkEntry.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 | local GObject = lgi.require("GObject", "2.0")
4 |
5 | local App = Gtk.Application({
6 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkEntry"
7 | })
8 |
9 | function App:on_startup()
10 | Gtk.ApplicationWindow({
11 | application = self,
12 | default_width = 400,
13 | default_height = 400,
14 | border_width = 10
15 | })
16 | end
17 |
18 | function App:on_activate()
19 | self.active_window:set_titlebar(Gtk.HeaderBar({
20 | visible = true,
21 | show_close_button = true,
22 | title = "GtkEntry"
23 | }))
24 |
25 | -- Label to be updated
26 | local Label = Gtk.Label({ visible = true })
27 |
28 | --[[ GtkEntry:
29 |
30 | An input widget
31 |
32 | ]]
33 | local Entry = Gtk.Entry({ visible = true })
34 |
35 | -- Updates the label text while typing
36 | function Entry:on_key_release_event()
37 | Label.label = Entry.text
38 | end
39 |
40 | local Box = Gtk.Box({
41 | visible = true,
42 | orientation = Gtk.Orientation.VERTICAL,
43 | spacing = 10,
44 | halign = Gtk.Align.CENTER,
45 | valign = Gtk.Align.CENTER,
46 |
47 | Gtk.Label({ visible = true, label = "Enter some text" }),
48 | Entry,
49 | Label
50 | })
51 |
52 | self.active_window:add(Box)
53 | self.active_window:present()
54 | end
55 |
56 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkEntryCompletion.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 | local GObject = lgi.require("GObject", "2.0")
4 |
5 | local App = Gtk.Application({
6 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkEntryCompletion"
7 | })
8 |
9 | function App:on_startup()
10 | Gtk.ApplicationWindow({
11 | application = self,
12 | default_width = 400,
13 | default_height = 400,
14 | border_width = 10
15 | })
16 | end
17 |
18 | function App:on_activate()
19 | self.active_window:set_titlebar(Gtk.HeaderBar({
20 | visible = true,
21 | show_close_button = true,
22 | title = "GtkEntryCompletion"
23 | }))
24 |
25 | -- Model for the entry completion
26 | local Model = Gtk.ListStore.new({ GObject.Type.STRING })
27 |
28 | -- Items to be completed
29 | local Items = { "GNOME", "Lua", "LGI", "GTK", "Moonsteal", "Example" }
30 |
31 | -- Add the items to the model
32 | for _, Name in ipairs(Items) do Model:append({ Name }) end
33 |
34 | -- The entry completion
35 | local Completion = Gtk.EntryCompletion({
36 | model = Model,
37 | text_column = 0,
38 | popup_completion = true
39 | })
40 |
41 | -- The entry
42 | local Entry = Gtk.Entry({ visible = true, completion = Completion })
43 |
44 | local Box = Gtk.Box({
45 | visible = true,
46 | orientation = Gtk.Orientation.VERTICAL,
47 | spacing = 10,
48 | halign = Gtk.Align.CENTER,
49 | valign = Gtk.Align.CENTER,
50 |
51 | Gtk.Label({ visible = true, label = "Try \"gnome\" or \"gtk\"" }),
52 | Entry
53 | })
54 |
55 | self.active_window:add(Box)
56 | self.active_window:present()
57 | end
58 |
59 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkExpander.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkExpander"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | border_width = 10,
13 | resizable = false
14 | })
15 | end
16 |
17 | function App:on_activate()
18 | self.active_window:set_titlebar(Gtk.HeaderBar({
19 | visible = true,
20 | show_close_button = true,
21 | title = "Your app title",
22 | subtitle = "Your app subtitle"
23 | }))
24 |
25 | local LoremIpsum = [[Duis in metus eros. Duis faucibus rutrum eros eu vestibulum.
26 | Proin et arcu nulla. Etiam at lacinia nibh. Vivamus pellentesque nunc nibh,
27 | ac dignissim massa lobortis ut. Integer eu felis in elit semper ullamcorper
28 | at in ipsum. Suspendisse tempus massa vel nibh tristique vestibulum.
29 | Vestibulum varius eu nunc eu interdum. Curabitur pulvinar velit in purus
30 | facilisis, et auctor augue consequat. Donec finibus felis ligula, a convallis
31 | justo tristique a.]]
32 |
33 | --[[ GtkExpander:
34 |
35 | A widget that allows hide its child, like a GtkRevealer, but you don't need to
36 | show/hide the child programmatically and has a small "hint" that there is
37 | "something" hidden.
38 |
39 | ]]
40 | local Expand = Gtk.Expander({
41 | visible = true,
42 | label = [[ Lorem ipsum ]],
43 | use_markup = true,
44 | resize_toplevel = true,
45 |
46 | Gtk.Label({ visible = true, label = LoremIpsum, wrap = true })
47 | })
48 |
49 | self.active_window:add(Expand)
50 | self.active_window:present()
51 | end
52 |
53 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkFileChooserButton.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkFileChooserButton"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400,
13 | border_width = 10
14 | })
15 | end
16 |
17 | function App:on_activate()
18 | self.active_window:set_titlebar(Gtk.HeaderBar({
19 | visible = true,
20 | show_close_button = true,
21 | title = "GtkFileChooserButton"
22 | }))
23 |
24 | local Box = Gtk.Box({
25 | visible = true,
26 | spacing = 10,
27 | orientation = Gtk.Orientation.VERTICAL,
28 | valign = Gtk.Align.CENTER,
29 | halign = Gtk.Align.CENTER,
30 |
31 | Gtk.Label({ visible = true, label = "Open a file" }),
32 | })
33 |
34 | --[[ GtkFileChooserButton:
35 |
36 | A button that exclusively opens a file selection dialog
37 |
38 | ]]
39 | local Btn = Gtk.FileChooserButton({
40 | visible = true,
41 | valign = Gtk.Align.CENTER,
42 | halign = Gtk.Align.CENTER
43 | })
44 |
45 | local Lbl = Gtk.Label({ visible = true, wrap = true })
46 |
47 | function Btn:on_file_set()
48 | Lbl.label = "Selected file: " .. self:get_filename()
49 | end
50 |
51 | Box:pack_start(Btn, false, true, 0)
52 | Box:pack_start(Lbl, false, true, 0)
53 |
54 | self.active_window:add(Box)
55 | self.active_window:present()
56 | end
57 |
58 | return App:run()
--------------------------------------------------------------------------------
/GtkFileChooserDialog.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkFileChooserDialog"
6 | })
7 |
8 | function App:on_startup()
9 | --[[ GtkFileChooserDialog:
10 |
11 | A file selection dialog
12 |
13 | ]]
14 | local Dialog = Gtk.FileChooserDialog({
15 | title = "Select a file",
16 | action = Gtk.FileChooserAction.OPEN
17 | })
18 |
19 | Dialog:add_button("Open", Gtk.ResponseType.OK)
20 | Dialog:add_button("Cancel", Gtk.ResponseType.CANCEL)
21 |
22 | self:add_window(Dialog)
23 | end
24 |
25 | function App:on_activate()
26 | local Res = self.active_window:run()
27 |
28 | if Res == Gtk.ResponseType.OK then
29 | local name = self.active_window:get_filename()
30 | self.active_window:destroy()
31 | print("You selected: " .. name)
32 | elseif Res == Gtk.ResponseType.CANCEL then
33 | self.active_window:destroy()
34 | print("Canceled")
35 | else
36 | self.active_window:destroy()
37 | print("Something else")
38 | end
39 | end
40 |
41 | return App:run()
--------------------------------------------------------------------------------
/GtkFixed.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local app = Gtk.Application { application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkFixed" }
5 |
6 | function app:on_activate()
7 | self.active_window:present()
8 | end
9 |
10 | function app:on_startup()
11 | local win = Gtk.ApplicationWindow {
12 | application = self,
13 | default_width = 400,
14 | default_height = 400,
15 | border_width = 10
16 | }
17 |
18 | win:set_titlebar(Gtk.HeaderBar {
19 | visible = true,
20 | show_close_button = true,
21 | title = "GtkFlowBox"
22 | })
23 |
24 | local fixed = Gtk.Fixed {
25 | visible = true,
26 |
27 | { Gtk.Label { visible = true, label = "A" }, y = 20, x = 10 },
28 | { Gtk.Label { visible = true, label = "B" }, y = 200, x = 100 },
29 | { Gtk.Label { visible = true, label = "C" }, y = 99, x = 326 }
30 | }
31 |
32 | win:add(fixed)
33 | end
34 |
35 | return app:run(arg)
--------------------------------------------------------------------------------
/GtkFlowBox.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local app = Gtk.Application { application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkFlowBox" }
5 |
6 | function app:on_activate()
7 | self.active_window:present()
8 | end
9 |
10 | function app:on_startup()
11 | local win = Gtk.ApplicationWindow {
12 | application = self,
13 | default_width = 400,
14 | default_height = 400,
15 | border_width = 10
16 | }
17 |
18 | win:set_titlebar(Gtk.HeaderBar {
19 | visible = true,
20 | show_close_button = true,
21 | title = "GtkFlowBox"
22 | })
23 |
24 | local flowbox = Gtk.FlowBox {
25 | visible = true,
26 | valign = Gtk.Align.START,
27 | selection_mode = Gtk.SelectionMode.NONE,
28 | max_children_per_line = 30
29 | }
30 |
31 | local scroll = Gtk.ScrolledWindow {
32 | visible = true,
33 | hscrollbar_policy = Gtk.PolicyType.NEVER
34 | }
35 |
36 | local icons = {
37 | "face-angry",
38 | "face-surprise",
39 | "face-laugh",
40 | "face-plain",
41 | "face-sad",
42 | "face-cool",
43 | "face-smirk",
44 | "face-sick",
45 | "face-kiss",
46 | "face-smile"
47 | }
48 |
49 | math.randomseed(os.time())
50 |
51 | for i = 0, 399 do
52 | flowbox:insert(Gtk.Image {
53 | visible = true,
54 | icon_name = icons[math.random(1, 10)],
55 | pixel_size = 32
56 | }, i)
57 | end
58 |
59 | scroll:add(flowbox)
60 | win:add(scroll)
61 | end
62 |
63 | return app:run(arg)
--------------------------------------------------------------------------------
/GtkGrid-Explained.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Miqueas/Lua-GTK-Examples/cf3269795477eea5c21396779e433701d9ee2326/GtkGrid-Explained.png
--------------------------------------------------------------------------------
/GtkGrid1.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkGrid1"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400,
13 | border_width = 10
14 | })
15 | end
16 |
17 | function App:on_activate()
18 | self.active_window:set_titlebar(Gtk.HeaderBar({
19 | visible = true,
20 | show_close_button = true,
21 | title = "GtkGrid",
22 | subtitle = "Example 1"
23 | }))
24 |
25 | --[[ GtkGrid:
26 |
27 | A container that acts like a grid, with columns, rows and cells.
28 | Just look at this example:
29 |
30 | ]]
31 | local Grid = Gtk.Grid({
32 | visible = true,
33 | -- This makes all columns of the same width
34 | column_homogeneous = true,
35 | -- Same, but for rows
36 | row_homogeneous = true,
37 | -- Sets the spacing between all columns
38 | column_spacing = 10,
39 | -- Same, but for rows
40 | row_spacing = 10,
41 |
42 | halign = Gtk.Align.CENTER,
43 | valign = Gtk.Align.CENTER,
44 |
45 | -- If you can't understand how GtkGrid works, see the GtkGrid-Explained.png file
46 | { Gtk.Label({ visible = true, label = "Top: 0. Left: 0" }), top_attach = 0, left_attach = 0 },
47 | { Gtk.Label({ visible = true, label = "Top: 0. Left: 1" }), top_attach = 0, left_attach = 1 },
48 | { Gtk.Label({ visible = true, label = "Top: 0. Left: 2" }), top_attach = 0, left_attach = 2 },
49 | { Gtk.Label({ visible = true, label = "Top: 1. Left: 0" }), top_attach = 1, left_attach = 0 },
50 | { Gtk.Label({ visible = true, label = "Top: 1. Left: 1" }), top_attach = 1, left_attach = 1 },
51 | { Gtk.Label({ visible = true, label = "Top: 1. Left: 2" }), top_attach = 1, left_attach = 2 },
52 | { Gtk.Label({ visible = true, label = "Top: 2. Left: 0" }), top_attach = 2, left_attach = 0 },
53 | { Gtk.Label({ visible = true, label = "Top: 2. Left: 1" }), top_attach = 2, left_attach = 1 },
54 | { Gtk.Label({ visible = true, label = "Top: 2. Left: 2" }), top_attach = 2, left_attach = 2 }
55 | })
56 |
57 | self.active_window:add(Grid)
58 | self.active_window:present()
59 | end
60 |
61 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkGrid2.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkGrid2"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400,
13 | border_width = 10
14 | })
15 | end
16 |
17 | function App:on_activate()
18 | self.active_window:set_titlebar(Gtk.HeaderBar({
19 | visible = true,
20 | show_close_button = true,
21 | title = "GtkGrid",
22 | subtitle = "Example 2"
23 | }))
24 |
25 | --[[
26 |
27 | In the example GtkGrid1.lua you see a basic grid. Here, you can see a little complex
28 | grid with two items that fills more than 1 cell in the grid.
29 |
30 | ]]
31 | local Grid = Gtk.Grid({
32 | visible = true,
33 | column_homogeneous = true,
34 | row_homogeneous = true,
35 | column_spacing = 10,
36 | row_spacing = 10,
37 |
38 | halign = Gtk.Align.CENTER,
39 | valign = Gtk.Align.CENTER,
40 |
41 | { Gtk.Label({ visible = true, label = "Columns" }), top_attach = 0, left_attach = 1, width = 2 },
42 | { Gtk.Label({ visible = true, label = "Rows" }), top_attach = 0, left_attach = 0, height = 3 },
43 | { Gtk.Label({ visible = true, label = "Top: 1. Left: 1" }), top_attach = 1, left_attach = 1 },
44 | { Gtk.Label({ visible = true, label = "Top: 1. Left: 2" }), top_attach = 1, left_attach = 2 },
45 | { Gtk.Label({ visible = true, label = "Top: 2. Left: 1" }), top_attach = 2, left_attach = 1 },
46 | { Gtk.Label({ visible = true, label = "Top: 2. Left: 2" }), top_attach = 2, left_attach = 2 }
47 | })
48 |
49 | self.active_window:add(Grid)
50 | self.active_window:present()
51 | end
52 |
53 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkGrid3.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkGrid3"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400,
13 | border_width = 10
14 | })
15 | end
16 |
17 | function App:on_activate()
18 | self.active_window:set_titlebar(Gtk.HeaderBar({
19 | visible = true,
20 | show_close_button = true,
21 | title = "GtkGrid",
22 | subtitle = "Example 3"
23 | }))
24 |
25 | --[[ GtkGrid:
26 |
27 | A container that acts like a grid, with columns, rows and cells.
28 | Just look at this example:
29 |
30 | ]]
31 | local Grid = Gtk.Grid({
32 | visible = true,
33 | -- This makes all columns of the same width
34 | column_homogeneous = true,
35 | -- Same, but for rows
36 | row_homogeneous = true,
37 | -- Sets the spacing between all columns
38 | column_spacing = 10,
39 | -- Same, but for rows
40 | row_spacing = 10,
41 |
42 | halign = Gtk.Align.CENTER,
43 | valign = Gtk.Align.CENTER,
44 | })
45 |
46 | for top = 0, 2 do
47 | for left = 0, 2 do
48 | Grid:attach(
49 | Gtk.Label({
50 | visible = true,
51 | label = ("Top: %d. Left: %d."):format(top, left)
52 | }),
53 | left, top, 1, 1
54 | )
55 | end
56 | end
57 |
58 | self.active_window:add(Grid)
59 | self.active_window:present()
60 | end
61 |
62 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkHeaderBar.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkHeaderBar"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400
13 | })
14 | end
15 |
16 | function App:on_activate()
17 | --[[ GtkHeaderBar:
18 |
19 | A container that acts as the title bar of the window.
20 | This is a CSD (Client Side Decorations), since we are
21 | setting up our own title bar for our window, rather
22 | than leaving it up to the WM to decorate it.
23 |
24 | In other examples we talk a little more about CSD using CSS.
25 |
26 | ]]
27 | self.active_window:set_titlebar(Gtk.HeaderBar({
28 | -- By default, all GTK widgets are not visibles
29 | visible = true,
30 | -- GtkHeaderBar don't sets the window controls buttons by default
31 | -- this property changes that behavior
32 | show_close_button = true,
33 | title = "Your app title",
34 | subtitle = "Your app subtitle"
35 | }))
36 |
37 | self.active_window:present()
38 | end
39 |
40 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkImage1.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkImage1"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400
13 | })
14 | end
15 |
16 | function App:on_activate()
17 | self.active_window:set_titlebar(Gtk.HeaderBar({
18 | visible = true,
19 | show_close_button = true,
20 | title = "GtkImage",
21 | subtitle = "Example 1"
22 | }))
23 |
24 | --[[ GtkImage:
25 |
26 | Well... A widget that display an image lol
27 |
28 | ]]
29 | local Img = Gtk.Image({ visible = true, file = "Moonsteal-Logo.jpg" })
30 |
31 | self.active_window:add(Img)
32 | self.active_window:present()
33 | end
34 |
35 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkImage2.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkImage2"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400
13 | })
14 | end
15 |
16 | function App:on_activate()
17 | self.active_window:set_titlebar(Gtk.HeaderBar({
18 | visible = true,
19 | show_close_button = true,
20 | title = "GtkImage",
21 | subtitle = "Example 2"
22 | }))
23 |
24 | local Img = Gtk.Image({ visible = true, icon_name = "computer", pixel_size = 256 })
25 |
26 | self.active_window:add(Img)
27 | self.active_window:present()
28 | end
29 |
30 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkInfoBar.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkInfoBar"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400,
13 | border_width = 10
14 | })
15 | end
16 |
17 | function App:on_activate()
18 | self.active_window:set_titlebar(Gtk.HeaderBar({
19 | visible = true,
20 | show_close_button = true,
21 | title = "GtkInfoBar"
22 | }))
23 |
24 | local Box = Gtk.Box({ visible = true, orientation = Gtk.Orientation.VERTICAL })
25 | local InfoBtn = Gtk.Button({ visible = true, label = "Info" })
26 | local WarningBtn = Gtk.Button({ visible = true, label = "Warning" })
27 | local QuestionBtn = Gtk.Button({ visible = true, label = "Question" })
28 | local ErrorBtn = Gtk.Button({ visible = true, label = "Error" })
29 | local OtherBtn = Gtk.Button({ visible = true, label = "Other" })
30 |
31 | function CreateInfoBar(self, data)
32 | --[[ GtkInfoBar:
33 |
34 | A widget that can be used to show messages to the user.
35 | Think in it as In-App notifications. The GtkInfoBar API
36 | is very similar to the GtkDialog API
37 |
38 | ]]
39 | local InfoBar = Gtk.InfoBar({
40 | visible = true,
41 | message_type = Gtk.MessageType[self.label:upper()],
42 | show_close_button = true
43 | })
44 |
45 | -- For this widget, the 'get_content_area()' method always returns a Gtk.Box
46 | InfoBar
47 | :get_content_area()
48 | :pack_start(Gtk.Label({ visible = true, label = self.label }), false, true, 0)
49 |
50 | -- When an action widget is clicked, this signal is emitted
51 | function InfoBar:on_response()
52 | -- For this case, just remove it from the Gtk.Box and then destroy it
53 | Box:remove(self)
54 | self:destroy()
55 | end
56 |
57 | -- Add the info bar to the Gtk.Box
58 | Box:pack_start(InfoBar, false, true, 0)
59 | end
60 |
61 | InfoBtn.on_clicked = CreateInfoBar
62 | WarningBtn.on_clicked = CreateInfoBar
63 | QuestionBtn.on_clicked = CreateInfoBar
64 | ErrorBtn.on_clicked = CreateInfoBar
65 | OtherBtn.on_clicked = CreateInfoBar
66 |
67 | local Grid = Gtk.Grid({
68 | visible = true,
69 | column_homogeneous = true,
70 | row_homogeneous = true,
71 | column_spacing = 10,
72 | row_spacing = 10,
73 |
74 | halign = Gtk.Align.CENTER,
75 | valign = Gtk.Align.CENTER,
76 |
77 | { InfoBtn, top_attach = 0, left_attach = 0 },
78 | { WarningBtn, top_attach = 0, left_attach = 1 },
79 | { QuestionBtn, top_attach = 1, left_attach = 0 },
80 | { ErrorBtn, top_attach = 1, left_attach = 1 },
81 | { OtherBtn, top_attach = 2, left_attach = 0, width = 2 },
82 | })
83 |
84 | Box:pack_end(Grid, true, true, 0)
85 |
86 | self.active_window:add(Box)
87 | self.active_window:present()
88 | end
89 |
90 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkLabel1.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkLabel1"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400
13 | })
14 | end
15 |
16 | function App:on_activate()
17 | self.active_window:set_titlebar(Gtk.HeaderBar({
18 | visible = true,
19 | show_close_button = true,
20 | title = "GtkLabel",
21 | subtitle = "Example 1"
22 | }))
23 |
24 | --[[ GtkLabel:
25 |
26 | A text widget
27 |
28 | ]]
29 | local Label = Gtk.Label({
30 | visible = true,
31 | label = "Hello, world!",
32 | valign = Gtk.Align.CENTER,
33 | halign = Gtk.Align.CENTER
34 | })
35 |
36 | self.active_window:add(Label)
37 | self.active_window:present()
38 | end
39 |
40 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkLabel2.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkLabel2"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400
13 | })
14 | end
15 |
16 | function App:on_activate()
17 | self.active_window:set_titlebar(Gtk.HeaderBar({
18 | visible = true,
19 | show_close_button = true,
20 | title = "GtkLabel",
21 | subtitle = "Example 2"
22 | }))
23 |
24 | local Label = Gtk.Label({
25 | visible = true,
26 | valign = Gtk.Align.CENTER,
27 | halign = Gtk.Align.CENTER,
28 | -- The use_markup property to use an HTML-like
29 | -- basic markup:
30 | use_markup = true,
31 | label = "Hello, world!"
32 | })
33 |
34 | self.active_window:add(Label)
35 | self.active_window:present()
36 | end
37 |
38 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkListBox1.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkListBox1"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400,
13 | border_width = 10
14 | })
15 | end
16 |
17 | function App:on_activate()
18 | self.active_window:set_titlebar(Gtk.HeaderBar({
19 | visible = true,
20 | show_close_button = true,
21 | title = "GtkListBox",
22 | subtitle = "Example 1"
23 | }))
24 |
25 | local Box = Gtk.Box({
26 | visible = true,
27 | orientation = Gtk.Orientation.VERTICAL
28 | })
29 |
30 | --[[ GtkListBox:
31 |
32 | A container for lists. It works better for lists
33 | than a GtkBox because of its vertical nature and
34 | the API it offers.
35 |
36 | ]]
37 | local List = Gtk.ListBox({ visible = true })
38 |
39 | local Scroll = Gtk.ScrolledWindow({
40 | visible = true,
41 | shadow_type = Gtk.ShadowType.NONE,
42 | propagate_natural_width = true,
43 | propagate_natural_height = true,
44 |
45 | List
46 | })
47 |
48 | local Btn = Gtk.Button({
49 | visible = true,
50 | label = "Load",
51 | halign = Gtk.Align.CENTER,
52 | valign = Gtk.Align.CENTER
53 | })
54 |
55 | function Btn:on_clicked()
56 | for i = 0, 100 do
57 | List:insert(Gtk.Label({ visible = true, label = "Text " .. i }), i)
58 | end
59 | end
60 |
61 | local Box = Gtk.Box({
62 | visible = true,
63 | orientation = Gtk.Orientation.VERTICAL,
64 | spacing = 10,
65 |
66 | { Scroll, expand = true },
67 | { Btn }
68 | })
69 |
70 | self.active_window:add(Box)
71 | self.active_window:present()
72 | end
73 |
74 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkMessageDialog.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 | local Pango = lgi.require("Pango", "1.0")
4 |
5 | local App = Gtk.Application({
6 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkMessageDialog"
7 | })
8 |
9 | function App:on_startup()
10 | local Title = [[Universe destruction]]
11 | local Message = "Our universe has a lot of problems and the only way to fix it is destroying the entire universe and this important decision is now in your hands."
12 |
13 | --[[ GtkMessageDialog:
14 |
15 | A GtkDialog for basic popup windows with a predefined design: title, message and choice buttons.
16 | Useful if you don't want to make a complex dialog.
17 |
18 | ]]
19 | local Dialog = Gtk.MessageDialog({
20 | application = self,
21 | buttons = Gtk.ButtonsType.NONE,
22 | message_type = Gtk.MessageType.QUESTION,
23 | title = "GtkMessageDialog",
24 | text = Title,
25 | use_markup = true,
26 | secondary_text = Message
27 | })
28 |
29 | Dialog:add_button("Yes 👍", Gtk.ResponseType.OK)
30 | Dialog:add_button("No 🛑", Gtk.ResponseType.CANCEL)
31 | end
32 |
33 | function App:on_activate()
34 | -- When you work with dialogs, use this instead of 'present()'
35 | local Res = self.active_window:run()
36 |
37 | if Res == Gtk.ResponseType.OK then
38 | self.active_window:destroy()
39 | print("Universe destroyed! 💥")
40 | elseif Res == Gtk.ResponseType.CANCEL then
41 | self.active_window:destroy()
42 | print("Universe is in peace now! 🙏")
43 | else
44 | self.active_window:destroy()
45 | print("Nothing happens! 🤔")
46 | end
47 | end
48 |
49 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkMessageDialog2.lua:
--------------------------------------------------------------------------------
1 | lgi = require "lgi"
2 | Gtk = lgi.require "Gtk"
3 |
4 | dialog = Gtk.MessageDialog { text = "This is a text message.", buttons = "YES_NO" }
5 |
6 | -- https://docs.gtk.org/gtk3/method.Dialog.response.html
7 | function dialog:on_response(response_id)
8 | --print("response_id:",response_id)
9 |
10 | -- https://valadoc.org/gtk+-3.0/Gtk.ResponseType.html
11 | if response_id==Gtk.ResponseType.YES then print("yes")
12 | elseif response_id==Gtk.ResponseType.NO then print("no")
13 | elseif response_id==Gtk.ResponseType.DELETE_EVENT then print("delete-event")
14 | end
15 |
16 | Gtk.main_quit()
17 | end
18 |
19 | dialog:show_all()
20 | Gtk.main()
21 |
--------------------------------------------------------------------------------
/GtkNotebook.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 | local GObject = lgi.require("GObject", "2.0")
4 |
5 | local App = Gtk.Application({
6 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkNotebook"
7 | })
8 |
9 | function App:on_startup()
10 | Gtk.ApplicationWindow({
11 | application = self,
12 | default_width = 400,
13 | default_height = 400
14 | })
15 | end
16 |
17 | function App:on_activate()
18 | -- Append button
19 | local Btn = Gtk.Button({
20 | visible = true,
21 |
22 | Gtk.Box({
23 | visible = true,
24 |
25 | Gtk.Image({ visible = true, icon_name = "list-add-symbolic" })
26 | })
27 | })
28 |
29 | -- Header bar
30 | local Header = Gtk.HeaderBar({
31 | visible = true,
32 | show_close_button = true,
33 | title = "GtkNotebook",
34 | subtitle = "Click in the + button!",
35 |
36 | Btn
37 | })
38 |
39 | self.active_window:set_titlebar(Header)
40 |
41 | --[[ GtkNotebook:
42 |
43 | Similar to a GtkStack, it shows only 1 child at a time,
44 | but with the difference that it uses "tabs" to switch
45 | between the visible children.
46 |
47 | ]]
48 | local Notebook = Gtk.Notebook({
49 | visible = true,
50 | -- Removes a default border around the widget
51 | show_border = false,
52 | -- If has a lot of tabs, then makes it "scrollable"
53 | scrollable = true
54 | })
55 |
56 | local count = 1
57 |
58 | function Btn:on_clicked()
59 | Notebook:append_page(
60 | -- Page content
61 | Gtk.Label({ visible = true, label = "Page " .. count }),
62 | -- Page tab widget
63 | Gtk.Label({ visible = true, label = "Tab " .. count })
64 | )
65 |
66 | count = count + 1
67 | end
68 |
69 | self.active_window:add(Notebook)
70 | self.active_window:present()
71 | end
72 |
73 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkOverlay.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkOverlay"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400
13 | })
14 | end
15 |
16 | function App:on_activate()
17 | self.active_window:set_titlebar(Gtk.HeaderBar({
18 | visible = true,
19 | show_close_button = true,
20 | title = "GtkOverlay"
21 | }))
22 |
23 | --[[ GtkOverlay:
24 |
25 | A widget that allows widgets on top of other (like an stack). GtkOverlay can have
26 | only one main (child) widget, trying to add another widget using the 'add()'
27 | method will throw an error. Use 'add_overlay()' instead, that put the given widget
28 | on top of the main widget or another widget.
29 |
30 | ]]
31 | local Overlay = Gtk.Overlay({
32 | visible = true,
33 |
34 | -- Our main widget
35 | Gtk.Image({
36 | visible = true,
37 | icon_name = "computer",
38 | pixel_size = 256
39 | })
40 | })
41 |
42 | Overlay:add_overlay(Gtk.Image({
43 | visible = true,
44 | icon_name = "computer-symbolic",
45 | pixel_size = 128
46 | }))
47 |
48 | Overlay:add_overlay(Gtk.Image({
49 | visible = true,
50 | icon_name = "input-gaming",
51 | pixel_size = 64
52 | }))
53 |
54 | Overlay:add_overlay(Gtk.Image({
55 | visible = true,
56 | icon_name = "input-gaming-symbolic",
57 | pixel_size = 32
58 | }))
59 |
60 | self.active_window:add(Overlay)
61 | self.active_window:present()
62 | end
63 |
64 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkPaned.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkPaned"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400
13 | })
14 | end
15 |
16 | function App:on_activate()
17 | self.active_window:set_titlebar(Gtk.HeaderBar({
18 | visible = true,
19 | show_close_button = true,
20 | title = "GtkPaned"
21 | }))
22 |
23 | --[[ GtkPaned:
24 |
25 | A widget with two adjustable panes
26 |
27 | ]]
28 | local Paned = Gtk.Paned({ visible = true, orientation = Gtk.Orientation.HORIZONTAL })
29 | Paned:pack1(Gtk.Label({ visible = true, label = "Space 1" }), true, false)
30 | Paned:pack2(Gtk.Label({ visible = true, label = "Space 2" }), true, false)
31 |
32 | self.active_window:add(Paned)
33 | self.active_window:present()
34 | end
35 |
36 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkRevealer.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkRevealer"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 10
13 | })
14 | end
15 |
16 | function App:on_activate()
17 | local Header = Gtk.HeaderBar({
18 | visible = true,
19 | show_close_button = true,
20 | title = "GtkRevealer"
21 | })
22 |
23 | local Btn = Gtk.Button({ visible = true, label = "Toggle message" })
24 | Header:pack_start(Btn)
25 | self.active_window:set_titlebar(Header)
26 |
27 | local Box = Gtk.Box({ visible = true, orientation = Gtk.Orientation.VERTICAL })
28 |
29 | --[[ GtkRevealer:
30 |
31 | A container for show/hide a widget with animation.
32 |
33 | ]]
34 | local Rev = Gtk.Revealer({
35 | visible = true,
36 | reveal_child = true,
37 |
38 | Gtk.Label({ visible = true, label = "Hello there!", margin = 10 })
39 | })
40 |
41 | function Btn:on_clicked()
42 | Rev.reveal_child = (not Rev.reveal_child) and true or false
43 | end
44 |
45 | Box:pack_start(Rev, false, true, 0)
46 |
47 | self.active_window:add(Box)
48 | self.active_window:present()
49 | end
50 |
51 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkScrolledWindow.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkScrolledWindow"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 400
13 | })
14 | end
15 |
16 | function App:on_activate()
17 | self.active_window:set_titlebar(Gtk.HeaderBar({
18 | visible = true,
19 | show_close_button = true,
20 | title = "GtkScrolledWindow"
21 | }))
22 |
23 | local Grid = Gtk.Grid({
24 | visible = true,
25 | column_homogeneous = true,
26 | row_homogeneous = true,
27 | column_spacing = 10,
28 | row_spacing = 10,
29 | halign = Gtk.Align.START,
30 | valign = Gtk.Align.START
31 | })
32 |
33 | --[[ GtkScrolledWindow:
34 |
35 | A container for scrollable content
36 |
37 | ]]
38 | local Scroll = Gtk.ScrolledWindow({
39 | visible = true,
40 | -- Removes a shadow that is added by default
41 | shadow_type = Gtk.ShadowType.NONE,
42 | -- "Expands" the child width, and makes the child use the real allocated width
43 | propagate_natural_width = true,
44 | -- Same, but for height
45 | propagate_natural_height = true,
46 |
47 | Grid
48 | })
49 |
50 | for top = 1, 100 do
51 | for left = 1, 100 do
52 | Grid:attach(
53 | Gtk.Label({ visible = true, label = "Top: "..top..". Left: "..left }),
54 | left - 1,
55 | top - 1,
56 | 1, 1
57 | )
58 | end
59 | end
60 |
61 | self.active_window:add(Scroll)
62 | self.active_window:present()
63 | end
64 |
65 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkSpinner.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local app = Gtk.Application { application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkSpinner" }
5 |
6 | function app:on_activate()
7 | self.active_window:present()
8 | end
9 |
10 | function app:on_startup()
11 | local win = Gtk.ApplicationWindow {
12 | application = self,
13 | default_width = 300,
14 | default_height = 300,
15 | border_width = 10
16 | }
17 |
18 | win:set_titlebar(Gtk.HeaderBar {
19 | visible = true,
20 | show_close_button = true,
21 | title = "GtkSpinner"
22 | })
23 |
24 | local spinner = Gtk.Spinner { visible = true }
25 | local btn = Gtk.Button { visible = true, label = "Start" }
26 |
27 | local box = Gtk.Box {
28 | visible = true,
29 | spacing = 10,
30 | orientation = Gtk.Orientation.VERTICAL,
31 | valign = Gtk.Align.CENTER,
32 | halign = Gtk.Align.CENTER,
33 |
34 | spinner,
35 | btn
36 | }
37 |
38 | function btn:on_clicked()
39 | if spinner.active then
40 | self.label = "Start"
41 | spinner:stop()
42 | else
43 | self.label = "Stop"
44 | spinner:start()
45 | end
46 | end
47 |
48 | win:add(box)
49 | end
50 |
51 | return app:run(arg)
--------------------------------------------------------------------------------
/GtkStack1.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkStack1"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 600,
12 | default_height = 400,
13 | border_width = 10
14 | })
15 | end
16 |
17 | function App:on_activate()
18 | self.active_window:set_titlebar(Gtk.HeaderBar({
19 | visible = true,
20 | show_close_button = true,
21 | title = "GtkStack",
22 | subtitle = "Example 1"
23 | }))
24 |
25 | --[[ GtkStack:
26 |
27 | A container that shows only one (1) child at a time
28 |
29 | ]]
30 | local Stack = Gtk.Stack({
31 | visible = true,
32 | transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT,
33 | -- Increment this value to see the animation more slow
34 | transition_duration = 280,
35 |
36 | {
37 | Gtk.Label({
38 | visible = true,
39 | label = "Page 0",
40 | use_markup = true
41 | }),
42 | title = "Page 0",
43 | name = "Page0"
44 | },
45 | {
46 | Gtk.Label({
47 | visible = true,
48 | label = "Page 1",
49 | use_markup = true
50 | }),
51 | title = "Page 1",
52 | name = "Page1"
53 | },
54 | {
55 | Gtk.Label({
56 | visible = true,
57 | label = "Page 2",
58 | use_markup = true
59 | }),
60 | title = "Page 2",
61 | name = "Page2"
62 | },
63 | {
64 | Gtk.Label({
65 | visible = true,
66 | label = "Page 3",
67 | use_markup = true
68 | }),
69 | title = "Page 3",
70 | name = "Page3"
71 | }
72 | })
73 |
74 | local Box = Gtk.Box({
75 | visible = true,
76 | orientation = Gtk.Orientation.VERTICAL,
77 |
78 | {
79 | Stack,
80 | expand = true
81 | },
82 |
83 | Gtk.StackSwitcher({
84 | visible = true,
85 | stack = Stack,
86 | halign = Gtk.Align.CENTER
87 | })
88 | })
89 |
90 | self.active_window:add(Box)
91 | self.active_window:present()
92 | end
93 |
94 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkStack2.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkStack2"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 600,
12 | default_height = 400
13 | })
14 | end
15 |
16 | function App:on_activate()
17 | self.active_window:set_titlebar(Gtk.HeaderBar({
18 | visible = true,
19 | show_close_button = true,
20 | title = "GtkStack",
21 | subtitle = "Example 2"
22 | }))
23 |
24 | --[[ GtkStack:
25 |
26 | A container that shows only one (1) child at a time
27 |
28 | ]]
29 | local Stack = Gtk.Stack({
30 | visible = true,
31 | transition_type = Gtk.StackTransitionType.SLIDE_UP_DOWN,
32 | -- Increment this value to see the animation more slow
33 | transition_duration = 280,
34 |
35 | {
36 | Gtk.Label({
37 | visible = true,
38 | label = "Page 0",
39 | use_markup = true
40 | }),
41 | title = "Page 0",
42 | name = "Page0"
43 | },
44 | {
45 | Gtk.Label({
46 | visible = true,
47 | label = "Page 1",
48 | use_markup = true
49 | }),
50 | title = "Page 1",
51 | name = "Page1"
52 | },
53 | {
54 | Gtk.Label({
55 | visible = true,
56 | label = "Page 2",
57 | use_markup = true
58 | }),
59 | title = "Page 2",
60 | name = "Page2"
61 | },
62 | {
63 | Gtk.Label({
64 | visible = true,
65 | label = "Page 3",
66 | use_markup = true
67 | }),
68 | title = "Page 3",
69 | name = "Page3"
70 | }
71 | })
72 |
73 | local Box = Gtk.Box({
74 | visible = true,
75 | orientation = Gtk.Orientation.HORIZONTAL,
76 |
77 | Gtk.StackSidebar({
78 | visible = true,
79 | stack = Stack,
80 | halign = Gtk.Align.CENTER
81 | }),
82 | {
83 | Stack,
84 | expand = true
85 | }
86 | })
87 |
88 | self.active_window:add(Box)
89 | self.active_window:present()
90 | end
91 |
92 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkStatusbar.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | local App = Gtk.Application({
5 | application_id = "com.github.Miqueas.Lua-GTK3-Examples.GtkStatusbar"
6 | })
7 |
8 | function App:on_startup()
9 | Gtk.ApplicationWindow({
10 | application = self,
11 | default_width = 400,
12 | default_height = 200
13 | })
14 | end
15 |
16 | function App:on_activate()
17 | self.active_window:set_titlebar(Gtk.HeaderBar({
18 | visible = true,
19 | show_close_button = true,
20 | title = "GtkStatusbar"
21 | }))
22 |
23 | --[[ GtkStatusbar:
24 |
25 | A widget like GtkActionBar for report some info to the user.
26 |
27 | ]]
28 | local Status = Gtk.Statusbar({ visible = true })
29 |
30 | local Btn = Gtk.Button({
31 | visible = true,
32 | label = "Click me",
33 | halign = Gtk.Align.CENTER,
34 | valign = Gtk.Align.CENTER
35 | })
36 |
37 |
38 | local c = 0
39 | function Btn:on_clicked()
40 | c = c + 1
41 | Status:push(c, "You clicked " .. c .. " times")
42 | end
43 |
44 | local Box = Gtk.Box({ visible = true, orientation = Gtk.Orientation.VERTICAL })
45 | Box:pack_start(Btn, true, true, 0)
46 | Box:pack_end(Status, false, true, 0)
47 |
48 | self.active_window:add(Box)
49 | self.active_window:present()
50 | end
51 |
52 | return App:run(arg)
--------------------------------------------------------------------------------
/GtkWindow.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | --[[ GtkWindow:
5 |
6 | Well... A window lmao
7 |
8 | ]]
9 | local Window = Gtk.Window({
10 | default_width = 400,
11 | default_height = 400
12 | })
13 |
14 | -- The "destroy" signal is emited when the user clicks in the
15 | -- close button
16 | Window.on_destroy = Gtk.main_quit
17 | Window:present()
18 |
19 | Gtk.main()
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | zlib License
2 |
3 | Copyright (c) 2021 Moonsteal
4 |
5 | This software is provided 'as-is', without any express or implied
6 | warranty. In no event will the authors be held liable for any damages
7 | arising from the use of this software.
8 |
9 | Permission is granted to anyone to use this software for any purpose,
10 | including commercial applications, and to alter it and redistribute it
11 | freely, subject to the following restrictions:
12 |
13 | 1. The origin of this software must not be misrepresented; you must not
14 | claim that you wrote the original software. If you use this software
15 | in a product, an acknowledgment in the product documentation would be
16 | appreciated but is not required.
17 |
18 | 2. Altered source versions must be plainly marked as such, and must not be
19 | misrepresented as being the original software.
20 |
21 | 3. This notice may not be removed or altered from any source
22 | distribution.
--------------------------------------------------------------------------------
/Moonsteal-Logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Miqueas/Lua-GTK-Examples/cf3269795477eea5c21396779e433701d9ee2326/Moonsteal-Logo.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # The Lua GTK examples are [here](https://github.com/Miqueas/GTK-Examples) now
2 |
3 | Hi! As you see, I archived this repo! Please go to the above link for more details :)
4 |
--------------------------------------------------------------------------------
/action-bar.lua:
--------------------------------------------------------------------------------
1 | local lgi = require("lgi")
2 | local Gtk = lgi.require("Gtk", "3.0")
3 |
4 | -- GtkActionBar: A full width container to add contextual actions.
5 |
6 | local app_id = "io.github.Miqueas.Lua-GTK-Examples.Gtk3.ActionBar"
7 | local app_title = "GtkActionBar"
8 | local app = Gtk.Application { application_id = app_id }
9 |
10 | function app:on_startup()
11 | local win = Gtk.ApplicationWindow {
12 | title = app_title,
13 | application = self,
14 | default_width = 400,
15 | default_height = 400
16 | }
17 |
18 | local bar = Gtk.ActionBar({ visible = true })
19 | local box = Gtk.Box({ visible = true, orientation = Gtk.Orientation.VERTICAL })
20 |
21 | bar:pack_start(Gtk.Label({ visible = true, label = "Something" }))
22 | bar:pack_end(Gtk.Button({ visible = true, label = "A button" }))
23 |
24 | box:pack_start(Gtk.Label({ visible = true, label = "Here is the content of your app" }), true, true, 0)
25 | box:pack_end(bar, false, true, 0)
26 |
27 | win:add(box)
28 | end
29 |
30 | function app:on_activate()
31 | self.active_window:present()
32 | end
33 |
34 | return app:run(arg)
--------------------------------------------------------------------------------
/mobdebug.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- MobDebug -- Lua remote debugger
3 | -- Copyright 2011-20 Paul Kulchenko
4 | -- Based on RemDebug 1.0 Copyright Kepler Project 2005
5 | --
6 |
7 | -- use loaded modules or load explicitly on those systems that require that
8 | local require = require
9 | local io = io or require "io"
10 | local table = table or require "table"
11 | local string = string or require "string"
12 | local coroutine = coroutine or require "coroutine"
13 | local debug = require "debug"
14 | -- protect require "os" as it may fail on embedded systems without os module
15 | local os = os or (function(module)
16 | local ok, res = pcall(require, module)
17 | return ok and res or nil
18 | end)("os")
19 |
20 | local mobdebug = {
21 | _NAME = "mobdebug",
22 | _VERSION = "0.803",
23 | _COPYRIGHT = "Paul Kulchenko",
24 | _DESCRIPTION = "Mobile Remote Debugger for the Lua programming language",
25 | port = os and os.getenv and tonumber((os.getenv("MOBDEBUG_PORT"))) or 8172,
26 | checkcount = 200,
27 | yieldtimeout = 0.02, -- yield timeout (s)
28 | connecttimeout = 2, -- connect timeout (s)
29 | }
30 |
31 | local HOOKMASK = "lcr"
32 | local error = error
33 | local getfenv = getfenv
34 | local setfenv = setfenv
35 | local loadstring = loadstring or load -- "load" replaced "loadstring" in Lua 5.2
36 | local pairs = pairs
37 | local setmetatable = setmetatable
38 | local tonumber = tonumber
39 | local unpack = table.unpack or unpack
40 | local rawget = rawget
41 | local gsub, sub, find = string.gsub, string.sub, string.find
42 |
43 | -- if strict.lua is used, then need to avoid referencing some global
44 | -- variables, as they can be undefined;
45 | -- use rawget to avoid complaints from strict.lua at run-time.
46 | -- it's safe to do the initialization here as all these variables
47 | -- should get defined values (if any) before the debugging starts.
48 | -- there is also global 'wx' variable, which is checked as part of
49 | -- the debug loop as 'wx' can be loaded at any time during debugging.
50 | local genv = _G or _ENV
51 | local jit = rawget(genv, "jit")
52 | local MOAICoroutine = rawget(genv, "MOAICoroutine")
53 |
54 | -- ngx_lua/Openresty requires special handling as its coroutine.*
55 | -- methods use a different mechanism that doesn't allow resume calls
56 | -- from debug hook handlers.
57 | -- Instead, the "original" coroutine.* methods are used.
58 | -- `rawget` needs to be used to protect against `strict` checks
59 | local ngx = rawget(genv, "ngx")
60 | if not ngx then
61 | -- "older" versions of ngx_lua (0.10.x at least) hide ngx table in metatable,
62 | -- so need to use that
63 | local metagindex = getmetatable(genv) and getmetatable(genv).__index
64 | ngx = type(metagindex) == "table" and metagindex.rawget and metagindex:rawget("ngx") or nil
65 | end
66 | local corocreate = ngx and coroutine._create or coroutine.create
67 | local cororesume = ngx and coroutine._resume or coroutine.resume
68 | local coroyield = ngx and coroutine._yield or coroutine.yield
69 | local corostatus = ngx and coroutine._status or coroutine.status
70 | local corowrap = coroutine.wrap
71 |
72 | if not setfenv then -- Lua 5.2+
73 | -- based on http://lua-users.org/lists/lua-l/2010-06/msg00314.html
74 | -- this assumes f is a function
75 | local function findenv(f)
76 | local level = 1
77 | repeat
78 | local name, value = debug.getupvalue(f, level)
79 | if name == '_ENV' then return level, value end
80 | level = level + 1
81 | until name == nil
82 | return nil end
83 | getfenv = function (f) return(select(2, findenv(f)) or _G) end
84 | setfenv = function (f, t)
85 | local level = findenv(f)
86 | if level then debug.setupvalue(f, level, t) end
87 | return f end
88 | end
89 |
90 | -- check for OS and convert file names to lower case on windows
91 | -- (its file system is case insensitive, but case preserving), as setting a
92 | -- breakpoint on x:\Foo.lua will not work if the file was loaded as X:\foo.lua.
93 | -- OSX and Windows behave the same way (case insensitive, but case preserving).
94 | -- OSX can be configured to be case-sensitive, so check for that. This doesn't
95 | -- handle the case of different partitions having different case-sensitivity.
96 | local win = os and os.getenv and (os.getenv('WINDIR') or (os.getenv('OS') or ''):match('[Ww]indows')) and true or false
97 | local mac = not win and (os and os.getenv and os.getenv('DYLD_LIBRARY_PATH') or not io.open("/proc")) and true or false
98 | local iscasepreserving = win or (mac and io.open('/library') ~= nil)
99 |
100 | -- turn jit off based on Mike Pall's comment in this discussion:
101 | -- http://www.freelists.org/post/luajit/Debug-hooks-and-JIT,2
102 | -- "You need to turn it off at the start if you plan to receive
103 | -- reliable hook calls at any later point in time."
104 | if jit and jit.off then jit.off() end
105 |
106 | local socket = require "socket"
107 | local coro_debugger
108 | local coro_debugee
109 | local coroutines = {}; setmetatable(coroutines, {__mode = "k"}) -- "weak" keys
110 | local events = { BREAK = 1, WATCH = 2, RESTART = 3, STACK = 4 }
111 | local breakpoints = {}
112 | local watches = {}
113 | local lastsource
114 | local lastfile
115 | local watchescnt = 0
116 | local abort -- default value is nil; this is used in start/loop distinction
117 | local seen_hook = false
118 | local checkcount = 0
119 | local step_into = false
120 | local step_over = false
121 | local step_level = 0
122 | local stack_level = 0
123 | local server
124 | local buf
125 | local outputs = {}
126 | local iobase = {print = print}
127 | local basedir = ""
128 | local deferror = "execution aborted at default debugee"
129 | local debugee = function ()
130 | local a = 1
131 | for _ = 1, 10 do a = a + 1 end
132 | error(deferror)
133 | end
134 | local function q(s) return string.gsub(s, '([%(%)%.%%%+%-%*%?%[%^%$%]])','%%%1') end
135 |
136 | local serpent = (function() ---- include Serpent module for serialization
137 | local n, v = "serpent", "0.302" -- (C) 2012-18 Paul Kulchenko; MIT License
138 | local c, d = "Paul Kulchenko", "Lua serializer and pretty printer"
139 | local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'}
140 | local badtype = {thread = true, userdata = true, cdata = true}
141 | local getmetatable = debug and debug.getmetatable or getmetatable
142 | local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+
143 | local keyword, globals, G = {}, {}, (_G or _ENV)
144 | for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false',
145 | 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
146 | 'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end
147 | for k,v in pairs(G) do globals[v] = k end -- build func to name mapping
148 | for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do
149 | for k,v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g..'.'..k end end
150 |
151 | local function s(t, opts)
152 | local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum
153 | local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge
154 | local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge)
155 | local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring
156 | local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge)
157 | local numformat = opts.numformat or "%.17g"
158 | local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0
159 | local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)",
160 | -- tostring(val) is needed because __tostring may return a non-string value
161 | function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return tostring(syms[s]) end)) end
162 | local function safestr(s) return type(s) == "number" and tostring(huge and snum[tostring(s)] or numformat:format(s))
163 | or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026
164 | or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end
165 | local function comment(s,l) return comm and (l or 0) < comm and ' --[['..select(2, pcall(tostring, s))..']]' or '' end
166 | local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal
167 | and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end
168 | local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r']
169 | local n = name == nil and '' or name
170 | local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n]
171 | local safe = plain and n or '['..safestr(n)..']'
172 | return (path or '')..(plain and path and '.' or '')..safe, safe end
173 | local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding
174 | local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'}
175 | local function padnum(d) return ("%0"..tostring(maxn).."d"):format(tonumber(d)) end
176 | table.sort(k, function(a,b)
177 | -- sort numeric keys first: k[key] is not nil for numerical keys
178 | return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum))
179 | < (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end
180 | local function val2str(t, name, indent, insref, path, plainindex, level)
181 | local ttype, level, mt = type(t), (level or 0), getmetatable(t)
182 | local spath, sname = safename(path, name)
183 | local tag = plainindex and
184 | ((type(name) == "number") and '' or name..space..'='..space) or
185 | (name ~= nil and sname..space..'='..space or '')
186 | if seen[t] then -- already seen this element
187 | sref[#sref+1] = spath..space..'='..space..seen[t]
188 | return tag..'nil'..comment('ref', level) end
189 | -- protect from those cases where __tostring may fail
190 | if type(mt) == 'table' and metatostring ~= false then
191 | local to, tr = pcall(function() return mt.__tostring(t) end)
192 | local so, sr = pcall(function() return mt.__serialize(t) end)
193 | if (to or so) then -- knows how to serialize itself
194 | seen[t] = insref or spath
195 | t = so and sr or tr
196 | ttype = type(t)
197 | end -- new value falls through to be serialized
198 | end
199 | if ttype == "table" then
200 | if level >= maxl then return tag..'{}'..comment('maxlvl', level) end
201 | seen[t] = insref or spath
202 | if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty
203 | if maxlen and maxlen < 0 then return tag..'{}'..comment('maxlen', level) end
204 | local maxn, o, out = math.min(#t, maxnum or #t), {}, {}
205 | for key = 1, maxn do o[key] = key end
206 | if not maxnum or #o < maxnum then
207 | local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables
208 | for key in pairs(t) do if o[key] ~= key then n = n + 1; o[n] = key end end end
209 | if maxnum and #o > maxnum then o[maxnum+1] = nil end
210 | if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end
211 | local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output)
212 | for n, key in ipairs(o) do
213 | local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse
214 | if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing
215 | or opts.keyallow and not opts.keyallow[key]
216 | or opts.keyignore and opts.keyignore[key]
217 | or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types
218 | or sparse and value == nil then -- skipping nils; do nothing
219 | elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then
220 | if not seen[key] and not globals[key] then
221 | sref[#sref+1] = 'placeholder'
222 | local sname = safename(iname, gensym(key)) -- iname is table for local variables
223 | sref[#sref] = val2str(key,sname,indent,sname,iname,true) end
224 | sref[#sref+1] = 'placeholder'
225 | local path = seen[t]..'['..tostring(seen[key] or globals[key] or gensym(key))..']'
226 | sref[#sref] = path..space..'='..space..tostring(seen[value] or val2str(value,nil,indent,path))
227 | else
228 | out[#out+1] = val2str(value,key,indent,nil,seen[t],plainindex,level+1)
229 | if maxlen then
230 | maxlen = maxlen - #out[#out]
231 | if maxlen < 0 then break end
232 | end
233 | end
234 | end
235 | local prefix = string.rep(indent or '', level)
236 | local head = indent and '{\n'..prefix..indent or '{'
237 | local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space))
238 | local tail = indent and "\n"..prefix..'}' or '}'
239 | return (custom and custom(tag,head,body,tail,level) or tag..head..body..tail)..comment(t, level)
240 | elseif badtype[ttype] then
241 | seen[t] = insref or spath
242 | return tag..globerr(t, level)
243 | elseif ttype == 'function' then
244 | seen[t] = insref or spath
245 | if opts.nocode then return tag.."function() --[[..skipped..]] end"..comment(t, level) end
246 | local ok, res = pcall(string.dump, t)
247 | local func = ok and "((loadstring or load)("..safestr(res)..",'@serialized'))"..comment(t, level)
248 | return tag..(func or globerr(t, level))
249 | else return tag..safestr(t) end -- handle all other types
250 | end
251 | local sepr = indent and "\n" or ";"..space
252 | local body = val2str(t, name, indent) -- this call also populates sref
253 | local tail = #sref>1 and table.concat(sref, sepr)..sepr or ''
254 | local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or ''
255 | return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end"
256 | end
257 |
258 | local function deserialize(data, opts)
259 | local env = (opts and opts.safe == false) and G
260 | or setmetatable({}, {
261 | __index = function(t,k) return t end,
262 | __call = function(t,...) error("cannot call functions") end
263 | })
264 | local f, res = (loadstring or load)('return '..data, nil, nil, env)
265 | if not f then f, res = (loadstring or load)(data, nil, nil, env) end
266 | if not f then return f, res end
267 | if setfenv then setfenv(f, env) end
268 | return pcall(f)
269 | end
270 |
271 | local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end
272 | return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s,
273 | load = deserialize,
274 | dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end,
275 | line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end,
276 | block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end }
277 | end)() ---- end of Serpent module
278 |
279 | mobdebug.line = serpent.line
280 | mobdebug.dump = serpent.dump
281 | mobdebug.linemap = nil
282 | mobdebug.loadstring = loadstring
283 |
284 | local function removebasedir(path, basedir)
285 | if iscasepreserving then
286 | -- check if the lowercased path matches the basedir
287 | -- if so, return substring of the original path (to not lowercase it)
288 | return path:lower():find('^'..q(basedir:lower()))
289 | and path:sub(#basedir+1) or path
290 | else
291 | return string.gsub(path, '^'..q(basedir), '')
292 | end
293 | end
294 |
295 | local function stack(start)
296 | local function vars(f)
297 | local func = debug.getinfo(f, "f").func
298 | local i = 1
299 | local locals = {}
300 | -- get locals
301 | while true do
302 | local name, value = debug.getlocal(f, i)
303 | if not name then break end
304 | if string.sub(name, 1, 1) ~= '(' then
305 | locals[name] = {value, select(2,pcall(tostring,value))}
306 | end
307 | i = i + 1
308 | end
309 | -- get varargs (these use negative indices)
310 | i = 1
311 | while true do
312 | local name, value = debug.getlocal(f, -i)
313 | if not name then break end
314 | locals[name:gsub("%)$"," "..i..")")] = {value, select(2,pcall(tostring,value))}
315 | i = i + 1
316 | end
317 | -- get upvalues
318 | i = 1
319 | local ups = {}
320 | while func do -- check for func as it may be nil for tail calls
321 | local name, value = debug.getupvalue(func, i)
322 | if not name then break end
323 | ups[name] = {value, select(2,pcall(tostring,value))}
324 | i = i + 1
325 | end
326 | return locals, ups
327 | end
328 |
329 | local stack = {}
330 | local linemap = mobdebug.linemap
331 | for i = (start or 0), 100 do
332 | local source = debug.getinfo(i, "Snl")
333 | if not source then break end
334 |
335 | local src = source.source
336 | if src:find("@") == 1 then
337 | src = src:sub(2):gsub("\\", "/")
338 | if src:find("%./") == 1 then src = src:sub(3) end
339 | end
340 |
341 | table.insert(stack, { -- remove basedir from source
342 | {source.name, removebasedir(src, basedir),
343 | linemap and linemap(source.linedefined, source.source) or source.linedefined,
344 | linemap and linemap(source.currentline, source.source) or source.currentline,
345 | source.what, source.namewhat, source.short_src},
346 | vars(i+1)})
347 | end
348 | return stack
349 | end
350 |
351 | local function set_breakpoint(file, line)
352 | if file == '-' and lastfile then file = lastfile
353 | elseif iscasepreserving then file = string.lower(file) end
354 | if not breakpoints[line] then breakpoints[line] = {} end
355 | breakpoints[line][file] = true
356 | end
357 |
358 | local function remove_breakpoint(file, line)
359 | if file == '-' and lastfile then file = lastfile
360 | elseif file == '*' and line == 0 then breakpoints = {}
361 | elseif iscasepreserving then file = string.lower(file) end
362 | if breakpoints[line] then breakpoints[line][file] = nil end
363 | end
364 |
365 | local function has_breakpoint(file, line)
366 | return breakpoints[line]
367 | and breakpoints[line][iscasepreserving and string.lower(file) or file]
368 | end
369 |
370 | local function restore_vars(vars)
371 | if type(vars) ~= 'table' then return end
372 |
373 | -- locals need to be processed in the reverse order, starting from
374 | -- the inner block out, to make sure that the localized variables
375 | -- are correctly updated with only the closest variable with
376 | -- the same name being changed
377 | -- first loop find how many local variables there is, while
378 | -- the second loop processes them from i to 1
379 | local i = 1
380 | while true do
381 | local name = debug.getlocal(3, i)
382 | if not name then break end
383 | i = i + 1
384 | end
385 | i = i - 1
386 | local written_vars = {}
387 | while i > 0 do
388 | local name = debug.getlocal(3, i)
389 | if not written_vars[name] then
390 | if string.sub(name, 1, 1) ~= '(' then
391 | debug.setlocal(3, i, rawget(vars, name))
392 | end
393 | written_vars[name] = true
394 | end
395 | i = i - 1
396 | end
397 |
398 | i = 1
399 | local func = debug.getinfo(3, "f").func
400 | while true do
401 | local name = debug.getupvalue(func, i)
402 | if not name then break end
403 | if not written_vars[name] then
404 | if string.sub(name, 1, 1) ~= '(' then
405 | debug.setupvalue(func, i, rawget(vars, name))
406 | end
407 | written_vars[name] = true
408 | end
409 | i = i + 1
410 | end
411 | end
412 |
413 | local function capture_vars(level, thread)
414 | level = (level or 0)+2 -- add two levels for this and debug calls
415 | local func = (thread and debug.getinfo(thread, level, "f") or debug.getinfo(level, "f") or {}).func
416 | if not func then return {} end
417 |
418 | local vars = {['...'] = {}}
419 | local i = 1
420 | while true do
421 | local name, value = debug.getupvalue(func, i)
422 | if not name then break end
423 | if string.sub(name, 1, 1) ~= '(' then vars[name] = value end
424 | i = i + 1
425 | end
426 | i = 1
427 | while true do
428 | local name, value
429 | if thread then
430 | name, value = debug.getlocal(thread, level, i)
431 | else
432 | name, value = debug.getlocal(level, i)
433 | end
434 | if not name then break end
435 | if string.sub(name, 1, 1) ~= '(' then vars[name] = value end
436 | i = i + 1
437 | end
438 | -- get varargs (these use negative indices)
439 | i = 1
440 | while true do
441 | local name, value
442 | if thread then
443 | name, value = debug.getlocal(thread, level, -i)
444 | else
445 | name, value = debug.getlocal(level, -i)
446 | end
447 | if not name then break end
448 | vars['...'][i] = value
449 | i = i + 1
450 | end
451 | -- returned 'vars' table plays a dual role: (1) it captures local values
452 | -- and upvalues to be restored later (in case they are modified in "eval"),
453 | -- and (2) it provides an environment for evaluated chunks.
454 | -- getfenv(func) is needed to provide proper environment for functions,
455 | -- including access to globals, but this causes vars[name] to fail in
456 | -- restore_vars on local variables or upvalues with `nil` values when
457 | -- 'strict' is in effect. To avoid this `rawget` is used in restore_vars.
458 | setmetatable(vars, { __index = getfenv(func), __newindex = getfenv(func), __mode = "v" })
459 | return vars
460 | end
461 |
462 | local function stack_depth(start_depth)
463 | for i = start_depth, 0, -1 do
464 | if debug.getinfo(i, "l") then return i+1 end
465 | end
466 | return start_depth
467 | end
468 |
469 | local function is_safe(stack_level)
470 | -- the stack grows up: 0 is getinfo, 1 is is_safe, 2 is debug_hook, 3 is user function
471 | if stack_level == 3 then return true end
472 | for i = 3, stack_level do
473 | -- return if it is not safe to abort
474 | local info = debug.getinfo(i, "S")
475 | if not info then return true end
476 | if info.what == "C" then return false end
477 | end
478 | return true
479 | end
480 |
481 | local function in_debugger()
482 | local this = debug.getinfo(1, "S").source
483 | -- only need to check few frames as mobdebug frames should be close
484 | for i = 3, 7 do
485 | local info = debug.getinfo(i, "S")
486 | if not info then return false end
487 | if info.source == this then return true end
488 | end
489 | return false
490 | end
491 |
492 | local function is_pending(peer)
493 | -- if there is something already in the buffer, skip check
494 | if not buf and checkcount >= mobdebug.checkcount then
495 | peer:settimeout(0) -- non-blocking
496 | buf = peer:receive(1)
497 | peer:settimeout() -- back to blocking
498 | checkcount = 0
499 | end
500 | return buf
501 | end
502 |
503 | local function readnext(peer, num)
504 | peer:settimeout(0) -- non-blocking
505 | local res, err, partial = peer:receive(num)
506 | peer:settimeout() -- back to blocking
507 | return res or partial or '', err
508 | end
509 |
510 | local function handle_breakpoint(peer)
511 | -- check if the buffer has the beginning of SETB/DELB command;
512 | -- this is to avoid reading the entire line for commands that
513 | -- don't need to be handled here.
514 | if not buf or not (buf:sub(1,1) == 'S' or buf:sub(1,1) == 'D') then return end
515 |
516 | -- check second character to avoid reading STEP or other S* and D* commands
517 | if #buf == 1 then buf = buf .. readnext(peer, 1) end
518 | if buf:sub(2,2) ~= 'E' then return end
519 |
520 | -- need to read few more characters
521 | buf = buf .. readnext(peer, 5-#buf)
522 | if buf ~= 'SETB ' and buf ~= 'DELB ' then return end
523 |
524 | local res, _, partial = peer:receive("*l") -- get the rest of the line; blocking
525 | if not res then
526 | if partial then buf = buf .. partial end
527 | return
528 | end
529 |
530 | local _, _, cmd, file, line = (buf..res):find("^([A-Z]+)%s+(.-)%s+(%d+)%s*$")
531 | if cmd == 'SETB' then set_breakpoint(file, tonumber(line))
532 | elseif cmd == 'DELB' then remove_breakpoint(file, tonumber(line))
533 | else
534 | -- this looks like a breakpoint command, but something went wrong;
535 | -- return here to let the "normal" processing to handle,
536 | -- although this is likely to not go well.
537 | return
538 | end
539 |
540 | buf = nil
541 | end
542 |
543 | local function normalize_path(file)
544 | local n
545 | repeat
546 | file, n = file:gsub("/+%.?/+","/") -- remove all `//` and `/./` references
547 | until n == 0
548 | -- collapse all up-dir references: this will clobber UNC prefix (\\?\)
549 | -- and disk on Windows when there are too many up-dir references: `D:\foo\..\..\bar`;
550 | -- handle the case of multiple up-dir references: `foo/bar/baz/../../../more`;
551 | -- only remove one at a time as otherwise `../../` could be removed;
552 | repeat
553 | file, n = file:gsub("[^/]+/%.%./", "", 1)
554 | until n == 0
555 | -- there may still be a leading up-dir reference left (as `/../` or `../`); remove it
556 | return (file:gsub("^(/?)%.%./", "%1"))
557 | end
558 |
559 | local function debug_hook(event, line)
560 | -- (1) LuaJIT needs special treatment. Because debug_hook is set for
561 | -- *all* coroutines, and not just the one being debugged as in regular Lua
562 | -- (http://lua-users.org/lists/lua-l/2011-06/msg00513.html),
563 | -- need to avoid debugging mobdebug's own code as LuaJIT doesn't
564 | -- always correctly generate call/return hook events (there are more
565 | -- calls than returns, which breaks stack depth calculation and
566 | -- 'step' and 'step over' commands stop working; possibly because
567 | -- 'tail return' events are not generated by LuaJIT).
568 | -- the next line checks if the debugger is run under LuaJIT and if
569 | -- one of debugger methods is present in the stack, it simply returns.
570 | -- ngx_lua/Openresty requires a slightly different handling, as it
571 | -- creates a coroutine wrapper, so this processing needs to be skipped.
572 | if jit and not (ngx and type(ngx) == "table" and ngx.say) then
573 | -- when luajit is compiled with LUAJIT_ENABLE_LUA52COMPAT,
574 | -- coroutine.running() returns non-nil for the main thread.
575 | local coro, main = coroutine.running()
576 | if not coro or main then coro = 'main' end
577 | local disabled = coroutines[coro] == false
578 | or coroutines[coro] == nil and coro ~= (coro_debugee or 'main')
579 | if coro_debugee and disabled or not coro_debugee and (disabled or in_debugger()) then
580 | return
581 | end
582 | end
583 |
584 | -- (2) check if abort has been requested and it's safe to abort
585 | if abort and is_safe(stack_level) then error(abort) end
586 |
587 | -- (3) also check if this debug hook has not been visited for any reason.
588 | -- this check is needed to avoid stepping in too early
589 | -- (for example, when coroutine.resume() is executed inside start()).
590 | if not seen_hook and in_debugger() then return end
591 |
592 | if event == "call" then
593 | stack_level = stack_level + 1
594 | elseif event == "return" or event == "tail return" then
595 | stack_level = stack_level - 1
596 | elseif event == "line" then
597 | if mobdebug.linemap then
598 | local ok, mappedline = pcall(mobdebug.linemap, line, debug.getinfo(2, "S").source)
599 | if ok then line = mappedline end
600 | if not line then return end
601 | end
602 |
603 | -- may need to fall through because of the following:
604 | -- (1) step_into
605 | -- (2) step_over and stack_level <= step_level (need stack_level)
606 | -- (3) breakpoint; check for line first as it's known; then for file
607 | -- (4) socket call (only do every Xth check)
608 | -- (5) at least one watch is registered
609 | if not (
610 | step_into or step_over or breakpoints[line] or watchescnt > 0
611 | or is_pending(server)
612 | ) then checkcount = checkcount + 1; return end
613 |
614 | checkcount = mobdebug.checkcount -- force check on the next command
615 |
616 | -- this is needed to check if the stack got shorter or longer.
617 | -- unfortunately counting call/return calls is not reliable.
618 | -- the discrepancy may happen when "pcall(load, '')" call is made
619 | -- or when "error()" is called in a function.
620 | -- in either case there are more "call" than "return" events reported.
621 | -- this validation is done for every "line" event, but should be "cheap"
622 | -- as it checks for the stack to get shorter (or longer by one call).
623 | -- start from one level higher just in case we need to grow the stack.
624 | -- this may happen after coroutine.resume call to a function that doesn't
625 | -- have any other instructions to execute. it triggers three returns:
626 | -- "return, tail return, return", which needs to be accounted for.
627 | stack_level = stack_depth(stack_level+1)
628 |
629 | local caller = debug.getinfo(2, "S")
630 |
631 | -- grab the filename and fix it if needed
632 | local file = lastfile
633 | if (lastsource ~= caller.source) then
634 | file, lastsource = caller.source, caller.source
635 | -- technically, users can supply names that may not use '@',
636 | -- for example when they call loadstring('...', 'filename.lua').
637 | -- Unfortunately, there is no reliable/quick way to figure out
638 | -- what is the filename and what is the source code.
639 | -- If the name doesn't start with `@`, assume it's a file name if it's all on one line.
640 | if find(file, "^@") or not find(file, "[\r\n]") then
641 | file = gsub(gsub(file, "^@", ""), "\\", "/")
642 | -- normalize paths that may include up-dir or same-dir references
643 | -- if the path starts from the up-dir or reference,
644 | -- prepend `basedir` to generate absolute path to keep breakpoints working.
645 | -- ignore qualified relative path (`D:../`) and UNC paths (`\\?\`)
646 | if find(file, "^%.%./") then file = basedir..file end
647 | if find(file, "/%.%.?/") then file = normalize_path(file) end
648 | -- need this conversion to be applied to relative and absolute
649 | -- file names as you may write "require 'Foo'" to
650 | -- load "foo.lua" (on a case insensitive file system) and breakpoints
651 | -- set on foo.lua will not work if not converted to the same case.
652 | if iscasepreserving then file = string.lower(file) end
653 | if find(file, "^%./") then file = sub(file, 3) end
654 | -- remove basedir, so that breakpoints are checked properly
655 | file = gsub(file, "^"..q(basedir), "")
656 | -- some file systems allow newlines in file names; remove these.
657 | file = gsub(file, "\n", ' ')
658 | else
659 | file = mobdebug.line(file)
660 | end
661 |
662 | -- set to true if we got here; this only needs to be done once per
663 | -- session, so do it here to at least avoid setting it for every line.
664 | seen_hook = true
665 | lastfile = file
666 | end
667 |
668 | if is_pending(server) then handle_breakpoint(server) end
669 |
670 | local vars, status, res
671 | if (watchescnt > 0) then
672 | vars = capture_vars(1)
673 | for index, value in pairs(watches) do
674 | setfenv(value, vars)
675 | local ok, fired = pcall(value)
676 | if ok and fired then
677 | status, res = cororesume(coro_debugger, events.WATCH, vars, file, line, index)
678 | break -- any one watch is enough; don't check multiple times
679 | end
680 | end
681 | end
682 |
683 | -- need to get into the "regular" debug handler, but only if there was
684 | -- no watch that was fired. If there was a watch, handle its result.
685 | local getin = (status == nil) and
686 | (step_into
687 | -- when coroutine.running() return `nil` (main thread in Lua 5.1),
688 | -- step_over will equal 'main', so need to check for that explicitly.
689 | or (step_over and step_over == (coroutine.running() or 'main') and stack_level <= step_level)
690 | or has_breakpoint(file, line)
691 | or is_pending(server))
692 |
693 | if getin then
694 | vars = vars or capture_vars(1)
695 | step_into = false
696 | step_over = false
697 | status, res = cororesume(coro_debugger, events.BREAK, vars, file, line)
698 | end
699 |
700 | -- handle 'stack' command that provides stack() information to the debugger
701 | while status and res == 'stack' do
702 | -- resume with the stack trace and variables
703 | if vars then restore_vars(vars) end -- restore vars so they are reflected in stack values
704 | status, res = cororesume(coro_debugger, events.STACK, stack(3), file, line)
705 | end
706 |
707 | -- need to recheck once more as resume after 'stack' command may
708 | -- return something else (for example, 'exit'), which needs to be handled
709 | if status and res and res ~= 'stack' then
710 | if not abort and res == "exit" then mobdebug.onexit(1, true); return end
711 | if not abort and res == "done" then mobdebug.done(); return end
712 | abort = res
713 | -- only abort if safe; if not, there is another (earlier) check inside
714 | -- debug_hook, which will abort execution at the first safe opportunity
715 | if is_safe(stack_level) then error(abort) end
716 | elseif not status and res then
717 | error(res, 2) -- report any other (internal) errors back to the application
718 | end
719 |
720 | if vars then restore_vars(vars) end
721 |
722 | -- last command requested Step Over/Out; store the current thread
723 | if step_over == true then step_over = coroutine.running() or 'main' end
724 | end
725 | end
726 |
727 | local function stringify_results(params, status, ...)
728 | if not status then return status, ... end -- on error report as it
729 |
730 | params = params or {}
731 | if params.nocode == nil then params.nocode = true end
732 | if params.comment == nil then params.comment = 1 end
733 |
734 | local t = {}
735 | for i = 1, select('#', ...) do -- stringify each of the returned values
736 | local ok, res = pcall(mobdebug.line, select(i, ...), params)
737 | t[i] = ok and res or ("%q"):format(res):gsub("\010","n"):gsub("\026","\\026")
738 | end
739 | -- stringify table with all returned values
740 | -- this is done to allow each returned value to be used (serialized or not)
741 | -- intependently and to preserve "original" comments
742 | return pcall(mobdebug.dump, t, {sparse = false})
743 | end
744 |
745 | local function isrunning()
746 | return coro_debugger and (corostatus(coro_debugger) == 'suspended' or corostatus(coro_debugger) == 'running')
747 | end
748 |
749 | -- this is a function that removes all hooks and closes the socket to
750 | -- report back to the controller that the debugging is done.
751 | -- the script that called `done` can still continue.
752 | local function done()
753 | if not (isrunning() and server) then return end
754 |
755 | if not jit then
756 | for co, debugged in pairs(coroutines) do
757 | if debugged then debug.sethook(co) end
758 | end
759 | end
760 |
761 | debug.sethook()
762 | server:close()
763 |
764 | coro_debugger = nil -- to make sure isrunning() returns `false`
765 | seen_hook = nil -- to make sure that the next start() call works
766 | abort = nil -- to make sure that callback calls use proper "abort" value
767 | basedir = "" -- to reset basedir in case the same module/state is reused
768 | end
769 |
770 | local function debugger_loop(sev, svars, sfile, sline)
771 | local command
772 | local eval_env = svars or {}
773 | local function emptyWatch () return false end
774 | local loaded = {}
775 | for k in pairs(package.loaded) do loaded[k] = true end
776 |
777 | while true do
778 | local line, err
779 | if mobdebug.yield and server.settimeout then server:settimeout(mobdebug.yieldtimeout) end
780 | while true do
781 | line, err = server:receive("*l")
782 | if not line then
783 | if err == "timeout" then
784 | if mobdebug.yield then mobdebug.yield() end
785 | elseif err == "closed" then
786 | error("Debugger connection closed", 0)
787 | else
788 | error(("Unexpected socket error: %s"):format(err), 0)
789 | end
790 | else
791 | -- if there is something in the pending buffer, prepend it to the line
792 | if buf then line = buf .. line; buf = nil end
793 | break
794 | end
795 | end
796 | if server.settimeout then server:settimeout() end -- back to blocking
797 | command = string.sub(line, string.find(line, "^[A-Z]+"))
798 | if command == "SETB" then
799 | local _, _, _, file, line = string.find(line, "^([A-Z]+)%s+(.-)%s+(%d+)%s*$")
800 | if file and line then
801 | set_breakpoint(file, tonumber(line))
802 | server:send("200 OK\n")
803 | else
804 | server:send("400 Bad Request\n")
805 | end
806 | elseif command == "DELB" then
807 | local _, _, _, file, line = string.find(line, "^([A-Z]+)%s+(.-)%s+(%d+)%s*$")
808 | if file and line then
809 | remove_breakpoint(file, tonumber(line))
810 | server:send("200 OK\n")
811 | else
812 | server:send("400 Bad Request\n")
813 | end
814 | elseif command == "EXEC" then
815 | -- extract any optional parameters
816 | local params = string.match(line, "--%s*(%b{})%s*$")
817 | local _, _, chunk = string.find(line, "^[A-Z]+%s+(.+)$")
818 | if chunk then
819 | local func, res = mobdebug.loadstring(chunk)
820 | local status
821 | if func then
822 | local pfunc = params and loadstring("return "..params) -- use internal function
823 | params = pfunc and pfunc()
824 | params = (type(params) == "table" and params or {})
825 | local stack = tonumber(params.stack)
826 | -- if the requested stack frame is not the current one, then use a new capture
827 | -- with a specific stack frame: `capture_vars(0, coro_debugee)`
828 | local env = stack and coro_debugee and capture_vars(stack-1, coro_debugee) or eval_env
829 | setfenv(func, env)
830 | status, res = stringify_results(params, pcall(func, unpack(rawget(env,'...') or {})))
831 | end
832 | if status then
833 | if mobdebug.onscratch then mobdebug.onscratch(res) end
834 | server:send("200 OK " .. tostring(#res) .. "\n")
835 | server:send(res)
836 | else
837 | -- fix error if not set (for example, when loadstring is not present)
838 | if not res then res = "Unknown error" end
839 | server:send("401 Error in Expression " .. tostring(#res) .. "\n")
840 | server:send(res)
841 | end
842 | else
843 | server:send("400 Bad Request\n")
844 | end
845 | elseif command == "LOAD" then
846 | local _, _, size, name = string.find(line, "^[A-Z]+%s+(%d+)%s+(%S.-)%s*$")
847 | size = tonumber(size)
848 |
849 | if abort == nil then -- no LOAD/RELOAD allowed inside start()
850 | if size > 0 then server:receive(size) end
851 | if sfile and sline then
852 | server:send("201 Started " .. sfile .. " " .. tostring(sline) .. "\n")
853 | else
854 | server:send("200 OK 0\n")
855 | end
856 | else
857 | -- reset environment to allow required modules to load again
858 | -- remove those packages that weren't loaded when debugger started
859 | for k in pairs(package.loaded) do
860 | if not loaded[k] then package.loaded[k] = nil end
861 | end
862 |
863 | if size == 0 and name == '-' then -- RELOAD the current script being debugged
864 | server:send("200 OK 0\n")
865 | coroyield("load")
866 | else
867 | -- receiving 0 bytes blocks (at least in luasocket 2.0.2), so skip reading
868 | local chunk = size == 0 and "" or server:receive(size)
869 | if chunk then -- LOAD a new script for debugging
870 | local func, res = mobdebug.loadstring(chunk, "@"..name)
871 | if func then
872 | server:send("200 OK 0\n")
873 | debugee = func
874 | coroyield("load")
875 | else
876 | server:send("401 Error in Expression " .. tostring(#res) .. "\n")
877 | server:send(res)
878 | end
879 | else
880 | server:send("400 Bad Request\n")
881 | end
882 | end
883 | end
884 | elseif command == "SETW" then
885 | local _, _, exp = string.find(line, "^[A-Z]+%s+(.+)%s*$")
886 | if exp then
887 | local func, res = mobdebug.loadstring("return(" .. exp .. ")")
888 | if func then
889 | watchescnt = watchescnt + 1
890 | local newidx = #watches + 1
891 | watches[newidx] = func
892 | server:send("200 OK " .. tostring(newidx) .. "\n")
893 | else
894 | server:send("401 Error in Expression " .. tostring(#res) .. "\n")
895 | server:send(res)
896 | end
897 | else
898 | server:send("400 Bad Request\n")
899 | end
900 | elseif command == "DELW" then
901 | local _, _, index = string.find(line, "^[A-Z]+%s+(%d+)%s*$")
902 | index = tonumber(index)
903 | if index > 0 and index <= #watches then
904 | watchescnt = watchescnt - (watches[index] ~= emptyWatch and 1 or 0)
905 | watches[index] = emptyWatch
906 | server:send("200 OK\n")
907 | else
908 | server:send("400 Bad Request\n")
909 | end
910 | elseif command == "RUN" then
911 | server:send("200 OK\n")
912 |
913 | local ev, vars, file, line, idx_watch = coroyield()
914 | eval_env = vars
915 | if ev == events.BREAK then
916 | server:send("202 Paused " .. file .. " " .. tostring(line) .. "\n")
917 | elseif ev == events.WATCH then
918 | server:send("203 Paused " .. file .. " " .. tostring(line) .. " " .. tostring(idx_watch) .. "\n")
919 | elseif ev == events.RESTART then
920 | -- nothing to do
921 | else
922 | server:send("401 Error in Execution " .. tostring(#file) .. "\n")
923 | server:send(file)
924 | end
925 | elseif command == "STEP" then
926 | server:send("200 OK\n")
927 | step_into = true
928 |
929 | local ev, vars, file, line, idx_watch = coroyield()
930 | eval_env = vars
931 | if ev == events.BREAK then
932 | server:send("202 Paused " .. file .. " " .. tostring(line) .. "\n")
933 | elseif ev == events.WATCH then
934 | server:send("203 Paused " .. file .. " " .. tostring(line) .. " " .. tostring(idx_watch) .. "\n")
935 | elseif ev == events.RESTART then
936 | -- nothing to do
937 | else
938 | server:send("401 Error in Execution " .. tostring(#file) .. "\n")
939 | server:send(file)
940 | end
941 | elseif command == "OVER" or command == "OUT" then
942 | server:send("200 OK\n")
943 | step_over = true
944 |
945 | -- OVER and OUT are very similar except for
946 | -- the stack level value at which to stop
947 | if command == "OUT" then step_level = stack_level - 1
948 | else step_level = stack_level end
949 |
950 | local ev, vars, file, line, idx_watch = coroyield()
951 | eval_env = vars
952 | if ev == events.BREAK then
953 | server:send("202 Paused " .. file .. " " .. tostring(line) .. "\n")
954 | elseif ev == events.WATCH then
955 | server:send("203 Paused " .. file .. " " .. tostring(line) .. " " .. tostring(idx_watch) .. "\n")
956 | elseif ev == events.RESTART then
957 | -- nothing to do
958 | else
959 | server:send("401 Error in Execution " .. tostring(#file) .. "\n")
960 | server:send(file)
961 | end
962 | elseif command == "BASEDIR" then
963 | local _, _, dir = string.find(line, "^[A-Z]+%s+(.+)%s*$")
964 | if dir then
965 | basedir = iscasepreserving and string.lower(dir) or dir
966 | -- reset cached source as it may change with basedir
967 | lastsource = nil
968 | server:send("200 OK\n")
969 | else
970 | server:send("400 Bad Request\n")
971 | end
972 | elseif command == "SUSPEND" then
973 | -- do nothing; it already fulfilled its role
974 | elseif command == "DONE" then
975 | coroyield("done")
976 | return -- done with all the debugging
977 | elseif command == "STACK" then
978 | -- first check if we can execute the stack command
979 | -- as it requires yielding back to debug_hook it cannot be executed
980 | -- if we have not seen the hook yet as happens after start().
981 | -- in this case we simply return an empty result
982 | local vars, ev = {}
983 | if seen_hook then
984 | ev, vars = coroyield("stack")
985 | end
986 | if ev and ev ~= events.STACK then
987 | server:send("401 Error in Execution " .. tostring(#vars) .. "\n")
988 | server:send(vars)
989 | else
990 | local params = string.match(line, "--%s*(%b{})%s*$")
991 | local pfunc = params and loadstring("return "..params) -- use internal function
992 | params = pfunc and pfunc()
993 | params = (type(params) == "table" and params or {})
994 | if params.nocode == nil then params.nocode = true end
995 | if params.sparse == nil then params.sparse = false end
996 | -- take into account additional levels for the stack frames and data management
997 | if tonumber(params.maxlevel) then params.maxlevel = tonumber(params.maxlevel)+4 end
998 |
999 | local ok, res = pcall(mobdebug.dump, vars, params)
1000 | if ok then
1001 | server:send("200 OK " .. tostring(res) .. "\n")
1002 | else
1003 | server:send("401 Error in Execution " .. tostring(#res) .. "\n")
1004 | server:send(res)
1005 | end
1006 | end
1007 | elseif command == "OUTPUT" then
1008 | local _, _, stream, mode = string.find(line, "^[A-Z]+%s+(%w+)%s+([dcr])%s*$")
1009 | if stream and mode and stream == "stdout" then
1010 | -- assign "print" in the global environment
1011 | local default = mode == 'd'
1012 | genv.print = default and iobase.print or corowrap(function()
1013 | -- wrapping into coroutine.wrap protects this function from
1014 | -- being stepped through in the debugger.
1015 | -- don't use vararg (...) as it adds a reference for its values,
1016 | -- which may affect how they are garbage collected
1017 | while true do
1018 | local tbl = {coroutine.yield()}
1019 | if mode == 'c' then iobase.print(unpack(tbl)) end
1020 | for n = 1, #tbl do
1021 | tbl[n] = select(2, pcall(mobdebug.line, tbl[n], {nocode = true, comment = false})) end
1022 | local file = table.concat(tbl, "\t").."\n"
1023 | server:send("204 Output " .. stream .. " " .. tostring(#file) .. "\n" .. file)
1024 | end
1025 | end)
1026 | if not default then genv.print() end -- "fake" print to start printing loop
1027 | server:send("200 OK\n")
1028 | else
1029 | server:send("400 Bad Request\n")
1030 | end
1031 | elseif command == "EXIT" then
1032 | server:send("200 OK\n")
1033 | coroyield("exit")
1034 | else
1035 | server:send("400 Bad Request\n")
1036 | end
1037 | end
1038 | end
1039 |
1040 | local function output(stream, data)
1041 | if server then return server:send("204 Output "..stream.." "..tostring(#data).."\n"..data) end
1042 | end
1043 |
1044 | local function connect(controller_host, controller_port)
1045 | local sock, err = socket.tcp()
1046 | if not sock then return nil, err end
1047 |
1048 | if sock.settimeout then sock:settimeout(mobdebug.connecttimeout) end
1049 | local res, err = sock:connect(controller_host, tostring(controller_port))
1050 | if sock.settimeout then sock:settimeout() end
1051 |
1052 | if not res then return nil, err end
1053 | return sock
1054 | end
1055 |
1056 | local lasthost, lastport
1057 |
1058 | -- Starts a debug session by connecting to a controller
1059 | local function start(controller_host, controller_port)
1060 | -- only one debugging session can be run (as there is only one debug hook)
1061 | if isrunning() then return end
1062 |
1063 | lasthost = controller_host or lasthost
1064 | lastport = controller_port or lastport
1065 |
1066 | controller_host = lasthost or "localhost"
1067 | controller_port = lastport or mobdebug.port
1068 |
1069 | local err
1070 | server, err = mobdebug.connect(controller_host, controller_port)
1071 | if server then
1072 | -- correct stack depth which already has some calls on it
1073 | -- so it doesn't go into negative when those calls return
1074 | -- as this breaks subsequence checks in stack_depth().
1075 | -- start from 16th frame, which is sufficiently large for this check.
1076 | stack_level = stack_depth(16)
1077 |
1078 | coro_debugger = corocreate(debugger_loop)
1079 | debug.sethook(debug_hook, HOOKMASK)
1080 | seen_hook = nil -- reset in case the last start() call was refused
1081 | step_into = true -- start with step command
1082 | return true
1083 | else
1084 | print(("Could not connect to %s:%s: %s")
1085 | :format(controller_host, controller_port, err or "unknown error"))
1086 | end
1087 | end
1088 |
1089 | local function controller(controller_host, controller_port, scratchpad)
1090 | -- only one debugging session can be run (as there is only one debug hook)
1091 | if isrunning() then return end
1092 |
1093 | lasthost = controller_host or lasthost
1094 | lastport = controller_port or lastport
1095 |
1096 | controller_host = lasthost or "localhost"
1097 | controller_port = lastport or mobdebug.port
1098 |
1099 | local exitonerror = not scratchpad
1100 | local err
1101 | server, err = mobdebug.connect(controller_host, controller_port)
1102 | if server then
1103 | local function report(trace, err)
1104 | local msg = err .. "\n" .. trace
1105 | server:send("401 Error in Execution " .. tostring(#msg) .. "\n")
1106 | server:send(msg)
1107 | return err
1108 | end
1109 |
1110 | seen_hook = true -- allow to accept all commands
1111 | coro_debugger = corocreate(debugger_loop)
1112 |
1113 | while true do
1114 | step_into = true -- start with step command
1115 | abort = false -- reset abort flag from the previous loop
1116 | if scratchpad then checkcount = mobdebug.checkcount end -- force suspend right away
1117 |
1118 | coro_debugee = corocreate(debugee)
1119 | debug.sethook(coro_debugee, debug_hook, HOOKMASK)
1120 | local status, err = cororesume(coro_debugee, unpack(arg or {}))
1121 |
1122 | -- was there an error or is the script done?
1123 | -- 'abort' state is allowed here; ignore it
1124 | if abort then
1125 | if tostring(abort) == 'exit' then break end
1126 | else
1127 | if status then -- no errors
1128 | if corostatus(coro_debugee) == "suspended" then
1129 | -- the script called `coroutine.yield` in the "main" thread
1130 | error("attempt to yield from the main thread", 3)
1131 | end
1132 | break -- normal execution is done
1133 | elseif err and not string.find(tostring(err), deferror) then
1134 | -- report the error back
1135 | -- err is not necessarily a string, so convert to string to report
1136 | report(debug.traceback(coro_debugee), tostring(err))
1137 | if exitonerror then break end
1138 | -- check if the debugging is done (coro_debugger is nil)
1139 | if not coro_debugger then break end
1140 | -- resume once more to clear the response the debugger wants to send
1141 | -- need to use capture_vars(0) to capture only two (default) levels,
1142 | -- as even though there is controller() call, because of the tail call,
1143 | -- the caller may not exist for it;
1144 | -- This is not entirely safe as the user may see the local
1145 | -- variable from console, but they will be reset anyway.
1146 | -- This functionality is used when scratchpad is paused to
1147 | -- gain access to remote console to modify global variables.
1148 | local status, err = cororesume(coro_debugger, events.RESTART, capture_vars(0))
1149 | if not status or status and err == "exit" then break end
1150 | end
1151 | end
1152 | end
1153 | else
1154 | print(("Could not connect to %s:%s: %s")
1155 | :format(controller_host, controller_port, err or "unknown error"))
1156 | return false
1157 | end
1158 | return true
1159 | end
1160 |
1161 | local function scratchpad(controller_host, controller_port)
1162 | return controller(controller_host, controller_port, true)
1163 | end
1164 |
1165 | local function loop(controller_host, controller_port)
1166 | return controller(controller_host, controller_port, false)
1167 | end
1168 |
1169 | local function on()
1170 | if not (isrunning() and server) then return end
1171 |
1172 | -- main is set to true under Lua5.2 for the "main" chunk.
1173 | -- Lua5.1 returns co as `nil` in that case.
1174 | local co, main = coroutine.running()
1175 | if main then co = nil end
1176 | if co then
1177 | coroutines[co] = true
1178 | debug.sethook(co, debug_hook, HOOKMASK)
1179 | else
1180 | if jit then coroutines.main = true end
1181 | debug.sethook(debug_hook, HOOKMASK)
1182 | end
1183 | end
1184 |
1185 | local function off()
1186 | if not (isrunning() and server) then return end
1187 |
1188 | -- main is set to true under Lua5.2 for the "main" chunk.
1189 | -- Lua5.1 returns co as `nil` in that case.
1190 | local co, main = coroutine.running()
1191 | if main then co = nil end
1192 |
1193 | -- don't remove coroutine hook under LuaJIT as there is only one (global) hook
1194 | if co then
1195 | coroutines[co] = false
1196 | if not jit then debug.sethook(co) end
1197 | else
1198 | if jit then coroutines.main = false end
1199 | if not jit then debug.sethook() end
1200 | end
1201 |
1202 | -- check if there is any thread that is still being debugged under LuaJIT;
1203 | -- if not, turn the debugging off
1204 | if jit then
1205 | local remove = true
1206 | for _, debugged in pairs(coroutines) do
1207 | if debugged then remove = false; break end
1208 | end
1209 | if remove then debug.sethook() end
1210 | end
1211 | end
1212 |
1213 | -- Handles server debugging commands
1214 | local function handle(params, client, options)
1215 | -- when `options.verbose` is not provided, use normal `print`; verbose output can be
1216 | -- disabled (`options.verbose == false`) or redirected (`options.verbose == function()...end`)
1217 | local verbose = not options or options.verbose ~= nil and options.verbose
1218 | local print = verbose and (type(verbose) == "function" and verbose or print) or function() end
1219 | local file, line, watch_idx
1220 | local _, _, command = string.find(params, "^([a-z]+)")
1221 | if command == "run" or command == "step" or command == "out"
1222 | or command == "over" or command == "exit" then
1223 | client:send(string.upper(command) .. "\n")
1224 | client:receive("*l") -- this should consume the first '200 OK' response
1225 | while true do
1226 | local done = true
1227 | local breakpoint = client:receive("*l")
1228 | if not breakpoint then
1229 | print("Program finished")
1230 | return nil, nil, false
1231 | end
1232 | local _, _, status = string.find(breakpoint, "^(%d+)")
1233 | if status == "200" then
1234 | -- don't need to do anything
1235 | elseif status == "202" then
1236 | _, _, file, line = string.find(breakpoint, "^202 Paused%s+(.-)%s+(%d+)%s*$")
1237 | if file and line then
1238 | print("Paused at file " .. file .. " line " .. line)
1239 | end
1240 | elseif status == "203" then
1241 | _, _, file, line, watch_idx = string.find(breakpoint, "^203 Paused%s+(.-)%s+(%d+)%s+(%d+)%s*$")
1242 | if file and line and watch_idx then
1243 | print("Paused at file " .. file .. " line " .. line .. " (watch expression " .. watch_idx .. ": [" .. watches[watch_idx] .. "])")
1244 | end
1245 | elseif status == "204" then
1246 | local _, _, stream, size = string.find(breakpoint, "^204 Output (%w+) (%d+)$")
1247 | if stream and size then
1248 | local size = tonumber(size)
1249 | local msg = size > 0 and client:receive(size) or ""
1250 | print(msg)
1251 | if outputs[stream] then outputs[stream](msg) end
1252 | -- this was just the output, so go back reading the response
1253 | done = false
1254 | end
1255 | elseif status == "401" then
1256 | local _, _, size = string.find(breakpoint, "^401 Error in Execution (%d+)$")
1257 | if size then
1258 | local msg = client:receive(tonumber(size))
1259 | print("Error in remote application: " .. msg)
1260 | return nil, nil, msg
1261 | end
1262 | else
1263 | print("Unknown error")
1264 | return nil, nil, "Debugger error: unexpected response '" .. breakpoint .. "'"
1265 | end
1266 | if done then break end
1267 | end
1268 | elseif command == "done" then
1269 | client:send(string.upper(command) .. "\n")
1270 | -- no response is expected
1271 | elseif command == "setb" or command == "asetb" then
1272 | _, _, _, file, line = string.find(params, "^([a-z]+)%s+(.-)%s+(%d+)%s*$")
1273 | if file and line then
1274 | -- if this is a file name, and not a file source
1275 | if not file:find('^".*"$') then
1276 | file = string.gsub(file, "\\", "/") -- convert slash
1277 | file = removebasedir(file, basedir)
1278 | end
1279 | client:send("SETB " .. file .. " " .. line .. "\n")
1280 | if command == "asetb" or client:receive("*l") == "200 OK" then
1281 | set_breakpoint(file, line)
1282 | else
1283 | print("Error: breakpoint not inserted")
1284 | end
1285 | else
1286 | print("Invalid command")
1287 | end
1288 | elseif command == "setw" then
1289 | local _, _, exp = string.find(params, "^[a-z]+%s+(.+)$")
1290 | if exp then
1291 | client:send("SETW " .. exp .. "\n")
1292 | local answer = client:receive("*l")
1293 | local _, _, watch_idx = string.find(answer, "^200 OK (%d+)%s*$")
1294 | if watch_idx then
1295 | watches[watch_idx] = exp
1296 | print("Inserted watch exp no. " .. watch_idx)
1297 | else
1298 | local _, _, size = string.find(answer, "^401 Error in Expression (%d+)$")
1299 | if size then
1300 | local err = client:receive(tonumber(size)):gsub(".-:%d+:%s*","")
1301 | print("Error: watch expression not set: " .. err)
1302 | else
1303 | print("Error: watch expression not set")
1304 | end
1305 | end
1306 | else
1307 | print("Invalid command")
1308 | end
1309 | elseif command == "delb" or command == "adelb" then
1310 | _, _, _, file, line = string.find(params, "^([a-z]+)%s+(.-)%s+(%d+)%s*$")
1311 | if file and line then
1312 | -- if this is a file name, and not a file source
1313 | if not file:find('^".*"$') then
1314 | file = string.gsub(file, "\\", "/") -- convert slash
1315 | file = removebasedir(file, basedir)
1316 | end
1317 | client:send("DELB " .. file .. " " .. line .. "\n")
1318 | if command == "adelb" or client:receive("*l") == "200 OK" then
1319 | remove_breakpoint(file, line)
1320 | else
1321 | print("Error: breakpoint not removed")
1322 | end
1323 | else
1324 | print("Invalid command")
1325 | end
1326 | elseif command == "delallb" then
1327 | local file, line = "*", 0
1328 | client:send("DELB " .. file .. " " .. tostring(line) .. "\n")
1329 | if client:receive("*l") == "200 OK" then
1330 | remove_breakpoint(file, line)
1331 | else
1332 | print("Error: all breakpoints not removed")
1333 | end
1334 | elseif command == "delw" then
1335 | local _, _, index = string.find(params, "^[a-z]+%s+(%d+)%s*$")
1336 | if index then
1337 | client:send("DELW " .. index .. "\n")
1338 | if client:receive("*l") == "200 OK" then
1339 | watches[index] = nil
1340 | else
1341 | print("Error: watch expression not removed")
1342 | end
1343 | else
1344 | print("Invalid command")
1345 | end
1346 | elseif command == "delallw" then
1347 | for index, exp in pairs(watches) do
1348 | client:send("DELW " .. index .. "\n")
1349 | if client:receive("*l") == "200 OK" then
1350 | watches[index] = nil
1351 | else
1352 | print("Error: watch expression at index " .. index .. " [" .. exp .. "] not removed")
1353 | end
1354 | end
1355 | elseif command == "eval" or command == "exec"
1356 | or command == "load" or command == "loadstring"
1357 | or command == "reload" then
1358 | local _, _, exp = string.find(params, "^[a-z]+%s+(.+)$")
1359 | if exp or (command == "reload") then
1360 | if command == "eval" or command == "exec" then
1361 | exp = exp:gsub("\n", "\r") -- convert new lines, so the fragment can be passed as one line
1362 | if command == "eval" then exp = "return " .. exp end
1363 | client:send("EXEC " .. exp .. "\n")
1364 | elseif command == "reload" then
1365 | client:send("LOAD 0 -\n")
1366 | elseif command == "loadstring" then
1367 | local _, _, _, file, lines = string.find(exp, "^([\"'])(.-)%1%s(.+)")
1368 | if not file then
1369 | _, _, file, lines = string.find(exp, "^(%S+)%s(.+)")
1370 | end
1371 | client:send("LOAD " .. tostring(#lines) .. " " .. file .. "\n")
1372 | client:send(lines)
1373 | else
1374 | local file = io.open(exp, "r")
1375 | if not file and pcall(require, "winapi") then
1376 | -- if file is not open and winapi is there, try with a short path;
1377 | -- this may be needed for unicode paths on windows
1378 | winapi.set_encoding(winapi.CP_UTF8)
1379 | local shortp = winapi.short_path(exp)
1380 | file = shortp and io.open(shortp, "r")
1381 | end
1382 | if not file then return nil, nil, "Cannot open file " .. exp end
1383 | -- read the file and remove the shebang line as it causes a compilation error
1384 | local lines = file:read("*all"):gsub("^#!.-\n", "\n")
1385 | file:close()
1386 |
1387 | local fname = string.gsub(exp, "\\", "/") -- convert slash
1388 | fname = removebasedir(fname, basedir)
1389 | client:send("LOAD " .. tostring(#lines) .. " " .. fname .. "\n")
1390 | if #lines > 0 then client:send(lines) end
1391 | end
1392 | while true do
1393 | local params, err = client:receive("*l")
1394 | if not params then
1395 | return nil, nil, "Debugger connection " .. (err or "error")
1396 | end
1397 | local done = true
1398 | local _, _, status, len = string.find(params, "^(%d+).-%s+(%d+)%s*$")
1399 | if status == "200" then
1400 | len = tonumber(len)
1401 | if len > 0 then
1402 | local status, res
1403 | local str = client:receive(len)
1404 | -- handle serialized table with results
1405 | local func, err = loadstring(str)
1406 | if func then
1407 | status, res = pcall(func)
1408 | if not status then err = res
1409 | elseif type(res) ~= "table" then
1410 | err = "received "..type(res).." instead of expected 'table'"
1411 | end
1412 | end
1413 | if err then
1414 | print("Error in processing results: " .. err)
1415 | return nil, nil, "Error in processing results: " .. err
1416 | end
1417 | print(unpack(res))
1418 | return res[1], res
1419 | end
1420 | elseif status == "201" then
1421 | _, _, file, line = string.find(params, "^201 Started%s+(.-)%s+(%d+)%s*$")
1422 | elseif status == "202" or params == "200 OK" then
1423 | -- do nothing; this only happens when RE/LOAD command gets the response
1424 | -- that was for the original command that was aborted
1425 | elseif status == "204" then
1426 | local _, _, stream, size = string.find(params, "^204 Output (%w+) (%d+)$")
1427 | if stream and size then
1428 | local size = tonumber(size)
1429 | local msg = size > 0 and client:receive(size) or ""
1430 | print(msg)
1431 | if outputs[stream] then outputs[stream](msg) end
1432 | -- this was just the output, so go back reading the response
1433 | done = false
1434 | end
1435 | elseif status == "401" then
1436 | len = tonumber(len)
1437 | local res = client:receive(len)
1438 | print("Error in expression: " .. res)
1439 | return nil, nil, res
1440 | else
1441 | print("Unknown error")
1442 | return nil, nil, "Debugger error: unexpected response after EXEC/LOAD '" .. params .. "'"
1443 | end
1444 | if done then break end
1445 | end
1446 | else
1447 | print("Invalid command")
1448 | end
1449 | elseif command == "listb" then
1450 | for l, v in pairs(breakpoints) do
1451 | for f in pairs(v) do
1452 | print(f .. ": " .. l)
1453 | end
1454 | end
1455 | elseif command == "listw" then
1456 | for i, v in pairs(watches) do
1457 | print("Watch exp. " .. i .. ": " .. v)
1458 | end
1459 | elseif command == "suspend" then
1460 | client:send("SUSPEND\n")
1461 | elseif command == "stack" then
1462 | local opts = string.match(params, "^[a-z]+%s+(.+)$")
1463 | client:send("STACK" .. (opts and " "..opts or "") .."\n")
1464 | local resp = client:receive("*l")
1465 | local _, _, status, res = string.find(resp, "^(%d+)%s+%w+%s+(.+)%s*$")
1466 | if status == "200" then
1467 | local func, err = loadstring(res)
1468 | if func == nil then
1469 | print("Error in stack information: " .. err)
1470 | return nil, nil, err
1471 | end
1472 | local ok, stack = pcall(func)
1473 | if not ok then
1474 | print("Error in stack information: " .. stack)
1475 | return nil, nil, stack
1476 | end
1477 | for _,frame in ipairs(stack) do
1478 | print(mobdebug.line(frame[1], {comment = false}))
1479 | end
1480 | return stack
1481 | elseif status == "401" then
1482 | local _, _, len = string.find(resp, "%s+(%d+)%s*$")
1483 | len = tonumber(len)
1484 | local res = len > 0 and client:receive(len) or "Invalid stack information."
1485 | print("Error in expression: " .. res)
1486 | return nil, nil, res
1487 | else
1488 | print("Unknown error")
1489 | return nil, nil, "Debugger error: unexpected response after STACK"
1490 | end
1491 | elseif command == "output" then
1492 | local _, _, stream, mode = string.find(params, "^[a-z]+%s+(%w+)%s+([dcr])%s*$")
1493 | if stream and mode then
1494 | client:send("OUTPUT "..stream.." "..mode.."\n")
1495 | local resp, err = client:receive("*l")
1496 | if not resp then
1497 | print("Unknown error: "..err)
1498 | return nil, nil, "Debugger connection error: "..err
1499 | end
1500 | local _, _, status = string.find(resp, "^(%d+)%s+%w+%s*$")
1501 | if status == "200" then
1502 | print("Stream "..stream.." redirected")
1503 | outputs[stream] = type(options) == 'table' and options.handler or nil
1504 | -- the client knows when she is doing, so install the handler
1505 | elseif type(options) == 'table' and options.handler then
1506 | outputs[stream] = options.handler
1507 | else
1508 | print("Unknown error")
1509 | return nil, nil, "Debugger error: can't redirect "..stream
1510 | end
1511 | else
1512 | print("Invalid command")
1513 | end
1514 | elseif command == "basedir" then
1515 | local _, _, dir = string.find(params, "^[a-z]+%s+(.+)$")
1516 | if dir then
1517 | dir = string.gsub(dir, "\\", "/") -- convert slash
1518 | if not string.find(dir, "/$") then dir = dir .. "/" end
1519 |
1520 | local remdir = dir:match("\t(.+)")
1521 | if remdir then dir = dir:gsub("/?\t.+", "/") end
1522 | basedir = dir
1523 |
1524 | client:send("BASEDIR "..(remdir or dir).."\n")
1525 | local resp, err = client:receive("*l")
1526 | if not resp then
1527 | print("Unknown error: "..err)
1528 | return nil, nil, "Debugger connection error: "..err
1529 | end
1530 | local _, _, status = string.find(resp, "^(%d+)%s+%w+%s*$")
1531 | if status == "200" then
1532 | print("New base directory is " .. basedir)
1533 | else
1534 | print("Unknown error")
1535 | return nil, nil, "Debugger error: unexpected response after BASEDIR"
1536 | end
1537 | else
1538 | print(basedir)
1539 | end
1540 | elseif command == "help" then
1541 | print("setb -- sets a breakpoint")
1542 | print("delb -- removes a breakpoint")
1543 | print("delallb -- removes all breakpoints")
1544 | print("setw -- adds a new watch expression")
1545 | print("delw -- removes the watch expression at index")
1546 | print("delallw -- removes all watch expressions")
1547 | print("run -- runs until next breakpoint")
1548 | print("step -- runs until next line, stepping into function calls")
1549 | print("over -- runs until next line, stepping over function calls")
1550 | print("out -- runs until line after returning from current function")
1551 | print("listb -- lists breakpoints")
1552 | print("listw -- lists watch expressions")
1553 | print("eval -- evaluates expression on the current context and returns its value")
1554 | print("exec -- executes statement on the current context")
1555 | print("load -- loads a local file for debugging")
1556 | print("reload -- restarts the current debugging session")
1557 | print("stack -- reports stack trace")
1558 | print("output stdout -- capture and redirect io stream (default|copy|redirect)")
1559 | print("basedir [] -- sets the base path of the remote application, or shows the current one")
1560 | print("done -- stops the debugger and continues application execution")
1561 | print("exit -- exits debugger and the application")
1562 | else
1563 | local _, _, spaces = string.find(params, "^(%s*)$")
1564 | if spaces then
1565 | return nil, nil, "Empty command"
1566 | else
1567 | print("Invalid command")
1568 | return nil, nil, "Invalid command"
1569 | end
1570 | end
1571 | return file, line
1572 | end
1573 |
1574 | -- Starts debugging server
1575 | local function listen(host, port)
1576 | host = host or "*"
1577 | port = port or mobdebug.port
1578 |
1579 | local socket = require "socket"
1580 |
1581 | print("Lua Remote Debugger")
1582 | print("Run the program you wish to debug")
1583 |
1584 | local server = socket.bind(host, port)
1585 | local client = server:accept()
1586 |
1587 | client:send("STEP\n")
1588 | client:receive("*l")
1589 |
1590 | local breakpoint = client:receive("*l")
1591 | local _, _, file, line = string.find(breakpoint, "^202 Paused%s+(.-)%s+(%d+)%s*$")
1592 | if file and line then
1593 | print("Paused at file " .. file )
1594 | print("Type 'help' for commands")
1595 | else
1596 | local _, _, size = string.find(breakpoint, "^401 Error in Execution (%d+)%s*$")
1597 | if size then
1598 | print("Error in remote application: ")
1599 | print(client:receive(size))
1600 | end
1601 | end
1602 |
1603 | while true do
1604 | io.write("> ")
1605 | local file, _, err = handle(io.read("*line"), client)
1606 | if not file and err == false then break end -- completed debugging
1607 | end
1608 |
1609 | client:close()
1610 | end
1611 |
1612 | local cocreate
1613 | local function coro()
1614 | if cocreate then return end -- only set once
1615 | cocreate = cocreate or coroutine.create
1616 | coroutine.create = function(f, ...)
1617 | return cocreate(function(...)
1618 | mobdebug.on()
1619 | return f(...)
1620 | end, ...)
1621 | end
1622 | end
1623 |
1624 | local moconew
1625 | local function moai()
1626 | if moconew then return end -- only set once
1627 | moconew = moconew or (MOAICoroutine and MOAICoroutine.new)
1628 | if not moconew then return end
1629 | MOAICoroutine.new = function(...)
1630 | local thread = moconew(...)
1631 | -- need to support both thread.run and getmetatable(thread).run, which
1632 | -- was used in earlier MOAI versions
1633 | local mt = thread.run and thread or getmetatable(thread)
1634 | local patched = mt.run
1635 | mt.run = function(self, f, ...)
1636 | return patched(self, function(...)
1637 | mobdebug.on()
1638 | return f(...)
1639 | end, ...)
1640 | end
1641 | return thread
1642 | end
1643 | end
1644 |
1645 | -- make public functions available
1646 | mobdebug.setbreakpoint = set_breakpoint
1647 | mobdebug.removebreakpoint = remove_breakpoint
1648 | mobdebug.listen = listen
1649 | mobdebug.loop = loop
1650 | mobdebug.scratchpad = scratchpad
1651 | mobdebug.handle = handle
1652 | mobdebug.connect = connect
1653 | mobdebug.start = start
1654 | mobdebug.on = on
1655 | mobdebug.off = off
1656 | mobdebug.moai = moai
1657 | mobdebug.coro = coro
1658 | mobdebug.done = done
1659 | mobdebug.pause = function() step_into = true end
1660 | mobdebug.yield = nil -- callback
1661 | mobdebug.output = output
1662 | mobdebug.onexit = os and os.exit or done
1663 | mobdebug.onscratch = nil -- callback
1664 | mobdebug.basedir = function(b) if b then basedir = b end return basedir end
1665 |
1666 | return mobdebug
1667 |
--------------------------------------------------------------------------------
/styles/GtkCssProvider1.css:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Díaz Urbaneja Víctor Eduardo Diex
3 | * @date 28.02.2021 02:26:39 -04
4 | */
5 |
6 | /**
7 | * I add to the children of window (GtkWindow) that are buttons (GtkButton)
8 | * a blue color
9 | */
10 | window button {
11 | color: #1E90FF;
12 | }
13 |
14 | /**
15 | * the labels that are inside the buttons and have the class red
16 | * will have a red color
17 | */
18 | button.red label {
19 | color: #E32424;
20 | }
21 |
--------------------------------------------------------------------------------