Skip to content

Commit 4e6659f

Browse files
authored
fix(linux): warn once when passthrough_unbounded_keys is set on the X11 backend (#1625)
1 parent 2cbe3ce commit 4e6659f

19 files changed

Lines changed: 384 additions & 29 deletions

cmd/neru/main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ func LaunchDaemon(configPath string) {
5555
zap.NewNop(),
5656
newAlertProvider(systemPort),
5757
)
58+
59+
// The display backend is a limit the platform column cannot say, so the
60+
// root that knows the backend hands the loader the words it cannot honor.
61+
if platform.CurrentProfile().DisplayServer == platform.DisplayServerX11 {
62+
service = service.WithBackendInert(config.X11InertWords)
63+
}
64+
5865
configResult := service.LoadWithValidation(configPath)
5966

6067
// If there's a validation error, show alert and exit
@@ -72,6 +79,7 @@ func LaunchDaemon(configPath string) {
7279
app.WithConfig(configResult.Config),
7380
app.WithWrittenConfig(configResult.Written),
7481
app.WithConfigPath(configResult.ConfigPath),
82+
app.WithConfigWarnings(configResult.Warnings),
7583
)
7684
if appErr != nil {
7785
fmt.Fprintf(os.Stderr, "Error creating app: %v\n", appErr)

docs/CROSS_PLATFORM.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,11 @@ is physically holding, and each stays the compositor's until released
618618
(`evdevSession.handlePress` and `evdevProxy.forwardWithheld`). It is **not**
619619
available on X11 (an `XGrabKeyboard` routes Neru's own XTest events back to
620620
itself, and `XSendEvent` is ignored by most apps) nor on the rare wl-keyboard
621-
fallback, which has no injection path. Windows needs no re-injection: a
621+
fallback, which has no injection path. The platform column cannot say "Linux,
622+
except X11", so a configuration that turns passthrough on under X11 warns once
623+
at load, in the same voice as the parity warning, and `neru doctor` lists the
624+
option and its two dependents in its `platform_support` row
625+
(`config.X11InertWords`). Windows needs no re-injection: a
622626
`WH_KEYBOARD_LL` hook forwards or blocks each event on its own
623627
(`eventtap/windows/tap.go`, `handleKey`). Classification (blacklist,
624628
mode-intercepted keys, the mode's own hotkeys, and the global chords it falls

internal/app/app.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,11 @@ type App struct {
4747
// handed to the config service so a later `neru config set` derives from
4848
// what the user wrote rather than from what was derived for them.
4949
writtenConfig *config.Config
50-
ConfigPath string
51-
logger *zap.Logger
50+
// configWarnings are what the launch-time load found wrong but loadable,
51+
// logged once the logger exists (WithConfigWarnings).
52+
configWarnings []string
53+
ConfigPath string
54+
logger *zap.Logger
5255

5356
systemPort ports.SystemPort
5457
accessibility ports.AccessibilityPort

internal/app/config.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"go.uber.org/zap"
77

8+
"github.com/y3owk1n/neru/internal/adapter/platform"
89
"github.com/y3owk1n/neru/internal/config"
910
"github.com/y3owk1n/neru/internal/config/loader"
1011
"github.com/y3owk1n/neru/internal/derrors"
@@ -15,9 +16,18 @@ import (
1516
// app was constructed with: the configuration to run on, and the one it was
1617
// derived from. Passing only the first leaves the service deriving from its own
1718
// output, which cannot re-infer — so the two travel together from here on.
19+
//
20+
// The backend limit is wired the same way main.go wires it for the first load,
21+
// so a hot reload warns about the same words the launch did.
1822
func (a *App) newConfigService(logger *zap.Logger) *loader.Service {
19-
return loader.NewService(a.config, a.ConfigPath, logger, a.systemPort).
23+
svc := loader.NewService(a.config, a.ConfigPath, logger, a.systemPort).
2024
WithWritten(a.writtenConfig)
25+
26+
if platform.CurrentProfile().DisplayServer == platform.DisplayServerX11 {
27+
svc = svc.WithBackendInert(config.X11InertWords)
28+
}
29+
30+
return svc
2131
}
2232

2333
// SetConfigField applies a single runtime config field change with full

internal/app/new.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ func New(opts ...Option) (*App, error) {
3737
app.logger = logger
3838
}
3939

40+
// Under the same name and message the config service logs a reload's, so
41+
// the two reads of the same file are one line to search for.
42+
for _, warning := range app.configWarnings {
43+
app.logger.Named("config").Warn("Configuration warning", zap.String("warning", warning))
44+
}
45+
4046
// Initialize the rest of the application
4147
return initializeApp(app)
4248
}

internal/app/options.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ func WithWrittenConfig(cfg *config.Config) Option {
3636
}
3737
}
3838

39+
// WithConfigWarnings carries the warnings the launch-time load found, for the
40+
// app to log once its logger exists. The load runs before there is a logger to
41+
// hand it, and a warning nobody prints is a warning nobody can act on (ADR
42+
// 0002); a hot reload logs its own through the config service.
43+
func WithConfigWarnings(warnings []string) Option {
44+
return func(a *App) error {
45+
a.configWarnings = warnings
46+
47+
return nil
48+
}
49+
}
50+
3951
// WithConfigPath sets the configuration file path.
4052
func WithConfigPath(path string) Option {
4153
return func(a *App) error {

internal/cli/config_loader.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package cli
2+
3+
import (
4+
"github.com/y3owk1n/neru/internal/adapter/platform"
5+
"github.com/y3owk1n/neru/internal/config"
6+
"github.com/y3owk1n/neru/internal/config/loader"
7+
)
8+
9+
// clientConfigLoader builds the loader a client-side command reads the
10+
// configuration with: defaults, no daemon, no logger, no alert dialog. The
11+
// commands that judge a file (`config validate`, `doctor`, `roles explain`)
12+
// share it so they read the same file the same way.
13+
//
14+
// The backend limit is wired here as it is in the daemon's two roots, so
15+
// `config validate` and `doctor` warn about the same words the daemon does.
16+
func clientConfigLoader() *loader.Service {
17+
svc := loader.NewService(config.DefaultConfig(), "", nil, nil)
18+
19+
if platform.CurrentProfile().DisplayServer == platform.DisplayServerX11 {
20+
svc = svc.WithBackendInert(config.X11InertWords)
21+
}
22+
23+
return svc
24+
}

internal/cli/config_validate.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ import (
44
"errors"
55

66
"github.com/spf13/cobra"
7-
8-
"github.com/y3owk1n/neru/internal/config"
9-
"github.com/y3owk1n/neru/internal/config/loader"
107
)
118

129
// errConfigValidationFailed is returned when config validation fails.
@@ -34,7 +31,7 @@ func init() {
3431
}
3532

3633
func runConfigValidate(cmd *cobra.Command) error {
37-
svc := loader.NewService(config.DefaultConfig(), "", nil, nil)
34+
svc := clientConfigLoader()
3835

3936
path := configPath
4037
if path == "" {

internal/cli/doctor_platform_support.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"github.com/spf13/cobra"
77

88
"github.com/y3owk1n/neru/internal/config"
9-
"github.com/y3owk1n/neru/internal/config/loader"
109
"github.com/y3owk1n/neru/internal/domain/parity"
1110
)
1211

@@ -23,7 +22,7 @@ const platformSupportRow = "platform_support"
2322
// One load for all of them: two would be two chances to disagree about which
2423
// file is even being judged, and the load is what discovers that file.
2524
func doctorConfigLoad() *config.LoadResult {
26-
svc := loader.NewService(config.DefaultConfig(), "", nil, nil)
25+
svc := clientConfigLoader()
2726

2827
path := configPath
2928
if path == "" {
@@ -34,7 +33,8 @@ func doctorConfigLoad() *config.LoadResult {
3433
}
3534

3635
// printPlatformSupportCheck reports the options, actions and mode flags this
37-
// configuration writes that do nothing on this platform.
36+
// configuration writes that do nothing on this platform or on the display
37+
// backend it is running.
3838
//
3939
// It never fails the doctor. An inert word is not a broken configuration — the
4040
// same file is meant to work on every platform, which is why writing one is a
@@ -62,7 +62,11 @@ func printPlatformSupportCheck(cmd *cobra.Command, loadResult *config.LoadResult
6262
return
6363
}
6464

65-
cmd.Printf(" ⚠️ %-24s %d %s nothing on %s (written and ignored; the daemon runs)\n",
65+
// "This session" rather than the platform: the list carries the words the
66+
// display backend cannot honor beside the ones the platform cannot, and
67+
// passthrough on X11 does nothing here without doing nothing on Linux.
68+
cmd.Printf(
69+
" ⚠️ %-24s %d %s nothing on this %s session (written and ignored; the daemon runs)\n",
6670
platformSupportRow,
6771
len(loadResult.Inert),
6872
pluralSettingsDo(len(loadResult.Inert)),

internal/cli/roles.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"github.com/spf13/cobra"
99

1010
"github.com/y3owk1n/neru/internal/config"
11-
"github.com/y3owk1n/neru/internal/config/loader"
1211
"github.com/y3owk1n/neru/internal/domain/element"
1312
)
1413

@@ -102,7 +101,7 @@ func runRolesList(cmd *cobra.Command) {
102101

103102
// runRolesExplain resolves the loaded configuration and reports each entry.
104103
func runRolesExplain(cmd *cobra.Command) error {
105-
svc := loader.NewService(config.DefaultConfig(), "", nil, nil)
104+
svc := clientConfigLoader()
106105

107106
path := configPath
108107
if path == "" {

0 commit comments

Comments
 (0)