├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ ├── resources │ ├── assets │ │ └── mcbrowser │ │ │ ├── icon.png │ │ │ └── lang │ │ │ └── en_us.json │ ├── mcbrowser.mixins.json │ └── fabric.mod.json │ └── java │ └── io │ └── github │ └── blobanium │ └── mcbrowser │ ├── util │ ├── BrowserCaches.java │ ├── button │ │ ├── BrowserTabIcon.java │ │ ├── NewTabButton.java │ │ ├── ReloadButton.java │ │ └── TabButton.java │ ├── TabHolder.java │ ├── BrowserImpl.java │ ├── TabManager.java │ └── BrowserUtil.java │ ├── config │ ├── ModMenuIntegration.java │ └── BrowserAutoConfig.java │ ├── feature │ ├── specialbutton │ │ ├── SpecialButtonHelper.java │ │ ├── SpecialButtonActions.java │ │ └── SpecialButtonAction.java │ └── BrowserFeatureUtil.java │ ├── mixin │ ├── UtilOperatingSystemMixin.java │ ├── CefUtilMixin.java │ ├── MinecraftClientMixin.java │ └── CefClientMixin.java │ ├── MCBrowser.java │ └── screen │ └── BrowserScreen.java ├── settings.gradle ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── gradle.properties ├── README.md ├── LICENSE ├── gradlew.bat └── gradlew /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /.gradle/ 3 | /build/ 4 | /.idea/ 5 | /dependencies/ 6 | /run/ 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blobanium/MCBrowser/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/assets/mcbrowser/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blobanium/MCBrowser/HEAD/src/main/resources/assets/mcbrowser/icon.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | gradlePluginPortal() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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.10 7 | yarn_mappings=1.21.10+build.2 8 | loader_version=0.17.3 9 | 10 | # Mod Properties 11 | mod_version = 1.3.2-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.136.0+1.21.10 18 | 19 | mcef_version=2.1.7-1.21.10-fabric 20 | cloth_config_version=20.0.148 21 | mod_menu_version=16.0.0-rc.1 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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/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 | "entrypoints": { 17 | "client": [ 18 | "io.github.blobanium.mcbrowser.MCBrowser" 19 | ], 20 | "modmenu": [ 21 | "io.github.blobanium.mcbrowser.config.ModMenuIntegration" 22 | ] 23 | }, 24 | "mixins": [ 25 | "mcbrowser.mixins.json" 26 | ], 27 | "suggests": { 28 | "modmenu":"*" 29 | }, 30 | "recommends": { 31 | "mcef": ">=2.1.6-1.21.10" 32 | }, 33 | "depends": { 34 | "fabricloader": ">=${loader_version}", 35 | "minecraft": "1.21.10", 36 | "mcef": ">=2.1.6-1.21.10", 37 | "cloth-config": "20.*.*", 38 | "fabric": "*" 39 | }, 40 | "conflicts": { 41 | "vulkanmod": "*" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /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/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/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/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.Click; 7 | import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; 8 | import net.minecraft.client.gui.widget.PressableWidget; 9 | import net.minecraft.client.input.AbstractInput; 10 | import net.minecraft.text.Text; 11 | 12 | public class NewTabButton extends PressableWidget { 13 | final int startX; 14 | 15 | public NewTabButton(int startX, int y, int width, int height, Text text) { 16 | super(0, y, width, height, text); 17 | this.startX = startX; 18 | } 19 | 20 | @Override 21 | public int getX() { 22 | return Math.min(BrowserUtil.instance.width - BrowserScreen.BD_OFFSET - 15, startX + ((BrowserUtil.instance.tabButtons.size()) * 105)); 23 | } 24 | 25 | @Override 26 | public void onPress(AbstractInput input) { 27 | //Required for Implementation 28 | } 29 | 30 | @Override 31 | protected void appendClickableNarrations(NarrationMessageBuilder builder) { 32 | //Required for Implementation 33 | } 34 | 35 | @Override 36 | public boolean mouseClicked(Click click, boolean doubled) { 37 | if (this.isSelected()){ 38 | if (click.button() == 2) { 39 | int i = TabManager.activeTab; 40 | TabManager.openNewTab(); 41 | TabManager.setActiveTab(i); 42 | return true; 43 | } 44 | TabManager.openNewTab(); 45 | return true; 46 | } 47 | return false; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /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/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 | @ConfigEntry.Category("privacyandsafety") 54 | @ConfigEntry.Gui.Tooltip 55 | public boolean openExternalApplications = false; 56 | } 57 | -------------------------------------------------------------------------------- /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 net.minecraft.client.gl.RenderPipelines; 6 | import net.minecraft.client.gui.Click; 7 | import net.minecraft.client.gui.DrawContext; 8 | import net.minecraft.util.Identifier; 9 | import net.minecraft.util.math.ColorHelper; 10 | import org.lwjgl.glfw.GLFW; 11 | 12 | import static io.github.blobanium.mcbrowser.screen.BrowserScreen.BD_OFFSET; 13 | 14 | public class BrowserImpl extends MCEFBrowser { 15 | 16 | public BrowserImpl(MCEFClient client, String url, boolean transparent) { 17 | super(client, url, transparent); 18 | } 19 | 20 | //Improves performance by limiting each getURL call to one tick for each instance. 21 | @Override 22 | public String getURL() { 23 | return BrowserCaches.urlCache.getOrDefault(this.getIdentifier(), super.getURL()); 24 | } 25 | 26 | @Override 27 | public boolean isLoading() { 28 | return BrowserCaches.isLoadingCache.getOrDefault(this.getIdentifier(), super.isLoading()); 29 | } 30 | 31 | public void render(DrawContext context, int x, int y, int width, int height) { 32 | Identifier textureLocation = getTextureLocation(); 33 | 34 | if (isTextureReady()) { 35 | context.drawTexture(RenderPipelines.GUI_TEXTURED, textureLocation, x, y, 0F, 0F, width, height, width, height, ColorHelper.getWhite(1F)); 36 | } 37 | } 38 | 39 | public void sendKeyPressRelease(int keyCode, int scanCode, int modifiers, boolean isPress) { 40 | if (isPress) { 41 | sendKeyPress(keyCode, scanCode, modifiers); 42 | } else { 43 | sendKeyRelease(keyCode, scanCode, modifiers); 44 | } 45 | } 46 | 47 | public void mouseButtonControl(Click click, boolean isDouble, boolean isClick) { 48 | if (click.button() == GLFW.GLFW_MOUSE_BUTTON_4 && canGoBack() && !isClick) { 49 | goBack(); 50 | } else if (click.button() == GLFW.GLFW_MOUSE_BUTTON_5 && canGoForward() && !isClick) { 51 | goForward(); 52 | } else { 53 | BrowserUtil.runAsyncIfEnabled(() -> { 54 | if (isClick) { 55 | sendMousePress(BrowserUtil.mouseX(click.x(), BD_OFFSET), BrowserUtil.mouseY(click.y(), BD_OFFSET), click.button()); 56 | } else { 57 | sendMouseRelease(BrowserUtil.mouseX(click.x(), BD_OFFSET), BrowserUtil.mouseY(click.y(), BD_OFFSET), click.button()); 58 | } 59 | }); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /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 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/util/button/ReloadButton.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.util.button; 2 | 3 | import io.github.blobanium.mcbrowser.util.BrowserUtil; 4 | import io.github.blobanium.mcbrowser.util.TabManager; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.client.gl.RenderPipelines; 7 | import net.minecraft.client.gui.Click; 8 | import net.minecraft.client.gui.DrawContext; 9 | import net.minecraft.client.gui.screen.ButtonTextures; 10 | import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; 11 | import net.minecraft.client.gui.widget.PressableWidget; 12 | import net.minecraft.client.input.AbstractInput; 13 | import net.minecraft.text.Text; 14 | import net.minecraft.util.Identifier; 15 | import net.minecraft.util.math.MathHelper; 16 | 17 | public class ReloadButton extends PressableWidget { 18 | private static final ButtonTextures TEXTURES = new ButtonTextures( 19 | Identifier.ofVanilla("widget/button"), Identifier.ofVanilla("widget/button_disabled"), Identifier.ofVanilla("widget/button_highlighted") 20 | ); 21 | 22 | public ReloadButton(int x, int y, int width, int height) { 23 | super(x, y, height, width, null); 24 | } 25 | 26 | @Override 27 | public void onPress(AbstractInput input) { 28 | //Required for Implementation 29 | } 30 | 31 | @Override 32 | protected void appendClickableNarrations(NarrationMessageBuilder builder) { 33 | //Required for Implementation 34 | } 35 | 36 | /** 37 | * Renders the widget on the screen. 38 | * 39 | * @param context the draw context 40 | * @param mouseX the X coordinate of the mouse 41 | * @param mouseY the Y coordinate of the mouse 42 | * @param delta the time in ticks between the previous and current render 43 | */ 44 | @Override 45 | protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { 46 | Identifier texture = TEXTURES.get(this.isInteractable(), this.isFocused()); 47 | MinecraftClient minecraftClient = MinecraftClient.getInstance(); 48 | context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, texture, this.getX(), this.getY(), this.getWidth(), this.getHeight()); 49 | 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); 50 | } 51 | 52 | @Override 53 | public boolean mouseClicked(Click click, boolean doubled) { 54 | if (!this.isSelected()) { 55 | return false; 56 | } 57 | 58 | if (click.button() == 2) { 59 | TabManager.copyTab(TabManager.activeTab); 60 | } else { 61 | if (BrowserUtil.instance != null) { 62 | BrowserUtil.instance.urlBox.setText(TabManager.getCurrentTab().getURL()); 63 | } 64 | reloadOrStopLoadPage(); 65 | } 66 | return true; 67 | } 68 | 69 | @Override 70 | public boolean isHovered() { 71 | setFocused(super.isHovered()); 72 | return super.isHovered(); 73 | } 74 | 75 | public void reloadOrStopLoadPage() { 76 | if (TabManager.getCurrentTab().isLoading()) { 77 | TabManager.getCurrentTab().stopLoad(); 78 | } else { 79 | TabManager.getCurrentTab().reload(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /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/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/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.cef.network.CefRequest; 17 | import org.spongepowered.asm.mixin.Mixin; 18 | import org.spongepowered.asm.mixin.Pseudo; 19 | import org.spongepowered.asm.mixin.injection.At; 20 | import org.spongepowered.asm.mixin.injection.Inject; 21 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 22 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 23 | 24 | import java.net.URI; 25 | import java.net.URISyntaxException; 26 | 27 | 28 | @Pseudo 29 | @Mixin(CefClient.class) 30 | public class CefClientMixin { 31 | @Inject(at = @At("HEAD"), method = "onAddressChange", remap = false) 32 | public void onAddressChange(CefBrowser browser, CefFrame frame, String url, CallbackInfo ci) { 33 | if (url != null && !(browser instanceof BrowserTabIcon) && (browser instanceof BrowserImpl)) { 34 | BrowserUtil.onUrlChange(); 35 | } 36 | BrowserCaches.urlCache.put(browser.getIdentifier(), url); 37 | } 38 | 39 | @Inject(at = @At("HEAD"), method = "onTooltip", remap = false) 40 | public void onTooltip(CefBrowser browser, String text, CallbackInfoReturnable cir) { 41 | BrowserUtil.tooltipText = text; 42 | } 43 | 44 | @Inject(at = @At("HEAD"), method = "onLoadingStateChange", remap = false) 45 | public void onLoadingStateChange(CefBrowser browser, boolean isLoading, boolean canGoBack, boolean canGoForward, CallbackInfo ci) { 46 | BrowserUtil.instance.updateWidgets(); 47 | BrowserCaches.isLoadingCache.put(browser.getIdentifier(), isLoading); 48 | } 49 | 50 | @Inject(at = @At("HEAD"), method = "onTitleChange", remap = false) 51 | public void onTitleChange(CefBrowser browser, String title, CallbackInfo ci) { 52 | TabManager.setTitleForTab(browser.getIdentifier(), title); 53 | } 54 | 55 | @Inject(at = @At("HEAD"), method = "onBeforeDownload", remap = false) 56 | public void onBeforeDownload(CefBrowser browser, CefDownloadItem downloadItem, String suggestedName, CefBeforeDownloadCallback callback, CallbackInfo ci){ 57 | if(MCBrowser.getConfig().allowDownloads){ 58 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.download.toast.started"), Text.translatable("mcbrowser.download.toast.started.description")); 59 | callback.Continue(suggestedName, true); 60 | }else{ 61 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.download.toast.disabled"), Text.translatable("mcbrowser.download.toast.disabled.description")); 62 | } 63 | } 64 | 65 | @Inject(at = @At("HEAD"), method = "onDownloadUpdated", remap = false) 66 | public void onDownloadUpdated(CefBrowser browser, CefDownloadItem downloadItem, CefDownloadItemCallback callback, CallbackInfo ci){ 67 | if(MCBrowser.isShuttingDown){ 68 | callback.cancel(); 69 | } 70 | System.out.println("Downloading " + downloadItem.getSuggestedFileName() + " (" + downloadItem.getPercentComplete() + "% Complete (" + downloadItem.getCurrentSpeed() + " bytes/s))"); 71 | if(downloadItem.isComplete()){ 72 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.download.toast.complete"), Text.translatable("mcbrowser.download.toast.completed.description", downloadItem.getSuggestedFileName())); 73 | } 74 | } 75 | 76 | @Inject(at = @At("HEAD"), method = "onBeforeBrowse", remap = false) 77 | public void onLoadEnd(CefBrowser browser, CefFrame frame, CefRequest request, boolean user_gesture, boolean is_redirect, CallbackInfoReturnable cir){ 78 | if(!request.getURL().startsWith("http")){ 79 | if(MCBrowser.getConfig().openExternalApplications) { 80 | try { 81 | MCBrowser.LOGGER.info("attempting to launch application with request URL: " + request.getURL()); 82 | BrowserUtil.openExternally(new URI(request.getURL())); 83 | } catch (URISyntaxException e) { 84 | throw new RuntimeException(e); 85 | } 86 | }else{ 87 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.toast.externalApplicationsDisabled"), Text.translatable("mcbrowser.toast.externalApplicationsDisabled.description")); 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /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 | "text.autoconfig.mcbrowser.option.openExternalApplications": "Allow MCBrowser to launch programs from websites", 31 | "text.autoconfig.mcbrowser.option.openExternalApplications.@Tooltip": "Allows to open external applications such as Roblox or Zoom \n \n§cWARNING:§f Leaving this setting on may present a potential security risk. Only turn this setting on if you need to run said application." , 32 | "mcbrowser.download.toast.started": "Download Started", 33 | "mcbrowser.download.toast.complete": "Download Completed!", 34 | "mcbrowser.download.toast.failed": "Download Failed", 35 | "mcbrowser.download.toast.disabled": "Downloads are disabled", 36 | "mcbrowser.download.toast.started.description": "Please wait while your file downloads.", 37 | "mcbrowser.download.toast.started.description.mod": "Please wait while your mod downloads.", 38 | "mcbrowser.download.toast.started.description.rp": "Please wait while your resource pack downloads.", 39 | "mcbrowser.download.toast.completed.description": "%s has finished downloading!", 40 | "mcbrowser.download.toast.complete.description.mod": "Restart your client for changes to take effect.", 41 | "mcbrowser.download.toast.complete.description.rp": "Resource Pack can be turned on in Resource Packs Settings", 42 | "mcbrowser.download.toast.failed.description": "Check your logs for more info", 43 | "mcbrowser.download.toast.disabled.description": "\nPlease enable 'Allow downloads' in the mod's settings to start downloading files.", 44 | "mcbrowser.toast.externalApplicationsDisabled": "Opening programs from websites is disabled.", 45 | "mcbrowser.toast.externalApplicationsDisabled.description": "To enable, go to the MCBrowser Config.", 46 | "mcbrowser.zoom.percent": "Zoom level: %d%%" 47 | } 48 | -------------------------------------------------------------------------------- /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.TabManager; 8 | import net.fabricmc.loader.api.FabricLoader; 9 | import net.minecraft.MinecraftVersion; 10 | import net.minecraft.text.MutableText; 11 | import net.minecraft.text.Text; 12 | import org.apache.commons.io.FileUtils; 13 | 14 | import java.io.File; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.net.*; 18 | import java.util.concurrent.CompletableFuture; 19 | 20 | public class SpecialButtonAction { 21 | public static final byte START_DL_DESCRIPTION = 0x00; 22 | public static final byte END_DL_DESCRIPTION = 0x01; 23 | 24 | //These two methods exist for compatability purposes. May change later when i learn more about enum values. 25 | public static void downloadModrinthMod() { 26 | downloadModrinth(SpecialButtonActions.MODRINTH_MOD); 27 | } 28 | 29 | public static void downloadModrinthRP() { 30 | downloadModrinth(SpecialButtonActions.MODRINTH_RP); 31 | } 32 | 33 | private static void downloadModrinth(SpecialButtonActions action) { 34 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.download.toast.started"), SpecialButtonActionSwitches.getTranslation(START_DL_DESCRIPTION, action)); 35 | 36 | CompletableFuture.runAsync(() -> { 37 | try { 38 | //Get the file from modrinth's API 39 | URL url = SpecialButtonActionSwitches.getTargetURL(action); 40 | HttpURLConnection http = (HttpURLConnection) url.openConnection(); 41 | http.setRequestMethod("GET"); 42 | http.connect(); 43 | InputStream stream = http.getInputStream(); 44 | String json = new String(stream.readAllBytes()); 45 | http.disconnect(); 46 | 47 | //Analyze JSON Result from Modrinth API 48 | Gson gson = new Gson(); 49 | JsonArray array = gson.fromJson(json, JsonArray.class); 50 | JsonObject object = array.get(0).getAsJsonObject(); 51 | JsonArray filesArray = object.get("files").getAsJsonArray(); 52 | JsonObject file = filesArray.get(0).getAsJsonObject(); 53 | 54 | //get file 55 | URL downloadURL = new URI(file.get("url").getAsString()).toURL(); 56 | FileUtils.copyURLToFile(downloadURL, new File(FabricLoader.getInstance().getGameDir().toFile(), SpecialButtonActionSwitches.getTargetDirectory(action) + cleanseFileUrl(downloadURL.getFile()))); 57 | 58 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.download.toast.complete"), SpecialButtonActionSwitches.getTranslation(END_DL_DESCRIPTION, action)); 59 | } catch (IOException | URISyntaxException e) { 60 | MCBrowser.sendToastMessage(Text.translatable("mcbrowser.download.toast.failed"), Text.translatable("mcbrowser.download.toast.failed.description")); 61 | MCBrowser.LOGGER.error("Failed to download file", e); 62 | } 63 | }); 64 | } 65 | 66 | 67 | private static String cleanseFileUrl(String url) { 68 | String[] array = url.split("/"); 69 | return array[array.length - 1].replace("%2B", "+"); 70 | } 71 | 72 | public static String getModrinthSlugFromUrl(String url) { 73 | String string = url.replace("https://modrinth.com/", ""); 74 | string = string.substring(string.indexOf("/") + 1); 75 | return string.split("/")[0]; 76 | } 77 | 78 | public static class SpecialButtonActionSwitches{ 79 | 80 | public static MutableText getTranslation(byte type, SpecialButtonActions action) { 81 | return switch (action) { 82 | case MODRINTH_MOD -> switch (type) { 83 | case START_DL_DESCRIPTION -> Text.translatable("mcbrowser.download.toast.started.description.mod"); 84 | case END_DL_DESCRIPTION -> Text.translatable("mcbrowser.download.toast.complete.description.mod"); 85 | default -> throw new IllegalStateException("Unexpected type value: " + type); 86 | }; 87 | case MODRINTH_RP -> switch (type) { 88 | case START_DL_DESCRIPTION -> Text.translatable("mcbrowser.download.toast.started.description.rp"); 89 | case END_DL_DESCRIPTION -> Text.translatable("mcbrowser.download.toast.complete.description.rp"); 90 | default -> throw new IllegalStateException("Unexpected type value: " + type); 91 | }; 92 | 93 | //Reserved for future usage. 94 | //noinspection UnnecessaryDefault 95 | default -> throw new IllegalStateException("Unexpected action value: " + action); 96 | }; 97 | } 98 | 99 | public static URL getTargetURL(SpecialButtonActions action) throws MalformedURLException, URISyntaxException { 100 | return switch (action) { 101 | case MODRINTH_MOD -> new URI("https://api.modrinth.com/v2/project/" + getModrinthSlugFromUrl(TabManager.getCurrentUrl()) + "/version?game_versions=[%22" + MinecraftVersion.create().name() + "%22]&loaders=[%22fabric%22]").toURL(); 102 | case MODRINTH_RP -> new URI("https://api.modrinth.com/v2/project/" + getModrinthSlugFromUrl(TabManager.getCurrentUrl()) + "/version?game_versions=[%22" + MinecraftVersion.create().name() + "%22]").toURL(); 103 | 104 | //Reserved for future usage. 105 | //noinspection UnnecessaryDefault 106 | default -> throw new IllegalStateException("Unexpected action value: " + action); 107 | }; 108 | } 109 | 110 | public static String getTargetDirectory(SpecialButtonActions action) { 111 | return switch (action) { 112 | case MODRINTH_MOD -> "mods/"; 113 | case MODRINTH_RP -> "resourcepacks/"; 114 | }; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /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(tab.getBrowser() != null && 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 | if(keyCodeModifiers == CTRL_T){ 146 | openNewTab(); 147 | }else if(keyCodeModifiers == CTRL_SHIFT_T && !closedTabs.isEmpty()){ 148 | int lastTab = closedTabs.size() - 1; 149 | openNewTab(closedTabs.get(lastTab)); 150 | closedTabs.remove(lastTab); 151 | }else if(keyCodeModifiers == CTRL_TAB){ 152 | if (activeTab == tabs.size() - 1) { 153 | setActiveTab(0); 154 | } else { 155 | setActiveTab(activeTab + 1); 156 | } 157 | }else if(keyCodeModifiers == CTRL_SHIFT_TAB){ 158 | if (activeTab == 0) { 159 | setActiveTab(tabs.size() - 1); 160 | } else { 161 | setActiveTab(activeTab - 1); 162 | } 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/io/github/blobanium/mcbrowser/util/button/TabButton.java: -------------------------------------------------------------------------------- 1 | package io.github.blobanium.mcbrowser.util.button; 2 | 3 | import io.github.blobanium.mcbrowser.MCBrowser; 4 | import io.github.blobanium.mcbrowser.screen.BrowserScreen; 5 | import io.github.blobanium.mcbrowser.util.BrowserUtil; 6 | import io.github.blobanium.mcbrowser.util.TabManager; 7 | import javax.imageio.ImageIO; 8 | import java.awt.image.BufferedImage; 9 | import java.io.IOException; 10 | import java.net.URISyntaxException; 11 | import java.net.URI; 12 | import java.util.concurrent.CompletableFuture; 13 | 14 | import net.minecraft.client.MinecraftClient; 15 | import net.minecraft.client.font.TextRenderer; 16 | import net.minecraft.client.gl.RenderPipelines; 17 | import net.minecraft.client.gui.Click; 18 | import net.minecraft.client.gui.DrawContext; 19 | import net.minecraft.client.gui.screen.ButtonTextures; 20 | import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; 21 | import net.minecraft.client.gui.widget.PressableWidget; 22 | import net.minecraft.client.input.AbstractInput; 23 | import net.minecraft.text.Text; 24 | import net.minecraft.util.Identifier; 25 | import net.minecraft.util.math.MathHelper; 26 | 27 | public class TabButton extends PressableWidget { 28 | private static final ButtonTextures TEXTURES = new ButtonTextures( 29 | Identifier.ofVanilla("widget/button"), Identifier.ofVanilla("widget/button_disabled"), Identifier.ofVanilla("widget/button_highlighted") 30 | ); 31 | int tab; 32 | final int startX; 33 | 34 | public TabButton(int startX, int y, int width, int height, int tab) { 35 | super(0, y, width, height, null); 36 | this.startX = startX; 37 | this.tab = tab; 38 | } 39 | 40 | @Override 41 | public int getX() { 42 | return startX + (tab * (getWidth() + 5)); 43 | } 44 | 45 | public void setTab(int tab) { 46 | this.tab = tab; 47 | } 48 | 49 | 50 | @Override 51 | public void onPress(AbstractInput input) { 52 | //Required For Implementation 53 | } 54 | 55 | @Override 56 | protected void appendClickableNarrations(NarrationMessageBuilder builder) { 57 | //Required For Implementation 58 | } 59 | 60 | private final boolean selected = TabManager.activeTab == tab; 61 | private final boolean tooSmall = this.getWidth() < this.getHeight() * 3; 62 | 63 | @Override 64 | public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { 65 | Identifier texture = TEXTURES.get(this.isInteractable(), this.isFocused()); 66 | 67 | if (this.getX() > BrowserUtil.instance.width - BrowserScreen.BD_OFFSET - 35) { return; } 68 | if (this.getWidth() > this.getHeight()) { context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, texture, this.getX(), this.getY(), this.getWidth(), this.getHeight()); } 69 | String name = TabManager.tabs.get(tab).getTitle(); 70 | if (name == null || name.isEmpty()) { name = "Loading..."; } 71 | TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; 72 | 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); 73 | 74 | context.fill(this.getX(), this.getY(), this.getX() + this.getHeight(), this.getY() + this.getHeight(), 0x00FFFFFF); 75 | renderIco(context); 76 | if (!tooSmall || selected) { 77 | context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, texture, this.getX() + (this.getWidth() - 15), this.getY(), 15, this.getHeight()); 78 | String cross = "❌"; 79 | context.drawText(textRenderer, cross, this.getX() + this.getWidth() - 8 - textRenderer.getWidth(cross) / 2, this.getY() + 4, 0xFFFFFFFF, true); 80 | } 81 | 82 | if (selected) { context.fill(this.getX(), this.getY() + this.getHeight(), this.getX() + this.getWidth(), this.getY() + this.getHeight() + 2, 0xFFFFFFFF); } 83 | } 84 | 85 | public void renderIco(DrawContext context) { 86 | BrowserTabIcon ico = TabManager.tabs.get(tab).getIcon(); 87 | if (ico == null) { 88 | initIco(); 89 | return; 90 | } 91 | if (!TabManager.tabs.get(tab).isInit()) { 92 | ico.render(context, this.getX() + 1, this.getY() + 1, this.getHeight() - 2, this.getHeight() - 2); 93 | return; 94 | } 95 | String browserUrl = TabManager.tabs.get(tab).getBrowser().getURL(); 96 | String icoUrl = ico.getURL(); 97 | if (!icoUrl.isEmpty() && !browserUrl.isEmpty()) { 98 | if(icoUrl.endsWith(browserUrl) || isIcoForUrl(icoUrl, browserUrl)){ 99 | ico.render(context, this.getX() + 1, this.getY() + 1, this.getHeight() - 2, this.getHeight() - 2); 100 | } else { 101 | resetIco(); 102 | } 103 | } 104 | } 105 | 106 | private boolean isIcoForUrl(String icoUrl, String url) { 107 | int end = icoUrl.length(); 108 | int begin = 0; 109 | if (icoUrl.contains("&size=")) { 110 | end = icoUrl.lastIndexOf("&size="); 111 | } 112 | if (icoUrl.contains("&url=")) { 113 | begin = icoUrl.lastIndexOf("&url=") + 5; 114 | } 115 | icoUrl = icoUrl.substring(begin, end); 116 | if (icoUrl.contains("://")) { 117 | icoUrl = icoUrl.substring(icoUrl.indexOf("://") + 3); 118 | } 119 | 120 | String siteUrl = url; 121 | if (siteUrl.contains("://")) { 122 | siteUrl = siteUrl.substring(siteUrl.indexOf("://") + 3); 123 | } 124 | if (siteUrl.contains("/")) { 125 | siteUrl = siteUrl.substring(0, siteUrl.indexOf('/')); 126 | } 127 | return icoUrl.startsWith(siteUrl); 128 | } 129 | 130 | private void initIco() { 131 | String currentUrl = TabManager.tabs.get(tab).getUrl(); 132 | if (currentUrl.isEmpty()) { 133 | return; 134 | } 135 | TabManager.tabs.get(tab).initIcon(currentUrl); 136 | CompletableFuture.runAsync(() -> { 137 | try { 138 | BufferedImage bufferedImage = ImageIO.read(new URI(BrowserTabIcon.API_URL + currentUrl).toURL()); 139 | TabManager.tabs.get(tab).getIcon().setSize(bufferedImage.getWidth()); 140 | } catch (IOException | URISyntaxException e) { 141 | MCBrowser.LOGGER.warn("Could not find size of ico for {}", currentUrl, e); 142 | } 143 | }); 144 | } 145 | 146 | public void resetIco() { 147 | TabManager.tabs.get(tab).resetIcon(); 148 | } 149 | 150 | @Override 151 | public boolean mouseClicked(Click click, boolean doubled) { 152 | if ((this.getX() > BrowserUtil.instance.width - BrowserScreen.BD_OFFSET - 35) || !this.isSelected()) { 153 | return false; 154 | } 155 | 156 | if (click.button() == 2) { 157 | close(); 158 | return true; 159 | } 160 | 161 | if (tooSmall && !selected) { 162 | open(); 163 | return true; 164 | } 165 | 166 | if (click.x() <= this.getX() + this.getWidth() - 15) open(); else close(); 167 | return true; 168 | } 169 | 170 | public void open() { 171 | TabManager.setActiveTab(tab); 172 | } 173 | 174 | public void close() { 175 | TabManager.closeTab(tab); 176 | } 177 | 178 | @Override 179 | public boolean isHovered(){ 180 | setFocused(super.isHovered()); 181 | return super.isHovered(); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /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.client.input.KeyInput; 16 | import net.minecraft.text.MutableText; 17 | import net.minecraft.text.Text; 18 | import net.minecraft.util.Util; 19 | import org.lwjgl.glfw.GLFW; 20 | 21 | public class BrowserUtil { 22 | //Mouse position 23 | public static double lastMouseX; 24 | public static double lastMouseY; 25 | 26 | public static BrowserScreen instance; 27 | 28 | public static String tooltipText; 29 | 30 | public static boolean openInExternalBrowser = false; 31 | 32 | //Navigation initialization methods 33 | public static ButtonWidget initButton(Text message, ButtonWidget.PressAction onPress, int positionX, int buttonLevel) { 34 | return ButtonWidget.builder(message, onPress) 35 | .dimensions(positionX, BrowserScreen.BD_OFFSET - (20 * buttonLevel), 15, 15) 36 | .build(); 37 | } 38 | 39 | //Matrix related commands 40 | 41 | public static int mouseX(double x, int offset) { 42 | lastMouseX = x; 43 | return (int) ((x - offset) * MinecraftClient.getInstance().getWindow().getScaleFactor()); 44 | } 45 | 46 | public static int mouseY(double y, int offset) { 47 | lastMouseY = y; 48 | return (int) ((y - offset) * MinecraftClient.getInstance().getWindow().getScaleFactor()); 49 | } 50 | 51 | public static void updateMouseLocation(double mouseX, double mouseY) { 52 | lastMouseX = mouseX; 53 | lastMouseY = mouseY; 54 | } 55 | 56 | public static int scaleX(double x, int offset) { 57 | return (int) ((x - offset * 2) * MinecraftClient.getInstance().getWindow().getScaleFactor()); 58 | } 59 | 60 | public static int scaleY(double y, int offset) { 61 | return (int) ((y - offset * 2) * MinecraftClient.getInstance().getWindow().getScaleFactor()); 62 | } 63 | 64 | public static int getUrlBoxWidth(int width, int offset) { 65 | return width - (offset * 2) - 80; 66 | } 67 | 68 | //Browser Creation 69 | public static BrowserImpl createBrowser(String url) { 70 | if (MCEF.isInitialized()) { 71 | BrowserImpl browser = new BrowserImpl(MCEF.getClient(), url, false); 72 | browser.setCloseAllowed(); 73 | browser.createImmediately(); 74 | return browser; 75 | } else { 76 | throw new IllegalStateException("Chromium Embedded Framework was never initialized."); 77 | } 78 | } 79 | 80 | public static BrowserTabIcon createIcon(String url) { 81 | if (MCEF.isInitialized()) { 82 | BrowserTabIcon icon = new BrowserTabIcon(MCEF.getClient(), url, false); 83 | icon.setCloseAllowed(); 84 | icon.createImmediately(); 85 | return icon; 86 | } else { 87 | throw new IllegalStateException("Chromium Embedded Framework was never initialized."); 88 | } 89 | } 90 | 91 | public static TextFieldWidget initUrlBox(int offset, int width) { 92 | TextFieldWidget urlBox = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, offset + 80, offset - 20, BrowserUtil.getUrlBoxWidth(width, offset), 15, Text.of("")) { 93 | @Override 94 | public boolean keyPressed(KeyInput input) { 95 | if (isFocused()) { 96 | for (TabHolder tab : TabManager.tabs) { 97 | BrowserImpl browser = tab.getBrowser(); 98 | if (browser != null) browser.setFocus(false); 99 | } 100 | if (input.getKeycode() == GLFW.GLFW_KEY_ENTER) { 101 | TabManager.getCurrentTab().loadURL(BrowserFeatureUtil.prediffyURL(getText())); 102 | setFocused(false); 103 | } 104 | } 105 | return super.keyPressed(input); 106 | } 107 | 108 | }; 109 | urlBox.setMaxLength(2048); //Most browsers have a max length of 2048 110 | return urlBox; 111 | } 112 | 113 | //Button related Methods 114 | public static void openInBrowser(){ 115 | try { 116 | openExternally(new URI(TabManager.getCurrentUrl())); 117 | } catch (URISyntaxException e) { 118 | MCBrowser.LOGGER.fatal("Unable to open Browser", e); 119 | } 120 | } 121 | 122 | public static void openExternally(URI uri){ 123 | openInExternalBrowser = true; 124 | Util.getOperatingSystem().open(uri); 125 | } 126 | 127 | public static void homeButtonAction(){ 128 | String prediffyedHomePage = BrowserFeatureUtil.prediffyURL(MCBrowser.getConfig().homePage); 129 | instance.urlBox.setText(prediffyedHomePage); 130 | TabManager.getCurrentTab().loadURL(prediffyedHomePage); 131 | } 132 | 133 | //Event Methods 134 | public static void onUrlChange() { 135 | if (instance.urlBox.isFocused()) { 136 | instance.urlBox.setFocused(false); 137 | } 138 | instance.urlBox.setText(TabManager.getCurrentUrl()); 139 | instance.urlBox.setCursorToStart(false); 140 | SpecialButtonActions action = SpecialButtonActions.getFromUrlConstantValue(TabManager.getCurrentUrl()); 141 | if (action != null) { 142 | instance.specialButton.setMessage(action.getButtonText()); 143 | } 144 | } 145 | 146 | public static boolean ctrlKeyPressedSwitch(int keyCode, int modifiers){ 147 | switch (keyCode) { 148 | case GLFW.GLFW_KEY_TAB: 149 | case GLFW.GLFW_KEY_T: 150 | // Tab Functions 151 | TabManager.tabControl(keyCode + modifiers); 152 | instance.setFocus(); 153 | return true; 154 | case GLFW.GLFW_KEY_EQUAL: 155 | instance.zoomControl(ZoomActions.INCREASE); 156 | return true; 157 | case GLFW.GLFW_KEY_MINUS: 158 | instance.zoomControl(ZoomActions.DECREASE); 159 | return true; 160 | case GLFW.GLFW_KEY_0: 161 | instance.zoomControl(ZoomActions.RESET); 162 | return true; 163 | } 164 | return false; 165 | } 166 | 167 | public static class Keybinds{ 168 | public static final int CTRL_T = GLFW.GLFW_MOD_CONTROL + GLFW.GLFW_KEY_T; 169 | public static final int CTRL_SHIFT_T = GLFW.GLFW_MOD_CONTROL + GLFW.GLFW_MOD_SHIFT + GLFW.GLFW_KEY_T; 170 | public static final int CTRL_TAB = GLFW.GLFW_MOD_CONTROL + GLFW.GLFW_KEY_TAB; 171 | public static final int CTRL_SHIFT_TAB = GLFW.GLFW_MOD_CONTROL + GLFW.GLFW_MOD_SHIFT + GLFW.GLFW_KEY_TAB; 172 | } 173 | 174 | public static void runAsyncIfEnabled(Runnable runnable){ 175 | if (MCBrowser.getConfig().asyncBrowserInput) { 176 | CompletableFuture.runAsync(runnable); 177 | } else { 178 | runnable.run(); 179 | } 180 | } 181 | 182 | public static int zoomLevelToZoomPercentage(double zoomLevel) { 183 | return (int) Math.round(Math.pow(1.2, zoomLevel) * 100); 184 | } 185 | 186 | public static MutableText getZoomLevelText(double zoomLevel){ 187 | return Text.translatable("mcbrowser.zoom.percent", zoomLevelToZoomPercentage(zoomLevel)); 188 | } 189 | 190 | public static class ZoomActions{ 191 | public static final byte INCREASE = 0x00; 192 | public static final byte DECREASE = 0x01; 193 | public static final byte RESET = 0x02; 194 | 195 | public static long lastTimeCalled = 0; 196 | 197 | public static void resetLastTimeCalled(){ 198 | lastTimeCalled = System.currentTimeMillis(); 199 | } 200 | 201 | public static boolean shouldRenderZoomElements(){ 202 | return System.currentTimeMillis() <= lastTimeCalled + 2500; 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 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\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | 118 | 119 | # Determine the Java command to use to start the JVM. 120 | if [ -n "$JAVA_HOME" ] ; then 121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 122 | # IBM's JDK on AIX uses strange locations for the executables 123 | JAVACMD=$JAVA_HOME/jre/sh/java 124 | else 125 | JAVACMD=$JAVA_HOME/bin/java 126 | fi 127 | if [ ! -x "$JAVACMD" ] ; then 128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 129 | 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | if ! command -v java >/dev/null 2>&1 136 | then 137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 138 | 139 | Please set the JAVA_HOME variable in your environment to match the 140 | location of your Java installation." 141 | fi 142 | fi 143 | 144 | # Increase the maximum file descriptors if we can. 145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 146 | case $MAX_FD in #( 147 | max*) 148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 149 | # shellcheck disable=SC2039,SC3045 150 | MAX_FD=$( ulimit -H -n ) || 151 | warn "Could not query maximum file descriptor limit" 152 | esac 153 | case $MAX_FD in #( 154 | '' | soft) :;; #( 155 | *) 156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 157 | # shellcheck disable=SC2039,SC3045 158 | ulimit -n "$MAX_FD" || 159 | warn "Could not set maximum file descriptor limit to $MAX_FD" 160 | esac 161 | fi 162 | 163 | # Collect all arguments for the java command, stacking in reverse order: 164 | # * args from the command line 165 | # * the main class name 166 | # * -classpath 167 | # * -D...appname settings 168 | # * --module-path (only if needed) 169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 170 | 171 | # For Cygwin or MSYS, switch paths to Windows format before running java 172 | if "$cygwin" || "$msys" ; then 173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /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.*; 13 | import net.minecraft.client.gui.screen.Screen; 14 | import net.minecraft.client.gui.widget.*; 15 | import net.minecraft.client.input.CharInput; 16 | import net.minecraft.client.input.KeyInput; 17 | import net.minecraft.text.Text; 18 | import org.lwjgl.glfw.GLFW; 19 | 20 | public class BrowserScreen extends Screen { 21 | public static final int BD_OFFSET = 50; 22 | //Previously BROWSER_DRAW_OFFSET 23 | 24 | //Ui 25 | public TextFieldWidget urlBox; 26 | public ButtonWidget forwardButton; 27 | public ButtonWidget backButton; 28 | public ReloadButton reloadButton; 29 | private PressableWidget[] navigationButtons; 30 | private ClickableWidget[] uiElements; 31 | private ClickableWidget[] zoomElements; 32 | public ArrayList tabButtons = new ArrayList<>(); 33 | private NewTabButton newTabButton = null; 34 | 35 | public ButtonWidget specialButton; 36 | 37 | private ButtonWidget openInBrowserButton; 38 | 39 | public TextWidget zoomDetails; 40 | public ButtonWidget zoomInButton; 41 | public ButtonWidget zoomOutButton; 42 | 43 | 44 | private int previousLimit; 45 | private boolean isFpsLowered = false; 46 | 47 | public BrowserImpl currentTab = TabManager.getCurrentTab(); 48 | 49 | public BrowserScreen(Text title) { 50 | super(title); 51 | } 52 | 53 | public void removeTab(int index) { 54 | remove(tabButtons.get(index)); 55 | tabButtons.get(index).resetIco(); 56 | tabButtons.remove(index); 57 | updateTabButtonsIndexes(index); 58 | updateTabSize(); 59 | } 60 | 61 | public void addTab(int index) { 62 | TabButton tabButton = new TabButton(BD_OFFSET, BD_OFFSET - 40, 100, 15, index); 63 | tabButtons.add(index, tabButton); 64 | updateTabButtonsIndexes(index + 1); 65 | addSelectableChild(tabButton); 66 | updateTabSize(); 67 | } 68 | 69 | private void updateTabButtonsIndexes(int i) { 70 | while (i < tabButtons.size()) { 71 | tabButtons.get(i).setTab(i); 72 | i++; 73 | } 74 | } 75 | 76 | private void updateTabSize() { 77 | if (!tabButtons.isEmpty()) { 78 | int size = Math.min(100, (this.width - (BD_OFFSET * 2) - 15) / tabButtons.size() - 5); 79 | for (TabButton tabButton : tabButtons) tabButton.setWidth(Math.max(15, size)); 80 | } 81 | } 82 | 83 | @Override 84 | protected void init() { 85 | super.init(); 86 | 87 | MinecraftClient client = MinecraftClient.getInstance(); 88 | BrowserAutoConfig config = MCBrowser.getConfig(); 89 | if(config.limitBrowserFramerate && client.options.getMaxFps().getValue() > config.browserFPS){ 90 | previousLimit = client.options.getMaxFps().getValue(); 91 | client.getInactivityFpsLimiter().setMaxFps(config.browserFPS); 92 | isFpsLowered = true; 93 | } 94 | 95 | BrowserUtil.instance = this; 96 | BrowserUtil.tooltipText = null; 97 | 98 | newTabButton = new NewTabButton(BD_OFFSET, BD_OFFSET - 40, 15, 15, Text.of("+")); 99 | for (TabHolder tab : TabManager.tabs) { 100 | int index = TabManager.tabs.indexOf(tab); 101 | TabButton tabButton = new TabButton(BD_OFFSET, BD_OFFSET - 40, 100, 15, index); 102 | tabButtons.add(tabButton); 103 | } 104 | for (TabButton tabButton : tabButtons) addSelectableChild(tabButton); 105 | updateTabSize(); 106 | initElements(); 107 | updateWidgets(); 108 | } 109 | 110 | public void initElements() { 111 | urlBox = BrowserUtil.initUrlBox(BD_OFFSET, width); 112 | 113 | backButton = BrowserUtil.initButton(Text.of("◀"), button -> currentTab.goBack(), BD_OFFSET, 1); 114 | forwardButton = BrowserUtil.initButton(Text.of("▶"), button -> currentTab.goForward(), BD_OFFSET + 20, 1); 115 | reloadButton = new ReloadButton(BD_OFFSET + 40, BD_OFFSET - 20, 15, 15); 116 | ButtonWidget homeButton = BrowserUtil.initButton(Text.of("⌂"), button -> BrowserUtil.homeButtonAction(), BD_OFFSET + 60, 1); 117 | specialButton = ButtonWidget.builder(Text.of(""), button -> SpecialButtonHelper.onPress(TabManager.getCurrentUrl())).dimensions(BD_OFFSET, height - BD_OFFSET + 5, 150, 15).build(); 118 | openInBrowserButton = ButtonWidget.builder(Text.of("Open In External Browser"), button -> BrowserUtil.openInBrowser()).dimensions(width - 200, height - BD_OFFSET + 5, 150, 15).build(); 119 | zoomDetails = new TextWidget(BrowserUtil.getZoomLevelText(currentTab.getZoomLevel()), MinecraftClient.getInstance().textRenderer); 120 | zoomDetails.setPosition(width-50-zoomDetails.getWidth(), BD_OFFSET - 49); 121 | zoomInButton = BrowserUtil.initButton(Text.of("+"), button -> zoomControl(BrowserUtil.ZoomActions.INCREASE), width - 65, 2); 122 | zoomOutButton = BrowserUtil.initButton(Text.of("-"), button -> zoomControl(BrowserUtil.ZoomActions.DECREASE), width - 85, 2); 123 | 124 | navigationButtons = new PressableWidget[]{forwardButton, backButton, reloadButton, homeButton}; 125 | zoomElements = new ClickableWidget[]{zoomInButton, zoomOutButton, zoomDetails}; 126 | uiElements = new ClickableWidget[]{forwardButton, backButton, reloadButton, homeButton, urlBox, specialButton, openInBrowserButton, newTabButton, zoomDetails, zoomInButton, zoomOutButton}; 127 | for (ClickableWidget widget : uiElements) addSelectableChild(widget); 128 | } 129 | 130 | public void updateWidgets() { 131 | urlBox.setText(currentTab.getURL()); 132 | urlBox.setCursorToStart(false); 133 | backButton.active = currentTab.canGoBack(); 134 | forwardButton.active = currentTab.canGoForward(); 135 | reloadButton.setMessage(Text.of(currentTab.isLoading() ? "❌" : "⟳")); 136 | SpecialButtonActions action = SpecialButtonActions.getFromUrlConstantValue(TabManager.getCurrentUrl()); 137 | if (action != null) specialButton.setMessage(action.getButtonText()); 138 | currentTab.resize(BrowserUtil.scaleX(width, BD_OFFSET), BrowserUtil.scaleY(height, BD_OFFSET)); 139 | } 140 | 141 | @Override 142 | public void resize(MinecraftClient minecraft, int i, int j) { 143 | ArrayList tempList = new ArrayList<>(tabButtons); 144 | tabButtons.clear(); 145 | super.resize(minecraft, i, j); 146 | resizeBrowser(); 147 | updateWidgets(); 148 | for (TabButton tabButton : tabButtons) { remove(tabButton); } 149 | tabButtons = tempList; 150 | for (TabButton tabButton : tabButtons) { addSelectableChild(tabButton); } 151 | updateTabSize(); 152 | for (ClickableWidget widget : uiElements) if (!children().contains(widget)) addSelectableChild(widget); 153 | } 154 | 155 | @Override 156 | public void close() { 157 | BrowserUtil.instance = null; 158 | for (TabButton tabButton : tabButtons) tabButton.resetIco(); 159 | if(isFpsLowered) MinecraftClient.getInstance().getInactivityFpsLimiter().setMaxFps(previousLimit); 160 | super.close(); 161 | } 162 | 163 | @Override 164 | public void render(DrawContext context, int mouseX, int mouseY, float delta) { 165 | super.render(context, mouseX, mouseY, delta); 166 | if (TabManager.getCurrentTabHolder().isInit()) { 167 | currentTab.render(context, BD_OFFSET, BD_OFFSET, this.width - BD_OFFSET * 2, this.height - BD_OFFSET * 2); 168 | } else { 169 | TabManager.getCurrentTabHolder().init(); 170 | resizeBrowser(); 171 | } 172 | renderWidgets(context, mouseX, mouseY, delta); 173 | 174 | if (BrowserUtil.tooltipText != null && BrowserUtil.tooltipText.getBytes().length != 0){ 175 | context.drawTooltip(MinecraftClient.getInstance().textRenderer, Text.of(BrowserUtil.tooltipText), mouseX, mouseY); 176 | } 177 | } 178 | 179 | private void renderWidgets(DrawContext context, int mouseX, int mouseY, float delta){ 180 | urlBox.renderWidget(context, mouseX, mouseY, delta); 181 | for (PressableWidget button : navigationButtons) button.render(context, mouseX, mouseY, delta); 182 | if (SpecialButtonHelper.isOnCompatableSite(TabManager.getCurrentUrl())) specialButton.render(context, mouseX, mouseY, delta); 183 | for (TabButton tabButton : tabButtons) tabButton.render(context, mouseX, mouseY, delta); 184 | for (ClickableWidget zoom :zoomElements) if (BrowserUtil.ZoomActions.shouldRenderZoomElements()) { 185 | if (zoom.isMouseOver(mouseX, mouseY)) BrowserUtil.ZoomActions.resetLastTimeCalled(); 186 | zoom.active = true; 187 | zoom.render(context, mouseX, mouseY, delta); 188 | }else{ 189 | zoom.active = false; 190 | } 191 | newTabButton.render(context, mouseX, mouseY, delta); 192 | openInBrowserButton.render(context, mouseX, mouseY, delta); 193 | } 194 | 195 | @Override 196 | public boolean mouseClicked(Click click, boolean doubled) { 197 | mouseButtonControlImpl(click, doubled, true); 198 | return super.mouseClicked(click, doubled); 199 | } 200 | 201 | @Override 202 | public boolean mouseReleased(Click click) { 203 | mouseButtonControlImpl(click, false, false); 204 | return super.mouseReleased(click); 205 | } 206 | 207 | @Override 208 | public void mouseMoved(double mouseX, double mouseY) { 209 | BrowserUtil.runAsyncIfEnabled(() -> currentTab.sendMouseMove(BrowserUtil.mouseX(mouseX, BD_OFFSET), BrowserUtil.mouseY(mouseY, BD_OFFSET))); 210 | super.mouseMoved(mouseX, mouseY); 211 | } 212 | 213 | @Override 214 | public boolean mouseDragged(Click click, double offsetX, double offsetY) { 215 | BrowserUtil.updateMouseLocation(click.x(), click.y()); 216 | return super.mouseDragged(click, offsetX, offsetY); 217 | } 218 | 219 | @Override 220 | public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { 221 | BrowserUtil.runAsyncIfEnabled(() -> currentTab.sendMouseWheel(BrowserUtil.mouseX(mouseX, BD_OFFSET), BrowserUtil.mouseY(mouseY, BD_OFFSET), verticalAmount, 0)); 222 | return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); 223 | } 224 | 225 | @Override 226 | public boolean keyPressed(KeyInput input) { 227 | sendKeyActivityAndSetFocus(input.getKeycode(), input.scancode(), input.modifiers(), true); 228 | 229 | // Ctrl and Enter Checks 230 | if ((client != null && client.isCtrlPressed() && BrowserUtil.ctrlKeyPressedSwitch(input.getKeycode(), input.modifiers())) || (Arrays.stream(uiElements).noneMatch(ClickableWidget::isFocused) && input.getKeycode() == GLFW.GLFW_KEY_ENTER)) { 231 | return true; 232 | } 233 | 234 | // Close screen on Esc key 235 | if (input.getKeycode() == 256 && this.shouldCloseOnEsc()) { 236 | this.close(); 237 | return true; 238 | } 239 | 240 | return this.getFocused() != null && this.getFocused().keyPressed(input); 241 | } 242 | 243 | @Override 244 | public boolean keyReleased(KeyInput input) { 245 | sendKeyActivityAndSetFocus(input.getKeycode(), input.scancode(), input.modifiers(), false); 246 | return super.keyReleased(input); 247 | } 248 | 249 | private void sendKeyActivityAndSetFocus(int keyCode, int scanCode, int modifiers, boolean isPress){ 250 | if (isPress ? !urlBox.isFocused() : !(client != null && client.isCtrlPressed()) || keyCode != GLFW.GLFW_KEY_TAB) BrowserUtil.runAsyncIfEnabled(() -> currentTab.sendKeyPressRelease(keyCode, scanCode, modifiers, isPress)); 251 | setFocus(); 252 | } 253 | 254 | @Override 255 | public boolean charTyped(CharInput input) { 256 | if(input.codepoint() == (char) 0) return false; 257 | BrowserUtil.runAsyncIfEnabled(() -> currentTab.sendKeyTyped((char) input.codepoint(), input.modifiers())); 258 | setFocus(); 259 | return super.charTyped(input); 260 | } 261 | 262 | 263 | //Multi Override Util Methods 264 | public void setFocus() { 265 | boolean browserFocus = true; 266 | for (ClickableWidget widget : uiElements) { 267 | boolean mouseOver = widget.isMouseOver(BrowserUtil.lastMouseX, BrowserUtil.lastMouseY); 268 | widget.setFocused(mouseOver); 269 | if(mouseOver) browserFocus = false; 270 | } 271 | currentTab.setFocus(browserFocus); 272 | } 273 | 274 | private void resizeBrowser() { 275 | if (width > 100 && height > 100) for (TabHolder tab : TabManager.tabs) if (tab.getBrowser() != null) tab.getBrowser().resize(BrowserUtil.scaleX(width, BD_OFFSET), BrowserUtil.scaleY(height, BD_OFFSET)); 276 | if (this.urlBox != null) urlBox.setWidth(BrowserUtil.getUrlBoxWidth(width, BD_OFFSET)); 277 | if (this.specialButton != null) specialButton.setPosition(BD_OFFSET, height - BD_OFFSET + 5); 278 | if (this.openInBrowserButton != null) openInBrowserButton.setPosition(width - 200, height - BD_OFFSET + 5); 279 | } 280 | 281 | private void mouseButtonControlImpl(Click click, boolean isDouble, boolean isClick) { 282 | if (click.x() > BD_OFFSET && click.x() < this.width - BD_OFFSET && click.y() > BD_OFFSET && click.y() < this.height - BD_OFFSET) currentTab.mouseButtonControl(click, isDouble, isClick); 283 | setFocus(); 284 | } 285 | 286 | public void zoomControl(byte zoomAction){ 287 | if(zoomAction == BrowserUtil.ZoomActions.INCREASE) currentTab.setZoomLevel(currentTab.getZoomLevel() + MCBrowser.getConfig().zoomScalingFactor); 288 | else if(zoomAction == BrowserUtil.ZoomActions.DECREASE) currentTab.setZoomLevel(currentTab.getZoomLevel() - MCBrowser.getConfig().zoomScalingFactor); 289 | else if(zoomAction == BrowserUtil.ZoomActions.RESET) currentTab.setZoomLevel(0.0); 290 | else throw new IllegalArgumentException("Invalid zoom action value: " + zoomAction); 291 | zoomDetails.setMessage(BrowserUtil.getZoomLevelText(currentTab.getZoomLevel())); 292 | BrowserUtil.ZoomActions.resetLastTimeCalled(); 293 | } 294 | } 295 | 296 | --------------------------------------------------------------------------------