1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

network: big feature enrichment

- Dedicated view in settings
- VPN profile management
- Ethernet disconnection
- Turn prompts into floating windows
This commit is contained in:
bbedward
2025-11-29 10:00:05 -05:00
parent 9c887fbe63
commit 1d3fe81ff7
51 changed files with 9807 additions and 2500 deletions

View File

@@ -328,6 +328,52 @@ func (_c *MockBackend_ConnectWiFi_Call) RunAndReturn(run func(network.Connection
return _c
}
// DeleteVPN provides a mock function with given fields: uuidOrName
func (_m *MockBackend) DeleteVPN(uuidOrName string) error {
ret := _m.Called(uuidOrName)
if len(ret) == 0 {
panic("no return value specified for DeleteVPN")
}
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(uuidOrName)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockBackend_DeleteVPN_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteVPN'
type MockBackend_DeleteVPN_Call struct {
*mock.Call
}
// DeleteVPN is a helper method to define mock.On call
// - uuidOrName string
func (_e *MockBackend_Expecter) DeleteVPN(uuidOrName interface{}) *MockBackend_DeleteVPN_Call {
return &MockBackend_DeleteVPN_Call{Call: _e.mock.On("DeleteVPN", uuidOrName)}
}
func (_c *MockBackend_DeleteVPN_Call) Run(run func(uuidOrName string)) *MockBackend_DeleteVPN_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string))
})
return _c
}
func (_c *MockBackend_DeleteVPN_Call) Return(_a0 error) *MockBackend_DeleteVPN_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockBackend_DeleteVPN_Call) RunAndReturn(run func(string) error) *MockBackend_DeleteVPN_Call {
_c.Call.Return(run)
return _c
}
// DisconnectAllVPN provides a mock function with no fields
func (_m *MockBackend) DisconnectAllVPN() error {
ret := _m.Called()
@@ -418,6 +464,52 @@ func (_c *MockBackend_DisconnectEthernet_Call) RunAndReturn(run func() error) *M
return _c
}
// DisconnectEthernetDevice provides a mock function with given fields: device
func (_m *MockBackend) DisconnectEthernetDevice(device string) error {
ret := _m.Called(device)
if len(ret) == 0 {
panic("no return value specified for DisconnectEthernetDevice")
}
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(device)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockBackend_DisconnectEthernetDevice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisconnectEthernetDevice'
type MockBackend_DisconnectEthernetDevice_Call struct {
*mock.Call
}
// DisconnectEthernetDevice is a helper method to define mock.On call
// - device string
func (_e *MockBackend_Expecter) DisconnectEthernetDevice(device interface{}) *MockBackend_DisconnectEthernetDevice_Call {
return &MockBackend_DisconnectEthernetDevice_Call{Call: _e.mock.On("DisconnectEthernetDevice", device)}
}
func (_c *MockBackend_DisconnectEthernetDevice_Call) Run(run func(device string)) *MockBackend_DisconnectEthernetDevice_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string))
})
return _c
}
func (_c *MockBackend_DisconnectEthernetDevice_Call) Return(_a0 error) *MockBackend_DisconnectEthernetDevice_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockBackend_DisconnectEthernetDevice_Call) RunAndReturn(run func(string) error) *MockBackend_DisconnectEthernetDevice_Call {
_c.Call.Return(run)
return _c
}
// DisconnectVPN provides a mock function with given fields: uuidOrName
func (_m *MockBackend) DisconnectVPN(uuidOrName string) error {
ret := _m.Called(uuidOrName)
@@ -658,6 +750,53 @@ func (_c *MockBackend_GetCurrentState_Call) RunAndReturn(run func() (*network.Ba
return _c
}
// GetEthernetDevices provides a mock function with no fields
func (_m *MockBackend) GetEthernetDevices() []network.EthernetDevice {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetEthernetDevices")
}
var r0 []network.EthernetDevice
if rf, ok := ret.Get(0).(func() []network.EthernetDevice); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]network.EthernetDevice)
}
}
return r0
}
// MockBackend_GetEthernetDevices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEthernetDevices'
type MockBackend_GetEthernetDevices_Call struct {
*mock.Call
}
// GetEthernetDevices is a helper method to define mock.On call
func (_e *MockBackend_Expecter) GetEthernetDevices() *MockBackend_GetEthernetDevices_Call {
return &MockBackend_GetEthernetDevices_Call{Call: _e.mock.On("GetEthernetDevices")}
}
func (_c *MockBackend_GetEthernetDevices_Call) Run(run func()) *MockBackend_GetEthernetDevices_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockBackend_GetEthernetDevices_Call) Return(_a0 []network.EthernetDevice) *MockBackend_GetEthernetDevices_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockBackend_GetEthernetDevices_Call) RunAndReturn(run func() []network.EthernetDevice) *MockBackend_GetEthernetDevices_Call {
_c.Call.Return(run)
return _c
}
// GetPromptBroker provides a mock function with no fields
func (_m *MockBackend) GetPromptBroker() network.PromptBroker {
ret := _m.Called()
@@ -705,6 +844,64 @@ func (_c *MockBackend_GetPromptBroker_Call) RunAndReturn(run func() network.Prom
return _c
}
// GetVPNConfig provides a mock function with given fields: uuidOrName
func (_m *MockBackend) GetVPNConfig(uuidOrName string) (*network.VPNConfig, error) {
ret := _m.Called(uuidOrName)
if len(ret) == 0 {
panic("no return value specified for GetVPNConfig")
}
var r0 *network.VPNConfig
var r1 error
if rf, ok := ret.Get(0).(func(string) (*network.VPNConfig, error)); ok {
return rf(uuidOrName)
}
if rf, ok := ret.Get(0).(func(string) *network.VPNConfig); ok {
r0 = rf(uuidOrName)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*network.VPNConfig)
}
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(uuidOrName)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockBackend_GetVPNConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetVPNConfig'
type MockBackend_GetVPNConfig_Call struct {
*mock.Call
}
// GetVPNConfig is a helper method to define mock.On call
// - uuidOrName string
func (_e *MockBackend_Expecter) GetVPNConfig(uuidOrName interface{}) *MockBackend_GetVPNConfig_Call {
return &MockBackend_GetVPNConfig_Call{Call: _e.mock.On("GetVPNConfig", uuidOrName)}
}
func (_c *MockBackend_GetVPNConfig_Call) Run(run func(uuidOrName string)) *MockBackend_GetVPNConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string))
})
return _c
}
func (_c *MockBackend_GetVPNConfig_Call) Return(_a0 *network.VPNConfig, _a1 error) *MockBackend_GetVPNConfig_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockBackend_GetVPNConfig_Call) RunAndReturn(run func(string) (*network.VPNConfig, error)) *MockBackend_GetVPNConfig_Call {
_c.Call.Return(run)
return _c
}
// GetWiFiDevices provides a mock function with no fields
func (_m *MockBackend) GetWiFiDevices() []network.WiFiDevice {
ret := _m.Called()
@@ -980,6 +1177,65 @@ func (_c *MockBackend_GetWiredNetworkDetails_Call) RunAndReturn(run func(string)
return _c
}
// ImportVPN provides a mock function with given fields: filePath, name
func (_m *MockBackend) ImportVPN(filePath string, name string) (*network.VPNImportResult, error) {
ret := _m.Called(filePath, name)
if len(ret) == 0 {
panic("no return value specified for ImportVPN")
}
var r0 *network.VPNImportResult
var r1 error
if rf, ok := ret.Get(0).(func(string, string) (*network.VPNImportResult, error)); ok {
return rf(filePath, name)
}
if rf, ok := ret.Get(0).(func(string, string) *network.VPNImportResult); ok {
r0 = rf(filePath, name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*network.VPNImportResult)
}
}
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(filePath, name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockBackend_ImportVPN_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ImportVPN'
type MockBackend_ImportVPN_Call struct {
*mock.Call
}
// ImportVPN is a helper method to define mock.On call
// - filePath string
// - name string
func (_e *MockBackend_Expecter) ImportVPN(filePath interface{}, name interface{}) *MockBackend_ImportVPN_Call {
return &MockBackend_ImportVPN_Call{Call: _e.mock.On("ImportVPN", filePath, name)}
}
func (_c *MockBackend_ImportVPN_Call) Run(run func(filePath string, name string)) *MockBackend_ImportVPN_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(string))
})
return _c
}
func (_c *MockBackend_ImportVPN_Call) Return(_a0 *network.VPNImportResult, _a1 error) *MockBackend_ImportVPN_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockBackend_ImportVPN_Call) RunAndReturn(run func(string, string) (*network.VPNImportResult, error)) *MockBackend_ImportVPN_Call {
_c.Call.Return(run)
return _c
}
// Initialize provides a mock function with no fields
func (_m *MockBackend) Initialize() error {
ret := _m.Called()
@@ -1082,6 +1338,63 @@ func (_c *MockBackend_ListActiveVPN_Call) RunAndReturn(run func() ([]network.VPN
return _c
}
// ListVPNPlugins provides a mock function with no fields
func (_m *MockBackend) ListVPNPlugins() ([]network.VPNPlugin, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for ListVPNPlugins")
}
var r0 []network.VPNPlugin
var r1 error
if rf, ok := ret.Get(0).(func() ([]network.VPNPlugin, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() []network.VPNPlugin); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]network.VPNPlugin)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockBackend_ListVPNPlugins_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListVPNPlugins'
type MockBackend_ListVPNPlugins_Call struct {
*mock.Call
}
// ListVPNPlugins is a helper method to define mock.On call
func (_e *MockBackend_Expecter) ListVPNPlugins() *MockBackend_ListVPNPlugins_Call {
return &MockBackend_ListVPNPlugins_Call{Call: _e.mock.On("ListVPNPlugins")}
}
func (_c *MockBackend_ListVPNPlugins_Call) Run(run func()) *MockBackend_ListVPNPlugins_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockBackend_ListVPNPlugins_Call) Return(_a0 []network.VPNPlugin, _a1 error) *MockBackend_ListVPNPlugins_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockBackend_ListVPNPlugins_Call) RunAndReturn(run func() ([]network.VPNPlugin, error)) *MockBackend_ListVPNPlugins_Call {
_c.Call.Return(run)
return _c
}
// ListVPNProfiles provides a mock function with no fields
func (_m *MockBackend) ListVPNProfiles() ([]network.VPNProfile, error) {
ret := _m.Called()
@@ -1276,6 +1589,55 @@ func (_c *MockBackend_SetPromptBroker_Call) RunAndReturn(run func(network.Prompt
return _c
}
// SetVPNCredentials provides a mock function with given fields: uuid, username, password, save
func (_m *MockBackend) SetVPNCredentials(uuid string, username string, password string, save bool) error {
ret := _m.Called(uuid, username, password, save)
if len(ret) == 0 {
panic("no return value specified for SetVPNCredentials")
}
var r0 error
if rf, ok := ret.Get(0).(func(string, string, string, bool) error); ok {
r0 = rf(uuid, username, password, save)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockBackend_SetVPNCredentials_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetVPNCredentials'
type MockBackend_SetVPNCredentials_Call struct {
*mock.Call
}
// SetVPNCredentials is a helper method to define mock.On call
// - uuid string
// - username string
// - password string
// - save bool
func (_e *MockBackend_Expecter) SetVPNCredentials(uuid interface{}, username interface{}, password interface{}, save interface{}) *MockBackend_SetVPNCredentials_Call {
return &MockBackend_SetVPNCredentials_Call{Call: _e.mock.On("SetVPNCredentials", uuid, username, password, save)}
}
func (_c *MockBackend_SetVPNCredentials_Call) Run(run func(uuid string, username string, password string, save bool)) *MockBackend_SetVPNCredentials_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(string), args[2].(string), args[3].(bool))
})
return _c
}
func (_c *MockBackend_SetVPNCredentials_Call) Return(_a0 error) *MockBackend_SetVPNCredentials_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockBackend_SetVPNCredentials_Call) RunAndReturn(run func(string, string, string, bool) error) *MockBackend_SetVPNCredentials_Call {
_c.Call.Return(run)
return _c
}
// SetWiFiAutoconnect provides a mock function with given fields: ssid, autoconnect
func (_m *MockBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
ret := _m.Called(ssid, autoconnect)
@@ -1495,6 +1857,53 @@ func (_c *MockBackend_SubmitCredentials_Call) RunAndReturn(run func(string, map[
return _c
}
// UpdateVPNConfig provides a mock function with given fields: uuid, updates
func (_m *MockBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
ret := _m.Called(uuid, updates)
if len(ret) == 0 {
panic("no return value specified for UpdateVPNConfig")
}
var r0 error
if rf, ok := ret.Get(0).(func(string, map[string]interface{}) error); ok {
r0 = rf(uuid, updates)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockBackend_UpdateVPNConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateVPNConfig'
type MockBackend_UpdateVPNConfig_Call struct {
*mock.Call
}
// UpdateVPNConfig is a helper method to define mock.On call
// - uuid string
// - updates map[string]interface{}
func (_e *MockBackend_Expecter) UpdateVPNConfig(uuid interface{}, updates interface{}) *MockBackend_UpdateVPNConfig_Call {
return &MockBackend_UpdateVPNConfig_Call{Call: _e.mock.On("UpdateVPNConfig", uuid, updates)}
}
func (_c *MockBackend_UpdateVPNConfig_Call) Run(run func(uuid string, updates map[string]interface{})) *MockBackend_UpdateVPNConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(map[string]interface{}))
})
return _c
}
func (_c *MockBackend_UpdateVPNConfig_Call) Return(_a0 error) *MockBackend_UpdateVPNConfig_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockBackend_UpdateVPNConfig_Call) RunAndReturn(run func(string, map[string]interface{}) error) *MockBackend_UpdateVPNConfig_Call {
_c.Call.Return(run)
return _c
}
// NewMockBackend creates a new instance of MockBackend. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockBackend(t interface {

View File

@@ -287,7 +287,7 @@ func TestNewManager(t *testing.T) {
} else {
assert.NotNil(t, manager)
assert.NotNil(t, manager.state)
assert.NotNil(t, manager.subscribers)
assert.NotNil(t, &manager.subscribers)
assert.NotNil(t, manager.stopChan)
manager.Close()

View File

@@ -18,24 +18,11 @@ func TestEventType_Constants(t *testing.T) {
func TestSessionState_Struct(t *testing.T) {
state := SessionState{
SessionID: "1",
SessionPath: "/org/freedesktop/login1/session/_31",
Locked: false,
Active: true,
IdleHint: false,
IdleSinceHint: 0,
LockedHint: false,
SessionType: "wayland",
SessionClass: "user",
User: 1000,
UserName: "testuser",
RemoteHost: "",
Service: "gdm-password",
TTY: "tty2",
Display: ":1",
Remote: false,
Seat: "seat0",
VTNr: 2,
PreparingForSleep: false,
}
assert.Equal(t, "1", state.SessionID)

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
@@ -176,8 +177,8 @@ func (a *SecretAgent) GetSecrets(
return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
}
log.Infof("[SecretAgent] VPN with empty hints but we're connecting - prompting for password")
fields = []string{"password"}
fields = inferVPNFields(conn, vpnSvc)
log.Infof("[SecretAgent] VPN with empty hints but we're connecting - inferred fields: %v", fields)
} else {
log.Infof("[SecretAgent] VPN with empty hints - deferring to other agents for %s", vpnSvc)
return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
@@ -251,6 +252,35 @@ func (a *SecretAgent) GetSecrets(
}
}
if settingName == "vpn" && a.backend != nil {
a.backend.cachedVPNCredsMu.Lock()
cached := a.backend.cachedVPNCreds
if cached != nil && cached.ConnectionUUID == connUuid {
a.backend.cachedVPNCreds = nil
a.backend.cachedVPNCredsMu.Unlock()
log.Infof("[SecretAgent] Using cached password from pre-activation prompt")
out := nmSettingMap{}
sec := nmVariantMap{}
sec["password"] = dbus.MakeVariant(cached.Password)
out[settingName] = sec
if cached.SavePassword {
a.backend.pendingVPNSaveMu.Lock()
a.backend.pendingVPNSave = &pendingVPNCredentials{
ConnectionPath: string(path),
Password: cached.Password,
SavePassword: true,
}
a.backend.pendingVPNSaveMu.Unlock()
}
return out, nil
}
a.backend.cachedVPNCredsMu.Unlock()
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
@@ -261,6 +291,7 @@ func (a *SecretAgent) GetSecrets(
VpnService: vpnSvc,
SettingName: settingName,
Fields: fields,
FieldsInfo: buildFieldsInfo(settingName, fields, vpnSvc),
Hints: hints,
Reason: reason,
ConnectionId: connId,
@@ -283,6 +314,7 @@ func (a *SecretAgent) GetSecrets(
wasConnecting := a.backend.state.IsConnecting
wasConnectingVPN := a.backend.state.IsConnectingVPN
cancelledSSID := a.backend.state.ConnectingSSID
cancelledVPNUUID := a.backend.state.ConnectingVPNUUID
if wasConnecting || wasConnectingVPN {
log.Infof("[SecretAgent] Clearing connecting state due to cancelled prompt")
a.backend.state.IsConnecting = false
@@ -301,6 +333,14 @@ func (a *SecretAgent) GetSecrets(
}
}
// If this was a VPN connection that was cancelled, deactivate it
if wasConnectingVPN && cancelledVPNUUID != "" {
log.Infof("[SecretAgent] Deactivating cancelled VPN connection: %s", cancelledVPNUUID)
if err := a.backend.DisconnectVPN(cancelledVPNUUID); err != nil {
log.Warnf("[SecretAgent] Failed to deactivate cancelled VPN: %v", err)
}
}
if (wasConnecting || wasConnectingVPN) && a.backend.onStateChange != nil {
a.backend.onStateChange()
}
@@ -320,7 +360,12 @@ func (a *SecretAgent) GetSecrets(
out := nmSettingMap{}
sec := nmVariantMap{}
var vpnUsername string
for k, v := range reply.Secrets {
if settingName == "vpn" && k == "username" {
vpnUsername = v
}
sec[k] = dbus.MakeVariant(v)
}
out[settingName] = sec
@@ -332,13 +377,22 @@ func (a *SecretAgent) GetSecrets(
log.Infof("[SecretAgent] Returning VPN secrets with %d fields for %s", len(sec), vpnSvc)
}
// If save=true, persist secrets in background after returning to NetworkManager
// This MUST happen after we return secrets, in a goroutine
if reply.Save {
if settingName == "vpn" && a.backend != nil && (vpnUsername != "" || reply.Save) {
pw, _ := reply.Secrets["password"]
a.backend.pendingVPNSaveMu.Lock()
a.backend.pendingVPNSave = &pendingVPNCredentials{
ConnectionPath: string(path),
Username: vpnUsername,
Password: pw,
SavePassword: reply.Save,
}
a.backend.pendingVPNSaveMu.Unlock()
log.Infof("[SecretAgent] Queued credentials persist for after connection succeeds")
} else if reply.Save && settingName != "vpn" {
// Non-VPN save logic
go func() {
log.Infof("[SecretAgent] Persisting secrets with Update2: path=%s, setting=%s", path, settingName)
// Get existing connection settings
connObj := a.conn.Object("org.freedesktop.NetworkManager", path)
var existingSettings map[string]map[string]dbus.Variant
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existingSettings); err != nil {
@@ -346,49 +400,12 @@ func (a *SecretAgent) GetSecrets(
return
}
// Build minimal settings with ONLY the section we're updating
// This avoids D-Bus type serialization issues with complex types like IPv6 addresses
settings := make(map[string]map[string]dbus.Variant)
// Copy connection section (required for Update2)
if connSection, ok := existingSettings["connection"]; ok {
settings["connection"] = connSection
}
// Update settings based on type
switch settingName {
case "vpn":
vpn, ok := existingSettings["vpn"]
if !ok {
vpn = make(map[string]dbus.Variant)
}
var data map[string]string
if dataVariant, ok := vpn["data"]; ok {
if dm, ok := dataVariant.Value().(map[string]string); ok {
data = make(map[string]string)
for k, v := range dm {
data[k] = v
}
} else {
data = make(map[string]string)
}
} else {
data = make(map[string]string)
}
data["password-flags"] = "0"
vpn["data"] = dbus.MakeVariant(data)
secs := make(map[string]string)
for k, v := range reply.Secrets {
secs[k] = v
}
vpn["secrets"] = dbus.MakeVariant(secs)
settings["vpn"] = vpn
log.Infof("[SecretAgent] Updated VPN settings: password-flags=0, secrets with %d fields", len(secs))
case "802-11-wireless-security":
wifiSec, ok := existingSettings["802-11-wireless-security"]
if !ok {
@@ -514,6 +531,102 @@ func fieldsNeeded(setting string, hints []string) []string {
}
}
func buildFieldsInfo(setting string, fields []string, vpnService string) []FieldInfo {
result := make([]FieldInfo, 0, len(fields))
for _, f := range fields {
info := FieldInfo{Name: f}
switch setting {
case "802-11-wireless-security":
info.Label = "Password"
info.IsSecret = true
case "802-1x":
switch f {
case "identity":
info.Label = "Username"
info.IsSecret = false
case "password":
info.Label = "Password"
info.IsSecret = true
default:
info.Label = f
info.IsSecret = true
}
case "vpn":
info.Label, info.IsSecret = vpnFieldMeta(f, vpnService)
default:
info.Label = f
info.IsSecret = true
}
result = append(result, info)
}
return result
}
func inferVPNFields(conn map[string]nmVariantMap, vpnService string) []string {
fields := []string{"password"}
vpnSettings, ok := conn["vpn"]
if !ok {
return fields
}
dataVariant, ok := vpnSettings["data"]
if !ok {
return fields
}
dataMap, ok := dataVariant.Value().(map[string]string)
if !ok {
return fields
}
connType := dataMap["connection-type"]
switch {
case strings.Contains(vpnService, "openvpn"):
if connType == "password" || connType == "password-tls" {
if dataMap["username"] == "" {
fields = []string{"username", "password"}
}
}
case strings.Contains(vpnService, "vpnc"), strings.Contains(vpnService, "l2tp"),
strings.Contains(vpnService, "pptp"), strings.Contains(vpnService, "openconnect"):
if dataMap["username"] == "" {
fields = []string{"username", "password"}
}
}
return fields
}
func vpnFieldMeta(field, vpnService string) (label string, isSecret bool) {
switch field {
case "password":
return "Password", true
case "Xauth password":
return "IPSec Password", true
case "IPSec secret":
return "IPSec Pre-Shared Key", true
case "cert-pass":
return "Certificate Password", true
case "http-proxy-password":
return "HTTP Proxy Password", true
case "username":
return "Username", false
case "Xauth username":
return "IPSec Username", false
case "proxy-password":
return "Proxy Password", true
case "private-key-password":
return "Private Key Password", true
}
if strings.HasSuffix(field, "password") || strings.HasSuffix(field, "secret") ||
strings.HasSuffix(field, "pass") || strings.HasSuffix(field, "psk") {
return strings.Title(strings.ReplaceAll(field, "-", " ")), true
}
return strings.Title(strings.ReplaceAll(field, "-", " ")), false
}
func readVPNPasswordFlags(conn map[string]nmVariantMap, settingName string) uint32 {
if settingName != "vpn" {
return 0xFFFF

View File

@@ -18,10 +18,12 @@ type Backend interface {
ForgetWiFiNetwork(ssid string) error
SetWiFiAutoconnect(ssid string, autoconnect bool) error
GetEthernetDevices() []EthernetDevice
GetWiredConnections() ([]WiredConnection, error)
GetWiredNetworkDetails(uuid string) (*WiredNetworkInfoResponse, error)
ConnectEthernet() error
DisconnectEthernet() error
DisconnectEthernetDevice(device string) error
ActivateWiredConnection(uuid string) error
ListVPNProfiles() ([]VPNProfile, error)
@@ -30,6 +32,12 @@ type Backend interface {
DisconnectVPN(uuidOrName string) error
DisconnectAllVPN() error
ClearVPNCredentials(uuidOrName string) error
ListVPNPlugins() ([]VPNPlugin, error)
ImportVPN(filePath string, name string) (*VPNImportResult, error)
GetVPNConfig(uuidOrName string) (*VPNConfig, error)
UpdateVPNConfig(uuid string, updates map[string]interface{}) error
SetVPNCredentials(uuid string, username string, password string, save bool) error
DeleteVPN(uuidOrName string) error
GetCurrentState() (*BackendState, error)
@@ -49,6 +57,7 @@ type BackendState struct {
EthernetDevice string
EthernetConnected bool
EthernetConnectionUuid string
EthernetDevices []EthernetDevice
WiFiIP string
WiFiDevice string
WiFiConnected bool

View File

@@ -84,6 +84,7 @@ func (b *HybridIwdNetworkdBackend) GetCurrentState() (*BackendState, error) {
merged.EthernetDevice = ls.EthernetDevice
merged.EthernetConnectionUuid = ls.EthernetConnectionUuid
merged.WiredConnections = ls.WiredConnections
merged.EthernetDevices = ls.EthernetDevices
if ls.EthernetConnected && ls.EthernetIP != "" {
merged.NetworkStatus = StatusEthernet
@@ -149,6 +150,14 @@ func (b *HybridIwdNetworkdBackend) DisconnectEthernet() error {
return b.l3.DisconnectEthernet()
}
func (b *HybridIwdNetworkdBackend) DisconnectEthernetDevice(device string) error {
return b.l3.DisconnectEthernetDevice(device)
}
func (b *HybridIwdNetworkdBackend) GetEthernetDevices() []EthernetDevice {
return b.l3.GetEthernetDevices()
}
func (b *HybridIwdNetworkdBackend) ActivateWiredConnection(uuid string) error {
return b.l3.ActivateWiredConnection(uuid)
}
@@ -177,6 +186,26 @@ func (b *HybridIwdNetworkdBackend) ClearVPNCredentials(uuidOrName string) error
return fmt.Errorf("VPN not supported in hybrid mode")
}
func (b *HybridIwdNetworkdBackend) ListVPNPlugins() ([]VPNPlugin, error) {
return []VPNPlugin{}, nil
}
func (b *HybridIwdNetworkdBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
return nil, fmt.Errorf("VPN not supported in hybrid mode")
}
func (b *HybridIwdNetworkdBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
return nil, fmt.Errorf("VPN not supported in hybrid mode")
}
func (b *HybridIwdNetworkdBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
return fmt.Errorf("VPN not supported in hybrid mode")
}
func (b *HybridIwdNetworkdBackend) DeleteVPN(uuidOrName string) error {
return fmt.Errorf("VPN not supported in hybrid mode")
}
func (b *HybridIwdNetworkdBackend) GetPromptBroker() PromptBroker {
return b.wifi.GetPromptBroker()
}
@@ -208,3 +237,7 @@ func (b *HybridIwdNetworkdBackend) DisconnectWiFiDevice(device string) error {
func (b *HybridIwdNetworkdBackend) GetWiFiDevices() []WiFiDevice {
return b.wifi.GetWiFiDevices()
}
func (b *HybridIwdNetworkdBackend) SetVPNCredentials(uuid, username, password string, save bool) error {
return fmt.Errorf("VPN not supported in hybrid mode")
}

View File

@@ -18,6 +18,14 @@ func (b *IWDBackend) DisconnectEthernet() error {
return fmt.Errorf("wired connections not supported by iwd")
}
func (b *IWDBackend) DisconnectEthernetDevice(device string) error {
return fmt.Errorf("wired connections not supported by iwd")
}
func (b *IWDBackend) GetEthernetDevices() []EthernetDevice {
return []EthernetDevice{}
}
func (b *IWDBackend) ActivateWiredConnection(uuid string) error {
return fmt.Errorf("wired connections not supported by iwd")
}
@@ -46,6 +54,30 @@ func (b *IWDBackend) ClearVPNCredentials(uuidOrName string) error {
return fmt.Errorf("VPN not supported by iwd backend")
}
func (b *IWDBackend) ListVPNPlugins() ([]VPNPlugin, error) {
return nil, fmt.Errorf("VPN not supported by iwd backend")
}
func (b *IWDBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
return nil, fmt.Errorf("VPN not supported by iwd backend")
}
func (b *IWDBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
return nil, fmt.Errorf("VPN not supported by iwd backend")
}
func (b *IWDBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
return fmt.Errorf("VPN not supported by iwd backend")
}
func (b *IWDBackend) DeleteVPN(uuidOrName string) error {
return fmt.Errorf("VPN not supported by iwd backend")
}
func (b *IWDBackend) SetVPNCredentials(uuid, username, password string, save bool) error {
return fmt.Errorf("VPN not supported by iwd backend")
}
func (b *IWDBackend) ScanWiFiDevice(device string) error {
return b.ScanWiFi()
}

View File

@@ -138,6 +138,7 @@ func (b *SystemdNetworkdBackend) updateState() error {
}
var wiredConns []WiredConnection
var ethernetDevices []EthernetDevice
for name, link := range b.links {
if b.isVirtualInterface(name) || strings.HasPrefix(name, "wlan") || strings.HasPrefix(name, "wlp") {
continue
@@ -151,6 +152,37 @@ func (b *SystemdNetworkdBackend) updateState() error {
Type: "ethernet",
IsActive: active,
})
var ip string
var hwAddr string
if iface, err := net.InterfaceByName(name); err == nil {
hwAddr = iface.HardwareAddr.String()
if addrs := b.getAddresses(name); len(addrs) > 0 {
ip = addrs[0]
}
}
stateStr := "disconnected"
switch link.opState {
case "routable":
stateStr = "routable"
case "carrier":
stateStr = "carrier"
case "degraded":
stateStr = "degraded"
case "no-carrier":
stateStr = "no-carrier"
case "off":
stateStr = "off"
}
ethernetDevices = append(ethernetDevices, EthernetDevice{
Name: name,
HwAddress: hwAddr,
State: stateStr,
Connected: active,
IP: ip,
})
}
b.stateMutex.Lock()
@@ -162,6 +194,7 @@ func (b *SystemdNetworkdBackend) updateState() error {
b.state.WiFiConnected = false
b.state.WiFiIP = ""
b.state.WiredConnections = wiredConns
b.state.EthernetDevices = ethernetDevices
if wiredIface != nil {
b.state.EthernetDevice = wiredIface.name

View File

@@ -108,3 +108,13 @@ func (b *SystemdNetworkdBackend) ActivateWiredConnection(id string) error {
linkObj := b.conn.Object(networkdBusName, link.path)
return linkObj.Call(networkdLinkIface+".Reconfigure", 0).Err
}
func (b *SystemdNetworkdBackend) GetEthernetDevices() []EthernetDevice {
b.stateMutex.RLock()
defer b.stateMutex.RUnlock()
return append([]EthernetDevice(nil), b.state.EthernetDevices...)
}
func (b *SystemdNetworkdBackend) DisconnectEthernetDevice(device string) error {
return fmt.Errorf("not supported by networkd backend")
}

View File

@@ -123,3 +123,25 @@ func TestSystemdNetworkdBackend_DisconnectEthernet(t *testing.T) {
assert.Error(t, err)
assert.Contains(t, err.Error(), "not supported")
}
func TestSystemdNetworkdBackend_GetEthernetDevices(t *testing.T) {
backend, _ := NewSystemdNetworkdBackend()
backend.state.EthernetDevices = []EthernetDevice{
{Name: "enp0s3", State: "routable", Connected: true},
{Name: "enp0s8", State: "no-carrier", Connected: false},
}
devices := backend.GetEthernetDevices()
assert.Len(t, devices, 2)
assert.Equal(t, "enp0s3", devices[0].Name)
assert.True(t, devices[0].Connected)
}
func TestSystemdNetworkdBackend_DisconnectEthernetDevice(t *testing.T) {
backend, _ := NewSystemdNetworkdBackend()
err := backend.DisconnectEthernetDevice("enp0s3")
assert.Error(t, err)
assert.Contains(t, err.Error(), "not supported")
}

View File

@@ -54,6 +54,30 @@ func (b *SystemdNetworkdBackend) ClearVPNCredentials(uuidOrName string) error {
return fmt.Errorf("VPN not supported by networkd backend")
}
func (b *SystemdNetworkdBackend) ListVPNPlugins() ([]VPNPlugin, error) {
return []VPNPlugin{}, nil
}
func (b *SystemdNetworkdBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
return nil, fmt.Errorf("VPN not supported by networkd backend")
}
func (b *SystemdNetworkdBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
return nil, fmt.Errorf("VPN not supported by networkd backend")
}
func (b *SystemdNetworkdBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
return fmt.Errorf("VPN not supported by networkd backend")
}
func (b *SystemdNetworkdBackend) DeleteVPN(uuidOrName string) error {
return fmt.Errorf("VPN not supported by networkd backend")
}
func (b *SystemdNetworkdBackend) SetVPNCredentials(uuid, username, password string, save bool) error {
return fmt.Errorf("VPN not supported by networkd backend")
}
func (b *SystemdNetworkdBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
return fmt.Errorf("WiFi autoconnect not supported by networkd backend")
}

View File

@@ -37,9 +37,17 @@ type wifiDeviceInfo struct {
hwAddress string
}
type ethernetDeviceInfo struct {
device gonetworkmanager.Device
wired gonetworkmanager.DeviceWired
name string
hwAddress string
}
type NetworkManagerBackend struct {
nmConn interface{}
ethernetDevice interface{}
ethernetDevices map[string]*ethernetDeviceInfo
wifiDevice interface{}
settings interface{}
wifiDev interface{}
@@ -60,9 +68,27 @@ type NetworkManagerBackend struct {
lastFailedTime int64
failedMutex sync.RWMutex
pendingVPNSave *pendingVPNCredentials
pendingVPNSaveMu sync.Mutex
cachedVPNCreds *cachedVPNCredentials
cachedVPNCredsMu sync.Mutex
onStateChange func()
}
type pendingVPNCredentials struct {
ConnectionPath string
Username string
Password string
SavePassword bool
}
type cachedVPNCredentials struct {
ConnectionUUID string
Password string
SavePassword bool
}
func NewNetworkManagerBackend(nmConn ...gonetworkmanager.NetworkManager) (*NetworkManagerBackend, error) {
var nm gonetworkmanager.NetworkManager
var err error
@@ -81,6 +107,7 @@ func NewNetworkManagerBackend(nmConn ...gonetworkmanager.NetworkManager) (*Netwo
backend := &NetworkManagerBackend{
nmConn: nm,
stopChan: make(chan struct{}),
ethernetDevices: make(map[string]*ethernetDeviceInfo),
wifiDevices: make(map[string]*wifiDeviceInfo),
state: &BackendState{
Backend: "networkmanager",
@@ -113,11 +140,30 @@ func (b *NetworkManagerBackend) Initialize() error {
if managed, _ := dev.GetPropertyManaged(); !managed {
continue
}
iface, err := dev.GetPropertyInterface()
if err != nil {
continue
}
w, err := gonetworkmanager.NewDeviceWired(dev.GetPath())
if err != nil {
continue
}
hwAddr, _ := w.GetPropertyHwAddress()
b.ethernetDevices[iface] = &ethernetDeviceInfo{
device: dev,
wired: w,
name: iface,
hwAddress: hwAddr,
}
if b.ethernetDevice == nil {
b.ethernetDevice = dev
}
if err := b.updateEthernetState(); err != nil {
continue
}
_, err := b.listEthernetConnections()
_, err = b.listEthernetConnections()
if err != nil {
return fmt.Errorf("failed to get wired configurations: %w", err)
}
@@ -165,6 +211,8 @@ func (b *NetworkManagerBackend) Initialize() error {
b.updateAllWiFiDevices()
}
b.updateAllEthernetDevices()
if err := b.updatePrimaryConnection(); err != nil {
return err
}
@@ -197,6 +245,7 @@ func (b *NetworkManagerBackend) GetCurrentState() (*BackendState, error) {
state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
state.WiFiDevices = append([]WiFiDevice(nil), b.state.WiFiDevices...)
state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
state.EthernetDevices = append([]EthernetDevice(nil), b.state.EthernetDevices...)
state.VPNProfiles = append([]VPNProfile(nil), b.state.VPNProfiles...)
state.VPNActive = append([]VPNActive(nil), b.state.VPNActive...)

View File

@@ -315,3 +315,88 @@ func (b *NetworkManagerBackend) listEthernetConnections() ([]WiredConnection, er
return wiredConfigs, nil
}
func (b *NetworkManagerBackend) GetEthernetDevices() []EthernetDevice {
b.stateMutex.RLock()
defer b.stateMutex.RUnlock()
return append([]EthernetDevice(nil), b.state.EthernetDevices...)
}
func (b *NetworkManagerBackend) DisconnectEthernetDevice(device string) error {
info, ok := b.ethernetDevices[device]
if !ok {
return fmt.Errorf("ethernet device %s not found", device)
}
if err := info.device.Disconnect(); err != nil {
return fmt.Errorf("failed to disconnect %s: %w", device, err)
}
b.updateAllEthernetDevices()
b.updateEthernetState()
b.listEthernetConnections()
b.updatePrimaryConnection()
if b.onStateChange != nil {
b.onStateChange()
}
return nil
}
func (b *NetworkManagerBackend) updateAllEthernetDevices() {
devices := make([]EthernetDevice, 0, len(b.ethernetDevices))
for name, info := range b.ethernetDevices {
state, _ := info.device.GetPropertyState()
connected := state == gonetworkmanager.NmDeviceStateActivated
driver, _ := info.device.GetPropertyDriver()
var ip string
var speed uint32 = 0
if connected {
ip = b.getDeviceIP(info.device)
}
if info.wired != nil {
speed, _ = info.wired.GetPropertySpeed()
}
stateStr := "disconnected"
switch state {
case gonetworkmanager.NmDeviceStateActivated:
stateStr = "activated"
case gonetworkmanager.NmDeviceStatePrepare:
stateStr = "preparing"
case gonetworkmanager.NmDeviceStateConfig:
stateStr = "configuring"
case gonetworkmanager.NmDeviceStateIpConfig:
stateStr = "ip-config"
case gonetworkmanager.NmDeviceStateIpCheck:
stateStr = "ip-check"
case gonetworkmanager.NmDeviceStateSecondaries:
stateStr = "secondaries"
case gonetworkmanager.NmDeviceStateDeactivating:
stateStr = "deactivating"
case gonetworkmanager.NmDeviceStateFailed:
stateStr = "failed"
case gonetworkmanager.NmDeviceStateUnavailable:
stateStr = "unavailable"
case gonetworkmanager.NmDeviceStateUnmanaged:
stateStr = "unmanaged"
}
devices = append(devices, EthernetDevice{
Name: name,
HwAddress: info.hwAddress,
State: stateStr,
Connected: connected,
IP: ip,
Speed: speed,
Driver: driver,
})
}
b.stateMutex.Lock()
b.state.EthernetDevices = devices
b.stateMutex.Unlock()
}

View File

@@ -82,3 +82,53 @@ func TestNetworkManagerBackend_ListEthernetConnections_NoDevice(t *testing.T) {
assert.Error(t, err)
assert.Contains(t, err.Error(), "no ethernet device available")
}
func TestNetworkManagerBackend_GetEthernetDevices_Empty(t *testing.T) {
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
backend, err := NewNetworkManagerBackend(mockNM)
assert.NoError(t, err)
devices := backend.GetEthernetDevices()
assert.Empty(t, devices)
}
func TestNetworkManagerBackend_GetEthernetDevices_WithState(t *testing.T) {
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
backend, err := NewNetworkManagerBackend(mockNM)
assert.NoError(t, err)
backend.state.EthernetDevices = []EthernetDevice{
{Name: "enp0s3", HwAddress: "00:11:22:33:44:55", State: "activated", Connected: true, IP: "192.168.1.100"},
{Name: "enp0s8", HwAddress: "00:11:22:33:44:66", State: "disconnected", Connected: false},
}
devices := backend.GetEthernetDevices()
assert.Len(t, devices, 2)
assert.Equal(t, "enp0s3", devices[0].Name)
assert.True(t, devices[0].Connected)
assert.Equal(t, "enp0s8", devices[1].Name)
assert.False(t, devices[1].Connected)
}
func TestNetworkManagerBackend_DisconnectEthernetDevice_NotFound(t *testing.T) {
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
backend, err := NewNetworkManagerBackend(mockNM)
assert.NoError(t, err)
err = backend.DisconnectEthernetDevice("nonexistent")
assert.Error(t, err)
assert.Contains(t, err.Error(), "not found")
}
func TestNetworkManagerBackend_UpdateAllEthernetDevices_Empty(t *testing.T) {
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
backend, err := NewNetworkManagerBackend(mockNM)
assert.NoError(t, err)
backend.updateAllEthernetDevices()
assert.Empty(t, backend.state.EthernetDevices)
}

View File

@@ -61,6 +61,26 @@ func (b *NetworkManagerBackend) startSignalPump() error {
return err
}
if err := conn.AddMatchSignal(
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
dbus.WithMatchInterface(dbusNMInterface),
dbus.WithMatchMember("DeviceAdded"),
); err != nil {
conn.RemoveSignal(signals)
conn.Close()
return err
}
if err := conn.AddMatchSignal(
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
dbus.WithMatchInterface(dbusNMInterface),
dbus.WithMatchMember("DeviceRemoved"),
); err != nil {
conn.RemoveSignal(signals)
conn.Close()
return err
}
if b.wifiDevice != nil {
dev := b.wifiDevice.(gonetworkmanager.Device)
if err := conn.AddMatchSignal(
@@ -175,6 +195,24 @@ func (b *NetworkManagerBackend) handleDBusSignal(sig *dbus.Signal) {
return
}
if sig.Name == "org.freedesktop.NetworkManager.DeviceAdded" {
if len(sig.Body) >= 1 {
if devicePath, ok := sig.Body[0].(dbus.ObjectPath); ok {
b.handleDeviceAdded(devicePath)
}
}
return
}
if sig.Name == "org.freedesktop.NetworkManager.DeviceRemoved" {
if len(sig.Body) >= 1 {
if devicePath, ok := sig.Body[0].(dbus.ObjectPath); ok {
b.handleDeviceRemoved(devicePath)
}
}
return
}
if len(sig.Body) < 2 {
return
}
@@ -319,3 +357,156 @@ func (b *NetworkManagerBackend) handleAccessPointChange(changes map[string]dbus.
}
}
}
func (b *NetworkManagerBackend) handleDeviceAdded(devicePath dbus.ObjectPath) {
dev, err := gonetworkmanager.NewDevice(devicePath)
if err != nil {
return
}
devType, err := dev.GetPropertyDeviceType()
if err != nil {
return
}
managed, _ := dev.GetPropertyManaged()
if !managed {
return
}
iface, err := dev.GetPropertyInterface()
if err != nil {
return
}
switch devType {
case gonetworkmanager.NmDeviceTypeEthernet:
w, err := gonetworkmanager.NewDeviceWired(devicePath)
if err != nil {
return
}
hwAddr, _ := w.GetPropertyHwAddress()
b.ethernetDevices[iface] = &ethernetDeviceInfo{
device: dev,
wired: w,
name: iface,
hwAddress: hwAddr,
}
if b.ethernetDevice == nil {
b.ethernetDevice = dev
}
if b.dbusConn != nil {
b.dbusConn.AddMatchSignal(
dbus.WithMatchObjectPath(devicePath),
dbus.WithMatchInterface(dbusPropsInterface),
dbus.WithMatchMember("PropertiesChanged"),
)
}
b.updateAllEthernetDevices()
b.updateEthernetState()
b.listEthernetConnections()
b.updatePrimaryConnection()
case gonetworkmanager.NmDeviceTypeWifi:
w, err := gonetworkmanager.NewDeviceWireless(devicePath)
if err != nil {
return
}
hwAddr, _ := w.GetPropertyHwAddress()
b.wifiDevices[iface] = &wifiDeviceInfo{
device: dev,
wireless: w,
name: iface,
hwAddress: hwAddr,
}
if b.wifiDevice == nil {
b.wifiDevice = dev
b.wifiDev = w
}
if b.dbusConn != nil {
b.dbusConn.AddMatchSignal(
dbus.WithMatchObjectPath(devicePath),
dbus.WithMatchInterface(dbusPropsInterface),
dbus.WithMatchMember("PropertiesChanged"),
)
}
b.updateAllWiFiDevices()
b.updateWiFiState()
}
if b.onStateChange != nil {
b.onStateChange()
}
}
func (b *NetworkManagerBackend) handleDeviceRemoved(devicePath dbus.ObjectPath) {
if b.dbusConn != nil {
b.dbusConn.RemoveMatchSignal(
dbus.WithMatchObjectPath(devicePath),
dbus.WithMatchInterface(dbusPropsInterface),
dbus.WithMatchMember("PropertiesChanged"),
)
}
for iface, info := range b.ethernetDevices {
if info.device.GetPath() == devicePath {
delete(b.ethernetDevices, iface)
if b.ethernetDevice != nil {
dev := b.ethernetDevice.(gonetworkmanager.Device)
if dev.GetPath() == devicePath {
b.ethernetDevice = nil
for _, remaining := range b.ethernetDevices {
b.ethernetDevice = remaining.device
break
}
}
}
b.updateAllEthernetDevices()
b.updateEthernetState()
b.listEthernetConnections()
b.updatePrimaryConnection()
if b.onStateChange != nil {
b.onStateChange()
}
return
}
}
for iface, info := range b.wifiDevices {
if info.device.GetPath() == devicePath {
delete(b.wifiDevices, iface)
if b.wifiDevice != nil {
dev := b.wifiDevice.(gonetworkmanager.Device)
if dev.GetPath() == devicePath {
b.wifiDevice = nil
b.wifiDev = nil
for _, remaining := range b.wifiDevices {
b.wifiDevice = remaining.device
b.wifiDev = remaining.wireless
break
}
}
}
b.updateAllWiFiDevices()
b.updateWiFiState()
if b.onStateChange != nil {
b.onStateChange()
}
return
}
}
}

View File

@@ -72,33 +72,34 @@ func (b *NetworkManagerBackend) updatePrimaryConnection() error {
}
func (b *NetworkManagerBackend) updateEthernetState() error {
if b.ethernetDevice == nil {
return nil
var connectedDevice string
var connectedIP string
var anyConnected bool
for name, info := range b.ethernetDevices {
state, err := info.device.GetPropertyState()
if err != nil {
continue
}
if state == gonetworkmanager.NmDeviceStateActivated {
anyConnected = true
connectedDevice = name
connectedIP = b.getDeviceIP(info.device)
break
}
}
if !anyConnected && b.ethernetDevice != nil {
dev := b.ethernetDevice.(gonetworkmanager.Device)
iface, err := dev.GetPropertyInterface()
if err != nil {
return err
}
state, err := dev.GetPropertyState()
if err != nil {
return err
}
connected := state == gonetworkmanager.NmDeviceStateActivated
var ip string
if connected {
ip = b.getDeviceIP(dev)
iface, _ := dev.GetPropertyInterface()
connectedDevice = iface
}
b.stateMutex.Lock()
b.state.EthernetDevice = iface
b.state.EthernetConnected = connected
b.state.EthernetIP = ip
b.state.EthernetDevice = connectedDevice
b.state.EthernetConnected = anyConnected
b.state.EthernetIP = connectedIP
b.stateMutex.Unlock()
return nil

View File

@@ -1,13 +1,19 @@
package network
import (
"bufio"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/Wifx/gonetworkmanager/v2"
"github.com/godbus/dbus/v5"
)
func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
@@ -46,11 +52,13 @@ func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
connID, _ := connMeta["id"].(string)
connUUID, _ := connMeta["uuid"].(string)
autoconnect, _ := connMeta["autoconnect"].(bool)
profile := VPNProfile{
Name: connID,
UUID: connUUID,
Type: connType,
Autoconnect: autoconnect,
}
if connType == "vpn" {
@@ -58,6 +66,16 @@ func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
if svcType, ok := vpnSettings["service-type"].(string); ok {
profile.ServiceType = svcType
}
// Get full data map
if data, ok := vpnSettings["data"].(map[string]string); ok {
profile.Data = data
if remote, ok := data["remote"]; ok {
profile.RemoteHost = remote
}
if username, ok := data["username"]; ok {
profile.Username = username
}
}
}
}
@@ -120,6 +138,31 @@ func (b *NetworkManagerBackend) ListActiveVPN() ([]VPNActive, error) {
Plugin: "",
}
// Get VPN device
devices, _ := activeConn.GetPropertyDevices()
if len(devices) > 0 {
if iface, err := devices[0].GetPropertyInterface(); err == nil {
vpnActive.Device = iface
}
}
// Get VPN IP from IP4Config
if ip4Config, err := activeConn.GetPropertyIP4Config(); err == nil && ip4Config != nil {
if addrData, err := ip4Config.GetPropertyAddressData(); err == nil && len(addrData) > 0 {
vpnActive.IP = addrData[0].Address
}
if gw, err := ip4Config.GetPropertyGateway(); err == nil {
vpnActive.Gateway = gw
}
}
// Get MTU from device
if len(devices) > 0 {
if mtu, err := devices[0].GetPropertyMtu(); err == nil {
vpnActive.MTU = mtu
}
}
if connType == "vpn" {
conn, _ := activeConn.GetPropertyConnection()
if conn != nil {
@@ -129,6 +172,16 @@ func (b *NetworkManagerBackend) ListActiveVPN() ([]VPNActive, error) {
if svcType, ok := vpnSettings["service-type"].(string); ok {
vpnActive.Plugin = svcType
}
// Get full data map
if data, ok := vpnSettings["data"].(map[string]string); ok {
vpnActive.Data = data
if remote, ok := data["remote"]; ok {
vpnActive.RemoteHost = remote
}
if username, ok := data["username"]; ok {
vpnActive.Username = username
}
}
}
}
}
@@ -219,10 +272,122 @@ func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool)
}
var targetUUID string
var connName string
if connMeta, ok := targetSettings["connection"]; ok {
if uuid, ok := connMeta["uuid"].(string); ok {
targetUUID = uuid
}
if id, ok := connMeta["id"].(string); ok {
connName = id
}
}
needsUsernamePrePrompt := false
var vpnServiceType string
if vpnSettings, ok := targetSettings["vpn"]; ok {
if svc, ok := vpnSettings["service-type"].(string); ok {
vpnServiceType = svc
}
if data, ok := vpnSettings["data"].(map[string]string); ok {
connType := data["connection-type"]
username := data["username"]
// OpenVPN password auth needs username in vpn.data
if strings.Contains(vpnServiceType, "openvpn") &&
(connType == "password" || connType == "password-tls") &&
username == "" {
needsUsernamePrePrompt = true
}
}
}
// If username is needed but missing, prompt for it before activating
if needsUsernamePrePrompt && b.promptBroker != nil {
log.Infof("[ConnectVPN] OpenVPN requires username in vpn.data - prompting before activation")
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
token, err := b.promptBroker.Ask(ctx, PromptRequest{
Name: connName,
ConnType: "vpn",
VpnService: vpnServiceType,
SettingName: "vpn",
Fields: []string{"username", "password"},
FieldsInfo: []FieldInfo{{Name: "username", Label: "Username", IsSecret: false}, {Name: "password", Label: "Password", IsSecret: true}},
Reason: "required",
ConnectionId: connName,
ConnectionUuid: targetUUID,
ConnectionPath: string(targetConn.GetPath()),
})
if err != nil {
return fmt.Errorf("failed to request credentials: %w", err)
}
reply, err := b.promptBroker.Wait(ctx, token)
if err != nil {
return fmt.Errorf("credentials prompt failed: %w", err)
}
username := reply.Secrets["username"]
password := reply.Secrets["password"]
if username != "" {
connObj := b.dbusConn.Object("org.freedesktop.NetworkManager", targetConn.GetPath())
var existingSettings map[string]map[string]dbus.Variant
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existingSettings); err != nil {
return fmt.Errorf("failed to get settings for username save: %w", err)
}
settings := make(map[string]map[string]dbus.Variant)
if connSection, ok := existingSettings["connection"]; ok {
settings["connection"] = connSection
}
vpn := existingSettings["vpn"]
var data map[string]string
if dataVariant, ok := vpn["data"]; ok {
if dm, ok := dataVariant.Value().(map[string]string); ok {
data = make(map[string]string)
for k, v := range dm {
data[k] = v
}
} else {
data = make(map[string]string)
}
} else {
data = make(map[string]string)
}
data["username"] = username
if reply.Save && password != "" {
data["password-flags"] = "0"
secs := make(map[string]string)
secs["password"] = password
vpn["secrets"] = dbus.MakeVariant(secs)
log.Infof("[ConnectVPN] Saving username and password to vpn.data")
} else {
log.Infof("[ConnectVPN] Saving username to vpn.data (password will be prompted)")
}
vpn["data"] = dbus.MakeVariant(data)
settings["vpn"] = vpn
var result map[string]dbus.Variant
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.Update2", 0,
settings, uint32(0x1), map[string]dbus.Variant{}).Store(&result); err != nil {
return fmt.Errorf("failed to save username: %w", err)
}
log.Infof("[ConnectVPN] Username saved to connection, now activating")
if password != "" && !reply.Save {
b.cachedVPNCredsMu.Lock()
b.cachedVPNCreds = &cachedVPNCredentials{
ConnectionUUID: targetUUID,
Password: password,
SavePassword: reply.Save,
}
b.cachedVPNCredsMu.Unlock()
log.Infof("[ConnectVPN] Cached password for GetSecrets")
}
}
}
b.stateMutex.Lock()
@@ -470,7 +635,7 @@ func (b *NetworkManagerBackend) updateVPNConnectionState() {
continue
}
uuid, err := activeConn.GetPropertyUUID()
connUUID, err := activeConn.GetPropertyUUID()
if err != nil {
continue
}
@@ -478,20 +643,29 @@ func (b *NetworkManagerBackend) updateVPNConnectionState() {
state, _ := activeConn.GetPropertyState()
stateReason, _ := activeConn.GetPropertyStateFlags()
if uuid == connectingVPNUUID {
if connUUID == connectingVPNUUID {
foundConnection = true
switch state {
case 2:
log.Infof("[updateVPNConnectionState] VPN connection successful: %s", uuid)
log.Infof("[updateVPNConnectionState] VPN connection successful: %s", connUUID)
b.stateMutex.Lock()
b.state.IsConnectingVPN = false
b.state.ConnectingVPNUUID = ""
b.state.LastError = ""
b.stateMutex.Unlock()
b.pendingVPNSaveMu.Lock()
pending := b.pendingVPNSave
b.pendingVPNSave = nil
b.pendingVPNSaveMu.Unlock()
if pending != nil {
go b.saveVPNCredentials(pending)
}
return
case 4:
log.Warnf("[updateVPNConnectionState] VPN connection failed/deactivated: %s (state=%d, flags=%d)", uuid, state, stateReason)
log.Warnf("[updateVPNConnectionState] VPN connection failed/deactivated: %s (state=%d, flags=%d)", connUUID, state, stateReason)
b.stateMutex.Lock()
b.state.IsConnectingVPN = false
b.state.ConnectingVPNUUID = ""
@@ -511,3 +685,622 @@ func (b *NetworkManagerBackend) updateVPNConnectionState() {
b.stateMutex.Unlock()
}
}
func (b *NetworkManagerBackend) saveVPNCredentials(creds *pendingVPNCredentials) {
log.Infof("[saveVPNCredentials] Saving credentials for %s (username=%v, savePassword=%v)",
creds.ConnectionPath, creds.Username != "", creds.SavePassword)
connObj := b.dbusConn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath(creds.ConnectionPath))
var existingSettings map[string]map[string]dbus.Variant
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existingSettings); err != nil {
log.Warnf("[saveVPNCredentials] GetSettings failed: %v", err)
return
}
settings := make(map[string]map[string]dbus.Variant)
if connSection, ok := existingSettings["connection"]; ok {
settings["connection"] = connSection
}
vpn, ok := existingSettings["vpn"]
if !ok {
vpn = make(map[string]dbus.Variant)
}
// Get existing data map
var data map[string]string
if dataVariant, ok := vpn["data"]; ok {
if dm, ok := dataVariant.Value().(map[string]string); ok {
data = make(map[string]string)
for k, v := range dm {
data[k] = v
}
} else {
data = make(map[string]string)
}
} else {
data = make(map[string]string)
}
// Always save username if provided
if creds.Username != "" {
data["username"] = creds.Username
log.Infof("[saveVPNCredentials] Saving username")
}
// Save password if requested
if creds.SavePassword {
data["password-flags"] = "0"
secs := make(map[string]string)
secs["password"] = creds.Password
vpn["secrets"] = dbus.MakeVariant(secs)
log.Infof("[saveVPNCredentials] Saving password with password-flags=0")
}
vpn["data"] = dbus.MakeVariant(data)
settings["vpn"] = vpn
var result map[string]dbus.Variant
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.Update2", 0,
settings, uint32(0x1), map[string]dbus.Variant{}).Store(&result); err != nil {
log.Warnf("[saveVPNCredentials] Update2 failed: %v", err)
} else {
log.Infof("[saveVPNCredentials] Successfully saved credentials")
}
}
func (b *NetworkManagerBackend) ListVPNPlugins() ([]VPNPlugin, error) {
plugins := []VPNPlugin{}
pluginDirs := []string{
"/usr/lib/NetworkManager/VPN",
"/usr/lib64/NetworkManager/VPN",
"/etc/NetworkManager/VPN",
}
seen := make(map[string]bool)
for _, dir := range pluginDirs {
entries, err := os.ReadDir(dir)
if err != nil {
continue
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".name") {
continue
}
filePath := filepath.Join(dir, entry.Name())
plugin, err := parseVPNPluginFile(filePath)
if err != nil {
log.Debugf("Failed to parse VPN plugin file %s: %v", filePath, err)
continue
}
if seen[plugin.ServiceType] {
continue
}
seen[plugin.ServiceType] = true
plugins = append(plugins, *plugin)
}
}
sort.Slice(plugins, func(i, j int) bool {
return strings.ToLower(plugins[i].Name) < strings.ToLower(plugins[j].Name)
})
return plugins, nil
}
func parseVPNPluginFile(path string) (*VPNPlugin, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
plugin := &VPNPlugin{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "[") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch key {
case "name":
plugin.Name = value
case "service":
plugin.ServiceType = value
case "program":
plugin.Program = value
case "supports":
plugin.Supports = strings.Split(value, ",")
for i := range plugin.Supports {
plugin.Supports[i] = strings.TrimSpace(plugin.Supports[i])
}
}
}
if plugin.ServiceType == "" {
return nil, fmt.Errorf("plugin file missing service type")
}
plugin.FileExtensions = getVPNFileExtensions(plugin.ServiceType)
return plugin, nil
}
func getVPNFileExtensions(serviceType string) []string {
switch {
case strings.Contains(serviceType, "openvpn"):
return []string{".ovpn", ".conf"}
case strings.Contains(serviceType, "wireguard"):
return []string{".conf"}
case strings.Contains(serviceType, "vpnc"), strings.Contains(serviceType, "cisco"):
return []string{".pcf", ".conf"}
case strings.Contains(serviceType, "openconnect"):
return []string{".conf"}
case strings.Contains(serviceType, "pptp"):
return []string{".conf"}
case strings.Contains(serviceType, "l2tp"):
return []string{".conf"}
case strings.Contains(serviceType, "strongswan"), strings.Contains(serviceType, "ipsec"):
return []string{".conf", ".sswan"}
default:
return []string{".conf"}
}
}
func (b *NetworkManagerBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return &VPNImportResult{
Success: false,
Error: fmt.Sprintf("file not found: %s", filePath),
}, nil
}
ext := strings.ToLower(filepath.Ext(filePath))
switch ext {
case ".ovpn", ".conf":
return b.importVPNWithNmcli(filePath, name)
default:
return b.importVPNWithNmcli(filePath, name)
}
}
func (b *NetworkManagerBackend) importVPNWithNmcli(filePath string, name string) (*VPNImportResult, error) {
args := []string{"connection", "import", "type", "openvpn", "file", filePath}
cmd := exec.Command("nmcli", args...)
output, err := cmd.CombinedOutput()
if err != nil {
outputStr := string(output)
if strings.Contains(outputStr, "vpnc") || strings.Contains(outputStr, "unknown connection type") {
for _, vpnType := range []string{"vpnc", "pptp", "l2tp", "openconnect", "strongswan", "wireguard"} {
args = []string{"connection", "import", "type", vpnType, "file", filePath}
cmd = exec.Command("nmcli", args...)
output, err = cmd.CombinedOutput()
if err == nil {
break
}
}
}
if err != nil {
return &VPNImportResult{
Success: false,
Error: fmt.Sprintf("import failed: %s", strings.TrimSpace(string(output))),
}, nil
}
}
outputStr := string(output)
var connUUID, connName string
lines := strings.Split(outputStr, "\n")
for _, line := range lines {
if strings.Contains(line, "successfully added") {
parts := strings.Fields(line)
for i, part := range parts {
if part == "(" && i+1 < len(parts) {
connUUID = strings.TrimSuffix(parts[i+1], ")")
break
}
}
}
}
if name != "" && connUUID != "" {
renameCmd := exec.Command("nmcli", "connection", "modify", connUUID, "connection.id", name)
if err := renameCmd.Run(); err != nil {
log.Warnf("Failed to rename imported VPN: %v", err)
} else {
connName = name
}
}
if connUUID == "" {
s := b.settings
if s == nil {
var settingsErr error
s, settingsErr = gonetworkmanager.NewSettings()
if settingsErr == nil {
b.settings = s
}
}
if s != nil {
settingsMgr := s.(gonetworkmanager.Settings)
connections, _ := settingsMgr.ListConnections()
baseName := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath))
for _, conn := range connections {
settings, err := conn.GetSettings()
if err != nil {
continue
}
connMeta, ok := settings["connection"]
if !ok {
continue
}
connType, _ := connMeta["type"].(string)
if connType != "vpn" && connType != "wireguard" {
continue
}
connID, _ := connMeta["id"].(string)
if strings.Contains(connID, baseName) || (name != "" && connID == name) {
connUUID, _ = connMeta["uuid"].(string)
connName = connID
break
}
}
}
}
b.ListVPNProfiles()
if b.onStateChange != nil {
b.onStateChange()
}
return &VPNImportResult{
Success: true,
UUID: connUUID,
Name: connName,
}, nil
}
func (b *NetworkManagerBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
s := b.settings
if s == nil {
var err error
s, err = gonetworkmanager.NewSettings()
if err != nil {
return nil, fmt.Errorf("failed to get settings: %w", err)
}
b.settings = s
}
settingsMgr := s.(gonetworkmanager.Settings)
connections, err := settingsMgr.ListConnections()
if err != nil {
return nil, fmt.Errorf("failed to get connections: %w", err)
}
for _, conn := range connections {
settings, err := conn.GetSettings()
if err != nil {
continue
}
connMeta, ok := settings["connection"]
if !ok {
continue
}
connType, _ := connMeta["type"].(string)
if connType != "vpn" && connType != "wireguard" {
continue
}
connID, _ := connMeta["id"].(string)
connUUID, _ := connMeta["uuid"].(string)
if connUUID != uuidOrName && connID != uuidOrName {
continue
}
autoconnect := true
if ac, ok := connMeta["autoconnect"].(bool); ok {
autoconnect = ac
}
config := &VPNConfig{
UUID: connUUID,
Name: connID,
Type: connType,
Autoconnect: autoconnect,
Data: make(map[string]string),
}
if connType == "vpn" {
if vpnSettings, ok := settings["vpn"]; ok {
if svcType, ok := vpnSettings["service-type"].(string); ok {
config.ServiceType = svcType
}
if dataMap, ok := vpnSettings["data"].(map[string]string); ok {
for k, v := range dataMap {
if !strings.Contains(strings.ToLower(k), "password") &&
!strings.Contains(strings.ToLower(k), "secret") &&
!strings.Contains(strings.ToLower(k), "key") {
config.Data[k] = v
}
}
}
}
}
return config, nil
}
return nil, fmt.Errorf("VPN connection not found: %s", uuidOrName)
}
func (b *NetworkManagerBackend) UpdateVPNConfig(connUUID string, updates map[string]interface{}) error {
s := b.settings
if s == nil {
var err error
s, err = gonetworkmanager.NewSettings()
if err != nil {
return fmt.Errorf("failed to get settings: %w", err)
}
b.settings = s
}
settingsMgr := s.(gonetworkmanager.Settings)
connections, err := settingsMgr.ListConnections()
if err != nil {
return fmt.Errorf("failed to get connections: %w", err)
}
for _, conn := range connections {
settings, err := conn.GetSettings()
if err != nil {
continue
}
connMeta, ok := settings["connection"]
if !ok {
continue
}
connType, _ := connMeta["type"].(string)
if connType != "vpn" && connType != "wireguard" {
continue
}
existingUUID, _ := connMeta["uuid"].(string)
if existingUUID != connUUID {
continue
}
if name, ok := updates["name"].(string); ok && name != "" {
connMeta["id"] = name
}
if autoconnect, ok := updates["autoconnect"].(bool); ok {
connMeta["autoconnect"] = autoconnect
}
if data, ok := updates["data"].(map[string]interface{}); ok {
if vpnSettings, ok := settings["vpn"]; ok {
existingData, _ := vpnSettings["data"].(map[string]string)
if existingData == nil {
existingData = make(map[string]string)
}
for k, v := range data {
if strVal, ok := v.(string); ok {
existingData[k] = strVal
}
}
vpnSettings["data"] = existingData
}
}
if ipv4, ok := settings["ipv4"]; ok {
delete(ipv4, "addresses")
delete(ipv4, "routes")
delete(ipv4, "dns")
}
if ipv6, ok := settings["ipv6"]; ok {
delete(ipv6, "addresses")
delete(ipv6, "routes")
delete(ipv6, "dns")
}
if err := conn.Update(settings); err != nil {
return fmt.Errorf("failed to update connection: %w", err)
}
b.ListVPNProfiles()
if b.onStateChange != nil {
b.onStateChange()
}
return nil
}
return fmt.Errorf("VPN connection not found: %s", connUUID)
}
func (b *NetworkManagerBackend) SetVPNCredentials(connUUID string, username string, password string, saveToKeyring bool) error {
s := b.settings
if s == nil {
var err error
s, err = gonetworkmanager.NewSettings()
if err != nil {
return fmt.Errorf("failed to get settings: %w", err)
}
b.settings = s
}
settingsMgr := s.(gonetworkmanager.Settings)
connections, err := settingsMgr.ListConnections()
if err != nil {
return fmt.Errorf("failed to get connections: %w", err)
}
for _, conn := range connections {
settings, err := conn.GetSettings()
if err != nil {
continue
}
connMeta, ok := settings["connection"]
if !ok {
continue
}
connType, _ := connMeta["type"].(string)
if connType != "vpn" && connType != "wireguard" {
continue
}
existingUUID, _ := connMeta["uuid"].(string)
if existingUUID != connUUID {
continue
}
vpnSettings, ok := settings["vpn"]
if !ok {
vpnSettings = make(map[string]interface{})
settings["vpn"] = vpnSettings
}
existingData, _ := vpnSettings["data"].(map[string]string)
if existingData == nil {
existingData = make(map[string]string)
}
if username != "" {
existingData["username"] = username
}
if saveToKeyring {
existingData["password-flags"] = "0"
} else {
existingData["password-flags"] = "2"
}
vpnSettings["data"] = existingData
if password != "" {
secrets := make(map[string]string)
secrets["password"] = password
vpnSettings["secrets"] = secrets
}
if ipv4, ok := settings["ipv4"]; ok {
delete(ipv4, "addresses")
delete(ipv4, "routes")
delete(ipv4, "dns")
}
if ipv6, ok := settings["ipv6"]; ok {
delete(ipv6, "addresses")
delete(ipv6, "routes")
delete(ipv6, "dns")
}
if err := conn.Update(settings); err != nil {
return fmt.Errorf("failed to update connection: %w", err)
}
log.Infof("Updated VPN credentials for %s (save=%v)", connUUID, saveToKeyring)
if b.onStateChange != nil {
b.onStateChange()
}
return nil
}
return fmt.Errorf("VPN connection not found: %s", connUUID)
}
func (b *NetworkManagerBackend) DeleteVPN(uuidOrName string) error {
active, _ := b.ListActiveVPN()
for _, vpn := range active {
if vpn.UUID == uuidOrName || vpn.Name == uuidOrName {
if err := b.DisconnectVPN(uuidOrName); err != nil {
log.Warnf("Failed to disconnect VPN before deletion: %v", err)
}
time.Sleep(200 * time.Millisecond)
break
}
}
s := b.settings
if s == nil {
var err error
s, err = gonetworkmanager.NewSettings()
if err != nil {
return fmt.Errorf("failed to get settings: %w", err)
}
b.settings = s
}
settingsMgr := s.(gonetworkmanager.Settings)
connections, err := settingsMgr.ListConnections()
if err != nil {
return fmt.Errorf("failed to get connections: %w", err)
}
for _, conn := range connections {
settings, err := conn.GetSettings()
if err != nil {
continue
}
connMeta, ok := settings["connection"]
if !ok {
continue
}
connType, _ := connMeta["type"].(string)
if connType != "vpn" && connType != "wireguard" {
continue
}
connID, _ := connMeta["id"].(string)
connUUID, _ := connMeta["uuid"].(string)
if connUUID == uuidOrName || connID == uuidOrName {
if err := conn.Delete(); err != nil {
return fmt.Errorf("failed to delete VPN: %w", err)
}
b.ListVPNProfiles()
if b.onStateChange != nil {
b.onStateChange()
}
log.Infof("Deleted VPN connection: %s (%s)", connID, connUUID)
return nil
}
}
return fmt.Errorf("VPN connection not found: %s", uuidOrName)
}

View File

@@ -579,31 +579,59 @@ func (b *NetworkManagerBackend) createAndConnectWiFiOnDevice(req ConnectionReque
"key-mgmt": "wpa-eap",
}
eapMethod := "peap"
if req.EAPMethod != "" {
eapMethod = req.EAPMethod
}
phase2Auth := "mschapv2"
if req.Phase2Auth != "" {
phase2Auth = req.Phase2Auth
}
useSystemCACerts := false
if req.UseSystemCACerts != nil {
useSystemCACerts = *req.UseSystemCACerts
}
x := map[string]interface{}{
"eap": []string{"peap"},
"phase2-auth": "mschapv2",
"system-ca-certs": false,
"eap": []string{eapMethod},
"system-ca-certs": useSystemCACerts,
"password-flags": uint32(0),
}
switch eapMethod {
case "peap", "ttls":
x["phase2-auth"] = phase2Auth
case "tls":
if req.ClientCertPath != "" {
x["client-cert"] = []byte("file://" + req.ClientCertPath)
}
if req.PrivateKeyPath != "" {
x["private-key"] = []byte("file://" + req.PrivateKeyPath)
}
}
if req.Username != "" {
x["identity"] = req.Username
}
if req.Password != "" {
x["password"] = req.Password
}
if req.AnonymousIdentity != "" {
x["anonymous-identity"] = req.AnonymousIdentity
}
if req.DomainSuffixMatch != "" {
x["domain-suffix-match"] = req.DomainSuffixMatch
}
if req.CACertPath != "" {
x["ca-cert"] = []byte("file://" + req.CACertPath)
}
settings["802-1x"] = x
log.Infof("[createAndConnectWiFi] WPA-EAP settings: eap=peap, phase2-auth=mschapv2, identity=%s, interactive=%v, system-ca-certs=%v, domain-suffix-match=%q",
req.Username, req.Interactive, x["system-ca-certs"], req.DomainSuffixMatch)
log.Infof("[createAndConnectWiFi] WPA-EAP settings: eap=%s, phase2-auth=%s, identity=%s, interactive=%v, system-ca-certs=%v, domain-suffix-match=%q",
eapMethod, phase2Auth, req.Username, req.Interactive, useSystemCACerts, req.DomainSuffixMatch)
case isPsk:
sec := map[string]interface{}{

View File

@@ -70,6 +70,18 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
handleDisconnectAllVPN(conn, req, manager)
case "network.vpn.clearCredentials":
handleClearVPNCredentials(conn, req, manager)
case "network.vpn.plugins":
handleListVPNPlugins(conn, req, manager)
case "network.vpn.import":
handleImportVPN(conn, req, manager)
case "network.vpn.getConfig":
handleGetVPNConfig(conn, req, manager)
case "network.vpn.updateConfig":
handleUpdateVPNConfig(conn, req, manager)
case "network.vpn.delete":
handleDeleteVPN(conn, req, manager)
case "network.vpn.setCredentials":
handleSetVPNCredentials(conn, req, manager)
case "network.wifi.setAutoconnect":
handleSetWiFiAutoconnect(conn, req, manager)
default:
@@ -200,6 +212,24 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
if domainSuffixMatch, ok := req.Params["domainSuffixMatch"].(string); ok {
connReq.DomainSuffixMatch = domainSuffixMatch
}
if eapMethod, ok := req.Params["eapMethod"].(string); ok {
connReq.EAPMethod = eapMethod
}
if phase2Auth, ok := req.Params["phase2Auth"].(string); ok {
connReq.Phase2Auth = phase2Auth
}
if caCertPath, ok := req.Params["caCertPath"].(string); ok {
connReq.CACertPath = caCertPath
}
if clientCertPath, ok := req.Params["clientCertPath"].(string); ok {
connReq.ClientCertPath = clientCertPath
}
if privateKeyPath, ok := req.Params["privateKeyPath"].(string); ok {
connReq.PrivateKeyPath = privateKeyPath
}
if useSystemCACerts, ok := req.Params["useSystemCACerts"].(bool); ok {
connReq.UseSystemCACerts = &useSystemCACerts
}
if err := manager.ConnectWiFi(connReq); err != nil {
models.RespondError(conn, req.ID, err.Error())
@@ -287,7 +317,14 @@ func handleConnectEthernet(conn net.Conn, req Request, manager *Manager) {
}
func handleDisconnectEthernet(conn net.Conn, req Request, manager *Manager) {
if err := manager.DisconnectEthernet(); err != nil {
device, _ := req.Params["device"].(string)
var err error
if device != "" {
err = manager.DisconnectEthernetDevice(device)
} else {
err = manager.DisconnectEthernet()
}
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
@@ -502,3 +539,138 @@ func handleSetWiFiAutoconnect(conn net.Conn, req Request, manager *Manager) {
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "autoconnect updated"})
}
func handleListVPNPlugins(conn net.Conn, req Request, manager *Manager) {
plugins, err := manager.ListVPNPlugins()
if err != nil {
log.Warnf("handleListVPNPlugins: failed to list plugins: %v", err)
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list VPN plugins: %v", err))
return
}
models.Respond(conn, req.ID, plugins)
}
func handleImportVPN(conn net.Conn, req Request, manager *Manager) {
filePath, ok := req.Params["file"].(string)
if !ok {
filePath, ok = req.Params["path"].(string)
}
if !ok {
models.RespondError(conn, req.ID, "missing 'file' or 'path' parameter")
return
}
name, _ := req.Params["name"].(string)
result, err := manager.ImportVPN(filePath, name)
if err != nil {
log.Warnf("handleImportVPN: failed to import: %v", err)
models.RespondError(conn, req.ID, fmt.Sprintf("failed to import VPN: %v", err))
return
}
models.Respond(conn, req.ID, result)
}
func handleGetVPNConfig(conn net.Conn, req Request, manager *Manager) {
uuidOrName, ok := req.Params["uuid"].(string)
if !ok {
uuidOrName, ok = req.Params["name"].(string)
}
if !ok {
uuidOrName, ok = req.Params["uuidOrName"].(string)
}
if !ok {
models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter")
return
}
config, err := manager.GetVPNConfig(uuidOrName)
if err != nil {
log.Warnf("handleGetVPNConfig: failed to get config: %v", err)
models.RespondError(conn, req.ID, fmt.Sprintf("failed to get VPN config: %v", err))
return
}
models.Respond(conn, req.ID, config)
}
func handleUpdateVPNConfig(conn net.Conn, req Request, manager *Manager) {
connUUID, ok := req.Params["uuid"].(string)
if !ok {
models.RespondError(conn, req.ID, "missing 'uuid' parameter")
return
}
updates := make(map[string]interface{})
if name, ok := req.Params["name"].(string); ok {
updates["name"] = name
}
if autoconnect, ok := req.Params["autoconnect"].(bool); ok {
updates["autoconnect"] = autoconnect
}
if data, ok := req.Params["data"].(map[string]interface{}); ok {
updates["data"] = data
}
if len(updates) == 0 {
models.RespondError(conn, req.ID, "no updates provided")
return
}
if err := manager.UpdateVPNConfig(connUUID, updates); err != nil {
log.Warnf("handleUpdateVPNConfig: failed to update: %v", err)
models.RespondError(conn, req.ID, fmt.Sprintf("failed to update VPN config: %v", err))
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN config updated"})
}
func handleDeleteVPN(conn net.Conn, req Request, manager *Manager) {
uuidOrName, ok := req.Params["uuid"].(string)
if !ok {
uuidOrName, ok = req.Params["name"].(string)
}
if !ok {
uuidOrName, ok = req.Params["uuidOrName"].(string)
}
if !ok {
models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter")
return
}
if err := manager.DeleteVPN(uuidOrName); err != nil {
log.Warnf("handleDeleteVPN: failed to delete: %v", err)
models.RespondError(conn, req.ID, fmt.Sprintf("failed to delete VPN: %v", err))
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN deleted"})
}
func handleSetVPNCredentials(conn net.Conn, req Request, manager *Manager) {
connUUID, ok := req.Params["uuid"].(string)
if !ok {
models.RespondError(conn, req.ID, "missing 'uuid' parameter")
return
}
username, _ := req.Params["username"].(string)
password, _ := req.Params["password"].(string)
save := true
if saveParam, ok := req.Params["save"].(bool); ok {
save = saveParam
}
if err := manager.SetVPNCredentials(connUUID, username, password, save); err != nil {
log.Warnf("handleSetVPNCredentials: failed to set credentials: %v", err)
models.RespondError(conn, req.ID, fmt.Sprintf("failed to set VPN credentials: %v", err))
return
}
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN credentials set"})
}

View File

@@ -109,6 +109,7 @@ func (m *Manager) syncStateFromBackend() error {
m.state.EthernetDevice = backendState.EthernetDevice
m.state.EthernetConnected = backendState.EthernetConnected
m.state.EthernetConnectionUuid = backendState.EthernetConnectionUuid
m.state.EthernetDevices = backendState.EthernetDevices
m.state.WiFiIP = backendState.WiFiIP
m.state.WiFiDevice = backendState.WiFiDevice
m.state.WiFiConnected = backendState.WiFiConnected
@@ -155,6 +156,7 @@ func (m *Manager) snapshotState() NetworkState {
s.WiFiNetworks = append([]WiFiNetwork(nil), m.state.WiFiNetworks...)
s.WiFiDevices = append([]WiFiDevice(nil), m.state.WiFiDevices...)
s.WiredConnections = append([]WiredConnection(nil), m.state.WiredConnections...)
s.EthernetDevices = append([]EthernetDevice(nil), m.state.EthernetDevices...)
s.VPNProfiles = append([]VPNProfile(nil), m.state.VPNProfiles...)
s.VPNActive = append([]VPNActive(nil), m.state.VPNActive...)
return s
@@ -213,6 +215,9 @@ func stateChangedMeaningfully(old, new *NetworkState) bool {
if len(old.WiredConnections) != len(new.WiredConnections) {
return true
}
if len(old.EthernetDevices) != len(new.EthernetDevices) {
return true
}
for i := range old.WiFiNetworks {
oldNet := &old.WiFiNetworks[i]
@@ -242,6 +247,23 @@ func stateChangedMeaningfully(old, new *NetworkState) bool {
}
}
for i := range old.EthernetDevices {
oldDev := &old.EthernetDevices[i]
newDev := &new.EthernetDevices[i]
if oldDev.Name != newDev.Name {
return true
}
if oldDev.Connected != newDev.Connected {
return true
}
if oldDev.State != newDev.State {
return true
}
if oldDev.IP != newDev.IP {
return true
}
}
// Check VPN profiles count
if len(old.VPNProfiles) != len(new.VPNProfiles) {
return true
@@ -480,6 +502,18 @@ func (m *Manager) DisconnectEthernet() error {
return m.backend.DisconnectEthernet()
}
func (m *Manager) DisconnectEthernetDevice(device string) error {
return m.backend.DisconnectEthernetDevice(device)
}
func (m *Manager) GetEthernetDevices() []EthernetDevice {
m.stateMutex.RLock()
defer m.stateMutex.RUnlock()
devices := make([]EthernetDevice, len(m.state.EthernetDevices))
copy(devices, m.state.EthernetDevices)
return devices
}
func (m *Manager) activateConnection(uuid string) error {
return m.backend.ActivateWiredConnection(uuid)
}
@@ -508,6 +542,30 @@ func (m *Manager) ClearVPNCredentials(uuidOrName string) error {
return m.backend.ClearVPNCredentials(uuidOrName)
}
func (m *Manager) ListVPNPlugins() ([]VPNPlugin, error) {
return m.backend.ListVPNPlugins()
}
func (m *Manager) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
return m.backend.ImportVPN(filePath, name)
}
func (m *Manager) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
return m.backend.GetVPNConfig(uuidOrName)
}
func (m *Manager) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
return m.backend.UpdateVPNConfig(uuid, updates)
}
func (m *Manager) DeleteVPN(uuidOrName string) error {
return m.backend.DeleteVPN(uuidOrName)
}
func (m *Manager) SetVPNCredentials(uuid, username, password string, save bool) error {
return m.backend.SetVPNCredentials(uuid, username, password, save)
}
func (m *Manager) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
return m.backend.SetWiFiAutoconnect(ssid, autoconnect)
}

View File

@@ -49,6 +49,7 @@ func (b *SubscriptionBroker) Ask(ctx context.Context, req PromptRequest) (string
VpnService: req.VpnService,
Setting: req.SettingName,
Fields: req.Fields,
FieldsInfo: req.FieldsInfo,
Hints: req.Hints,
Reason: req.Reason,
ConnectionId: req.ConnectionId,

View File

@@ -52,11 +52,25 @@ type WiFiDevice struct {
Networks []WiFiNetwork `json:"networks"`
}
type EthernetDevice struct {
Name string `json:"name"`
HwAddress string `json:"hwAddress"`
State string `json:"state"`
Connected bool `json:"connected"`
IP string `json:"ip,omitempty"`
Speed uint32 `json:"speed,omitempty"`
Driver string `json:"driver,omitempty"`
}
type VPNProfile struct {
Name string `json:"name"`
UUID string `json:"uuid"`
Type string `json:"type"`
ServiceType string `json:"serviceType"`
RemoteHost string `json:"remoteHost,omitempty"`
Username string `json:"username,omitempty"`
Autoconnect bool `json:"autoconnect"`
Data map[string]string `json:"data,omitempty"`
}
type VPNActive struct {
@@ -66,6 +80,12 @@ type VPNActive struct {
State string `json:"state,omitempty"`
Type string `json:"type"`
Plugin string `json:"serviceType"`
IP string `json:"ip,omitempty"`
Gateway string `json:"gateway,omitempty"`
RemoteHost string `json:"remoteHost,omitempty"`
Username string `json:"username,omitempty"`
MTU uint32 `json:"mtu,omitempty"`
Data map[string]string `json:"data,omitempty"`
}
type VPNState struct {
@@ -81,6 +101,7 @@ type NetworkState struct {
EthernetDevice string `json:"ethernetDevice"`
EthernetConnected bool `json:"ethernetConnected"`
EthernetConnectionUuid string `json:"ethernetConnectionUuid"`
EthernetDevices []EthernetDevice `json:"ethernetDevices"`
WiFiIP string `json:"wifiIP"`
WiFiDevice string `json:"wifiDevice"`
WiFiConnected bool `json:"wifiConnected"`
@@ -107,6 +128,12 @@ type ConnectionRequest struct {
DomainSuffixMatch string `json:"domainSuffixMatch,omitempty"`
Interactive bool `json:"interactive,omitempty"`
Device string `json:"device,omitempty"`
EAPMethod string `json:"eapMethod,omitempty"`
Phase2Auth string `json:"phase2Auth,omitempty"`
CACertPath string `json:"caCertPath,omitempty"`
ClientCertPath string `json:"clientCertPath,omitempty"`
PrivateKeyPath string `json:"privateKeyPath,omitempty"`
UseSystemCACerts *bool `json:"useSystemCACerts,omitempty"`
}
type WiredConnection struct {
@@ -156,6 +183,7 @@ type PromptRequest struct {
VpnService string `json:"vpnService"`
SettingName string `json:"setting"`
Fields []string `json:"fields"`
FieldsInfo []FieldInfo `json:"fieldsInfo"`
Hints []string `json:"hints"`
Reason string `json:"reason"`
ConnectionId string `json:"connectionId"`
@@ -169,6 +197,12 @@ type PromptReply struct {
Cancel bool `json:"cancel"`
}
type FieldInfo struct {
Name string `json:"name"`
Label string `json:"label"`
IsSecret bool `json:"isSecret"`
}
type CredentialPrompt struct {
Token string `json:"token"`
Name string `json:"name"`
@@ -177,6 +211,7 @@ type CredentialPrompt struct {
VpnService string `json:"vpnService"`
Setting string `json:"setting"`
Fields []string `json:"fields"`
FieldsInfo []FieldInfo `json:"fieldsInfo"`
Hints []string `json:"hints"`
Reason string `json:"reason"`
ConnectionId string `json:"connectionId"`
@@ -203,3 +238,28 @@ type WiredIPConfig struct {
Gateway string `json:"gateway"`
DNS string `json:"dns"`
}
type VPNPlugin struct {
Name string `json:"name"`
ServiceType string `json:"serviceType"`
Program string `json:"program,omitempty"`
Supports []string `json:"supports,omitempty"`
FileExtensions []string `json:"fileExtensions"`
}
type VPNConfig struct {
UUID string `json:"uuid"`
Name string `json:"name"`
Type string `json:"type"`
ServiceType string `json:"serviceType,omitempty"`
Autoconnect bool `json:"autoconnect"`
Data map[string]string `json:"data,omitempty"`
}
type VPNImportResult struct {
Success bool `json:"success"`
UUID string `json:"uuid,omitempty"`
Name string `json:"name,omitempty"`
ServiceType string `json:"serviceType,omitempty"`
Error string `json:"error,omitempty"`
}

View File

@@ -21,3 +21,31 @@ func TestManager_GetWiredConfigs(t *testing.T) {
assert.Len(t, configs, 1)
assert.Equal(t, "Test", configs[0].ID)
}
func TestManager_GetEthernetDevices(t *testing.T) {
manager := &Manager{
state: &NetworkState{
EthernetDevices: []EthernetDevice{
{Name: "enp0s3", Connected: true, IP: "192.168.1.100"},
{Name: "enp0s8", Connected: false},
},
},
}
devices := manager.GetEthernetDevices()
assert.Len(t, devices, 2)
assert.Equal(t, "enp0s3", devices[0].Name)
assert.True(t, devices[0].Connected)
assert.Equal(t, "enp0s8", devices[1].Name)
assert.False(t, devices[1].Connected)
}
func TestManager_GetEthernetDevices_Empty(t *testing.T) {
manager := &Manager{
state: &NetworkState{},
}
devices := manager.GetEthernetDevices()
assert.Empty(t, devices)
}

View File

@@ -31,7 +31,7 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
)
const APIVersion = 20
const APIVersion = 21
type Capabilities struct {
Capabilities []string `json:"capabilities"`
@@ -1073,7 +1073,7 @@ func Start(printDocs bool) error {
log.Info(" network.getState - Get current network state")
log.Info(" network.wifi.scan - Scan for WiFi networks (params: device?)")
log.Info(" network.wifi.networks - Get WiFi network list")
log.Info(" network.wifi.connect - Connect to WiFi (params: ssid, password?, username?, device?)")
log.Info(" network.wifi.connect - Connect to WiFi (params: ssid, password?, username?, device?, eapMethod?, phase2Auth?, caCertPath?, clientCertPath?, privateKeyPath?, useSystemCACerts?)")
log.Info(" network.wifi.disconnect - Disconnect WiFi (params: device?)")
log.Info(" network.wifi.forget - Forget network (params: ssid)")
log.Info(" network.wifi.toggle - Toggle WiFi radio")
@@ -1089,6 +1089,11 @@ func Start(printDocs bool) error {
log.Info(" network.vpn.disconnect - Disconnect VPN (params: uuidOrName|name|uuid)")
log.Info(" network.vpn.disconnectAll - Disconnect all VPNs")
log.Info(" network.vpn.clearCredentials - Clear saved VPN credentials (params: uuidOrName|name|uuid)")
log.Info(" network.vpn.plugins - List available VPN plugins")
log.Info(" network.vpn.import - Import VPN from file (params: file|path, name?)")
log.Info(" network.vpn.getConfig - Get VPN configuration (params: uuid|name|uuidOrName)")
log.Info(" network.vpn.updateConfig - Update VPN configuration (params: uuid, name?, autoconnect?, data?)")
log.Info(" network.vpn.delete - Delete VPN connection (params: uuid|name|uuidOrName)")
log.Info(" network.preference.set - Set preference (params: preference [auto|wifi|ethernet])")
log.Info(" network.info - Get network info (params: ssid)")
log.Info(" network.credentials.submit - Submit credentials for prompt (params: token, secrets, save?)")

View File

@@ -3,7 +3,6 @@ import Quickshell
import qs.Common
import qs.Modals
import qs.Modals.Clipboard
import qs.Modals.Common
import qs.Modals.Settings
import qs.Modals.Spotlight
import qs.Modules
@@ -253,6 +252,10 @@ Item {
PolkitAuthModal {
id: polkitAuthModal
Component.onCompleted: {
PopoutService.polkitAuthModal = polkitAuthModal;
}
}
BluetoothPairingModal {
@@ -269,21 +272,21 @@ Item {
Connections {
target: NetworkService
function onCredentialsNeeded(token, ssid, setting, fields, hints, reason, connType, connName, vpnService) {
function onCredentialsNeeded(token, ssid, setting, fields, hints, reason, connType, connName, vpnService, fieldsInfo) {
const now = Date.now();
const timeSinceLastPrompt = now - lastCredentialsTime;
if (wifiPasswordModal.shouldBeVisible && timeSinceLastPrompt < 1000) {
if (wifiPasswordModal.visible && timeSinceLastPrompt < 1000) {
NetworkService.cancelCredentials(lastCredentialsToken);
lastCredentialsToken = token;
lastCredentialsTime = now;
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService);
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService, fieldsInfo);
return;
}
lastCredentialsToken = token;
lastCredentialsTime = now;
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService);
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService, fieldsInfo);
}
}

View File

@@ -1,69 +1,60 @@
import QtQuick
import Quickshell.Hyprland
import Quickshell
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
FloatingWindow {
id: root
layerNamespace: "dms:polkit"
HyprlandFocusGrab {
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}
property string passwordInput: ""
property var currentFlow: PolkitService.agent?.flow
property bool isLoading: false
property real minHeight: 240
property int calculatedHeight: Math.max(240, headerRow.implicitHeight + mainColumn.implicitHeight + Theme.spacingM * 3)
function focusPasswordField() {
passwordField.forceActiveFocus();
}
function show() {
passwordInput = "";
isLoading = false;
open();
Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.passwordField) {
contentLoader.item.passwordField.forceActiveFocus();
}
});
visible = true;
Qt.callLater(focusPasswordField);
}
shouldBeVisible: false
modalWidth: 420
modalHeight: Math.max(minHeight, contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 240)
Connections {
target: contentLoader.item
function onImplicitHeightChanged() {
if (shouldBeVisible && contentLoader.item) {
const newHeight = contentLoader.item.implicitHeight + Theme.spacingM * 2;
if (newHeight > minHeight) {
minHeight = newHeight;
}
}
}
function hide() {
visible = false;
}
onOpened: {
Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.passwordField) {
contentLoader.item.passwordField.forceActiveFocus();
}
});
}
onDialogClosed: {
function submitAuth() {
if (passwordInput.length === 0 || !currentFlow || isLoading)
return;
isLoading = true;
currentFlow.submit(passwordInput);
passwordInput = "";
isLoading = false;
}
onBackgroundClicked: () => {
if (currentFlow && !isLoading) {
function cancelAuth() {
if (!currentFlow || isLoading)
return;
currentFlow.cancelAuthenticationRequest();
}
objectName: "polkitAuthModal"
title: I18n.tr("Authentication")
minimumSize: Qt.size(420, calculatedHeight)
maximumSize: Qt.size(420, calculatedHeight)
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
visible: false
onVisibleChanged: {
if (visible) {
Qt.callLater(focusPasswordField);
return;
}
passwordInput = "";
isLoading = false;
}
Connections {
@@ -75,9 +66,8 @@ DankModal {
}
function onIsActiveChanged() {
if (!(PolkitService.agent?.isActive ?? false)) {
close();
}
if (!(PolkitService.agent?.isActive ?? false))
hide();
}
}
@@ -86,17 +76,15 @@ DankModal {
enabled: currentFlow !== null
function onIsResponseRequiredChanged() {
if (currentFlow.isResponseRequired) {
if (!currentFlow.isResponseRequired)
return;
isLoading = false;
passwordInput = "";
if (contentLoader.item && contentLoader.item.passwordField) {
contentLoader.item.passwordField.forceActiveFocus();
}
}
passwordField.forceActiveFocus();
}
function onAuthenticationSucceeded() {
close();
hide();
}
function onAuthenticationFailed() {
@@ -104,24 +92,18 @@ DankModal {
}
function onAuthenticationRequestCancelled() {
close();
hide();
}
}
content: Component {
FocusScope {
id: authContent
property alias passwordField: passwordField
id: contentFocusScope
anchors.fill: parent
focus: true
implicitHeight: headerRow.implicitHeight + mainColumn.implicitHeight + Theme.spacingM
Keys.onEscapePressed: event => {
if (currentFlow && !isLoading) {
currentFlow.cancelAuthenticationRequest();
}
cancelAuth();
event.accepted = true;
}
@@ -175,11 +157,7 @@ DankModal {
iconColor: Theme.surfaceText
enabled: !isLoading
opacity: enabled ? 1 : 0.5
onClicked: () => {
if (currentFlow) {
currentFlow.cancelAuthenticationRequest();
}
}
onClicked: cancelAuth()
}
}
@@ -213,9 +191,7 @@ DankModal {
MouseArea {
anchors.fill: parent
enabled: !isLoading
onClicked: () => {
passwordField.forceActiveFocus();
}
onClicked: passwordField.forceActiveFocus()
}
DankTextField {
@@ -229,16 +205,8 @@ DankModal {
placeholderText: ""
backgroundColor: "transparent"
enabled: !isLoading
onTextEdited: () => {
passwordInput = text;
}
onAccepted: () => {
if (passwordInput.length > 0 && currentFlow && !isLoading) {
isLoading = true;
currentFlow.submit(passwordInput);
passwordInput = "";
}
}
onTextEdited: passwordInput = text
onAccepted: submitAuth()
}
}
@@ -292,7 +260,6 @@ DankModal {
StyledText {
id: cancelText
anchors.centerIn: parent
text: I18n.tr("Cancel")
font.pixelSize: Theme.fontSizeMedium
@@ -302,16 +269,11 @@ DankModal {
MouseArea {
id: cancelArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: () => {
if (currentFlow) {
currentFlow.cancelAuthenticationRequest();
}
}
onClicked: cancelAuth()
}
}
@@ -325,7 +287,6 @@ DankModal {
StyledText {
id: authText
anchors.centerIn: parent
text: I18n.tr("Authenticate")
font.pixelSize: Theme.fontSizeMedium
@@ -335,18 +296,11 @@ DankModal {
MouseArea {
id: authArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: () => {
if (currentFlow && !isLoading) {
isLoading = true;
currentFlow.submit(passwordInput);
passwordInput = "";
}
}
onClicked: submitAuth()
}
Behavior on color {
@@ -361,4 +315,3 @@ DankModal {
}
}
}
}

View File

@@ -129,13 +129,30 @@ FocusScope {
}
Loader {
id: launcherLoader
id: networkLoader
anchors.fill: parent
active: root.currentIndex === 6
visible: active
focus: active
sourceComponent: NetworkTab {}
onActiveChanged: {
if (active && item) {
Qt.callLater(() => item.forceActiveFocus());
}
}
}
Loader {
id: launcherLoader
anchors.fill: parent
active: root.currentIndex === 7
visible: active
focus: active
sourceComponent: LauncherTab {}
onActiveChanged: {
@@ -149,7 +166,7 @@ FocusScope {
id: themeColorsLoader
anchors.fill: parent
active: root.currentIndex === 7
active: root.currentIndex === 8
visible: active
focus: active
@@ -166,7 +183,7 @@ FocusScope {
id: powerLoader
anchors.fill: parent
active: root.currentIndex === 8
active: root.currentIndex === 9
visible: active
focus: active
@@ -183,7 +200,7 @@ FocusScope {
id: pluginsLoader
anchors.fill: parent
active: root.currentIndex === 9
active: root.currentIndex === 10
visible: active
focus: active
@@ -202,7 +219,7 @@ FocusScope {
id: aboutLoader
anchors.fill: parent
active: root.currentIndex === 10
active: root.currentIndex === 11
visible: active
focus: active

View File

@@ -112,7 +112,7 @@ FloatingWindow {
focus: true
Keys.onPressed: event => {
const tabCount = 11;
const tabCount = 12;
if (event.key === Qt.Key_Escape) {
hide();
event.accepted = true;

View File

@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import qs.Modals.Settings
import qs.Services
import qs.Widgets
Rectangle {
@@ -10,59 +11,81 @@ Rectangle {
property int currentIndex: 0
property var parentModal: null
readonly property var sidebarItems: [
readonly property var allSidebarItems: [
{
"text": I18n.tr("Personalization"),
"icon": "person"
"icon": "person",
"tabIndex": 0
},
{
"text": I18n.tr("Time & Weather"),
"icon": "schedule"
"icon": "schedule",
"tabIndex": 1
},
{
"text": I18n.tr("Dank Bar"),
"icon": "toolbar"
"icon": "toolbar",
"tabIndex": 2
},
{
"text": I18n.tr("Widgets"),
"icon": "widgets"
"icon": "widgets",
"tabIndex": 3
},
{
"text": I18n.tr("Dock"),
"icon": "dock_to_bottom"
"icon": "dock_to_bottom",
"tabIndex": 4
},
{
"text": I18n.tr("Displays"),
"icon": "monitor"
"icon": "monitor",
"tabIndex": 5
},
{
"text": I18n.tr("Network"),
"icon": "wifi",
"dmsOnly": true,
"tabIndex": 6
},
{
"text": I18n.tr("Launcher"),
"icon": "apps"
"icon": "apps",
"tabIndex": 7
},
{
"text": I18n.tr("Theme & Colors"),
"icon": "palette"
"icon": "palette",
"tabIndex": 8
},
{
"text": I18n.tr("Power & Security"),
"icon": "power"
"icon": "power",
"tabIndex": 9
},
{
"text": I18n.tr("Plugins"),
"icon": "extension"
"icon": "extension",
"tabIndex": 10
},
{
"text": I18n.tr("About"),
"icon": "info"
"icon": "info",
"tabIndex": 11
}
]
readonly property var sidebarItems: allSidebarItems.filter(item => !item.dmsOnly || !NetworkService.usingLegacy)
function navigateNext() {
currentIndex = (currentIndex + 1) % sidebarItems.length;
const currentItemIndex = sidebarItems.findIndex(item => item.tabIndex === currentIndex);
const nextIndex = (currentItemIndex + 1) % sidebarItems.length;
currentIndex = sidebarItems[nextIndex].tabIndex;
}
function navigatePrevious() {
currentIndex = (currentIndex - 1 + sidebarItems.length) % sidebarItems.length;
const currentItemIndex = sidebarItems.findIndex(item => item.tabIndex === currentIndex);
const prevIndex = (currentItemIndex - 1 + sidebarItems.length) % sidebarItems.length;
currentIndex = sidebarItems[prevIndex].tabIndex;
}
width: 270
@@ -111,7 +134,7 @@ Rectangle {
required property int index
required property var modelData
property bool isActive: sidebarContainer.currentIndex === index
property bool isActive: sidebarContainer.currentIndex === modelData.tabIndex
width: parent.width - parent.leftPadding - parent.rightPadding
height: 44
@@ -147,7 +170,7 @@ Rectangle {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
sidebarContainer.currentIndex = index;
sidebarContainer.currentIndex = modelData.tabIndex;
}
}

View File

@@ -1,21 +1,12 @@
import QtQuick
import Quickshell.Hyprland
import Quickshell
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
FloatingWindow {
id: root
layerNamespace: "dms:wifi-password"
keepPopoutsOpen: true
HyprlandFocusGrab {
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}
property string wifiPasswordSSID: ""
property string wifiPasswordInput: ""
property string wifiUsernameInput: ""
@@ -34,6 +25,34 @@ DankModal {
property string connectionName: ""
property string vpnServiceType: ""
property string connectionType: ""
property var fieldsInfo: []
property var secretValues: ({})
property int calculatedHeight: {
if (fieldsInfo.length > 0)
return 180 + (fieldsInfo.length * 60);
if (requiresEnterprise)
return 430;
if (isVpnPrompt)
return 260;
return 230;
}
function focusFirstField() {
if (fieldsInfo.length > 0) {
if (dynamicFieldsRepeater.count > 0) {
const firstItem = dynamicFieldsRepeater.itemAt(0);
if (firstItem)
firstItem.children[0].forceActiveFocus();
}
return;
}
if (requiresEnterprise && !isVpnPrompt) {
usernameInput.forceActiveFocus();
return;
}
passwordInput.forceActiveFocus();
}
function show(ssid) {
wifiPasswordSSID = ssid;
@@ -50,23 +69,17 @@ DankModal {
connectionName = "";
vpnServiceType = "";
connectionType = "";
fieldsInfo = [];
secretValues = {};
const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid);
requiresEnterprise = network?.enterprise || false;
open();
Qt.callLater(() => {
if (contentLoader.item) {
if (requiresEnterprise && contentLoader.item.usernameInput) {
contentLoader.item.usernameInput.forceActiveFocus();
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus();
}
}
});
visible = true;
Qt.callLater(focusFirstField);
}
function showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService) {
function showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService, fInfo) {
isPromptMode = true;
promptToken = token;
promptReason = reason;
@@ -75,129 +88,172 @@ DankModal {
connectionType = connType || "802-11-wireless";
connectionName = connName || ssid || "";
vpnServiceType = vpnService || "";
fieldsInfo = fInfo || [];
secretValues = {};
isVpnPrompt = (connectionType === "vpn" || connectionType === "wireguard");
wifiPasswordSSID = isVpnPrompt ? connectionName : ssid;
requiresEnterprise = setting === "802-1x";
if (reason === "wrong-password") {
wifiPasswordInput = "";
wifiUsernameInput = "";
} else {
wifiPasswordInput = "";
wifiUsernameInput = "";
wifiAnonymousIdentityInput = "";
wifiDomainInput = "";
}
open();
visible = true;
Qt.callLater(() => {
if (contentLoader.item) {
if (reason === "wrong-password" && contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.text = "";
contentLoader.item.passwordInput.forceActiveFocus();
} else if (requiresEnterprise && contentLoader.item.usernameInput) {
contentLoader.item.usernameInput.forceActiveFocus();
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus();
}
if (reason === "wrong-password" && fieldsInfo.length === 0) {
passwordInput.text = "";
}
focusFirstField();
});
}
shouldBeVisible: false
modalWidth: 420
modalHeight: {
if (requiresEnterprise)
return 430;
if (isVpnPrompt)
return 260;
return 230;
function hide() {
visible = false;
}
onShouldBeVisibleChanged: () => {
if (!shouldBeVisible) {
wifiPasswordInput = "";
wifiUsernameInput = "";
wifiAnonymousIdentityInput = "";
wifiDomainInput = "";
function getFieldLabel(fieldName) {
switch (fieldName) {
case "username":
case "identity":
return I18n.tr("Username");
case "password":
return I18n.tr("Password");
case "cert-pass":
case "certpass":
return I18n.tr("Certificate Password");
case "private-key-password":
return I18n.tr("Private Key Password");
case "pin":
return I18n.tr("PIN");
case "psk":
return I18n.tr("Password");
case "anonymous-identity":
return I18n.tr("Anonymous Identity");
default:
return fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace(/-/g, " ");
}
}
onOpened: {
Qt.callLater(() => {
if (contentLoader.item) {
if (requiresEnterprise && contentLoader.item.usernameInput) {
contentLoader.item.usernameInput.forceActiveFocus();
} else if (contentLoader.item.passwordInput) {
contentLoader.item.passwordInput.forceActiveFocus();
function submitCredentialsAndClose() {
if (fieldsInfo.length > 0) {
NetworkService.submitCredentials(promptToken, secretValues, savePasswordCheckbox.checked);
hide();
secretValues = {};
return;
}
}
});
}
onBackgroundClicked: () => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken);
const secrets = {};
if (isVpnPrompt) {
if (passwordInput.text)
secrets["password"] = passwordInput.text;
} else if (promptSetting === "802-11-wireless-security") {
secrets["psk"] = passwordInput.text;
} else if (promptSetting === "802-1x") {
if (usernameInput.text)
secrets["identity"] = usernameInput.text;
if (passwordInput.text)
secrets["password"] = passwordInput.text;
if (wifiAnonymousIdentityInput)
secrets["anonymous-identity"] = wifiAnonymousIdentityInput;
}
close();
NetworkService.submitCredentials(promptToken, secrets, savePasswordCheckbox.checked);
} else {
const username = requiresEnterprise ? usernameInput.text : "";
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text, username, wifiAnonymousIdentityInput, wifiDomainInput);
}
hide();
wifiPasswordInput = "";
wifiUsernameInput = "";
wifiAnonymousIdentityInput = "";
wifiDomainInput = "";
passwordInput.text = "";
if (requiresEnterprise)
usernameInput.text = "";
}
function clearAndClose() {
if (isPromptMode)
NetworkService.cancelCredentials(promptToken);
hide();
wifiPasswordInput = "";
wifiUsernameInput = "";
wifiAnonymousIdentityInput = "";
wifiDomainInput = "";
secretValues = {};
}
objectName: "wifiPasswordModal"
title: isVpnPrompt ? I18n.tr("VPN Password") : I18n.tr("Wi-Fi Password")
minimumSize: Qt.size(420, calculatedHeight)
maximumSize: Qt.size(420, calculatedHeight)
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
visible: false
onVisibleChanged: {
if (visible) {
Qt.callLater(focusFirstField);
return;
}
wifiPasswordInput = "";
wifiUsernameInput = "";
wifiAnonymousIdentityInput = "";
wifiDomainInput = "";
secretValues = {};
passwordInput.text = "";
usernameInput.text = "";
anonInput.text = "";
domainMatchInput.text = "";
for (let i = 0; i < dynamicFieldsRepeater.count; i++) {
const item = dynamicFieldsRepeater.itemAt(i);
if (item?.children[0])
item.children[0].text = "";
}
}
Connections {
target: NetworkService
function onPasswordDialogShouldReopenChanged() {
if (NetworkService.passwordDialogShouldReopen && NetworkService.connectingSSID !== "") {
if (!NetworkService.passwordDialogShouldReopen || NetworkService.connectingSSID === "")
return;
wifiPasswordSSID = NetworkService.connectingSSID;
wifiPasswordInput = "";
open();
visible = true;
NetworkService.passwordDialogShouldReopen = false;
}
}
}
content: Component {
FocusScope {
id: wifiContent
property alias usernameInput: usernameInput
property alias passwordInput: passwordInput
id: contentFocusScope
anchors.fill: parent
focus: true
Keys.onEscapePressed: event => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken);
}
close();
wifiPasswordInput = "";
wifiUsernameInput = "";
wifiAnonymousIdentityInput = "";
wifiDomainInput = "";
clearAndClose();
event.accepted = true;
}
Column {
id: contentCol
anchors.centerIn: parent
width: parent.width - Theme.spacingM * 2
spacing: Theme.spacingM
Row {
width: parent.width
width: contentCol.width
Column {
width: parent.width - 40
spacing: Theme.spacingXS
StyledText {
text: {
if (isVpnPrompt) {
return I18n.tr("Connect to VPN");
}
return I18n.tr("Connect to Wi-Fi");
}
text: isVpnPrompt ? I18n.tr("Connect to VPN") : I18n.tr("Connect to Wi-Fi")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
@@ -209,9 +265,10 @@ DankModal {
StyledText {
text: {
if (isVpnPrompt) {
if (fieldsInfo.length > 0)
return I18n.tr("Enter credentials for ") + wifiPasswordSSID;
if (isVpnPrompt)
return I18n.tr("Enter password for ") + wifiPasswordSSID;
}
const prefix = requiresEnterprise ? I18n.tr("Enter credentials for ") : I18n.tr("Enter password for ");
return prefix + wifiPasswordSSID;
}
@@ -235,15 +292,76 @@ DankModal {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: () => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken);
onClicked: clearAndClose()
}
}
Repeater {
id: dynamicFieldsRepeater
model: fieldsInfo
delegate: Rectangle {
required property var modelData
required property int index
width: contentCol.width
height: 50
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: fieldInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: fieldInput.activeFocus ? 2 : 1
DankTextField {
id: fieldInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
echoMode: modelData.isSecret ? TextInput.Password : TextInput.Normal
placeholderText: getFieldLabel(modelData.name)
backgroundColor: "transparent"
enabled: root.visible
Keys.onTabPressed: event => {
if (index < fieldsInfo.length - 1) {
const nextItem = dynamicFieldsRepeater.itemAt(index + 1);
if (nextItem)
nextItem.children[0].forceActiveFocus();
} else {
const firstItem = dynamicFieldsRepeater.itemAt(0);
if (firstItem)
firstItem.children[0].forceActiveFocus();
}
event.accepted = true;
}
Keys.onBacktabPressed: event => {
if (index > 0) {
const prevItem = dynamicFieldsRepeater.itemAt(index - 1);
if (prevItem)
prevItem.children[0].forceActiveFocus();
} else {
const lastItem = dynamicFieldsRepeater.itemAt(fieldsInfo.length - 1);
if (lastItem)
lastItem.children[0].forceActiveFocus();
}
event.accepted = true;
}
onTextEdited: {
let updated = Object.assign({}, root.secretValues);
updated[modelData.name] = text;
root.secretValues = updated;
}
onAccepted: {
if (index < fieldsInfo.length - 1) {
const nextItem = dynamicFieldsRepeater.itemAt(index + 1);
if (nextItem)
nextItem.children[0].forceActiveFocus();
return;
}
submitCredentialsAndClose();
}
close();
wifiPasswordInput = "";
wifiUsernameInput = "";
wifiAnonymousIdentityInput = "";
wifiDomainInput = "";
}
}
}
@@ -255,13 +373,11 @@ DankModal {
color: Theme.surfaceHover
border.color: usernameInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: usernameInput.activeFocus ? 2 : 1
visible: requiresEnterprise && !isVpnPrompt
visible: requiresEnterprise && !isVpnPrompt && fieldsInfo.length === 0
MouseArea {
anchors.fill: parent
onClicked: () => {
usernameInput.forceActiveFocus();
}
onClicked: usernameInput.forceActiveFocus()
}
DankTextField {
@@ -273,15 +389,11 @@ DankModal {
text: wifiUsernameInput
placeholderText: I18n.tr("Username")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
wifiUsernameInput = text;
}
onAccepted: () => {
if (passwordInput) {
passwordInput.forceActiveFocus();
}
}
enabled: root.visible
keyNavigationTab: passwordInput
keyNavigationBacktab: domainMatchInput
onTextEdited: wifiUsernameInput = text
onAccepted: passwordInput.forceActiveFocus()
}
}
@@ -292,12 +404,11 @@ DankModal {
color: Theme.surfaceHover
border.color: passwordInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: passwordInput.activeFocus ? 2 : 1
visible: fieldsInfo.length === 0
MouseArea {
anchors.fill: parent
onClicked: () => {
passwordInput.forceActiveFocus();
}
onClicked: passwordInput.forceActiveFocus()
}
DankTextField {
@@ -310,69 +421,16 @@ DankModal {
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
placeholderText: (requiresEnterprise && !isVpnPrompt) ? I18n.tr("Password") : ""
backgroundColor: "transparent"
focus: !requiresEnterprise
enabled: root.shouldBeVisible
onTextEdited: () => {
wifiPasswordInput = text;
}
onAccepted: () => {
if (isPromptMode) {
const secrets = {};
if (isVpnPrompt) {
if (passwordInput.text)
secrets["password"] = passwordInput.text;
} else if (promptSetting === "802-11-wireless-security") {
secrets["psk"] = passwordInput.text;
} else if (promptSetting === "802-1x") {
if (usernameInput.text)
secrets["identity"] = usernameInput.text;
if (passwordInput.text)
secrets["password"] = passwordInput.text;
if (wifiAnonymousIdentityInput)
secrets["anonymous-identity"] = wifiAnonymousIdentityInput;
}
NetworkService.submitCredentials(promptToken, secrets, savePasswordCheckbox.checked);
} else {
const username = requiresEnterprise ? usernameInput.text : "";
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text, username, wifiAnonymousIdentityInput, wifiDomainInput);
}
close();
wifiPasswordInput = "";
wifiUsernameInput = "";
wifiAnonymousIdentityInput = "";
wifiDomainInput = "";
passwordInput.text = "";
if (requiresEnterprise)
usernameInput.text = "";
}
Component.onCompleted: () => {
if (root.shouldBeVisible && !requiresEnterprise)
focusDelayTimer.start();
}
Timer {
id: focusDelayTimer
interval: 100
repeat: false
onTriggered: () => {
if (root.shouldBeVisible) {
if (requiresEnterprise && usernameInput) {
usernameInput.forceActiveFocus();
} else {
passwordInput.forceActiveFocus();
}
}
}
}
Connections {
target: root
function onShouldBeVisibleChanged() {
if (root.shouldBeVisible)
focusDelayTimer.start();
enabled: root.visible
keyNavigationTab: (requiresEnterprise && !isVpnPrompt) ? anonInput : null
keyNavigationBacktab: (requiresEnterprise && !isVpnPrompt) ? usernameInput : null
onTextEdited: wifiPasswordInput = text
onAccepted: {
if (requiresEnterprise && !isVpnPrompt) {
anonInput.forceActiveFocus();
return;
}
submitCredentialsAndClose();
}
}
}
@@ -388,9 +446,7 @@ DankModal {
MouseArea {
anchors.fill: parent
onClicked: () => {
anonInput.forceActiveFocus();
}
onClicked: anonInput.forceActiveFocus()
}
DankTextField {
@@ -402,10 +458,11 @@ DankModal {
text: wifiAnonymousIdentityInput
placeholderText: I18n.tr("Anonymous Identity (optional)")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
wifiAnonymousIdentityInput = text;
}
enabled: root.visible
keyNavigationTab: domainMatchInput
keyNavigationBacktab: passwordInput
onTextEdited: wifiAnonymousIdentityInput = text
onAccepted: domainMatchInput.forceActiveFocus()
}
}
@@ -420,9 +477,7 @@ DankModal {
MouseArea {
anchors.fill: parent
onClicked: () => {
domainMatchInput.forceActiveFocus();
}
onClicked: domainMatchInput.forceActiveFocus()
}
DankTextField {
@@ -434,10 +489,11 @@ DankModal {
text: wifiDomainInput
placeholderText: I18n.tr("Domain (optional)")
backgroundColor: "transparent"
enabled: root.shouldBeVisible
onTextEdited: () => {
wifiDomainInput = text;
}
enabled: root.visible
keyNavigationTab: usernameInput
keyNavigationBacktab: anonInput
onTextEdited: wifiDomainInput = text
onAccepted: submitCredentialsAndClose()
}
}
@@ -447,6 +503,7 @@ DankModal {
Row {
spacing: Theme.spacingS
visible: fieldsInfo.length === 0
Rectangle {
id: showPasswordCheckbox
@@ -472,9 +529,7 @@ DankModal {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
showPasswordCheckbox.checked = !showPasswordCheckbox.checked;
}
onClicked: showPasswordCheckbox.checked = !showPasswordCheckbox.checked
}
}
@@ -488,7 +543,7 @@ DankModal {
Row {
spacing: Theme.spacingS
visible: isVpnPrompt
visible: isVpnPrompt || fieldsInfo.length > 0
Rectangle {
id: savePasswordCheckbox
@@ -514,9 +569,7 @@ DankModal {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
savePasswordCheckbox.checked = !savePasswordCheckbox.checked;
}
onClicked: savePasswordCheckbox.checked = !savePasswordCheckbox.checked
}
}
@@ -548,7 +601,6 @@ DankModal {
StyledText {
id: cancelText
anchors.centerIn: parent
text: I18n.tr("Cancel")
font.pixelSize: Theme.fontSizeMedium
@@ -558,20 +610,10 @@ DankModal {
MouseArea {
id: cancelArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
if (isPromptMode) {
NetworkService.cancelCredentials(promptToken);
}
close();
wifiPasswordInput = "";
wifiUsernameInput = "";
wifiAnonymousIdentityInput = "";
wifiDomainInput = "";
}
onClicked: clearAndClose()
}
}
@@ -581,16 +623,24 @@ DankModal {
radius: Theme.cornerRadius
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
enabled: {
if (isVpnPrompt) {
return passwordInput.text.length > 0;
if (fieldsInfo.length > 0) {
for (let i = 0; i < fieldsInfo.length; i++) {
if (!fieldsInfo[i].isSecret)
continue;
const fieldName = fieldsInfo[i].name;
if (!secretValues[fieldName] || secretValues[fieldName].length === 0)
return false;
}
return true;
}
if (isVpnPrompt)
return passwordInput.text.length > 0;
return requiresEnterprise ? (usernameInput.text.length > 0 && passwordInput.text.length > 0) : passwordInput.text.length > 0;
}
opacity: enabled ? 1 : 0.5
StyledText {
id: connectText
anchors.centerIn: parent
text: I18n.tr("Connect")
font.pixelSize: Theme.fontSizeMedium
@@ -600,41 +650,11 @@ DankModal {
MouseArea {
id: connectArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: parent.enabled
onClicked: () => {
if (isPromptMode) {
const secrets = {};
if (isVpnPrompt) {
if (passwordInput.text)
secrets["password"] = passwordInput.text;
} else if (promptSetting === "802-11-wireless-security") {
secrets["psk"] = passwordInput.text;
} else if (promptSetting === "802-1x") {
if (usernameInput.text)
secrets["identity"] = usernameInput.text;
if (passwordInput.text)
secrets["password"] = passwordInput.text;
if (wifiAnonymousIdentityInput)
secrets["anonymous-identity"] = wifiAnonymousIdentityInput;
}
NetworkService.submitCredentials(promptToken, secrets, savePasswordCheckbox.checked);
} else {
const username = requiresEnterprise ? usernameInput.text : "";
NetworkService.connectToWifi(wifiPasswordSSID, passwordInput.text, username, wifiAnonymousIdentityInput, wifiDomainInput);
}
close();
wifiPasswordInput = "";
wifiUsernameInput = "";
wifiAnonymousIdentityInput = "";
wifiDomainInput = "";
passwordInput.text = "";
if (requiresEnterprise)
usernameInput.text = "";
}
onClicked: submitCredentialsAndClose()
}
Behavior on color {
@@ -649,4 +669,3 @@ DankModal {
}
}
}
}

View File

@@ -1,6 +1,4 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
@@ -13,239 +11,23 @@ PluginComponent {
service: DMSNetworkService
}
ccWidgetIcon: DMSNetworkService.isBusy ? "sync" : (DMSNetworkService.connected ? "vpn_lock" : "vpn_key_off")
ccWidgetPrimaryText: "VPN"
ccWidgetSecondaryText: {
if (!DMSNetworkService.connected)
return "Disconnected"
const names = DMSNetworkService.activeNames || []
return I18n.tr("Disconnected");
const names = DMSNetworkService.activeNames || [];
if (names.length <= 1)
return names[0] || "Connected"
return names[0] + " +" + (names.length - 1)
return names[0] || I18n.tr("Connected");
return names[0] + " +" + (names.length - 1);
}
ccWidgetIsActive: DMSNetworkService.connected
onCcWidgetToggled: {
DMSNetworkService.toggleVpn()
}
onCcWidgetToggled: DMSNetworkService.toggleVpn()
ccDetailContent: Component {
Rectangle {
id: detailRoot
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
Column {
id: detailColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
RowLayout {
spacing: Theme.spacingS
width: parent.width
StyledText {
text: {
if (!DMSNetworkService.connected)
return "Active: None"
const names = DMSNetworkService.activeNames || []
if (names.length <= 1)
return "Active: " + (names[0] || "VPN")
return "Active: " + names[0] + " +" + (names.length - 1)
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
wrapMode: Text.NoWrap
Layout.fillWidth: true
Layout.maximumWidth: parent.width - 120
}
Rectangle {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: DMSNetworkService.connected
width: 110
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "link_off"
size: Theme.fontSizeSmall
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Disconnect")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: discAllArea
anchors.fill: parent
hoverEnabled: true
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !DMSNetworkService.isBusy
onClicked: DMSNetworkService.disconnectAllActive()
}
}
}
Rectangle {
height: 1
width: parent.width
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
DankFlickable {
width: parent.width
height: 160
contentHeight: listCol.height
clip: true
Column {
id: listCol
width: parent.width
spacing: Theme.spacingXS
Item {
width: parent.width
height: DMSNetworkService.profiles.length === 0 ? 120 : 0
visible: height > 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "playlist_remove"
size: 36
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("No VPN profiles found")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("Add a VPN in NetworkManager")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Repeater {
model: DMSNetworkService.profiles
delegate: Rectangle {
required property var modelData
width: parent ? parent.width : 300
height: 50
radius: Theme.cornerRadius
color: rowArea.containsMouse ? Theme.primaryHoverLight : (DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: DMSNetworkService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: DMSNetworkService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
Column {
spacing: 2
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
elide: Text.ElideRight
wrapMode: Text.NoWrap
width: parent.width
}
StyledText {
text: {
if (modelData.type === "wireguard")
return "WireGuard"
const svc = modelData.serviceType || ""
if (svc.indexOf("openvpn") !== -1)
return "OpenVPN"
if (svc.indexOf("wireguard") !== -1)
return "WireGuard (plugin)"
if (svc.indexOf("openconnect") !== -1)
return "OpenConnect"
if (svc.indexOf("fortissl") !== -1 || svc.indexOf("forti") !== -1)
return "Fortinet"
if (svc.indexOf("strongswan") !== -1)
return "IPsec (strongSwan)"
if (svc.indexOf("libreswan") !== -1)
return "IPsec (Libreswan)"
if (svc.indexOf("l2tp") !== -1)
return "L2TP/IPsec"
if (svc.indexOf("pptp") !== -1)
return "PPTP"
if (svc.indexOf("vpnc") !== -1)
return "Cisco (vpnc)"
if (svc.indexOf("sstp") !== -1)
return "SSTP"
if (svc)
return svc.split('.').pop()
return "VPN"
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
}
}
Item {
Layout.fillWidth: true
}
}
MouseArea {
id: rowArea
anchors.fill: parent
hoverEnabled: true
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !DMSNetworkService.isBusy
onClicked: DMSNetworkService.toggle(modelData.uuid)
}
}
}
}
}
}
VpnDetailContent {
listHeight: 180
}
}
}

View File

@@ -65,13 +65,16 @@ DankPopout {
shouldBeVisible: false
property bool credentialsPromptOpen: NetworkService.credentialsRequested
property bool wifiPasswordModalOpen: PopoutService.wifiPasswordModal?.visible ?? false
property bool polkitModalOpen: PopoutService.polkitAuthModal?.visible ?? false
property bool anyModalOpen: credentialsPromptOpen || wifiPasswordModalOpen || polkitModalOpen || powerMenuOpen
backgroundInteractive: !anyModalOpen
customKeyboardFocus: {
if (!shouldBeVisible)
return WlrKeyboardFocus.None;
if (powerMenuOpen)
return WlrKeyboardFocus.None;
if (credentialsPromptOpen)
if (anyModalOpen)
return WlrKeyboardFocus.None;
if (CompositorService.isHyprland)
return WlrKeyboardFocus.OnDemand;

View File

@@ -353,8 +353,9 @@ Rectangle {
}
MenuItem {
text: "Activate"
text: I18n.tr("Activate")
height: !wiredNetworkContextMenu.currentConnected ? 32 : 0
visible: !wiredNetworkContextMenu.currentConnected
contentItem: StyledText {
text: parent.text
@@ -370,15 +371,39 @@ Rectangle {
}
onTriggered: {
if (!networkContextMenu.currentConnected) {
if (!wiredNetworkContextMenu.currentConnected) {
NetworkService.connectToSpecificWiredConfig(wiredNetworkContextMenu.currentUUID);
}
}
}
MenuItem {
text: I18n.tr("Disconnect")
height: wiredNetworkContextMenu.currentConnected ? 32 : 0
visible: wiredNetworkContextMenu.currentConnected
contentItem: StyledText {
text: parent.text
font.pixelSize: Theme.fontSizeSmall
color: Theme.error
leftPadding: Theme.spacingS
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: parent.hovered ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : "transparent"
radius: Theme.cornerRadius / 2
}
onTriggered: {
NetworkService.toggleNetworkConnection("ethernet");
}
}
MenuItem {
text: I18n.tr("Network Info")
height: wiredNetworkContextMenu.currentConnected ? 32 : 0
visible: wiredNetworkContextMenu.currentConnected
contentItem: StyledText {
text: parent.text

View File

@@ -1,11 +1,5 @@
// No external details import; content inlined for consistency
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Services
import qs.Widgets
@@ -20,22 +14,21 @@ DankPopout {
}
property bool wasVisible: false
onShouldBeVisibleChanged: {
if (shouldBeVisible && !wasVisible) {
DMSNetworkService.getState()
}
wasVisible = shouldBeVisible
}
property var triggerScreen: null
popupWidth: 360
popupHeight: Math.min(Screen.height - 100, contentLoader.item ? contentLoader.item.implicitHeight : 260)
popupWidth: 380
popupHeight: Math.min(Screen.height - 100, contentLoader.item ? contentLoader.item.implicitHeight : 320)
triggerWidth: 70
screen: triggerScreen
shouldBeVisible: false
onShouldBeVisibleChanged: {
if (shouldBeVisible && !wasVisible) {
DMSNetworkService.getState();
}
wasVisible = shouldBeVisible;
}
onBackgroundClicked: close()
content: Component {
@@ -47,46 +40,15 @@ DankPopout {
radius: Theme.cornerRadius
border.color: Theme.outlineMedium
border.width: 0
antialiasing: true
smooth: true
focus: true
Keys.onPressed: function(event) {
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
root.close();
event.accepted = true;
}
}
// Outer subtle shadow rings to match BatteryPopout
Rectangle {
anchors.fill: parent
anchors.margins: -3
color: "transparent"
radius: parent.radius + 3
border.color: Qt.rgba(0, 0, 0, 0.05)
border.width: 0
z: -3
}
Rectangle {
anchors.fill: parent
anchors.margins: -2
color: "transparent"
radius: parent.radius + 2
border.color: Theme.shadowMedium
border.width: 0
z: -2
}
Rectangle {
anchors.fill: parent
color: "transparent"
border.color: Theme.outlineStrong
border.width: 0
radius: parent.radius
z: -1
}
Column {
id: contentColumn
@@ -96,334 +58,33 @@ DankPopout {
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Item {
RowLayout {
width: parent.width
height: 32
spacing: Theme.spacingS
StyledText {
text: I18n.tr("VPN Connections")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
// Close button (matches BatteryPopout)
Rectangle {
width: 32
height: 32
radius: 16
color: closeArea.containsMouse ? Theme.errorHover : "transparent"
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: "close"
size: Theme.iconSize - 4
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: root.close()
}
}
}
// Inlined VPN details
Rectangle {
id: vpnDetail
width: parent.width
implicitHeight: detailsColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Theme.outlineStrong
border.width: 0
clip: true
Column {
id: detailsColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
RowLayout {
spacing: Theme.spacingS
width: parent.width
StyledText {
text: {
if (!DMSNetworkService.connected) {
return "Active: None";
}
const names = DMSNetworkService.activeNames || [];
if (names.length <= 1) {
return "Active: " + (names[0] || "VPN");
}
return "Active: " + names[0] + " +" + (names.length - 1);
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
wrapMode: Text.NoWrap
Layout.fillWidth: true
Layout.maximumWidth: parent.width - 140
}
// Removed Quick Connect for clarity
Item {
width: 1
height: 1
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: root.close()
}
}
// Disconnect all (shown only when any active)
Rectangle {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: DMSNetworkService.connected
width: 130
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
border.width: 0
border.color: Theme.outlineLight
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "link_off"
size: Theme.fontSizeSmall
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Disconnect")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: discAllArea
anchors.fill: parent
hoverEnabled: true
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !DMSNetworkService.isBusy
onClicked: DMSNetworkService.disconnectAllActive()
}
}
}
Rectangle {
height: 1
VpnDetailContent {
width: parent.width
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
listHeight: 200
parentPopout: root
}
DankFlickable {
width: parent.width
height: 160
contentHeight: listCol.height
clip: true
Column {
id: listCol
width: parent.width
spacing: Theme.spacingXS
Item {
width: parent.width
height: DMSNetworkService.profiles.length === 0 ? 120 : 0
visible: height > 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "playlist_remove"
size: 36
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("No VPN profiles found")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("Add a VPN in NetworkManager")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Repeater {
model: DMSNetworkService.profiles
delegate: Rectangle {
required property var modelData
width: parent ? parent.width : 300
height: 50
radius: Theme.cornerRadius
color: rowArea.containsMouse ? Theme.primaryHoverLight : (DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primaryPressed : Theme.surfaceLight)
border.width: DMSNetworkService.isActiveUuid(modelData.uuid) ? 2 : 1
border.color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.outlineLight
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: DMSNetworkService.isActiveUuid(modelData.uuid) ? "vpn_lock" : "vpn_key_off"
size: Theme.iconSize - 4
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
}
Column {
spacing: 2
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: DMSNetworkService.isActiveUuid(modelData.uuid) ? Theme.primary : Theme.surfaceText
elide: Text.ElideRight
wrapMode: Text.NoWrap
width: parent.width
}
StyledText {
text: {
if (modelData.type === "wireguard") {
return "WireGuard";
}
const svc = modelData.serviceType || "";
if (svc.indexOf("openvpn") !== -1) {
return "OpenVPN";
}
if (svc.indexOf("wireguard") !== -1) {
return "WireGuard (plugin)";
}
if (svc.indexOf("openconnect") !== -1) {
return "OpenConnect";
}
if (svc.indexOf("fortissl") !== -1 || svc.indexOf("forti") !== -1) {
return "Fortinet";
}
if (svc.indexOf("strongswan") !== -1) {
return "IPsec (strongSwan)";
}
if (svc.indexOf("libreswan") !== -1) {
return "IPsec (Libreswan)";
}
if (svc.indexOf("l2tp") !== -1) {
return "L2TP/IPsec";
}
if (svc.indexOf("pptp") !== -1) {
return "PPTP";
}
if (svc.indexOf("vpnc") !== -1) {
return "Cisco (vpnc)";
}
if (svc.indexOf("sstp") !== -1) {
return "SSTP";
}
if (svc) {
const parts = svc.split('.');
return parts[parts.length - 1];
}
return "VPN";
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
}
}
Item {
Layout.fillWidth: true
height: 1
}
}
MouseArea {
id: rowArea
anchors.fill: parent
hoverEnabled: true
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !DMSNetworkService.isBusy
onClicked: DMSNetworkService.toggle(modelData.uuid)
}
}
}
Item {
height: 1
width: 1
}
}
}
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@ Singleton {
property string ethernetInterface: ""
property bool ethernetConnected: false
property string ethernetConnectionUuid: ""
property var ethernetDevices: []
property var wiredConnections: []
@@ -116,7 +117,7 @@ Singleton {
signal networksUpdated
signal connectionChanged
signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason, string connType, string connName, string vpnService)
signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason, string connType, string connName, string vpnService, var fieldsInfo)
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
@@ -197,8 +198,9 @@ Singleton {
const connType = data.connType || "";
const connName = data.name || data.connectionId || "";
const vpnService = data.vpnService || "";
const fInfo = data.fieldsInfo || [];
credentialsNeeded(credentialsToken, credentialsSSID, credentialsSetting, credentialsFields, credentialsHints, credentialsReason, connType, connName, vpnService);
credentialsNeeded(credentialsToken, credentialsSSID, credentialsSetting, credentialsFields, credentialsHints, credentialsReason, connType, connName, vpnService, fInfo);
}
function addRef() {
@@ -244,6 +246,7 @@ Singleton {
ethernetInterface = state.ethernetDevice || "";
ethernetConnected = state.ethernetConnected || false;
ethernetConnectionUuid = state.ethernetConnectionUuid || "";
ethernetDevices = state.ethernetDevices || [];
wiredConnections = state.wiredConnections || [];
@@ -618,7 +621,7 @@ Singleton {
if (!networkAvailable)
return;
if (type === "ethernet") {
if (networkStatus === "ethernet") {
if (ethernetConnected) {
DMSService.sendRequest("network.ethernet.disconnect", null, null);
} else {
DMSService.sendRequest("network.ethernet.connect", null, null);
@@ -626,6 +629,14 @@ Singleton {
}
}
function disconnectEthernetDevice(deviceName) {
if (!networkAvailable)
return;
DMSService.sendRequest("network.ethernet.disconnect", {
device: deviceName
}, null);
}
function startAutoScan() {
autoScan = true;
autoRefreshEnabled = true;

View File

@@ -176,15 +176,6 @@ Singleton {
}
}
Connections {
target: SessionService
function onPrepareForSleep() {
if (SettingsData.lockBeforeSuspend) {
root.lockRequested();
}
}
}
Connections {
target: SettingsData
function onPreventIdleForMediaChanged() {

View File

@@ -16,6 +16,7 @@ Singleton {
property string ethernetInterface: activeService?.ethernetInterface ?? ""
property bool ethernetConnected: activeService?.ethernetConnected ?? false
property string ethernetConnectionUuid: activeService?.ethernetConnectionUuid ?? ""
property var ethernetDevices: activeService?.ethernetDevices ?? []
property var wiredConnections: activeService?.wiredConnections ?? []
@@ -88,7 +89,7 @@ Singleton {
signal networksUpdated
signal connectionChanged
signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason, string connType, string connName, string vpnService)
signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason, string connType, string connName, string vpnService, var fieldsInfo)
property bool usingLegacy: false
property var activeService: null
@@ -230,6 +231,12 @@ Singleton {
}
}
function disconnectEthernetDevice(deviceName) {
if (activeService && activeService.disconnectEthernetDevice) {
activeService.disconnectEthernetDevice(deviceName);
}
}
function startAutoScan() {
if (activeService && activeService.startAutoScan) {
activeService.startAutoScan();

View File

@@ -1,7 +1,7 @@
import QtQuick
import Quickshell
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
@@ -23,6 +23,7 @@ Singleton {
property var colorPickerModal: null
property var notificationModal: null
property var wifiPasswordModal: null
property var polkitAuthModal: null
property var bluetoothPairingModal: null
property var networkInfoModal: null
@@ -30,267 +31,267 @@ Singleton {
function setPosition(popout, x, y, width, section, screen) {
if (popout && popout.setTriggerPosition && arguments.length >= 6) {
popout.setTriggerPosition(x, y, width, section, screen)
popout.setTriggerPosition(x, y, width, section, screen);
}
}
function openControlCenter(x, y, width, section, screen) {
if (controlCenterPopout) {
setPosition(controlCenterPopout, x, y, width, section, screen)
controlCenterPopout.open()
setPosition(controlCenterPopout, x, y, width, section, screen);
controlCenterPopout.open();
}
}
function closeControlCenter() {
controlCenterPopout?.close()
controlCenterPopout?.close();
}
function toggleControlCenter(x, y, width, section, screen) {
if (controlCenterPopout) {
setPosition(controlCenterPopout, x, y, width, section, screen)
controlCenterPopout.toggle()
setPosition(controlCenterPopout, x, y, width, section, screen);
controlCenterPopout.toggle();
}
}
function openNotificationCenter(x, y, width, section, screen) {
if (notificationCenterPopout) {
setPosition(notificationCenterPopout, x, y, width, section, screen)
notificationCenterPopout.open()
setPosition(notificationCenterPopout, x, y, width, section, screen);
notificationCenterPopout.open();
}
}
function closeNotificationCenter() {
notificationCenterPopout?.close()
notificationCenterPopout?.close();
}
function toggleNotificationCenter(x, y, width, section, screen) {
if (notificationCenterPopout) {
setPosition(notificationCenterPopout, x, y, width, section, screen)
notificationCenterPopout.toggle()
setPosition(notificationCenterPopout, x, y, width, section, screen);
notificationCenterPopout.toggle();
}
}
function openAppDrawer(x, y, width, section, screen) {
if (appDrawerPopout) {
setPosition(appDrawerPopout, x, y, width, section, screen)
appDrawerPopout.open()
setPosition(appDrawerPopout, x, y, width, section, screen);
appDrawerPopout.open();
}
}
function closeAppDrawer() {
appDrawerPopout?.close()
appDrawerPopout?.close();
}
function toggleAppDrawer(x, y, width, section, screen) {
if (appDrawerPopout) {
setPosition(appDrawerPopout, x, y, width, section, screen)
appDrawerPopout.toggle()
setPosition(appDrawerPopout, x, y, width, section, screen);
appDrawerPopout.toggle();
}
}
function openProcessList(x, y, width, section, screen) {
if (processListPopout) {
setPosition(processListPopout, x, y, width, section, screen)
processListPopout.open()
setPosition(processListPopout, x, y, width, section, screen);
processListPopout.open();
}
}
function closeProcessList() {
processListPopout?.close()
processListPopout?.close();
}
function toggleProcessList(x, y, width, section, screen) {
if (processListPopout) {
setPosition(processListPopout, x, y, width, section, screen)
processListPopout.toggle()
setPosition(processListPopout, x, y, width, section, screen);
processListPopout.toggle();
}
}
function openDankDash(tabIndex, x, y, width, section, screen) {
if (dankDashPopout) {
if (arguments.length >= 6) {
setPosition(dankDashPopout, x, y, width, section, screen)
setPosition(dankDashPopout, x, y, width, section, screen);
}
dankDashPopout.currentTabIndex = tabIndex || 0
dankDashPopout.dashVisible = true
dankDashPopout.currentTabIndex = tabIndex || 0;
dankDashPopout.dashVisible = true;
}
}
function closeDankDash() {
if (dankDashPopout) {
dankDashPopout.dashVisible = false
dankDashPopout.dashVisible = false;
}
}
function toggleDankDash(tabIndex, x, y, width, section, screen) {
if (dankDashPopout) {
if (arguments.length >= 6) {
setPosition(dankDashPopout, x, y, width, section, screen)
setPosition(dankDashPopout, x, y, width, section, screen);
}
if (dankDashPopout.dashVisible) {
dankDashPopout.dashVisible = false
dankDashPopout.dashVisible = false;
} else {
dankDashPopout.currentTabIndex = tabIndex || 0
dankDashPopout.dashVisible = true
dankDashPopout.currentTabIndex = tabIndex || 0;
dankDashPopout.dashVisible = true;
}
}
}
function openBattery(x, y, width, section, screen) {
if (batteryPopout) {
setPosition(batteryPopout, x, y, width, section, screen)
batteryPopout.open()
setPosition(batteryPopout, x, y, width, section, screen);
batteryPopout.open();
}
}
function closeBattery() {
batteryPopout?.close()
batteryPopout?.close();
}
function toggleBattery(x, y, width, section, screen) {
if (batteryPopout) {
setPosition(batteryPopout, x, y, width, section, screen)
batteryPopout.toggle()
setPosition(batteryPopout, x, y, width, section, screen);
batteryPopout.toggle();
}
}
function openVpn(x, y, width, section, screen) {
if (vpnPopout) {
setPosition(vpnPopout, x, y, width, section, screen)
vpnPopout.open()
setPosition(vpnPopout, x, y, width, section, screen);
vpnPopout.open();
}
}
function closeVpn() {
vpnPopout?.close()
vpnPopout?.close();
}
function toggleVpn(x, y, width, section, screen) {
if (vpnPopout) {
setPosition(vpnPopout, x, y, width, section, screen)
vpnPopout.toggle()
setPosition(vpnPopout, x, y, width, section, screen);
vpnPopout.toggle();
}
}
function openSystemUpdate(x, y, width, section, screen) {
if (systemUpdatePopout) {
setPosition(systemUpdatePopout, x, y, width, section, screen)
systemUpdatePopout.open()
setPosition(systemUpdatePopout, x, y, width, section, screen);
systemUpdatePopout.open();
}
}
function closeSystemUpdate() {
systemUpdatePopout?.close()
systemUpdatePopout?.close();
}
function toggleSystemUpdate(x, y, width, section, screen) {
if (systemUpdatePopout) {
setPosition(systemUpdatePopout, x, y, width, section, screen)
systemUpdatePopout.toggle()
setPosition(systemUpdatePopout, x, y, width, section, screen);
systemUpdatePopout.toggle();
}
}
function openSettings() {
settingsModal?.show()
settingsModal?.show();
}
function closeSettings() {
settingsModal?.close()
settingsModal?.close();
}
function openClipboardHistory() {
clipboardHistoryModal?.show()
clipboardHistoryModal?.show();
}
function closeClipboardHistory() {
clipboardHistoryModal?.close()
clipboardHistoryModal?.close();
}
function openSpotlight() {
spotlightModal?.show()
spotlightModal?.show();
}
function closeSpotlight() {
spotlightModal?.close()
spotlightModal?.close();
}
function openPowerMenu() {
powerMenuModal?.openCentered()
powerMenuModal?.openCentered();
}
function closePowerMenu() {
powerMenuModal?.close()
powerMenuModal?.close();
}
function togglePowerMenu() {
if (powerMenuModal) {
if (powerMenuModal.shouldBeVisible) {
powerMenuModal.close()
powerMenuModal.close();
} else {
powerMenuModal.openCentered()
powerMenuModal.openCentered();
}
}
}
function showProcessListModal() {
processListModal?.show()
processListModal?.show();
}
function hideProcessListModal() {
processListModal?.hide()
processListModal?.hide();
}
function toggleProcessListModal() {
processListModal?.toggle()
processListModal?.toggle();
}
function showColorPicker() {
colorPickerModal?.show()
colorPickerModal?.show();
}
function hideColorPicker() {
colorPickerModal?.close()
colorPickerModal?.close();
}
function showNotificationModal() {
notificationModal?.show()
notificationModal?.show();
}
function hideNotificationModal() {
notificationModal?.close()
notificationModal?.close();
}
function showWifiPasswordModal() {
wifiPasswordModal?.show()
wifiPasswordModal?.show();
}
function hideWifiPasswordModal() {
wifiPasswordModal?.close()
wifiPasswordModal?.hide();
}
function showNetworkInfoModal() {
networkInfoModal?.show()
networkInfoModal?.show();
}
function hideNetworkInfoModal() {
networkInfoModal?.close()
networkInfoModal?.close();
}
function openNotepad() {
if (notepadSlideouts.length > 0) {
notepadSlideouts[0]?.show()
notepadSlideouts[0]?.show();
}
}
function closeNotepad() {
if (notepadSlideouts.length > 0) {
notepadSlideouts[0]?.hide()
notepadSlideouts[0]?.hide();
}
}
function toggleNotepad() {
if (notepadSlideouts.length > 0) {
notepadSlideouts[0]?.toggle()
notepadSlideouts[0]?.toggle();
}
}
}

View File

@@ -0,0 +1,220 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Common
Singleton {
id: root
readonly property bool available: DMSNetworkService.vpnAvailable
property var plugins: []
property var allExtensions: []
property bool importing: false
property string importError: ""
property var editConfig: null
property bool configLoading: false
property bool pluginsLoading: false
signal importComplete(string uuid, string name)
signal configLoaded(var config)
signal configUpdated
signal vpnDeleted(string uuid)
Component.onCompleted: {
if (available) {
fetchPlugins();
}
}
Connections {
target: DMSNetworkService
function onVpnAvailableChanged() {
if (DMSNetworkService.vpnAvailable && plugins.length === 0) {
fetchPlugins();
}
}
}
function fetchPlugins() {
if (!available || pluginsLoading)
return;
pluginsLoading = true;
DMSService.sendRequest("network.vpn.plugins", null, response => {
pluginsLoading = false;
if (response.error) {
console.warn("VPNService: Failed to fetch plugins:", response.error);
return;
}
if (!response.result)
return;
plugins = response.result;
const extSet = new Set();
for (const plugin of response.result) {
for (const ext of plugin.fileExtensions || []) {
extSet.add(ext);
}
}
allExtensions = Array.from(extSet);
});
}
function importVpn(filePath, name = "") {
if (!available || importing)
return;
importing = true;
importError = "";
const params = {
file: filePath
};
if (name)
params.name = name;
DMSService.sendRequest("network.vpn.import", params, response => {
importing = false;
if (response.error) {
importError = response.error;
ToastService.showError(I18n.tr("Failed to import VPN"));
return;
}
if (!response.result)
return;
if (response.result.success) {
ToastService.showInfo(I18n.tr("VPN imported: ") + (response.result.name || ""));
DMSNetworkService.refreshVpnProfiles();
importComplete(response.result.uuid || "", response.result.name || "");
return;
}
importError = response.result.error || "Import failed";
ToastService.showError(importError);
});
}
function getConfig(uuidOrName) {
if (!available)
return;
configLoading = true;
editConfig = null;
DMSService.sendRequest("network.vpn.getConfig", {
uuid: uuidOrName
}, response => {
configLoading = false;
if (response.error) {
ToastService.showError(I18n.tr("Failed to load VPN config"));
return;
}
if (response.result) {
editConfig = response.result;
configLoaded(response.result);
}
});
}
function updateConfig(uuid, updates) {
if (!available)
return;
const params = {
uuid: uuid
};
if (updates.name !== undefined)
params.name = updates.name;
if (updates.autoconnect !== undefined)
params.autoconnect = updates.autoconnect;
if (updates.data !== undefined)
params.data = updates.data;
DMSService.sendRequest("network.vpn.updateConfig", params, response => {
if (response.error) {
ToastService.showError(I18n.tr("Failed to update VPN"));
return;
}
ToastService.showInfo(I18n.tr("VPN configuration updated"));
DMSNetworkService.refreshVpnProfiles();
configUpdated();
});
}
function deleteVpn(uuidOrName) {
if (!available)
return;
DMSService.sendRequest("network.vpn.delete", {
uuid: uuidOrName
}, response => {
if (response.error) {
ToastService.showError(I18n.tr("Failed to delete VPN"));
return;
}
ToastService.showInfo(I18n.tr("VPN deleted"));
DMSNetworkService.refreshVpnProfiles();
vpnDeleted(uuidOrName);
});
}
function getFileFilter() {
if (allExtensions.length === 0) {
return ["*.ovpn", "*.conf"];
}
return allExtensions.map(e => "*" + e);
}
function getExtensionsForPlugin(serviceType) {
const plugin = plugins.find(p => p.serviceType === serviceType);
if (!plugin)
return ["*.conf"];
return (plugin.fileExtensions || [".conf"]).map(e => "*" + e);
}
function getPluginName(serviceType) {
if (!serviceType)
return "VPN";
const plugin = plugins.find(p => p.serviceType === serviceType);
if (plugin)
return plugin.name;
const svc = serviceType.toLowerCase();
if (svc.includes("openvpn"))
return "OpenVPN";
if (svc.includes("wireguard"))
return "WireGuard";
if (svc.includes("openconnect"))
return "OpenConnect";
if (svc.includes("fortissl") || svc.includes("forti"))
return "Fortinet";
if (svc.includes("strongswan"))
return "IPsec (strongSwan)";
if (svc.includes("libreswan"))
return "IPsec (Libreswan)";
if (svc.includes("l2tp"))
return "L2TP/IPsec";
if (svc.includes("pptp"))
return "PPTP";
if (svc.includes("vpnc"))
return "Cisco (vpnc)";
if (svc.includes("sstp"))
return "SSTP";
const parts = serviceType.split('.');
return parts[parts.length - 1] || "VPN";
}
function getVpnTypeFromProfile(profile) {
if (!profile)
return "VPN";
if (profile.type === "wireguard")
return "WireGuard";
return getPluginName(profile.serviceType);
}
}

View File

@@ -28,6 +28,7 @@ Item {
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
property bool shouldBeVisible: false
property var customKeyboardFocus: null
property bool backgroundInteractive: true
property real storedBarThickness: Theme.barHeight - 4
property real storedBarSpacing: 4
@@ -227,8 +228,8 @@ Item {
item: Rectangle {
x: root.maskX
y: root.maskY
width: shouldBeVisible ? root.maskWidth : 0
height: shouldBeVisible ? root.maskHeight : 0
width: (shouldBeVisible && backgroundInteractive) ? root.maskWidth : 0
height: (shouldBeVisible && backgroundInteractive) ? root.maskHeight : 0
}
}
@@ -237,7 +238,7 @@ Item {
y: root.maskY
width: root.maskWidth
height: root.maskHeight
enabled: shouldBeVisible
enabled: shouldBeVisible && backgroundInteractive
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
const clickX = mouse.x + root.maskX;

View File

@@ -0,0 +1,476 @@
import QtQuick
import QtQuick.Layouts
import qs.Common
import qs.Modals.Common
import qs.Modals.FileBrowser
import qs.Services
import qs.Widgets
Rectangle {
id: root
property var parentPopout: null
property string expandedUuid: ""
property int listHeight: 180
implicitHeight: contentColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
FileBrowserSurfaceModal {
id: fileBrowser
browserTitle: I18n.tr("Import VPN")
browserIcon: "vpn_key"
browserType: "vpn"
fileExtensions: VPNService.getFileFilter()
parentPopout: root.parentPopout
onFileSelected: path => {
VPNService.importVpn(path.replace("file://", ""));
}
}
ConfirmModal {
id: deleteConfirm
}
Column {
id: contentColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
RowLayout {
spacing: Theme.spacingS
width: parent.width
StyledText {
text: {
if (!DMSNetworkService.connected)
return I18n.tr("Active: None");
const names = DMSNetworkService.activeNames || [];
if (names.length <= 1)
return I18n.tr("Active: ") + (names[0] || "VPN");
return I18n.tr("Active: ") + names[0] + " +" + (names.length - 1);
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
wrapMode: Text.NoWrap
Layout.fillWidth: true
}
Rectangle {
height: 28
radius: 14
color: importArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight
width: 90
Layout.alignment: Qt.AlignVCenter
opacity: VPNService.importing ? 0.5 : 1.0
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: VPNService.importing ? "sync" : "add"
size: Theme.fontSizeSmall
color: Theme.primary
}
StyledText {
text: I18n.tr("Import")
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
font.weight: Font.Medium
}
}
MouseArea {
id: importArea
anchors.fill: parent
hoverEnabled: true
cursorShape: VPNService.importing ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !VPNService.importing
onClicked: fileBrowser.open()
}
}
Rectangle {
height: 28
radius: 14
color: discAllArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
visible: DMSNetworkService.connected
width: 100
Layout.alignment: Qt.AlignVCenter
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "link_off"
size: Theme.fontSizeSmall
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Disconnect")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: discAllArea
anchors.fill: parent
hoverEnabled: true
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !DMSNetworkService.isBusy
onClicked: DMSNetworkService.disconnectAllActive()
}
}
}
Rectangle {
height: 1
width: parent.width
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
DankFlickable {
width: parent.width
height: root.listHeight
contentHeight: listCol.height
clip: true
Column {
id: listCol
width: parent.width
spacing: 4
Item {
width: parent.width
height: DMSNetworkService.profiles.length === 0 ? 100 : 0
visible: height > 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "vpn_key_off"
size: 36
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("No VPN profiles")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("Click Import to add a .ovpn or .conf")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Repeater {
model: DMSNetworkService.profiles
delegate: Rectangle {
id: profileRow
required property var modelData
required property int index
readonly property bool isActive: DMSNetworkService.isActiveUuid(modelData.uuid)
readonly property bool isExpanded: root.expandedUuid === modelData.uuid
readonly property bool isHovered: rowArea.containsMouse || expandBtn.containsMouse || deleteBtn.containsMouse
readonly property var configData: isExpanded ? VPNService.editConfig : null
width: listCol.width
height: isExpanded ? 46 + expandedContent.height : 46
radius: Theme.cornerRadius
color: isHovered ? Theme.primaryHoverLight : (isActive ? Theme.primaryPressed : Theme.surfaceLight)
border.width: isActive ? 2 : 1
border.color: isActive ? Theme.primary : Theme.outlineLight
opacity: DMSNetworkService.isBusy ? 0.5 : 1.0
clip: true
Behavior on height {
NumberAnimation {
duration: 150
easing.type: Easing.OutQuad
}
}
MouseArea {
id: rowArea
anchors.fill: parent
hoverEnabled: true
cursorShape: DMSNetworkService.isBusy ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !DMSNetworkService.isBusy
onClicked: DMSNetworkService.toggle(modelData.uuid)
}
Column {
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: Theme.spacingS
Row {
width: parent.width
height: 46 - Theme.spacingS * 2
spacing: Theme.spacingS
DankIcon {
name: isActive ? "vpn_lock" : "vpn_key_off"
size: 20
color: isActive ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
spacing: 1
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 20 - 28 - 28 - Theme.spacingS * 4
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeMedium
color: isActive ? Theme.primary : Theme.surfaceText
elide: Text.ElideRight
wrapMode: Text.NoWrap
width: parent.width
}
StyledText {
text: VPNService.getVpnTypeFromProfile(modelData)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
wrapMode: Text.NoWrap
width: parent.width
elide: Text.ElideRight
}
}
Item {
width: Theme.spacingXS
height: 1
}
Rectangle {
id: expandBtnRect
width: 28
height: 28
radius: 14
color: expandBtn.containsMouse ? Theme.surfacePressed : "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: isExpanded ? "expand_less" : "expand_more"
size: 18
color: Theme.surfaceText
}
MouseArea {
id: expandBtn
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (isExpanded) {
root.expandedUuid = "";
} else {
root.expandedUuid = modelData.uuid;
VPNService.getConfig(modelData.uuid);
}
}
}
}
Rectangle {
id: deleteBtnRect
width: 28
height: 28
radius: 14
color: deleteBtn.containsMouse ? Theme.errorHover : "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: "delete"
size: 18
color: deleteBtn.containsMouse ? Theme.error : Theme.surfaceVariantText
}
MouseArea {
id: deleteBtn
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
deleteConfirm.showWithOptions({
title: I18n.tr("Delete VPN"),
message: I18n.tr("Delete \"") + modelData.name + "\"?",
confirmText: I18n.tr("Delete"),
confirmColor: Theme.error,
onConfirm: () => VPNService.deleteVpn(modelData.uuid)
});
}
}
}
}
Column {
id: expandedContent
width: parent.width
spacing: Theme.spacingXS
visible: isExpanded
Rectangle {
width: parent.width
height: 1
color: Theme.outlineLight
}
Item {
width: parent.width
height: VPNService.configLoading ? 40 : 0
visible: VPNService.configLoading
Row {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "sync"
size: 16
color: Theme.surfaceVariantText
}
StyledText {
text: I18n.tr("Loading...")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
Flow {
width: parent.width
spacing: Theme.spacingXS
visible: !VPNService.configLoading && configData
Repeater {
model: {
if (!configData)
return [];
const fields = [];
const data = configData.data || {};
if (data.remote)
fields.push({
label: I18n.tr("Server"),
value: data.remote
});
if (configData.username || data.username)
fields.push({
label: I18n.tr("Username"),
value: configData.username || data.username
});
if (data.cipher)
fields.push({
label: I18n.tr("Cipher"),
value: data.cipher
});
if (data.auth)
fields.push({
label: I18n.tr("Auth"),
value: data.auth
});
if (data["proto-tcp"] === "yes" || data["proto-tcp"] === "no")
fields.push({
label: I18n.tr("Protocol"),
value: data["proto-tcp"] === "yes" ? "TCP" : "UDP"
});
if (data["tunnel-mtu"])
fields.push({
label: I18n.tr("MTU"),
value: data["tunnel-mtu"]
});
if (data["connection-type"])
fields.push({
label: I18n.tr("Auth Type"),
value: data["connection-type"]
});
fields.push({
label: I18n.tr("Autoconnect"),
value: configData.autoconnect ? I18n.tr("Yes") : I18n.tr("No")
});
return fields;
}
delegate: Rectangle {
required property var modelData
required property int index
width: fieldContent.width + Theme.spacingM * 2
height: 32
radius: Theme.cornerRadius - 2
color: Theme.surfaceContainerHigh
border.width: 1
border.color: Theme.outlineLight
Row {
id: fieldContent
anchors.centerIn: parent
spacing: Theme.spacingXS
StyledText {
text: modelData.label + ":"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: modelData.value
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
Item {
width: 1
height: Theme.spacingXS
}
}
}
}
}
}
}
Item {
width: 1
height: Theme.spacingS
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,18 @@
{
"%1 adapter(s), none connected": {
"%1 adapter(s), none connected": ""
},
"%1 characters": {
"%1 characters": "%1 caratteri"
},
"%1 connected": {
"%1 connected": ""
},
"%1 display(s)": {
"%1 display(s)": ""
"%1 display(s)": "%1 display(s)"
},
"%1 widgets": {
"%1 widgets": ""
"%1 widgets": "%1 widgets"
},
"(Unnamed)": {
"(Unnamed)": "(Senza nome)"
@@ -50,11 +56,26 @@
"Actions": {
"Actions": "Azioni"
},
"Activate": {
"Activate": ""
},
"Active": {
"Active": ""
},
"Active: ": {
"Active: ": ""
},
"Active: None": {
"Active: None": ""
},
"Adapters": {
"Adapters": ""
},
"Add": {
"Add": "Aggiungi"
},
"Add Bar": {
"Add Bar": ""
"Add Bar": "Aggiungi Barra"
},
"Add Widget": {
"Add Widget": "Aggiungi Widget"
@@ -62,6 +83,9 @@
"Add Widget to ": {
"Add Widget to ": "Aggiungi Widget"
},
"Add Widget to %1 Section": {
"Add Widget to %1 Section": ""
},
"Add a VPN in NetworkManager": {
"Add a VPN in NetworkManager": "Aggiungi una VPN in NetworkManager"
},
@@ -84,7 +108,7 @@
"Always Show OSD Percentage": "Visualizza sempre percentuale OSD"
},
"Always on icons": {
"Always on icons": ""
"Always on icons": "Icone sempre attive"
},
"Always show a minimum of 3 workspaces, even if fewer are available": {
"Always show a minimum of 3 workspaces, even if fewer are available": "Visualizza sempre un minimo di 3 workspaces, anche se ne sono visibili meno"
@@ -92,6 +116,9 @@
"Animation Speed": {
"Animation Speed": "Velocità Animazione"
},
"Anonymous Identity": {
"Anonymous Identity": ""
},
"Anonymous Identity (optional)": {
"Anonymous Identity (optional)": "Identità anonima (facoltativa)"
},
@@ -134,6 +161,9 @@
"Are you sure you want to suspend the system?": {
"Are you sure you want to suspend the system?": "Sei sicuro di voler sospendere il sistema?"
},
"Audio": {
"Audio": ""
},
"Audio Codec": {
"Audio Codec": "Codec Audio"
},
@@ -149,9 +179,18 @@
"Audio Output Devices (": {
"Audio Output Devices (": "Dispositivi Uscita Audio ("
},
"Auth": {
"Auth": ""
},
"Auth Type": {
"Auth Type": ""
},
"Authenticate": {
"Authenticate": "Autenticare"
},
"Authentication": {
"Authentication": ""
},
"Authentication Required": {
"Authentication Required": "Autenticazione Richiesta"
},
@@ -167,6 +206,9 @@
"Authorize service for ": {
"Authorize service for ": "Autorizza servizio per "
},
"Auto": {
"Auto": ""
},
"Auto Location": {
"Auto Location": "Posizione Automatica"
},
@@ -182,6 +224,9 @@
"Auto-saving...": {
"Auto-saving...": "Salvataggio automatico..."
},
"Autoconnect": {
"Autoconnect": ""
},
"Autoconnect disabled": {
"Autoconnect disabled": "Connessione automatica disabilitata"
},
@@ -224,23 +269,32 @@
"Available Layouts": {
"Available Layouts": "Layouts Disponibili"
},
"Available Networks": {
"Available Networks": ""
},
"Available Plugins": {
"Available Plugins": "Plugins Disponibili"
},
"Available Screens (": {
"Available Screens (": "Schermi Disponibili ("
},
"BSSID": {
"BSSID": ""
},
"Back": {
"Back": "Indietro"
},
"Backend": {
"Backend": ""
},
"Balanced palette with focused accents (default).": {
"Balanced palette with focused accents (default).": "Tavolozza bilanciata con accenti focalizzati (default)."
},
"Bar Configurations": {
"Bar Configurations": ""
"Bar Configurations": "Configurazioni Barra"
},
"Bar Transparency": {
"Bar Transparency": ""
"Bar Transparency": "Trasparenza Barra"
},
"Battery": {
"Battery": "Batteria"
@@ -251,6 +305,9 @@
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": {
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": "Collega il blocca schermo ai segnali dbus da loginctl.\nDisabilità se stai usando un blocca schermo esterno"
},
"Bluetooth": {
"Bluetooth": ""
},
"Bluetooth Icon": {
"Bluetooth Icon": "Icona Bluetooth"
},
@@ -341,17 +398,23 @@
"Center Tiling": {
"Center Tiling": "Tiling Centrale"
},
"Certificate Password": {
"Certificate Password": ""
},
"Change bar appearance": {
"Change bar appearance": ""
"Change bar appearance": "Cambia aspetto barra"
},
"Changes:": {
"Changes:": "Modifiche:"
},
"Channel": {
"Channel": ""
},
"Check for system updates": {
"Check for system updates": "Controlla aggiornamenti di sistema"
},
"Choose Color": {
"Choose Color": ""
"Choose Color": "Scegli colore"
},
"Choose Launcher Logo Color": {
"Choose Launcher Logo Color": "Scegli Colore Logo Launcher"
@@ -360,7 +423,7 @@
"Choose icon": "Scegli icona"
},
"Choose the background color for widgets": {
"Choose the background color for widgets": ""
"Choose the background color for widgets": "Scegli il colore di sfondo per i widgets"
},
"Choose the border accent color": {
"Choose the border accent color": "Scegli il colore di accento dei bordi"
@@ -368,12 +431,18 @@
"Choose the logo displayed on the launcher button in DankBar": {
"Choose the logo displayed on the launcher button in DankBar": "Scegli il logo visualizzato sul bottone lanciatore nella DankBar"
},
"Choose the widget outline accent color": {
"Choose the widget outline accent color": ""
},
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "Scegli dove i popup delle notifiche appaiono sullo schermo"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": "Scegli dove i messaggi appaiono sullo schermo"
},
"Cipher": {
"Cipher": ""
},
"Clear": {
"Clear": "Pulisci"
},
@@ -383,6 +452,9 @@
"Clear All History?": {
"Clear All History?": "Pulire tutta la cronologia?"
},
"Click Import to add a .ovpn or .conf": {
"Click Import to add a .ovpn or .conf": ""
},
"Clipboard History": {
"Clipboard History": "Cronologia Clipboard"
},
@@ -399,7 +471,7 @@
"Close": "Chiudi"
},
"Close Overview on Launch": {
"Close Overview on Launch": ""
"Close Overview on Launch": "Chiudi Overview all'Avvio"
},
"Color Override": {
"Color Override": "Sostituzione Colore"
@@ -470,6 +542,9 @@
"Connect to Wi-Fi": {
"Connect to Wi-Fi": "Connetti a Wi-Fi"
},
"Connected": {
"Connected": ""
},
"Connected Displays": {
"Connected Displays": "Display Connessi"
},
@@ -617,6 +692,12 @@
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": {
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": "Del: Elimina • Shift+Del: Elimina Tutto • 1-9: Azioni • F10: Help • Esc: Chiude"
},
"Delete": {
"Delete": ""
},
"Delete VPN": {
"Delete VPN": ""
},
"Derives colors that closely match the underlying image.": {
"Derives colors that closely match the underlying image.": "Deriva colori che si avvicinano all'immagine sottostante"
},
@@ -641,6 +722,9 @@
"Disconnect": {
"Disconnect": "Disconnetti"
},
"Disconnected": {
"Disconnected": ""
},
"Disconnected from WiFi": {
"Disconnected from WiFi": "Disconnesso dal Wi-Fi"
},
@@ -654,10 +738,10 @@
"Dismiss": "Dismetti"
},
"Display Assignment": {
"Display Assignment": ""
"Display Assignment": "Assegnazione di visualizzazione"
},
"Display Name Format": {
"Display Name Format": ""
"Display Name Format": "Formato del nome visualizzato"
},
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": {
"Display a dock with pinned and running applications that can be positioned at the top, bottom, left, or right edge of the screen": "Visualizza una dock con applicazioni pinnate ed in esecuzione che possono essere posizionate nell'angolo superiore, inferiore, sinistro o destro dello schermo"
@@ -674,6 +758,9 @@
"Display currently focused application title": {
"Display currently focused application title": "Mostra il titolo delle applicazioni attualmente in focus"
},
"Display only workspaces that contain windows": {
"Display only workspaces that contain windows": ""
},
"Display power menu actions in a grid instead of a list": {
"Display power menu actions in a grid instead of a list": "Visualizza le azioni di alimentazioni in griglia invece di una lista"
},
@@ -716,6 +803,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "Trascina i widget per riordinarli nelle sezioni. Usa l'icona occhio per nascondere/mostrare i widget (mantenendo la spaziatura), o X per rimuoverli completamente."
},
"Driver": {
"Driver": ""
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": "Duplica Sfondo con Sfocatura"
},
@@ -738,7 +828,7 @@
"Enable Autoconnect": "Abilita connessione automatica"
},
"Enable Bar": {
"Enable Bar": ""
"Enable Bar": "Abilita Barra"
},
"Enable GPU Temperature": {
"Enable GPU Temperature": "Abilita Temperatura GPU"
@@ -800,9 +890,15 @@
"Enter this passkey on ": {
"Enter this passkey on ": "Inserisci questa passkey su "
},
"Enterprise": {
"Enterprise": ""
},
"Error": {
"Error": "Errore"
},
"Ethernet": {
"Ethernet": ""
},
"Exclusive Zone Offset": {
"Exclusive Zone Offset": "Offset Zona Esclusa"
},
@@ -812,6 +908,12 @@
"F1/I: Toggle • F10: Help": {
"F1/I: Toggle • F10: Help": "F1/I: Cambia • F10: Help"
},
"Fade grace period": {
"Fade grace period": ""
},
"Fade to lock screen": {
"Fade to lock screen": ""
},
"Failed to activate configuration": {
"Failed to activate configuration": "Impossibile attivare configurazione"
},
@@ -827,6 +929,9 @@
"Failed to connect to ": {
"Failed to connect to ": "Impossibile connettersi a "
},
"Failed to delete VPN": {
"Failed to delete VPN": ""
},
"Failed to disconnect VPN": {
"Failed to disconnect VPN": "Impossibile disconnettersi dalla VPN"
},
@@ -839,6 +944,12 @@
"Failed to enable WiFi": {
"Failed to enable WiFi": "Impossibile abilitare WiFi"
},
"Failed to import VPN": {
"Failed to import VPN": ""
},
"Failed to load VPN config": {
"Failed to load VPN config": ""
},
"Failed to pause printer": {
"Failed to pause printer": "Impossibile mettere in pausa la stampante"
},
@@ -857,6 +968,9 @@
"Failed to start connection to ": {
"Failed to start connection to ": "Impossibile avviare connessione a "
},
"Failed to update VPN": {
"Failed to update VPN": ""
},
"Failed to update autoconnect": {
"Failed to update autoconnect": "Impossibile aggiornare connessione automatica"
},
@@ -899,6 +1013,9 @@
"Force terminal applications to always use dark color schemes": {
"Force terminal applications to always use dark color schemes": "Forza applicazioni da terminale ad usare schemi di colori scuri"
},
"Forget": {
"Forget": ""
},
"Forget Device": {
"Forget Device": "Dimentica Dispositivo"
},
@@ -911,6 +1028,9 @@
"Format Legend": {
"Format Legend": "Legenda Formato"
},
"Frequency": {
"Frequency": ""
},
"Fun": {
"Fun": "Svago"
},
@@ -944,6 +1064,9 @@
"Goth Corners": {
"Goth Corners": "Angoli Gotici"
},
"Gradually fade the screen before locking with a configurable grace period": {
"Gradually fade the screen before locking with a configurable grace period": ""
},
"Graphics": {
"Graphics": "Grafica"
},
@@ -975,7 +1098,7 @@
"Hibernate": "Iberna"
},
"Hide Delay (ms)": {
"Hide Delay (ms)": ""
"Hide Delay (ms)": "Ritardo Scomparsa (ms)"
},
"Hide the dock when not in use and reveal it when hovering near the dock area": {
"Hide the dock when not in use and reveal it when hovering near the dock area": "Nascondi la dock quando "
@@ -986,6 +1109,18 @@
"High-fidelity palette that preserves source hues.": {
"High-fidelity palette that preserves source hues.": "Tavolozza alta-fedeltà per preservare la tonalità della sorgente"
},
"Hold Duration": {
"Hold Duration": ""
},
"Hold longer to confirm": {
"Hold longer to confirm": ""
},
"Hold to Confirm Power Actions": {
"Hold to Confirm Power Actions": ""
},
"Hold to confirm (%1s)": {
"Hold to confirm (%1s)": ""
},
"Hour": {
"Hour": "Ore"
},
@@ -998,6 +1133,12 @@
"I Understand": {
"I Understand": "Ho capito"
},
"IP": {
"IP": ""
},
"IP Address:": {
"IP Address:": ""
},
"Icon Size": {
"Icon Size": "Dimensione Icona"
},
@@ -1011,7 +1152,7 @@
"Idle Inhibitor": "Inibitore Riposo"
},
"Idle Inhibitor OSD": {
"Idle Inhibitor OSD": ""
"Idle Inhibitor OSD": "OSD Inibitore inattività"
},
"Idle Settings": {
"Idle Settings": "Impostazioni Riposo"
@@ -1022,6 +1163,12 @@
"Image": {
"Image": "Immagine"
},
"Import": {
"Import": ""
},
"Import VPN": {
"Import VPN": ""
},
"Include Transitions": {
"Include Transitions": "Includi Transizioni"
},
@@ -1035,7 +1182,7 @@
"Individual Batteries": "Batterie Individuali"
},
"Individual bar configuration": {
"Individual bar configuration": ""
"Individual bar configuration": "Configurazione barra individuale"
},
"Inhibit idle timeout when audio or video is playing": {
"Inhibit idle timeout when audio or video is playing": "Impedisci tempo inattività quando audio o video sono in riproduzione"
@@ -1046,6 +1193,9 @@
"Install plugins from the DMS plugin registry": {
"Install plugins from the DMS plugin registry": "Installa plugins dal registro dei plugin DMS"
},
"Interface:": {
"Interface:": ""
},
"Interlock Open": {
"Interlock Open": "Interblocco Aperto"
},
@@ -1065,13 +1215,13 @@
"Jobs: ": "Lavori:"
},
"Keep Awake": {
"Keep Awake": ""
"Keep Awake": "Mantieni Attivo"
},
"Keep Changes": {
"Keep Changes": "Conserva Modifiche"
},
"Keeping Awake": {
"Keeping Awake": ""
"Keeping Awake": "Mantieni Attivo"
},
"Keyboard Layout Name": {
"Keyboard Layout Name": "Nome Layout Tastiera"
@@ -1133,6 +1283,9 @@
"Loading plugins...": {
"Loading plugins...": "Caricamento plugins..."
},
"Loading...": {
"Loading...": ""
},
"Location Search": {
"Location Search": "Ricerca Posizione"
},
@@ -1160,11 +1313,17 @@
"Low Priority": {
"Low Priority": "Bassa Priorità"
},
"MAC": {
"MAC": ""
},
"MTU": {
"MTU": ""
},
"Manage and configure plugins for extending DMS functionality": {
"Manage and configure plugins for extending DMS functionality": "Gestione e configurazione plugins per estendere le funzionalità di DMS"
},
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": {
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": ""
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": "Gestisci fino a 4 configurazioni di barre indipendenti. Ogni barra ha la propria posizione, widget, stile e assegnazione di visualizzazione. "
},
"Manual Coordinates": {
"Manual Coordinates": "Coordinate Manuali"
@@ -1230,7 +1389,7 @@
"Media Players (": "Media Players ("
},
"Media Volume OSD": {
"Media Volume OSD": ""
"Media Volume OSD": "OSD Volume Media"
},
"Memory": {
"Memory": "Memoria"
@@ -1245,10 +1404,10 @@
"Microphone": "Microfono"
},
"Microphone Mute OSD": {
"Microphone Mute OSD": ""
"Microphone Mute OSD": "OSD Microfono Muto"
},
"Middle Section": {
"Middle Section": ""
"Middle Section": "Sezione Centrale"
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "Tavolozza minima costruita attorno a una singola tonalità."
@@ -1256,6 +1415,9 @@
"Minute": {
"Minute": "Minuti"
},
"Mode": {
"Mode": ""
},
"Mode:": {
"Mode:": "Modalità:"
},
@@ -1316,6 +1478,9 @@
"Network Speed Monitor": {
"Network Speed Monitor": "Monitor Velocità Rete"
},
"Network Status": {
"Network Status": ""
},
"Network download and upload speed display": {
"Network download and upload speed display": "Mostra velocità di download e upload della rete"
},
@@ -1334,6 +1499,9 @@
"Night Temperature": {
"Night Temperature": "Temperatura Notte"
},
"No": {
"No": ""
},
"No Active Players": {
"No Active Players": "Nessun Riproduttore Attivo"
},
@@ -1346,12 +1514,18 @@
"No Media": {
"No Media": "Nessun Media"
},
"No VPN profiles": {
"No VPN profiles": ""
},
"No VPN profiles found": {
"No VPN profiles found": "Nessun profilo VPN trovato"
},
"No Weather Data Available": {
"No Weather Data Available": "Nessun Dato Meteo Disponibile"
},
"No adapters": {
"No adapters": ""
},
"No clipboard entries found": {
"No clipboard entries found": "Nessun appunto trovato"
},
@@ -1379,6 +1553,9 @@
"Normal Priority": {
"Normal Priority": "Priorità Normale"
},
"Not connected": {
"Not connected": ""
},
"Notepad": {
"Notepad": "Notepad"
},
@@ -1431,7 +1608,7 @@
"On-Screen Displays": "Visualizzazione A Schermo"
},
"On-screen Displays": {
"On-screen Displays": ""
"On-screen Displays": "Visualizzazione a Schermo"
},
"Only adjust gamma based on time or location rules.": {
"Only adjust gamma based on time or location rules.": "Regolare gamma solo in base alle regole di tempo o di posizione."
@@ -1454,6 +1631,15 @@
"Other": {
"Other": "Altro"
},
"Outline Color": {
"Outline Color": ""
},
"Outline Opacity": {
"Outline Opacity": ""
},
"Outline Thickness": {
"Outline Thickness": ""
},
"Output Area Almost Full": {
"Output Area Almost Full": "Area Uscita Quasi Piena"
},
@@ -1466,9 +1652,15 @@
"Overview": {
"Overview": "Panoramica"
},
"Overview of your network connections": {
"Overview of your network connections": ""
},
"Overwrite": {
"Overwrite": "Sovrascrivi"
},
"PIN": {
"PIN": ""
},
"Padding": {
"Padding": "Padding"
},
@@ -1532,6 +1724,9 @@
"Play sounds for system events": {
"Play sounds for system events": "Riproduci suono per eventi di sistema"
},
"Playback": {
"Playback": ""
},
"Plugged In": {
"Plugged In": "Connesso"
},
@@ -1583,6 +1778,9 @@
"Power Profile OSD": {
"Power Profile OSD": "OSD Profilo Alimentazione"
},
"Preference": {
"Preference": ""
},
"Pressure": {
"Pressure": "Pressione"
},
@@ -1598,6 +1796,9 @@
"Print Server not available": {
"Print Server not available": "Server di Stampa non disponibile"
},
"Printer": {
"Printer": ""
},
"Printers": {
"Printers": "Stampanti"
},
@@ -1607,6 +1808,9 @@
"Privacy Indicator": {
"Privacy Indicator": "Indicatore Privacy"
},
"Private Key Password": {
"Private Key Password": ""
},
"Process": {
"Process": "Processo"
},
@@ -1619,6 +1823,9 @@
"Profile image is too large. Please use a smaller image.": {
"Profile image is too large. Please use a smaller image.": "Immagine profilo troppo grande. Per favore usa un'immagine più piccola"
},
"Protocol": {
"Protocol": ""
},
"Quick access to application launcher": {
"Quick access to application launcher": "Accesso veloce al launcher applicazioni"
},
@@ -1637,6 +1844,9 @@
"Rain Chance": {
"Rain Chance": "Possibili Piogge"
},
"Rate": {
"Rate": ""
},
"Reason": {
"Reason": "Motivo"
},
@@ -1664,6 +1874,9 @@
"Request confirmation on power off, restart, suspend, hibernate and logout actions": {
"Request confirmation on power off, restart, suspend, hibernate and logout actions": "Richiedi conferma alle azioni di spegnimento, riavvio, sospensione, hibernazione e logout"
},
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": {
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": ""
},
"Requires DWL compositor": {
"Requires DWL compositor": "Richiede compositor DWL"
},
@@ -1722,11 +1935,14 @@
"Save Notepad File": "Salva File Notepad"
},
"Save password": {
"Save password": ""
"Save password": "Salva password"
},
"Saved": {
"Saved": "Salvato"
},
"Saved Configurations": {
"Saved Configurations": ""
},
"Scale DankBar font sizes independently": {
"Scale DankBar font sizes independently": "Scala indipendentemente le dimensioni dei font della DankBar"
},
@@ -1736,6 +1952,9 @@
"Scan": {
"Scan": "Scansiona"
},
"Scanning...": {
"Scanning...": ""
},
"Science": {
"Science": "Scienza"
},
@@ -1757,12 +1976,21 @@
"Search plugins...": {
"Search plugins...": "Cerca plugins..."
},
"Search widgets...": {
"Search widgets...": ""
},
"Search...": {
"Search...": "Cerca..."
},
"Searching...": {
"Searching...": "Cercando..."
},
"Secured": {
"Secured": ""
},
"Security": {
"Security": ""
},
"Select Launcher Logo": {
"Select Launcher Logo": "Seleziona Logo Launcher"
},
@@ -1775,6 +2003,9 @@
"Select a widget to add to the ": {
"Select a widget to add to the ": "Seleziona un widget da aggiungere a "
},
"Select a widget to add. You can add multiple instances of the same widget if needed.": {
"Select a widget to add. You can add multiple instances of the same widget if needed.": ""
},
"Select an image file...": {
"Select an image file...": "Seleziona un'immagine"
},
@@ -1805,6 +2036,9 @@
"Separator": {
"Separator": "Separatore"
},
"Server": {
"Server": ""
},
"Set different wallpapers for each connected monitor": {
"Set different wallpapers for each connected monitor": "Imposta sfondi differenti per ogni monitor connesso"
},
@@ -1838,6 +2072,9 @@
"Show Log Out": {
"Show Log Out": "Mostra Log Out"
},
"Show Occupied Workspaces Only": {
"Show Occupied Workspaces Only": ""
},
"Show Power Actions": {
"Show Power Actions": "Mostra Azioni Alimentazione"
},
@@ -1881,10 +2118,10 @@
"Show on-screen display when caps lock state changes": "Visualizza un messaggio a schermo quando lo stato del maiuscolo cambia"
},
"Show on-screen display when idle inhibitor state changes": {
"Show on-screen display when idle inhibitor state changes": ""
"Show on-screen display when idle inhibitor state changes": "Mostra visualizzazione a schermo quando cambia lo stato dell'inibitore di inattività"
},
"Show on-screen display when media player volume changes": {
"Show on-screen display when media player volume changes": ""
"Show on-screen display when media player volume changes": "Mostra visualizzazione a schermo quando cambia il volume del lettore multimediale"
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": "Visualizza un messaggio a schermo quando il microfono è mutato/aperto"
@@ -1931,6 +2168,12 @@
"Shutdown": {
"Shutdown": "Spegni"
},
"Signal": {
"Signal": ""
},
"Signal:": {
"Signal:": ""
},
"Size": {
"Size": "Dimensione"
},
@@ -1949,6 +2192,9 @@
"Spacing": {
"Spacing": "Spaziatura"
},
"Speed": {
"Speed": ""
},
"Spool Area Full": {
"Spool Area Full": "Area Spool Piena"
},
@@ -1961,6 +2207,9 @@
"Start typing your notes here...": {
"Start typing your notes here...": "Inizia a scrivere i tuoi appunti qui..."
},
"State": {
"State": ""
},
"Status": {
"Status": "Stato"
},
@@ -2109,7 +2358,10 @@
"Toggle top bar visibility manually (can be controlled via IPC)": "Cambia manualmente visibiltà barra superiore (può essere controllata via IPC)"
},
"Toggle visibility of this bar configuration": {
"Toggle visibility of this bar configuration": ""
"Toggle visibility of this bar configuration": "Attiva/disattiva la visibilità di questa configurazione della barra"
},
"Toggling...": {
"Toggling...": ""
},
"Tomorrow": {
"Tomorrow": "Domani"
@@ -2135,9 +2387,15 @@
"Turn off monitors after": {
"Turn off monitors after": "Spegni monitors dopo"
},
"Unavailable": {
"Unavailable": ""
},
"Uninstall Plugin": {
"Uninstall Plugin": "Disinstalla Plugin"
},
"Unknown": {
"Unknown": ""
},
"Unpin from Dock": {
"Unpin from Dock": "Sblocca da Dock"
},
@@ -2225,6 +2483,18 @@
"VPN Connections": {
"VPN Connections": "Connessioni VPN"
},
"VPN Password": {
"VPN Password": ""
},
"VPN configuration updated": {
"VPN configuration updated": ""
},
"VPN deleted": {
"VPN deleted": ""
},
"VPN imported: ": {
"VPN imported: ": ""
},
"VPN status and quick connect": {
"VPN status and quick connect": "Stato VPN e connessione veloce"
},
@@ -2264,6 +2534,9 @@
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "Volume, luminosità, e altri OSD di sistema"
},
"WPA/WPA2": {
"WPA/WPA2": ""
},
"Wallpaper": {
"Wallpaper": "Sfondo"
},
@@ -2291,6 +2564,15 @@
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": {
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "Quando abilitato, le applicazioni sono ordinate alfabeticamente. Quando disabilitato, le applicazioni sono ordinate per frequenza d'uso"
},
"Wi-Fi Password": {
"Wi-Fi Password": ""
},
"WiFi": {
"WiFi": ""
},
"WiFi Device": {
"WiFi Device": ""
},
"WiFi disabled": {
"WiFi disabled": "WiFi disabilitato"
},
@@ -2301,19 +2583,22 @@
"WiFi is off": "WiFi spento"
},
"Widget Background Color": {
"Widget Background Color": ""
"Widget Background Color": "Colore Sfondo Widget"
},
"Widget Management": {
"Widget Management": "Gestione Widget"
},
"Widget Outline": {
"Widget Outline": ""
},
"Widget Style": {
"Widget Style": ""
"Widget Style": "Stile Widget"
},
"Widget Styling": {
"Widget Styling": "Widget Styling"
},
"Widget Transparency": {
"Widget Transparency": ""
"Widget Transparency": "Trasparenza Widget"
},
"Widgets": {
"Widgets": "Widgets"
@@ -2336,6 +2621,9 @@
"Workspace Switcher": {
"Workspace Switcher": "Switcher Workspace"
},
"Yes": {
"Yes": ""
},
"You have unsaved changes. Save before closing this tab?": {
"You have unsaved changes. Save before closing this tab?": "Ci sono modifiche non salvate. Salvare prima di chiudere questa tab?"
},
@@ -2349,10 +2637,10 @@
"You have unsaved changes. Save before opening a file?": "Ci sono modifiche non salvate. Salvare prima di aprire un file?"
},
"custom theme file browser title": {
"Select Custom Theme": ""
"Select Custom Theme": "Seleziona Tema Personalizzato"
},
"dark mode wallpaper file browser title | light mode wallpaper file browser title | wallpaper file browser title": {
"Select Wallpaper": ""
"Select Wallpaper": "Seleziona Sfondo"
},
"events": {
"events": "eventi"
@@ -2367,19 +2655,19 @@
"official": "ufficiale"
},
"profile image file browser title": {
"Select Profile Image": ""
"Select Profile Image": "Seleziona Immagine Profilo"
},
"settings window title": {
"Settings": ""
"Settings": "Impostazioni"
},
"sysmon window title": {
"System Monitor": ""
"System Monitor": "Monitor Sistema"
},
"update dms for NM integration.": {
"update dms for NM integration.": "aggiorna dms per integrazione NM"
},
"wallpaper directory file browser title": {
"Select Wallpaper Directory": ""
"Select Wallpaper Directory": "Seleziona Cartella Sfondo"
},
"• Install only from trusted sources": {
"• Install only from trusted sources": "• Installa solo da sorgenti fidate"

View File

@@ -1,12 +1,18 @@
{
"%1 adapter(s), none connected": {
"%1 adapter(s), none connected": ""
},
"%1 characters": {
"%1 characters": "%1 文字"
},
"%1 connected": {
"%1 connected": ""
},
"%1 display(s)": {
"%1 display(s)": ""
"%1 display(s)": "%1 表示"
},
"%1 widgets": {
"%1 widgets": ""
"%1 widgets": "%1 ウィジェット"
},
"(Unnamed)": {
"(Unnamed)": "(名前なし)"
@@ -50,11 +56,26 @@
"Actions": {
"Actions": "アクション"
},
"Activate": {
"Activate": ""
},
"Active": {
"Active": ""
},
"Active: ": {
"Active: ": ""
},
"Active: None": {
"Active: None": ""
},
"Adapters": {
"Adapters": ""
},
"Add": {
"Add": "追加"
},
"Add Bar": {
"Add Bar": ""
"Add Bar": "バーを作る"
},
"Add Widget": {
"Add Widget": "ウィジェットを追加"
@@ -62,6 +83,9 @@
"Add Widget to ": {
"Add Widget to ": "ウィジェットを追加 "
},
"Add Widget to %1 Section": {
"Add Widget to %1 Section": ""
},
"Add a VPN in NetworkManager": {
"Add a VPN in NetworkManager": "NetworkManagerでVPNを追加"
},
@@ -92,6 +116,9 @@
"Animation Speed": {
"Animation Speed": "アニメーション速度"
},
"Anonymous Identity": {
"Anonymous Identity": ""
},
"Anonymous Identity (optional)": {
"Anonymous Identity (optional)": "匿名 ID (オプション)"
},
@@ -134,6 +161,9 @@
"Are you sure you want to suspend the system?": {
"Are you sure you want to suspend the system?": "システムを一時停止してもよろしいですか?"
},
"Audio": {
"Audio": ""
},
"Audio Codec": {
"Audio Codec": "オーディオコーデック"
},
@@ -149,9 +179,18 @@
"Audio Output Devices (": {
"Audio Output Devices (": "オーディオ出力デバイス("
},
"Auth": {
"Auth": ""
},
"Auth Type": {
"Auth Type": ""
},
"Authenticate": {
"Authenticate": "認証"
},
"Authentication": {
"Authentication": ""
},
"Authentication Required": {
"Authentication Required": "認証が必要です"
},
@@ -167,6 +206,9 @@
"Authorize service for ": {
"Authorize service for ": "サービスを許可 "
},
"Auto": {
"Auto": ""
},
"Auto Location": {
"Auto Location": "自動位置検出"
},
@@ -182,6 +224,9 @@
"Auto-saving...": {
"Auto-saving...": "自動保存中..."
},
"Autoconnect": {
"Autoconnect": ""
},
"Autoconnect disabled": {
"Autoconnect disabled": "自動接続が無効"
},
@@ -224,23 +269,32 @@
"Available Layouts": {
"Available Layouts": "利用可能なレイアウト"
},
"Available Networks": {
"Available Networks": ""
},
"Available Plugins": {
"Available Plugins": "利用可能なプラグイン"
},
"Available Screens (": {
"Available Screens (": "利用可能なスクリーン("
},
"BSSID": {
"BSSID": ""
},
"Back": {
"Back": "戻る"
},
"Backend": {
"Backend": ""
},
"Balanced palette with focused accents (default).": {
"Balanced palette with focused accents (default).": "アクセントに焦点を絞ったバランスの取れたパレット(デフォルト)。"
},
"Bar Configurations": {
"Bar Configurations": ""
"Bar Configurations": "バーの設定"
},
"Bar Transparency": {
"Bar Transparency": ""
"Bar Transparency": "バーの透明度"
},
"Battery": {
"Battery": "バッテリー"
@@ -251,6 +305,9 @@
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": {
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": "ロック画面をloginctlからのdbus信号にバインド。外部ロック画面を使用している場合は無効に"
},
"Bluetooth": {
"Bluetooth": ""
},
"Bluetooth Icon": {
"Bluetooth Icon": "Bluetoothアイコン"
},
@@ -341,17 +398,23 @@
"Center Tiling": {
"Center Tiling": "中央タイルリング"
},
"Certificate Password": {
"Certificate Password": ""
},
"Change bar appearance": {
"Change bar appearance": ""
"Change bar appearance": "バーの見た目を変更"
},
"Changes:": {
"Changes:": "変更:"
},
"Channel": {
"Channel": ""
},
"Check for system updates": {
"Check for system updates": "システムアップデートを検査"
},
"Choose Color": {
"Choose Color": ""
"Choose Color": "色を選んでください"
},
"Choose Launcher Logo Color": {
"Choose Launcher Logo Color": "ランチャーロゴの色を選ぶ"
@@ -360,7 +423,7 @@
"Choose icon": "アイコンを選ぶ"
},
"Choose the background color for widgets": {
"Choose the background color for widgets": ""
"Choose the background color for widgets": "ウィジェットの背景色を選んでください"
},
"Choose the border accent color": {
"Choose the border accent color": "ボーダーの強調色を選ぶ"
@@ -368,12 +431,18 @@
"Choose the logo displayed on the launcher button in DankBar": {
"Choose the logo displayed on the launcher button in DankBar": "Dank Barのランチャーボタンに表示されるロゴを選ぶ"
},
"Choose the widget outline accent color": {
"Choose the widget outline accent color": ""
},
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "通知ポップアップが画面に表示される場所を選ぶ"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": "OSDの表示する場所を選んでください"
},
"Cipher": {
"Cipher": ""
},
"Clear": {
"Clear": "クリア"
},
@@ -383,6 +452,9 @@
"Clear All History?": {
"Clear All History?": "すべての履歴をクリアしますか?"
},
"Click Import to add a .ovpn or .conf": {
"Click Import to add a .ovpn or .conf": ""
},
"Clipboard History": {
"Clipboard History": "クリップボード履歴"
},
@@ -399,7 +471,7 @@
"Close": "閉じる"
},
"Close Overview on Launch": {
"Close Overview on Launch": ""
"Close Overview on Launch": "起動中のときに概要を閉じる"
},
"Color Override": {
"Color Override": "色のオーバーライド"
@@ -470,6 +542,9 @@
"Connect to Wi-Fi": {
"Connect to Wi-Fi": "Wi-Fiに接続"
},
"Connected": {
"Connected": ""
},
"Connected Displays": {
"Connected Displays": "接続されたディスプレイ"
},
@@ -617,6 +692,12 @@
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": {
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": "Del: クリア • Shift+Del: すべてクリア • 1-9: アクション • F10: ヘルプ • Esc: 閉じる"
},
"Delete": {
"Delete": ""
},
"Delete VPN": {
"Delete VPN": ""
},
"Derives colors that closely match the underlying image.": {
"Derives colors that closely match the underlying image.": "下にある画像に密接に一致する色を導き出します。"
},
@@ -641,6 +722,9 @@
"Disconnect": {
"Disconnect": "切断"
},
"Disconnected": {
"Disconnected": ""
},
"Disconnected from WiFi": {
"Disconnected from WiFi": "WiFiから切断されました"
},
@@ -654,7 +738,7 @@
"Dismiss": "解除"
},
"Display Assignment": {
"Display Assignment": ""
"Display Assignment": "表示割り当て"
},
"Display Name Format": {
"Display Name Format": "名称形式を表示"
@@ -674,6 +758,9 @@
"Display currently focused application title": {
"Display currently focused application title": "現在フォーカスされているアプリケーションのタイトルを表示"
},
"Display only workspaces that contain windows": {
"Display only workspaces that contain windows": ""
},
"Display power menu actions in a grid instead of a list": {
"Display power menu actions in a grid instead of a list": "電源メニューのアクションをリストではなくグリッドに表示"
},
@@ -716,6 +803,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "ウィジェットをドラッグしてセクション内で順序を変更できます。目のアイコンでウィジェットを表示/非表示にスペースは維持、Xで完全に削除できます。"
},
"Driver": {
"Driver": ""
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": "ぼかしで壁紙を複製"
},
@@ -738,7 +828,7 @@
"Enable Autoconnect": "自動接続を有効"
},
"Enable Bar": {
"Enable Bar": ""
"Enable Bar": "バーを起用"
},
"Enable GPU Temperature": {
"Enable GPU Temperature": "GPU温度を有効にする"
@@ -800,9 +890,15 @@
"Enter this passkey on ": {
"Enter this passkey on ": "ここでパスキーを入力してください "
},
"Enterprise": {
"Enterprise": ""
},
"Error": {
"Error": "エラー"
},
"Ethernet": {
"Ethernet": ""
},
"Exclusive Zone Offset": {
"Exclusive Zone Offset": "排他ゾーンオフセット"
},
@@ -812,6 +908,12 @@
"F1/I: Toggle • F10: Help": {
"F1/I: Toggle • F10: Help": "F1/I: 切り替え • F10: ヘルプ"
},
"Fade grace period": {
"Fade grace period": ""
},
"Fade to lock screen": {
"Fade to lock screen": ""
},
"Failed to activate configuration": {
"Failed to activate configuration": "設定が適用できませんでした"
},
@@ -827,6 +929,9 @@
"Failed to connect to ": {
"Failed to connect to ": "接続ができませんでした "
},
"Failed to delete VPN": {
"Failed to delete VPN": ""
},
"Failed to disconnect VPN": {
"Failed to disconnect VPN": "VPNへの切断が失敗しました"
},
@@ -839,6 +944,12 @@
"Failed to enable WiFi": {
"Failed to enable WiFi": "WiFiを有効化にできませんでした"
},
"Failed to import VPN": {
"Failed to import VPN": ""
},
"Failed to load VPN config": {
"Failed to load VPN config": ""
},
"Failed to pause printer": {
"Failed to pause printer": "プリンターの一時中断に失敗しました"
},
@@ -857,6 +968,9 @@
"Failed to start connection to ": {
"Failed to start connection to ": "接続ができませんでした "
},
"Failed to update VPN": {
"Failed to update VPN": ""
},
"Failed to update autoconnect": {
"Failed to update autoconnect": "自動接続の更新が失敗しました"
},
@@ -899,6 +1013,9 @@
"Force terminal applications to always use dark color schemes": {
"Force terminal applications to always use dark color schemes": "端末アプリで常に暗い配色を強制使用"
},
"Forget": {
"Forget": ""
},
"Forget Device": {
"Forget Device": "デバイスを忘れる"
},
@@ -911,6 +1028,9 @@
"Format Legend": {
"Format Legend": "フォーマット凡例"
},
"Frequency": {
"Frequency": ""
},
"Fun": {
"Fun": "娯楽"
},
@@ -944,6 +1064,9 @@
"Goth Corners": {
"Goth Corners": "ゴスコーナーズ"
},
"Gradually fade the screen before locking with a configurable grace period": {
"Gradually fade the screen before locking with a configurable grace period": ""
},
"Graphics": {
"Graphics": "グラフィック"
},
@@ -986,6 +1109,18 @@
"High-fidelity palette that preserves source hues.": {
"High-fidelity palette that preserves source hues.": "ソースの色相を保持する忠実度の高いパレット。"
},
"Hold Duration": {
"Hold Duration": ""
},
"Hold longer to confirm": {
"Hold longer to confirm": ""
},
"Hold to Confirm Power Actions": {
"Hold to Confirm Power Actions": ""
},
"Hold to confirm (%1s)": {
"Hold to confirm (%1s)": ""
},
"Hour": {
"Hour": "時間"
},
@@ -998,6 +1133,12 @@
"I Understand": {
"I Understand": "わかりました"
},
"IP": {
"IP": ""
},
"IP Address:": {
"IP Address:": ""
},
"Icon Size": {
"Icon Size": "アイコンサイズ"
},
@@ -1022,6 +1163,12 @@
"Image": {
"Image": "画像"
},
"Import": {
"Import": ""
},
"Import VPN": {
"Import VPN": ""
},
"Include Transitions": {
"Include Transitions": "トランジションを含める"
},
@@ -1035,7 +1182,7 @@
"Individual Batteries": "バッテリーごと"
},
"Individual bar configuration": {
"Individual bar configuration": ""
"Individual bar configuration": "バーの個別設定"
},
"Inhibit idle timeout when audio or video is playing": {
"Inhibit idle timeout when audio or video is playing": "オーディオまたはビデオの再生中のアイドルタイムアウトを禁止"
@@ -1046,6 +1193,9 @@
"Install plugins from the DMS plugin registry": {
"Install plugins from the DMS plugin registry": "DMSプラグインレジストリからプラグインをインストールする"
},
"Interface:": {
"Interface:": ""
},
"Interlock Open": {
"Interlock Open": "インターロックオープン"
},
@@ -1065,13 +1215,13 @@
"Jobs: ": "ジョブ: "
},
"Keep Awake": {
"Keep Awake": ""
"Keep Awake": "活動状態を維持"
},
"Keep Changes": {
"Keep Changes": "変更を保持"
},
"Keeping Awake": {
"Keeping Awake": ""
"Keeping Awake": "活動状態を維持"
},
"Keyboard Layout Name": {
"Keyboard Layout Name": "キーボードレイアウト名"
@@ -1133,6 +1283,9 @@
"Loading plugins...": {
"Loading plugins...": "プラグインを読み込んでいます..."
},
"Loading...": {
"Loading...": ""
},
"Location Search": {
"Location Search": "ロケーション検索"
},
@@ -1160,11 +1313,17 @@
"Low Priority": {
"Low Priority": "低優先度"
},
"MAC": {
"MAC": ""
},
"MTU": {
"MTU": ""
},
"Manage and configure plugins for extending DMS functionality": {
"Manage and configure plugins for extending DMS functionality": "DMS 機能を拡張するためのプラグインを管理および構成"
},
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": {
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": ""
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": "最大4つの独立したバー構成を管理できます。各バーには独自の位置、ウィジェット、スタイリング、表示割り当てがあります。"
},
"Manual Coordinates": {
"Manual Coordinates": "手動座標"
@@ -1230,7 +1389,7 @@
"Media Players (": "メディアプレーヤー("
},
"Media Volume OSD": {
"Media Volume OSD": ""
"Media Volume OSD": "メディア音量OSD"
},
"Memory": {
"Memory": "メモリ"
@@ -1248,7 +1407,7 @@
"Microphone Mute OSD": "マイクミュートOSD"
},
"Middle Section": {
"Middle Section": ""
"Middle Section": "中間区間"
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "単一の色相を中心に構築された最小限のパレット。"
@@ -1256,6 +1415,9 @@
"Minute": {
"Minute": "分"
},
"Mode": {
"Mode": ""
},
"Mode:": {
"Mode:": "モード:"
},
@@ -1316,6 +1478,9 @@
"Network Speed Monitor": {
"Network Speed Monitor": "ネットワーク速度モニター"
},
"Network Status": {
"Network Status": ""
},
"Network download and upload speed display": {
"Network download and upload speed display": "ネットワークのダウンロードおよびアップロード速度を表示"
},
@@ -1334,6 +1499,9 @@
"Night Temperature": {
"Night Temperature": "夜間の温度"
},
"No": {
"No": ""
},
"No Active Players": {
"No Active Players": "アクティブプレイヤーなし"
},
@@ -1346,12 +1514,18 @@
"No Media": {
"No Media": "メディアなし"
},
"No VPN profiles": {
"No VPN profiles": ""
},
"No VPN profiles found": {
"No VPN profiles found": "VPNプロファイルが見つかりませんでした"
},
"No Weather Data Available": {
"No Weather Data Available": "気象データはありません"
},
"No adapters": {
"No adapters": ""
},
"No clipboard entries found": {
"No clipboard entries found": "クリップボードのエントリが見つかりませんでした"
},
@@ -1379,6 +1553,9 @@
"Normal Priority": {
"Normal Priority": "通常の優先度"
},
"Not connected": {
"Not connected": ""
},
"Notepad": {
"Notepad": "メモ帳"
},
@@ -1454,6 +1631,15 @@
"Other": {
"Other": "他"
},
"Outline Color": {
"Outline Color": ""
},
"Outline Opacity": {
"Outline Opacity": ""
},
"Outline Thickness": {
"Outline Thickness": ""
},
"Output Area Almost Full": {
"Output Area Almost Full": "アウトプットエリアがほぼ満杯"
},
@@ -1466,9 +1652,15 @@
"Overview": {
"Overview": "概要"
},
"Overview of your network connections": {
"Overview of your network connections": ""
},
"Overwrite": {
"Overwrite": "上書き"
},
"PIN": {
"PIN": ""
},
"Padding": {
"Padding": "パディング"
},
@@ -1532,6 +1724,9 @@
"Play sounds for system events": {
"Play sounds for system events": "システムイベントが発生したときにサウンドを再生"
},
"Playback": {
"Playback": ""
},
"Plugged In": {
"Plugged In": "接続"
},
@@ -1583,6 +1778,9 @@
"Power Profile OSD": {
"Power Profile OSD": "電源プロファイルOSD"
},
"Preference": {
"Preference": ""
},
"Pressure": {
"Pressure": "プレッシャー"
},
@@ -1598,6 +1796,9 @@
"Print Server not available": {
"Print Server not available": "プリントサーバーが利用できません"
},
"Printer": {
"Printer": ""
},
"Printers": {
"Printers": "プリンター"
},
@@ -1607,6 +1808,9 @@
"Privacy Indicator": {
"Privacy Indicator": "プライバシーインジケーター"
},
"Private Key Password": {
"Private Key Password": ""
},
"Process": {
"Process": "プロセス"
},
@@ -1619,6 +1823,9 @@
"Profile image is too large. Please use a smaller image.": {
"Profile image is too large. Please use a smaller image.": "プロフィール画像が大きすぎます。より小さい画像を使用してください。"
},
"Protocol": {
"Protocol": ""
},
"Quick access to application launcher": {
"Quick access to application launcher": "アプリケーションランチャーへのクイックアクセス"
},
@@ -1637,6 +1844,9 @@
"Rain Chance": {
"Rain Chance": "降水確率"
},
"Rate": {
"Rate": ""
},
"Reason": {
"Reason": "原因"
},
@@ -1664,6 +1874,9 @@
"Request confirmation on power off, restart, suspend, hibernate and logout actions": {
"Request confirmation on power off, restart, suspend, hibernate and logout actions": "電源オフ、再起動、一時停止、休止状態、ログアウトアクションの確認を要求"
},
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": {
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": ""
},
"Requires DWL compositor": {
"Requires DWL compositor": "DWLコンポジターが必要"
},
@@ -1722,11 +1935,14 @@
"Save Notepad File": "メモ帳ファイルを保存"
},
"Save password": {
"Save password": ""
"Save password": "パスワードを保存"
},
"Saved": {
"Saved": "保存されました"
},
"Saved Configurations": {
"Saved Configurations": ""
},
"Scale DankBar font sizes independently": {
"Scale DankBar font sizes independently": "Dank Barのフォントサイズを個別に調整"
},
@@ -1736,6 +1952,9 @@
"Scan": {
"Scan": "スキャン"
},
"Scanning...": {
"Scanning...": ""
},
"Science": {
"Science": "科学"
},
@@ -1757,12 +1976,21 @@
"Search plugins...": {
"Search plugins...": "プラグインを検索..."
},
"Search widgets...": {
"Search widgets...": ""
},
"Search...": {
"Search...": "検索..."
},
"Searching...": {
"Searching...": "検索中..."
},
"Secured": {
"Secured": ""
},
"Security": {
"Security": ""
},
"Select Launcher Logo": {
"Select Launcher Logo": "ランチャーロゴを選ぶ"
},
@@ -1775,6 +2003,9 @@
"Select a widget to add to the ": {
"Select a widget to add to the ": "追加するウィジェットを選ぶ "
},
"Select a widget to add. You can add multiple instances of the same widget if needed.": {
"Select a widget to add. You can add multiple instances of the same widget if needed.": ""
},
"Select an image file...": {
"Select an image file...": "画像ファイルを選ぶ..."
},
@@ -1805,6 +2036,9 @@
"Separator": {
"Separator": "区切り"
},
"Server": {
"Server": ""
},
"Set different wallpapers for each connected monitor": {
"Set different wallpapers for each connected monitor": "接続されているモニターごとに異なる壁紙を設定する"
},
@@ -1838,6 +2072,9 @@
"Show Log Out": {
"Show Log Out": "ログアウトを表示"
},
"Show Occupied Workspaces Only": {
"Show Occupied Workspaces Only": ""
},
"Show Power Actions": {
"Show Power Actions": "電源アクションを表示"
},
@@ -1884,7 +2121,7 @@
"Show on-screen display when idle inhibitor state changes": "アイドルインヒビターの状態が変化した時にOSDを表示"
},
"Show on-screen display when media player volume changes": {
"Show on-screen display when media player volume changes": ""
"Show on-screen display when media player volume changes": "メディアプレーヤーの音量が変化したときにOSDを表示"
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": "マイクがミュート/ミュート解除された時にOSDを表示"
@@ -1931,6 +2168,12 @@
"Shutdown": {
"Shutdown": "シャットダウン"
},
"Signal": {
"Signal": ""
},
"Signal:": {
"Signal:": ""
},
"Size": {
"Size": "サイズ"
},
@@ -1949,6 +2192,9 @@
"Spacing": {
"Spacing": "間隔"
},
"Speed": {
"Speed": ""
},
"Spool Area Full": {
"Spool Area Full": "スプールエリアがいっぱい"
},
@@ -1961,6 +2207,9 @@
"Start typing your notes here...": {
"Start typing your notes here...": "ここでメモを入力しましょう..."
},
"State": {
"State": ""
},
"Status": {
"Status": "状態"
},
@@ -2109,7 +2358,10 @@
"Toggle top bar visibility manually (can be controlled via IPC)": "トップバーの表示を手動で切り替えるIPC 経由で制御可能)"
},
"Toggle visibility of this bar configuration": {
"Toggle visibility of this bar configuration": ""
"Toggle visibility of this bar configuration": "このバー構成の可視性を切り替える"
},
"Toggling...": {
"Toggling...": ""
},
"Tomorrow": {
"Tomorrow": "明日"
@@ -2135,9 +2387,15 @@
"Turn off monitors after": {
"Turn off monitors after": "後にモニターの電源を切る"
},
"Unavailable": {
"Unavailable": ""
},
"Uninstall Plugin": {
"Uninstall Plugin": "プラグインをアンインストール"
},
"Unknown": {
"Unknown": ""
},
"Unpin from Dock": {
"Unpin from Dock": "ドックから固定を解除"
},
@@ -2225,6 +2483,18 @@
"VPN Connections": {
"VPN Connections": "VPN接続"
},
"VPN Password": {
"VPN Password": ""
},
"VPN configuration updated": {
"VPN configuration updated": ""
},
"VPN deleted": {
"VPN deleted": ""
},
"VPN imported: ": {
"VPN imported: ": ""
},
"VPN status and quick connect": {
"VPN status and quick connect": "VPNステータスとクイック接続"
},
@@ -2264,6 +2534,9 @@
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "音量、明るさ、その他のシステム OSD"
},
"WPA/WPA2": {
"WPA/WPA2": ""
},
"Wallpaper": {
"Wallpaper": "壁紙"
},
@@ -2291,6 +2564,15 @@
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": {
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "有効にすると、アプリはアルファベット順に並べ替えられます。無効にすると、アプリは使用頻度で並べ替えられます。"
},
"Wi-Fi Password": {
"Wi-Fi Password": ""
},
"WiFi": {
"WiFi": ""
},
"WiFi Device": {
"WiFi Device": ""
},
"WiFi disabled": {
"WiFi disabled": "WiFiを無効化にしました"
},
@@ -2301,19 +2583,22 @@
"WiFi is off": "Wi-Fiはオフ中"
},
"Widget Background Color": {
"Widget Background Color": ""
"Widget Background Color": "ウィジェットの背景色"
},
"Widget Management": {
"Widget Management": "ウィジェット管理"
},
"Widget Outline": {
"Widget Outline": ""
},
"Widget Style": {
"Widget Style": ""
"Widget Style": "ウィジェットのスタイル"
},
"Widget Styling": {
"Widget Styling": "ウィジェットのスタイル"
},
"Widget Transparency": {
"Widget Transparency": ""
"Widget Transparency": "ウィジェットの透明度"
},
"Widgets": {
"Widgets": "ウィジェット"
@@ -2336,6 +2621,9 @@
"Workspace Switcher": {
"Workspace Switcher": "ワークスペーススイッチャー"
},
"Yes": {
"Yes": ""
},
"You have unsaved changes. Save before closing this tab?": {
"You have unsaved changes. Save before closing this tab?": "保存されていない変更があります。このタブを閉じる前に保存しますか?"
},
@@ -2349,10 +2637,10 @@
"You have unsaved changes. Save before opening a file?": "保存されていない変更があります。ファイルを開く前に保存しますか?"
},
"custom theme file browser title": {
"Select Custom Theme": ""
"Select Custom Theme": "カスタムテーマを選んでください"
},
"dark mode wallpaper file browser title | light mode wallpaper file browser title | wallpaper file browser title": {
"Select Wallpaper": ""
"Select Wallpaper": "壁紙を選んでください"
},
"events": {
"events": "イベント"
@@ -2367,19 +2655,19 @@
"official": "公式"
},
"profile image file browser title": {
"Select Profile Image": ""
"Select Profile Image": "プロファイル画像を選んでください"
},
"settings window title": {
"Settings": ""
"Settings": "設定"
},
"sysmon window title": {
"System Monitor": ""
"System Monitor": "システムモニタ"
},
"update dms for NM integration.": {
"update dms for NM integration.": "NM統合のためにDMSを更新します。"
},
"wallpaper directory file browser title": {
"Select Wallpaper Directory": ""
"Select Wallpaper Directory": "壁紙のディレクトリを選んでください"
},
"• Install only from trusted sources": {
"• Install only from trusted sources": "• 信頼できるソースからのみインストールする"

View File

@@ -1,12 +1,18 @@
{
"%1 adapter(s), none connected": {
"%1 adapter(s), none connected": ""
},
"%1 characters": {
"%1 characters": "%1 znaków"
},
"%1 connected": {
"%1 connected": ""
},
"%1 display(s)": {
"%1 display(s)": ""
"%1 display(s)": "%1 wyświetlaczy"
},
"%1 widgets": {
"%1 widgets": ""
"%1 widgets": "%1 widżetów"
},
"(Unnamed)": {
"(Unnamed)": "(Bez nazwy)"
@@ -50,11 +56,26 @@
"Actions": {
"Actions": "Akcje"
},
"Activate": {
"Activate": ""
},
"Active": {
"Active": ""
},
"Active: ": {
"Active: ": ""
},
"Active: None": {
"Active: None": ""
},
"Adapters": {
"Adapters": ""
},
"Add": {
"Add": "Dodaj"
},
"Add Bar": {
"Add Bar": ""
"Add Bar": "Dodaj pasek"
},
"Add Widget": {
"Add Widget": "Dodaj widżet"
@@ -62,6 +83,9 @@
"Add Widget to ": {
"Add Widget to ": "Dodaj widżet do"
},
"Add Widget to %1 Section": {
"Add Widget to %1 Section": ""
},
"Add a VPN in NetworkManager": {
"Add a VPN in NetworkManager": "Dodaj sieć VPN w NetworkManager"
},
@@ -92,6 +116,9 @@
"Animation Speed": {
"Animation Speed": "Szybkość animacji"
},
"Anonymous Identity": {
"Anonymous Identity": ""
},
"Anonymous Identity (optional)": {
"Anonymous Identity (optional)": "Tożsamość anonimowa (opcjonalnie)"
},
@@ -134,6 +161,9 @@
"Are you sure you want to suspend the system?": {
"Are you sure you want to suspend the system?": "Czy na pewno chcesz uśpić komputer?"
},
"Audio": {
"Audio": ""
},
"Audio Codec": {
"Audio Codec": "Kodek audio"
},
@@ -149,9 +179,18 @@
"Audio Output Devices (": {
"Audio Output Devices (": "Urządzenia wyjściowe audio ("
},
"Auth": {
"Auth": ""
},
"Auth Type": {
"Auth Type": ""
},
"Authenticate": {
"Authenticate": "Uwierzytelnij"
},
"Authentication": {
"Authentication": ""
},
"Authentication Required": {
"Authentication Required": "Wymagane uwierzytelnienie"
},
@@ -167,6 +206,9 @@
"Authorize service for ": {
"Authorize service for ": "Autoryzuj usługę dla "
},
"Auto": {
"Auto": ""
},
"Auto Location": {
"Auto Location": "Automatyczna lokalizacja"
},
@@ -182,6 +224,9 @@
"Auto-saving...": {
"Auto-saving...": "Automatyczny zapis..."
},
"Autoconnect": {
"Autoconnect": ""
},
"Autoconnect disabled": {
"Autoconnect disabled": "Automatyczne łączenie wyłączone"
},
@@ -224,23 +269,32 @@
"Available Layouts": {
"Available Layouts": "Dostępne układy"
},
"Available Networks": {
"Available Networks": ""
},
"Available Plugins": {
"Available Plugins": "Dostępne wtyczki"
},
"Available Screens (": {
"Available Screens (": "Dostępne ekrany ("
},
"BSSID": {
"BSSID": ""
},
"Back": {
"Back": "Wstecz"
},
"Backend": {
"Backend": ""
},
"Balanced palette with focused accents (default).": {
"Balanced palette with focused accents (default).": "Zrównoważona paleta ze skupionymi akcentami (domyślnie)."
},
"Bar Configurations": {
"Bar Configurations": ""
"Bar Configurations": "Konfiguracje pasków"
},
"Bar Transparency": {
"Bar Transparency": ""
"Bar Transparency": "Przezroczystość paska"
},
"Battery": {
"Battery": "Bateria"
@@ -251,6 +305,9 @@
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": {
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": "Powiąż ekran blokady z sygnałami dbus z loginctl. Wyłącz, jeśli używasz zewnętrznego ekranu blokady"
},
"Bluetooth": {
"Bluetooth": ""
},
"Bluetooth Icon": {
"Bluetooth Icon": "Ikona Bluetooth"
},
@@ -341,17 +398,23 @@
"Center Tiling": {
"Center Tiling": "Płytki środkowe"
},
"Certificate Password": {
"Certificate Password": ""
},
"Change bar appearance": {
"Change bar appearance": ""
"Change bar appearance": "Zmień wygląd paska"
},
"Changes:": {
"Changes:": "Zmiany:"
},
"Channel": {
"Channel": ""
},
"Check for system updates": {
"Check for system updates": "Sprawdź aktualizacje systemu"
},
"Choose Color": {
"Choose Color": ""
"Choose Color": "Wybierz kolor"
},
"Choose Launcher Logo Color": {
"Choose Launcher Logo Color": "Wybierz kolor logo launchera"
@@ -360,7 +423,7 @@
"Choose icon": "Wybierz ikonę"
},
"Choose the background color for widgets": {
"Choose the background color for widgets": ""
"Choose the background color for widgets": "Wybierz kolor tła dla widżetów"
},
"Choose the border accent color": {
"Choose the border accent color": "Wybierz kolor akcentu obramowania"
@@ -368,12 +431,18 @@
"Choose the logo displayed on the launcher button in DankBar": {
"Choose the logo displayed on the launcher button in DankBar": "Wybierz logo wyświetlane na przycisku launchera w DankBar"
},
"Choose the widget outline accent color": {
"Choose the widget outline accent color": ""
},
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "Wybierz, gdzie na ekranie mają pojawiać się powiadomienia"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": "Wybierz miejsce wyświetlania informacji na ekranie"
},
"Cipher": {
"Cipher": ""
},
"Clear": {
"Clear": "Wyczyść"
},
@@ -383,6 +452,9 @@
"Clear All History?": {
"Clear All History?": "Usunąć całą historię?"
},
"Click Import to add a .ovpn or .conf": {
"Click Import to add a .ovpn or .conf": ""
},
"Clipboard History": {
"Clipboard History": "Historia schowka"
},
@@ -399,7 +471,7 @@
"Close": "Zamknij"
},
"Close Overview on Launch": {
"Close Overview on Launch": ""
"Close Overview on Launch": "Zamknij podgląd przy uruchomieniu"
},
"Color Override": {
"Color Override": "Nadpisanie koloru"
@@ -470,6 +542,9 @@
"Connect to Wi-Fi": {
"Connect to Wi-Fi": "Połącz z siecią Wi-Fi"
},
"Connected": {
"Connected": ""
},
"Connected Displays": {
"Connected Displays": "Podłączone ekrany"
},
@@ -617,6 +692,12 @@
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": {
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": "Del: Wyczyść • Shift+Del: Wyczyść wszystko • 1-9: Akcje • F10: Pomoc • Esc: Zamknij"
},
"Delete": {
"Delete": ""
},
"Delete VPN": {
"Delete VPN": ""
},
"Derives colors that closely match the underlying image.": {
"Derives colors that closely match the underlying image.": "Uzyskuje kolory, które ściśle pasują do obrazu bazowego."
},
@@ -641,6 +722,9 @@
"Disconnect": {
"Disconnect": "Rozłącz"
},
"Disconnected": {
"Disconnected": ""
},
"Disconnected from WiFi": {
"Disconnected from WiFi": "Rozłączono z Wi-Fi"
},
@@ -654,7 +738,7 @@
"Dismiss": "Odrzuć"
},
"Display Assignment": {
"Display Assignment": ""
"Display Assignment": "Przypisanie wyświetlacza"
},
"Display Name Format": {
"Display Name Format": "Format nazwy wyświetlanej"
@@ -674,6 +758,9 @@
"Display currently focused application title": {
"Display currently focused application title": "Wyświetlaj tytuł aktywnej aplikacji"
},
"Display only workspaces that contain windows": {
"Display only workspaces that contain windows": ""
},
"Display power menu actions in a grid instead of a list": {
"Display power menu actions in a grid instead of a list": "Wyświetl elementy menu zasilania w siatce zamiast listy"
},
@@ -716,6 +803,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "Przeciągnij widżety, aby zmienić kolejność w sekcjach. Użyj ikony oka, aby ukryć/pokazać widżety (zachowując odstępy), lub X, aby je całkowicie usunąć."
},
"Driver": {
"Driver": ""
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": "Powiel tapetę z rozmyciem"
},
@@ -738,7 +828,7 @@
"Enable Autoconnect": "Włącz automatyczne łączenie"
},
"Enable Bar": {
"Enable Bar": ""
"Enable Bar": "Włącz pasek"
},
"Enable GPU Temperature": {
"Enable GPU Temperature": "Pokaż temperaturę GPU"
@@ -800,9 +890,15 @@
"Enter this passkey on ": {
"Enter this passkey on ": "Wprowadź ten klucz dostępu "
},
"Enterprise": {
"Enterprise": ""
},
"Error": {
"Error": "Błąd"
},
"Ethernet": {
"Ethernet": ""
},
"Exclusive Zone Offset": {
"Exclusive Zone Offset": "Przesunięcie strefy wyłączności"
},
@@ -812,6 +908,12 @@
"F1/I: Toggle • F10: Help": {
"F1/I: Toggle • F10: Help": "F1/I: Przełącz • F10: Pomoc"
},
"Fade grace period": {
"Fade grace period": ""
},
"Fade to lock screen": {
"Fade to lock screen": ""
},
"Failed to activate configuration": {
"Failed to activate configuration": "Nie udało się aktywować konfiguracji"
},
@@ -827,6 +929,9 @@
"Failed to connect to ": {
"Failed to connect to ": "Nie udało się połączyć z "
},
"Failed to delete VPN": {
"Failed to delete VPN": ""
},
"Failed to disconnect VPN": {
"Failed to disconnect VPN": "Nie udało się rozłączyć z VPN"
},
@@ -839,6 +944,12 @@
"Failed to enable WiFi": {
"Failed to enable WiFi": "Nie udało się włączyć Wi-Fi"
},
"Failed to import VPN": {
"Failed to import VPN": ""
},
"Failed to load VPN config": {
"Failed to load VPN config": ""
},
"Failed to pause printer": {
"Failed to pause printer": "Wstrzymanie drukarki nie powiodło się"
},
@@ -857,6 +968,9 @@
"Failed to start connection to ": {
"Failed to start connection to ": "Nie udało się rozpocząć połączenia z "
},
"Failed to update VPN": {
"Failed to update VPN": ""
},
"Failed to update autoconnect": {
"Failed to update autoconnect": "Nie udało się zaktualizować automatycznego łączenia"
},
@@ -899,6 +1013,9 @@
"Force terminal applications to always use dark color schemes": {
"Force terminal applications to always use dark color schemes": "Wymuś ciemny motyw na aplikacjach terminala"
},
"Forget": {
"Forget": ""
},
"Forget Device": {
"Forget Device": "Zapomnij urządzenie"
},
@@ -911,6 +1028,9 @@
"Format Legend": {
"Format Legend": "Legenda formatowania"
},
"Frequency": {
"Frequency": ""
},
"Fun": {
"Fun": "Zabawa"
},
@@ -944,6 +1064,9 @@
"Goth Corners": {
"Goth Corners": "Gotyckie narożniki"
},
"Gradually fade the screen before locking with a configurable grace period": {
"Gradually fade the screen before locking with a configurable grace period": ""
},
"Graphics": {
"Graphics": "Grafika"
},
@@ -986,6 +1109,18 @@
"High-fidelity palette that preserves source hues.": {
"High-fidelity palette that preserves source hues.": "Wysokiej jakości paleta, która zachowuje źródłowe odcienie."
},
"Hold Duration": {
"Hold Duration": ""
},
"Hold longer to confirm": {
"Hold longer to confirm": ""
},
"Hold to Confirm Power Actions": {
"Hold to Confirm Power Actions": ""
},
"Hold to confirm (%1s)": {
"Hold to confirm (%1s)": ""
},
"Hour": {
"Hour": "Godzina"
},
@@ -998,6 +1133,12 @@
"I Understand": {
"I Understand": "Rozumiem"
},
"IP": {
"IP": ""
},
"IP Address:": {
"IP Address:": ""
},
"Icon Size": {
"Icon Size": "Rozmiar ikony"
},
@@ -1022,6 +1163,12 @@
"Image": {
"Image": "Zdjęcie"
},
"Import": {
"Import": ""
},
"Import VPN": {
"Import VPN": ""
},
"Include Transitions": {
"Include Transitions": "Uwzględnij przejścia"
},
@@ -1035,7 +1182,7 @@
"Individual Batteries": "Pojedyncze akumulatory"
},
"Individual bar configuration": {
"Individual bar configuration": ""
"Individual bar configuration": "Indywidualna konfiguracja paska"
},
"Inhibit idle timeout when audio or video is playing": {
"Inhibit idle timeout when audio or video is playing": "Blokuj limit czasu bezczynności podczas odtwarzania dźwięku lub obrazu"
@@ -1046,6 +1193,9 @@
"Install plugins from the DMS plugin registry": {
"Install plugins from the DMS plugin registry": "Instaluj wtyczki z rejestru wtyczek DMS"
},
"Interface:": {
"Interface:": ""
},
"Interlock Open": {
"Interlock Open": "Blokada otwarta"
},
@@ -1065,13 +1215,13 @@
"Jobs: ": "Zadania: "
},
"Keep Awake": {
"Keep Awake": ""
"Keep Awake": "Utrzymuj aktywność"
},
"Keep Changes": {
"Keep Changes": "Zachowaj zmiany"
},
"Keeping Awake": {
"Keeping Awake": ""
"Keeping Awake": "Utrzymywanie aktywności"
},
"Keyboard Layout Name": {
"Keyboard Layout Name": "Nazwa układu klawiatury"
@@ -1133,6 +1283,9 @@
"Loading plugins...": {
"Loading plugins...": "Ładowanie wtyczek..."
},
"Loading...": {
"Loading...": ""
},
"Location Search": {
"Location Search": "Wyszukiwanie lokalizacji"
},
@@ -1160,11 +1313,17 @@
"Low Priority": {
"Low Priority": "Niski priorytet"
},
"MAC": {
"MAC": ""
},
"MTU": {
"MTU": ""
},
"Manage and configure plugins for extending DMS functionality": {
"Manage and configure plugins for extending DMS functionality": "Zarządzaj i konfiguruj wtyczki rozszerzające funkcjonalność DMS"
},
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": {
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": ""
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": "Zarządzaj maksymalnie 4 niezależnymi konfiguracjami pasków. Każdy pasek ma własną pozycję, widżety, styl i przypisanie do wyświetlacza."
},
"Manual Coordinates": {
"Manual Coordinates": "Ręczne współrzędne"
@@ -1230,7 +1389,7 @@
"Media Players (": "Odtwarzacze multimediów ("
},
"Media Volume OSD": {
"Media Volume OSD": ""
"Media Volume OSD": "OSD głośności multimediów"
},
"Memory": {
"Memory": "Pamięć"
@@ -1248,7 +1407,7 @@
"Microphone Mute OSD": "OSD Wyciszenia Mikrofonu"
},
"Middle Section": {
"Middle Section": ""
"Middle Section": "Sekcja środkowa"
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "Minimalna paleta zbudowana wokół jednego odcienia."
@@ -1256,6 +1415,9 @@
"Minute": {
"Minute": "Minuta"
},
"Mode": {
"Mode": ""
},
"Mode:": {
"Mode:": "Tryb:"
},
@@ -1316,6 +1478,9 @@
"Network Speed Monitor": {
"Network Speed Monitor": "Monitor prędkości sieci"
},
"Network Status": {
"Network Status": ""
},
"Network download and upload speed display": {
"Network download and upload speed display": "Wyświetlanie prędkości pobierania i wysyłania sieci"
},
@@ -1334,6 +1499,9 @@
"Night Temperature": {
"Night Temperature": "Temperatura w nocy"
},
"No": {
"No": ""
},
"No Active Players": {
"No Active Players": "Brak aktywnych odtwarzaczy"
},
@@ -1346,12 +1514,18 @@
"No Media": {
"No Media": "Brak mediów"
},
"No VPN profiles": {
"No VPN profiles": ""
},
"No VPN profiles found": {
"No VPN profiles found": "Nie znaleziono profili VPN"
},
"No Weather Data Available": {
"No Weather Data Available": "Brak danych pogodowych"
},
"No adapters": {
"No adapters": ""
},
"No clipboard entries found": {
"No clipboard entries found": "Nie znaleziono wpisów w schowku"
},
@@ -1379,6 +1553,9 @@
"Normal Priority": {
"Normal Priority": "Normalny priorytet"
},
"Not connected": {
"Not connected": ""
},
"Notepad": {
"Notepad": "Notatnik"
},
@@ -1454,6 +1631,15 @@
"Other": {
"Other": "Inne"
},
"Outline Color": {
"Outline Color": ""
},
"Outline Opacity": {
"Outline Opacity": ""
},
"Outline Thickness": {
"Outline Thickness": ""
},
"Output Area Almost Full": {
"Output Area Almost Full": "Obszar wyjściowy prawie pełny"
},
@@ -1466,9 +1652,15 @@
"Overview": {
"Overview": "Podgląd"
},
"Overview of your network connections": {
"Overview of your network connections": ""
},
"Overwrite": {
"Overwrite": "Nadpisz"
},
"PIN": {
"PIN": ""
},
"Padding": {
"Padding": "Dopełnienie"
},
@@ -1532,6 +1724,9 @@
"Play sounds for system events": {
"Play sounds for system events": "Odtwarzaj dźwięki zdarzeń systemowych"
},
"Playback": {
"Playback": ""
},
"Plugged In": {
"Plugged In": "Podłączony"
},
@@ -1583,6 +1778,9 @@
"Power Profile OSD": {
"Power Profile OSD": "OSD Profilu Zasilania"
},
"Preference": {
"Preference": ""
},
"Pressure": {
"Pressure": "Ciśnienie"
},
@@ -1598,6 +1796,9 @@
"Print Server not available": {
"Print Server not available": "Serwer wydruku niedostępny"
},
"Printer": {
"Printer": ""
},
"Printers": {
"Printers": "Drukarki"
},
@@ -1607,6 +1808,9 @@
"Privacy Indicator": {
"Privacy Indicator": "Wskaźnik prywatności"
},
"Private Key Password": {
"Private Key Password": ""
},
"Process": {
"Process": "Proces"
},
@@ -1619,6 +1823,9 @@
"Profile image is too large. Please use a smaller image.": {
"Profile image is too large. Please use a smaller image.": "Zdjęcie profilowe jest za duże. Użyj mniejszego zdjęcia."
},
"Protocol": {
"Protocol": ""
},
"Quick access to application launcher": {
"Quick access to application launcher": "Szybki dostęp do uruchamiania aplikacji"
},
@@ -1637,6 +1844,9 @@
"Rain Chance": {
"Rain Chance": "Szansa na deszcz"
},
"Rate": {
"Rate": ""
},
"Reason": {
"Reason": "Powód"
},
@@ -1664,6 +1874,9 @@
"Request confirmation on power off, restart, suspend, hibernate and logout actions": {
"Request confirmation on power off, restart, suspend, hibernate and logout actions": "Poproś o potwierdzenie czynności wyłączenia, ponownego uruchomienia, zawieszenia, hibernacji i wylogowania."
},
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": {
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": ""
},
"Requires DWL compositor": {
"Requires DWL compositor": "Wymaga kompozytora DWL"
},
@@ -1722,11 +1935,14 @@
"Save Notepad File": "Zapisz plik notatnika"
},
"Save password": {
"Save password": ""
"Save password": "Zapisz hasło"
},
"Saved": {
"Saved": "Zapisano"
},
"Saved Configurations": {
"Saved Configurations": ""
},
"Scale DankBar font sizes independently": {
"Scale DankBar font sizes independently": "Skaluj rozmiary czcionek DankBar niezależnie"
},
@@ -1736,6 +1952,9 @@
"Scan": {
"Scan": "Skanuj"
},
"Scanning...": {
"Scanning...": ""
},
"Science": {
"Science": "Nauka"
},
@@ -1757,12 +1976,21 @@
"Search plugins...": {
"Search plugins...": "Szukaj wtyczek..."
},
"Search widgets...": {
"Search widgets...": ""
},
"Search...": {
"Search...": "Szukaj..."
},
"Searching...": {
"Searching...": "Wyszukiwanie..."
},
"Secured": {
"Secured": ""
},
"Security": {
"Security": ""
},
"Select Launcher Logo": {
"Select Launcher Logo": "Wybierz logo launchera"
},
@@ -1775,6 +2003,9 @@
"Select a widget to add to the ": {
"Select a widget to add to the ": "Wybierz widżet do dodania do "
},
"Select a widget to add. You can add multiple instances of the same widget if needed.": {
"Select a widget to add. You can add multiple instances of the same widget if needed.": ""
},
"Select an image file...": {
"Select an image file...": "Wybierz plik obrazu..."
},
@@ -1805,6 +2036,9 @@
"Separator": {
"Separator": "Separator"
},
"Server": {
"Server": ""
},
"Set different wallpapers for each connected monitor": {
"Set different wallpapers for each connected monitor": "Ustaw różne tapety dla każdego podłączonego monitora"
},
@@ -1838,6 +2072,9 @@
"Show Log Out": {
"Show Log Out": "Pokaż wylogowanie"
},
"Show Occupied Workspaces Only": {
"Show Occupied Workspaces Only": ""
},
"Show Power Actions": {
"Show Power Actions": "Pokaż akcje zasilania"
},
@@ -1884,7 +2121,7 @@
"Show on-screen display when idle inhibitor state changes": "Wyświetlaj powiadomienie ekranowe przy zmianie stanu inhibitora bezczynności"
},
"Show on-screen display when media player volume changes": {
"Show on-screen display when media player volume changes": ""
"Show on-screen display when media player volume changes": "Pokaż OSD przy zmianie głośności multimediów"
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": "Wyświetlaj powiadomienie ekranowe przy wyciszaniu/włączaniu mikrofonu"
@@ -1931,6 +2168,12 @@
"Shutdown": {
"Shutdown": "Wyłączenie"
},
"Signal": {
"Signal": ""
},
"Signal:": {
"Signal:": ""
},
"Size": {
"Size": "Rozmiar"
},
@@ -1949,6 +2192,9 @@
"Spacing": {
"Spacing": "Odstępy"
},
"Speed": {
"Speed": ""
},
"Spool Area Full": {
"Spool Area Full": "Obszar buforowania pełny"
},
@@ -1961,6 +2207,9 @@
"Start typing your notes here...": {
"Start typing your notes here...": "Zacznij pisać swoje notatki tutaj..."
},
"State": {
"State": ""
},
"Status": {
"Status": "Status"
},
@@ -2109,7 +2358,10 @@
"Toggle top bar visibility manually (can be controlled via IPC)": "Ręczne przełączanie widoczności górnego paska (można kontrolować przez IPC)"
},
"Toggle visibility of this bar configuration": {
"Toggle visibility of this bar configuration": ""
"Toggle visibility of this bar configuration": "Przełącz widoczność tej konfiguracji paska"
},
"Toggling...": {
"Toggling...": ""
},
"Tomorrow": {
"Tomorrow": "Jutro"
@@ -2135,9 +2387,15 @@
"Turn off monitors after": {
"Turn off monitors after": "Wyłącz monitory po"
},
"Unavailable": {
"Unavailable": ""
},
"Uninstall Plugin": {
"Uninstall Plugin": "Odinstaluj wtyczkę"
},
"Unknown": {
"Unknown": ""
},
"Unpin from Dock": {
"Unpin from Dock": "Odepnij z doku"
},
@@ -2225,6 +2483,18 @@
"VPN Connections": {
"VPN Connections": "Połączenia VPN"
},
"VPN Password": {
"VPN Password": ""
},
"VPN configuration updated": {
"VPN configuration updated": ""
},
"VPN deleted": {
"VPN deleted": ""
},
"VPN imported: ": {
"VPN imported: ": ""
},
"VPN status and quick connect": {
"VPN status and quick connect": "Status VPN i szybkie połączenie"
},
@@ -2264,6 +2534,9 @@
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "Głośność, jasność i inne systemowe menu ekranowe"
},
"WPA/WPA2": {
"WPA/WPA2": ""
},
"Wallpaper": {
"Wallpaper": "Tapeta"
},
@@ -2291,6 +2564,15 @@
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": {
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "Po włączeniu aplikacje są sortowane alfabetycznie. Po wyłączeniu aplikacje są sortowane według częstotliwości użytkowania."
},
"Wi-Fi Password": {
"Wi-Fi Password": ""
},
"WiFi": {
"WiFi": ""
},
"WiFi Device": {
"WiFi Device": ""
},
"WiFi disabled": {
"WiFi disabled": "WiFi wyłączone"
},
@@ -2301,19 +2583,22 @@
"WiFi is off": "WiFi jest wyłączone"
},
"Widget Background Color": {
"Widget Background Color": ""
"Widget Background Color": "Kolor tła widżetu"
},
"Widget Management": {
"Widget Management": "Zarządzanie widżetami"
},
"Widget Outline": {
"Widget Outline": ""
},
"Widget Style": {
"Widget Style": ""
"Widget Style": "Styl widżetu"
},
"Widget Styling": {
"Widget Styling": "Stylizacja widżetów"
},
"Widget Transparency": {
"Widget Transparency": ""
"Widget Transparency": "Przezroczystość widżetu"
},
"Widgets": {
"Widgets": "Widżety"
@@ -2336,6 +2621,9 @@
"Workspace Switcher": {
"Workspace Switcher": "Przełącznik obszarów roboczych"
},
"Yes": {
"Yes": ""
},
"You have unsaved changes. Save before closing this tab?": {
"You have unsaved changes. Save before closing this tab?": "Masz niezapisane zmiany. Zapisać przed zamknięciem tej karty?"
},
@@ -2349,10 +2637,10 @@
"You have unsaved changes. Save before opening a file?": "Masz niezapisane zmiany. Zapisać przed otwarciem pliku?"
},
"custom theme file browser title": {
"Select Custom Theme": ""
"Select Custom Theme": "Wybierz niestandardowy motyw"
},
"dark mode wallpaper file browser title | light mode wallpaper file browser title | wallpaper file browser title": {
"Select Wallpaper": ""
"Select Wallpaper": "Wybierz tapetę"
},
"events": {
"events": "wydarzenia"
@@ -2367,19 +2655,19 @@
"official": "oficjalny"
},
"profile image file browser title": {
"Select Profile Image": ""
"Select Profile Image": "Wybierz obraz profilowy"
},
"settings window title": {
"Settings": ""
"Settings": "Ustawienia"
},
"sysmon window title": {
"System Monitor": ""
"System Monitor": "Monitor systemu"
},
"update dms for NM integration.": {
"update dms for NM integration.": "zaktualizuj dms dla integracji z NM."
},
"wallpaper directory file browser title": {
"Select Wallpaper Directory": ""
"Select Wallpaper Directory": "Wybierz katalog z tapetami"
},
"• Install only from trusted sources": {
"• Install only from trusted sources": "• Instaluj tylko z zaufanych źródeł"

View File

@@ -1,7 +1,13 @@
{
"%1 adapter(s), none connected": {
"%1 adapter(s), none connected": ""
},
"%1 characters": {
"%1 characters": "%1 caracteres"
},
"%1 connected": {
"%1 connected": ""
},
"%1 display(s)": {
"%1 display(s)": ""
},
@@ -50,6 +56,21 @@
"Actions": {
"Actions": "Ações"
},
"Activate": {
"Activate": ""
},
"Active": {
"Active": ""
},
"Active: ": {
"Active: ": ""
},
"Active: None": {
"Active: None": ""
},
"Adapters": {
"Adapters": ""
},
"Add": {
"Add": "Adicionar"
},
@@ -62,6 +83,9 @@
"Add Widget to ": {
"Add Widget to ": "Adicionar Widget para "
},
"Add Widget to %1 Section": {
"Add Widget to %1 Section": ""
},
"Add a VPN in NetworkManager": {
"Add a VPN in NetworkManager": "Adicionar um VPN ao NetworkManager"
},
@@ -92,6 +116,9 @@
"Animation Speed": {
"Animation Speed": "Velocidade de Animação"
},
"Anonymous Identity": {
"Anonymous Identity": ""
},
"Anonymous Identity (optional)": {
"Anonymous Identity (optional)": "Identidade Anônima (opcional)"
},
@@ -134,6 +161,9 @@
"Are you sure you want to suspend the system?": {
"Are you sure you want to suspend the system?": "Você tem certeza que deseja suspender o sistema?"
},
"Audio": {
"Audio": ""
},
"Audio Codec": {
"Audio Codec": "Codec de Áudio"
},
@@ -149,9 +179,18 @@
"Audio Output Devices (": {
"Audio Output Devices (": "Dispositivos de Saída de Áudio ("
},
"Auth": {
"Auth": ""
},
"Auth Type": {
"Auth Type": ""
},
"Authenticate": {
"Authenticate": "Autenticar"
},
"Authentication": {
"Authentication": ""
},
"Authentication Required": {
"Authentication Required": "Autenticação Necessária"
},
@@ -167,6 +206,9 @@
"Authorize service for ": {
"Authorize service for ": "Autorizar serviço para "
},
"Auto": {
"Auto": ""
},
"Auto Location": {
"Auto Location": "Localização Automática"
},
@@ -182,6 +224,9 @@
"Auto-saving...": {
"Auto-saving...": "Salvando automáticamente..."
},
"Autoconnect": {
"Autoconnect": ""
},
"Autoconnect disabled": {
"Autoconnect disabled": ""
},
@@ -224,15 +269,24 @@
"Available Layouts": {
"Available Layouts": ""
},
"Available Networks": {
"Available Networks": ""
},
"Available Plugins": {
"Available Plugins": "Plugins disponíveis"
},
"Available Screens (": {
"Available Screens (": "Telas disponíveis("
},
"BSSID": {
"BSSID": ""
},
"Back": {
"Back": "Voltar"
},
"Backend": {
"Backend": ""
},
"Balanced palette with focused accents (default).": {
"Balanced palette with focused accents (default).": "Paleta equilibrada com destaques de cor focados (padrão)."
},
@@ -251,6 +305,9 @@
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": {
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": "Vincular o bloqueio de tela aos sinais do DBus do loginctl. Desative se estiver usando um bloqueio de tela externo"
},
"Bluetooth": {
"Bluetooth": ""
},
"Bluetooth Icon": {
"Bluetooth Icon": "ícone do Bluetooth"
},
@@ -341,12 +398,18 @@
"Center Tiling": {
"Center Tiling": ""
},
"Certificate Password": {
"Certificate Password": ""
},
"Change bar appearance": {
"Change bar appearance": ""
},
"Changes:": {
"Changes:": ""
},
"Channel": {
"Channel": ""
},
"Check for system updates": {
"Check for system updates": "Verificar por atualizações de sistema"
},
@@ -368,12 +431,18 @@
"Choose the logo displayed on the launcher button in DankBar": {
"Choose the logo displayed on the launcher button in DankBar": "Escolher a logo que será exibida no botão do Lançador no DankBar"
},
"Choose the widget outline accent color": {
"Choose the widget outline accent color": ""
},
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "Escolher onde as notificações irão aparecer na tela"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": ""
},
"Cipher": {
"Cipher": ""
},
"Clear": {
"Clear": "Limpar"
},
@@ -383,6 +452,9 @@
"Clear All History?": {
"Clear All History?": "Apagar Todo o Histórico?"
},
"Click Import to add a .ovpn or .conf": {
"Click Import to add a .ovpn or .conf": ""
},
"Clipboard History": {
"Clipboard History": "Histórico da Área de Transferência"
},
@@ -470,6 +542,9 @@
"Connect to Wi-Fi": {
"Connect to Wi-Fi": "Conectar ao Wi-Fi"
},
"Connected": {
"Connected": ""
},
"Connected Displays": {
"Connected Displays": "Telas Conectadas"
},
@@ -617,6 +692,12 @@
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": {
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": "Del: Limpar • Shift+Del: Limpar Tudo • 1-9: Ações • F10: Ajuda • Esc: Fechar"
},
"Delete": {
"Delete": ""
},
"Delete VPN": {
"Delete VPN": ""
},
"Derives colors that closely match the underlying image.": {
"Derives colors that closely match the underlying image.": "Deriva cores que combinam de perto com a imagem de fundo."
},
@@ -641,6 +722,9 @@
"Disconnect": {
"Disconnect": "Desconectar"
},
"Disconnected": {
"Disconnected": ""
},
"Disconnected from WiFi": {
"Disconnected from WiFi": "Disconectado do WiFi"
},
@@ -674,6 +758,9 @@
"Display currently focused application title": {
"Display currently focused application title": "Mostrar título do app em foco"
},
"Display only workspaces that contain windows": {
"Display only workspaces that contain windows": ""
},
"Display power menu actions in a grid instead of a list": {
"Display power menu actions in a grid instead of a list": ""
},
@@ -716,6 +803,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "Arraste Widgets para reordená-los nas seções. Use o ícone de olho para esconder ou mostrá-los (manter o espaçamento), ou clique no X para removê-los por completo."
},
"Driver": {
"Driver": ""
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": ""
},
@@ -800,9 +890,15 @@
"Enter this passkey on ": {
"Enter this passkey on ": ""
},
"Enterprise": {
"Enterprise": ""
},
"Error": {
"Error": ""
},
"Ethernet": {
"Ethernet": ""
},
"Exclusive Zone Offset": {
"Exclusive Zone Offset": "Ajuste da Zona Exclusiva"
},
@@ -812,6 +908,12 @@
"F1/I: Toggle • F10: Help": {
"F1/I: Toggle • F10: Help": "F1/I: Ativar • F10: Ajuda"
},
"Fade grace period": {
"Fade grace period": ""
},
"Fade to lock screen": {
"Fade to lock screen": ""
},
"Failed to activate configuration": {
"Failed to activate configuration": "Erro ao ativar configuração"
},
@@ -827,6 +929,9 @@
"Failed to connect to ": {
"Failed to connect to ": "Erro ao conectar a "
},
"Failed to delete VPN": {
"Failed to delete VPN": ""
},
"Failed to disconnect VPN": {
"Failed to disconnect VPN": "Erro ao desconectar VPN"
},
@@ -839,6 +944,12 @@
"Failed to enable WiFi": {
"Failed to enable WiFi": "Erro ao habilitar WiFi"
},
"Failed to import VPN": {
"Failed to import VPN": ""
},
"Failed to load VPN config": {
"Failed to load VPN config": ""
},
"Failed to pause printer": {
"Failed to pause printer": ""
},
@@ -857,6 +968,9 @@
"Failed to start connection to ": {
"Failed to start connection to ": "Erro ao iniciar conexão a "
},
"Failed to update VPN": {
"Failed to update VPN": ""
},
"Failed to update autoconnect": {
"Failed to update autoconnect": ""
},
@@ -899,6 +1013,9 @@
"Force terminal applications to always use dark color schemes": {
"Force terminal applications to always use dark color schemes": ""
},
"Forget": {
"Forget": ""
},
"Forget Device": {
"Forget Device": "Esquecer Dispositivo"
},
@@ -911,6 +1028,9 @@
"Format Legend": {
"Format Legend": "Formatar Legenda"
},
"Frequency": {
"Frequency": ""
},
"Fun": {
"Fun": "Diversão"
},
@@ -944,6 +1064,9 @@
"Goth Corners": {
"Goth Corners": "Cantos Gotícos"
},
"Gradually fade the screen before locking with a configurable grace period": {
"Gradually fade the screen before locking with a configurable grace period": ""
},
"Graphics": {
"Graphics": "Gráficos"
},
@@ -986,6 +1109,18 @@
"High-fidelity palette that preserves source hues.": {
"High-fidelity palette that preserves source hues.": "Paleta de alta fidelidade que preserva tons da fonte."
},
"Hold Duration": {
"Hold Duration": ""
},
"Hold longer to confirm": {
"Hold longer to confirm": ""
},
"Hold to Confirm Power Actions": {
"Hold to Confirm Power Actions": ""
},
"Hold to confirm (%1s)": {
"Hold to confirm (%1s)": ""
},
"Hour": {
"Hour": "Hora"
},
@@ -998,6 +1133,12 @@
"I Understand": {
"I Understand": "Entendi"
},
"IP": {
"IP": ""
},
"IP Address:": {
"IP Address:": ""
},
"Icon Size": {
"Icon Size": "Tamanho do Ícone"
},
@@ -1022,6 +1163,12 @@
"Image": {
"Image": "Imagem"
},
"Import": {
"Import": ""
},
"Import VPN": {
"Import VPN": ""
},
"Include Transitions": {
"Include Transitions": "Incluir Transições"
},
@@ -1046,6 +1193,9 @@
"Install plugins from the DMS plugin registry": {
"Install plugins from the DMS plugin registry": "Instale plugins a partir do registro de plugins do DMS"
},
"Interface:": {
"Interface:": ""
},
"Interlock Open": {
"Interlock Open": ""
},
@@ -1133,6 +1283,9 @@
"Loading plugins...": {
"Loading plugins...": "Carregando plugins..."
},
"Loading...": {
"Loading...": ""
},
"Location Search": {
"Location Search": "Procurar Localização"
},
@@ -1160,6 +1313,12 @@
"Low Priority": {
"Low Priority": "Baixa Prioridade"
},
"MAC": {
"MAC": ""
},
"MTU": {
"MTU": ""
},
"Manage and configure plugins for extending DMS functionality": {
"Manage and configure plugins for extending DMS functionality": "Gerencia e configure plugins para extender funcionalidades do DMS"
},
@@ -1256,6 +1415,9 @@
"Minute": {
"Minute": "Minuto"
},
"Mode": {
"Mode": ""
},
"Mode:": {
"Mode:": "Modo:"
},
@@ -1316,6 +1478,9 @@
"Network Speed Monitor": {
"Network Speed Monitor": "Monitor de Velocidade da Rede"
},
"Network Status": {
"Network Status": ""
},
"Network download and upload speed display": {
"Network download and upload speed display": "Monitor de velocidades de download e upload da rede"
},
@@ -1334,6 +1499,9 @@
"Night Temperature": {
"Night Temperature": "Temperatura Noturna"
},
"No": {
"No": ""
},
"No Active Players": {
"No Active Players": "Sem Tocadores Ativos"
},
@@ -1346,12 +1514,18 @@
"No Media": {
"No Media": "Sem Mídia"
},
"No VPN profiles": {
"No VPN profiles": ""
},
"No VPN profiles found": {
"No VPN profiles found": "Nenhum perfil de VPN encontrado"
},
"No Weather Data Available": {
"No Weather Data Available": "Informações de Clima não dispóniveis"
},
"No adapters": {
"No adapters": ""
},
"No clipboard entries found": {
"No clipboard entries found": "Área de transferência vazia"
},
@@ -1379,6 +1553,9 @@
"Normal Priority": {
"Normal Priority": "Prioridade Normal"
},
"Not connected": {
"Not connected": ""
},
"Notepad": {
"Notepad": "Bloco de Nota"
},
@@ -1454,6 +1631,15 @@
"Other": {
"Other": ""
},
"Outline Color": {
"Outline Color": ""
},
"Outline Opacity": {
"Outline Opacity": ""
},
"Outline Thickness": {
"Outline Thickness": ""
},
"Output Area Almost Full": {
"Output Area Almost Full": ""
},
@@ -1466,9 +1652,15 @@
"Overview": {
"Overview": "Visão Geral"
},
"Overview of your network connections": {
"Overview of your network connections": ""
},
"Overwrite": {
"Overwrite": "Sobrescrever"
},
"PIN": {
"PIN": ""
},
"Padding": {
"Padding": "Preenchimento"
},
@@ -1532,6 +1724,9 @@
"Play sounds for system events": {
"Play sounds for system events": "Reproduzir sons para eventos do sistema"
},
"Playback": {
"Playback": ""
},
"Plugged In": {
"Plugged In": "Conectado"
},
@@ -1583,6 +1778,9 @@
"Power Profile OSD": {
"Power Profile OSD": ""
},
"Preference": {
"Preference": ""
},
"Pressure": {
"Pressure": "Pressão"
},
@@ -1598,6 +1796,9 @@
"Print Server not available": {
"Print Server not available": ""
},
"Printer": {
"Printer": ""
},
"Printers": {
"Printers": ""
},
@@ -1607,6 +1808,9 @@
"Privacy Indicator": {
"Privacy Indicator": "Indicador de Privacidade"
},
"Private Key Password": {
"Private Key Password": ""
},
"Process": {
"Process": "Processo"
},
@@ -1619,6 +1823,9 @@
"Profile image is too large. Please use a smaller image.": {
"Profile image is too large. Please use a smaller image.": "A imagem de perfil é muito grande. Por favor use uma imagem menor."
},
"Protocol": {
"Protocol": ""
},
"Quick access to application launcher": {
"Quick access to application launcher": "Acesso rápido ao lançador de aplicativos"
},
@@ -1637,6 +1844,9 @@
"Rain Chance": {
"Rain Chance": "Chance de Chuva"
},
"Rate": {
"Rate": ""
},
"Reason": {
"Reason": ""
},
@@ -1664,6 +1874,9 @@
"Request confirmation on power off, restart, suspend, hibernate and logout actions": {
"Request confirmation on power off, restart, suspend, hibernate and logout actions": "Pedir confirmação ao desligar, reiniciar, suspender, hibernar e encerrar sessão"
},
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": {
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": ""
},
"Requires DWL compositor": {
"Requires DWL compositor": ""
},
@@ -1727,6 +1940,9 @@
"Saved": {
"Saved": "Salvo"
},
"Saved Configurations": {
"Saved Configurations": ""
},
"Scale DankBar font sizes independently": {
"Scale DankBar font sizes independently": "Ajustar tamanho da fonte da DankBar de forma independente"
},
@@ -1736,6 +1952,9 @@
"Scan": {
"Scan": "Escanear"
},
"Scanning...": {
"Scanning...": ""
},
"Science": {
"Science": "Ciência"
},
@@ -1757,12 +1976,21 @@
"Search plugins...": {
"Search plugins...": "Buscar plugins..."
},
"Search widgets...": {
"Search widgets...": ""
},
"Search...": {
"Search...": "Buscar..."
},
"Searching...": {
"Searching...": "Pesquisando..."
},
"Secured": {
"Secured": ""
},
"Security": {
"Security": ""
},
"Select Launcher Logo": {
"Select Launcher Logo": "Selecionar Logo do Lançador"
},
@@ -1775,6 +2003,9 @@
"Select a widget to add to the ": {
"Select a widget to add to the ": "Selecione um widget para adicionar a "
},
"Select a widget to add. You can add multiple instances of the same widget if needed.": {
"Select a widget to add. You can add multiple instances of the same widget if needed.": ""
},
"Select an image file...": {
"Select an image file...": "Selecione um arquivo de imagem..."
},
@@ -1805,6 +2036,9 @@
"Separator": {
"Separator": "Separador"
},
"Server": {
"Server": ""
},
"Set different wallpapers for each connected monitor": {
"Set different wallpapers for each connected monitor": "Use papéis de paredes diferentes para cada monitor conectado"
},
@@ -1838,6 +2072,9 @@
"Show Log Out": {
"Show Log Out": ""
},
"Show Occupied Workspaces Only": {
"Show Occupied Workspaces Only": ""
},
"Show Power Actions": {
"Show Power Actions": "Mostrar Ações de Energia"
},
@@ -1931,6 +2168,12 @@
"Shutdown": {
"Shutdown": ""
},
"Signal": {
"Signal": ""
},
"Signal:": {
"Signal:": ""
},
"Size": {
"Size": "Tamanho"
},
@@ -1949,6 +2192,9 @@
"Spacing": {
"Spacing": "Espaçamento"
},
"Speed": {
"Speed": ""
},
"Spool Area Full": {
"Spool Area Full": ""
},
@@ -1961,6 +2207,9 @@
"Start typing your notes here...": {
"Start typing your notes here...": "Comece a digitar suas anotações aqui..."
},
"State": {
"State": ""
},
"Status": {
"Status": "Status"
},
@@ -2111,6 +2360,9 @@
"Toggle visibility of this bar configuration": {
"Toggle visibility of this bar configuration": ""
},
"Toggling...": {
"Toggling...": ""
},
"Tomorrow": {
"Tomorrow": "Amanhã"
},
@@ -2135,9 +2387,15 @@
"Turn off monitors after": {
"Turn off monitors after": "Desligar monitores depois de"
},
"Unavailable": {
"Unavailable": ""
},
"Uninstall Plugin": {
"Uninstall Plugin": "Desinstalar Plugin"
},
"Unknown": {
"Unknown": ""
},
"Unpin from Dock": {
"Unpin from Dock": "Desafixar do Dock"
},
@@ -2225,6 +2483,18 @@
"VPN Connections": {
"VPN Connections": "Conexões de VPN"
},
"VPN Password": {
"VPN Password": ""
},
"VPN configuration updated": {
"VPN configuration updated": ""
},
"VPN deleted": {
"VPN deleted": ""
},
"VPN imported: ": {
"VPN imported: ": ""
},
"VPN status and quick connect": {
"VPN status and quick connect": "Status de VPN e conexão rápida"
},
@@ -2264,6 +2534,9 @@
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "Volume, brilho, e outros OSDs do sistema"
},
"WPA/WPA2": {
"WPA/WPA2": ""
},
"Wallpaper": {
"Wallpaper": "Papel de parede"
},
@@ -2291,6 +2564,15 @@
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": {
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "Quando ativado, apps são ordenados alfabeticamente. Quando desativado, apps são ordenados por frequência de uso."
},
"Wi-Fi Password": {
"Wi-Fi Password": ""
},
"WiFi": {
"WiFi": ""
},
"WiFi Device": {
"WiFi Device": ""
},
"WiFi disabled": {
"WiFi disabled": "WiFi desativado"
},
@@ -2306,6 +2588,9 @@
"Widget Management": {
"Widget Management": "Gerenciamento de Widgets"
},
"Widget Outline": {
"Widget Outline": ""
},
"Widget Style": {
"Widget Style": ""
},
@@ -2336,6 +2621,9 @@
"Workspace Switcher": {
"Workspace Switcher": "Seletor de Áreas de Trabalho"
},
"Yes": {
"Yes": ""
},
"You have unsaved changes. Save before closing this tab?": {
"You have unsaved changes. Save before closing this tab?": "Você tem mudanças não salvas. Salvar antes de fechar esta guia?"
},

View File

@@ -1,12 +1,18 @@
{
"%1 adapter(s), none connected": {
"%1 adapter(s), none connected": ""
},
"%1 characters": {
"%1 characters": "%1 karakter"
},
"%1 connected": {
"%1 connected": ""
},
"%1 display(s)": {
"%1 display(s)": ""
"%1 display(s)": "%1 ekran"
},
"%1 widgets": {
"%1 widgets": ""
"%1 widgets": "%1 widget"
},
"(Unnamed)": {
"(Unnamed)": "(İsimsiz)"
@@ -50,11 +56,26 @@
"Actions": {
"Actions": "Eylemler"
},
"Activate": {
"Activate": ""
},
"Active": {
"Active": ""
},
"Active: ": {
"Active: ": ""
},
"Active: None": {
"Active: None": ""
},
"Adapters": {
"Adapters": ""
},
"Add": {
"Add": "Ekle"
},
"Add Bar": {
"Add Bar": ""
"Add Bar": "Bar Ekle"
},
"Add Widget": {
"Add Widget": "Widget Ekle"
@@ -62,6 +83,9 @@
"Add Widget to ": {
"Add Widget to ": "Şuraya Widget ekle:"
},
"Add Widget to %1 Section": {
"Add Widget to %1 Section": ""
},
"Add a VPN in NetworkManager": {
"Add a VPN in NetworkManager": "NetworkManager'da VPN ekle"
},
@@ -92,6 +116,9 @@
"Animation Speed": {
"Animation Speed": "Animasyon Hızı"
},
"Anonymous Identity": {
"Anonymous Identity": ""
},
"Anonymous Identity (optional)": {
"Anonymous Identity (optional)": "Anonim Kimlik (isteğe bağlı)"
},
@@ -134,6 +161,9 @@
"Are you sure you want to suspend the system?": {
"Are you sure you want to suspend the system?": "Sistemi askıya almak istediğinizden emin misiniz?"
},
"Audio": {
"Audio": ""
},
"Audio Codec": {
"Audio Codec": "Ses Codec'i"
},
@@ -149,9 +179,18 @@
"Audio Output Devices (": {
"Audio Output Devices (": "Ses Çıkış Aygıtları ("
},
"Auth": {
"Auth": ""
},
"Auth Type": {
"Auth Type": ""
},
"Authenticate": {
"Authenticate": "Kimlik Doğrula"
},
"Authentication": {
"Authentication": ""
},
"Authentication Required": {
"Authentication Required": "Kimlik Doğrulama Gerekli"
},
@@ -167,6 +206,9 @@
"Authorize service for ": {
"Authorize service for ": "Şunun için servisi yetkilendir: "
},
"Auto": {
"Auto": ""
},
"Auto Location": {
"Auto Location": "Otomatik Konum"
},
@@ -182,6 +224,9 @@
"Auto-saving...": {
"Auto-saving...": "Otomatik kaydetme..."
},
"Autoconnect": {
"Autoconnect": ""
},
"Autoconnect disabled": {
"Autoconnect disabled": "Otomatik bağlanma devre dışı"
},
@@ -224,23 +269,32 @@
"Available Layouts": {
"Available Layouts": "Mevcut Düzenler"
},
"Available Networks": {
"Available Networks": ""
},
"Available Plugins": {
"Available Plugins": "Kullanılabilir Eklentiler"
},
"Available Screens (": {
"Available Screens (": "Kullanılabilir Ekranlar ("
},
"BSSID": {
"BSSID": ""
},
"Back": {
"Back": "Geri"
},
"Backend": {
"Backend": ""
},
"Balanced palette with focused accents (default).": {
"Balanced palette with focused accents (default).": "Odaklanmış vurgularla dengeli palet (varsayılan)."
},
"Bar Configurations": {
"Bar Configurations": ""
"Bar Configurations": "Bar Ayarları"
},
"Bar Transparency": {
"Bar Transparency": ""
"Bar Transparency": "Bar Opaklığı"
},
"Battery": {
"Battery": "Batarya"
@@ -251,6 +305,9 @@
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": {
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": "Kilit ekranını loginctl'den gelen dbus sinyallerine bağlayın. Harici bir kilit ekranı kullanıyorsanız devre dışı bırakın"
},
"Bluetooth": {
"Bluetooth": ""
},
"Bluetooth Icon": {
"Bluetooth Icon": "Bluetooth Simgesi"
},
@@ -341,17 +398,23 @@
"Center Tiling": {
"Center Tiling": "Merkez Döşeme"
},
"Certificate Password": {
"Certificate Password": ""
},
"Change bar appearance": {
"Change bar appearance": ""
"Change bar appearance": "Bar görünümünü değiştir"
},
"Changes:": {
"Changes:": "Değişiklikler:"
},
"Channel": {
"Channel": ""
},
"Check for system updates": {
"Check for system updates": "Sistem güncellemelerini kontrol et"
},
"Choose Color": {
"Choose Color": ""
"Choose Color": "Renk Seç"
},
"Choose Launcher Logo Color": {
"Choose Launcher Logo Color": "Başlatıcı Logo Rengini Seçin"
@@ -360,7 +423,7 @@
"Choose icon": "Simge seçin"
},
"Choose the background color for widgets": {
"Choose the background color for widgets": ""
"Choose the background color for widgets": "Widgetlar için arkaplan rengini seç"
},
"Choose the border accent color": {
"Choose the border accent color": "Kenarlık vurgu rengini seç"
@@ -368,12 +431,18 @@
"Choose the logo displayed on the launcher button in DankBar": {
"Choose the logo displayed on the launcher button in DankBar": "DankBar'daki başlatıcı düğmesinde görüntülenen logoyu seçin"
},
"Choose the widget outline accent color": {
"Choose the widget outline accent color": ""
},
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "Bildirim açılır pencerelerinin ekranda nerede görüneceğini seçin"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": "Ekran gösterimlerinin ekranda nerede gösterileceğini seç"
},
"Cipher": {
"Cipher": ""
},
"Clear": {
"Clear": "Temizle"
},
@@ -383,6 +452,9 @@
"Clear All History?": {
"Clear All History?": "Tüm Geçmişi Temizle?"
},
"Click Import to add a .ovpn or .conf": {
"Click Import to add a .ovpn or .conf": ""
},
"Clipboard History": {
"Clipboard History": "Pano Geçmişi"
},
@@ -399,7 +471,7 @@
"Close": "Kapat"
},
"Close Overview on Launch": {
"Close Overview on Launch": ""
"Close Overview on Launch": "Başlatmada Genel Görünümü Kapat"
},
"Color Override": {
"Color Override": "Renk Değiştirme"
@@ -470,6 +542,9 @@
"Connect to Wi-Fi": {
"Connect to Wi-Fi": "Wi-Fi'ye Bağlan"
},
"Connected": {
"Connected": ""
},
"Connected Displays": {
"Connected Displays": "Bağlı Ekranlar"
},
@@ -617,6 +692,12 @@
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": {
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": "Del: Temizle • Shift+Del: Tümünü Temizle • 1-9: Eylemler • F10: Yardım • Esc: Kapat"
},
"Delete": {
"Delete": ""
},
"Delete VPN": {
"Delete VPN": ""
},
"Derives colors that closely match the underlying image.": {
"Derives colors that closely match the underlying image.": "Altta yatan görüntüye çok yakın renkler türetir"
},
@@ -641,6 +722,9 @@
"Disconnect": {
"Disconnect": "Bağlantıyı Kes"
},
"Disconnected": {
"Disconnected": ""
},
"Disconnected from WiFi": {
"Disconnected from WiFi": "WiFi bağlantısı kesildi"
},
@@ -654,7 +738,7 @@
"Dismiss": "Reddet"
},
"Display Assignment": {
"Display Assignment": ""
"Display Assignment": "Ekran Ataması"
},
"Display Name Format": {
"Display Name Format": "Ekran İsim Formatı"
@@ -674,6 +758,9 @@
"Display currently focused application title": {
"Display currently focused application title": "Şu anda odaklanmış uygulamanın başlığını göster"
},
"Display only workspaces that contain windows": {
"Display only workspaces that contain windows": ""
},
"Display power menu actions in a grid instead of a list": {
"Display power menu actions in a grid instead of a list": "Güç menüsü eylemlerini liste yerine ızgara şeklinde göster"
},
@@ -716,6 +803,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "Widget'ları sürükleyerek bölümler içinde yeniden sıralayın. Göz simgesini kullanarak widget'ları gizleyin/gösterin (aralıkları korur) veya X simgesini kullanarak tamamen kaldırın."
},
"Driver": {
"Driver": ""
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": "Duvar kağıdını bulanıklık ile çoğalt"
},
@@ -738,7 +828,7 @@
"Enable Autoconnect": "Otomatik Bağlanmayı Etkinleştir"
},
"Enable Bar": {
"Enable Bar": ""
"Enable Bar": "Barı Etkinleştir"
},
"Enable GPU Temperature": {
"Enable GPU Temperature": "GPU Sıcaklığını Etkinleştir"
@@ -800,9 +890,15 @@
"Enter this passkey on ": {
"Enter this passkey on ": "Bu şifreyi şuraya gir: "
},
"Enterprise": {
"Enterprise": ""
},
"Error": {
"Error": "Hata"
},
"Ethernet": {
"Ethernet": ""
},
"Exclusive Zone Offset": {
"Exclusive Zone Offset": "Özel Bölge Ofseti"
},
@@ -812,6 +908,12 @@
"F1/I: Toggle • F10: Help": {
"F1/I: Toggle • F10: Help": "F1/I: Değiştir • F10: Yardım"
},
"Fade grace period": {
"Fade grace period": ""
},
"Fade to lock screen": {
"Fade to lock screen": ""
},
"Failed to activate configuration": {
"Failed to activate configuration": "Yapılandırma etkinleştirilemedi"
},
@@ -827,6 +929,9 @@
"Failed to connect to ": {
"Failed to connect to ": "Bağlanılamadı "
},
"Failed to delete VPN": {
"Failed to delete VPN": ""
},
"Failed to disconnect VPN": {
"Failed to disconnect VPN": "VPN bağlantısı kesilemedi"
},
@@ -839,6 +944,12 @@
"Failed to enable WiFi": {
"Failed to enable WiFi": "WiFi etkinleştirilemedi"
},
"Failed to import VPN": {
"Failed to import VPN": ""
},
"Failed to load VPN config": {
"Failed to load VPN config": ""
},
"Failed to pause printer": {
"Failed to pause printer": "Yazıcıyı duraklatma başarısız"
},
@@ -857,6 +968,9 @@
"Failed to start connection to ": {
"Failed to start connection to ": "Bağlantı başlatılamadı "
},
"Failed to update VPN": {
"Failed to update VPN": ""
},
"Failed to update autoconnect": {
"Failed to update autoconnect": "Otomatik bağlanma güncellenemedi"
},
@@ -899,6 +1013,9 @@
"Force terminal applications to always use dark color schemes": {
"Force terminal applications to always use dark color schemes": "Terminal uygulamalarının her zaman koyu renk şemalarını kullanmasını zorla"
},
"Forget": {
"Forget": ""
},
"Forget Device": {
"Forget Device": "Aygıtı Unut"
},
@@ -911,6 +1028,9 @@
"Format Legend": {
"Format Legend": "Biçim Açıklaması"
},
"Frequency": {
"Frequency": ""
},
"Fun": {
"Fun": "Eğlence"
},
@@ -944,6 +1064,9 @@
"Goth Corners": {
"Goth Corners": "Gotik Köşeler"
},
"Gradually fade the screen before locking with a configurable grace period": {
"Gradually fade the screen before locking with a configurable grace period": ""
},
"Graphics": {
"Graphics": "Grafik"
},
@@ -986,6 +1109,18 @@
"High-fidelity palette that preserves source hues.": {
"High-fidelity palette that preserves source hues.": "Kaynak tonları koruyan yüksek sadakatli palet"
},
"Hold Duration": {
"Hold Duration": ""
},
"Hold longer to confirm": {
"Hold longer to confirm": ""
},
"Hold to Confirm Power Actions": {
"Hold to Confirm Power Actions": ""
},
"Hold to confirm (%1s)": {
"Hold to confirm (%1s)": ""
},
"Hour": {
"Hour": "Saat"
},
@@ -998,6 +1133,12 @@
"I Understand": {
"I Understand": "Anladım"
},
"IP": {
"IP": ""
},
"IP Address:": {
"IP Address:": ""
},
"Icon Size": {
"Icon Size": "Simge Boyutu"
},
@@ -1022,6 +1163,12 @@
"Image": {
"Image": "Resim"
},
"Import": {
"Import": ""
},
"Import VPN": {
"Import VPN": ""
},
"Include Transitions": {
"Include Transitions": "Geçişleri Dahil Et"
},
@@ -1035,7 +1182,7 @@
"Individual Batteries": "Tekil Piller"
},
"Individual bar configuration": {
"Individual bar configuration": ""
"Individual bar configuration": "Bireysel bar yapılandırması"
},
"Inhibit idle timeout when audio or video is playing": {
"Inhibit idle timeout when audio or video is playing": "Ses veya video oynatılırken boşta kalma süresini engelle"
@@ -1046,6 +1193,9 @@
"Install plugins from the DMS plugin registry": {
"Install plugins from the DMS plugin registry": "DMS eklenti deposundan eklentiler yükle"
},
"Interface:": {
"Interface:": ""
},
"Interlock Open": {
"Interlock Open": "Kilit Açık"
},
@@ -1065,13 +1215,13 @@
"Jobs: ": "İşler:"
},
"Keep Awake": {
"Keep Awake": ""
"Keep Awake": "Uyanık Tut"
},
"Keep Changes": {
"Keep Changes": "Değişiklikleri Tut"
},
"Keeping Awake": {
"Keeping Awake": ""
"Keeping Awake": "Uyanık Tutuluyor"
},
"Keyboard Layout Name": {
"Keyboard Layout Name": "Klavye Düzeni Adı"
@@ -1133,6 +1283,9 @@
"Loading plugins...": {
"Loading plugins...": "Eklentiler yükleniyor..."
},
"Loading...": {
"Loading...": ""
},
"Location Search": {
"Location Search": "Konum Arama"
},
@@ -1160,11 +1313,17 @@
"Low Priority": {
"Low Priority": "Düşük Öncelik"
},
"MAC": {
"MAC": ""
},
"MTU": {
"MTU": ""
},
"Manage and configure plugins for extending DMS functionality": {
"Manage and configure plugins for extending DMS functionality": "DMS işlevselliğini genişletmek için eklentileri yönetin ve yapılandırın"
},
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": {
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": ""
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": "4 adede kadar bağımsız bar yapılandırmasını yönetin. Her barın kendi konumu, widget'ları, stili ve ekran ataması vardır."
},
"Manual Coordinates": {
"Manual Coordinates": "Manuel Koordinatlar"
@@ -1230,7 +1389,7 @@
"Media Players (": "Medya Oynatıcıları ("
},
"Media Volume OSD": {
"Media Volume OSD": ""
"Media Volume OSD": "Medya Ses OSD"
},
"Memory": {
"Memory": "Bellek"
@@ -1248,7 +1407,7 @@
"Microphone Mute OSD": "Mikrofon Sessiz OSD"
},
"Middle Section": {
"Middle Section": ""
"Middle Section": "Orta Bölüm"
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "Tek bir renk tonu etrafında oluşturulmuş minimal palet."
@@ -1256,6 +1415,9 @@
"Minute": {
"Minute": "Dakika"
},
"Mode": {
"Mode": ""
},
"Mode:": {
"Mode:": "Mod:"
},
@@ -1316,6 +1478,9 @@
"Network Speed Monitor": {
"Network Speed Monitor": "Ağ Hız Monitörü"
},
"Network Status": {
"Network Status": ""
},
"Network download and upload speed display": {
"Network download and upload speed display": "Ağ indirme ve yükleme hız gösterimi"
},
@@ -1334,6 +1499,9 @@
"Night Temperature": {
"Night Temperature": "Gece Sıcaklığı"
},
"No": {
"No": ""
},
"No Active Players": {
"No Active Players": "Aktif Oynatıcı Yok"
},
@@ -1346,12 +1514,18 @@
"No Media": {
"No Media": "Medya Yok"
},
"No VPN profiles": {
"No VPN profiles": ""
},
"No VPN profiles found": {
"No VPN profiles found": "VPN profilleri bulunamadı"
},
"No Weather Data Available": {
"No Weather Data Available": "Hava Durumu Verileri Mevcut Değil"
},
"No adapters": {
"No adapters": ""
},
"No clipboard entries found": {
"No clipboard entries found": "Pano girişleri bulunamadı"
},
@@ -1379,6 +1553,9 @@
"Normal Priority": {
"Normal Priority": "Normal Öncelik"
},
"Not connected": {
"Not connected": ""
},
"Notepad": {
"Notepad": "Not Defteri"
},
@@ -1454,6 +1631,15 @@
"Other": {
"Other": "Diğer"
},
"Outline Color": {
"Outline Color": ""
},
"Outline Opacity": {
"Outline Opacity": ""
},
"Outline Thickness": {
"Outline Thickness": ""
},
"Output Area Almost Full": {
"Output Area Almost Full": ıkış Alanı Neredeyse Dolu"
},
@@ -1466,9 +1652,15 @@
"Overview": {
"Overview": "Genel Görünüm"
},
"Overview of your network connections": {
"Overview of your network connections": ""
},
"Overwrite": {
"Overwrite": "Üstüne Yaz"
},
"PIN": {
"PIN": ""
},
"Padding": {
"Padding": "Dolgu"
},
@@ -1532,6 +1724,9 @@
"Play sounds for system events": {
"Play sounds for system events": "Sistem etkinlikleri için ses çal"
},
"Playback": {
"Playback": ""
},
"Plugged In": {
"Plugged In": "Bağlandı"
},
@@ -1583,6 +1778,9 @@
"Power Profile OSD": {
"Power Profile OSD": "Güç Profili OSD"
},
"Preference": {
"Preference": ""
},
"Pressure": {
"Pressure": "Basınç"
},
@@ -1598,6 +1796,9 @@
"Print Server not available": {
"Print Server not available": "Yazıcı Sunucusu Kullanılamıyor"
},
"Printer": {
"Printer": ""
},
"Printers": {
"Printers": "Yazıcılar"
},
@@ -1607,6 +1808,9 @@
"Privacy Indicator": {
"Privacy Indicator": "Gizlilik Göstergesi"
},
"Private Key Password": {
"Private Key Password": ""
},
"Process": {
"Process": "Süreç"
},
@@ -1619,6 +1823,9 @@
"Profile image is too large. Please use a smaller image.": {
"Profile image is too large. Please use a smaller image.": "Profil resmi çok büyük. Lütfen daha küçük bir resim kullanın."
},
"Protocol": {
"Protocol": ""
},
"Quick access to application launcher": {
"Quick access to application launcher": "Uygulama başlatıcısına hızlı erişim"
},
@@ -1637,6 +1844,9 @@
"Rain Chance": {
"Rain Chance": "Yağış İhtimali"
},
"Rate": {
"Rate": ""
},
"Reason": {
"Reason": "Sebep"
},
@@ -1664,6 +1874,9 @@
"Request confirmation on power off, restart, suspend, hibernate and logout actions": {
"Request confirmation on power off, restart, suspend, hibernate and logout actions": "Kapatma, yeniden başlatma, askıya alma, hazırda bekletme ve oturumu kapatma işlemlerinde onay iste"
},
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": {
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": ""
},
"Requires DWL compositor": {
"Requires DWL compositor": "DWL kompozitör gerektirir"
},
@@ -1722,11 +1935,14 @@
"Save Notepad File": "Not Defteri Dosyasını Kaydet"
},
"Save password": {
"Save password": ""
"Save password": "Parolayı kaydet"
},
"Saved": {
"Saved": "Kaydedildi"
},
"Saved Configurations": {
"Saved Configurations": ""
},
"Scale DankBar font sizes independently": {
"Scale DankBar font sizes independently": "DankBar yazı tipi boyutlarını bağımsız olarak ölçeklendir"
},
@@ -1736,6 +1952,9 @@
"Scan": {
"Scan": "Tara"
},
"Scanning...": {
"Scanning...": ""
},
"Science": {
"Science": "Bilim"
},
@@ -1757,12 +1976,21 @@
"Search plugins...": {
"Search plugins...": "Eklentileri ara..."
},
"Search widgets...": {
"Search widgets...": ""
},
"Search...": {
"Search...": "Ara..."
},
"Searching...": {
"Searching...": "Arıyor..."
},
"Secured": {
"Secured": ""
},
"Security": {
"Security": ""
},
"Select Launcher Logo": {
"Select Launcher Logo": "Başlatıcı Logosu Seç"
},
@@ -1775,6 +2003,9 @@
"Select a widget to add to the ": {
"Select a widget to add to the ": "Eklemek için widget seçin: "
},
"Select a widget to add. You can add multiple instances of the same widget if needed.": {
"Select a widget to add. You can add multiple instances of the same widget if needed.": ""
},
"Select an image file...": {
"Select an image file...": "Bir resim dosyası seçin..."
},
@@ -1805,6 +2036,9 @@
"Separator": {
"Separator": "Ayraç"
},
"Server": {
"Server": ""
},
"Set different wallpapers for each connected monitor": {
"Set different wallpapers for each connected monitor": "Bağlı her monitör için farklı duvar kağıtları ayarlayın"
},
@@ -1838,6 +2072,9 @@
"Show Log Out": {
"Show Log Out": ıkışı Göster"
},
"Show Occupied Workspaces Only": {
"Show Occupied Workspaces Only": ""
},
"Show Power Actions": {
"Show Power Actions": "Güç Eylemlerini Göster"
},
@@ -1884,7 +2121,7 @@
"Show on-screen display when idle inhibitor state changes": "Boşta kalma engelleyici durumu değiştiğinde ekran gösterimi göster"
},
"Show on-screen display when media player volume changes": {
"Show on-screen display when media player volume changes": ""
"Show on-screen display when media player volume changes": "Medya oynatıcının ses seviyesi değiştiğinde ekran üstü görüntü göster"
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": "Mikrofon sessize alındığında/sessizden çıkarıldığında ekran gösterimi göster"
@@ -1931,6 +2168,12 @@
"Shutdown": {
"Shutdown": "Kapat"
},
"Signal": {
"Signal": ""
},
"Signal:": {
"Signal:": ""
},
"Size": {
"Size": "Boyut"
},
@@ -1949,6 +2192,9 @@
"Spacing": {
"Spacing": "Boşluk"
},
"Speed": {
"Speed": ""
},
"Spool Area Full": {
"Spool Area Full": "Aktarım Alanı Dolu"
},
@@ -1961,6 +2207,9 @@
"Start typing your notes here...": {
"Start typing your notes here...": "Notunuzu buraya yazmaya başlayın..."
},
"State": {
"State": ""
},
"Status": {
"Status": "Durum"
},
@@ -2109,7 +2358,10 @@
"Toggle top bar visibility manually (can be controlled via IPC)": "Üst çubuğun görünürlüğünü manuel olarak değiştirin (IPC ile kontrol edilebilir)"
},
"Toggle visibility of this bar configuration": {
"Toggle visibility of this bar configuration": ""
"Toggle visibility of this bar configuration": "Bu bar yapılandırmasının görünürlüğünü değiştir"
},
"Toggling...": {
"Toggling...": ""
},
"Tomorrow": {
"Tomorrow": "Yarın"
@@ -2135,9 +2387,15 @@
"Turn off monitors after": {
"Turn off monitors after": "Şu zaman sonra monitörleri kapat"
},
"Unavailable": {
"Unavailable": ""
},
"Uninstall Plugin": {
"Uninstall Plugin": "Eklentiyi Kaldır"
},
"Unknown": {
"Unknown": ""
},
"Unpin from Dock": {
"Unpin from Dock": "Dock'tan Sabitlemeyi Kaldır"
},
@@ -2225,6 +2483,18 @@
"VPN Connections": {
"VPN Connections": "VPN Bağlantıları"
},
"VPN Password": {
"VPN Password": ""
},
"VPN configuration updated": {
"VPN configuration updated": ""
},
"VPN deleted": {
"VPN deleted": ""
},
"VPN imported: ": {
"VPN imported: ": ""
},
"VPN status and quick connect": {
"VPN status and quick connect": "VPN durumu ve hızlı bağlanma"
},
@@ -2264,6 +2534,9 @@
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "Ses, parlaklık ve diğer sistem ekran üstü gösterimleri"
},
"WPA/WPA2": {
"WPA/WPA2": ""
},
"Wallpaper": {
"Wallpaper": "Duvar Kağıdı"
},
@@ -2291,6 +2564,15 @@
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": {
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "Etkinleştirildiğinde, uygulamalar alfabetik olarak sıralanır. Devre dışı bırakıldığında, uygulamalar kullanım sıklığına göre sıralanır."
},
"Wi-Fi Password": {
"Wi-Fi Password": ""
},
"WiFi": {
"WiFi": ""
},
"WiFi Device": {
"WiFi Device": ""
},
"WiFi disabled": {
"WiFi disabled": "WiFi devre dışı"
},
@@ -2301,19 +2583,22 @@
"WiFi is off": "WiFi kapalı"
},
"Widget Background Color": {
"Widget Background Color": ""
"Widget Background Color": "Widget Arkaplan Rengi"
},
"Widget Management": {
"Widget Management": "Widget Yönetimi"
},
"Widget Outline": {
"Widget Outline": ""
},
"Widget Style": {
"Widget Style": ""
"Widget Style": "Widget Stili"
},
"Widget Styling": {
"Widget Styling": "Widget Stili"
},
"Widget Transparency": {
"Widget Transparency": ""
"Widget Transparency": "Widget Opaklığı"
},
"Widgets": {
"Widgets": "Widgetlar"
@@ -2336,6 +2621,9 @@
"Workspace Switcher": {
"Workspace Switcher": "Çalışma Alanı Değiştirici"
},
"Yes": {
"Yes": ""
},
"You have unsaved changes. Save before closing this tab?": {
"You have unsaved changes. Save before closing this tab?": "Kaydedilmemiş değişiklikleriniz var. Sekmeyi kapatmadan önce kaydedelim mi?"
},
@@ -2349,10 +2637,10 @@
"You have unsaved changes. Save before opening a file?": "Kaydedilmemiş değişiklikleriniz var. Yeni dosya açmadan önce kaydedelim mi?"
},
"custom theme file browser title": {
"Select Custom Theme": ""
"Select Custom Theme": "Özel Tema Seç"
},
"dark mode wallpaper file browser title | light mode wallpaper file browser title | wallpaper file browser title": {
"Select Wallpaper": ""
"Select Wallpaper": "Duvar Kağıdı Seç"
},
"events": {
"events": "etkinlikler"
@@ -2367,19 +2655,19 @@
"official": "resmi"
},
"profile image file browser title": {
"Select Profile Image": ""
"Select Profile Image": "Profil Resmi Seç"
},
"settings window title": {
"Settings": ""
"Settings": "Ayarlar"
},
"sysmon window title": {
"System Monitor": ""
"System Monitor": "Sistem Monitörü"
},
"update dms for NM integration.": {
"update dms for NM integration.": "NM entegrasyonu için dms'yi güncelle"
},
"wallpaper directory file browser title": {
"Select Wallpaper Directory": ""
"Select Wallpaper Directory": "Duvar Kağıdı Dizinini Seç"
},
"• Install only from trusted sources": {
"• Install only from trusted sources": "• Yalnızca güvenilir kaynaklardan yükle"

View File

@@ -1,12 +1,18 @@
{
"%1 adapter(s), none connected": {
"%1 adapter(s), none connected": ""
},
"%1 characters": {
"%1 characters": "%1个字符"
},
"%1 connected": {
"%1 connected": ""
},
"%1 display(s)": {
"%1 display(s)": ""
"%1 display(s)": "%1 显示"
},
"%1 widgets": {
"%1 widgets": ""
"%1 widgets": "%1 小挂件"
},
"(Unnamed)": {
"(Unnamed)": "(未命名)"
@@ -50,11 +56,26 @@
"Actions": {
"Actions": "操作"
},
"Activate": {
"Activate": ""
},
"Active": {
"Active": ""
},
"Active: ": {
"Active: ": ""
},
"Active: None": {
"Active: None": ""
},
"Adapters": {
"Adapters": ""
},
"Add": {
"Add": "添加"
},
"Add Bar": {
"Add Bar": ""
"Add Bar": "添加状态栏"
},
"Add Widget": {
"Add Widget": "添加小组件"
@@ -62,6 +83,9 @@
"Add Widget to ": {
"Add Widget to ": "添加小组件到"
},
"Add Widget to %1 Section": {
"Add Widget to %1 Section": ""
},
"Add a VPN in NetworkManager": {
"Add a VPN in NetworkManager": "在 NetworkManager 中添加 VPN"
},
@@ -92,6 +116,9 @@
"Animation Speed": {
"Animation Speed": "动画速度"
},
"Anonymous Identity": {
"Anonymous Identity": ""
},
"Anonymous Identity (optional)": {
"Anonymous Identity (optional)": "匿名身份(可选)"
},
@@ -134,6 +161,9 @@
"Are you sure you want to suspend the system?": {
"Are you sure you want to suspend the system?": "你确定要挂起系统吗?"
},
"Audio": {
"Audio": ""
},
"Audio Codec": {
"Audio Codec": "音频编解码器"
},
@@ -149,9 +179,18 @@
"Audio Output Devices (": {
"Audio Output Devices (": "音频输出设备 ("
},
"Auth": {
"Auth": ""
},
"Auth Type": {
"Auth Type": ""
},
"Authenticate": {
"Authenticate": "认证"
},
"Authentication": {
"Authentication": ""
},
"Authentication Required": {
"Authentication Required": "需要认证"
},
@@ -167,6 +206,9 @@
"Authorize service for ": {
"Authorize service for ": "授权服务 "
},
"Auto": {
"Auto": ""
},
"Auto Location": {
"Auto Location": "自动定位"
},
@@ -182,6 +224,9 @@
"Auto-saving...": {
"Auto-saving...": "自动保存中..."
},
"Autoconnect": {
"Autoconnect": ""
},
"Autoconnect disabled": {
"Autoconnect disabled": "已禁用自动连接"
},
@@ -224,23 +269,32 @@
"Available Layouts": {
"Available Layouts": "可用布局"
},
"Available Networks": {
"Available Networks": ""
},
"Available Plugins": {
"Available Plugins": "可用插件"
},
"Available Screens (": {
"Available Screens (": "可用显示器 ("
},
"BSSID": {
"BSSID": ""
},
"Back": {
"Back": "返回"
},
"Backend": {
"Backend": ""
},
"Balanced palette with focused accents (default).": {
"Balanced palette with focused accents (default).": "均衡配色,强调重点(默认)"
},
"Bar Configurations": {
"Bar Configurations": ""
"Bar Configurations": "Bar状态栏"
},
"Bar Transparency": {
"Bar Transparency": ""
"Bar Transparency": "状态栏透明度"
},
"Battery": {
"Battery": "电池"
@@ -251,6 +305,9 @@
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": {
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": "将锁屏绑定到来自 loginctl 的 dbus 信号。如使用外部锁屏程序,请禁用此项"
},
"Bluetooth": {
"Bluetooth": ""
},
"Bluetooth Icon": {
"Bluetooth Icon": "蓝牙图标"
},
@@ -341,17 +398,23 @@
"Center Tiling": {
"Center Tiling": "中心平铺"
},
"Certificate Password": {
"Certificate Password": ""
},
"Change bar appearance": {
"Change bar appearance": ""
"Change bar appearance": "更改状态栏外观"
},
"Changes:": {
"Changes:": "更改:"
},
"Channel": {
"Channel": ""
},
"Check for system updates": {
"Check for system updates": "检查系统更新"
},
"Choose Color": {
"Choose Color": ""
"Choose Color": "选择颜色"
},
"Choose Launcher Logo Color": {
"Choose Launcher Logo Color": "设置启动器 Logo 颜色"
@@ -360,7 +423,7 @@
"Choose icon": "选择图标"
},
"Choose the background color for widgets": {
"Choose the background color for widgets": ""
"Choose the background color for widgets": "选择小组件背景色"
},
"Choose the border accent color": {
"Choose the border accent color": "选择边框强调色"
@@ -368,12 +431,18 @@
"Choose the logo displayed on the launcher button in DankBar": {
"Choose the logo displayed on the launcher button in DankBar": "选择在 Dank Bar 启动器按钮上显示的 Logo"
},
"Choose the widget outline accent color": {
"Choose the widget outline accent color": ""
},
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "设置通知弹窗的出现位置"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": "选择OSD在屏幕上出现的位置"
},
"Cipher": {
"Cipher": ""
},
"Clear": {
"Clear": "清除"
},
@@ -383,6 +452,9 @@
"Clear All History?": {
"Clear All History?": "清空历史记录?"
},
"Click Import to add a .ovpn or .conf": {
"Click Import to add a .ovpn or .conf": ""
},
"Clipboard History": {
"Clipboard History": "剪切板历史记录"
},
@@ -399,7 +471,7 @@
"Close": "关闭"
},
"Close Overview on Launch": {
"Close Overview on Launch": ""
"Close Overview on Launch": "在启动时关闭概览"
},
"Color Override": {
"Color Override": "覆盖颜色"
@@ -470,6 +542,9 @@
"Connect to Wi-Fi": {
"Connect to Wi-Fi": "连接到Wi-Fi"
},
"Connected": {
"Connected": ""
},
"Connected Displays": {
"Connected Displays": "已连接显示器"
},
@@ -617,6 +692,12 @@
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": {
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": "Del: 清除 • Shift+Del: 清空 • 1-9: 操作 • F10: 帮助 • Esc: 关闭"
},
"Delete": {
"Delete": ""
},
"Delete VPN": {
"Delete VPN": ""
},
"Derives colors that closely match the underlying image.": {
"Derives colors that closely match the underlying image.": "提取与壁纸高度匹配的颜色。"
},
@@ -641,6 +722,9 @@
"Disconnect": {
"Disconnect": "断开连接"
},
"Disconnected": {
"Disconnected": ""
},
"Disconnected from WiFi": {
"Disconnected from WiFi": "Wi-Fi 已断开"
},
@@ -654,7 +738,7 @@
"Dismiss": "忽略"
},
"Display Assignment": {
"Display Assignment": ""
"Display Assignment": "显示布局"
},
"Display Name Format": {
"Display Name Format": "显示名称格式"
@@ -674,6 +758,9 @@
"Display currently focused application title": {
"Display currently focused application title": "显示当前聚焦应用的标题"
},
"Display only workspaces that contain windows": {
"Display only workspaces that contain windows": ""
},
"Display power menu actions in a grid instead of a list": {
"Display power menu actions in a grid instead of a list": "通过网格而非列表显示电源菜单"
},
@@ -716,6 +803,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "拖动组件以在各区块内重新排序。点击眼睛图标可隐藏/显示组件(保留间距),点击 X 可彻底移除。"
},
"Driver": {
"Driver": ""
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": "带模糊效果的壁纸复本"
},
@@ -738,7 +828,7 @@
"Enable Autoconnect": "启用自动连接"
},
"Enable Bar": {
"Enable Bar": ""
"Enable Bar": "启用状态栏"
},
"Enable GPU Temperature": {
"Enable GPU Temperature": "显示 GPU 温度"
@@ -800,9 +890,15 @@
"Enter this passkey on ": {
"Enter this passkey on ": "在该处输入此通行密钥 "
},
"Enterprise": {
"Enterprise": ""
},
"Error": {
"Error": "错误"
},
"Ethernet": {
"Ethernet": ""
},
"Exclusive Zone Offset": {
"Exclusive Zone Offset": "独占区域偏移量"
},
@@ -812,6 +908,12 @@
"F1/I: Toggle • F10: Help": {
"F1/I: Toggle • F10: Help": "F1/I: 切换 • F10: 帮助"
},
"Fade grace period": {
"Fade grace period": ""
},
"Fade to lock screen": {
"Fade to lock screen": ""
},
"Failed to activate configuration": {
"Failed to activate configuration": "无法应用配置"
},
@@ -827,6 +929,9 @@
"Failed to connect to ": {
"Failed to connect to ": "无法连接至 "
},
"Failed to delete VPN": {
"Failed to delete VPN": ""
},
"Failed to disconnect VPN": {
"Failed to disconnect VPN": "断开 VPN 失败"
},
@@ -839,6 +944,12 @@
"Failed to enable WiFi": {
"Failed to enable WiFi": "无法启用 Wi-Fi"
},
"Failed to import VPN": {
"Failed to import VPN": ""
},
"Failed to load VPN config": {
"Failed to load VPN config": ""
},
"Failed to pause printer": {
"Failed to pause printer": "无法暂停打印机"
},
@@ -857,6 +968,9 @@
"Failed to start connection to ": {
"Failed to start connection to ": "无法启动连接至 "
},
"Failed to update VPN": {
"Failed to update VPN": ""
},
"Failed to update autoconnect": {
"Failed to update autoconnect": "无法更新自动连接"
},
@@ -899,6 +1013,9 @@
"Force terminal applications to always use dark color schemes": {
"Force terminal applications to always use dark color schemes": "强制终端应用使用暗色"
},
"Forget": {
"Forget": ""
},
"Forget Device": {
"Forget Device": "取消配对"
},
@@ -911,6 +1028,9 @@
"Format Legend": {
"Format Legend": "参考格式"
},
"Frequency": {
"Frequency": ""
},
"Fun": {
"Fun": "娱乐"
},
@@ -944,6 +1064,9 @@
"Goth Corners": {
"Goth Corners": "哥特风格圆角"
},
"Gradually fade the screen before locking with a configurable grace period": {
"Gradually fade the screen before locking with a configurable grace period": ""
},
"Graphics": {
"Graphics": "图形"
},
@@ -986,6 +1109,18 @@
"High-fidelity palette that preserves source hues.": {
"High-fidelity palette that preserves source hues.": "高保真配色,保留原始色调。"
},
"Hold Duration": {
"Hold Duration": ""
},
"Hold longer to confirm": {
"Hold longer to confirm": ""
},
"Hold to Confirm Power Actions": {
"Hold to Confirm Power Actions": ""
},
"Hold to confirm (%1s)": {
"Hold to confirm (%1s)": ""
},
"Hour": {
"Hour": "小时"
},
@@ -998,6 +1133,12 @@
"I Understand": {
"I Understand": "我明白以上内容"
},
"IP": {
"IP": ""
},
"IP Address:": {
"IP Address:": ""
},
"Icon Size": {
"Icon Size": "图标大小"
},
@@ -1022,6 +1163,12 @@
"Image": {
"Image": "图像"
},
"Import": {
"Import": ""
},
"Import VPN": {
"Import VPN": ""
},
"Include Transitions": {
"Include Transitions": "包含过渡效果"
},
@@ -1035,7 +1182,7 @@
"Individual Batteries": "分别显示电池"
},
"Individual bar configuration": {
"Individual bar configuration": ""
"Individual bar configuration": "单栏配置"
},
"Inhibit idle timeout when audio or video is playing": {
"Inhibit idle timeout when audio or video is playing": "在播放音频或视频时,禁止待机超时"
@@ -1046,6 +1193,9 @@
"Install plugins from the DMS plugin registry": {
"Install plugins from the DMS plugin registry": "从 DMS 插件库安装插件"
},
"Interface:": {
"Interface:": ""
},
"Interlock Open": {
"Interlock Open": "安全联锁已打开"
},
@@ -1065,13 +1215,13 @@
"Jobs: ": "任务: "
},
"Keep Awake": {
"Keep Awake": ""
"Keep Awake": "保持活动"
},
"Keep Changes": {
"Keep Changes": "保持更改"
},
"Keeping Awake": {
"Keeping Awake": ""
"Keeping Awake": "保持活动"
},
"Keyboard Layout Name": {
"Keyboard Layout Name": "键盘布局名称"
@@ -1133,6 +1283,9 @@
"Loading plugins...": {
"Loading plugins...": "正在加载插件…..."
},
"Loading...": {
"Loading...": ""
},
"Location Search": {
"Location Search": "位置搜索"
},
@@ -1160,11 +1313,17 @@
"Low Priority": {
"Low Priority": "次要通知"
},
"MAC": {
"MAC": ""
},
"MTU": {
"MTU": ""
},
"Manage and configure plugins for extending DMS functionality": {
"Manage and configure plugins for extending DMS functionality": "管理和配置插件以扩展 DMS 功能"
},
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": {
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": ""
"Manage up to 4 independent bar configurations. Each bar has its own position, widgets, styling, and display assignment.": "至多可管理4个独立的状态栏配置。每个状态栏都有自己的位置、控件、样式和显示布局。"
},
"Manual Coordinates": {
"Manual Coordinates": "手动设置坐标"
@@ -1230,7 +1389,7 @@
"Media Players (": "媒体播放器 ("
},
"Media Volume OSD": {
"Media Volume OSD": ""
"Media Volume OSD": "媒体音量OSD"
},
"Memory": {
"Memory": "内存"
@@ -1248,7 +1407,7 @@
"Microphone Mute OSD": "OSD麦克风静音"
},
"Middle Section": {
"Middle Section": ""
"Middle Section": "中间区域"
},
"Minimal palette built around a single hue.": {
"Minimal palette built around a single hue.": "围绕单一色调构建的简约配色。"
@@ -1256,6 +1415,9 @@
"Minute": {
"Minute": "分钟"
},
"Mode": {
"Mode": ""
},
"Mode:": {
"Mode:": "模式:"
},
@@ -1316,6 +1478,9 @@
"Network Speed Monitor": {
"Network Speed Monitor": "实时网速显示"
},
"Network Status": {
"Network Status": ""
},
"Network download and upload speed display": {
"Network download and upload speed display": "显示网络下载与上传速度"
},
@@ -1334,6 +1499,9 @@
"Night Temperature": {
"Night Temperature": "夜间色温"
},
"No": {
"No": ""
},
"No Active Players": {
"No Active Players": "没有正在播放的音频"
},
@@ -1346,12 +1514,18 @@
"No Media": {
"No Media": "当前无播放内容"
},
"No VPN profiles": {
"No VPN profiles": ""
},
"No VPN profiles found": {
"No VPN profiles found": "未找到 VPN 配置"
},
"No Weather Data Available": {
"No Weather Data Available": "暂无天气数据"
},
"No adapters": {
"No adapters": ""
},
"No clipboard entries found": {
"No clipboard entries found": "无可用记录"
},
@@ -1379,6 +1553,9 @@
"Normal Priority": {
"Normal Priority": "标准通知"
},
"Not connected": {
"Not connected": ""
},
"Notepad": {
"Notepad": "便签"
},
@@ -1454,6 +1631,15 @@
"Other": {
"Other": "其他"
},
"Outline Color": {
"Outline Color": ""
},
"Outline Opacity": {
"Outline Opacity": ""
},
"Outline Thickness": {
"Outline Thickness": ""
},
"Output Area Almost Full": {
"Output Area Almost Full": "出纸盒即将饱和"
},
@@ -1466,9 +1652,15 @@
"Overview": {
"Overview": "概览"
},
"Overview of your network connections": {
"Overview of your network connections": ""
},
"Overwrite": {
"Overwrite": "覆盖"
},
"PIN": {
"PIN": ""
},
"Padding": {
"Padding": "内边距"
},
@@ -1532,6 +1724,9 @@
"Play sounds for system events": {
"Play sounds for system events": "为系统事件播放声音"
},
"Playback": {
"Playback": ""
},
"Plugged In": {
"Plugged In": "连接电源"
},
@@ -1583,6 +1778,9 @@
"Power Profile OSD": {
"Power Profile OSD": "OSD电源配置"
},
"Preference": {
"Preference": ""
},
"Pressure": {
"Pressure": "气压"
},
@@ -1598,6 +1796,9 @@
"Print Server not available": {
"Print Server not available": "打印服务不可用"
},
"Printer": {
"Printer": ""
},
"Printers": {
"Printers": "打印机"
},
@@ -1607,6 +1808,9 @@
"Privacy Indicator": {
"Privacy Indicator": "隐私指示器"
},
"Private Key Password": {
"Private Key Password": ""
},
"Process": {
"Process": "进程"
},
@@ -1619,6 +1823,9 @@
"Profile image is too large. Please use a smaller image.": {
"Profile image is too large. Please use a smaller image.": "个人资料图片过大,请选择更小的图片。"
},
"Protocol": {
"Protocol": ""
},
"Quick access to application launcher": {
"Quick access to application launcher": "快捷访问应用启动器"
},
@@ -1637,6 +1844,9 @@
"Rain Chance": {
"Rain Chance": "降雨概率"
},
"Rate": {
"Rate": ""
},
"Reason": {
"Reason": "原因"
},
@@ -1664,6 +1874,9 @@
"Request confirmation on power off, restart, suspend, hibernate and logout actions": {
"Request confirmation on power off, restart, suspend, hibernate and logout actions": "关机、重启、挂起、休眠和注销前请求确认"
},
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": {
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": ""
},
"Requires DWL compositor": {
"Requires DWL compositor": "需要 DWL 合成器"
},
@@ -1722,11 +1935,14 @@
"Save Notepad File": "保存便签"
},
"Save password": {
"Save password": ""
"Save password": "保存密码"
},
"Saved": {
"Saved": "已保存"
},
"Saved Configurations": {
"Saved Configurations": ""
},
"Scale DankBar font sizes independently": {
"Scale DankBar font sizes independently": "独立调整Dank Bar字体缩放"
},
@@ -1736,6 +1952,9 @@
"Scan": {
"Scan": "扫描"
},
"Scanning...": {
"Scanning...": ""
},
"Science": {
"Science": "科学"
},
@@ -1757,12 +1976,21 @@
"Search plugins...": {
"Search plugins...": "搜索插件中..."
},
"Search widgets...": {
"Search widgets...": ""
},
"Search...": {
"Search...": "搜索中..."
},
"Searching...": {
"Searching...": "检索中..."
},
"Secured": {
"Secured": ""
},
"Security": {
"Security": ""
},
"Select Launcher Logo": {
"Select Launcher Logo": "选择启动器Logo"
},
@@ -1775,6 +2003,9 @@
"Select a widget to add to the ": {
"Select a widget to add to the ": "选择一个小组件添加到 "
},
"Select a widget to add. You can add multiple instances of the same widget if needed.": {
"Select a widget to add. You can add multiple instances of the same widget if needed.": ""
},
"Select an image file...": {
"Select an image file...": "选择一张图片..."
},
@@ -1805,6 +2036,9 @@
"Separator": {
"Separator": "分隔符"
},
"Server": {
"Server": ""
},
"Set different wallpapers for each connected monitor": {
"Set different wallpapers for each connected monitor": "分别为各个显示器设置壁纸"
},
@@ -1838,6 +2072,9 @@
"Show Log Out": {
"Show Log Out": "显示注销"
},
"Show Occupied Workspaces Only": {
"Show Occupied Workspaces Only": ""
},
"Show Power Actions": {
"Show Power Actions": "显示电源操作"
},
@@ -1884,7 +2121,7 @@
"Show on-screen display when idle inhibitor state changes": "当空闲抑制状态改变时显示OSD"
},
"Show on-screen display when media player volume changes": {
"Show on-screen display when media player volume changes": ""
"Show on-screen display when media player volume changes": "当媒体音量改变时显示OSD"
},
"Show on-screen display when microphone is muted/unmuted": {
"Show on-screen display when microphone is muted/unmuted": "当麦克风静音状态切换时显示OSD"
@@ -1931,6 +2168,12 @@
"Shutdown": {
"Shutdown": "关机"
},
"Signal": {
"Signal": ""
},
"Signal:": {
"Signal:": ""
},
"Size": {
"Size": "尺寸"
},
@@ -1949,6 +2192,9 @@
"Spacing": {
"Spacing": "间距"
},
"Speed": {
"Speed": ""
},
"Spool Area Full": {
"Spool Area Full": "打印缓冲区已满"
},
@@ -1961,6 +2207,9 @@
"Start typing your notes here...": {
"Start typing your notes here...": "请在这里记录你的笔记..."
},
"State": {
"State": ""
},
"Status": {
"Status": "状态"
},
@@ -2109,7 +2358,10 @@
"Toggle top bar visibility manually (can be controlled via IPC)": "手动切换顶栏可见性(可通过 IPC 控制)"
},
"Toggle visibility of this bar configuration": {
"Toggle visibility of this bar configuration": ""
"Toggle visibility of this bar configuration": "显示/隐藏当前状态栏配置"
},
"Toggling...": {
"Toggling...": ""
},
"Tomorrow": {
"Tomorrow": "明日"
@@ -2135,9 +2387,15 @@
"Turn off monitors after": {
"Turn off monitors after": "在此时间后关闭显示器"
},
"Unavailable": {
"Unavailable": ""
},
"Uninstall Plugin": {
"Uninstall Plugin": "卸载插件"
},
"Unknown": {
"Unknown": ""
},
"Unpin from Dock": {
"Unpin from Dock": "从程序坞取消固定"
},
@@ -2225,6 +2483,18 @@
"VPN Connections": {
"VPN Connections": "VPN 连接"
},
"VPN Password": {
"VPN Password": ""
},
"VPN configuration updated": {
"VPN configuration updated": ""
},
"VPN deleted": {
"VPN deleted": ""
},
"VPN imported: ": {
"VPN imported: ": ""
},
"VPN status and quick connect": {
"VPN status and quick connect": "VPN 状态与快速连接"
},
@@ -2264,6 +2534,9 @@
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "音量、亮度和其他系统屏幕显示"
},
"WPA/WPA2": {
"WPA/WPA2": ""
},
"Wallpaper": {
"Wallpaper": "壁纸"
},
@@ -2291,6 +2564,15 @@
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": {
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "启用后,应用按字母顺序排序;禁用则按使用频率排序"
},
"Wi-Fi Password": {
"Wi-Fi Password": ""
},
"WiFi": {
"WiFi": ""
},
"WiFi Device": {
"WiFi Device": ""
},
"WiFi disabled": {
"WiFi disabled": "Wi-Fi 已停用"
},
@@ -2301,19 +2583,22 @@
"WiFi is off": "Wi-Fi 已关闭"
},
"Widget Background Color": {
"Widget Background Color": ""
"Widget Background Color": "小挂件背景色"
},
"Widget Management": {
"Widget Management": "小组件管理"
},
"Widget Outline": {
"Widget Outline": ""
},
"Widget Style": {
"Widget Style": ""
"Widget Style": "小挂件风格"
},
"Widget Styling": {
"Widget Styling": "小组件样式"
},
"Widget Transparency": {
"Widget Transparency": ""
"Widget Transparency": "小挂件透明度"
},
"Widgets": {
"Widgets": "小组件"
@@ -2336,6 +2621,9 @@
"Workspace Switcher": {
"Workspace Switcher": "工作区切换器"
},
"Yes": {
"Yes": ""
},
"You have unsaved changes. Save before closing this tab?": {
"You have unsaved changes. Save before closing this tab?": "检测到未保存的更改,是否在关闭此标签页前保存?"
},
@@ -2349,10 +2637,10 @@
"You have unsaved changes. Save before opening a file?": "检测到未保存的更改,是否在打开文件前保存?"
},
"custom theme file browser title": {
"Select Custom Theme": ""
"Select Custom Theme": "选择自定义主题"
},
"dark mode wallpaper file browser title | light mode wallpaper file browser title | wallpaper file browser title": {
"Select Wallpaper": ""
"Select Wallpaper": "选择壁纸"
},
"events": {
"events": "事件"
@@ -2367,19 +2655,19 @@
"official": "官方"
},
"profile image file browser title": {
"Select Profile Image": ""
"Select Profile Image": "选择个人信息图像"
},
"settings window title": {
"Settings": ""
"Settings": "设置"
},
"sysmon window title": {
"System Monitor": ""
"System Monitor": "系统监视器"
},
"update dms for NM integration.": {
"update dms for NM integration.": "更新 DMS 以集成 NM"
},
"wallpaper directory file browser title": {
"Select Wallpaper Directory": ""
"Select Wallpaper Directory": "选择壁纸位置"
},
"• Install only from trusted sources": {
"• Install only from trusted sources": "• 仅从可信来源安装"

View File

@@ -1,7 +1,13 @@
{
"%1 adapter(s), none connected": {
"%1 adapter(s), none connected": ""
},
"%1 characters": {
"%1 characters": "%1 個字元"
},
"%1 connected": {
"%1 connected": ""
},
"%1 display(s)": {
"%1 display(s)": ""
},
@@ -50,6 +56,21 @@
"Actions": {
"Actions": "選項"
},
"Activate": {
"Activate": ""
},
"Active": {
"Active": ""
},
"Active: ": {
"Active: ": ""
},
"Active: None": {
"Active: None": ""
},
"Adapters": {
"Adapters": ""
},
"Add": {
"Add": "新增"
},
@@ -62,6 +83,9 @@
"Add Widget to ": {
"Add Widget to ": "新增部件到 "
},
"Add Widget to %1 Section": {
"Add Widget to %1 Section": ""
},
"Add a VPN in NetworkManager": {
"Add a VPN in NetworkManager": "新增VPN至網路管理器"
},
@@ -92,6 +116,9 @@
"Animation Speed": {
"Animation Speed": "動畫速度"
},
"Anonymous Identity": {
"Anonymous Identity": ""
},
"Anonymous Identity (optional)": {
"Anonymous Identity (optional)": "匿名身分 (可選)"
},
@@ -134,6 +161,9 @@
"Are you sure you want to suspend the system?": {
"Are you sure you want to suspend the system?": "你確定要暫停系統嗎?"
},
"Audio": {
"Audio": ""
},
"Audio Codec": {
"Audio Codec": "音訊編解碼器"
},
@@ -149,9 +179,18 @@
"Audio Output Devices (": {
"Audio Output Devices (": "音訊輸出設備 ("
},
"Auth": {
"Auth": ""
},
"Auth Type": {
"Auth Type": ""
},
"Authenticate": {
"Authenticate": "驗證"
},
"Authentication": {
"Authentication": ""
},
"Authentication Required": {
"Authentication Required": "需要驗證"
},
@@ -167,6 +206,9 @@
"Authorize service for ": {
"Authorize service for ": "授權服務 "
},
"Auto": {
"Auto": ""
},
"Auto Location": {
"Auto Location": "自動定位"
},
@@ -182,6 +224,9 @@
"Auto-saving...": {
"Auto-saving...": "自動保存..."
},
"Autoconnect": {
"Autoconnect": ""
},
"Autoconnect disabled": {
"Autoconnect disabled": "自動連線關閉"
},
@@ -224,15 +269,24 @@
"Available Layouts": {
"Available Layouts": "可用佈局"
},
"Available Networks": {
"Available Networks": ""
},
"Available Plugins": {
"Available Plugins": "可用的插件"
},
"Available Screens (": {
"Available Screens (": "可用的螢幕 ("
},
"BSSID": {
"BSSID": ""
},
"Back": {
"Back": "返回"
},
"Backend": {
"Backend": ""
},
"Balanced palette with focused accents (default).": {
"Balanced palette with focused accents (default).": "顏色平衡且帶有重點點綴的調色板 (預設)。"
},
@@ -251,6 +305,9 @@
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": {
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": "將鎖定畫面綁定到 loginctl 的 dbus 訊號。如果使用外部鎖屏,請停用"
},
"Bluetooth": {
"Bluetooth": ""
},
"Bluetooth Icon": {
"Bluetooth Icon": "藍芽圖示"
},
@@ -341,12 +398,18 @@
"Center Tiling": {
"Center Tiling": "居中平鋪"
},
"Certificate Password": {
"Certificate Password": ""
},
"Change bar appearance": {
"Change bar appearance": ""
},
"Changes:": {
"Changes:": "變更:"
},
"Channel": {
"Channel": ""
},
"Check for system updates": {
"Check for system updates": "檢查系統更新"
},
@@ -368,12 +431,18 @@
"Choose the logo displayed on the launcher button in DankBar": {
"Choose the logo displayed on the launcher button in DankBar": "選擇 DankBar 啟動器按鈕上顯示的 logo"
},
"Choose the widget outline accent color": {
"Choose the widget outline accent color": ""
},
"Choose where notification popups appear on screen": {
"Choose where notification popups appear on screen": "選擇通知彈出視窗在螢幕上出現的位置"
},
"Choose where on-screen displays appear on screen": {
"Choose where on-screen displays appear on screen": "選擇螢幕顯示出現在螢幕上的位置"
},
"Cipher": {
"Cipher": ""
},
"Clear": {
"Clear": "清除"
},
@@ -383,6 +452,9 @@
"Clear All History?": {
"Clear All History?": "清除所有紀錄?"
},
"Click Import to add a .ovpn or .conf": {
"Click Import to add a .ovpn or .conf": ""
},
"Clipboard History": {
"Clipboard History": "剪貼簿歷史"
},
@@ -470,6 +542,9 @@
"Connect to Wi-Fi": {
"Connect to Wi-Fi": "連線到 Wi-Fi"
},
"Connected": {
"Connected": ""
},
"Connected Displays": {
"Connected Displays": "已連接的螢幕"
},
@@ -617,6 +692,12 @@
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": {
"Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close": "Del: 清除 • Shift+Del: 清除所有 • 1-9: 動作 • F10: 幫助 • Esc: 關閉"
},
"Delete": {
"Delete": ""
},
"Delete VPN": {
"Delete VPN": ""
},
"Derives colors that closely match the underlying image.": {
"Derives colors that closely match the underlying image.": "提取與底層圖像高度匹配的顏色。"
},
@@ -641,6 +722,9 @@
"Disconnect": {
"Disconnect": "斷開連線"
},
"Disconnected": {
"Disconnected": ""
},
"Disconnected from WiFi": {
"Disconnected from WiFi": "已中斷 WiFi 連線"
},
@@ -674,6 +758,9 @@
"Display currently focused application title": {
"Display currently focused application title": "顯示目前焦點應用程式的標題"
},
"Display only workspaces that contain windows": {
"Display only workspaces that contain windows": ""
},
"Display power menu actions in a grid instead of a list": {
"Display power menu actions in a grid instead of a list": "以網格而非列表顯示電源選單操作"
},
@@ -716,6 +803,9 @@
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": {
"Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely.": "拖曳部件即可在版塊內重新排序。使用眼睛圖示隱藏/顯示部件 (會保持間距),或使用 X 將其完全移除。"
},
"Driver": {
"Driver": ""
},
"Duplicate Wallpaper with Blur": {
"Duplicate Wallpaper with Blur": "模糊化重複桌布"
},
@@ -800,9 +890,15 @@
"Enter this passkey on ": {
"Enter this passkey on ": ""
},
"Enterprise": {
"Enterprise": ""
},
"Error": {
"Error": "錯誤"
},
"Ethernet": {
"Ethernet": ""
},
"Exclusive Zone Offset": {
"Exclusive Zone Offset": "獨佔區域偏移"
},
@@ -812,6 +908,12 @@
"F1/I: Toggle • F10: Help": {
"F1/I: Toggle • F10: Help": "F1/I: 切換 • F10: 幫助"
},
"Fade grace period": {
"Fade grace period": ""
},
"Fade to lock screen": {
"Fade to lock screen": ""
},
"Failed to activate configuration": {
"Failed to activate configuration": "無法啟動配置"
},
@@ -827,6 +929,9 @@
"Failed to connect to ": {
"Failed to connect to ": "無法連線到 "
},
"Failed to delete VPN": {
"Failed to delete VPN": ""
},
"Failed to disconnect VPN": {
"Failed to disconnect VPN": "無法斷開 VPN"
},
@@ -839,6 +944,12 @@
"Failed to enable WiFi": {
"Failed to enable WiFi": "無法啟用 WiFi"
},
"Failed to import VPN": {
"Failed to import VPN": ""
},
"Failed to load VPN config": {
"Failed to load VPN config": ""
},
"Failed to pause printer": {
"Failed to pause printer": "無法暫停印表機"
},
@@ -857,6 +968,9 @@
"Failed to start connection to ": {
"Failed to start connection to ": "無法啟動連線到 "
},
"Failed to update VPN": {
"Failed to update VPN": ""
},
"Failed to update autoconnect": {
"Failed to update autoconnect": "自動連線更新失敗"
},
@@ -899,6 +1013,9 @@
"Force terminal applications to always use dark color schemes": {
"Force terminal applications to always use dark color schemes": "強制終端應用程式始終使用深色配色方案"
},
"Forget": {
"Forget": ""
},
"Forget Device": {
"Forget Device": "忘記設備"
},
@@ -911,6 +1028,9 @@
"Format Legend": {
"Format Legend": "格式說明"
},
"Frequency": {
"Frequency": ""
},
"Fun": {
"Fun": "有趣的"
},
@@ -944,6 +1064,9 @@
"Goth Corners": {
"Goth Corners": "圓角介面融合"
},
"Gradually fade the screen before locking with a configurable grace period": {
"Gradually fade the screen before locking with a configurable grace period": ""
},
"Graphics": {
"Graphics": "圖形"
},
@@ -986,6 +1109,18 @@
"High-fidelity palette that preserves source hues.": {
"High-fidelity palette that preserves source hues.": "保留來源色調的高保真調色板。"
},
"Hold Duration": {
"Hold Duration": ""
},
"Hold longer to confirm": {
"Hold longer to confirm": ""
},
"Hold to Confirm Power Actions": {
"Hold to Confirm Power Actions": ""
},
"Hold to confirm (%1s)": {
"Hold to confirm (%1s)": ""
},
"Hour": {
"Hour": "小時"
},
@@ -998,6 +1133,12 @@
"I Understand": {
"I Understand": "我了解"
},
"IP": {
"IP": ""
},
"IP Address:": {
"IP Address:": ""
},
"Icon Size": {
"Icon Size": "圖示大小"
},
@@ -1022,6 +1163,12 @@
"Image": {
"Image": "圖片"
},
"Import": {
"Import": ""
},
"Import VPN": {
"Import VPN": ""
},
"Include Transitions": {
"Include Transitions": "包括過渡"
},
@@ -1046,6 +1193,9 @@
"Install plugins from the DMS plugin registry": {
"Install plugins from the DMS plugin registry": "從 DMS 插件註冊表安裝插件"
},
"Interface:": {
"Interface:": ""
},
"Interlock Open": {
"Interlock Open": "聯鎖開啟"
},
@@ -1133,6 +1283,9 @@
"Loading plugins...": {
"Loading plugins...": "讀取插件..."
},
"Loading...": {
"Loading...": ""
},
"Location Search": {
"Location Search": "位置搜尋"
},
@@ -1160,6 +1313,12 @@
"Low Priority": {
"Low Priority": "低優先級"
},
"MAC": {
"MAC": ""
},
"MTU": {
"MTU": ""
},
"Manage and configure plugins for extending DMS functionality": {
"Manage and configure plugins for extending DMS functionality": "管理和配置用於擴展 DMS 功能的插件"
},
@@ -1256,6 +1415,9 @@
"Minute": {
"Minute": "分鐘"
},
"Mode": {
"Mode": ""
},
"Mode:": {
"Mode:": "模式:"
},
@@ -1316,6 +1478,9 @@
"Network Speed Monitor": {
"Network Speed Monitor": "網速監視"
},
"Network Status": {
"Network Status": ""
},
"Network download and upload speed display": {
"Network download and upload speed display": "顯示網路下載跟上傳速度"
},
@@ -1334,6 +1499,9 @@
"Night Temperature": {
"Night Temperature": "夜間色溫"
},
"No": {
"No": ""
},
"No Active Players": {
"No Active Players": "無播放器"
},
@@ -1346,12 +1514,18 @@
"No Media": {
"No Media": "無媒體"
},
"No VPN profiles": {
"No VPN profiles": ""
},
"No VPN profiles found": {
"No VPN profiles found": "未找到 VPN 設定檔"
},
"No Weather Data Available": {
"No Weather Data Available": "沒有氣象數據"
},
"No adapters": {
"No adapters": ""
},
"No clipboard entries found": {
"No clipboard entries found": "剪貼簿無內容"
},
@@ -1379,6 +1553,9 @@
"Normal Priority": {
"Normal Priority": "普通優先級"
},
"Not connected": {
"Not connected": ""
},
"Notepad": {
"Notepad": "筆記本"
},
@@ -1454,6 +1631,15 @@
"Other": {
"Other": "其他"
},
"Outline Color": {
"Outline Color": ""
},
"Outline Opacity": {
"Outline Opacity": ""
},
"Outline Thickness": {
"Outline Thickness": ""
},
"Output Area Almost Full": {
"Output Area Almost Full": "輸出區域即將滿載"
},
@@ -1466,9 +1652,15 @@
"Overview": {
"Overview": "概覽"
},
"Overview of your network connections": {
"Overview of your network connections": ""
},
"Overwrite": {
"Overwrite": "覆寫"
},
"PIN": {
"PIN": ""
},
"Padding": {
"Padding": "內距"
},
@@ -1532,6 +1724,9 @@
"Play sounds for system events": {
"Play sounds for system events": "播放系統事件的音效"
},
"Playback": {
"Playback": ""
},
"Plugged In": {
"Plugged In": "已插入"
},
@@ -1583,6 +1778,9 @@
"Power Profile OSD": {
"Power Profile OSD": "電源設定檔 OSD"
},
"Preference": {
"Preference": ""
},
"Pressure": {
"Pressure": "氣壓"
},
@@ -1598,6 +1796,9 @@
"Print Server not available": {
"Print Server not available": "列印伺服器無法使用"
},
"Printer": {
"Printer": ""
},
"Printers": {
"Printers": "印表機"
},
@@ -1607,6 +1808,9 @@
"Privacy Indicator": {
"Privacy Indicator": "隱私指示器"
},
"Private Key Password": {
"Private Key Password": ""
},
"Process": {
"Process": "程序"
},
@@ -1619,6 +1823,9 @@
"Profile image is too large. Please use a smaller image.": {
"Profile image is too large. Please use a smaller image.": "個人資料圖片太大。請使用較小的圖片。"
},
"Protocol": {
"Protocol": ""
},
"Quick access to application launcher": {
"Quick access to application launcher": "快速存取應用程式啟動器"
},
@@ -1637,6 +1844,9 @@
"Rain Chance": {
"Rain Chance": "降雨機率"
},
"Rate": {
"Rate": ""
},
"Reason": {
"Reason": "原因"
},
@@ -1664,6 +1874,9 @@
"Request confirmation on power off, restart, suspend, hibernate and logout actions": {
"Request confirmation on power off, restart, suspend, hibernate and logout actions": "請求確認關機、重新啟動、暫停、休眠和登出操作"
},
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": {
"Require holding button/key to confirm power off, restart, suspend, hibernate and logout": ""
},
"Requires DWL compositor": {
"Requires DWL compositor": "需要 DWL 混成器"
},
@@ -1727,6 +1940,9 @@
"Saved": {
"Saved": "已儲存"
},
"Saved Configurations": {
"Saved Configurations": ""
},
"Scale DankBar font sizes independently": {
"Scale DankBar font sizes independently": "獨立縮放 DankBar 字體大小"
},
@@ -1736,6 +1952,9 @@
"Scan": {
"Scan": "掃描"
},
"Scanning...": {
"Scanning...": ""
},
"Science": {
"Science": "科學"
},
@@ -1757,12 +1976,21 @@
"Search plugins...": {
"Search plugins...": "搜尋插件..."
},
"Search widgets...": {
"Search widgets...": ""
},
"Search...": {
"Search...": "搜尋..."
},
"Searching...": {
"Searching...": "搜尋中..."
},
"Secured": {
"Secured": ""
},
"Security": {
"Security": ""
},
"Select Launcher Logo": {
"Select Launcher Logo": "選擇啟動器 Logo"
},
@@ -1775,6 +2003,9 @@
"Select a widget to add to the ": {
"Select a widget to add to the ": "選擇一個部件加到 "
},
"Select a widget to add. You can add multiple instances of the same widget if needed.": {
"Select a widget to add. You can add multiple instances of the same widget if needed.": ""
},
"Select an image file...": {
"Select an image file...": "選擇一張圖片..."
},
@@ -1805,6 +2036,9 @@
"Separator": {
"Separator": "分隔符"
},
"Server": {
"Server": ""
},
"Set different wallpapers for each connected monitor": {
"Set different wallpapers for each connected monitor": "為每個連接的螢幕設定不同的桌布"
},
@@ -1838,6 +2072,9 @@
"Show Log Out": {
"Show Log Out": "顯示登出"
},
"Show Occupied Workspaces Only": {
"Show Occupied Workspaces Only": ""
},
"Show Power Actions": {
"Show Power Actions": "顯示電源選項"
},
@@ -1931,6 +2168,12 @@
"Shutdown": {
"Shutdown": "關機"
},
"Signal": {
"Signal": ""
},
"Signal:": {
"Signal:": ""
},
"Size": {
"Size": "大小"
},
@@ -1949,6 +2192,9 @@
"Spacing": {
"Spacing": "間距"
},
"Speed": {
"Speed": ""
},
"Spool Area Full": {
"Spool Area Full": "緩衝區已滿"
},
@@ -1961,6 +2207,9 @@
"Start typing your notes here...": {
"Start typing your notes here...": "從這裡開始輸入您的筆記..."
},
"State": {
"State": ""
},
"Status": {
"Status": "狀態"
},
@@ -2111,6 +2360,9 @@
"Toggle visibility of this bar configuration": {
"Toggle visibility of this bar configuration": ""
},
"Toggling...": {
"Toggling...": ""
},
"Tomorrow": {
"Tomorrow": "明天"
},
@@ -2135,9 +2387,15 @@
"Turn off monitors after": {
"Turn off monitors after": "關閉螢幕之後"
},
"Unavailable": {
"Unavailable": ""
},
"Uninstall Plugin": {
"Uninstall Plugin": "解除安裝插件"
},
"Unknown": {
"Unknown": ""
},
"Unpin from Dock": {
"Unpin from Dock": "取消 Dock 釘選"
},
@@ -2225,6 +2483,18 @@
"VPN Connections": {
"VPN Connections": "VPN 連線"
},
"VPN Password": {
"VPN Password": ""
},
"VPN configuration updated": {
"VPN configuration updated": ""
},
"VPN deleted": {
"VPN deleted": ""
},
"VPN imported: ": {
"VPN imported: ": ""
},
"VPN status and quick connect": {
"VPN status and quick connect": "VPN 狀態和快速連線"
},
@@ -2264,6 +2534,9 @@
"Volume, brightness, and other system OSDs": {
"Volume, brightness, and other system OSDs": "音量、亮度及其他系統OSD"
},
"WPA/WPA2": {
"WPA/WPA2": ""
},
"Wallpaper": {
"Wallpaper": "桌布"
},
@@ -2291,6 +2564,15 @@
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": {
"When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.": "啟用後,應用程式將按字母順序排序。禁用後,應用程式將按使用頻率排序。"
},
"Wi-Fi Password": {
"Wi-Fi Password": ""
},
"WiFi": {
"WiFi": ""
},
"WiFi Device": {
"WiFi Device": ""
},
"WiFi disabled": {
"WiFi disabled": "關閉 WiFi"
},
@@ -2306,6 +2588,9 @@
"Widget Management": {
"Widget Management": "部件管理"
},
"Widget Outline": {
"Widget Outline": ""
},
"Widget Style": {
"Widget Style": ""
},
@@ -2336,6 +2621,9 @@
"Workspace Switcher": {
"Workspace Switcher": "工作區切換器"
},
"Yes": {
"Yes": ""
},
"You have unsaved changes. Save before closing this tab?": {
"You have unsaved changes. Save before closing this tab?": "您有未儲存的變更。是否要儲存?"
},

File diff suppressed because it is too large Load Diff