summaryrefslogtreecommitdiff
path: root/docs/handbook/meshmc/settings-system.md
blob: 7d746971d7be447a8f2e873fe155582c84a2c9db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# Settings System

## Overview

MeshMC uses a hierarchical settings system built on Qt's `QObject` infrastructure. Settings support default values, overrides, signal-based change notification, and per-instance customization through a gate/override pattern.

## Architecture

### Key Classes

| Class | File | Purpose |
|---|---|---|
| `SettingsObject` | `settings/SettingsObject.{h,cpp}` | Abstract settings container |
| `INISettingsObject` | `settings/INISettingsObject.{h,cpp}` | File-backed settings |
| `INIFile` | `settings/INIFile.{h,cpp}` | QVariant map with file I/O |
| `Setting` | `settings/Setting.{h,cpp}` | Individual setting entry |
| `OverrideSetting` | `settings/OverrideSetting.{h,cpp}` | Per-instance override |
| `PassthroughSetting` | `settings/PassthroughSetting.{h,cpp}` | Delegate to another setting |

### Hierarchy

```
Global Settings (Application::settings())
    │
    ├── INISettingsObject backed by meshmc.cfg
    ├── Contains defaults for all application settings
    │
    ├──→ Instance Settings (BaseInstance::settings())
    │       │
    │       ├── INISettingsObject backed by instance.cfg
    │       ├── Uses OverrideSetting to selectively override globals
    │       └── Unoverridden settings transparently fall through to global
    │
    └──→ MinecraftInstance adds additional Minecraft-specific settings
```

## SettingsObject

The abstract base class for all settings containers:

```cpp
class SettingsObject : public QObject
{
    Q_OBJECT
public:
    virtual ~SettingsObject();

    // Registration
    std::shared_ptr<Setting> registerSetting(QStringList synonyms, QVariant defVal = QVariant());
    std::shared_ptr<Setting> registerOverride(std::shared_ptr<Setting> original, std::shared_ptr<Setting> gate);
    std::shared_ptr<Setting> registerPassthrough(std::shared_ptr<Setting> original, std::shared_ptr<Setting> gate);

    // Access
    std::shared_ptr<Setting> getSetting(const QString& id) const;
    QVariant get(const QString& id) const;
    bool set(const QString& id, QVariant value);
    void reset(const QString& id) const;
    bool contains(const QString& id);

    bool reload();

signals:
    void settingChanged(const Setting& setting, QVariant value);
    void settingReset(const Setting& setting);

protected:
    virtual void changeSetting(const Setting& setting, QVariant value) = 0;
    virtual void resetSetting(const Setting& setting) = 0;
    virtual QVariant retrieveValue(const Setting& setting) = 0;
    virtual bool contains(const QString& id) const = 0;

    QMap<QString, std::shared_ptr<Setting>> m_settings;
};
```

### Registration Methods

- **`registerSetting(synonyms, default)`** — registers a basic setting with optional synonyms for backward compatibility
- **`registerOverride(original, gate)`** — registers a setting that overrides `original` when `gate` is `true`; otherwise falls through to `original`
- **`registerPassthrough(original, gate)`** — registers a setting that always reads from the instance but writes only when `gate` is `true`

## Setting

Individual setting entries:

```cpp
class Setting : public QObject
{
    Q_OBJECT
public:
    Setting(QStringList synonyms, QVariant defVal);

    // Value access
    virtual QVariant get() const;
    virtual QVariant defValue() const;
    virtual void set(QVariant value);
    virtual void reset();

    // Identity
    QString id() const;
    QStringList synonyms() const;

signals:
    void SettingChanged(const Setting& setting, QVariant value);
    void SettingReset(const Setting& setting);
};
```

### Synonyms

Settings can have multiple names for backward compatibility:

```cpp
s->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854);
```

The first synonym is the canonical ID. Lookups work with any synonym. This allows renaming settings without breaking existing `meshmc.cfg` files.

## INISettingsObject

File-backed implementation using `INIFile`:

```cpp
class INISettingsObject : public SettingsObject
{
    Q_OBJECT
public:
    explicit INISettingsObject(const QString& path, QObject* parent = 0);
    explicit INISettingsObject(std::shared_ptr<INIFile> file, QObject* parent = 0);

    bool reload() override;

protected:
    void changeSetting(const Setting& setting, QVariant value) override;
    void resetSetting(const Setting& setting) override;
    QVariant retrieveValue(const Setting& setting) override;

    std::shared_ptr<INIFile> m_ini;
};
```

### INIFile

Simple key-value storage backed by a text file:

```cpp
class INIFile : public QMap<QString, QVariant>
{
public:
    explicit INIFile(const QString& filename);

    bool loadFile(const QString& fileName);
    bool saveFile(const QString& fileName);

    QVariant get(const QString& key, QVariant def) const;
    void set(const QString& key, QVariant val);
};
```

Format of `meshmc.cfg` / `instance.cfg`:
```ini
MinMemAlloc=512
MaxMemAlloc=4096
JavaPath=/usr/lib/jvm/java-21-openjdk/bin/java
Language=en_US
IconTheme=pe_colored
LaunchMaximized=false
```

## Override System

### OverrideSetting

Used by instance settings to selectively override global settings:

```cpp
class OverrideSetting : public Setting
{
    Q_OBJECT
public:
    OverrideSetting(std::shared_ptr<Setting> other, std::shared_ptr<Setting> gate);

    // Delegation logic:
    // get()   → if gate is true, return local value; else return other->get()
    // set()   → sets local value and sets gate to true
    // reset() → resets local value and sets gate to false

    bool isOverridden() const;  // Returns gate value

    virtual QVariant get() const override;
    virtual void set(QVariant value) override;
    virtual void reset() override;
    virtual QVariant defValue() const override;

private:
    std::shared_ptr<Setting> m_other;
    std::shared_ptr<Setting> m_gate;
};
```

The **gate** setting is a boolean that determines whether the instance-local value or the global value is used. When the gate is `false`, the setting falls through to the global setting.

### PassthroughSetting

Similar to `OverrideSetting` but always reads from instance storage:

```cpp
class PassthroughSetting : public Setting
{
    Q_OBJECT
public:
    PassthroughSetting(std::shared_ptr<Setting> other, std::shared_ptr<Setting> gate);

    // Always reads from instance storage
    // Writes only when gate is true
    // Falls through to other when gate is false for writes
};
```

## Global Settings Registration

In `Application.cpp`, global settings are registered:

```cpp
// Memory
m_settings->registerSetting("MinMemAlloc", 512);
m_settings->registerSetting("MaxMemAlloc", 4096);
m_settings->registerSetting("PermGen", 128);

// Java
m_settings->registerSetting("JavaPath", "");
m_settings->registerSetting("JvmArgs", "");
m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false);

// Window
m_settings->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854);
m_settings->registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480);
m_settings->registerSetting("LaunchMaximized", false);

// Network/proxy
m_settings->registerSetting("ProxyType", "None");
m_settings->registerSetting("ProxyAddr", "127.0.0.1");
m_settings->registerSetting("ProxyPort", 8080);
m_settings->registerSetting("ProxyUser", "");
m_settings->registerSetting("ProxyPass", "");

// Console
m_settings->registerSetting("ShowConsole", false);
m_settings->registerSetting("AutoCloseConsole", false);
m_settings->registerSetting("ShowConsoleOnError", true);
m_settings->registerSetting("LogPrePostOutput", true);

// Custom commands
m_settings->registerSetting("PreLaunchCommand", "");
m_settings->registerSetting("WrapperCommand", "");
m_settings->registerSetting("PostExitCommand", "");

// UI
m_settings->registerSetting("IconTheme", "pe_colored");
m_settings->registerSetting("ApplicationTheme", "system");
m_settings->registerSetting("Language", "");

// Updates
m_settings->registerSetting("AutoUpdate", true);
m_settings->registerSetting("UpdateChannel", "stable");

// Analytics
m_settings->registerSetting("Analytics", true);

// Miscellaneous
m_settings->registerSetting("InstSortMode", "Name");
m_settings->registerSetting("SelectedInstance", "");
m_settings->registerSetting("UpdateDialogGeometry", "");
m_settings->registerSetting("CatStyle", "kitteh");
```

## Instance Settings Override

`MinecraftInstance::settings()` creates override settings for per-instance customization:

```cpp
auto globalSettings = APPLICATION->settings();
auto s = m_settings;  // instance SettingsObject

// Memory overrides
s->registerOverride(globalSettings->getSetting("MinMemAlloc"), gate);
s->registerOverride(globalSettings->getSetting("MaxMemAlloc"), gate);

// Java overrides
auto javaGate = s->registerSetting("OverrideJavaLocation", false);
s->registerOverride(globalSettings->getSetting("JavaPath"), javaGate);

auto jvmArgsGate = s->registerSetting("OverrideJvmArgs", false);
s->registerOverride(globalSettings->getSetting("JvmArgs"), jvmArgsGate);

// Window overrides
auto windowGate = s->registerSetting("OverrideWindow", false);
s->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowGate);
s->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowGate);
s->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowGate);

// Console overrides
auto consoleGate = s->registerSetting("OverrideConsole", false);
s->registerOverride(globalSettings->getSetting("ShowConsole"), consoleGate);
s->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleGate);
s->registerOverride(globalSettings->getSetting("ShowConsoleOnError"), consoleGate);
```

### Gate Pattern

Each category of overridable settings has its own gate setting:
- `OverrideJavaLocation` — gates JavaPath
- `OverrideJvmArgs` — gates JvmArgs
- `OverrideMemory` — gates MinMemAlloc, MaxMemAlloc, PermGen
- `OverrideWindow` — gates MinecraftWinWidth, MinecraftWinHeight, LaunchMaximized
- `OverrideConsole` — gates ShowConsole, AutoCloseConsole, ShowConsoleOnError
- `OverrideCommands` — gates PreLaunchCommand, WrapperCommand, PostExitCommand
- `OverrideNativeWorkarounds` — gates UseNativeOpenAL, UseNativeGLFW

In the UI, each category has a checkbox. Enabling the checkbox:
1. Sets the gate to `true`
2. Enables the corresponding UI fields
3. Makes the setting read from instance storage instead of global

## Settings UI

### Global Settings Pages

Settings UI is organized into pages, each a `BasePage` subclass:

| Page | File | Settings |
|---|---|---|
| `MeshMCPage` | `pages/global/MeshMCPage.h` | Update channel, auto-update, analytics |
| `MinecraftPage` | `pages/global/MinecraftPage.h` | Window size, maximize, console behavior |
| `JavaPage` | `pages/global/JavaPage.h` | Java path, memory, JVM args |
| `LanguagePage` | `pages/global/LanguagePage.h` | UI language selection |
| `ProxyPage` | `pages/global/ProxyPage.h` | Network proxy settings |
| `ExternalToolsPage` | `pages/global/ExternalToolsPage.h` | Profiler, editor paths |
| `PasteEEPage` | `pages/global/PasteEEPage.h` | Paste service configuration |
| `CustomCommandsPage` | `pages/global/CustomCommandsPage.h` | Pre/post/wrapper commands |
| `AppearancePage` | `pages/global/AppearancePage.h` | Theme, icon theme, cat style |

### Instance Settings Pages

Instance setting pages mirror global pages but include the override checkboxes:

| Instance Page | Override Gate |
|---|---|
| Instance Java settings | `OverrideJavaLocation`, `OverrideJvmArgs`, `OverrideMemory` |
| Instance window settings | `OverrideWindow` |
| Instance console settings | `OverrideConsole` |
| Instance custom commands | `OverrideCommands` |

## Settings File Locations

| File | Location | Content |
|---|---|---|
| `meshmc.cfg` | Data directory root | Global settings |
| `instance.cfg` | Instance directory | Per-instance settings + overrides |
| `accounts.json` | Data directory root | Account data (see Account Management) |
| `metacache/` | Data directory root | HTTP cache metadata |

## Change Notification

Settings use Qt signals for reactive updates:

```cpp
// Connect to a specific setting change
connect(APPLICATION->settings().get(), &SettingsObject::settingChanged,
    this, [](const Setting& setting, QVariant value) {
        if (setting.id() == "Language") {
            // Reload translations
        }
    });

// Settings emit when changed
app->settings()->set("Language", "de_DE");
// → emits settingChanged(Setting("Language"), "de_DE")
```

## Data Path Resolution

The settings data path is determined at startup in `Application::Application()`:

1. **Portable mode**: If `meshmc_portable.txt` exists next to the binary, data lives alongside the binary
2. **Standard paths**: Otherwise uses `QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/MeshMC"`
3. **CLI override**: `--dir <path>` overrides the data directory

The data directory contains:
```
<data_dir>/
├── meshmc.cfg          # Global settings
├── accounts.json       # Account storage
├── instances/          # Instance directories
├── icons/              # Custom icons
├── themes/             # Custom themes
├── translations/       # Translation files
├── metacache/          # HTTP cache
├── logs/               # Application logs
└── java/               # Managed Java installations
```