├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── io │ └── github │ └── blobanium │ └── mcbrowser │ ├── MCBrowser.java │ ├── config │ ├── BrowserAutoConfig.java │ └── ModMenuIntegration.java │ ├── feature │ ├── BrowserFeatureUtil.java │ └── specialbutton │ │ ├── SpecialButtonAction.java │ │ ├── SpecialButtonActions.java │ │ └── SpecialButtonHelper.java │ ├── mixin │ ├── CefClientMixin.java │ ├── CefUtilMixin.java │ ├── MinecraftClientMixin.java │ └── UtilOperatingSystemMixin.java │ ├── screen │ └── BrowserScreen.java │ └── util │ ├── BrowserCaches.java │ ├── BrowserImpl.java │ ├── BrowserUtil.java │ ├── SwitchFunctions.java │ ├── TabHolder.java │ ├── TabManager.java │ └── button │ ├── BrowserTabIcon.java │ ├── NewTabButton.java │ ├── ReloadButton.java │ └── TabButton.java └── resources ├── assets └── mcbrowser │ ├── icon.png │ └── lang │ └── en_us.json ├── fabric.mod.json ├── mcbrowser.accesswidener └── mcbrowser.mixins.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **PLEASE READ THIS BEFORE CREATING YOUR ISSUE** 11 | If the issue you are experiencing is with the browser itself, please check to see if it is related to MCEF. MCEF is a critical component of making MCBrowser work. If you are unsure if the issue you are experiencing is related to MCEF, open the issue here and we'll take a look. You can find MCEF's issue page at https://github.com/CinemaMod/mcef/issues 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /.gradle/ 3 | /build/ 4 | /.idea/ 5 | /dependencies/ 6 | /run/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Blobanium 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MCBrowser 2 | 3 | This is still a WIP. Expect Bugs. 4 | 5 | REQUIRES [MCEF](https://modrinth.com/mod/mcef) and [Cloth Config](https://modrinth.com/mod/cloth-config) (For 0.0.3-Beta+ only) 6 | 7 | [Fabric API](https://modrinth.com/mod/fabric-api) isn't required because MCEF already includes the fabric libraries we depend on. But however it is recomended that you install it. (Its required for versions 0.0.2-Beta and prior OR it says you need `fabric-command-api-v2` and/or `fabric-lifecycle-events-v1`) 8 | 9 | type `/browser` or `/br` to open the browser. 10 | 11 | NOTICE: The developers of this mod (aka Myself) are not responsible for any sus or illegal things you come across while using MCBrowser. Use this mod at your own risk. 12 | 13 | Find us on [Modrinth](https://modrinth.com/mod/mcbrowser)! (Curseforge not yet this mod needs an icon.) 14 | 15 | 16 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '1.10-SNAPSHOT' 3 | id 'maven-publish' 4 | } 5 | 6 | version = project.mod_version 7 | group = project.maven_group 8 | 9 | repositories { 10 | // Add repositories to retrieve artifacts from in here. 11 | // You should only use this when depending on other mods because 12 | // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. 13 | // See https://docs.gradle.org/current/userguide/declaring_repositories.html 14 | // for more information about repositories. 15 | 16 | maven { url "https://mcef-download.cinemamod.com/repositories/releases" } 17 | maven { url "https://maven.shedaniel.me/" } 18 | maven { url "https://maven.terraformersmc.com/releases/" } 19 | } 20 | 21 | dependencies { 22 | // To change the versions see the gradle.properties file 23 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 24 | mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" 25 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 26 | 27 | // Fabric API. This is technically optional, but you probably want it anyway. 28 | modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" 29 | modImplementation "com.cinemamod:mcef:${project.mcef_version}" 30 | modRuntimeOnly "com.cinemamod:mcef-fabric:${project.mcef_version}" 31 | modApi("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}") { 32 | exclude(group: "net.fabricmc.fabric-api") 33 | } 34 | modApi "com.terraformersmc:modmenu:${project.mod_menu_version}" 35 | } 36 | 37 | processResources { 38 | inputs.property "version", project.version 39 | inputs.property "minecraft_version", project.minecraft_version 40 | inputs.property "loader_version", project.loader_version 41 | filteringCharset "UTF-8" 42 | 43 | filesMatching("fabric.mod.json") { 44 | expand "version": project.version, 45 | "minecraft_version": project.minecraft_version, 46 | "loader_version": project.loader_version 47 | } 48 | } 49 | 50 | def targetJavaVersion = 21 51 | tasks.withType(JavaCompile).configureEach { 52 | // ensure that the encoding is set to UTF-8, no matter what the system default is 53 | // this fixes some edge cases with special characters not displaying correctly 54 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 55 | // If Javadoc is generated, this must be specified in that task too. 56 | it.options.encoding = "UTF-8" 57 | if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { 58 | it.options.release = targetJavaVersion 59 | } 60 | } 61 | 62 | loom { 63 | accessWidenerPath = file("src/main/resources/mcbrowser.accesswidener") 64 | } 65 | 66 | java { 67 | def javaVersion = JavaVersion.toVersion(targetJavaVersion) 68 | if (JavaVersion.current() < javaVersion) { 69 | toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) 70 | } 71 | archivesBaseName = project.archives_base_name 72 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 73 | // if it is present. 74 | // If you remove this line, sources will not be generated. 75 | withSourcesJar() 76 | } 77 | 78 | jar { 79 | from("LICENSE") { 80 | rename { "${it}_${project.archivesBaseName}"} 81 | } 82 | } 83 | 84 | // configure the maven publication 85 | publishing { 86 | publications { 87 | mavenJava(MavenPublication) { 88 | from components.java 89 | } 90 | } 91 | 92 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 93 | repositories { 94 | // Add repositories to publish to here. 95 | // Notice: This block does NOT have the same function as the block in the top level. 96 | // The repositories here will be used for publishing your artifact, not for 97 | // retrieving dependencies. 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx1G 3 | 4 | # Fabric Properties 5 | # check these on https://modmuss50.me/fabric.html 6 | minecraft_version=1.21.4 7 | yarn_mappings=1.21.4+build.8 8 | loader_version=0.16.10 9 | 10 | # Mod Properties 11 | mod_version = 1.2.3-Snapshot 12 | maven_group = io.github.blobanium 13 | archives_base_name = MCBrowser 14 | 15 | # Dependencies 16 | # check this on https://modmuss50.me/fabric.html 17 | fabric_version=0.118.0+1.21.4 18 | 19 | mcef_version=2.1.6-1.21.4 20 | cloth_config_version=17.0.144 21 | mod_menu_version=13.0.2 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blobanium/MCBrowser/41488cd2844a848b569101c9dc9e7031d4bc5792/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | gradlePluginPortal() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/MCBrowser.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser; 2 | 3 | import com.cinemamod.mcef.MCEF; 4 | import com.mojang.brigadier.arguments.StringArgumentType; 5 | import io.github.blobanium.mcbrowser.config.BrowserAutoConfig; 6 | import io.github.blobanium.mcbrowser.feature.BrowserFeatureUtil; 7 | import io.github.blobanium.mcbrowser.screen.BrowserScreen; 8 | import io.github.blobanium.mcbrowser.util.TabHolder; 9 | import io.github.blobanium.mcbrowser.util.TabManager; 10 | import me.shedaniel.autoconfig.AutoConfig; 11 | import me.shedaniel.autoconfig.serializer.GsonConfigSerializer; 12 | import net.fabricmc.api.ClientModInitializer; 13 | import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; 14 | import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; 15 | import net.minecraft.client.MinecraftClient; 16 | import net.minecraft.client.toast.SystemToast; 17 | import net.minecraft.text.Text; 18 | import org.apache.logging.log4j.LogManager; 19 | import org.apache.logging.log4j.Logger; 20 | 21 | 22 | public class MCBrowser implements ClientModInitializer { 23 | public static final Logger LOGGER = LogManager.getLogger("MCBrowser"); 24 | 25 | private static boolean firstOpen = true; 26 | public static boolean isShuttingDown = false; 27 | 28 | @Override 29 | public void onInitializeClient() { 30 | AutoConfig.register(BrowserAutoConfig.class, GsonConfigSerializer::new); 31 | 32 | if (MCEF.getSettings().getUserAgent().equals("null")) { 33 | MCEF.getSettings().setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) MCEF/2 Chrome/119.0.0.0 Safari/537.36"); 34 | } 35 | 36 | for (String command : new String[]{"browser", "br"}) { 37 | ClientCommandRegistrationCallback.EVENT.register(((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal(command) 38 | .executes(context -> { 39 | openBrowser(); 40 | return 0; 41 | }).then(ClientCommandManager.literal("url") 42 | .then(ClientCommandManager.argument("url", StringArgumentType.greedyString()) 43 | .executes(context -> { 44 | TabManager.openNewTab(BrowserFeatureUtil.prediffyURL(StringArgumentType.getString(context, "url"))); 45 | return 0; 46 | })) 47 | ).then(ClientCommandManager.literal("close") 48 | .executes(context -> { 49 | TabManager.reset(); 50 | return 0; 51 | }) 52 | )))); 53 | } 54 | } 55 | 56 | private static final MinecraftClient minecraft = MinecraftClient.getInstance(); 57 | 58 | public static void openBrowser() { 59 | if (firstOpen) { 60 | TabManager.loadTabsFromJson(); 61 | TabManager.setActiveTab(Math.max(0, TabManager.tabs.size() - 1)); 62 | } 63 | firstOpen = false; 64 | if (TabManager.tabs.isEmpty()) { 65 | TabManager.tabs.add(new TabHolder(BrowserFeatureUtil.prediffyURL(getConfig().homePage))); 66 | } 67 | minecraft.send(() -> minecraft.setScreen(new BrowserScreen(Text.literal("Basic Browser")))); 68 | } 69 | 70 | public static BrowserAutoConfig getConfig() { 71 | return AutoConfig.getConfigHolder(BrowserAutoConfig.class).getConfig(); 72 | } 73 | 74 | public static void sendToastMessage(Text title, Text description) { 75 | MinecraftClient.getInstance().getToastManager().add(new SystemToast(new SystemToast.Type(), title, description)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/config/BrowserAutoConfig.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.config; 2 | 3 | import me.shedaniel.autoconfig.ConfigData; 4 | import me.shedaniel.autoconfig.annotation.Config; 5 | import me.shedaniel.autoconfig.annotation.ConfigEntry; 6 | 7 | @Config(name = "mcbrowser") 8 | public class BrowserAutoConfig implements ConfigData { 9 | @ConfigEntry.Gui.Tooltip 10 | public boolean openLinkInBrowser = true; 11 | @ConfigEntry.Gui.Tooltip 12 | public String homePage = "https://www.google.com"; 13 | 14 | @ConfigEntry.Category("performance") 15 | @ConfigEntry.Gui.Tooltip 16 | public boolean asyncBrowserInput = true; 17 | 18 | @ConfigEntry.Gui.Tooltip 19 | public boolean saveTabs = true; 20 | 21 | @ConfigEntry.Category("performance") 22 | @ConfigEntry.Gui.Tooltip 23 | public boolean limitBrowserFramerate = false; 24 | 25 | @ConfigEntry.Category("performance") 26 | @ConfigEntry.Gui.Tooltip 27 | public int browserFPS = 60; 28 | 29 | @ConfigEntry.Gui.Tooltip 30 | public double zoomScalingFactor = 0.5; 31 | 32 | @ConfigEntry.Category("privacyandsafety") 33 | @ConfigEntry.Gui.Tooltip 34 | @ConfigEntry.Gui.RequiresRestart 35 | public boolean saveCookies = false; 36 | 37 | @ConfigEntry.Category("privacyandsafety") 38 | @ConfigEntry.Gui.Tooltip 39 | @ConfigEntry.Gui.RequiresRestart 40 | public boolean enableMediaStream = false; 41 | 42 | @ConfigEntry.Category("privacyandsafety") 43 | @ConfigEntry.Gui.Tooltip 44 | public boolean allowDownloads = false; 45 | 46 | @ConfigEntry.Gui.Tooltip 47 | public boolean killJcefHelperOnClose = true; 48 | 49 | @ConfigEntry.Gui.Tooltip 50 | @ConfigEntry.Gui.RequiresRestart 51 | public String customSwitches = ""; 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/config/ModMenuIntegration.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.config; 2 | 3 | import com.terraformersmc.modmenu.api.ConfigScreenFactory; 4 | import com.terraformersmc.modmenu.api.ModMenuApi; 5 | import me.shedaniel.autoconfig.AutoConfig; 6 | 7 | public class ModMenuIntegration implements ModMenuApi { 8 | 9 | @Override 10 | public ConfigScreenFactory getModConfigScreenFactory() { 11 | return parent -> AutoConfig.getConfigScreen(BrowserAutoConfig.class, parent).get(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/feature/BrowserFeatureUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.feature; 2 | 3 | public class BrowserFeatureUtil { 4 | public static String prediffyURL(String url){ 5 | //See if it has the scheme (aka where it says "http" or https), were only doing this because there are URL schemes other than http and https. 6 | if(url.contains("://")){ 7 | return url; 8 | 9 | }else if(url.contains(".") && !url.contains(" ")){ 10 | //See if this is an actual link a user typed in manually, if it is then append HTTP to the beginning 11 | return "http://" + url; //This should default to HTTPS if it's A HTTPS Site 12 | }else{ 13 | //Treat if this is a google search. 14 | return searchToURL(url); 15 | } 16 | } 17 | 18 | private static String searchToURL(String search){ 19 | //This is for when we can give the user the option to choose what search engine they want to use in the future but for now use google. 20 | String query = search.replace(" ", "+"); 21 | return "https://www.google.com/search?q=" + query; 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/feature/specialbutton/SpecialButtonAction.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.feature.specialbutton; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonArray; 5 | import com.google.gson.JsonObject; 6 | import io.github.blobanium.mcbrowser.MCBrowser; 7 | import io.github.blobanium.mcbrowser.util.SwitchFunctions; 8 | import net.fabricmc.loader.api.FabricLoader; 9 | import net.minecraft.text.Text; 10 | import org.apache.commons.io.FileUtils; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.net.HttpURLConnection; 16 | import java.net.URISyntaxException; 17 | import java.net.URL; 18 | import java.net.URI; 19 | import java.util.concurrent.CompletableFuture; 20 | 21 | public class SpecialButtonAction { 22 | public static final byte START_DL_DESCRIPTION = 0x00; 23 | public static final byte END_DL_DESCRIPTION = 0x01; 24 | 25 | //These two methods exist for compatability purposes. May change later when i learn more about enum values. 26 | public static void downloadModrinthMod() { 27 | downloadModrinth(SpecialButtonActions.MODRINTH_MOD); 28 | } 29 | 30 | public static void downloadModrinthRP() { 31 | downloadModrinth(SpecialButtonActions.MODRINTH_RP); 32 | } 33 | 34 | private static void downloadModrinth(SpecialButtonActions action) { 35 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.download.toast.started"), SwitchFunctions.SpecialButtonActionSwitches.getTranslation(START_DL_DESCRIPTION, action)); 36 | 37 | CompletableFuture.runAsync(() -> { 38 | try { 39 | //Get the file from modrinth's API 40 | URL url = SwitchFunctions.SpecialButtonActionSwitches.getTargetURL(action); 41 | HttpURLConnection http = (HttpURLConnection) url.openConnection(); 42 | http.setRequestMethod("GET"); 43 | http.connect(); 44 | InputStream stream = http.getInputStream(); 45 | String json = new String(stream.readAllBytes()); 46 | http.disconnect(); 47 | 48 | //Analyze JSON Result from Modrinth API 49 | Gson gson = new Gson(); 50 | JsonArray array = gson.fromJson(json, JsonArray.class); 51 | JsonObject object = array.get(0).getAsJsonObject(); 52 | JsonArray filesArray = object.get("files").getAsJsonArray(); 53 | JsonObject file = filesArray.get(0).getAsJsonObject(); 54 | 55 | //get file 56 | URL downloadURL = new URI(file.get("url").getAsString()).toURL(); 57 | FileUtils.copyURLToFile(downloadURL, new File(FabricLoader.getInstance().getGameDir().toFile(), SwitchFunctions.SpecialButtonActionSwitches.getTargetDirectory(action) + cleanseFileUrl(downloadURL.getFile()))); 58 | 59 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.download.toast.complete"), SwitchFunctions.SpecialButtonActionSwitches.getTranslation(END_DL_DESCRIPTION, action)); 60 | } catch (IOException | URISyntaxException e) { 61 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.download.toast.failed"), Text.translatable("mcbrowser.download.toast.failed.description")); 62 | MCBrowser.LOGGER.error("Failed to download file", e); 63 | } 64 | }); 65 | } 66 | 67 | 68 | private static String cleanseFileUrl(String url) { 69 | String[] array = url.split("/"); 70 | return array[array.length - 1].replace("%2B", "+"); 71 | } 72 | 73 | public static String getModrinthSlugFromUrl(String url) { 74 | String string = url.replace("https://modrinth.com/", ""); 75 | string = string.substring(string.indexOf("/") + 1); 76 | return string.split("/")[0]; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/feature/specialbutton/SpecialButtonActions.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.feature.specialbutton; 2 | 3 | import net.minecraft.text.Text; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public enum SpecialButtonActions { 9 | MODRINTH_MOD("https://modrinth.com/mod/", Text.of("Download Mod"), SpecialButtonAction::downloadModrinthMod), 10 | MODRINTH_RP("https://modrinth.com/resourcepack/", Text.of("Download Resource Pack"), SpecialButtonAction::downloadModrinthRP); 11 | 12 | private final String url; 13 | private final Text buttonText; 14 | private final Runnable onExecute; 15 | 16 | 17 | SpecialButtonActions(String url, Text buttonText, Runnable onExecute){ 18 | this.url = url; 19 | this.buttonText = buttonText; 20 | this.onExecute = onExecute; 21 | } 22 | 23 | 24 | public String getUrl() { 25 | return url; 26 | } 27 | 28 | public Text getButtonText() { 29 | return buttonText; 30 | } 31 | 32 | public Runnable getOnExecute() { 33 | return onExecute; 34 | } 35 | 36 | public static List getAllUrls() { 37 | List urls = new ArrayList<>(); 38 | for (SpecialButtonActions action : values()) { 39 | urls.add(action.getUrl()); 40 | } 41 | return urls; 42 | } 43 | 44 | public static SpecialButtonActions getFromUrlConstantValue(String urlConstantValue) { 45 | for (SpecialButtonActions action : SpecialButtonActions.values()) { 46 | if (urlConstantValue.contains(action.getUrl())) { 47 | return action; 48 | } 49 | } 50 | return null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/feature/specialbutton/SpecialButtonHelper.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.feature.specialbutton; 2 | 3 | import io.github.blobanium.mcbrowser.MCBrowser; 4 | 5 | public class SpecialButtonHelper { 6 | 7 | public static void onPress(String url){ 8 | try { 9 | if(SpecialButtonActions.getFromUrlConstantValue(url) != null) { 10 | //noinspection DataFlowIssue 11 | SpecialButtonActions.getFromUrlConstantValue(url).getOnExecute().run(); 12 | } 13 | }catch (IllegalArgumentException e){ 14 | MCBrowser.LOGGER.error("An error occurred with specialButtons, please report this to the dev", e); 15 | } 16 | } 17 | 18 | public static boolean isOnCompatableSite(String url){ 19 | for(String element : SpecialButtonActions.getAllUrls()){ 20 | if(url != null && url.contains(element)){ 21 | return true; 22 | } 23 | } 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/mixin/CefClientMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.mixin; 2 | 3 | import io.github.blobanium.mcbrowser.MCBrowser; 4 | import io.github.blobanium.mcbrowser.util.BrowserCaches; 5 | import io.github.blobanium.mcbrowser.util.BrowserImpl; 6 | import io.github.blobanium.mcbrowser.util.BrowserUtil; 7 | import io.github.blobanium.mcbrowser.util.TabManager; 8 | import io.github.blobanium.mcbrowser.util.button.BrowserTabIcon; 9 | import net.minecraft.text.Text; 10 | import org.cef.CefClient; 11 | import org.cef.browser.CefBrowser; 12 | import org.cef.browser.CefFrame; 13 | import org.cef.callback.CefBeforeDownloadCallback; 14 | import org.cef.callback.CefDownloadItem; 15 | import org.cef.callback.CefDownloadItemCallback; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.Pseudo; 18 | import org.spongepowered.asm.mixin.injection.At; 19 | import org.spongepowered.asm.mixin.injection.Inject; 20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 21 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 22 | 23 | 24 | @Pseudo 25 | @Mixin(CefClient.class) 26 | public class CefClientMixin { 27 | @Inject(at = @At("HEAD"), method = "onAddressChange", remap = false) 28 | public void onAddressChange(CefBrowser browser, CefFrame frame, String url, CallbackInfo ci) { 29 | if (url != null && !(browser instanceof BrowserTabIcon) && (browser instanceof BrowserImpl)) { 30 | BrowserUtil.onUrlChange(); 31 | } 32 | BrowserCaches.urlCache.put(browser.getIdentifier(), url); 33 | } 34 | 35 | @Inject(at = @At("HEAD"), method = "onTooltip", remap = false) 36 | public void onTooltip(CefBrowser browser, String text, CallbackInfoReturnable cir) { 37 | BrowserUtil.tooltipText = text; 38 | } 39 | 40 | @Inject(at = @At("HEAD"), method = "onLoadingStateChange", remap = false) 41 | public void onLoadingStateChange(CefBrowser browser, boolean isLoading, boolean canGoBack, boolean canGoForward, CallbackInfo ci) { 42 | BrowserUtil.instance.updateWidgets(); 43 | BrowserCaches.isLoadingCache.put(browser.getIdentifier(), isLoading); 44 | } 45 | 46 | @Inject(at = @At("HEAD"), method = "onTitleChange", remap = false) 47 | public void onTitleChange(CefBrowser browser, String title, CallbackInfo ci) { 48 | TabManager.setTitleForTab(browser.getIdentifier(), title); 49 | } 50 | 51 | @Inject(at = @At("HEAD"), method = "onBeforeDownload", remap = false) 52 | public void onBeforeDownload(CefBrowser browser, CefDownloadItem downloadItem, String suggestedName, CefBeforeDownloadCallback callback, CallbackInfo ci){ 53 | if(MCBrowser.getConfig().allowDownloads){ 54 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.download.toast.started"), Text.translatable("mcbrowser.download.toast.started.description")); 55 | callback.Continue(suggestedName, true); 56 | }else{ 57 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.download.toast.disabled"), Text.translatable("mcbrowser.download.toast.disabled.description")); 58 | } 59 | } 60 | 61 | @Inject(at = @At("HEAD"), method = "onDownloadUpdated", remap = false) 62 | public void onDownloadUpdated(CefBrowser browser, CefDownloadItem downloadItem, CefDownloadItemCallback callback, CallbackInfo ci){ 63 | if(MCBrowser.isShuttingDown){ 64 | callback.cancel(); 65 | } 66 | System.out.println("Downloading " + downloadItem.getSuggestedFileName() + " (" + downloadItem.getPercentComplete() + "% Complete (" + downloadItem.getCurrentSpeed() + " bytes/s))"); 67 | if(downloadItem.isComplete()){ 68 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.download.toast.complete"), Text.translatable("mcbrowser.download.toast.completed.description", downloadItem.getSuggestedFileName())); 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/mixin/CefUtilMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.mixin; 2 | 3 | import io.github.blobanium.mcbrowser.MCBrowser; 4 | 5 | import java.util.*; 6 | 7 | import net.fabricmc.loader.api.FabricLoader; 8 | import org.cef.CefSettings; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.ModifyArg; 12 | 13 | @Mixin(targets = "com.cinemamod.mcef.CefUtil") 14 | public class CefUtilMixin { 15 | 16 | @ModifyArg(method = "init", at = @At(value = "INVOKE", target = "Lorg/cef/CefApp;getInstance([Ljava/lang/String;Lorg/cef/CefSettings;)Lorg/cef/CefApp;"), remap = false, index = 0) 17 | private static String[] customCefSwitches(String[] args) { 18 | ArrayList list = new ArrayList<>(List.of(args)); 19 | if (MCBrowser.getConfig().enableMediaStream) { 20 | list.add("--enable-media-stream"); 21 | } 22 | if(!MCBrowser.getConfig().customSwitches.isEmpty()){ 23 | String[] cefswitches = MCBrowser.getConfig().customSwitches.split(" "); 24 | Collections.addAll(list, cefswitches); 25 | } 26 | args = list.toArray(new String[0]); 27 | return args; 28 | } 29 | 30 | @ModifyArg(method = "init", at = @At(value = "INVOKE", target = "Lorg/cef/CefApp;getInstance([Ljava/lang/String;Lorg/cef/CefSettings;)Lorg/cef/CefApp;"), remap = false, index = 1) 31 | private static CefSettings customCefSettings(CefSettings settings) { 32 | if (MCBrowser.getConfig().saveCookies) { 33 | settings.cache_path = FabricLoader.getInstance().getConfigDir().resolve("MCBrowser") + "/browser"; 34 | settings.persist_session_cookies = true; 35 | } 36 | return settings; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/mixin/MinecraftClientMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.mixin; 2 | 3 | import io.github.blobanium.mcbrowser.MCBrowser; 4 | import io.github.blobanium.mcbrowser.util.TabManager; 5 | import net.fabricmc.loader.api.FabricLoader; 6 | import net.fabricmc.loader.api.SemanticVersion; 7 | import net.fabricmc.loader.api.VersionParsingException; 8 | import net.fabricmc.loader.api.metadata.version.VersionComparisonOperator; 9 | import net.minecraft.client.MinecraftClient; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | import java.io.BufferedReader; 16 | import java.io.IOException; 17 | import java.io.InputStreamReader; 18 | 19 | @Mixin(MinecraftClient.class) 20 | public class MinecraftClientMixin { 21 | @Inject(at = @At(value = "HEAD"), method = "close") 22 | private void onClose(CallbackInfo ci) { 23 | MCBrowser.isShuttingDown = true; 24 | if (MCBrowser.getConfig().saveTabs) { 25 | TabManager.saveTabsToJson(); 26 | } 27 | TabManager.reset(); 28 | } 29 | 30 | /** 31 | * @author JetbrainsAI 32 | * Injected method to be called after the Minecraft client's 'close' method. 33 | * This method ensures that any lingering JCEF (Java Chromium Embedded Framework) 34 | * helper processes are terminated to prevent CPU resource usage after closing 35 | * the Minecraft client. 36 | * @author Blobanium 37 | * The lingering JCEF Processes are due to a bug with the MCEF library and this method 38 | * is only implemented as a temporary workaround and will be removed once MCEF corrects 39 | * the issue. 40 | */ 41 | @Inject(at = @At("TAIL"), method = "close") 42 | private void onAfterClose(CallbackInfo ci) { 43 | try { 44 | if (VersionComparisonOperator.LESS_EQUAL.test(SemanticVersion.parse(FabricLoader.getInstance().getModContainer("mcef").get().getMetadata().getVersion().toString()), SemanticVersion.parse("2.1.5")) && System.getProperty("os.name").toLowerCase().contains("win")) { 45 | String processName = "jcef_helper.exe"; 46 | ProcessBuilder processBuilder = new ProcessBuilder("tasklist"); 47 | Process process = processBuilder.start(); 48 | 49 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 50 | String line; 51 | boolean isRunning = false; 52 | while ((line = reader.readLine()) != null) { 53 | if (line.contains(processName)) { 54 | isRunning = true; 55 | break; 56 | } 57 | } 58 | reader.close(); 59 | 60 | if (isRunning && MCBrowser.getConfig().killJcefHelperOnClose) { 61 | MCBrowser.LOGGER.warn("JCEF Processes are still running when they should have been shut down, attempting to close them to ensure processes are terminated and dont use up CPU resources after closing minecraft."); 62 | ProcessBuilder killProcess = new ProcessBuilder("taskkill", "/F", "/IM", processName); 63 | killProcess.start(); 64 | } 65 | } 66 | } catch (VersionParsingException | IOException e) { 67 | MCBrowser.LOGGER.fatal("JCEF Process Check Failed. There still may be lingering processes running in the background and eating up system resources. Please report this error to the developer.", e); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/mixin/UtilOperatingSystemMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.mixin; 2 | 3 | import io.github.blobanium.mcbrowser.MCBrowser; 4 | import io.github.blobanium.mcbrowser.util.BrowserUtil; 5 | import io.github.blobanium.mcbrowser.util.TabManager; 6 | import net.minecraft.util.Util; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | import java.net.MalformedURLException; 13 | import java.net.URI; 14 | 15 | @Mixin(Util.OperatingSystem.class) 16 | public class UtilOperatingSystemMixin { 17 | @Inject(method = "open(Ljava/net/URI;)V", at = @At("HEAD"), cancellable = true) 18 | private void open(URI uri, CallbackInfo ci){ 19 | try { 20 | if (MCBrowser.getConfig().openLinkInBrowser && (uri.getScheme().equals("http") || uri.getScheme().equals("https")) && !BrowserUtil.openInExternalBrowser) { 21 | TabManager.openNewTab(uri.toURL().toString()); 22 | ci.cancel(); 23 | } 24 | BrowserUtil.openInExternalBrowser = false; 25 | } catch (MalformedURLException e) { 26 | MCBrowser.LOGGER.error("Opening in browser. Failed to convert to URL", e); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/screen/BrowserScreen.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.screen; 2 | 3 | import io.github.blobanium.mcbrowser.MCBrowser; 4 | import io.github.blobanium.mcbrowser.config.BrowserAutoConfig; 5 | import io.github.blobanium.mcbrowser.feature.specialbutton.*; 6 | import io.github.blobanium.mcbrowser.util.*; 7 | import io.github.blobanium.mcbrowser.util.button.*; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | 11 | import net.minecraft.client.MinecraftClient; 12 | import net.minecraft.client.gui.DrawContext; 13 | import net.minecraft.client.gui.screen.Screen; 14 | import net.minecraft.client.gui.widget.*; 15 | import net.minecraft.text.Text; 16 | import org.lwjgl.glfw.GLFW; 17 | 18 | public class BrowserScreen extends Screen { 19 | public static final int BD_OFFSET = 50; 20 | //Previously BROWSER_DRAW_OFFSET 21 | 22 | //Ui 23 | public TextFieldWidget urlBox; 24 | public ButtonWidget forwardButton; 25 | public ButtonWidget backButton; 26 | public ReloadButton reloadButton; 27 | private PressableWidget[] navigationButtons; 28 | private ClickableWidget[] uiElements; 29 | private ClickableWidget[] zoomElements; 30 | public ArrayList tabButtons = new ArrayList<>(); 31 | private NewTabButton newTabButton = null; 32 | 33 | public ButtonWidget specialButton; 34 | 35 | private ButtonWidget openInBrowserButton; 36 | 37 | public TextWidget zoomDetails; 38 | public ButtonWidget zoomInButton; 39 | public ButtonWidget zoomOutButton; 40 | 41 | 42 | private int previousLimit; 43 | private boolean isFpsLowered = false; 44 | 45 | public BrowserImpl currentTab = TabManager.getCurrentTab(); 46 | 47 | public BrowserScreen(Text title) { 48 | super(title); 49 | } 50 | 51 | public void removeTab(int index) { 52 | remove(tabButtons.get(index)); 53 | tabButtons.get(index).resetIco(); 54 | tabButtons.remove(index); 55 | updateTabButtonsIndexes(index); 56 | updateTabSize(); 57 | } 58 | 59 | public void addTab(int index) { 60 | TabButton tabButton = new TabButton(BD_OFFSET, BD_OFFSET - 40, 100, 15, index); 61 | tabButtons.add(index, tabButton); 62 | updateTabButtonsIndexes(index + 1); 63 | addSelectableChild(tabButton); 64 | updateTabSize(); 65 | } 66 | 67 | private void updateTabButtonsIndexes(int i) { 68 | while (i < tabButtons.size()) { 69 | tabButtons.get(i).setTab(i); 70 | i++; 71 | } 72 | } 73 | 74 | private void updateTabSize() { 75 | if (!tabButtons.isEmpty()) { 76 | int size = Math.min(100, (this.width - (BD_OFFSET * 2) - 15) / tabButtons.size() - 5); 77 | for (TabButton tabButton : tabButtons) tabButton.setWidth(Math.max(15, size)); 78 | } 79 | } 80 | 81 | @Override 82 | protected void init() { 83 | super.init(); 84 | 85 | MinecraftClient client = MinecraftClient.getInstance(); 86 | BrowserAutoConfig config = MCBrowser.getConfig(); 87 | if(config.limitBrowserFramerate && client.options.getMaxFps().getValue() > config.browserFPS){ 88 | previousLimit = client.options.getMaxFps().getValue(); 89 | client.getInactivityFpsLimiter().setMaxFps(config.browserFPS); 90 | isFpsLowered = true; 91 | } 92 | 93 | BrowserUtil.instance = this; 94 | BrowserUtil.tooltipText = null; 95 | 96 | newTabButton = new NewTabButton(BD_OFFSET, BD_OFFSET - 40, 15, 15, Text.of("+")); 97 | for (TabHolder tab : TabManager.tabs) { 98 | int index = TabManager.tabs.indexOf(tab); 99 | TabButton tabButton = new TabButton(BD_OFFSET, BD_OFFSET - 40, 100, 15, index); 100 | tabButtons.add(tabButton); 101 | } 102 | for (TabButton tabButton : tabButtons) addSelectableChild(tabButton); 103 | updateTabSize(); 104 | initElements(); 105 | updateWidgets(); 106 | } 107 | 108 | public void initElements() { 109 | urlBox = BrowserUtil.initUrlBox(BD_OFFSET, width); 110 | 111 | backButton = BrowserUtil.initButton(Text.of("◀"), button -> currentTab.goBack(), BD_OFFSET, 1); 112 | forwardButton = BrowserUtil.initButton(Text.of("▶"), button -> currentTab.goForward(), BD_OFFSET + 20, 1); 113 | reloadButton = new ReloadButton(BD_OFFSET + 40, BD_OFFSET - 20, 15, 15); 114 | ButtonWidget homeButton = BrowserUtil.initButton(Text.of("⌂"), button -> BrowserUtil.homeButtonAction(), BD_OFFSET + 60, 1); 115 | specialButton = ButtonWidget.builder(Text.of(""), button -> SpecialButtonHelper.onPress(TabManager.getCurrentUrl())).dimensions(BD_OFFSET, height - BD_OFFSET + 5, 150, 15).build(); 116 | openInBrowserButton = ButtonWidget.builder(Text.of("Open In External Browser"), button -> BrowserUtil.openInBrowser()).dimensions(width - 200, height - BD_OFFSET + 5, 150, 15).build(); 117 | zoomDetails = new TextWidget(BrowserUtil.getZoomLevelText(currentTab.getZoomLevel()), MinecraftClient.getInstance().textRenderer); 118 | zoomDetails.setPosition(width-50-zoomDetails.getWidth(), BD_OFFSET - 49); 119 | zoomInButton = BrowserUtil.initButton(Text.of("+"), button -> zoomControl(BrowserUtil.ZoomActions.INCREASE), width - 65, 2); 120 | zoomOutButton = BrowserUtil.initButton(Text.of("-"), button -> zoomControl(BrowserUtil.ZoomActions.DECREASE), width - 85, 2); 121 | 122 | navigationButtons = new PressableWidget[]{forwardButton, backButton, reloadButton, homeButton}; 123 | zoomElements = new ClickableWidget[]{zoomInButton, zoomOutButton, zoomDetails}; 124 | uiElements = new ClickableWidget[]{forwardButton, backButton, reloadButton, homeButton, urlBox, specialButton, openInBrowserButton, newTabButton, zoomDetails, zoomInButton, zoomOutButton}; 125 | for (ClickableWidget widget : uiElements) addSelectableChild(widget); 126 | } 127 | 128 | public void updateWidgets() { 129 | urlBox.setText(currentTab.getURL()); 130 | urlBox.setCursorToStart(false); 131 | backButton.active = currentTab.canGoBack(); 132 | forwardButton.active = currentTab.canGoForward(); 133 | reloadButton.setMessage(Text.of(currentTab.isLoading() ? "❌" : "⟳")); 134 | SpecialButtonActions action = SpecialButtonActions.getFromUrlConstantValue(TabManager.getCurrentUrl()); 135 | if (action != null) specialButton.setMessage(action.getButtonText()); 136 | currentTab.resize(BrowserUtil.scaleX(width, BD_OFFSET), BrowserUtil.scaleY(height, BD_OFFSET)); 137 | } 138 | 139 | @Override 140 | public void resize(MinecraftClient minecraft, int i, int j) { 141 | ArrayList tempList = new ArrayList<>(tabButtons); 142 | tabButtons.clear(); 143 | super.resize(minecraft, i, j); 144 | resizeBrowser(); 145 | updateWidgets(); 146 | for (TabButton tabButton : tabButtons) { remove(tabButton); } 147 | tabButtons = tempList; 148 | for (TabButton tabButton : tabButtons) { addSelectableChild(tabButton); } 149 | updateTabSize(); 150 | for (ClickableWidget widget : uiElements) if (!children().contains(widget)) addSelectableChild(widget); 151 | } 152 | 153 | @Override 154 | public void close() { 155 | BrowserUtil.instance = null; 156 | for (TabButton tabButton : tabButtons) tabButton.resetIco(); 157 | if(isFpsLowered) MinecraftClient.getInstance().getInactivityFpsLimiter().setMaxFps(previousLimit); 158 | super.close(); 159 | } 160 | 161 | @Override 162 | public void render(DrawContext context, int mouseX, int mouseY, float delta) { 163 | super.render(context, mouseX, mouseY, delta); 164 | if (TabManager.getCurrentTabHolder().isInit()) { 165 | currentTab.render(BD_OFFSET, BD_OFFSET, this.width - BD_OFFSET * 2, this.height - BD_OFFSET * 2); 166 | } else { 167 | TabManager.getCurrentTabHolder().init(); 168 | resizeBrowser(); 169 | } 170 | renderWidgets(context, mouseX, mouseY, delta); 171 | if (BrowserUtil.tooltipText != null && BrowserUtil.tooltipText.getBytes().length != 0) setTooltip(Text.of(BrowserUtil.tooltipText)); 172 | } 173 | 174 | private void renderWidgets(DrawContext context, int mouseX, int mouseY, float delta){ 175 | urlBox.renderWidget(context, mouseX, mouseY, delta); 176 | for (PressableWidget button : navigationButtons) button.render(context, mouseX, mouseY, delta); 177 | if (SpecialButtonHelper.isOnCompatableSite(TabManager.getCurrentUrl())) specialButton.render(context, mouseX, mouseY, delta); 178 | for (TabButton tabButton : tabButtons) tabButton.render(context, mouseX, mouseY, delta); 179 | for (ClickableWidget zoom :zoomElements) if (BrowserUtil.ZoomActions.shouldRenderZoomElements()) { 180 | if (zoom.isMouseOver(mouseX, mouseY)) BrowserUtil.ZoomActions.resetLastTimeCalled(); 181 | zoom.active = true; 182 | zoom.render(context, mouseX, mouseY, delta); 183 | }else{ 184 | zoom.active = false; 185 | } 186 | newTabButton.render(context, mouseX, mouseY, delta); 187 | openInBrowserButton.render(context, mouseX, mouseY, delta); 188 | } 189 | 190 | @Override 191 | public boolean mouseClicked(double mouseX, double mouseY, int button) { 192 | mouseButtonControlImpl(mouseX, mouseY, button, true); 193 | return super.mouseClicked(mouseX, mouseY, button); 194 | } 195 | 196 | @Override 197 | public boolean mouseReleased(double mouseX, double mouseY, int button) { 198 | mouseButtonControlImpl(mouseX, mouseY, button, false); 199 | return super.mouseReleased(mouseX, mouseY, button); 200 | } 201 | 202 | @Override 203 | public void mouseMoved(double mouseX, double mouseY) { 204 | BrowserUtil.runAsyncIfEnabled(() -> currentTab.sendMouseMove(BrowserUtil.mouseX(mouseX, BD_OFFSET), BrowserUtil.mouseY(mouseY, BD_OFFSET))); 205 | super.mouseMoved(mouseX, mouseY); 206 | } 207 | 208 | @Override 209 | public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { 210 | BrowserUtil.updateMouseLocation(mouseX, mouseY); 211 | return super.mouseDragged(mouseX, mouseY, button, dragX, dragY); 212 | } 213 | 214 | @Override 215 | public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { 216 | BrowserUtil.runAsyncIfEnabled(() -> currentTab.sendMouseWheel(BrowserUtil.mouseX(mouseX, BD_OFFSET), BrowserUtil.mouseY(mouseY, BD_OFFSET), verticalAmount, 0)); 217 | return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); 218 | } 219 | 220 | @Override 221 | public boolean keyPressed(int keyCode, int scanCode, int modifiers) { 222 | if (Screen.hasControlDown() && SwitchFunctions.ctrlKeyPressedSwitch(keyCode, modifiers)) { 223 | return true; 224 | } 225 | 226 | sendKeyActivityAndSetFocus(keyCode, scanCode, modifiers, true); 227 | 228 | // Prevent the enter key if buttons aren’t focused 229 | if (Arrays.stream(uiElements).noneMatch(ClickableWidget::isFocused) && keyCode == GLFW.GLFW_KEY_ENTER) { 230 | return true; 231 | } 232 | 233 | // Close screen on Esc key 234 | if (keyCode == 256 && this.shouldCloseOnEsc()) { 235 | this.close(); 236 | return true; 237 | } 238 | 239 | return this.getFocused() != null && this.getFocused().keyPressed(keyCode, scanCode, modifiers); 240 | } 241 | 242 | @Override 243 | public boolean keyReleased(int keyCode, int scanCode, int modifiers) { 244 | sendKeyActivityAndSetFocus(keyCode, scanCode, modifiers, false); 245 | return super.keyReleased(keyCode, scanCode, modifiers); 246 | } 247 | 248 | private void sendKeyActivityAndSetFocus(int keyCode, int scanCode, int modifiers, boolean isPress){ 249 | if (isPress ? !urlBox.isFocused() : !Screen.hasControlDown() || keyCode != GLFW.GLFW_KEY_TAB) BrowserUtil.runAsyncIfEnabled(() -> currentTab.sendKeyPressRelease(keyCode, scanCode, modifiers, isPress)); 250 | setFocus(); 251 | } 252 | 253 | @Override 254 | public boolean charTyped(char codePoint, int modifiers) { 255 | if(codePoint == (char) 0) return false; 256 | BrowserUtil.runAsyncIfEnabled(() -> currentTab.sendKeyTyped(codePoint, modifiers)); 257 | setFocus(); 258 | return super.charTyped(codePoint, modifiers); 259 | } 260 | 261 | 262 | //Multi Override Util Methods 263 | public void setFocus() { 264 | boolean browserFocus = true; 265 | for (ClickableWidget widget : uiElements) { 266 | boolean mouseOver = widget.isMouseOver(BrowserUtil.lastMouseX, BrowserUtil.lastMouseY); 267 | widget.setFocused(mouseOver); 268 | if(mouseOver) browserFocus = false; 269 | } 270 | currentTab.setFocus(browserFocus); 271 | } 272 | 273 | private void resizeBrowser() { 274 | if (width > 100 && height > 100) for (TabHolder tab : TabManager.tabs) tab.getBrowser().resize(BrowserUtil.scaleX(width, BD_OFFSET), BrowserUtil.scaleY(height, BD_OFFSET)); 275 | if (this.urlBox != null) urlBox.setWidth(BrowserUtil.getUrlBoxWidth(width, BD_OFFSET)); 276 | if (this.specialButton != null) specialButton.setPosition(BD_OFFSET, height - BD_OFFSET + 5); 277 | if (this.openInBrowserButton != null) openInBrowserButton.setPosition(width - 200, height - BD_OFFSET + 5); 278 | } 279 | 280 | private void mouseButtonControlImpl(double mouseX, double mouseY, int button, boolean isClick) { 281 | if (mouseX > BD_OFFSET && mouseX < this.width - BD_OFFSET && mouseY > BD_OFFSET && mouseY < this.height - BD_OFFSET) currentTab.mouseButtonControl(mouseX, mouseY, button, isClick); 282 | setFocus(); 283 | } 284 | 285 | public void zoomControl(byte zoomAction){ 286 | if(zoomAction == BrowserUtil.ZoomActions.INCREASE) currentTab.setZoomLevel(currentTab.getZoomLevel() + MCBrowser.getConfig().zoomScalingFactor); 287 | else if(zoomAction == BrowserUtil.ZoomActions.DECREASE) currentTab.setZoomLevel(currentTab.getZoomLevel() - MCBrowser.getConfig().zoomScalingFactor); 288 | else if(zoomAction == BrowserUtil.ZoomActions.RESET) currentTab.setZoomLevel(0.0); 289 | else throw new IllegalArgumentException("Invalid zoom action value: " + zoomAction); 290 | zoomDetails.setMessage(BrowserUtil.getZoomLevelText(currentTab.getZoomLevel())); 291 | BrowserUtil.ZoomActions.resetLastTimeCalled(); 292 | } 293 | } 294 | 295 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/util/BrowserCaches.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.util; 2 | 3 | import java.util.HashMap; 4 | 5 | public class BrowserCaches { 6 | public static final HashMap urlCache = new HashMap<>(); 7 | public static final HashMap isLoadingCache = new HashMap<>(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/util/BrowserImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.util; 2 | 3 | import com.cinemamod.mcef.MCEFBrowser; 4 | import com.cinemamod.mcef.MCEFClient; 5 | import com.mojang.blaze3d.systems.RenderSystem; 6 | import net.minecraft.client.gl.ShaderProgramKeys; 7 | import net.minecraft.client.render.*; 8 | import org.lwjgl.glfw.GLFW; 9 | 10 | import static io.github.blobanium.mcbrowser.screen.BrowserScreen.BD_OFFSET; 11 | 12 | public class BrowserImpl extends MCEFBrowser { 13 | public BrowserImpl(MCEFClient client, String url, boolean transparent) { 14 | super(client, url, transparent); 15 | } 16 | 17 | //Improves performance by limiting each getURL call to one tick for each instance. 18 | @Override 19 | public String getURL(){ 20 | return BrowserCaches.urlCache.getOrDefault(this.getIdentifier(), super.getURL()); 21 | } 22 | 23 | @Override 24 | public boolean isLoading(){ 25 | return BrowserCaches.isLoadingCache.getOrDefault(this.getIdentifier(), super.isLoading()); 26 | } 27 | 28 | protected static final int Z_SHIFT = -1; 29 | 30 | public void render(int x, int y, int width, int height) { 31 | RenderSystem.disableDepthTest(); 32 | RenderSystem.setShader(ShaderProgramKeys.POSITION_TEX_COLOR); 33 | RenderSystem.setShaderTexture(0, this.getRenderer().getTextureID()); 34 | Tessellator t = Tessellator.getInstance(); 35 | BufferBuilder buffer = t.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR); 36 | buffer.vertex(x, y + height, Z_SHIFT).texture(0.0f, 1.0f).color(255, 255, 255, 255); 37 | buffer.vertex(x + width, y + height, Z_SHIFT).texture(1.0f, 1.0f).color(255, 255, 255, 255); 38 | buffer.vertex(x + width, y, Z_SHIFT).texture(1.0f, 0.0f).color(255, 255, 255, 255); 39 | buffer.vertex(x, y, Z_SHIFT).texture(0.0f, 0.0f).color(255, 255, 255, 255); 40 | BufferRenderer.drawWithGlobalProgram(buffer.end()); 41 | RenderSystem.setShaderTexture(0, 0); 42 | RenderSystem.enableDepthTest(); 43 | } 44 | 45 | public void sendKeyPressRelease(int keyCode, int scanCode, int modifiers, boolean isPress){ 46 | if(isPress){ 47 | sendKeyPress(keyCode, scanCode, modifiers); 48 | }else{ 49 | sendKeyRelease(keyCode, scanCode, modifiers); 50 | } 51 | } 52 | 53 | public void mouseButtonControl(double mouseX, double mouseY, int button, boolean isClick) { 54 | if (button == GLFW.GLFW_MOUSE_BUTTON_4 && canGoBack() && !isClick) { 55 | goBack(); 56 | } else if (button == GLFW.GLFW_MOUSE_BUTTON_5 && canGoForward() && !isClick) { 57 | goForward(); 58 | } else { 59 | BrowserUtil.runAsyncIfEnabled(() -> { 60 | if (isClick) { 61 | sendMousePress(BrowserUtil.mouseX(mouseX, BD_OFFSET), BrowserUtil.mouseY(mouseY, BD_OFFSET), button); 62 | } else { 63 | sendMouseRelease(BrowserUtil.mouseX(mouseX, BD_OFFSET), BrowserUtil.mouseY(mouseY, BD_OFFSET), button); 64 | } 65 | }); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/util/BrowserUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.util; 2 | 3 | import com.cinemamod.mcef.MCEF; 4 | import io.github.blobanium.mcbrowser.MCBrowser; 5 | import io.github.blobanium.mcbrowser.feature.BrowserFeatureUtil; 6 | import io.github.blobanium.mcbrowser.feature.specialbutton.SpecialButtonActions; 7 | import io.github.blobanium.mcbrowser.screen.BrowserScreen; 8 | import io.github.blobanium.mcbrowser.util.button.BrowserTabIcon; 9 | import java.net.URI; 10 | import java.net.URISyntaxException; 11 | import java.util.concurrent.CompletableFuture; 12 | import net.minecraft.client.MinecraftClient; 13 | import net.minecraft.client.gui.widget.ButtonWidget; 14 | import net.minecraft.client.gui.widget.TextFieldWidget; 15 | import net.minecraft.text.MutableText; 16 | import net.minecraft.text.Text; 17 | import net.minecraft.util.Util; 18 | import org.lwjgl.glfw.GLFW; 19 | 20 | public class BrowserUtil { 21 | //Mouse position 22 | public static double lastMouseX; 23 | public static double lastMouseY; 24 | 25 | public static BrowserScreen instance; 26 | 27 | public static String tooltipText; 28 | 29 | public static boolean openInExternalBrowser = false; 30 | 31 | //Navigation initialization methods 32 | public static ButtonWidget initButton(Text message, ButtonWidget.PressAction onPress, int positionX, int buttonLevel) { 33 | return ButtonWidget.builder(message, onPress) 34 | .dimensions(positionX, BrowserScreen.BD_OFFSET - (20 * buttonLevel), 15, 15) 35 | .build(); 36 | } 37 | 38 | //Matrix related commands 39 | 40 | public static int mouseX(double x, int offset) { 41 | lastMouseX = x; 42 | return (int) ((x - offset) * MinecraftClient.getInstance().getWindow().getScaleFactor()); 43 | } 44 | 45 | public static int mouseY(double y, int offset) { 46 | lastMouseY = y; 47 | return (int) ((y - offset) * MinecraftClient.getInstance().getWindow().getScaleFactor()); 48 | } 49 | 50 | public static void updateMouseLocation(double mouseX, double mouseY) { 51 | lastMouseX = mouseX; 52 | lastMouseY = mouseY; 53 | } 54 | 55 | public static int scaleX(double x, int offset) { 56 | return (int) ((x - offset * 2) * MinecraftClient.getInstance().getWindow().getScaleFactor()); 57 | } 58 | 59 | public static int scaleY(double y, int offset) { 60 | return (int) ((y - offset * 2) * MinecraftClient.getInstance().getWindow().getScaleFactor()); 61 | } 62 | 63 | public static int getUrlBoxWidth(int width, int offset) { 64 | return width - (offset * 2) - 80; 65 | } 66 | 67 | //Browser Creation 68 | public static BrowserImpl createBrowser(String url) { 69 | if (MCEF.isInitialized()) { 70 | BrowserImpl browser = new BrowserImpl(MCEF.getClient(), url, false); 71 | browser.setCloseAllowed(); 72 | browser.createImmediately(); 73 | return browser; 74 | } else { 75 | throw new IllegalStateException("Chromium Embedded Framework was never initialized."); 76 | } 77 | } 78 | 79 | public static BrowserTabIcon createIcon(String url) { 80 | if (MCEF.isInitialized()) { 81 | BrowserTabIcon icon = new BrowserTabIcon(MCEF.getClient(), url, false); 82 | icon.setCloseAllowed(); 83 | icon.createImmediately(); 84 | return icon; 85 | } else { 86 | throw new IllegalStateException("Chromium Embedded Framework was never initialized."); 87 | } 88 | } 89 | 90 | public static TextFieldWidget initUrlBox(int offset, int width) { 91 | TextFieldWidget urlBox = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, offset + 80, offset - 20, BrowserUtil.getUrlBoxWidth(width, offset), 15, Text.of("")) { 92 | @Override 93 | public boolean keyPressed(int keyCode, int scanCode, int modifiers) { 94 | if (isFocused()) { 95 | for (TabHolder tab : TabManager.tabs) { 96 | BrowserImpl browser = tab.getBrowser(); 97 | if (browser != null) browser.setFocus(false); 98 | } 99 | if (keyCode == GLFW.GLFW_KEY_ENTER) { 100 | TabManager.getCurrentTab().loadURL(BrowserFeatureUtil.prediffyURL(getText())); 101 | setFocused(false); 102 | } 103 | } 104 | return super.keyPressed(keyCode, scanCode, modifiers); 105 | } 106 | 107 | }; 108 | urlBox.setMaxLength(2048); //Most browsers have a max length of 2048 109 | return urlBox; 110 | } 111 | 112 | //Button related Methods 113 | public static void openInBrowser(){ 114 | try { 115 | openInExternalBrowser = true; 116 | Util.getOperatingSystem().open(new URI(TabManager.getCurrentUrl())); 117 | } catch (URISyntaxException e) { 118 | MCBrowser.LOGGER.fatal("Unable to open Browser", e); 119 | } 120 | } 121 | 122 | public static void homeButtonAction(){ 123 | String prediffyedHomePage = BrowserFeatureUtil.prediffyURL(MCBrowser.getConfig().homePage); 124 | instance.urlBox.setText(prediffyedHomePage); 125 | TabManager.getCurrentTab().loadURL(prediffyedHomePage); 126 | } 127 | 128 | //Event Methods 129 | public static void onUrlChange() { 130 | if (instance.urlBox.isFocused()) { 131 | instance.urlBox.setFocused(false); 132 | } 133 | instance.urlBox.setText(TabManager.getCurrentUrl()); 134 | instance.urlBox.setCursorToStart(false); 135 | SpecialButtonActions action = SpecialButtonActions.getFromUrlConstantValue(TabManager.getCurrentUrl()); 136 | if (action != null) { 137 | instance.specialButton.setMessage(action.getButtonText()); 138 | } 139 | } 140 | 141 | public static class Keybinds{ 142 | public static final int CTRL_T = GLFW.GLFW_MOD_CONTROL + GLFW.GLFW_KEY_T; 143 | public static final int CTRL_SHIFT_T = GLFW.GLFW_MOD_CONTROL + GLFW.GLFW_MOD_SHIFT + GLFW.GLFW_KEY_T; 144 | public static final int CTRL_TAB = GLFW.GLFW_MOD_CONTROL + GLFW.GLFW_KEY_TAB; 145 | public static final int CTRL_SHIFT_TAB = GLFW.GLFW_MOD_CONTROL + GLFW.GLFW_MOD_SHIFT + GLFW.GLFW_KEY_TAB; 146 | } 147 | 148 | public static void runAsyncIfEnabled(Runnable runnable){ 149 | if (MCBrowser.getConfig().asyncBrowserInput) { 150 | CompletableFuture.runAsync(runnable); 151 | } else { 152 | runnable.run(); 153 | } 154 | } 155 | 156 | public static int zoomLevelToZoomPercentage(double zoomLevel) { 157 | return (int) Math.round(Math.pow(1.2, zoomLevel) * 100); 158 | } 159 | 160 | public static MutableText getZoomLevelText(double zoomLevel){ 161 | return Text.translatable("mcbrowser.zoom.percent", zoomLevelToZoomPercentage(zoomLevel)); 162 | } 163 | 164 | public static class ZoomActions{ 165 | public static final byte INCREASE = 0x00; 166 | public static final byte DECREASE = 0x01; 167 | public static final byte RESET = 0x02; 168 | 169 | public static long lastTimeCalled = 0; 170 | 171 | public static void resetLastTimeCalled(){ 172 | lastTimeCalled = System.currentTimeMillis(); 173 | } 174 | 175 | public static boolean shouldRenderZoomElements(){ 176 | return System.currentTimeMillis() <= lastTimeCalled + 2500; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/util/SwitchFunctions.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.util; 2 | 3 | import io.github.blobanium.mcbrowser.feature.specialbutton.SpecialButtonAction; 4 | import io.github.blobanium.mcbrowser.feature.specialbutton.SpecialButtonActions; 5 | import net.minecraft.MinecraftVersion; 6 | import net.minecraft.text.MutableText; 7 | import net.minecraft.text.Text; 8 | import org.lwjgl.glfw.GLFW; 9 | 10 | import java.net.MalformedURLException; 11 | import java.net.URISyntaxException; 12 | import java.net.URL; 13 | import java.net.URI; 14 | 15 | 16 | 17 | /** 18 | * The SwitchFunctions class contains static utility classes and methods. 19 | * There exists an issue where code climate quality fails to properly rate a file. Making it look like it got a perfect rating, but in reality the code is really sloppy. 20 | * Code Climate is a third party application blobanium uses to make sure code is as clean as possible. 21 | * This file serves as a temporary workaround for this issue. 22 | */ 23 | public class SwitchFunctions { 24 | 25 | public static boolean ctrlKeyPressedSwitch(int keyCode, int modifiers){ 26 | switch (keyCode) { 27 | case GLFW.GLFW_KEY_TAB: 28 | case GLFW.GLFW_KEY_T: 29 | // Tab Functions 30 | TabManager.tabControl(keyCode + modifiers); 31 | BrowserUtil.instance.setFocus(); 32 | return true; 33 | case GLFW.GLFW_KEY_EQUAL: 34 | BrowserUtil.instance.zoomControl(BrowserUtil.ZoomActions.INCREASE); 35 | return true; 36 | case GLFW.GLFW_KEY_MINUS: 37 | BrowserUtil.instance.zoomControl(BrowserUtil.ZoomActions.DECREASE); 38 | return true; 39 | case GLFW.GLFW_KEY_0: 40 | BrowserUtil.instance.zoomControl(BrowserUtil.ZoomActions.RESET); 41 | return true; 42 | } 43 | return false; 44 | } 45 | public static class SpecialButtonActionSwitches{ 46 | 47 | public static MutableText getTranslation(byte type, SpecialButtonActions action) { 48 | return switch (action) { 49 | case MODRINTH_MOD -> switch (type) { 50 | case SpecialButtonAction.START_DL_DESCRIPTION -> Text.translatable("mcbrowser.download.toast.started.description.mod"); 51 | case SpecialButtonAction.END_DL_DESCRIPTION -> Text.translatable("mcbrowser.download.toast.complete.description.mod"); 52 | default -> throw new IllegalStateException("Unexpected type value: " + type); 53 | }; 54 | case MODRINTH_RP -> switch (type) { 55 | case SpecialButtonAction.START_DL_DESCRIPTION -> Text.translatable("mcbrowser.download.toast.started.description.rp"); 56 | case SpecialButtonAction.END_DL_DESCRIPTION -> Text.translatable("mcbrowser.download.toast.complete.description.rp"); 57 | default -> throw new IllegalStateException("Unexpected type value: " + type); 58 | }; 59 | 60 | //Reserved for future usage. 61 | //noinspection UnnecessaryDefault 62 | default -> throw new IllegalStateException("Unexpected action value: " + action); 63 | }; 64 | } 65 | 66 | public static URL getTargetURL(SpecialButtonActions action) throws MalformedURLException, URISyntaxException { 67 | return switch (action) { 68 | case MODRINTH_MOD -> new URI("https://api.modrinth.com/v2/project/" + SpecialButtonAction.getModrinthSlugFromUrl(TabManager.getCurrentUrl()) + "/version?game_versions=[%22" + MinecraftVersion.CURRENT.getName() + "%22]&loaders=[%22fabric%22]").toURL(); 69 | case MODRINTH_RP -> new URI("https://api.modrinth.com/v2/project/" + SpecialButtonAction.getModrinthSlugFromUrl(TabManager.getCurrentUrl()) + "/version?game_versions=[%22" + MinecraftVersion.CURRENT.getName() + "%22]").toURL(); 70 | 71 | //Reserved for future usage. 72 | //noinspection UnnecessaryDefault 73 | default -> throw new IllegalStateException("Unexpected action value: " + action); 74 | }; 75 | } 76 | 77 | public static String getTargetDirectory(SpecialButtonActions action) { 78 | return switch (action) { 79 | case MODRINTH_MOD -> "mods/"; 80 | case MODRINTH_RP -> "resourcepacks/"; 81 | }; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/util/TabHolder.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.util; 2 | 3 | import io.github.blobanium.mcbrowser.util.button.BrowserTabIcon; 4 | 5 | public class TabHolder { 6 | public final String holderUrl; 7 | BrowserImpl browser; 8 | BrowserTabIcon icon = null; 9 | private boolean init = false; 10 | public String holderTitle; 11 | 12 | public TabHolder(String url) { 13 | holderUrl = url; 14 | } 15 | 16 | public void init() { 17 | if (browser != null) { 18 | browser.close(); 19 | } 20 | browser = BrowserUtil.createBrowser(holderUrl); 21 | init = true; 22 | } 23 | 24 | public boolean isInit() { 25 | return init; 26 | } 27 | 28 | public String getUrl() { 29 | return isInit() ? getBrowser().getURL() : holderUrl; 30 | } 31 | 32 | public void initIcon(String url) { 33 | String parsedUrl = url; 34 | if (url.contains("://")) { 35 | parsedUrl = url.substring(url.indexOf("://") + 3); 36 | } 37 | icon = BrowserUtil.createIcon(parsedUrl); 38 | } 39 | 40 | public BrowserImpl getBrowser() { 41 | return browser; 42 | } 43 | 44 | public BrowserTabIcon getIcon() { 45 | return icon; 46 | } 47 | 48 | public String getTitle() { 49 | return holderTitle; 50 | } 51 | 52 | public void setTitle(String title){ 53 | holderTitle = title; 54 | } 55 | 56 | public void resetIcon() { 57 | if (icon == null) { 58 | return; 59 | } 60 | icon.close(); 61 | icon = null; 62 | } 63 | 64 | public void close() { 65 | if (isInit()) { 66 | browser.close(); 67 | } 68 | resetIcon(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/util/TabManager.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.util; 2 | 3 | import com.google.common.reflect.TypeToken; 4 | import com.google.gson.Gson; 5 | import com.google.gson.GsonBuilder; 6 | import io.github.blobanium.mcbrowser.MCBrowser; 7 | import io.github.blobanium.mcbrowser.feature.BrowserFeatureUtil; 8 | import io.github.blobanium.mcbrowser.screen.BrowserScreen; 9 | import java.io.File; 10 | import java.io.FileReader; 11 | import java.io.FileWriter; 12 | import java.io.IOException; 13 | import java.lang.reflect.Type; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import net.fabricmc.loader.api.FabricLoader; 17 | import net.minecraft.client.MinecraftClient; 18 | 19 | import static io.github.blobanium.mcbrowser.util.BrowserUtil.Keybinds.*; 20 | 21 | public class TabManager { 22 | public static final List tabs = new ArrayList<>(); 23 | public static final List closedTabs = new ArrayList<>(); 24 | public static int activeTab = 0; 25 | 26 | public static void setActiveTab(int index) { 27 | activeTab = index; 28 | if (MinecraftClient.getInstance().currentScreen instanceof BrowserScreen screen) { 29 | screen.currentTab = getCurrentTab(); 30 | BrowserUtil.instance.updateWidgets(); 31 | } 32 | } 33 | 34 | public static void openNewTab() { 35 | openNewTab(BrowserFeatureUtil.prediffyURL(MCBrowser.getConfig().homePage)); 36 | } 37 | 38 | public static void openNewTab(String url) { 39 | openNewTab(url, tabs.size()); 40 | } 41 | 42 | public static void openNewTab(String url, int index) { 43 | openNewTab(url, index, index); 44 | } 45 | 46 | public static void openNewTab(String url, int index, int setActive) { 47 | tabs.add(index, new TabHolder(url)); 48 | setActiveTab(setActive); 49 | if (MinecraftClient.getInstance().currentScreen instanceof BrowserScreen) { 50 | BrowserUtil.instance.addTab(index); 51 | } else { 52 | MCBrowser.openBrowser(); 53 | } 54 | } 55 | 56 | public static void closeTab(int index) { 57 | if (BrowserUtil.instance != null) { 58 | BrowserUtil.instance.removeTab(index); 59 | } 60 | closedTabs.add(tabs.get(index).getUrl()); 61 | tabs.get(index).close(); 62 | tabs.remove(index); 63 | if (tabs.isEmpty() && BrowserUtil.instance != null) { 64 | BrowserUtil.instance.close(); 65 | return; 66 | } 67 | if (index <= activeTab && activeTab != 0) { 68 | setActiveTab(activeTab - 1); 69 | } else if (BrowserUtil.instance != null) { 70 | BrowserUtil.instance.updateWidgets(); 71 | } 72 | } 73 | 74 | public static void copyTab(int index) { 75 | openNewTab(tabs.get(index).getUrl(), index + 1, index); 76 | } 77 | 78 | public static TabHolder getCurrentTabHolder() { 79 | return tabs.get(activeTab); 80 | } 81 | 82 | public static BrowserImpl getCurrentTab() { 83 | if (!tabs.get(activeTab).isInit()) { 84 | tabs.get(activeTab).init(); 85 | } 86 | return tabs.get(activeTab).getBrowser(); 87 | } 88 | 89 | public static void saveTabsToJson() { 90 | ArrayList urls = new ArrayList<>(); 91 | for (TabHolder tab : tabs) { 92 | urls.add(tab.getUrl()); 93 | } 94 | try (FileWriter fileWriter = new FileWriter(FabricLoader.getInstance().getConfigDir().resolve("MCBrowser") + "\\tabs" + ".json")){ 95 | Gson gson = new GsonBuilder().setPrettyPrinting().create(); 96 | gson.toJson(urls, fileWriter); 97 | } catch (IOException e) { 98 | MCBrowser.LOGGER.error("Could not save opened tabs for MCBrowser", e); 99 | return; 100 | } 101 | MCBrowser.LOGGER.info("Successfully saved tabs for MCBrowser"); 102 | } 103 | 104 | public static void setTitleForTab(int identifier, String title){ 105 | for(TabHolder tab: tabs){ 106 | if(identifier == tab.getBrowser().getIdentifier()){ 107 | tab.setTitle(title); 108 | } 109 | } 110 | } 111 | 112 | public static void loadTabsFromJson() { 113 | String filename = FabricLoader.getInstance().getConfigDir().resolve("MCBrowser") + "\\tabs" + ".json"; 114 | if (new File(filename).exists()) { 115 | try (FileReader fileReader = new FileReader(filename)){ 116 | Type type = new TypeToken>() {}.getType(); 117 | Gson gson = new Gson(); 118 | ArrayList urls = gson.fromJson(fileReader, type); 119 | fileReader.close(); 120 | for (String url : urls) { 121 | if (!url.isEmpty()) { 122 | TabHolder tab = new TabHolder(url); 123 | tabs.add(tab); 124 | } 125 | } 126 | } catch (IOException e) { 127 | MCBrowser.LOGGER.error("Could not read list of tabs from \"{}\"", filename, e); 128 | } 129 | } 130 | } 131 | 132 | public static void reset() { 133 | for (TabHolder tab : tabs) { 134 | tab.close(); 135 | } 136 | tabs.clear(); 137 | activeTab = 0; 138 | } 139 | 140 | public static String getCurrentUrl() { 141 | return getCurrentTab().getURL(); 142 | } 143 | 144 | public static void tabControl(int keyCodeModifiers){ 145 | //TODO: Convert to switch once Code Climate fixes an analysis bug with switches. 146 | if(keyCodeModifiers == CTRL_T){ 147 | openNewTab(); 148 | }else if(keyCodeModifiers == CTRL_SHIFT_T && !closedTabs.isEmpty()){ 149 | int lastTab = closedTabs.size() - 1; 150 | openNewTab(closedTabs.get(lastTab)); 151 | closedTabs.remove(lastTab); 152 | }else if(keyCodeModifiers == CTRL_TAB){ 153 | if (activeTab == tabs.size() - 1) { 154 | setActiveTab(0); 155 | } else { 156 | setActiveTab(activeTab + 1); 157 | } 158 | }else if(keyCodeModifiers == CTRL_SHIFT_TAB){ 159 | if (activeTab == 0) { 160 | setActiveTab(tabs.size() - 1); 161 | } else { 162 | setActiveTab(activeTab - 1); 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/util/button/BrowserTabIcon.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.util.button; 2 | 3 | import com.cinemamod.mcef.MCEFClient; 4 | import io.github.blobanium.mcbrowser.util.BrowserImpl; 5 | 6 | public class BrowserTabIcon extends BrowserImpl { 7 | static final String API_URL = "https://www.google.com/s2/favicons?sz=64&domain_url="; 8 | //TODO maybe replace the apiUrl thing with https://besticon-demo.herokuapp.com/allicons.json?url=URL, 9 | // cause it can help changing size of rendered field to match size of icon 10 | int size = 64; 11 | 12 | public BrowserTabIcon(MCEFClient client, String url, boolean transparent) { 13 | super(client, API_URL + url, transparent); 14 | setSize(size); 15 | } 16 | 17 | public void setSize(int size) { 18 | this.size = size; 19 | this.resize(this.size, this.size); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/util/button/NewTabButton.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.util.button; 2 | 3 | import io.github.blobanium.mcbrowser.screen.BrowserScreen; 4 | import io.github.blobanium.mcbrowser.util.BrowserUtil; 5 | import io.github.blobanium.mcbrowser.util.TabManager; 6 | import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; 7 | import net.minecraft.client.gui.widget.PressableWidget; 8 | import net.minecraft.text.Text; 9 | 10 | public class NewTabButton extends PressableWidget { 11 | final int startX; 12 | 13 | public NewTabButton(int startX, int y, int width, int height, Text text) { 14 | super(0, y, width, height, text); 15 | this.startX = startX; 16 | } 17 | 18 | @Override 19 | public int getX() { 20 | return Math.min(BrowserUtil.instance.width - BrowserScreen.BD_OFFSET - 15, startX + ((BrowserUtil.instance.tabButtons.size()) * 105)); 21 | } 22 | 23 | @Override 24 | public void onPress() { 25 | //Required for Implementation 26 | } 27 | 28 | @Override 29 | protected void appendClickableNarrations(NarrationMessageBuilder builder) { 30 | //Required for Implementation 31 | } 32 | 33 | @Override 34 | public boolean mouseClicked(double mouseX, double mouseY, int button) { 35 | if (this.isSelected()){ 36 | if (button == 2) { 37 | int i = TabManager.activeTab; 38 | TabManager.openNewTab(); 39 | TabManager.setActiveTab(i); 40 | return true; 41 | } 42 | TabManager.openNewTab(); 43 | return true; 44 | } 45 | return false; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/util/button/ReloadButton.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.util.button; 2 | 3 | import com.mojang.blaze3d.systems.RenderSystem; 4 | import io.github.blobanium.mcbrowser.util.BrowserUtil; 5 | import io.github.blobanium.mcbrowser.util.TabManager; 6 | import net.minecraft.client.MinecraftClient; 7 | import net.minecraft.client.gui.DrawContext; 8 | import net.minecraft.client.gui.screen.ButtonTextures; 9 | import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; 10 | import net.minecraft.client.gui.widget.PressableWidget; 11 | import net.minecraft.client.render.RenderLayer; 12 | import net.minecraft.text.Text; 13 | import net.minecraft.util.Identifier; 14 | import net.minecraft.util.math.MathHelper; 15 | 16 | public class ReloadButton extends PressableWidget { 17 | private static final ButtonTextures TEXTURES = new ButtonTextures( 18 | Identifier.ofVanilla("widget/button"), Identifier.ofVanilla("widget/button_disabled"), Identifier.ofVanilla("widget/button_highlighted") 19 | ); 20 | 21 | public ReloadButton(int x, int y, int width, int height) { 22 | super(x, y, height, width, null); 23 | } 24 | 25 | @Override 26 | public void onPress() { 27 | //Required for Implementation 28 | } 29 | 30 | @Override 31 | protected void appendClickableNarrations(NarrationMessageBuilder builder) { 32 | //Required for Implementation 33 | } 34 | 35 | /** 36 | * Renders the widget on the screen. 37 | * 38 | * @param context the draw context 39 | * @param mouseX the X coordinate of the mouse 40 | * @param mouseY the Y coordinate of the mouse 41 | * @param delta the time in ticks between the previous and current render 42 | */ 43 | @Override 44 | protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { 45 | Identifier texture = TEXTURES.get(this.isNarratable(), this.isFocused()); 46 | MinecraftClient minecraftClient = MinecraftClient.getInstance(); 47 | RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); 48 | RenderSystem.enableBlend(); 49 | RenderSystem.enableDepthTest(); 50 | context.drawGuiTexture(RenderLayer.GUI_TEXTURED,texture, this.getX(), this.getY(), this.getWidth(), this.getHeight()); 51 | RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); 52 | drawScrollableText(context, minecraftClient.textRenderer, Text.of(TabManager.getCurrentTab().isLoading() ? "❌" : "⟳"), this.getX() + 2, this.getY(), this.getX() + this.getWidth() - 2, this.getY() + this.getHeight(), 16777215 | MathHelper.ceil(this.alpha * 255.0F) << 24); 53 | } 54 | 55 | @Override 56 | public boolean mouseClicked(double mouseX, double mouseY, int button) { 57 | if (!this.isSelected()) { 58 | return false; 59 | } 60 | 61 | if (button == 2) { 62 | TabManager.copyTab(TabManager.activeTab); 63 | } else { 64 | if (BrowserUtil.instance != null) { 65 | BrowserUtil.instance.urlBox.setText(TabManager.getCurrentTab().getURL()); 66 | } 67 | reloadOrStopLoadPage(); 68 | } 69 | return true; 70 | } 71 | 72 | @Override 73 | public boolean isHovered(){ 74 | setFocused(super.isHovered()); 75 | return super.isHovered(); 76 | } 77 | 78 | public void reloadOrStopLoadPage() { 79 | if (TabManager.getCurrentTab().isLoading()) { 80 | TabManager.getCurrentTab().stopLoad(); 81 | } else { 82 | TabManager.getCurrentTab().reload(); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/util/button/TabButton.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.util.button; 2 | 3 | import com.mojang.blaze3d.systems.RenderSystem; 4 | import io.github.blobanium.mcbrowser.MCBrowser; 5 | import io.github.blobanium.mcbrowser.screen.BrowserScreen; 6 | import io.github.blobanium.mcbrowser.util.BrowserUtil; 7 | import io.github.blobanium.mcbrowser.util.TabManager; 8 | import javax.imageio.ImageIO; 9 | import java.awt.image.BufferedImage; 10 | import java.io.IOException; 11 | import java.net.URISyntaxException; 12 | import java.net.URI; 13 | import java.util.concurrent.CompletableFuture; 14 | 15 | import net.minecraft.client.MinecraftClient; 16 | import net.minecraft.client.font.TextRenderer; 17 | import net.minecraft.client.gui.DrawContext; 18 | import net.minecraft.client.gui.screen.ButtonTextures; 19 | import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; 20 | import net.minecraft.client.gui.widget.PressableWidget; 21 | import net.minecraft.client.render.RenderLayer; 22 | import net.minecraft.text.Text; 23 | import net.minecraft.util.Identifier; 24 | import net.minecraft.util.math.MathHelper; 25 | public class TabButton extends PressableWidget { 26 | private static final ButtonTextures TEXTURES = new ButtonTextures( 27 | Identifier.ofVanilla("widget/button"), Identifier.ofVanilla("widget/button_disabled"), Identifier.ofVanilla("widget/button_highlighted") 28 | ); 29 | int tab; 30 | final int startX; 31 | 32 | public TabButton(int startX, int y, int width, int height, int tab) { 33 | super(0, y, width, height, null); 34 | this.startX = startX; 35 | this.tab = tab; 36 | } 37 | 38 | @Override 39 | public int getX() { 40 | return startX + (tab * (getWidth() + 5)); 41 | } 42 | 43 | public void setTab(int tab) { 44 | this.tab = tab; 45 | } 46 | 47 | 48 | @Override 49 | public void onPress() { 50 | //Required For Implementation 51 | } 52 | 53 | @Override 54 | protected void appendClickableNarrations(NarrationMessageBuilder builder) { 55 | //Required For Implementation 56 | } 57 | 58 | private final boolean selected = TabManager.activeTab == tab; 59 | private final boolean tooSmall = this.getWidth() < this.getHeight() * 3; 60 | 61 | @Override 62 | public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { 63 | Identifier texture = TEXTURES.get(this.isNarratable(), this.isFocused()); 64 | 65 | if (this.getX() > BrowserUtil.instance.width - BrowserScreen.BD_OFFSET - 35) { return; } 66 | RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); 67 | RenderSystem.enableBlend(); 68 | RenderSystem.enableDepthTest(); 69 | if (this.getWidth() > this.getHeight()) { context.drawGuiTexture(RenderLayer.GUI_TEXTURED, texture, this.getX(), this.getY(), this.getWidth(), this.getHeight()); } 70 | RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); 71 | String name = TabManager.tabs.get(tab).getTitle(); 72 | if (name == null || name.isEmpty()) { name = "Loading..."; } 73 | TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; 74 | drawScrollableText(context, textRenderer, Text.of(name), this.getX() + 2 + 15, this.getY(), this.getX() + this.getWidth() - (!tooSmall || selected ? 17 : 2), this.getY() + this.getHeight(), 16777215 | MathHelper.ceil(this.alpha * 255.0F) << 24); 75 | 76 | context.fill(this.getX(), this.getY(), this.getX() + this.getHeight(), this.getY() + this.getHeight(), 0x00FFFFFF); 77 | renderIco(); 78 | if (!tooSmall || selected) { 79 | context.drawGuiTexture(RenderLayer.GUI_TEXTURED, texture, this.getX() + (this.getWidth() - 15), this.getY(), 15, this.getHeight()); 80 | String cross = "❌"; 81 | context.drawText(textRenderer, cross, this.getX() + this.getWidth() - 8 - textRenderer.getWidth(cross) / 2, this.getY() + 4, 0xFFFFFFFF, true); 82 | } 83 | 84 | if (selected) { context.fill(this.getX(), this.getY() + this.getHeight(), this.getX() + this.getWidth(), this.getY() + this.getHeight() + 2, 0xFFFFFFFF); } 85 | } 86 | 87 | public void renderIco() { 88 | BrowserTabIcon ico = TabManager.tabs.get(tab).getIcon(); 89 | if (ico == null) { 90 | initIco(); 91 | return; 92 | } 93 | if (!TabManager.tabs.get(tab).isInit()) { 94 | ico.render(this.getX() + 1, this.getY() + 1, this.getHeight() - 2, this.getHeight() - 2); 95 | return; 96 | } 97 | String browserUrl = TabManager.tabs.get(tab).getBrowser().getURL(); 98 | String icoUrl = ico.getURL(); 99 | if (!icoUrl.isEmpty() && !browserUrl.isEmpty()) { 100 | if(icoUrl.endsWith(browserUrl) || isIcoForUrl(icoUrl, browserUrl)){ 101 | ico.render(this.getX() + 1, this.getY() + 1, this.getHeight() - 2, this.getHeight() - 2); 102 | } else { 103 | resetIco(); 104 | } 105 | } 106 | } 107 | 108 | private boolean isIcoForUrl(String icoUrl, String url) { 109 | int end = icoUrl.length(); 110 | int begin = 0; 111 | if (icoUrl.contains("&size=")) { 112 | end = icoUrl.lastIndexOf("&size="); 113 | } 114 | if (icoUrl.contains("&url=")) { 115 | begin = icoUrl.lastIndexOf("&url=") + 5; 116 | } 117 | icoUrl = icoUrl.substring(begin, end); 118 | if (icoUrl.contains("://")) { 119 | icoUrl = icoUrl.substring(icoUrl.indexOf("://") + 3); 120 | } 121 | 122 | String siteUrl = url; 123 | if (siteUrl.contains("://")) { 124 | siteUrl = siteUrl.substring(siteUrl.indexOf("://") + 3); 125 | } 126 | if (siteUrl.contains("/")) { 127 | siteUrl = siteUrl.substring(0, siteUrl.indexOf('/')); 128 | } 129 | return icoUrl.startsWith(siteUrl); 130 | } 131 | 132 | private void initIco() { 133 | String currentUrl = TabManager.tabs.get(tab).getUrl(); 134 | if (currentUrl.isEmpty()) { 135 | return; 136 | } 137 | TabManager.tabs.get(tab).initIcon(currentUrl); 138 | CompletableFuture.runAsync(() -> { 139 | try { 140 | BufferedImage bufferedImage = ImageIO.read(new URI(BrowserTabIcon.API_URL + currentUrl).toURL()); 141 | TabManager.tabs.get(tab).getIcon().setSize(bufferedImage.getWidth()); 142 | } catch (IOException | URISyntaxException e) { 143 | MCBrowser.LOGGER.warn("Could not find size of ico for {}", currentUrl, e); 144 | } 145 | }); 146 | } 147 | 148 | public void resetIco() { 149 | TabManager.tabs.get(tab).resetIcon(); 150 | } 151 | 152 | @Override 153 | public boolean mouseClicked(double mouseX, double mouseY, int button) { 154 | if ((this.getX() > BrowserUtil.instance.width - BrowserScreen.BD_OFFSET - 35) || !this.isSelected()) { 155 | return false; 156 | } 157 | 158 | if (button == 2) { 159 | close(); 160 | return true; 161 | } 162 | 163 | if (tooSmall && !selected) { 164 | open(); 165 | return true; 166 | } 167 | 168 | if (mouseX <= this.getX() + this.getWidth() - 15) open(); else close(); 169 | return true; 170 | } 171 | 172 | public void open() { 173 | TabManager.setActiveTab(tab); 174 | } 175 | 176 | public void close() { 177 | TabManager.closeTab(tab); 178 | } 179 | 180 | @Override 181 | public boolean isHovered(){ 182 | setFocused(super.isHovered()); 183 | return super.isHovered(); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/main/resources/assets/mcbrowser/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blobanium/MCBrowser/41488cd2844a848b569101c9dc9e7031d4bc5792/src/main/resources/assets/mcbrowser/icon.png -------------------------------------------------------------------------------- /src/main/resources/assets/mcbrowser/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "text.autoconfig.mcbrowser.title": "MCBrowser Config", 3 | "text.autoconfig.mcbrowser.category.default": "General Browser Settings", 4 | "text.autoconfig.mcbrowser.category.performance": "Performance", 5 | "text.autoconfig.mcbrowser.category.privacyandsafety": "Privacy and Safety", 6 | "text.autoconfig.mcbrowser.option.openLinkInBrowser": "Open Links In MCBrowser", 7 | "text.autoconfig.mcbrowser.option.openLinkInBrowser.@Tooltip": "Opens MCBrowser instead of your external browser when you go to open a link within Minecraft.", 8 | "text.autoconfig.mcbrowser.option.homePage": "Default Home Page", 9 | "text.autoconfig.mcbrowser.option.homePage.@Tooltip": "Sets the default homepage", 10 | "text.autoconfig.mcbrowser.option.asyncBrowserInput": "Async Browser Input", 11 | "text.autoconfig.mcbrowser.option.asyncBrowserInput.@Tooltip": "Makes input asynchronous to improve performance. \nTurn this off if you are having input-related issues.", 12 | "text.autoconfig.mcbrowser.option.saveCookies": "Save Cookies", 13 | "text.autoconfig.mcbrowser.option.saveCookies.@Tooltip": "If true, browser cookies will not be deleted and will be stored in \".minecraft/config/MCBrowser/browser\".\nIt means, your accounts logins will be saved on game restart", 14 | "text.autoconfig.mcbrowser.option.saveTabs": "Save Tabs", 15 | "text.autoconfig.mcbrowser.option.saveTabs.@Tooltip": "If true, list of opened tabs will be kept on restart", 16 | "text.autoconfig.mcbrowser.option.enableMediaStream": "Allow usage of media devices", 17 | "text.autoconfig.mcbrowser.option.enableMediaStream.@Tooltip": "If true, MCBrowser will have access to your microphone and webcam", 18 | "text.autoconfig.mcbrowser.option.limitBrowserFramerate": "Limit framerate when browser is open", 19 | "text.autoconfig.mcbrowser.option.limitBrowserFramerate.@Tooltip": "Helps save computational resources in certain scenarios", 20 | "text.autoconfig.mcbrowser.option.browserFPS": "Limit Framerate FPS", 21 | "text.autoconfig.mcbrowser.option.browserFPS.@Tooltip": "Sets the framerate when the browser is open. \nThis only works if Limit framerate is turned on.", 22 | "text.autoconfig.mcbrowser.option.zoomScalingFactor": "Zoom Scale Factor", 23 | "text.autoconfig.mcbrowser.option.zoomScalingFactor.@Tooltip": "Sets how much to zoom in and out", 24 | "text.autoconfig.mcbrowser.option.killJcefHelperOnClose": "Kill JCEF helper processes on shutdown", 25 | "text.autoconfig.mcbrowser.option.killJcefHelperOnClose.@Tooltip": "Temporary workaround to prevent lingering JCEF background processes that eat up CPU resources after minecraft is shutdown \nTurning this option off is strongly not recommended unless you are having issues with other applications that utilize JCEF.", 26 | "text.autoconfig.mcbrowser.option.allowDownloads": "Allow Downloads", 27 | "text.autoconfig.mcbrowser.option.allowDownloads.@Tooltip": "Allows downloading files to your computer using MCBrowser", 28 | "text.autoconfig.mcbrowser.option.customSwitches": "Custom Chromium Switches", 29 | "text.autoconfig.mcbrowser.option.customSwitches.@Tooltip": "Use Custom Chromium Switches with MCBrowser \n \n§cWARNING:§f Do not paste anything here if someone told you to. Doing so may compromise privacy and security and give bad actors access to your computer!" , 30 | "mcbrowser.download.toast.started": "Download Started", 31 | "mcbrowser.download.toast.complete": "Download Completed!", 32 | "mcbrowser.download.toast.failed": "Download Failed", 33 | "mcbrowser.download.toast.disabled": "Downloads are disabled", 34 | "mcbrowser.download.toast.started.description": "Please wait while your file downloads.", 35 | "mcbrowser.download.toast.started.description.mod": "Please wait while your mod downloads.", 36 | "mcbrowser.download.toast.started.description.rp": "Please wait while your resource pack downloads.", 37 | "mcbrowser.download.toast.completed.description": "%s has finished downloading!", 38 | "mcbrowser.download.toast.complete.description.mod": "Restart your client for changes to take effect.", 39 | "mcbrowser.download.toast.complete.description.rp": "Resource Pack can be turned on in Resource Packs Settings", 40 | "mcbrowser.download.toast.failed.description": "Check your logs for more info", 41 | "mcbrowser.download.toast.disabled.description": "\nPlease enable 'Allow downloads' in the mod's settings to start downloading files.", 42 | "mcbrowser.zoom.percent": "Zoom level: %d%%" 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "mcbrowser", 4 | "version": "${version}", 5 | "name": "MCBrowser", 6 | "description": "Internet Browser For Block Game", 7 | "authors": ["Blobanium"], 8 | "contact": { 9 | "sources": "https://github.com/Blobanium/MCBrowser", 10 | "issues": "https://github.com/Blobanium/MCBrowser/issues", 11 | "discord": "https://discord.gg/UTJHBYugUD" 12 | }, 13 | "license": "MIT", 14 | "icon": "assets/mcbrowser/icon.png", 15 | "environment": "client", 16 | "accessWidener": "mcbrowser.accesswidener", 17 | "entrypoints": { 18 | "client": [ 19 | "io.github.blobanium.mcbrowser.MCBrowser" 20 | ], 21 | "modmenu": [ 22 | "io.github.blobanium.mcbrowser.config.ModMenuIntegration" 23 | ] 24 | }, 25 | "mixins": [ 26 | "mcbrowser.mixins.json" 27 | ], 28 | "suggests": { 29 | "modmenu":"*" 30 | }, 31 | "recommends": { 32 | "mcef": ">=2.1.5-1.21" 33 | }, 34 | "depends": { 35 | "fabricloader": ">=${loader_version}", 36 | "minecraft": "1.21.4", 37 | "mcef": ">=2.1.0-1.20.1", 38 | "cloth-config": "17.*.*", 39 | "fabric": "*" 40 | }, 41 | "conflicts": { 42 | "vulkanmod": "*" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/mcbrowser.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v2 named 2 | accessible field net/minecraft/client/render/RenderLayer GUI_TEXTURED Ljava/util/function/Function; -------------------------------------------------------------------------------- /src/main/resources/mcbrowser.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "io.github.blobanium.mcbrowser.mixin", 5 | "compatibilityLevel": "JAVA_21", 6 | "mixins": [ 7 | "CefClientMixin", 8 | "CefUtilMixin", 9 | "UtilOperatingSystemMixin" 10 | ], 11 | "client": [ 12 | "MinecraftClientMixin" 13 | ], 14 | "injectors": { 15 | "defaultRequire": 1 16 | } 17 | } 18 | --------------------------------------------------------------------------------