fusuma-plugin-appmatcher 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +70 -1
- data/lib/fusuma/plugin/appmatcher/cosmic.rb +133 -0
- data/lib/fusuma/plugin/appmatcher/version.rb +1 -1
- data/lib/fusuma/plugin/appmatcher.rb +11 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1eb7a30939beb5781398de99a315a6382beb4d8183e9f87fa076c560a6d2497e
|
|
4
|
+
data.tar.gz: e198858920d58779268878849b14fe35a57ad3b4fb9c90c973d1347acf65e791
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fdce434ee079f9dc5ac2c258e7f5748ee4add701a7eb36d49a90ef9922046f12e8340240ba46d6750b6c6b83ed92b57ef9a0a9f1cc8473715a965bb884754d4d
|
|
7
|
+
data.tar.gz: 1633f89b6c50380ab6bf1acd31253f60b96d252f554c5630e23c0b6fd318d63ebd827602fb6414b48f8af6f9dd21ea373c8ef741592e93b2912c1bea74777490
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[Fusuma](https://github.com/iberianpig/fusuma) plugin configure app-specific gestures
|
|
4
4
|
|
|
5
5
|
* Switch gesture mappings by detecting active application.
|
|
6
|
-
* Support X11,
|
|
6
|
+
* Support X11, GNOME Wayland, Hyprland, COSMIC
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
@@ -27,6 +27,14 @@ $ fusuma-appmatcher --install-gnome-extension
|
|
|
27
27
|
|
|
28
28
|
Restart your session(logout/login), then activate Appmatcher on gnome-extensions-app
|
|
29
29
|
|
|
30
|
+
### Install cos-cli for COSMIC Desktop
|
|
31
|
+
|
|
32
|
+
On POP!_OS COSMIC, fusuma-plugin-appmatcher uses [cos-cli](https://github.com/estin/cos-cli) (a third-party CLI) to read active window state from the cosmic-comp Wayland compositor.
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
$ cargo install --git https://github.com/estin/cos-cli
|
|
36
|
+
```
|
|
37
|
+
|
|
30
38
|
## List Running Application names
|
|
31
39
|
|
|
32
40
|
`$ fusuma-appmatcher -l` prints Running Application names.
|
|
@@ -97,10 +105,71 @@ swipe:
|
|
|
97
105
|
sendkey: 'LEFTSHIFT+LEFTCTRL+W'
|
|
98
106
|
```
|
|
99
107
|
|
|
108
|
+
## Multiple Applications (OR condition)
|
|
109
|
+
|
|
110
|
+
**Requires fusuma v3.12.0 or later**
|
|
111
|
+
|
|
112
|
+
You can specify multiple applications using array format. The gesture will be triggered when **any** of the listed applications is active.
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
---
|
|
116
|
+
context:
|
|
117
|
+
application:
|
|
118
|
+
- Google-chrome
|
|
119
|
+
- Firefox
|
|
120
|
+
swipe:
|
|
121
|
+
3:
|
|
122
|
+
left:
|
|
123
|
+
sendkey: 'LEFTALT+RIGHT'
|
|
124
|
+
right:
|
|
125
|
+
sendkey: 'LEFTALT+LEFT'
|
|
126
|
+
up:
|
|
127
|
+
sendkey: 'LEFTCTRL+T'
|
|
128
|
+
down:
|
|
129
|
+
sendkey: 'LEFTCTRL+W'
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Combining with Other Context Plugins (AND condition)
|
|
133
|
+
|
|
134
|
+
**Requires fusuma v3.12.0 or later**
|
|
135
|
+
|
|
136
|
+
You can combine `application` with other context conditions. When multiple keys are specified under `context:`, **all** conditions must be satisfied (AND logic).
|
|
137
|
+
|
|
138
|
+
For example, with [fusuma-plugin-thumbsense](https://github.com/iberianpig/fusuma-plugin-thumbsense):
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
---
|
|
142
|
+
context:
|
|
143
|
+
thumbsense: true
|
|
144
|
+
application:
|
|
145
|
+
- Alacritty
|
|
146
|
+
- Gnome-terminal
|
|
147
|
+
swipe:
|
|
148
|
+
3:
|
|
149
|
+
up:
|
|
150
|
+
sendkey: 'LEFTSHIFT+LEFTCTRL+T'
|
|
151
|
+
down:
|
|
152
|
+
sendkey: 'LEFTSHIFT+LEFTCTRL+W'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
In this example, the gesture is only triggered when:
|
|
156
|
+
1. Thumbsense mode is active (finger is touching the touchpad)
|
|
157
|
+
2. AND the active application is either Alacritty or Gnome-terminal
|
|
158
|
+
|
|
159
|
+
This AND logic works with any context plugin that provides context conditions
|
|
160
|
+
|
|
100
161
|
## Contributing
|
|
101
162
|
|
|
102
163
|
Bug reports and pull requests are welcome on GitHub at https://github.com/iberianpig/fusuma-plugin-appmatcher. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
103
164
|
|
|
165
|
+
### Help Wanted: Support for Other Wayland Compositors
|
|
166
|
+
|
|
167
|
+
Currently, this plugin supports X11, GNOME Wayland, Hyprland, and COSMIC. We'd love to expand support to other Wayland compositors (Sway, KDE Plasma, wlroots-based compositors, etc.).
|
|
168
|
+
|
|
169
|
+
If you're using an unsupported compositor:
|
|
170
|
+
- Please [open an issue](https://github.com/iberianpig/fusuma-plugin-appmatcher/issues) to let us know
|
|
171
|
+
- Help with testing and feedback is greatly appreciated
|
|
172
|
+
|
|
104
173
|
## License
|
|
105
174
|
|
|
106
175
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "user_switcher"
|
|
6
|
+
require "fusuma/multi_logger"
|
|
7
|
+
require "fusuma/custom_process"
|
|
8
|
+
|
|
9
|
+
module Fusuma
|
|
10
|
+
module Plugin
|
|
11
|
+
module Appmatcher
|
|
12
|
+
# Search Active Window's Name for COSMIC desktop via cos-cli (third-party).
|
|
13
|
+
# cos-cli must be installed separately:
|
|
14
|
+
# cargo install --git https://github.com/estin/cos-cli
|
|
15
|
+
class Cosmic
|
|
16
|
+
include UserSwitcher
|
|
17
|
+
|
|
18
|
+
attr_reader :reader, :writer
|
|
19
|
+
|
|
20
|
+
# Search PATH in pure Ruby: the external `which` command is not
|
|
21
|
+
# installed on minimal systems (e.g. Arch containers).
|
|
22
|
+
# @return [Boolean]
|
|
23
|
+
def self.available?
|
|
24
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? do |dir|
|
|
25
|
+
path = File.join(dir, "cos-cli")
|
|
26
|
+
File.executable?(path) && !File.directory?(path)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize
|
|
31
|
+
@reader, @writer = IO.pipe
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def watch_start
|
|
35
|
+
as_user(proctitle: self.class.name.underscore) do |_user|
|
|
36
|
+
@reader.close
|
|
37
|
+
register_on_application_changed(Matcher.new)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def register_on_application_changed(matcher)
|
|
44
|
+
@writer.puts(matcher.active_application || "NOT FOUND")
|
|
45
|
+
matcher.on_active_application_changed { |name| notify(name) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def notify(name)
|
|
49
|
+
@writer.puts(name)
|
|
50
|
+
rescue Errno::EPIPE
|
|
51
|
+
exit 0
|
|
52
|
+
rescue => e
|
|
53
|
+
MultiLogger.error e.message
|
|
54
|
+
exit 1
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class Matcher
|
|
58
|
+
# @return [String, nil]
|
|
59
|
+
def active_application
|
|
60
|
+
extract_activated_app_id(fetch_info)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @return [Array<String>]
|
|
64
|
+
def running_applications
|
|
65
|
+
state = fetch_info
|
|
66
|
+
return [] unless state
|
|
67
|
+
(state["apps"] || []).map { |a| a["app_id"] }.compact.uniq
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Sentinel value distinct from nil, so the first iteration always
|
|
71
|
+
# yields (otherwise initial nil == nil would skip yielding NOT FOUND).
|
|
72
|
+
UNSET = Object.new.freeze
|
|
73
|
+
private_constant :UNSET
|
|
74
|
+
|
|
75
|
+
def on_active_application_changed
|
|
76
|
+
last = UNSET
|
|
77
|
+
subscribe_state_change do |state|
|
|
78
|
+
app_id = extract_activated_app_id(state)
|
|
79
|
+
next if app_id == last
|
|
80
|
+
last = app_id
|
|
81
|
+
yield(app_id || "NOT FOUND")
|
|
82
|
+
end
|
|
83
|
+
rescue => e
|
|
84
|
+
MultiLogger.error "Cosmic subscription error: #{e.message}"
|
|
85
|
+
sleep 1
|
|
86
|
+
retry
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
# @return [Hash, nil]
|
|
92
|
+
def fetch_info
|
|
93
|
+
stdout, _stderr, status = Open3.capture3("cos-cli", "info", "--json")
|
|
94
|
+
return nil unless status.success? && !stdout.empty?
|
|
95
|
+
JSON.parse(stdout)
|
|
96
|
+
rescue JSON::ParserError, Errno::ENOENT
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @param state [Hash, nil]
|
|
101
|
+
# @return [String, nil]
|
|
102
|
+
def extract_activated_app_id(state)
|
|
103
|
+
return nil unless state
|
|
104
|
+
apps = state["apps"] || []
|
|
105
|
+
focused = apps.find { |a| (a["state"] || []).include?("activated") }
|
|
106
|
+
focused && focused["app_id"]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def subscribe_state_change
|
|
110
|
+
# cos-cli serve is a stdio JSON-RPC server and exits on stdin EOF,
|
|
111
|
+
# so keep stdin open while reading notifications.
|
|
112
|
+
Open3.popen3("cos-cli", "serve") do |_stdin, stdout, stderr, _wait_thr|
|
|
113
|
+
stdout.each_line do |line|
|
|
114
|
+
msg = JSON.parse(line)
|
|
115
|
+
next unless msg["method"] == "state_change"
|
|
116
|
+
state = msg.dig("params", "state")
|
|
117
|
+
yield state if state
|
|
118
|
+
rescue JSON::ParserError => e
|
|
119
|
+
MultiLogger.warn "Failed to parse cos-cli message: #{e.message}"
|
|
120
|
+
end
|
|
121
|
+
# A clean serve exit must not stop the watcher silently:
|
|
122
|
+
# raise so on_active_application_changed resubscribes via retry.
|
|
123
|
+
raise "cos-cli serve exited: #{stderr.read}"
|
|
124
|
+
end
|
|
125
|
+
rescue Errno::ENOENT
|
|
126
|
+
MultiLogger.error "cos-cli command not found. Install with: cargo install --git https://github.com/estin/cos-cli"
|
|
127
|
+
raise
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -6,6 +6,7 @@ require "fusuma/plugin/appmatcher/x11"
|
|
|
6
6
|
require "fusuma/plugin/appmatcher/gnome_extension"
|
|
7
7
|
require "fusuma/plugin/appmatcher/gnome_extensions/installer"
|
|
8
8
|
require "fusuma/plugin/appmatcher/hyprland"
|
|
9
|
+
require "fusuma/plugin/appmatcher/cosmic"
|
|
9
10
|
require "fusuma/plugin/appmatcher/unsupported_backend"
|
|
10
11
|
|
|
11
12
|
module Fusuma
|
|
@@ -33,6 +34,16 @@ module Fusuma
|
|
|
33
34
|
end
|
|
34
35
|
when /Hyprland/i
|
|
35
36
|
return Hyprland if hyprland_available?
|
|
37
|
+
when /COSMIC/i
|
|
38
|
+
if Cosmic.available?
|
|
39
|
+
return Cosmic
|
|
40
|
+
else
|
|
41
|
+
MultiLogger.warn "cos-cli command not found"
|
|
42
|
+
MultiLogger.warn "Please install cos-cli to use appmatcher with COSMIC desktop:"
|
|
43
|
+
MultiLogger.warn ""
|
|
44
|
+
MultiLogger.warn " $ cargo install --git https://github.com/estin/cos-cli"
|
|
45
|
+
MultiLogger.warn ""
|
|
46
|
+
end
|
|
36
47
|
end
|
|
37
48
|
end
|
|
38
49
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fusuma-plugin-appmatcher
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- iberianpig
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rexml
|
|
@@ -68,6 +68,7 @@ files:
|
|
|
68
68
|
- exe/fusuma-appmatcher
|
|
69
69
|
- fusuma-plugin-appmatcher.gemspec
|
|
70
70
|
- lib/fusuma/plugin/appmatcher.rb
|
|
71
|
+
- lib/fusuma/plugin/appmatcher/cosmic.rb
|
|
71
72
|
- lib/fusuma/plugin/appmatcher/gnome_extension.rb
|
|
72
73
|
- lib/fusuma/plugin/appmatcher/gnome_extensions/appmatcher45@iberianpig.dev/extension.js
|
|
73
74
|
- lib/fusuma/plugin/appmatcher/gnome_extensions/appmatcher45@iberianpig.dev/metadata.json
|