fusuma-plugin-appmatcher 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/appmatcher_detector_spec.rb +20 -20
- data/spec/spec_helper.rb +3 -3
- metadata +13 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8c8a2031d1414a081bd70dea5825e21f535ea1f841d4652e5c0b10a6ff29e06
|
4
|
+
data.tar.gz: 73ac1f0c80065a82cfb58bdb350d7c9b94fb4ffd7589be0dcdaf51ff3c7065eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1774db74c7a616f756bd95854c272c92eeaaabb1af75e3890742318add038ca624efca167122018ae136c19144c54b262a3bc34f12fff142556e914f49b965b5
|
7
|
+
data.tar.gz: 8bad8ab33a911177ea34e156fd8f5e7b842ecc020f6b4ddd6b3f18f81fbfba93a2210b2b58e2d2b77a87d8114585fc88ca1760eacd414d00fb7676524727ef18
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Fusuma::Plugin::Appmatcher [![Gem Version](https://badge.fury.io/rb/fusuma-plugin-appmatcher.svg)](https://badge.fury.io/rb/fusuma-plugin-appmatcher) [![Build Status](https://
|
1
|
+
# Fusuma::Plugin::Appmatcher [![Gem Version](https://badge.fury.io/rb/fusuma-plugin-appmatcher.svg)](https://badge.fury.io/rb/fusuma-plugin-appmatcher) [![Build Status](https://github.com/iberianpig/fusuma/actions/workflows/ubuntu.yml/badge.svg)](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.0"
|
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.0
|
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-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rexml
|
@@ -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,6 +81,7 @@ 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
87
|
- spec/fusuma/plugin/inputs/appmatcher_detector_spec.rb
|
@@ -84,7 +89,8 @@ files:
|
|
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
115
|
- spec/fusuma/plugin/inputs/appmatcher_detector_spec.rb
|
116
|
+
- spec/fusuma/plugin/appmatcher/gnome_extensions/installer_spec.rb
|
117
|
+
- spec/fusuma/plugin/appmatcher/gnome_spec.rb
|
111
118
|
- spec/spec_helper.rb
|