├── .gitignore ├── Dockerfile ├── README.md ├── apps.txt ├── bashrc ├── build.sh ├── dock ├── compile.sh └── dock.c ├── gjs ├── gjs.js └── window.js ├── init.sh └── screen.png /.gitignore: -------------------------------------------------------------------------------- 1 | /dock/dock 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM pritunl/archlinux:latest 2 | 3 | COPY gjs /gjs 4 | 5 | EXPOSE 8085 6 | 7 | RUN pacman --noconfirm -S gtk3 xfce4 midori mypaint gedit nautilus gnome-mines htop gcc pkg-config gjs vim 8 | #toilix 9 | RUN pacman --noconfirm -S arc-gtk-theme git wget yajl dub dmd terminator 10 | 11 | RUN pacman -S --noconfirm --needed base-devel 12 | 13 | RUN pacman -S --noconfirm gnome gnome-extra mate mate-extra 14 | 15 | RUN git clone https://github.com/gnunn1/tilix.git; cd tilix; dub build --build=release; ./install.sh 16 | 17 | #useradd -m -g user bash user 18 | 19 | ENV GDK_BACKEND broadway 20 | ENV BROADWAY_DISPLAY :5 21 | 22 | COPY init.sh /init.sh 23 | CMD ["/init.sh"] 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gtk3-docker 2 | 3 | Run native gtk3+ applications inside a container via the browser 4 | 5 | ![alt](screen.png) 6 | 7 | ## Instructions 8 | 9 | 10 | `docker run --rm -p 8085:8085 chadmoon/gtk3-docker` 11 | 12 | Open `http://localhost:8085` 13 | 14 | 15 | or build image locally 16 | 17 | `docker build -t gtk3-docker .` 18 | `docker run --rm -p 8085:8085 gtk3-docker` 19 | 20 | ## Description 21 | 22 | This container is based on arch linux. It sets up the environment to use the BROADWAY html5 display for rendering applications. A small gtk3+ "launcher" application is used as the entrypoint. -------------------------------------------------------------------------------- /apps.txt: -------------------------------------------------------------------------------- 1 | xfce4-terminal 2 | midori 3 | gedit 4 | nautilus 5 | -------------------------------------------------------------------------------- /bashrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moondev/gtk3-docker/7f72a2e2f27ee3a3af7f5c3c64ad79f58b7e1874/bashrc -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker kill gtk3-docker 4 | docker rm gtk3-docker 5 | 6 | docker build -t gtk3-docker . 7 | 8 | docker run -d --rm -it -p 8085:8085 --name gtk3-docker gtk3-docker 9 | 10 | open http://localhost:8085 11 | 12 | -------------------------------------------------------------------------------- /dock/compile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | gcc `pkg-config --cflags gtk+-3.0` -o dock dock.c `pkg-config --libs gtk+-3.0` 4 | 5 | -------------------------------------------------------------------------------- /dock/dock.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static gint 4 | sh_cmd (gchar * path, gchar * cmd, gchar * args) 5 | { 6 | gchar cmd_line[256]; 7 | gchar **argv; 8 | gint argp; 9 | gint rc = 0; 10 | 11 | if (cmd == NULL) 12 | return FALSE; 13 | 14 | if (cmd[0] == '\0') 15 | return FALSE; 16 | 17 | if (path != NULL) 18 | chdir (path); 19 | 20 | snprintf (cmd_line, sizeof (cmd_line), "%s %s", cmd, args); 21 | 22 | rc = g_shell_parse_argv (cmd_line, &argp, &argv, NULL); 23 | if (!rc) 24 | { 25 | g_strfreev (argv); 26 | return rc; 27 | } 28 | 29 | rc = g_spawn_async (path, argv, NULL, 30 | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_SEARCH_PATH, 31 | NULL, NULL, NULL, NULL); 32 | 33 | g_strfreev (argv); 34 | 35 | return rc; 36 | } 37 | 38 | 39 | 40 | static void 41 | print_hello (GtkWidget *widget, 42 | gpointer data) 43 | { 44 | 45 | gchar cmd_line[256]; 46 | gchar **argv; 47 | gint argp; 48 | gint rc = 0; 49 | 50 | sh_cmd ("/usr/sbin", "midori", "http://news.ycombinator.com"); 51 | 52 | } 53 | 54 | static void 55 | print_hello2 (GtkWidget *widget, 56 | gpointer data) 57 | { 58 | 59 | gchar cmd_line[256]; 60 | gchar **argv; 61 | gint argp; 62 | gint rc = 0; 63 | 64 | sh_cmd ("/usr/sbin", "xfce4-terminal", "--show-borders"); 65 | } 66 | 67 | static void 68 | print_hello3 (GtkWidget *widget, 69 | gpointer data) 70 | { 71 | 72 | gchar cmd_line[256]; 73 | gchar **argv; 74 | gint argp; 75 | gint rc = 0; 76 | 77 | sh_cmd ("/usr/sbin", "mypaint", "-f"); 78 | } 79 | 80 | static void 81 | run_cmd (GtkWidget *widget, 82 | gpointer data) 83 | { 84 | 85 | gchar cmd_line[256]; 86 | gchar **argv; 87 | gint argp; 88 | gint rc = 0; 89 | 90 | sh_cmd ("/usr/sbin", "gedit", "--new-window"); 91 | } 92 | 93 | static void 94 | run_cmd2 (GtkWidget *widget, 95 | gpointer data) 96 | { 97 | 98 | gchar cmd_line[256]; 99 | gchar **argv; 100 | gint argp; 101 | gint rc = 0; 102 | 103 | sh_cmd ("/usr/sbin", "nautilus", "--new-window"); 104 | } 105 | 106 | static void 107 | run_cmd3 (GtkWidget *widget, 108 | gpointer data) 109 | { 110 | 111 | gchar cmd_line[256]; 112 | gchar **argv; 113 | gint argp; 114 | gint rc = 0; 115 | 116 | sh_cmd ("/usr/sbin", "gnome-mines", "--medium"); 117 | } 118 | 119 | static void 120 | activate (GtkApplication *app, 121 | gpointer user_data) 122 | { 123 | GtkWidget *window; 124 | 125 | 126 | GtkWidget *button; 127 | GtkWidget *button_box; 128 | 129 | window = gtk_application_window_new (app); 130 | 131 | gtk_window_set_decorated (GTK_WINDOW (window), FALSE); 132 | 133 | button_box = gtk_button_box_new (GTK_ORIENTATION_VERTICAL); 134 | 135 | gtk_button_box_set_layout (GTK_CONTAINER (button_box), GTK_BUTTONBOX_SPREAD); 136 | 137 | gtk_container_add (GTK_CONTAINER (window), button_box); 138 | 139 | button = gtk_button_new_with_label ("Browser"); 140 | g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL); 141 | 142 | gtk_container_add (GTK_CONTAINER (button_box), button); 143 | 144 | 145 | GtkWidget *button2; 146 | 147 | GtkWidget *button3; 148 | 149 | button2 = gtk_button_new_with_label ("Terminal"); 150 | g_signal_connect (button2, "clicked", G_CALLBACK (print_hello2), NULL); 151 | 152 | gtk_container_add (GTK_CONTAINER (button_box), button2); 153 | 154 | button3 = gtk_button_new_with_label ("MyPaint"); 155 | g_signal_connect (button3, "clicked", G_CALLBACK (print_hello3), NULL); 156 | 157 | gtk_container_add (GTK_CONTAINER (button_box), button3); 158 | 159 | GtkWidget *button4; 160 | button4 = gtk_button_new_with_label ("Gedit"); 161 | g_signal_connect (button4, "clicked", G_CALLBACK (run_cmd), NULL); 162 | 163 | gtk_container_add (GTK_CONTAINER (button_box), button4); 164 | 165 | GtkWidget *button5; 166 | button5 = gtk_button_new_with_label ("File Manager"); 167 | g_signal_connect (button5, "clicked", G_CALLBACK (run_cmd2), NULL); 168 | 169 | gtk_container_add (GTK_CONTAINER (button_box), button5); 170 | 171 | GtkWidget *button6; 172 | button6 = gtk_button_new_with_label ("Minesweeper"); 173 | g_signal_connect (button6, "clicked", G_CALLBACK (run_cmd3), NULL); 174 | 175 | gtk_container_add (GTK_CONTAINER (button_box), button6); 176 | 177 | 178 | gtk_widget_show_all (window); 179 | } 180 | 181 | int 182 | main (int argc, 183 | char **argv) 184 | { 185 | GtkApplication *app; 186 | int status; 187 | 188 | app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE); 189 | g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); 190 | status = g_application_run (G_APPLICATION (app), argc, argv); 191 | g_object_unref (app); 192 | 193 | return status; 194 | } -------------------------------------------------------------------------------- /gjs/gjs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/gjs 2 | 3 | const Gio = imports.gi.Gio; 4 | const GLib = imports.gi.GLib; 5 | const Gtk = imports.gi.Gtk; 6 | const Lang = imports.lang; 7 | 8 | const Application = new Lang.Class ({ 9 | Name: 'Application', 10 | 11 | //create the application 12 | _init: function () { 13 | this.application = new Gtk.Application ({ 14 | application_id: 'org.example.myapp', 15 | flags: Gio.ApplicationFlags.FLAGS_NONE 16 | }); 17 | 18 | this.application.connect('activate', Lang.bind(this, this._onActivate)); 19 | }, 20 | 21 | //callback function for 'activate' signal 22 | _onActivate: function () { 23 | 24 | MyWindow = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); 25 | MyWindow.title = "Welcome to GNOME"; 26 | 27 | /* Here are a few ways we can customize our window. 28 | Try uncommenting them or changing their values! */ 29 | //MyWindow.set_default_size (400,200); 30 | //MyWindow.set_has_resize_grip (false); 31 | //MyWindow.set_opacity (0.5); 32 | //MyWindow.maximize (); 33 | 34 | //show the window and all child widgets (none in this case) 35 | MyWindow.show_all(); 36 | this.application.add_window(MyWindow); 37 | } 38 | }); 39 | 40 | //run the application 41 | let app = new Application (); 42 | app.application.run (ARGV); -------------------------------------------------------------------------------- /gjs/window.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/gjs 2 | 3 | const Gdk = imports.gi.Gdk; 4 | const Gio = imports.gi.Gio; 5 | const GLib = imports.gi.GLib; 6 | const Gtk = imports.gi.Gtk; 7 | const Lang = imports.lang; 8 | 9 | const Application = new Lang.Class({ 10 | Name: 'Application', 11 | 12 | //create the application 13 | _init: function() { 14 | this.application = new Gtk.Application({ 15 | application_id: 'org.example.myapp', 16 | flags: Gio.ApplicationFlags.FLAGS_NONE 17 | }); 18 | 19 | //connect to 'activate' and 'startup' signals to the callback functions 20 | this.application.connect('activate', Lang.bind(this, this._onActivate)); 21 | this.application.connect('startup', Lang.bind(this, this._onStartup)); 22 | }, 23 | 24 | //create the UI (in this case it's just the ApplicationWindow 25 | _buildUI: function() { 26 | this._window = new Gtk.ApplicationWindow({ application: this.application, 27 | window_position: Gtk.WindowPosition.CENTER, 28 | title: "Toolbar Example", 29 | default_height: 200, 30 | default_width: 400 }); 31 | 32 | this._grid = new Gtk.Grid(); 33 | this._window.add(this._grid); 34 | this._grid.show(); 35 | 36 | this._createToolbar(); 37 | this._toolbar.set_hexpand(true); 38 | this._grid.attach(this._toolbar, 0, 0, 1, 1); 39 | 40 | //show the toolbar and window 41 | this._toolbar.show(); 42 | this._window.show(); 43 | }, 44 | 45 | //callback function for 'activate' signal 46 | _onActivate: function() { 47 | this._window.present(); 48 | }, 49 | 50 | //callback function for 'startup' signal 51 | _onStartup: function() { 52 | this._initMenus(); 53 | this._buildUI(); 54 | }, 55 | 56 | //create the toolbar, its toolbuttons and their actions 57 | _createToolbar: function() { 58 | 59 | this._toolbar = new Gtk.Toolbar(); 60 | this._toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR); 61 | 62 | //create the "New" ToolButton and its SimpleAction. 63 | //Using actions allows you to add them to the app menu 64 | //without duplicating code. 65 | let newAction = new Gio.SimpleAction({ name: 'new'}); 66 | newAction.connect('activate', Lang.bind(this, 67 | function() { 68 | this._newCB(); 69 | })); 70 | this.application.add_action(newAction);//note: this action is added to the app 71 | 72 | this._newButton = new Gtk.ToolButton.new_from_stock(Gtk.STOCK_NEW); 73 | this._newButton.is_important = true; 74 | this._toolbar.add(this._newButton); 75 | this._newButton.show(); 76 | this._newButton.action_name = "app.new"; 77 | 78 | //create the "Open" ToolButton and its SimpleAction 79 | let openAction = new Gio.SimpleAction({ name: 'open'}); 80 | openAction.connect('activate', Lang.bind(this, 81 | function() { 82 | this._openCB(); 83 | })); 84 | this.application.add_action(openAction); 85 | 86 | this._openButton = new Gtk.ToolButton.new_from_stock(Gtk.STOCK_OPEN); 87 | this._openButton.is_important = true; 88 | this._toolbar.add(this._openButton); 89 | this._openButton.show(); 90 | this._openButton.action_name = "app.open"; 91 | 92 | //create the "Undo" ToolButton and its SimpleAction 93 | let undoAction = new Gio.SimpleAction({ name: 'undo'}); 94 | undoAction.connect('activate', Lang.bind (this, 95 | function() { 96 | this._undoCB(); 97 | })); 98 | this._window.add_action(undoAction);//note this action is added to the window 99 | 100 | this._undoButton = new Gtk.ToolButton.new_from_stock(Gtk.STOCK_UNDO); 101 | this._undoButton.is_important = true; 102 | this._toolbar.add(this._undoButton); 103 | this._undoButton.show(); 104 | this._undoButton.action_name = "win.undo"; 105 | 106 | //create the "Fullscreen" ToolButton and its SimpleAction 107 | let fullscreenToggleAction = new Gio.SimpleAction ({ name: 'fullscreenToggle' }); 108 | fullscreenToggleAction.connect ('activate', Lang.bind (this, 109 | function () { 110 | this._fullscreenToggleCB(); 111 | })); 112 | this._window.add_action(fullscreenToggleAction); 113 | 114 | this._fullscreenButton = new Gtk.ToolButton.new_from_stock(Gtk.STOCK_FULLSCREEN); 115 | this._fullscreenButton.is_important = true; 116 | this._toolbar.add(this._fullscreenButton); 117 | this._fullscreenButton.show(); 118 | this._fullscreenButton.action_name = "win.fullscreenToggle"; 119 | 120 | //create the "leaveFullscreen" ToolButton, and set the action name to "win.fullscreenToggle" 121 | this._leaveFullscreenButton = new Gtk.ToolButton.new_from_stock(Gtk.STOCK_LEAVE_FULLSCREEN); 122 | this._leaveFullscreenButton.is_important = true; 123 | this._toolbar.add(this._leaveFullscreenButton); 124 | this._leaveFullscreenButton.action_name = "win.fullscreenToggle"; 125 | }, 126 | 127 | _initMenus: function () { 128 | let menu = new Gio.Menu(); 129 | menu.append("New", 'app.new'); 130 | menu.append("Open", 'app.open'); 131 | menu.append("Quit", 'app.quit'); 132 | 133 | this.application.set_app_menu(menu); 134 | 135 | let quitAction = new Gio.SimpleAction({name: 'quit' }); 136 | quitAction.connect('activate', Lang.bind(this, 137 | function() { 138 | this._window.destroy(); 139 | })); 140 | this.application.add_action(quitAction); 141 | }, 142 | 143 | _newCB: function() { 144 | print("You clicked 'New'."); 145 | }, 146 | 147 | _openCB: function() { 148 | print("You clicked 'Open'."); 149 | }, 150 | 151 | _undoCB:function () { 152 | print ("You clicked 'Undo'."); 153 | }, 154 | 155 | _fullscreenToggleCB: function() { 156 | if ((this._window.get_window().get_state() & Gdk.WindowState.FULLSCREEN) != 0 ) { 157 | this._window.unfullscreen(); 158 | this._leaveFullscreenButton.hide(); 159 | this._fullscreenButton.show(); 160 | } 161 | else { 162 | this._window.fullscreen(); 163 | this._fullscreenButton.hide(); 164 | this._leaveFullscreenButton.show(); 165 | } 166 | } 167 | }); 168 | 169 | //run the application 170 | let app = new Application(); 171 | app.application.run(ARGV); -------------------------------------------------------------------------------- /init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | nohup broadwayd :5 & 4 | 5 | mkdir -p /root/Desktop 6 | mkdir -p /root/.config/nautilus 7 | sleep 5 8 | 9 | #xfce4-terminal 10 | #remmina 11 | #nautilus 12 | #gnome-terminal 13 | #/tilix/tilix 14 | #midori 15 | gsettings set org.gnome.desktop.interface gtk-theme Arc-Darker 16 | 17 | git clone https://github.com/optimisme/gjs-examples.git 18 | cd /gjs-examples 19 | gjs egHeader.js 20 | # cd /gjs 21 | # gjs window.js -------------------------------------------------------------------------------- /screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moondev/gtk3-docker/7f72a2e2f27ee3a3af7f5c3c64ad79f58b7e1874/screen.png --------------------------------------------------------------------------------