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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbc93e3e0a8b5fb94642dc58a9b790fca024187e4d47af726b10662b352bd4ba
4
- data.tar.gz: 77a6db018f3b2507618672d09ffc20609264ec2c514bffb996fae3b9ffa336ea
3
+ metadata.gz: 1eb7a30939beb5781398de99a315a6382beb4d8183e9f87fa076c560a6d2497e
4
+ data.tar.gz: e198858920d58779268878849b14fe35a57ad3b4fb9c90c973d1347acf65e791
5
5
  SHA512:
6
- metadata.gz: 7b068878e2526e441fb7943f8e7b730d9fdfaa48ef710fb22bf917d1516e0143c2997bad952a0a9488633749e5125960d41982af99d899ba398369a2b20c05aa
7
- data.tar.gz: 875ac50f1ecabd943d4591d01c7241e0a729381eb251427d945d61740d22a358b4a43d9d74dc0187cee0f20383ee82aaadad98d0713d67b62acdfe153c26bb17
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, Ubuntu-Wayland
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
@@ -3,7 +3,7 @@
3
3
  module Fusuma
4
4
  module Plugin
5
5
  module Appmatcher
6
- VERSION = "0.11.0"
6
+ VERSION = "0.12.0"
7
7
  end
8
8
  end
9
9
  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.11.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-01-20 00:00:00.000000000 Z
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