fusuma-plugin-appmatcher 0.1.6 → 0.2.3
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 +12 -1
- data/exe/fusuma-appmatcher +23 -0
- data/fusuma-plugin-appmatcher.gemspec +20 -16
- data/lib/fusuma/plugin/appmatcher/gnome.rb +7 -7
- data/lib/fusuma/plugin/appmatcher/gnome_extension.rb +128 -0
- data/lib/fusuma/plugin/appmatcher/gnome_extensions/appmatcher@iberianpig.dev/extension.js +75 -0
- data/lib/fusuma/plugin/appmatcher/gnome_extensions/appmatcher@iberianpig.dev/metadata.json +13 -0
- data/lib/fusuma/plugin/appmatcher/gnome_extensions/installer.rb +58 -0
- data/lib/fusuma/plugin/appmatcher/user_switcher.rb +7 -3
- data/lib/fusuma/plugin/appmatcher/version.rb +1 -1
- data/lib/fusuma/plugin/appmatcher/x11.rb +12 -12
- data/lib/fusuma/plugin/appmatcher.rb +25 -13
- data/lib/fusuma/plugin/buffers/appmatcher_buffer.rb +1 -1
- data/lib/fusuma/plugin/detectors/appmatcher_detector.rb +3 -4
- data/lib/fusuma/plugin/inputs/appmatcher_input.rb +6 -6
- data/lib/fusuma/plugin/parsers/appmatcher_parser.rb +1 -1
- data/spec/fusuma/plugin/appmatcher/gnome_extensions/installer_spec.rb +28 -0
- data/spec/fusuma/plugin/appmatcher/gnome_spec.rb +3 -3
- data/spec/fusuma/plugin/appmatcher_spec.rb +30 -18
- data/spec/fusuma/plugin/{inputs → detectors}/appmatcher_detector_spec.rb +20 -20
- data/spec/spec_helper.rb +3 -3
- metadata +17 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7821bcc57ed92ff1ab9a45afc0e760dec0e852678b2db2a0c6d8cb6b39604ba8
|
4
|
+
data.tar.gz: 4d335957d70eee1cb86294a3f57219020cbf42b713dc105c91ae205ed6677ee0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc4ccfbb2450075a702f9909bebbf5be21650b44794e07f71720a39abd80a221c27b59b39d958ecc37c54c2c8c9b2bf81df04417d8cbdd9a4641f4bd26fc774c
|
7
|
+
data.tar.gz: bf37777c2cb5304aa59b8a66c1cb788fc8ef923f5cda1ca5020eb39991fa07156786e6aaa7ff6bc0245c98060bcf0abe1baa7a1e568a4e2795114f390d05d1bb
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Fusuma::Plugin::Appmatcher [](https://badge.fury.io/rb/fusuma-plugin-appmatcher) [](https://badge.fury.io/rb/fusuma-plugin-appmatcher) [](https://github.com/iberianpig/fusuma/actions/workflows/ubuntu.yml)
|
2
2
|
|
3
3
|
[Fusuma](https://github.com/iberianpig/fusuma) plugin configure app-specific gestures
|
4
4
|
|
@@ -16,6 +16,17 @@ Run the following code in your terminal.
|
|
16
16
|
$ sudo gem install fusuma-plugin-appmatcher
|
17
17
|
```
|
18
18
|
|
19
|
+
### Install Appmatcher GNOME Shell Extensions on Wayland
|
20
|
+
|
21
|
+
Gnome Wayland version 41 and later does not allow to access information about window or application like focused app.
|
22
|
+
So fusuma-plugin-appmatcher solves this problem via Appmatcher gnome-extension.
|
23
|
+
|
24
|
+
```sh
|
25
|
+
$ fusuma-appmatcher --install-gnome-extension
|
26
|
+
```
|
27
|
+
|
28
|
+
Restart your session(logout/login), then activate Appmatcher on gnome-extensions-app
|
29
|
+
|
19
30
|
## List Running Application names
|
20
31
|
|
21
32
|
`$ fusuma-appmatcher -l` prints Running Application names.
|
data/exe/fusuma-appmatcher
CHANGED
@@ -16,8 +16,31 @@ opt.on('--version', 'Show version') do |v|
|
|
16
16
|
option[:version] = v
|
17
17
|
end
|
18
18
|
|
19
|
+
opt.on('--install-gnome-extension',
|
20
|
+
'Install GNOME extension for appmatcher') do |v|
|
21
|
+
option[:install_gnome_extension] = v
|
22
|
+
end
|
23
|
+
|
24
|
+
opt.on('--uninstall-gnome-extension',
|
25
|
+
'Uninstall GNOME extension for appmatcher') do |v|
|
26
|
+
option[:uninstall_gnome_extension] = v
|
27
|
+
end
|
28
|
+
|
19
29
|
opt.parse!(ARGV)
|
20
30
|
|
31
|
+
if option[:install_gnome_extension]
|
32
|
+
require_relative '../lib/fusuma/plugin/appmatcher/gnome_extensions/installer.rb'
|
33
|
+
Fusuma::Plugin::Appmatcher::GnomeExtensions::Installer.new.install
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
if option[:uninstall_gnome_extension]
|
38
|
+
require_relative '../lib/fusuma/plugin/appmatcher/gnome_extensions/installer.rb'
|
39
|
+
Fusuma::Plugin::Appmatcher::GnomeExtensions::Installer.new.uninstall
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
43
|
+
|
21
44
|
if option[:list]
|
22
45
|
matcher = Fusuma::Plugin::Appmatcher.backend_klass::Matcher.new
|
23
46
|
puts matcher.running_applications
|
@@ -1,29 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
lib = File.expand_path(
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
-
require
|
5
|
+
require "fusuma/plugin/appmatcher/version"
|
6
6
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
|
-
spec.name =
|
8
|
+
spec.name = "fusuma-plugin-appmatcher"
|
9
9
|
spec.version = Fusuma::Plugin::Appmatcher::VERSION
|
10
|
-
spec.authors = [
|
11
|
-
spec.email = [
|
10
|
+
spec.authors = ["iberianpig"]
|
11
|
+
spec.email = ["yhkyky@gmail.com"]
|
12
12
|
|
13
|
-
spec.summary =
|
14
|
-
spec.description =
|
15
|
-
spec.homepage =
|
16
|
-
spec.license =
|
13
|
+
spec.summary = "Fusuma plugin to assign gesture mapping per application"
|
14
|
+
spec.description = "fusuma-plugin-appmatcher is Fusuma plugin for assigning gesture mapping per application."
|
15
|
+
spec.homepage = "https://github.com/iberianpig/fusuma-plugin-appmatcher"
|
16
|
+
spec.license = "MIT"
|
17
17
|
|
18
18
|
# Specify which files should be added to the gem when it is released.
|
19
|
-
spec.files = Dir[
|
20
|
-
spec.test_files = Dir[
|
21
|
-
spec.bindir =
|
19
|
+
spec.files = Dir["{bin,lib,exe}/**/*", "LICENSE*", "README*", "*.gemspec"]
|
20
|
+
spec.test_files = Dir["{test,spec,features}/**/*"]
|
21
|
+
spec.bindir = "exe"
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
-
spec.require_paths = [
|
23
|
+
spec.require_paths = ["lib"]
|
24
24
|
|
25
|
-
spec.add_dependency "rexml" # ruby-dbus doesn't resolve dependency
|
26
|
-
spec.add_runtime_dependency
|
25
|
+
spec.add_dependency "rexml" # ruby-dbus doesn't resolve dependency
|
26
|
+
spec.add_runtime_dependency "ruby-dbus"
|
27
27
|
|
28
|
-
spec.add_dependency
|
28
|
+
spec.add_dependency "fusuma", "~> 2.3"
|
29
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
30
|
+
|
31
|
+
spec.required_ruby_version = ">= 2.5.1" # https://packages.ubuntu.com/search?keywords=ruby&searchon=names&exact=1&suite=all§ion=main
|
32
|
+
# support bionic (18.04LTS) 2.5.1
|
29
33
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require_relative
|
3
|
+
require "json"
|
4
|
+
require "dbus"
|
5
|
+
require_relative "./user_switcher"
|
6
6
|
|
7
7
|
module Fusuma
|
8
8
|
module Plugin
|
@@ -21,7 +21,7 @@ module Fusuma
|
|
21
21
|
@watch_start ||= begin
|
22
22
|
pid = UserSwitcher.new.as_user do |user|
|
23
23
|
@reader.close
|
24
|
-
ENV[
|
24
|
+
ENV["DBUS_SESSION_BUS_ADDRESS"] = "unix:path=/run/user/#{user.uid}/bus"
|
25
25
|
register_on_application_changed(Matcher.new)
|
26
26
|
end
|
27
27
|
Process.detach(pid)
|
@@ -50,8 +50,8 @@ module Fusuma
|
|
50
50
|
class Matcher
|
51
51
|
def initialize
|
52
52
|
session_bus = DBus.session_bus
|
53
|
-
service = session_bus.service(
|
54
|
-
@interface = service[
|
53
|
+
service = session_bus.service("org.gnome.Shell")
|
54
|
+
@interface = service["/org/gnome/Shell"]["org.gnome.Shell"]
|
55
55
|
rescue DBus::Error => e
|
56
56
|
MultiLogger.error "DBus::Error: #{e.message}"
|
57
57
|
|
@@ -116,7 +116,7 @@ module Fusuma
|
|
116
116
|
new_application = active_application
|
117
117
|
next if @old_application == new_application
|
118
118
|
|
119
|
-
yield(new_application ||
|
119
|
+
yield(new_application || "NOT FOUND") if block_given?
|
120
120
|
@old_application = new_application
|
121
121
|
end
|
122
122
|
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "dbus"
|
5
|
+
require_relative "./user_switcher"
|
6
|
+
|
7
|
+
module Fusuma
|
8
|
+
module Plugin
|
9
|
+
module Appmatcher
|
10
|
+
# Search Active Window's Name
|
11
|
+
class GnomeExtension
|
12
|
+
attr_reader :matcher, :reader, :writer
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@reader, @writer = IO.pipe
|
16
|
+
end
|
17
|
+
|
18
|
+
# fork process and watch signal
|
19
|
+
# @return [Integer] Process id
|
20
|
+
def watch_start
|
21
|
+
@watch_start ||= begin
|
22
|
+
pid = UserSwitcher.new.as_user do |user|
|
23
|
+
@reader.close
|
24
|
+
ENV["DBUS_SESSION_BUS_ADDRESS"] = "unix:path=/run/user/#{user.uid}/bus"
|
25
|
+
register_on_application_changed(Matcher.new)
|
26
|
+
end
|
27
|
+
Process.detach(pid)
|
28
|
+
pid
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# @param matcher [Matcher]
|
35
|
+
def register_on_application_changed(matcher)
|
36
|
+
# NOTE: push current application to pipe before start
|
37
|
+
@writer.puts(matcher.active_application)
|
38
|
+
|
39
|
+
matcher.on_active_application_changed do |name|
|
40
|
+
notify(name)
|
41
|
+
end
|
42
|
+
|
43
|
+
execute_loop(matcher)
|
44
|
+
end
|
45
|
+
|
46
|
+
def notify(name)
|
47
|
+
@writer.puts(name)
|
48
|
+
rescue Errno::EPIPE
|
49
|
+
exit 0
|
50
|
+
rescue StandardError => e
|
51
|
+
MultiLogger.error e.message
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
|
55
|
+
def execute_loop(matcher)
|
56
|
+
loop = DBus::Main.new
|
57
|
+
loop << matcher.session_bus
|
58
|
+
loop.run
|
59
|
+
end
|
60
|
+
|
61
|
+
# Look up application name using dbus
|
62
|
+
class Matcher
|
63
|
+
attr_reader :session_bus
|
64
|
+
|
65
|
+
def initialize
|
66
|
+
@session_bus = DBus.session_bus
|
67
|
+
service = session_bus.service("org.gnome.Shell")
|
68
|
+
@interface = service["/dev/iberianpig/Appmatcher"]["dev.iberianpig.Appmatcher"]
|
69
|
+
rescue DBus::Error => e
|
70
|
+
MultiLogger.error "DBus::Error: #{e.message}"
|
71
|
+
MultiLogger.error "Have you installed GNOME extension?"
|
72
|
+
MultiLogger.error "$ fusuma-appmatcher --install-gnome-extension"
|
73
|
+
MultiLogger.error "Then Restart your session"
|
74
|
+
|
75
|
+
exit 1
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [Array<String>]
|
79
|
+
def running_applications
|
80
|
+
applications = JSON.parse(@interface.ListWindows, object_class: Application)
|
81
|
+
applications.map(&:wm_class)
|
82
|
+
rescue JSON::ParserError => e
|
83
|
+
MultiLogger.error e.message
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [String]
|
88
|
+
def active_application
|
89
|
+
app = JSON.parse(@interface.ActiveWindow, object_class: Application)
|
90
|
+
app&.wm_class
|
91
|
+
rescue JSON::ParserError => e
|
92
|
+
MultiLogger.error e.message
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def on_active_application_changed
|
97
|
+
@interface.on_signal("ActiveWindowChanged") do |json|
|
98
|
+
# if app is is not found, fetch active_application_id
|
99
|
+
app =
|
100
|
+
begin
|
101
|
+
JSON.parse(json, object_class: Application)
|
102
|
+
rescue JSON::ParserError => e
|
103
|
+
MultiLogger.error e.message
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
yield(app&.wm_class || "NOT FOUND") if block_given?
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Focused Application
|
113
|
+
class Application
|
114
|
+
attr_reader :wm_class, :pid, :id, :title, :focus
|
115
|
+
|
116
|
+
# to specify as object_class in JSON.parse
|
117
|
+
def []=(key, value)
|
118
|
+
instance_variable_set("@#{key}", value)
|
119
|
+
end
|
120
|
+
|
121
|
+
def inspect
|
122
|
+
"wm_class: #{wm_class}, pid: #{pid}, id: #{id}, title: #{title}, focus: #{focus}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
const { Gio, GLib } = imports.gi;
|
2
|
+
|
3
|
+
const DBUS_INTERFACE = `
|
4
|
+
<node>
|
5
|
+
<interface name="dev.iberianpig.Appmatcher">
|
6
|
+
<method name="ActiveWindow">
|
7
|
+
<arg type="s" direction="out" name="win"/>
|
8
|
+
</method>
|
9
|
+
<method name="ListWindows">
|
10
|
+
<arg type="s" direction="out" name="wins"/>
|
11
|
+
</method>
|
12
|
+
<signal name="ActiveWindowChanged">
|
13
|
+
<arg type="s" name="new_win"/>
|
14
|
+
</signal>
|
15
|
+
</interface>
|
16
|
+
</node>`;
|
17
|
+
|
18
|
+
class Extension {
|
19
|
+
constructor() {
|
20
|
+
}
|
21
|
+
|
22
|
+
enable() {
|
23
|
+
this._dbus = Gio.DBusExportedObject.wrapJSObject(DBUS_INTERFACE, this);
|
24
|
+
this._dbus.export(Gio.DBus.session, '/dev/iberianpig/Appmatcher');
|
25
|
+
this._callback_id = global.display.connect('notify::focus-window', ()=> {
|
26
|
+
this.activeWindowChanged()
|
27
|
+
})
|
28
|
+
|
29
|
+
}
|
30
|
+
|
31
|
+
disable() {
|
32
|
+
this._dbus.flush();
|
33
|
+
this._dbus.unexport();
|
34
|
+
if (this._callback_id) {
|
35
|
+
global.display.disconnect(this._callback_id);
|
36
|
+
delete this._callback_id
|
37
|
+
}
|
38
|
+
delete this._dbus;
|
39
|
+
}
|
40
|
+
|
41
|
+
activeWindowChanged() {
|
42
|
+
const w = global.display.get_focus_window();
|
43
|
+
if(!w) { return; }
|
44
|
+
|
45
|
+
try {
|
46
|
+
const obj = { wm_class: w.get_wm_class(), pid: w.get_pid(), id: w.get_id(), title: w.get_title(), focus: w.has_focus()}
|
47
|
+
this._dbus.emit_signal('ActiveWindowChanged', new GLib.Variant('(s)', [JSON.stringify(obj)]));
|
48
|
+
} catch (e) {
|
49
|
+
global.logError(e, 'failed to Emit DBus signal');
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
ListWindows() {
|
54
|
+
const wins = global.get_window_actors()
|
55
|
+
.map(a => a.meta_window)
|
56
|
+
.map(w => ({ wm_class: w.get_wm_class(), pid: w.get_pid(), id: w.get_id(), title: w.get_title(), focus: w.has_focus()}));
|
57
|
+
return JSON.stringify(wins);
|
58
|
+
}
|
59
|
+
|
60
|
+
ActiveWindow() {
|
61
|
+
const actor = global.get_window_actors().find(a=>a.meta_window.has_focus()===true)
|
62
|
+
if(!actor) { return '{}'; }
|
63
|
+
const w = actor.get_meta_window()
|
64
|
+
try {
|
65
|
+
const obj = { wm_class: w.get_wm_class(), pid: w.get_pid(), id: w.get_id(), title: w.get_title(), focus: w.has_focus()}
|
66
|
+
return JSON.stringify(obj);
|
67
|
+
} catch (e) {
|
68
|
+
global.logError(e, 'failed to fetch ActiveWindow()');
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
function init() {
|
74
|
+
return new Extension();
|
75
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
{
|
2
|
+
"name": "Appmatcher",
|
3
|
+
"description": "notify focused application using D-Bus",
|
4
|
+
"shell-version" : [
|
5
|
+
"3.36",
|
6
|
+
"3.38",
|
7
|
+
"40",
|
8
|
+
"41"
|
9
|
+
],
|
10
|
+
"url" : "https://github.com/iberianpig/fusuma-plugin-appmatcher",
|
11
|
+
"uuid": "appmatcher@iberianpig.dev",
|
12
|
+
"version" : 1
|
13
|
+
}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../user_switcher"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module Fusuma
|
7
|
+
module Plugin
|
8
|
+
module Appmatcher
|
9
|
+
module GnomeExtensions
|
10
|
+
# Install Gnome Extension
|
11
|
+
class Installer
|
12
|
+
EXTENSION = "./appmatcher@iberianpig.dev"
|
13
|
+
|
14
|
+
def install
|
15
|
+
pid = UserSwitcher.new.as_user do |user|
|
16
|
+
FileUtils.cp_r(source_path, user_extension_dir(user.username))
|
17
|
+
puts "Installed Appmatcher Gnome Shell Extension to #{user_extension_dir(user.username)}"
|
18
|
+
puts "Restart your session, then activate Appmatcher on gnome-extensions-app"
|
19
|
+
end
|
20
|
+
Process.waitpid(pid)
|
21
|
+
end
|
22
|
+
|
23
|
+
def uninstall
|
24
|
+
return puts "Appmatcher Gnome Shell Extension is not installed in #{user_extension_dir}/" unless installed?
|
25
|
+
|
26
|
+
pid = UserSwitcher.new.as_user do |user|
|
27
|
+
FileUtils.rm_r(install_path(user.username))
|
28
|
+
puts "Uninstalled Appmatcher Gnome Shell Extension from #{install_path(user.username)}"
|
29
|
+
end
|
30
|
+
Process.waitpid(pid)
|
31
|
+
end
|
32
|
+
|
33
|
+
def installed?
|
34
|
+
File.exist?(install_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def user_extension_dir(username = login_username)
|
40
|
+
File.expand_path("#{Dir.home(username)}/.local/share/gnome-shell/extensions/")
|
41
|
+
end
|
42
|
+
|
43
|
+
def install_path(username = login_username)
|
44
|
+
File.expand_path("#{Dir.home(username)}/.local/share/gnome-shell/extensions/#{EXTENSION}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def source_path
|
48
|
+
File.expand_path(EXTENSION, __dir__)
|
49
|
+
end
|
50
|
+
|
51
|
+
def login_username
|
52
|
+
UserSwitcher.new.login_user.username
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -1,16 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "fusuma/custom_process"
|
4
|
+
require "etc"
|
5
5
|
|
6
6
|
module Fusuma
|
7
7
|
module Plugin
|
8
8
|
module Appmatcher
|
9
|
+
# Drop sudo privileges
|
9
10
|
class UserSwitcher
|
10
11
|
include CustomProcess
|
11
12
|
User = Struct.new(:username, :uid, :gid)
|
13
|
+
|
14
|
+
attr_reader :login_user
|
15
|
+
|
12
16
|
def initialize
|
13
|
-
username = ENV[
|
17
|
+
username = ENV["SUDO_USER"] || Etc.getlogin
|
14
18
|
uid = `id -u #{username}`.chomp.to_i
|
15
19
|
gid = `id -g #{username}`.chomp.to_i
|
16
20
|
@login_user = User.new(username, uid, gid)
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require_relative
|
5
|
-
require
|
6
|
-
require
|
3
|
+
require "open3"
|
4
|
+
require_relative "./user_switcher"
|
5
|
+
require "fusuma/multi_logger"
|
6
|
+
require "fusuma/custom_process"
|
7
7
|
|
8
8
|
module Fusuma
|
9
9
|
module Plugin
|
@@ -50,7 +50,7 @@ module Fusuma
|
|
50
50
|
class Matcher
|
51
51
|
# @return [Array<String>]
|
52
52
|
def running_applications
|
53
|
-
`xprop -root _NET_CLIENT_LIST_STACKING`.split(
|
53
|
+
`xprop -root _NET_CLIENT_LIST_STACKING`.split(", ")
|
54
54
|
.map { |id_str| id_str.match(/0x[\da-z]{2,}/).to_s }
|
55
55
|
.map { |id| active_application(id) }
|
56
56
|
end
|
@@ -59,16 +59,16 @@ module Fusuma
|
|
59
59
|
# @return [NilClass]
|
60
60
|
def active_application(id = active_window_id)
|
61
61
|
@cache ||= {}
|
62
|
-
@cache[id] ||=
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
@cache[id] ||= if id.nil?
|
63
|
+
nil
|
64
|
+
else
|
65
|
+
`xprop -id #{id} WM_CLASS | cut -d "=" -f 2 | tr -d '"'`.strip.split(", ").last
|
66
|
+
end
|
67
67
|
end
|
68
68
|
|
69
69
|
def on_active_application_changed
|
70
70
|
active_window_id(watch: true) do |id|
|
71
|
-
yield(active_application(id) ||
|
71
|
+
yield(active_application(id) || "NOT FOUND")
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
@@ -99,7 +99,7 @@ module Fusuma
|
|
99
99
|
# @param spy [TrueClass, FalseClass]
|
100
100
|
# @return [String]
|
101
101
|
def xprop_active_window_id(spy)
|
102
|
-
spy_option =
|
102
|
+
spy_option = "-spy" if spy
|
103
103
|
"xprop #{spy_option} -root _NET_ACTIVE_WINDOW"
|
104
104
|
end
|
105
105
|
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "fusuma/plugin/appmatcher/version"
|
4
4
|
|
5
|
-
require_relative
|
6
|
-
require_relative
|
5
|
+
require_relative "appmatcher/x11"
|
6
|
+
require_relative "appmatcher/gnome"
|
7
|
+
require_relative "appmatcher/gnome_extension"
|
8
|
+
require_relative "appmatcher/gnome_extensions/installer"
|
7
9
|
|
8
10
|
module Fusuma
|
9
11
|
module Plugin
|
@@ -17,24 +19,34 @@ module Fusuma
|
|
17
19
|
when /x11/
|
18
20
|
return X11
|
19
21
|
when /wayland/
|
20
|
-
|
22
|
+
case xdg_current_desktop
|
23
|
+
when /GNOME/
|
24
|
+
return GnomeExtension if GnomeExtensions::Installer.new.installed?
|
25
|
+
|
26
|
+
return Gnome
|
27
|
+
end
|
21
28
|
end
|
22
|
-
|
23
|
-
|
24
|
-
appmatcher doesn't support
|
25
|
-
XDG_CURRENT_DESKTOP: '#{xdg_current_desktop}'
|
26
|
-
XDG_SESSION_TYPE: '#{xdg_session_type}'"
|
27
|
-
ERROR
|
28
|
-
)
|
29
|
+
|
30
|
+
error_message_not_supported
|
29
31
|
exit 1
|
30
32
|
end
|
31
33
|
|
32
34
|
def xdg_session_type
|
33
|
-
ENV.fetch(
|
35
|
+
ENV.fetch("XDG_SESSION_TYPE", "")
|
34
36
|
end
|
35
37
|
|
36
38
|
def xdg_current_desktop
|
37
|
-
ENV.fetch(
|
39
|
+
ENV.fetch("XDG_CURRENT_DESKTOP", "")
|
40
|
+
end
|
41
|
+
|
42
|
+
def error_message_not_supported
|
43
|
+
MultiLogger.error(
|
44
|
+
<<~ERROR
|
45
|
+
appmatcher doesn't support
|
46
|
+
XDG_CURRENT_DESKTOP: '#{xdg_current_desktop}'
|
47
|
+
XDG_SESSION_TYPE: '#{xdg_session_type}'"
|
48
|
+
ERROR
|
49
|
+
)
|
38
50
|
end
|
39
51
|
end
|
40
52
|
end
|
@@ -5,9 +5,8 @@ module Fusuma
|
|
5
5
|
module Detectors
|
6
6
|
# Detect KeypressEvent from KeypressBuffer
|
7
7
|
class AppmatcherDetector < Detector
|
8
|
-
SOURCES = [
|
9
|
-
BUFFER_TYPE =
|
10
|
-
DEFAULT_NAME = 'global'
|
8
|
+
SOURCES = ["appmatcher"].freeze
|
9
|
+
BUFFER_TYPE = "appmatcher"
|
11
10
|
|
12
11
|
# Always watch buffers and detect them.
|
13
12
|
def watch?
|
@@ -25,7 +24,7 @@ module Fusuma
|
|
25
24
|
record = buffer.events.last.record
|
26
25
|
|
27
26
|
context_record = Events::Records::ContextRecord.new(
|
28
|
-
name:
|
27
|
+
name: "application",
|
29
28
|
value: record.name
|
30
29
|
)
|
31
30
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "../appmatcher"
|
4
4
|
|
5
5
|
module Fusuma
|
6
6
|
module Plugin
|
@@ -13,12 +13,12 @@ module Fusuma
|
|
13
13
|
@backend ||= Appmatcher.backend_klass.new
|
14
14
|
|
15
15
|
@pid ||= begin
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
pid = @backend.watch_start
|
17
|
+
# NOTE: Closing the parent process's pipe
|
18
|
+
@backend.writer.close
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
pid
|
21
|
+
end
|
22
22
|
|
23
23
|
@backend.reader
|
24
24
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
module Fusuma
|
6
|
+
module Plugin
|
7
|
+
module Appmatcher
|
8
|
+
module GnomeExtensions
|
9
|
+
RSpec.describe Installer do
|
10
|
+
describe "#install" do
|
11
|
+
it "should copy file to users dir"
|
12
|
+
end
|
13
|
+
describe "#uninstall" do
|
14
|
+
context "when extension is NOT installed" do
|
15
|
+
it "should remove file to users dir"
|
16
|
+
end
|
17
|
+
context "when extension is NOT installed" do
|
18
|
+
it "should NOT execute"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
describe "#installed?" do
|
22
|
+
it "check extension is in the installed path"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "spec_helper"
|
4
|
+
require "./lib/fusuma/plugin/appmatcher/gnome"
|
5
5
|
|
6
6
|
module Fusuma
|
7
7
|
module Plugin
|
8
8
|
module Appmatcher
|
9
9
|
RSpec.describe Gnome do
|
10
|
-
it
|
10
|
+
it "requires ruby-dbus" do
|
11
11
|
expect(DBus).not_to be nil
|
12
12
|
end
|
13
13
|
end
|
@@ -1,36 +1,48 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
module Fusuma
|
6
6
|
module Plugin
|
7
7
|
RSpec.describe Appmatcher do
|
8
|
-
it
|
8
|
+
it "has a version number" do
|
9
9
|
expect(Appmatcher::VERSION).not_to be nil
|
10
10
|
end
|
11
11
|
|
12
|
-
describe
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
describe "#backend_klass" do
|
13
|
+
subject { Appmatcher.backend_klass }
|
14
|
+
context "when XDG_SESSION_TYPE is x11" do
|
15
|
+
before { allow(Appmatcher).to receive(:xdg_session_type).and_return("x11") }
|
16
|
+
it { is_expected.to eq Appmatcher::X11 }
|
16
17
|
end
|
17
18
|
|
18
|
-
context
|
19
|
-
before { allow(Appmatcher).to receive(:xdg_session_type).and_return(
|
20
|
-
|
21
|
-
|
19
|
+
context "when XDG_SESSION_TYPE is wayland" do
|
20
|
+
before { allow(Appmatcher).to receive(:xdg_session_type).and_return("wayland") }
|
21
|
+
|
22
|
+
context "when XDG_CURRENT_DESKTOP is ubuntu:GNOME" do
|
23
|
+
before { allow(Appmatcher).to receive(:xdg_current_desktop).and_return("ubuntu:GNOME") }
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
+
context "when gnome-extension is installed" do
|
26
|
+
before do
|
27
|
+
allow_any_instance_of(Appmatcher::GnomeExtensions::Installer).to receive(:installed?).and_return(true)
|
28
|
+
end
|
29
|
+
it { is_expected.to eq Appmatcher::GnomeExtension }
|
30
|
+
end
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
|
32
|
+
context "when gnome-extension is NOT installed" do
|
33
|
+
before do
|
34
|
+
allow_any_instance_of(Appmatcher::GnomeExtensions::Installer).to receive(:installed?).and_return(false)
|
35
|
+
end
|
36
|
+
it { is_expected.to eq Appmatcher::Gnome }
|
37
|
+
end
|
29
38
|
end
|
30
39
|
|
31
|
-
context
|
32
|
-
before
|
33
|
-
|
40
|
+
context "when XDG_CURRENT_DESKTOP is UNKNOWN" do
|
41
|
+
before do
|
42
|
+
allow(Appmatcher).to receive(:xdg_current_desktop).and_return("UNKNOWN")
|
43
|
+
allow(MultiLogger).to receive(:error)
|
44
|
+
end
|
45
|
+
it { expect { subject }.to raise_error(SystemExit) }
|
34
46
|
end
|
35
47
|
end
|
36
48
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
5
|
+
require "fusuma/plugin/detectors/detector"
|
6
|
+
require "fusuma/plugin/buffers/buffer"
|
7
7
|
|
8
|
-
require
|
9
|
-
require
|
8
|
+
require "./lib/fusuma/plugin/buffers/appmatcher_buffer"
|
9
|
+
require "./lib/fusuma/plugin/detectors/appmatcher_detector"
|
10
10
|
|
11
11
|
module Fusuma
|
12
12
|
module Plugin
|
@@ -17,8 +17,8 @@ module Fusuma
|
|
17
17
|
@buffer = Buffers::AppmatcherBuffer.new
|
18
18
|
end
|
19
19
|
|
20
|
-
describe
|
21
|
-
context
|
20
|
+
describe "#detector" do
|
21
|
+
context "with no appmatcher event in buffer" do
|
22
22
|
before do
|
23
23
|
@buffer.clear
|
24
24
|
end
|
@@ -27,39 +27,39 @@ module Fusuma
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
context
|
30
|
+
context "with appmatcher events in buffer" do
|
31
31
|
before do
|
32
|
-
record = Events::Records::AppmatcherRecord.new(name:
|
33
|
-
event = Events::Event.new(tag:
|
32
|
+
record = Events::Records::AppmatcherRecord.new(name: "dummy")
|
33
|
+
event = Events::Event.new(tag: "appmatcher_parser", record: record)
|
34
34
|
|
35
35
|
@buffer.buffer(event)
|
36
36
|
end
|
37
37
|
it { expect(@detector.detect([@buffer])).to be_a Events::Event }
|
38
|
-
it
|
38
|
+
it "should detect ContextRecord" do
|
39
39
|
expect(@detector.detect([@buffer]).record).to be_a Events::Records::ContextRecord
|
40
40
|
end
|
41
|
-
it
|
41
|
+
it "should detect context: { application: dummy }" do
|
42
42
|
record = @detector.detect([@buffer]).record
|
43
43
|
expect(record.name).to eq :application
|
44
|
-
expect(record.value).to eq
|
44
|
+
expect(record.value).to eq "dummy"
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
context
|
48
|
+
context "with two different appmatcher events in buffer" do
|
49
49
|
before do
|
50
|
-
record1 = Events::Records::AppmatcherRecord.new(name:
|
51
|
-
record2 = Events::Records::AppmatcherRecord.new(name:
|
52
|
-
event1 = Events::Event.new(tag:
|
53
|
-
event2 = Events::Event.new(tag:
|
50
|
+
record1 = Events::Records::AppmatcherRecord.new(name: "dummy1")
|
51
|
+
record2 = Events::Records::AppmatcherRecord.new(name: "dummy2")
|
52
|
+
event1 = Events::Event.new(tag: "appmatcher_parser", record: record1)
|
53
|
+
event2 = Events::Event.new(tag: "appmatcher_parser", record: record2)
|
54
54
|
|
55
55
|
@buffer.buffer(event1)
|
56
56
|
@buffer.buffer(event2)
|
57
57
|
end
|
58
58
|
|
59
|
-
it
|
59
|
+
it "should detect latest application" do
|
60
60
|
record = @detector.detect([@buffer]).record
|
61
61
|
expect(record.name).to eq :application
|
62
|
-
expect(record.value).to eq
|
62
|
+
expect(record.value).to eq "dummy2"
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "bundler/setup"
|
4
|
+
require "fusuma/plugin/appmatcher"
|
5
5
|
|
6
6
|
RSpec.configure do |config|
|
7
7
|
# Enable flags like --only-failures and --next-failure
|
8
|
-
config.example_status_persistence_file_path =
|
8
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
9
9
|
|
10
10
|
# Disable RSpec exposing methods globally on `Module` and `main`
|
11
11
|
config.disable_monkey_patching!
|
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.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- iberianpig
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rexml
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '2.
|
47
|
+
version: '2.3'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '2.
|
54
|
+
version: '2.3'
|
55
55
|
description: fusuma-plugin-appmatcher is Fusuma plugin for assigning gesture mapping
|
56
56
|
per application.
|
57
57
|
email:
|
@@ -69,6 +69,10 @@ files:
|
|
69
69
|
- fusuma-plugin-appmatcher.gemspec
|
70
70
|
- lib/fusuma/plugin/appmatcher.rb
|
71
71
|
- lib/fusuma/plugin/appmatcher/gnome.rb
|
72
|
+
- lib/fusuma/plugin/appmatcher/gnome_extension.rb
|
73
|
+
- lib/fusuma/plugin/appmatcher/gnome_extensions/appmatcher@iberianpig.dev/extension.js
|
74
|
+
- lib/fusuma/plugin/appmatcher/gnome_extensions/appmatcher@iberianpig.dev/metadata.json
|
75
|
+
- lib/fusuma/plugin/appmatcher/gnome_extensions/installer.rb
|
72
76
|
- lib/fusuma/plugin/appmatcher/user_switcher.rb
|
73
77
|
- lib/fusuma/plugin/appmatcher/version.rb
|
74
78
|
- lib/fusuma/plugin/appmatcher/x11.rb
|
@@ -77,14 +81,16 @@ files:
|
|
77
81
|
- lib/fusuma/plugin/events/records/appmatcher_record.rb
|
78
82
|
- lib/fusuma/plugin/inputs/appmatcher_input.rb
|
79
83
|
- lib/fusuma/plugin/parsers/appmatcher_parser.rb
|
84
|
+
- spec/fusuma/plugin/appmatcher/gnome_extensions/installer_spec.rb
|
80
85
|
- spec/fusuma/plugin/appmatcher/gnome_spec.rb
|
81
86
|
- spec/fusuma/plugin/appmatcher_spec.rb
|
82
|
-
- spec/fusuma/plugin/
|
87
|
+
- spec/fusuma/plugin/detectors/appmatcher_detector_spec.rb
|
83
88
|
- spec/spec_helper.rb
|
84
89
|
homepage: https://github.com/iberianpig/fusuma-plugin-appmatcher
|
85
90
|
licenses:
|
86
91
|
- MIT
|
87
|
-
metadata:
|
92
|
+
metadata:
|
93
|
+
rubygems_mfa_required: 'true'
|
88
94
|
post_install_message:
|
89
95
|
rdoc_options: []
|
90
96
|
require_paths:
|
@@ -93,19 +99,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
99
|
requirements:
|
94
100
|
- - ">="
|
95
101
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
102
|
+
version: 2.5.1
|
97
103
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
104
|
requirements:
|
99
105
|
- - ">="
|
100
106
|
- !ruby/object:Gem::Version
|
101
107
|
version: '0'
|
102
108
|
requirements: []
|
103
|
-
rubygems_version: 3.
|
109
|
+
rubygems_version: 3.0.3.1
|
104
110
|
signing_key:
|
105
111
|
specification_version: 4
|
106
112
|
summary: Fusuma plugin to assign gesture mapping per application
|
107
113
|
test_files:
|
108
|
-
- spec/fusuma/plugin/appmatcher/gnome_spec.rb
|
109
114
|
- spec/fusuma/plugin/appmatcher_spec.rb
|
110
|
-
- spec/fusuma/plugin/
|
115
|
+
- spec/fusuma/plugin/appmatcher/gnome_extensions/installer_spec.rb
|
116
|
+
- spec/fusuma/plugin/appmatcher/gnome_spec.rb
|
117
|
+
- spec/fusuma/plugin/detectors/appmatcher_detector_spec.rb
|
111
118
|
- spec/spec_helper.rb
|