From ca2a2992b0c3c3280dbd4a46bcb0dafc46778aba Mon Sep 17 00:00:00 2001 From: Alvarito050506 Date: Thu, 22 Jul 2021 23:51:26 -0300 Subject: [PATCH] Vala rewrite (v0.13.0). --- .github/workflows/build.yml | 31 ++ .github/workflows/build_amd64.yml | 22 -- .gitignore | 2 + Makefile | 111 +++---- README.md | 30 +- ci.sh | 16 + res/control | 13 + .../share}/applications/gmcpil.desktop | 0 res/{ => usr/share}/doc/gmcpil/CHANGELOG.txt | 5 + res/{ => usr/share}/doc/gmcpil/copyright | 0 .../icons/hicolor/128x128/apps/gmcpil.png | Bin .../icons/hicolor/16x16/apps/gmcpil.png | Bin .../icons/hicolor/24x24/apps/gmcpil.png | Bin .../icons/hicolor/32x32/apps/gmcpil.png | Bin .../icons/hicolor/48x48/apps/gmcpil.png | Bin .../icons/hicolor/64x64/apps/gmcpil.png | Bin screenshot.png | Bin 26380 -> 30802 bytes src/config.c | 222 -------------- src/config.vala | 159 ++++++++++ src/features.c | 252 ---------------- src/features.vala | 55 ++++ src/gmcpil.vapi | 5 + src/gui.vala | 218 ++++++++++++++ src/include/config.h | 124 -------- src/include/mcpil.h | 87 ------ src/include/splashes.h | 77 ----- src/mcpil.c | 151 ---------- src/play.c | 282 ------------------ src/play.vala | 167 +++++++++++ src/profile.vala | 115 +++++++ src/servers.c | 116 ------- src/servers.vala | 49 +++ 32 files changed, 877 insertions(+), 1432 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/build_amd64.yml create mode 100755 ci.sh create mode 100644 res/control rename res/{ => usr/share}/applications/gmcpil.desktop (100%) rename res/{ => usr/share}/doc/gmcpil/CHANGELOG.txt (89%) rename res/{ => usr/share}/doc/gmcpil/copyright (100%) rename res/{ => usr/share}/icons/hicolor/128x128/apps/gmcpil.png (100%) rename res/{ => usr/share}/icons/hicolor/16x16/apps/gmcpil.png (100%) rename res/{ => usr/share}/icons/hicolor/24x24/apps/gmcpil.png (100%) rename res/{ => usr/share}/icons/hicolor/32x32/apps/gmcpil.png (100%) rename res/{ => usr/share}/icons/hicolor/48x48/apps/gmcpil.png (100%) rename res/{ => usr/share}/icons/hicolor/64x64/apps/gmcpil.png (100%) delete mode 100644 src/config.c create mode 100644 src/config.vala delete mode 100644 src/features.c create mode 100644 src/features.vala create mode 100644 src/gmcpil.vapi create mode 100644 src/gui.vala delete mode 100644 src/include/config.h delete mode 100644 src/include/mcpil.h delete mode 100644 src/include/splashes.h delete mode 100644 src/mcpil.c delete mode 100644 src/play.c create mode 100644 src/play.vala create mode 100644 src/profile.vala delete mode 100644 src/servers.c create mode 100644 src/servers.vala diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7398a69 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,31 @@ +name: Build all + +on: + push: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: docker-practice/actions-setup-docker@master + - name: Setup Docker Multiarch + run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - name: Build ARM + run: | + docker pull arm32v7/buildpack-deps:buster + docker run -v $PWD:$PWD -w $PWD --rm arm32v7/buildpack-deps:buster bash ./ci.sh + - name: Build AArch64 + run: | + docker pull arm64v8/buildpack-deps:buster + docker run -v $PWD:$PWD -w $PWD --rm arm64v8/buildpack-deps:buster bash ./ci.sh + - name: Build x86_64 + run: | + docker pull amd64/buildpack-deps:buster + docker run -v $PWD:$PWD -w $PWD --rm amd64/buildpack-deps:buster bash ./ci.sh + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: gmcpil_debs + path: gmcpil_*.deb diff --git a/.github/workflows/build_amd64.yml b/.github/workflows/build_amd64.yml deleted file mode 100644 index 90401a4..0000000 --- a/.github/workflows/build_amd64.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Build amd64 - -on: - push: - branches: [ master ] - -jobs: - build_amd64: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - name: Install Dependencies - run: sudo apt update && sudo apt install -y git make fakeroot binutils-arm-linux-gnueabihf gcc-arm-linux-gnueabihf dpkg-dev build-essential crossbuild-essential-armhf libgtk-3-dev libjson-glib-dev - - name: Make - run: make - - name: Make DEB - run: make pack - - name: Upload artifacts - uses: actions/upload-artifact@v2 - with: - name: gmcpil_latest_amd64.deb - path: gmcpil_*.deb diff --git a/.gitignore b/.gitignore index 1cde438..14787ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Builds build/ +*.c +*.o # Debian *.deb diff --git a/Makefile b/Makefile index 9e4f632..d28b695 100644 --- a/Makefile +++ b/Makefile @@ -1,104 +1,61 @@ -# -# Makefile -# -# Copyright 2021 Alvarito050506 -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# +VERSION:=0.13.0 +SRCS:=$(patsubst %,./src/%.vala,profile config gui play features servers) +SRCS+=./src/gmcpil.vapi -ifdef USE_CLANG -ifeq ($(origin CC),default) -CC:=clang -endif -STRIP?=llvm-strip -LDFLAGS:=-fuse-ld=lld -ARCH:=$(shell $(CC) -dumpmachine | grep -Eo "arm|aarch|86|x86_64") -else -ifeq ($(origin CC),default) -CC:=gcc -endif STRIP?=strip -LDFLAGS:= -ARCH:=$(shell $(CC) -print-multiarch | grep -Eo "arm|aarch|86|x86_64") +PKG_CONFIG?=pkg-config +ifdef CROSS_COMPILE +CC:=$(CROSS_COMPILE)-gcc +PKG_CONFIG:=$(CROSS_COMPILE)-pkg-config +STRIP:=$(CROSS_COMPILE)-strip endif -VERSION:=0.12.0 - -OBJS:=$(patsubst %,build/%.o,mcpil config features play servers) +VFLAGS:=--pkg json-glib-1.0 --pkg gio-2.0 --pkg gtk+-3.0 +VFLAGS+=--cc=$(CC) --pkg-config=$(PKG_CONFIG) -X -DGMCPIL_VERSION="\"v$(VERSION)\"" -CFLAGS+=-DGMCPIL_VERSION=\"v$(VERSION)\" -I./src/include -Wall -Wno-address-of-packed-member -Wno-pointer-to-int-cast -Wno-unused-result -CFLAGS+=$(shell pkg-config --cflags gtk+-3.0 json-glib-1.0) -LDFLAGS+=-Wl,--no-undefined $(shell pkg-config --libs gtk+-3.0 json-glib-1.0) +ARCH:=$(shell $(CC) -dumpmachine | grep -Eo "arm|aarch|86|x86_64") +ifeq ($(ARCH),86) + DEB_ARCH:=i386 +else ifeq ($(ARCH),x86_64) + DEB_ARCH:=amd64 +else ifeq ($(ARCH),aarch) + DEB_ARCH:=arm64 +else + DEB_ARCH:=armhf +endif ifdef DEBUG -CFLAGS+=-g -Wextra -Werror +VFLAGS+=-X -g --save-temps --fatal-warnings else -CFLAGS+=-O3 +VFLAGS+=-X -O3 endif -# Some GTK+/GDK-Pixbuf combinations may generate this warning -# for internal functions, so let's "ignore" it. -CFLAGS+=-Wno-error=deprecated-declarations - -.PHONY: ./build/gmcpil +ifndef NO_BUSTER_COMPAT +VFLAGS+=-D BUSTER_COMPAT +endif -./build/gmcpil: mkdir $(OBJS) - $(CC) -fPIC -fpie $(OBJS) -o $@ $(CFLAGS) $(LDFLAGS) +./build/gmcpil: ./build/ $(SRCS) + valac $(VFLAGS) $(SRCS) -o ./build/gmcpil ifndef DEBUG $(STRIP) ./build/gmcpil endif -./build/%.o: ./src/%.c ./src/include/*.h - $(CC) -fPIC -fpie -c $< -o $@ $(CFLAGS) - -mkdir: +./build/: mkdir -p ./build/ pack: ./build/gmcpil mkdir -p ./deb/DEBIAN/ mkdir -p ./deb/usr/bin/ - mkdir -p ./deb/usr/share/ cp ./build/gmcpil ./deb/usr/bin/ - cp -r ./res/. ./deb/usr/share/ + cp -r ./res/usr/share/ ./deb/usr/ chmod a+x ./deb/usr/bin/gmcpil - @echo "Package: gmcpil" > ./deb/DEBIAN/control - @echo "Version: $(VERSION)" >> ./deb/DEBIAN/control - @echo "Priority: optional" >> ./deb/DEBIAN/control -ifeq ($(ARCH),86) - @echo "Architecture: i386" >> ./deb/DEBIAN/control -else ifeq ($(ARCH),x86_64) - @echo "Architecture: amd64" >> ./deb/DEBIAN/control -else ifeq ($(ARCH),aarch) - @echo "Architecture: arm64" >> ./deb/DEBIAN/control -else - @echo "Architecture: armhf" >> ./deb/DEBIAN/control -endif - @echo "Section: contrib/misc" >> ./deb/DEBIAN/control - @echo "Depends: libc6 (>= 2.28), minecraft-pi-reborn-client (>= 2.1.0), libgtk-3-0, libjson-glib-1.0-0" >> ./deb/DEBIAN/control - @echo "Maintainer: Alvarito050506 " >> ./deb/DEBIAN/control - @echo "Homepage: https://mcpirevival.tk" >> ./deb/DEBIAN/control - @echo "Vcs-Browser: https://github.com/MCPI-Revival/gMCPIL" >> ./deb/DEBIAN/control - @echo "Vcs-Git: https://github.com/MCPI-Revival/gMCPIL.git" >> ./deb/DEBIAN/control - @echo "Description: Simple launcher for Minecraft: Pi Edition - GTK+ Edition." >> ./deb/DEBIAN/control - @echo " Simple GUI launcher for Minecraft: Pi Edition and MCPI-Reborn," >> ./deb/DEBIAN/control - @echo " rewritten in C using GTK+ 3." >> ./deb/DEBIAN/control - fakeroot dpkg-deb -b ./deb/ ./gmcpil_$(VERSION).deb + sed "s/{{ARCH}}/$(DEB_ARCH)/g; s/{{VERSION}}/$(VERSION)/g" ./res/control > ./deb/DEBIAN/control + fakeroot dpkg-deb -b ./deb/ ./gmcpil_$(VERSION)_$(DEB_ARCH).deb clean: rm -rf ./deb/ + rm -rf ./src/*.c rm -rf ./build/ + +purge: clean rm -f ./gmcpil_*.deb diff --git a/README.md b/README.md index 6ae8ca8..8049c74 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,16 @@

gMCPIL

- Simple launcher for Minecraft: Pi Edition and MCPI-Reborn - GTK+ Edition. + Simple launcher for Minecraft: Pi Edition and MCPI-Reborn - Vala/GTK+ Edition.

- GPL-2.0 + GPL-2.0 Latest release

-

screenshot

@@ -34,36 +33,23 @@ sudo apt install gmcpil You can also download and install it from the [releases](https://github.com/MCPI-Revival/gMCPIL/releases) section. ### Compiling -To build gMCPIL, you'll need GCC or Clang for the native and `arm-linux-gnueabihf` targets, plus some additional packages. -Assuming a Debian-based distro, you can install them with the following command: -```sh -# For GCC -sudo apt install gcc build-essential gcc-arm-linux-gnueabihf crossbuild-essential-armhf - -# Libraries and other build dependencies -sudo apt install git make libjson-glib-dev libgtk-3-dev dpkg-dev fakeroot -``` +To build gMCPIL, you'll need Vala, a C compiler, and some additional packages. +Assuming a Debian-based distro, you can install them running the commands listed +[here](https://github.com/MCPI-Revival/gMCPIL/blob/master/ci.sh#L7-L13) as root. After installing the build dependencies, you can clone the repo and build gMCPIL: ```sh git clone https://github.com/MCPI-Revival/gMCPIL cd gMCPIL -make # Build gMCPIL and mods +make # Build gMCPIL make pack # Make a Debian package ``` -You can also set the `DEBUG` and `USE_CLANG` environment variables to add debug symbols to the executable -and build using Clang instead of GCC, respectively. - -## Features - + Full MCPI-Reborn integration - + Proxy-free multiplayer - + Featured servers - + Coming soon: More stuff +You can also set the `DEBUG` environment variable to add debug symbols to the executable. ## Changelog -See the [CHANGELOG.txt](https://github.com/MCPI-Revival/gMCPIL/blob/master/res/doc/gmcpil/CHANGELOG.txt) file. +See the [CHANGELOG.txt](https://github.com/MCPI-Revival/gMCPIL/blob/master/res/usr/share/doc/gmcpil/CHANGELOG.txt) file. ## Licensing All the code of this project is licensed under the [GNU General Public License version 2.0](https://github.com/Alvarito050506/MCPIL/blob/master/LICENSE) (GPL-2.0). diff --git a/ci.sh b/ci.sh new file mode 100755 index 0000000..b7d55f5 --- /dev/null +++ b/ci.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +export DEBIAN_FRONTEND="noninteractive" + +echo "deb [trusted=yes] https://deb.debian.org/debian buster-backports main contrib non-free" >> /etc/apt/sources.list + +apt update +apt upgrade -y +apt install -y git make fakeroot dpkg-dev build-essential libgtk-3-dev libjson-glib-dev +apt install -y -t buster-backports valac +apt --fix-broken install + +make pack +make clean diff --git a/res/control b/res/control new file mode 100644 index 0000000..6f6fd41 --- /dev/null +++ b/res/control @@ -0,0 +1,13 @@ +Package: gmcpil +Version: {{VERSION}} +Priority: optional +Architecture: {{ARCH}} +Section: contrib/misc +Depends: libc6 (>= 2.28), minecraft-pi-reborn-client (>= 2.1.0), libgtk-3-0, libjson-glib-1.0-0 +Maintainer: Alvarito050506 +Homepage: https://mcpirevival.tk +Vcs-Browser: https://github.com/MCPI-Revival/gMCPIL +Vcs-Git: https://github.com/MCPI-Revival/gMCPIL.git +Description: Simple launcher for Minecraft: Pi Edition - GTK+ Edition. + Simple GUI launcher for Minecraft: Pi Edition and MCPI-Reborn, + rewritten in Vala using GTK+ 3. diff --git a/res/applications/gmcpil.desktop b/res/usr/share/applications/gmcpil.desktop similarity index 100% rename from res/applications/gmcpil.desktop rename to res/usr/share/applications/gmcpil.desktop diff --git a/res/doc/gmcpil/CHANGELOG.txt b/res/usr/share/doc/gmcpil/CHANGELOG.txt similarity index 89% rename from res/doc/gmcpil/CHANGELOG.txt rename to res/usr/share/doc/gmcpil/CHANGELOG.txt index 9d08595..4eaf96e 100644 --- a/res/doc/gmcpil/CHANGELOG.txt +++ b/res/usr/share/doc/gmcpil/CHANGELOG.txt @@ -1,3 +1,8 @@ +v0.13.0: + + Rewritten in Vala. + + More refactoring. + + Fixed various bugs. + v0.12.0: + New, fancy GUI. + Lots of "refactoring". diff --git a/res/doc/gmcpil/copyright b/res/usr/share/doc/gmcpil/copyright similarity index 100% rename from res/doc/gmcpil/copyright rename to res/usr/share/doc/gmcpil/copyright diff --git a/res/icons/hicolor/128x128/apps/gmcpil.png b/res/usr/share/icons/hicolor/128x128/apps/gmcpil.png similarity index 100% rename from res/icons/hicolor/128x128/apps/gmcpil.png rename to res/usr/share/icons/hicolor/128x128/apps/gmcpil.png diff --git a/res/icons/hicolor/16x16/apps/gmcpil.png b/res/usr/share/icons/hicolor/16x16/apps/gmcpil.png similarity index 100% rename from res/icons/hicolor/16x16/apps/gmcpil.png rename to res/usr/share/icons/hicolor/16x16/apps/gmcpil.png diff --git a/res/icons/hicolor/24x24/apps/gmcpil.png b/res/usr/share/icons/hicolor/24x24/apps/gmcpil.png similarity index 100% rename from res/icons/hicolor/24x24/apps/gmcpil.png rename to res/usr/share/icons/hicolor/24x24/apps/gmcpil.png diff --git a/res/icons/hicolor/32x32/apps/gmcpil.png b/res/usr/share/icons/hicolor/32x32/apps/gmcpil.png similarity index 100% rename from res/icons/hicolor/32x32/apps/gmcpil.png rename to res/usr/share/icons/hicolor/32x32/apps/gmcpil.png diff --git a/res/icons/hicolor/48x48/apps/gmcpil.png b/res/usr/share/icons/hicolor/48x48/apps/gmcpil.png similarity index 100% rename from res/icons/hicolor/48x48/apps/gmcpil.png rename to res/usr/share/icons/hicolor/48x48/apps/gmcpil.png diff --git a/res/icons/hicolor/64x64/apps/gmcpil.png b/res/usr/share/icons/hicolor/64x64/apps/gmcpil.png similarity index 100% rename from res/icons/hicolor/64x64/apps/gmcpil.png rename to res/usr/share/icons/hicolor/64x64/apps/gmcpil.png diff --git a/screenshot.png b/screenshot.png index 2df238763949f789b670b2782bdadb875df3923e..66b1506204bc2b709c73e93727f4142af383e546 100644 GIT binary patch literal 30802 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 Trr%Bq{B=QAQ6^35+JpZG3>6LR literal 26380 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 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * - */ - -#define _GNU_SOURCE /* Required for asprintf */ - -#include -#include - -#include -#include -#include - -struct __attribute__((packed)) GMCPILConfigPrivate -{ - gchar* username; - gchar* features; - gchar* distance; - gchar* last_profile; - gchar* hud; - gchar* hide; - gchar* last_featc; - gchar* filename; -}; - -enum -{ - PROP_NULL, - PROP_USERNAME, - PROP_FEATURES, - PROP_DISTANCE, - PROP_LAST_PROFILE, - PROP_HUD, - PROP_HIDE, - PROP_LAST_FEATC, - PROP_LAST -}; - -G_DEFINE_TYPE_WITH_CODE(GMCPILConfig, gmcpil_config, G_TYPE_OBJECT, G_ADD_PRIVATE(GMCPILConfig)) - -static void gmcpil_config_finalize(GObject* obj); -static void gmcpil_config_set_property(GObject* obj, guint prop_id, const GValue* value, GParamSpec* pspec); -static void gmcpil_config_get_property(GObject* obj, guint prop_id, GValue* value, GParamSpec* pspec); - -static void gmcpil_config_class_init(GMCPILConfigClass* klass) -{ - GObjectClass* gobject_class = G_OBJECT_CLASS(klass); - GParamSpec* pspec; - - gobject_class->finalize = gmcpil_config_finalize; - - gobject_class->get_property = gmcpil_config_get_property; - gobject_class->set_property = gmcpil_config_set_property; - - GMCPIL_GLIB_PROPERTY("username", "Username", "Player name", PROP_USERNAME); - GMCPIL_GLIB_PROPERTY("features", "Features", "MCPI-Reborn features", PROP_FEATURES); - GMCPIL_GLIB_PROPERTY("distance", "Distance", "Render distance", PROP_DISTANCE); - GMCPIL_GLIB_PROPERTY("last_profile", "Last profile", "Last selected profile", PROP_LAST_PROFILE); - GMCPIL_GLIB_PROPERTY("hud", "Gallium HUD", "Gallium HUD options", PROP_HUD); - GMCPIL_GLIB_PROPERTY("hide", "Hide launcher", "Hide launcher on launch", PROP_HIDE); - GMCPIL_GLIB_PROPERTY("last_featc", "Last feature count", "Last MCPI-Reborn feature count", PROP_LAST_FEATC); - return; -} - -static void gmcpil_config_init(GMCPILConfig* self) -{ - int i = 0; - GMCPILConfigPrivate* private = GMCPIL_CONFIG_PRIVATE(self); - gchar** private_gchar = (gchar**)(private); - - while (i < PROP_LAST - 1) - { - private_gchar[i] = NULL; - i++; - } - return; -} - -static void gmcpil_config_finalize(GObject* obj) -{ - int i = 0; - GMCPILConfig* self = GMCPIL_CONFIG(obj); - GMCPILConfigPrivate* private = GMCPIL_CONFIG_PRIVATE(self); - GObjectClass* parent_class = G_OBJECT_CLASS(gmcpil_config_parent_class); - gchar** private_gchar = (gchar**)(private); - - while (i < PROP_LAST - 1) - { - if (private_gchar[i] != NULL) - { - g_free(private_gchar[i]); - } - i++; - } - (*parent_class->finalize)(obj); - return; -} - -GMCPIL_GETTER_SETTER(username); -GMCPIL_GETTER_SETTER(features); -GMCPIL_GETTER_SETTER(distance); -GMCPIL_INT_GETTER_SETTER(last_profile); -GMCPIL_GETTER_SETTER(hud); -GMCPIL_GETTER_SETTER(hide); -GMCPIL_INT_GETTER_SETTER(last_featc); - -static void gmcpil_config_set_property(GObject* obj, guint prop_id, const GValue* value, GParamSpec* pspec) -{ - GMCPILConfig* self = GMCPIL_CONFIG(obj); - GMCPILConfigPrivate* private = GMCPIL_CONFIG_PRIVATE(self); - gchar** private_gchar = (gchar**)(private); - - if (prop_id <= PROP_LAST) - { - private_gchar[prop_id - 1] = g_strdup((gchar*)g_value_get_string(value)); - } else - { - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); - } - return; -} - -static void gmcpil_config_get_property(GObject* obj, guint prop_id, GValue* value, GParamSpec* pspec) -{ - GMCPILConfig* self = GMCPIL_CONFIG(obj); - GMCPILConfigPrivate* private = GMCPIL_CONFIG_PRIVATE(self); - gchar** private_gchar = (gchar**)(private); - - if (prop_id <= PROP_LAST) - { - g_value_set_string(value, private_gchar[prop_id - 1]); - } else - { - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); - } - return; -} - -GMCPILConfig* gmcpil_config_new(gchar* filename) -{ - int sz; - char* buff; - FILE* fd; - GMCPILConfig* self; - GMCPILConfigPrivate* private; - GObject* obj; - GError* err = NULL; - - fd = fopen(filename, "a+"); - if (fd == NULL) - { - return NULL; - } - - fseek(fd, 0, SEEK_END); - sz = ftell(fd); - fseek(fd, 0, SEEK_SET); - - if (sz <= 13) /* {"ip":"a.co"} */ - { - buff = (char*)malloc(2); - buff[0] = '{'; - buff[1] = '}'; - sz = 2; - } else - { - buff = (char*)malloc(sz + 1); - fread(buff, 1, sz, fd); - } - - obj = json_gobject_from_data(GMCPIL_TYPE_CONFIG, buff, sz, &err); - if (err != NULL) - { - err = NULL; - obj = g_object_new(GMCPIL_TYPE_CONFIG, 0); - } - self = GMCPIL_CONFIG(obj); - private = GMCPIL_CONFIG_PRIVATE(self); - private->filename = g_strdup(filename); - - free(buff); - fclose(fd); - return self; -} - -int gmcpil_config_save(GMCPILConfig* self) -{ - char* buff; - FILE* fd; - GMCPILConfigPrivate* private = GMCPIL_CONFIG_PRIVATE(self); - - fd = fopen(private->filename, "w"); - if (fd == NULL) - { - return -1; - } - - buff = json_gobject_to_data(G_OBJECT(self), NULL); - fwrite(buff, 1, strlen(buff), fd); - - free(buff); - fclose(fd); - return 0; -} diff --git a/src/config.vala b/src/config.vala new file mode 100644 index 0000000..a676aa5 --- /dev/null +++ b/src/config.vala @@ -0,0 +1,159 @@ +using Gtk; + +namespace GMCPIL +{ + public enum Distance + { + TINY, + SHORT, + NORMAL, + FAR + } + + public class Config : Object, Json.Serializable + { + public bool hide { get; set; default = true; } + public string hud { get; set; default = "simple,fps"; } + public string username { get; set; default = "StevePi"; } + public Object features { get; set; } + public ProfileType profile { get; set; } + public Distance distance { get; set; default = Distance.SHORT; } + public bool modified = false; + string filename; + public TextBuffer servers; + + public static string[] distance_names = { + "Tiny", + "Short", + "Normal", + "Far" + }; + + construct + { + this.notify.connect(() => { + modified = true; + }); + } + + public static Config from_file(string filename) throws Error + { + File file; + FileInputStream stream; + Json.Parser parser; + Json.Node root; + Json.Object root_obj; + Config config; + + file = File.new_for_path(filename); + if (!file.query_exists()) + { + file.create(FileCreateFlags.NONE); + } + stream = file.read(); + + parser = new Json.Parser(); + parser.load_from_stream(stream); + + root = parser.get_root(); + if (root == null) + { + root = new Json.Node(Json.NodeType.OBJECT); + root_obj = new Json.Object(); + root.set_object(root_obj); + } + + config = Json.gobject_deserialize(typeof(Config), root) as Config; + config.filename = filename; + if (config.features == null) + { + config.features = Profile.deserialize(new Json.Object()); + } + stream.close(); + Profile.profiles[ProfileType.CUSTOM] = config.features; + return config; + } + + public void save() throws Error + { + File file; + string home; + string buff; + string servers_path; + TextIter start; + TextIter end; + DataOutputStream stream; + Json.Node root; + Json.Generator gen; + + root = Json.gobject_serialize(this); + gen = new Json.Generator(); + gen.set_root(root); + gen.to_file(filename); + + servers.get_bounds(out start, out end); + buff = servers.get_text(start, end, false); + + home = Environment.get_variable("HOME"); + servers_path = Path.build_filename(home, ".minecraft-pi", "servers.txt"); + file = File.new_for_path(servers_path); + stream = new DataOutputStream(file.replace(null, false, FileCreateFlags.NONE)); + stream.put_string(buff, null); + return; + } + + public void set_env() + { + string name; + bool enabled; + StringBuilder builder; + + if (!modified) + { + return; + } + builder = new StringBuilder(); + for (int i = 0; i < Profile.features.length; i++) + { + name = Profile.features.index(i); + enabled = Profile.profiles[profile].get_data(name); + if (enabled) + { + builder.append_printf("%s|", name); + } + } + Environment.set_variable("GALLIUM_HUD", hud, true); + Environment.set_variable("MCPI_USERNAME", username, true); + Environment.set_variable("MCPI_FEATURE_FLAGS", builder.str, true); + Environment.set_variable("MCPI_RENDER_DISTANCE", distance_names[distance], true); + modified = false; + return; + } + + public Json.Node serialize_property(string name, Value val, ParamSpec spec) + { + Json.Node node; + + if (name != "features") + { + return default_serialize_property(name, val, spec); + } + + node = new Json.Node(Json.NodeType.OBJECT); + node.set_object(Profile.serialize(val.get_object())); + return node; + } + + public bool deserialize_property(string name, out Value val, ParamSpec spec, Json.Node node) + { + if (name != "features") + { + return default_deserialize_property(name, out val, spec, node); + } + + val = Value(typeof(Object)); + val.set_object(Profile.deserialize(node.get_object())); + return true; + } + } +} diff --git a/src/features.c b/src/features.c deleted file mode 100644 index 649994a..0000000 --- a/src/features.c +++ /dev/null @@ -1,252 +0,0 @@ -/* - * features.c - gMCPIL features tab - * - * Copyright 2021 Alvarito050506 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * - */ - -#include -#include -#include -#include -#include - -#include -#include - -int featc; -char* features_envs[5] = {""}; -feature_t features[32]; - -static int get_features() -{ - int i = 0; - int sz = 0; - int offset = 0; - size_t buff_sz; - char* buff; - FILE* file; - - file = fopen("/opt/minecraft-pi-reborn-client/available-feature-flags", "r"); - if (file == NULL) - { - return -1; - } - - buff_sz = 48; - buff = (char*)malloc((int)buff_sz + 1); - buff[0] = 0x00; - while ((sz = getline(&buff, &buff_sz, file)) > 0) - { - FEAT_BOOL(i) = (void*)(intptr_t)(buff[0] == 'T'); - buff[sz - 1] = 0x00; - offset = FEAT_BOOL(i) == FALSE ? 6 : 5; - FEAT_PTR(i) = strdup(buff + offset); - i++; - } - free(buff); - fclose(file); - return i; -} - -static int set_feature_envs(int feat) -{ - int i = 0; - int sz[3] = {1, 1, 1}; - int len[3] = {0, 0, 0}; - int tmp[3] = {0, 0, 0}; - - while (i < 3) - { - if (i == 0 && FEAT_CMP(feat, "Touch GUI")) - { - i++; - continue; - } - if (i == 2 && (FEAT_CMP(feat, "Fancy Graphics") - || FEAT_CMP(feat, "Smooth Lighting") - || FEAT_CMP(feat, "Animated Water") - || FEAT_CMP(feat, "Disable gui_blocks Atlas"))) - { - i++; - continue; - } - if (FEAT_INT(feat) == TRUE || (i >= 2 && i <= 3 && FEAT_CMP(feat, "Force Mob Spawning"))) - { - len[i] = strlen(FEAT_STR(feat)); - tmp[i] = sz[i] + len[i] + 1; - features_envs[i + 1] = (char*)realloc((void*)features_envs[i + 1], tmp[i]); - if (sz[i] == 1) - { - strcpy(features_envs[i + 1], FEAT_STR(feat)); - } else - { - strcat(features_envs[i + 1], FEAT_STR(feat)); - } - features_envs[i + 1][tmp[i] - 2] = '|'; - features_envs[i + 1][tmp[i] - 1] = 0x00; - sz[i] = tmp[i]; - } - i++; - } - return 0; -} - -static feature_t* get_feature(char* str) -{ - int i = 0; - - while (i < featc) - { - if (FEAT_CMP(i, str)) - { - return &(features[i]); - } - i++; - } - return NULL; -} - -static void features_cb(__attribute__((unused)) GtkWidget* button, void* udata) -{ - int i = 0; - int sz = 1; - int len = 0; - int tmp = 0; - - if (features_envs[4] != NULL) - { - free(features_envs[4]); - } - features_envs[4] = (char*)malloc(1); - - while (i < featc) - { - if (FEAT_INT(i) == TRUE) - { - len = strlen(FEAT_STR(i)); - tmp = sz + len + 1; - features_envs[4] = (char*)realloc((void*)features_envs[4], tmp); - if (sz == 1) - { - strcpy(features_envs[4], FEAT_STR(i)); - } else - { - strcat(features_envs[4], FEAT_STR(i)); - } - features_envs[4][tmp - 2] = '|'; - features_envs[4][tmp - 1] = 0x00; - sz = tmp; - } - i++; - } - - if (udata == NULL) - { - gmcpil_config_set_features(config, features_envs[4]); - gmcpil_config_save(config); - } - return; -} - -static void toggle_cb(__attribute__((unused)) GtkWidget* check, void* udata) -{ - int i = (int)udata; - - FEAT_BOOL(i) = (void*)(intptr_t)(!FEAT_INT(i)); - return; -} - -TAB(Features, features_cb, { - int i = 0; - int last_profile; - int last_featc; - char* tmp; - char* features_buff; - GtkWidget* feature_check; - GtkWidget* scroll; - GtkWidget* vbox; - feature_t* feature; - - featc = 0; - featc = get_features(); - last_featc = gmcpil_config_get_last_featc(config); - last_profile = gmcpil_config_get_last_profile(config); - - features_envs[1] = (char*)malloc(1); - features_envs[2] = (char*)malloc(1); - features_envs[3] = (char*)malloc(1); - features_envs[4] = NULL; - while (i < featc) - { - set_feature_envs(i); - i++; - } - - features_buff = gmcpil_config_get_features(config); - if (features_buff != NULL) - { - i = 0; - while (i < last_featc) - { - FEAT_BOOL(i) = FALSE; - i++; - } - - i = 0; - tmp = strtok(features_buff, "|"); - while (tmp != NULL && i < last_featc) - { - feature = get_feature(tmp); - if (feature != NULL) - { - *feature[0] = (void*)TRUE; - } - tmp = strtok(NULL, "|"); - i++; - } - } else - { - printf("Yeah, no.\n"); - } - - scroll = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_NONE); - gtk_container_set_border_width(GTK_CONTAINER(scroll), 0); - - vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_set_margin_top(vbox, 6); - - i = 0; - while (i < featc) - { - feature_check = gtk_check_button_new_with_label(FEAT_STR(i)); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(feature_check), FEAT_INT(i)); - g_signal_connect(feature_check, "toggled", G_CALLBACK(toggle_cb), (void*)(intptr_t)i); - gtk_box_pack_start(GTK_BOX(vbox), feature_check, FALSE, FALSE, 0); - i++; - } - - features_cb(NULL, (void*)TRUE); - gmcpil_config_set_last_featc(config, featc); - gmcpil_config_set_features(config, features_envs[4]); - setenv("MCPI_FEATURE_FLAGS", features_envs[last_profile], 1); - gtk_container_add(GTK_CONTAINER(scroll), vbox); - gtk_box_pack_start(GTK_BOX(tab), scroll, TRUE, TRUE, 0); -}); diff --git a/src/features.vala b/src/features.vala new file mode 100644 index 0000000..a8e96c3 --- /dev/null +++ b/src/features.vala @@ -0,0 +1,55 @@ +using Gtk; + +namespace GMCPIL +{ + public class FeatureButton : Bin + { + private string feat_name; + private Config config; + private CheckButton button; + + public FeatureButton(Config config, string feat_name, bool feat_enabled) + { + Object(); + this.feat_name = feat_name; + this.config = config; + + button = new CheckButton.with_label(feat_name); + button.set_active(feat_enabled); + button.toggled.connect(toggled_cb); + add_with_properties(button, null); + } + + private void toggled_cb() + { + config.features.set_data(feat_name, button.get_active()); + config.modified = true; + } + } + + public void features_tab(Config config, Box tab) throws Error + { + string name; + bool enabled; + Box list_vbox; + FeatureButton button; + ScrolledWindow scroll; + + list_vbox = new Box(Orientation.VERTICAL, 0); + scroll = new ScrolledWindow(null, null); + scroll.set_policy(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC); + scroll.set_shadow_type(ShadowType.NONE); + scroll.set_border_width(0); + + for (int i = 0; i < Profile.features.length; i++) + { + name = Profile.features.index(i); + enabled = config.features.get_data(name); + button = new FeatureButton(config, name, enabled); + list_vbox.pack_start(button, false, false, 0); + } + + scroll.add(list_vbox); + tab.pack_start(scroll, true, true, 4); + } +} diff --git a/src/gmcpil.vapi b/src/gmcpil.vapi new file mode 100644 index 0000000..b748aaf --- /dev/null +++ b/src/gmcpil.vapi @@ -0,0 +1,5 @@ +[CCode (cheader_filename = "")] +namespace GMCPIL +{ + public const string VERSION; +} diff --git a/src/gui.vala b/src/gui.vala new file mode 100644 index 0000000..575a09d --- /dev/null +++ b/src/gui.vala @@ -0,0 +1,218 @@ +using Gtk; + +namespace GMCPIL +{ + public weak string REPO_URL = "https://github.com/MCPI-Revival/gMCPIL"; + public weak string COPYRIGHT = "Copyright 2021 MCPI-Revival contributors"; + public weak string DESCRIPTION = "Simple launcher for MCPI-Reborn - Vala/GTK+ Edition."; + public weak string DEFAULT_SERVERS = "thebrokenrail.com\npbptanarchy.tk\n"; + public weak string SERVERS_LABEL = "Each line is an ip:port pair. If the port is omitted, 19132 is used."; + + public delegate void TabDelegate(Config config, Box tab) throws Error; + + public string? read_file(string path) throws Error + { + int64 sz; + uint8[]? buff; + File file; + FileInfo info; + DataInputStream stream; + + file = File.new_for_path(path); + if (!file.query_exists()) + { + file.create(FileCreateFlags.NONE); + } + stream = new DataInputStream(file.read()); + info = file.query_info("*", FileQueryInfoFlags.NONE); + sz = info.get_size(); + buff = null; + if (sz > 0) + { + buff = new uint8[sz + 1]; + stream.read(buff); + buff[sz] = 0x00; + return (string)buff; + } + return null; + } + + public class Gui : Window + { + Box hbox; + Box vbox; + Box switcher_box; + Stack stack; + StackSwitcher switcher; + HeaderBar header; + Button launch_button; + Button about_button; + Config config; + + public Gui(Config config) + { + Object( + title: "gMCPIL", + default_width: -1, + default_width: 360, + icon_name: "gmcpil" + ); + + this.config = config; + + vbox = new Box(Orientation.VERTICAL, 0); + + hbox = new Box(Orientation.HORIZONTAL, 2); + launch_button = new Button.with_label("Launch"); + launch_button.set_margin_end(2); + launch_button.set_margin_bottom(2); + + stack = new Stack(); + switcher_box = new Box(Orientation.HORIZONTAL, 0); + switcher = new StackSwitcher(); + switcher.set_stack(stack); + + about_button = new Button.from_icon_name("help-about-symbolic", IconSize.LARGE_TOOLBAR); + + header = new HeaderBar(); + header.set_show_close_button(true); + header.set_custom_title(switcher_box); + + switcher_box.pack_start(switcher, true, false, 0); + switcher_box.pack_end(about_button, true, false, 2); + + hbox.pack_end(launch_button, false, false, 0); + + vbox.pack_start(stack, true, true, 0); + vbox.pack_start(hbox, false, false, 0); + + about_button.clicked.connect(about_cb); + launch_button.clicked.connect(launch_cb); + destroy.connect(() => { + try + { + config.save(); + } catch (Error err) + { + stderr.printf("Error: %s.\n", err.message); + } + Gtk.main_quit(); + }); + + set_titlebar(header); + header.set_decoration_layout(":close"); + add(vbox); + } + + void about_cb() + { + AboutDialog about_dialog; + + about_dialog = new AboutDialog(); + about_dialog.set_program_name("gMCPIL"); + about_dialog.set_logo_icon_name("gmcpil"); + about_dialog.set_version(VERSION); + about_dialog.set_website(REPO_URL); + about_dialog.set_copyright(COPYRIGHT); + about_dialog.set_comments(DESCRIPTION); + about_dialog.run(); + about_dialog.destroy(); + return; + } + + void launch_cb() + { + Pid pid; + string[] argv = {"minecraft-pi-reborn-client"}; + + config.set_env(); + + try + { + Process.spawn_async( + null, argv, null, + SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD, + null, out pid + ); + } catch (Error err) + { + stderr.printf("Error: %s.\n", err.message); + return; + } + + if (this.config.hide) + { + this.hide(); + } + + ChildWatch.add(pid, (pid, status) => { + if (this.config.hide) + { + this.show_all(); + } + Process.close_pid(pid); + }); + } + + public void add_tab(string name, TabDelegate code) throws Error + { + Box tab; + tab = new Box(Orientation.VERTICAL, 0); + code(config, tab); + stack.add_titled(tab, name, name); + return; + } + } +} + +public static int main(string[] args) +{ + bool ubuntu; + string home; + string path; + string os_release; + GMCPIL.Config config; + GMCPIL.Gui gui; + CssProvider provider; +#if BUSTER_COMPAT + try + { + os_release = GMCPIL.read_file("/etc/os-release"); + ubuntu = "NAME=\"Ubuntu\"\n" in os_release; + } catch (Error err) + { + ubuntu = false; + } +#else + ubuntu = Environment.get_os_info(OsInfoKey.NAME) == "Ubuntu"; +#endif + + Gtk.init(ref args); + if (ubuntu) + { + provider = CssProvider.get_named("Yaru", "dark"); + } else + { + provider = CssProvider.get_named("Adwaita", "dark"); + } + StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), provider, STYLE_PROVIDER_PRIORITY_USER); + try + { + home = Environment.get_variable("HOME"); + path = Path.build_filename(home, ".gmcpil.json"); + + GMCPIL.Profile.init("/opt/minecraft-pi-reborn-client/available-feature-flags"); + config = GMCPIL.Config.from_file(path); + gui = new GMCPIL.Gui(config); + gui.add_tab("Play", GMCPIL.play_tab); + gui.add_tab("Features", GMCPIL.features_tab); + gui.add_tab("Servers", GMCPIL.servers_tab); + gui.show_all(); + Gtk.main(); + } catch (Error err) + { + stderr.printf("Error: %s.\n", err.message); + return -1; + } + return 0; +} diff --git a/src/include/config.h b/src/include/config.h deleted file mode 100644 index 867d02f..0000000 --- a/src/include/config.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * config.h - * - * Copyright 2021 Alvarito050506 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * - */ - -#ifndef CONFIG_H -#define CONFIG_H - -#include -#include - -G_BEGIN_DECLS - -#define GMCPIL_TYPE_CONFIG gmcpil_config_get_type() -#define GMCPIL_CONFIG(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, GMCPIL_TYPE_CONFIG, GMCPILConfig) -#define GMCPIL_IS_CONFIG(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, GMCPIL_TYPE_CONFIG) -#define GMCPIL_CONFIG_PRIVATE(obj) gmcpil_config_get_instance_private(obj) - -#define VALID_JSON_ARGS(dst, name, parent) \ - (parent.node != NULL || dst != NULL || name != NULL || JSON_NODE_HOLDS_OBJECT(parent.node) || parent.obj == NULL) -#define SAFE_ATOI(str) strtol(str ? str : "", NULL, 10) - -#define GMCPIL_GETTER(name) gchar* gmcpil_config_get_ ## name (GMCPILConfig* self) \ -{ \ - GMCPILConfigPrivate* private; \ - private = GMCPIL_CONFIG_PRIVATE(self); \ - return private->name; \ -} - -#define GMCPIL_SETTER(name) void gmcpil_config_set_ ## name (GMCPILConfig* self, const gchar* name) \ -{ \ - GMCPILConfigPrivate* private; \ - private = GMCPIL_CONFIG_PRIVATE(self); \ - private->name = g_strdup((gchar*)name); \ - return; \ -} - -#define GMCPIL_INT_GETTER(name) gint gmcpil_config_get_ ## name (GMCPILConfig* self) \ -{ \ - GMCPILConfigPrivate* private; \ - private = GMCPIL_CONFIG_PRIVATE(self); \ - return SAFE_ATOI(private->name); \ -} - -#define GMCPIL_INT_SETTER(name) void gmcpil_config_set_ ## name (GMCPILConfig* self, const gint name) \ -{ \ - char* tmp; \ - GMCPILConfigPrivate* private; \ - asprintf(&tmp, "%i", name); \ - private = GMCPIL_CONFIG_PRIVATE(self); \ - private->name = g_strdup((gchar*)tmp); \ - free(tmp); \ - return; \ -} - -#define GMCPIL_GETTER_SETTER(name) GMCPIL_GETTER(name); GMCPIL_SETTER(name); -#define GMCPIL_INT_GETTER_SETTER(name) GMCPIL_INT_GETTER(name); GMCPIL_INT_SETTER(name); - -#define GMCPIL_GLIB_PROPERTY(id, name, description, prop) \ - pspec = g_param_spec_string(id, name, description, NULL, G_PARAM_READWRITE); \ - g_object_class_install_property(gobject_class, prop, pspec); - -#define GMCPIL_SET_DEFAULT(name, value) \ - default_##name = gmcpil_config_get_##name(config); \ - if (default_##name == NULL) \ - { \ - default_##name = value; \ - gmcpil_config_set_##name(config, default_##name); \ - } - -typedef struct GMCPILConfigPrivate GMCPILConfigPrivate; - -typedef struct GMCPILConfig -{ - GObject parent; -} GMCPILConfig; - -typedef struct GMCPILConfigClass -{ - GObjectClass parent_class; -} GMCPILConfigClass; - -GType gmcpil_config_get_type() G_GNUC_CONST; - -GMCPILConfig* gmcpil_config_new(gchar* filename); - -void gmcpil_config_set_username(GMCPILConfig* self, const gchar* username); -void gmcpil_config_set_features(GMCPILConfig* self, const gchar* features); -void gmcpil_config_set_distance(GMCPILConfig* self, const gchar* distance); -void gmcpil_config_set_last_profile(GMCPILConfig* self, const gint last_profile); -void gmcpil_config_set_hud(GMCPILConfig* self, const gchar* hud); -void gmcpil_config_set_hide(GMCPILConfig* self, const gchar* hide); -void gmcpil_config_set_last_featc(GMCPILConfig* self, const gint last_featc); - -gchar* gmcpil_config_get_username(GMCPILConfig* self); -gchar* gmcpil_config_get_features(GMCPILConfig* self); -gchar* gmcpil_config_get_distance(GMCPILConfig* self); -gint gmcpil_config_get_last_profile(GMCPILConfig* self); -gchar* gmcpil_config_get_hud(GMCPILConfig* self); -gchar* gmcpil_config_get_hide(GMCPILConfig* self); -gint gmcpil_config_get_last_featc(GMCPILConfig* self); - -int gmcpil_config_save(GMCPILConfig* self); - -G_END_DECLS - -#endif /* CONFIG_H */ diff --git a/src/include/mcpil.h b/src/include/mcpil.h deleted file mode 100644 index 99d855f..0000000 --- a/src/include/mcpil.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * mcpil.h - gMCPIL main header - * - * Copyright 2021 Alvarito050506 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * - */ - -#ifndef GMCPIL_H -#define GMCPIL_H - -#include -#include - -#define STR(str) #str - -#define FEAT_BOOL(i) (features[i][0]) -#define FEAT_INT(i) ((int)features[i][0]) -#define FEAT_PTR(i) (features[i][1]) -#define FEAT_STR(i) ((char*)features[i][1]) -#define FEAT_CMP(i, str) (strcmp(FEAT_STR(i), str) == 0) - -#define TAB(name, button_cb, code) GtkWidget* name ## _tab(GtkWidget* stack) \ -{ \ - void* cb_udata = NULL; \ - GtkWidget* tab = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); \ - GtkWidget* button = gtk_button_new_with_label("Save"); \ - GtkWidget* button_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2); \ - code; \ - gtk_box_pack_end(GTK_BOX(button_hbox), button, FALSE, FALSE, 0); \ - gtk_box_pack_end(GTK_BOX(tab), button_hbox, FALSE, FALSE, 0); \ - gtk_stack_add_titled(GTK_STACK(stack), tab, STR(name), STR(name)); \ - g_signal_connect(button, "clicked", G_CALLBACK(button_cb), cb_udata); \ - return tab; \ -} - -#define GMCPIL_REPO_URL "https://github.com/MCPI-Revival/gMCPIL" -#define GMCPIL_COPYRIGHT "Copyright 2021 MCPI-Revival contributors" -#define GMCPIL_DESCRIPTION "Simple launcher for MCPI-Reborn - GTK+ Edition." -#define DEFAULT_SERVERS "thebrokenrail.com\npbptanarchy.tk\n" -#define SERVERS_LABEL "External multiplayer is now built-in into MCPI-Reborn,\n" \ - "this is a just simple editor for the servers.txt file.\n" \ - "Each line is an ip:port tuple. If the port is omitted,\n" \ - "the default (19132) is used.\n" - -typedef void* feature_t[2]; - -typedef struct settings_box_t -{ - GtkEntry* username_entry; - GtkEntry* hud_entry; - GtkComboBoxText* distance_combo; - GtkComboBoxText* profile_combo; - GtkCheckButton* hide_check; - char* buff; -} settings_box_t; - -/* Global variables */ -extern int featc; -extern char* servers_path; -extern char* features_envs[5]; -extern settings_box_t settings_box; -extern feature_t features[32]; -extern GMCPILConfig* config; -extern GtkWidget* window; - -/* Tabs */ -GtkWidget* Play_tab(GtkWidget* notebook); -GtkWidget* Features_tab(GtkWidget* notebook); -GtkWidget* Servers_tab(GtkWidget* notebook); -GtkWidget* Settings_tab(GtkWidget* notebook); - -#endif /* MCPIL_H */ diff --git a/src/include/splashes.h b/src/include/splashes.h deleted file mode 100644 index 3d78702..0000000 --- a/src/include/splashes.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * splashes.h - MCPIL splash texts - * - * Copyright 2021 MCPI-Revival contributors - * Copyright 2021 Alvarito050506 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * - */ - -#ifndef SPLASHES_H -#define SPLASHES_H - -#include - -extern char* splashes[41]; - -#ifdef DEFINE_SPLASHES -char* splashes[41] = { - "Sexy!", - "We Fixed The Rail!", - "Who Broke The Rail?", - "The World Of Notch!", - "WDYM Discontinued?", - "Oh, ok, Pigmen...", - "Check out the non-existant Far Lands!", - "mcpirevival.tk!", - "#alvarito-come-back", - "Classic!", - "Wow! Modded MCPI!", - "Now with fly-hacks!", - "Where Is My Pie?", - "I was promised pie!", - "@Banana", - "BANANA!", - "To \"Na\" Or Not To \"Na\", that is the question!", - "Not Minecraft Java!", - "Definitely not Minecraft Console!", - "Oh yeah, that version...", - "Join The Discord!", - "Segmentation fault (core dumped)", - "It's the Segment's Fault!", - "As seen on Discord!", - "Obfuscated!", - "As not seen on TV!", - "Why must you hurt me in this way?", - "Who is StevePi?", - "(Not) Made by Notch!", - "Open-Source!", - "FREE!", - "RIP MCPIL-Legacy, 2020-2020", - "StevePi = popbob confirmed?", - "Watch out for StevePi!", - "Didn't remove Herobrine!", - "Minceraft", - "MCPI-Docker", - "MCPI-Devs", - "RIP Picraft", - "Check out PBPT Anarchy!", - "Now without Docker!" -}; -#endif /* DEFINE_SPLASHES */ - -#endif /* SPLASHES_H */ diff --git a/src/mcpil.c b/src/mcpil.c deleted file mode 100644 index 7689403..0000000 --- a/src/mcpil.c +++ /dev/null @@ -1,151 +0,0 @@ -/* - * mcpil.c - MCPIL GTK+ Edition - * - * Copyright 2021 Alvarito050506 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * - */ - -#define _GNU_SOURCE /* Required for asprintf */ -#define DEFINE_SPLASHES - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -/* Global variables */ -settings_box_t settings_box; -GMCPILConfig* config; -GtkWidget* window; - -void about_cb(__attribute__((unused)) GtkWidget* button, __attribute__((unused)) void* udata) -{ - GdkPixbuf* logo; - GtkWidget* about_dialog; - - logo = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), "gmcpil", 64, GTK_ICON_LOOKUP_NO_SVG, NULL); - about_dialog = gtk_about_dialog_new(); - gtk_about_dialog_set_program_name(GTK_ABOUT_DIALOG(about_dialog), "gMCPIL"); - gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about_dialog), GMCPIL_VERSION); - gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(about_dialog), logo); - gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(about_dialog), GMCPIL_COPYRIGHT); - gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(about_dialog), GMCPIL_REPO_URL); - gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(about_dialog), GMCPIL_DESCRIPTION); - gtk_dialog_run(GTK_DIALOG(about_dialog)); - gtk_widget_destroy(about_dialog); - return; -} - -void activate_cb(GtkApplication* app, __attribute__((unused)) void* udata) -{ - GdkPixbuf* icon; - GtkWidget* stack; - GtkWidget* switcher; - GtkWidget* header; - GtkWidget* switcher_box; - GtkWidget* about_button; - - window = gtk_application_window_new(app); - gtk_window_set_title(GTK_WINDOW(window), "gMCPIL"); - gtk_window_set_default_size(GTK_WINDOW(window), -1, 375); - - icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), "gmcpil", 32, GTK_ICON_LOOKUP_NO_SVG, NULL); - gtk_window_set_icon(GTK_WINDOW(window), icon); - - stack = gtk_stack_new(); - - switcher_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - - switcher = gtk_stack_switcher_new(); - gtk_stack_switcher_set_stack(GTK_STACK_SWITCHER(switcher), GTK_STACK(stack)); - - about_button = gtk_button_new_from_icon_name("help-about-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR); - - header = gtk_header_bar_new(); - gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(header), TRUE); - gtk_header_bar_set_custom_title(GTK_HEADER_BAR(header), switcher_box); - - Play_tab(stack); - Features_tab(stack); - Servers_tab(stack); - - gtk_box_pack_start(GTK_BOX(switcher_box), switcher, TRUE, FALSE, 0); - gtk_box_pack_end(GTK_BOX(switcher_box), about_button, TRUE, FALSE, 2); - - g_signal_connect(about_button, "clicked", G_CALLBACK(about_cb), NULL); - - gtk_window_set_titlebar(GTK_WINDOW(window), header); - gtk_container_add(GTK_CONTAINER(window), stack); - gtk_widget_show_all(window); - return; -} - -int main(int argc, char* argv[]) -{ - int i = 0; - int rt = 0; - char* config_path; - GtkApplication* app; - - /* Initialize */ - srand(time(NULL)); - /* This is cursed, but it works:tm: */ - setenv("GTK_THEME", "Adwaita:dark", 1); - - asprintf(&config_path, "%s/.gmcpil.json", getenv("HOME")); - config = gmcpil_config_new(config_path); - free(config_path); - - gtk_init(&argc, &argv); - - app = gtk_application_new("tk.mcpirevival.gmcpil", G_APPLICATION_FLAGS_NONE); - - /* Signals */ - g_signal_connect(app, "activate", G_CALLBACK(activate_cb), NULL); - - rt = g_application_run(G_APPLICATION(app), argc, argv); - - /* Free memory and resources */ - g_object_unref(app); - free(features_envs[1]); - free(features_envs[2]); - free(features_envs[3]); - free(features_envs[4]); - while (i < featc) - { - free(FEAT_PTR(i)); - i++; - } - if (settings_box.buff != NULL) - { - free(settings_box.buff); - } - free(servers_path); - - gmcpil_config_save(config); - g_object_unref(config); - return rt; -} diff --git a/src/play.c b/src/play.c deleted file mode 100644 index 4a11d15..0000000 --- a/src/play.c +++ /dev/null @@ -1,282 +0,0 @@ -/* - * settings.c - gMCPIL settings tab - * - * Copyright 2021 Alvarito050506 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * - */ - -#define _GNU_SOURCE /* Required for asprintf */ - -#include -#include -#include -#include -#include - -#include -#include -#include - -char* distances[4] = {"Far", "Normal", "Short", "Tiny"}; -char* profile_names[5] = {"Classic MCPI", "Modded MCPI", "Modded MCPE", "Optimized MCPE", "Custom Profile"}; -char* profile_descriptions[5] = { - "Classic Minecraft Pi Edition (Not Recommended):\nAll optional features disabled.", - "Modded Minecraft Pi Edition:\nDefault MCPI-Reborn optional features without Touch GUI.", - "Modded Minecraft Pocket Edition (Recommended):\nDefault MCPI-Reborn optional features.", - "Optimized Minecraft Pocket Edition:\nDefault MCPI-Reborn features with lower quality graphics.", - "Custom Profile: Modify its settings in the Features tab." -}; - -static char* get_splash_text() -{ - return splashes[rand() % sizeof(splashes) / sizeof(char*)]; -} - -static int get_distance(char* str) -{ - switch (str[0]) - { - case 'F': - return 0; - break; - case 'N': - return 1; - break; - case 'S': - return 2; - break; - case 'T': - return 3; - break; - } - return -1; -} - -static void settings_cb(__attribute__((unused)) GtkWidget* button, __attribute__((unused)) void* udata) -{ - int profile; - const char* username; - const char* distance; - const char* hud; - gboolean hide; - GtkEntryBuffer* username_buff; - GtkEntryBuffer* hud_buff; - - username_buff = gtk_entry_get_buffer(GTK_ENTRY(settings_box.username_entry)); - hud_buff = gtk_entry_get_buffer(GTK_ENTRY(settings_box.hud_entry)); - - username = gtk_entry_buffer_get_text(username_buff); - hud = gtk_entry_buffer_get_text(hud_buff); - distance = gtk_combo_box_text_get_active_text(settings_box.distance_combo); - hide = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(settings_box.hide_check)); - - setenv("MCPI_USERNAME", username, 1); - setenv("MCPI_RENDER_DISTANCE", distance, 1); - setenv("GALLIUM_HUD", hud, 1); - - gmcpil_config_set_username(config, username); - gmcpil_config_set_hud(config, hud); - gmcpil_config_set_distance(config, distance); - if (hide) - { - gmcpil_config_set_hide(config, "TRUE"); - } else - { - gmcpil_config_set_hide(config, "FALSE"); - } - - profile = gtk_combo_box_get_active(GTK_COMBO_BOX(settings_box.profile_combo)); - setenv("MCPI_FEATURE_FLAGS", features_envs[profile], 1); - - gmcpil_config_set_last_profile(config, profile); - gmcpil_config_save(config); - return; -} - -static void watch_cb(GPid pid, __attribute__((unused)) int status, __attribute__((unused)) void* udata) -{ - gtk_widget_show_all(window); - g_spawn_close_pid(pid); - return; -} - -static void launch_cb(__attribute__((unused)) GtkWidget* button, __attribute__((unused)) void* udata) -{ - char* argv[] = {"minecraft-pi-reborn-client", NULL}; - GPid pid; - GError* err = NULL; - - settings_cb(NULL, NULL); - g_spawn_async(NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, &err); - - if (err != NULL) - { - g_error("Spawning child failed: %s", err->message); - return; - } - if (gmcpil_config_get_hide(config)[0] == 'T') - { - gtk_widget_hide(window); - } - g_child_watch_add(pid, watch_cb, NULL); - return; -} - -static void select_cb(GtkComboBox* combo, void* udata) -{ - int profile; - GtkLabel* description; - - description = GTK_LABEL(udata); - profile = gtk_combo_box_get_active(combo); - gtk_label_set_text(description, profile_descriptions[profile]); - return; -} - -TAB(Play, settings_cb, { - int i = 0; - int distance_int; - int last_profile; - char* default_username; - char* default_distance; - char* default_hud; - char* default_hide; - char* splash_text; - GtkWidget* username_hbox; - GtkWidget* username_label; - GtkWidget* username_entry; - GtkWidget* distance_hbox; - GtkWidget* distance_label; - GtkWidget* distance_combo; - GtkWidget* profile_hbox; - GtkWidget* profile_label; - GtkWidget* profile_combo; - GtkWidget* hud_hbox; - GtkWidget* hud_label; - GtkWidget* hud_entry; - GtkWidget* hide_hbox; - GtkWidget* hide_check; - GtkWidget* launch_button; - GtkWidget* splash; - GtkWidget* title; - GtkWidget* title_hbox; - GtkWidget* description; - - title_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - - title = gtk_label_new(NULL); - gtk_label_set_markup(GTK_LABEL(title), "Minecraft Pi Launcher"); - - splash = gtk_label_new(NULL); - asprintf(&splash_text, "%s\n", get_splash_text()); - gtk_label_set_markup(GTK_LABEL(splash), splash_text); - - GMCPIL_SET_DEFAULT(distance, "Short"); - GMCPIL_SET_DEFAULT(username, "StevePi"); - GMCPIL_SET_DEFAULT(hud, "simple,fps"); - GMCPIL_SET_DEFAULT(hide, "TRUE"); - - distance_int = get_distance(default_distance); - - username_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - username_label = gtk_label_new("Username"); - username_entry = gtk_entry_new_with_buffer(gtk_entry_buffer_new(default_username, strlen(default_username))); - gtk_widget_set_size_request(username_entry, 25 * 10, -1); - - hud_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - hud_label = gtk_label_new("Gallium HUD options"); - hud_entry = gtk_entry_new_with_buffer(gtk_entry_buffer_new(default_hud, strlen(default_hud))); - gtk_widget_set_size_request(hud_entry, 25 * 10, -1); - - distance_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - distance_label = gtk_label_new("Rendering distance"); - distance_combo = gtk_combo_box_text_new(); - while (i < 4) - { - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(distance_combo), distances[i]); - i++; - } - gtk_combo_box_set_active(GTK_COMBO_BOX(distance_combo), distance_int); - gtk_widget_set_size_request(distance_combo, 25 * 10, -1); - - i = 0; - profile_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - profile_label = gtk_label_new("Profile"); - profile_combo = gtk_combo_box_text_new(); - last_profile = gmcpil_config_get_last_profile(config); - while (i < 5) - { - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(profile_combo), profile_names[i]); - i++; - } - gtk_combo_box_set_active(GTK_COMBO_BOX(profile_combo), last_profile); - gtk_widget_set_size_request(profile_combo, 25 * 10, -1); - - hide_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - hide_check = gtk_check_button_new_with_label("Hide launcher"); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(hide_check), default_hide[0] == 'T'); - gtk_widget_set_size_request(hide_check, -1, 32); // To match rest of input widgets - - description = gtk_label_new(profile_descriptions[last_profile]); - gtk_label_set_justify(GTK_LABEL(description), GTK_JUSTIFY_CENTER); - gtk_label_set_line_wrap(GTK_LABEL(description), TRUE); - - launch_button = gtk_button_new_with_label("Launch"); - - gtk_box_pack_start(GTK_BOX(title_hbox), title, TRUE, FALSE, 0); - - gtk_box_pack_start(GTK_BOX(tab), title_hbox, FALSE, FALSE, 4); - gtk_box_pack_start(GTK_BOX(tab), splash, FALSE, FALSE, 0); - - gtk_box_pack_start(GTK_BOX(username_hbox), username_label, FALSE, FALSE, 10); - gtk_box_pack_end(GTK_BOX(username_hbox), username_entry, FALSE, FALSE, 10); - - gtk_box_pack_start(GTK_BOX(hud_hbox), hud_label, FALSE, FALSE, 10); - gtk_box_pack_end(GTK_BOX(hud_hbox), hud_entry, FALSE, FALSE, 10); - - gtk_box_pack_start(GTK_BOX(distance_hbox), distance_label, FALSE, FALSE, 10); - gtk_box_pack_end(GTK_BOX(distance_hbox), distance_combo, FALSE, FALSE, 10); - - gtk_box_pack_start(GTK_BOX(profile_hbox), profile_label, FALSE, FALSE, 10); - gtk_box_pack_end(GTK_BOX(profile_hbox), profile_combo, FALSE, FALSE, 10); - - gtk_box_pack_end(GTK_BOX(hide_hbox), hide_check, FALSE, FALSE, 10); - - gtk_box_pack_start(GTK_BOX(tab), username_hbox, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(tab), hud_hbox, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(tab), distance_hbox, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(tab), profile_hbox, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(tab), hide_hbox, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(tab), description, FALSE, FALSE, 2); - - gtk_box_pack_end(GTK_BOX(button_hbox), launch_button, FALSE, FALSE, 0); - - settings_box.username_entry = GTK_ENTRY(username_entry); - settings_box.hud_entry = GTK_ENTRY(hud_entry); - settings_box.distance_combo = GTK_COMBO_BOX_TEXT(distance_combo); - settings_box.profile_combo = GTK_COMBO_BOX_TEXT(profile_combo); - settings_box.hide_check = GTK_CHECK_BUTTON(hide_check); - - g_signal_connect(launch_button, "clicked", G_CALLBACK(launch_cb), NULL); - g_signal_connect(profile_combo, "changed", G_CALLBACK(select_cb), (void*)description); - - setenv("MCPI_USERNAME", default_username, 1); - setenv("MCPI_RENDER_DISTANCE", distances[distance_int], 1); - setenv("GALLIUM_HUD", default_hud, 1); - free(splash_text); -}); diff --git a/src/play.vala b/src/play.vala new file mode 100644 index 0000000..88ef218 --- /dev/null +++ b/src/play.vala @@ -0,0 +1,167 @@ +using Gtk; + +namespace GMCPIL +{ + class SettingBox : Box + { + public SettingBox(string name, Widget widget) + { + Object(orientation: Orientation.HORIZONTAL, spacing: 0); + pack_start(new Label(name), false, false, 10); + pack_end(widget, false, false, 10); + } + } + + public string get_splash_text() + { + string[] splashes = { + "Sexy!", + "We Fixed The Rail!", + "Who Broke The Rail?", + "The World Of Notch!", + "WDYM Discontinued?", + "Oh, ok, Pigmen...", + "Check out the non-existant Far Lands!", + "mcpirevival.tk!", + "#alvarito-come-back", + "Classic!", + "Wow! Modded MCPI!", + "Now with fly-hacks!", + "Where Is My Pie?", + "I was promised pie!", + "@Banana", + "BANANA!", + "To \"Na\" Or Not To \"Na\", that is the question!", + "Not Minecraft Java!", + "Definitely not Minecraft Console!", + "Oh yeah, that version...", + "Join The Discord!", + "Segmentation fault (core dumped)", + "It's the Segment's Fault!", + "As seen on Discord!", + "Obfuscated!", + "As not seen on TV!", + "Why must you hurt me in this way?", + "Who is StevePi?", + "(Not) Made by Notch!", + "Open-Source!", + "FREE!", + "RIP MCPIL-Legacy, 2020-2020", + "StevePi = popbob confirmed?", + "Watch out for StevePi!", + "Didn't remove Herobrine!", + "Minceraft", + "MCPI-Docker", + "MCPI-Devs", + "RIP Picraft", + "Check out PBPT Anarchy!", + "Now without Docker!", + "Banana!", + "Fancy Vala" + }; + return splashes[Random.int_range(0, splashes.length - 1)]; + } + + public void play_tab(Config config, Box tab) throws Error + { + Box hide_hbox; + Box title_hbox; + Label title; + Label splash; + Label description; + Entry hud; + Entry username; + ComboBoxText profile; + ComboBoxText distance; + CheckButton hide; + string splash_text; + string[] profile_names = { + "Classic MCPI", + "Modded MCPI", + "Modded MCPE", + "Optimized MCPE", + "Custom Profile" + }; + string[] profile_descriptions = { + "Classic Minecraft Pi Edition (Not Recommended):\nAll optional features disabled.", + "Modded Minecraft Pi Edition:\nDefault MCPI-Reborn optional features without Touch GUI.", + "Modded Minecraft Pocket Edition (Recommended):\nDefault MCPI-Reborn optional features.", + "Optimized Minecraft Pocket Edition:\nDefault MCPI-Reborn features with lower quality graphics.", + "Custom Profile: Modify its settings in the Features tab." + }; + + splash_text = @"$(get_splash_text())\n"; + + title_hbox = new Box(Orientation.HORIZONTAL, 0); + + title = new Label(null); + title.set_markup("Minecraft Pi Launcher"); + + splash = new Label(null); + splash.set_markup(splash_text); + + username = new Entry(); + username.set_text(config.username); + username.set_size_request(25 * 10, -1); + + hud = new Entry(); + hud.set_text(config.hud); + hud.set_size_request(25 * 10, -1); + + distance = new ComboBoxText(); + for (Distance i = 0; i <= Distance.FAR; i += 1) + { + distance.append_text(Config.distance_names[i]); + } + distance.set_active(config.distance); + distance.set_size_request(25 * 10, -1); + + profile = new ComboBoxText(); + for (ProfileType i = 0; i <= ProfileType.CUSTOM; i += 1) + { + profile.append_text(profile_names[i]); + } + profile.set_active(config.profile); + profile.set_size_request(25 * 10, -1); + + hide_hbox = new Box(Orientation.HORIZONTAL, 0); + hide = new CheckButton.with_label("Hide launcher"); + hide.set_active(config.hide); + hide.set_size_request(-1, 32); /* To match other input widgets */ + + description = new Label(profile_descriptions[config.profile]); + description.set_justify(Justification.CENTER); + description.set_line_wrap(true); + + title_hbox.pack_start(title, true, false, 0); + + tab.pack_start(title_hbox, false, false, 4); + tab.pack_start(splash, false, false, 0); + + hide_hbox.pack_end(hide, false, false, 10); + + tab.pack_start(new SettingBox("Username", username), false, false, 2); + tab.pack_start(new SettingBox("Gallium HUD options", hud), false, false, 2); + tab.pack_start(new SettingBox("Distance", distance), false, false, 2); + tab.pack_start(new SettingBox("Profile", profile), false, false, 2); + tab.pack_start(hide_hbox, false, false, 2); + tab.pack_start(description, false, false, 2); + + username.changed.connect(() => { + config.username = username.get_text(); + }); + hud.changed.connect(() => { + config.hud = hud.get_text(); + }); + distance.changed.connect(() => { + config.distance = (Distance)distance.get_active(); + }); + profile.changed.connect(() => { + config.profile = (ProfileType)profile.get_active(); + description.set_text(profile_descriptions[config.profile]); + }); + hide.toggled.connect(() => { + config.hide = hide.get_active(); + }); + } +} diff --git a/src/profile.vala b/src/profile.vala new file mode 100644 index 0000000..fd906ae --- /dev/null +++ b/src/profile.vala @@ -0,0 +1,115 @@ +namespace GMCPIL +{ + public enum ProfileType + { + VANILLA, + MODDED, + MCPE, + OPTIMIZED, + CUSTOM + } + + public class Profile + { + public static Array features; + public static Object[] profiles; + static string[] optimized_disable = { + "Fancy Graphics", + "Smooth Lighting", + "Animated Water", + "Disable \"gui_blocks\" Atlas" + }; + + public static void init(string path) throws Error + { + File file; + bool enabled; + string line; + string[] tuple; + FileInputStream file_stream; + DataInputStream data_stream; + + features = new Array(); + profiles = new Object[5]; + + for (ProfileType i = 0; i < ProfileType.CUSTOM; i += 1) + { + profiles[i] = new Object(); + } + + file = File.new_for_path(path); + file_stream = file.read(); + data_stream = new DataInputStream(file_stream); + line = data_stream.read_line(); + while (line != null) + { + tuple = line.split(" ", 2); + enabled = tuple[0][0] == 'T'; + features.append_val(tuple[1]); + for (ProfileType i = 0; i < ProfileType.CUSTOM; i += 1) + { + switch (i) + { + case ProfileType.VANILLA: + continue; + case ProfileType.MODDED: + if (tuple[1] == "Touch GUI") + { + continue; + } + break; + case ProfileType.OPTIMIZED: + if (tuple[1] in optimized_disable) + { + continue; + } + break; + default: + break; + } + profiles[i].set_data(tuple[1], enabled); + } + line = data_stream.read_line(); + } + return; + } + + public static Json.Object serialize(Object obj) + { + string name; + bool enabled; + Json.Object tmp; + + tmp = new Json.Object(); + for (int i = 0; i < features.length; i++) + { + name = features.index(i); + enabled = obj.get_data(name); + tmp.set_boolean_member(name, enabled); + } + return tmp; + } + + public static Object deserialize(Json.Object obj) + { + string name; + bool enabled; + Object tmp; + + tmp = new Object(); + for (int i = 0; i < features.length; i++) + { + name = features.index(i); + if (obj.has_member(name)) + { + enabled = obj.get_boolean_member(name); + } else + { + enabled = profiles[ProfileType.MCPE].get_data(name); + } + tmp.set_data(name, enabled); + } + return tmp; + } + } +} diff --git a/src/servers.c b/src/servers.c deleted file mode 100644 index 958d6ec..0000000 --- a/src/servers.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * servers.c - gMCPIL servers tab - * - * Copyright 2021 Alvarito050506 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * - */ - -#define _GNU_SOURCE /* Required for asprintf */ - -#include -#include -#include -#include -#include - -#include -#include - -char* servers_path; - -static void servers_cb(__attribute__((unused)) GtkWidget* button, void* udata) -{ - char* buff; - FILE* fd; - GtkTextView* view; - GtkTextBuffer* gtk_buff; - GtkTextIter start; - GtkTextIter end; - - fd = fopen(servers_path, "w+"); - if (fd == NULL) - { - fprintf(stderr, "Could not save servers file: %s.", strerror(errno)); - return; - } - view = (GtkTextView*)udata; - - gtk_buff = gtk_text_view_get_buffer(view); - gtk_text_buffer_get_start_iter(gtk_buff, &start); - gtk_text_buffer_get_end_iter(gtk_buff, &end); - buff = gtk_text_buffer_get_text(gtk_buff, &start, &end, FALSE); - - fwrite(buff, 1, strlen(buff), fd); - fclose(fd); - return; -} - -TAB(Servers, servers_cb, { - int sz = 0; - char* buff; - FILE* fd; - GtkWidget* label; - GtkWidget* scroll; - GtkWidget* view; - GtkTextBuffer* gtk_buff; - - buff = NULL; - asprintf(&servers_path, "%s/.minecraft-pi/servers.txt", getenv("HOME")); - - fd = fopen(servers_path, "r+"); - if (fd != NULL) - { - fseek(fd, 0, SEEK_END); - sz = ftell(fd); - fseek(fd, 0, SEEK_SET); - buff = (char*)malloc(sz + 1); - fread((void*)buff, 1, sz, fd); - buff[sz] = 0x00; - fclose(fd); - } - - label = gtk_label_new(SERVERS_LABEL); - gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); - gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER); - gtk_widget_set_margin_top(label, 6); - - scroll = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_ETCHED_IN); - gtk_container_set_border_width(GTK_CONTAINER(scroll), 5); - - gtk_buff = gtk_text_buffer_new(NULL); - - if (buff != NULL) - { - gtk_text_buffer_set_text(gtk_buff, buff, -1); - } else - { - gtk_text_buffer_set_text(gtk_buff, DEFAULT_SERVERS, -1); - } - - view = gtk_text_view_new_with_buffer(gtk_buff); - gtk_text_view_set_editable(GTK_TEXT_VIEW(view), TRUE); - gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), TRUE); - gtk_container_add(GTK_CONTAINER(scroll), view); - - gtk_box_pack_start(GTK_BOX(tab), label, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(tab), scroll, TRUE, TRUE, 0); - cb_udata = (void*)view; - free(buff); -}); diff --git a/src/servers.vala b/src/servers.vala new file mode 100644 index 0000000..ac60460 --- /dev/null +++ b/src/servers.vala @@ -0,0 +1,49 @@ +using Gtk; + +namespace GMCPIL +{ + public void servers_tab(Config config, Box tab) throws Error + { + string? buff; + string home; + string filename; + string dirname; + File dir; + Label label; + TextView view; + ScrolledWindow scroll; + + home = Environment.get_variable("HOME"); + dirname = Path.build_filename(home, ".minecraft-pi"); + filename = Path.build_filename(dirname, "servers.txt"); + + label = new Label(SERVERS_LABEL); + label.set_line_wrap(true); + label.set_justify(Justification.CENTER); + label.set_margin_top(6); + + scroll = new ScrolledWindow(null, null); + scroll.set_policy(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC); + scroll.set_shadow_type(ShadowType.NONE); + scroll.set_border_width(5); + + dir = File.new_for_path(dirname); + if (!dir.query_exists()) + { + dir.make_directory(null); + } + + buff = read_file(filename); + + config.servers = new TextBuffer(null); + config.servers.set_text(buff != null ? buff : DEFAULT_SERVERS); + + view = new TextView.with_buffer(config.servers); + view.set_editable(true); + view.set_cursor_visible(true); + + scroll.add(view); + tab.pack_start(label, false, false, 0); + tab.pack_start(scroll, true, true, 0); + } +}