├── example
└── meson.build
├── icons
├── 48x48
│ └── wstroke.png
├── 64x64
│ └── wstroke.png
├── 72x72
│ └── wstroke.png
├── 96x96
│ └── wstroke.png
├── 128x128
│ └── wstroke.png
├── 160x160
│ └── wstroke.png
├── 192x192
│ └── wstroke.png
├── convert-icons.fsh
├── meson.build
├── wstroke.svg
└── plugin-wstroke.svg
├── src
├── config.h.in
├── resources.xml
├── input_inhibitor.vapi
├── stroke_draw.h
├── meson.build
├── stroke.h
├── stroke_drawing_area.h
├── convert_keycodes.h
├── gesture.cc
├── actiondb_plugin.cc
├── appchooser.h
├── input_events.hpp
├── stroke_drawing_area.cpp
├── convert_keycodes.cc
├── gesture.h
├── stroke_draw.cc
├── cellrenderertextish.vala
├── appchooser.cc
├── main.cc
├── stroke.c
├── actions.h
├── input_events.cpp
├── actiondb.cc
├── actiondb_config.cc
└── actiondb.h
├── wstroke-config.desktop
├── wstroke-config.1
├── LICENSE
├── input-inhibitor
├── meson.build
├── input_inhibitor.h
├── input_inhibitor.c
└── wlr-input-inhibitor-unstable-v1.xml
├── toplevel-grabber
├── meson.build
├── toplevel-grabber-test.c
├── toplevel-grabber.h
├── toplevel-grabber.c
└── wlr-foreign-toplevel-management-unstable-v1.xml
├── NEWS
├── meson.build
├── wstroke.xml
└── README.md
/example/meson.build:
--------------------------------------------------------------------------------
1 | install_data('actions-wstroke-2', install_dir: conf_data.get('DATA_DIR'))
2 |
--------------------------------------------------------------------------------
/icons/48x48/wstroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkondor/wstroke/HEAD/icons/48x48/wstroke.png
--------------------------------------------------------------------------------
/icons/64x64/wstroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkondor/wstroke/HEAD/icons/64x64/wstroke.png
--------------------------------------------------------------------------------
/icons/72x72/wstroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkondor/wstroke/HEAD/icons/72x72/wstroke.png
--------------------------------------------------------------------------------
/icons/96x96/wstroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkondor/wstroke/HEAD/icons/96x96/wstroke.png
--------------------------------------------------------------------------------
/icons/128x128/wstroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkondor/wstroke/HEAD/icons/128x128/wstroke.png
--------------------------------------------------------------------------------
/icons/160x160/wstroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkondor/wstroke/HEAD/icons/160x160/wstroke.png
--------------------------------------------------------------------------------
/icons/192x192/wstroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkondor/wstroke/HEAD/icons/192x192/wstroke.png
--------------------------------------------------------------------------------
/src/config.h.in:
--------------------------------------------------------------------------------
1 | #ifndef CONFIG_H
2 | #define CONFIG_H
3 |
4 | #define DATA_DIR "@DATA_DIR@"
5 |
6 | #endif
7 |
--------------------------------------------------------------------------------
/src/resources.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | gui.glade
5 |
6 |
7 |
--------------------------------------------------------------------------------
/icons/convert-icons.fsh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/fish
2 | for a in 48 64 72 96 128 160 192
3 | set s "$a"x"$a"
4 | set d (math $a \* 2.25)
5 | convert -background none -density $d wstroke.svg $s/wstroke.png
6 | end
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/input_inhibitor.vapi:
--------------------------------------------------------------------------------
1 |
2 | [CCode (cheader_filename = "input_inhibitor.h")]
3 | namespace Inhibitor {
4 | [CCode (cname = "input_inhibitor_grab")]
5 | bool grab();
6 | [CCode (cname = "input_inhibitor_ungrab")]
7 | void ungrab();
8 | }
9 |
--------------------------------------------------------------------------------
/wstroke-config.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Version=1.0
3 | Name=WStroke config
4 | Type=Application
5 | Terminal=false
6 | Exec=wstroke-config
7 | Icon=wstroke
8 | Categories=GTK;Utility;Accessibility;
9 | Comment=Configure mouse gestures used with Wayfire
10 | Keywords=gestures;input;wayfire;
11 |
--------------------------------------------------------------------------------
/wstroke-config.1:
--------------------------------------------------------------------------------
1 | .TH WSTROKE-CONFIG 1 2024-08-11
2 | .SH NAME
3 | wstroke-config \- graphical configuration of mouse gestures
4 | .SH SYNOPSIS
5 | .BR wstroke-config
6 | .SH DESCRIPTION
7 | .BR wstroke-config
8 | is a graphical utility to configure gestures recognized by
9 | .BR wstroke ,
10 | a mouse gesture plugin for the
11 | .BR wayfire (1)
12 | compositor.
13 | Note that additional options that affect the behavior of
14 | .BR wstroke
15 | are configured using wayfire's regular config system, e.g. by
16 | .BR wcm.
17 | .SH ENVIRONMENT VARIABLES
18 | .BR wstroke-config
19 | respects the XDG_CONFIG_HOME environment variable: configuration is stored under
20 | ${XDG_CONFIG_HOME}/wstroke.
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008-2009, Thomas Jaeger
2 | Copyright (c) 2020-2023, Daniel Kondor
3 |
4 | Permission to use, copy, modify, and/or distribute this software for any
5 | purpose with or without fee is hereby granted, provided that the above
6 | copyright notice and this permission notice appear in all copies.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14 | PERFORMANCE OF THIS SOFTWARE.
15 |
--------------------------------------------------------------------------------
/input-inhibitor/meson.build:
--------------------------------------------------------------------------------
1 | client_protocols = [
2 | './wlr-input-inhibitor-unstable-v1.xml'
3 | ]
4 |
5 | wl_protos_client_src = []
6 | wl_protos_headers = []
7 |
8 | foreach p : client_protocols
9 | xml = join_paths(p)
10 | wl_protos_headers += wayland_scanner_client.process(xml)
11 | wl_protos_client_src += wayland_scanner_code.process(xml)
12 | endforeach
13 |
14 | lib_inhibitor_protos = static_library('wl_inhibitor_protos', wl_protos_client_src + wl_protos_headers,
15 | dependencies: [wayland_client]) # for the include directory
16 |
17 | protos = declare_dependency(
18 | link_with: lib_inhibitor_protos,
19 | sources: wl_protos_headers,
20 | )
21 |
22 | input_inhibitor = static_library('input_inhibitor', 'input_inhibitor.c',
23 | dependencies: [wayland_client, protos, gdk])
24 |
25 | input_inhibitor_dep = declare_dependency(
26 | link_with: input_inhibitor,
27 | include_directories: include_directories('.')
28 | )
29 |
--------------------------------------------------------------------------------
/icons/meson.build:
--------------------------------------------------------------------------------
1 | install_data('plugin-wstroke.svg', install_dir: wayfire.get_variable(pkgconfig: 'icondir'))
2 | install_data('wstroke.svg', install_dir: join_paths(icon_dir, 'hicolor', 'scalable', 'apps'))
3 | install_data(join_paths('48x48', 'wstroke.png'), install_dir: join_paths(icon_dir, 'hicolor', '48x48', 'apps'))
4 | install_data(join_paths('64x64', 'wstroke.png'), install_dir: join_paths(icon_dir, 'hicolor', '64x64', 'apps'))
5 | install_data(join_paths('72x72', 'wstroke.png'), install_dir: join_paths(icon_dir, 'hicolor', '72x72', 'apps'))
6 | install_data(join_paths('96x96', 'wstroke.png'), install_dir: join_paths(icon_dir, 'hicolor', '96x96', 'apps'))
7 | install_data(join_paths('128x128', 'wstroke.png'), install_dir: join_paths(icon_dir, 'hicolor', '128x128', 'apps'))
8 | install_data(join_paths('160x160', 'wstroke.png'), install_dir: join_paths(icon_dir, 'hicolor', '160x160', 'apps'))
9 | install_data(join_paths('192x192', 'wstroke.png'), install_dir: join_paths(icon_dir, 'hicolor', '192x192', 'apps'))
10 |
--------------------------------------------------------------------------------
/toplevel-grabber/meson.build:
--------------------------------------------------------------------------------
1 | # toplevel-grabber library and example program
2 | grabber_protocols = [
3 | './wlr-foreign-toplevel-management-unstable-v1.xml'
4 | ]
5 |
6 | grabber_protos_client_src = []
7 | grabber_protos_headers = []
8 |
9 | foreach p : grabber_protocols
10 | xml = join_paths(p)
11 | grabber_protos_headers += wayland_scanner_client.process(xml)
12 | grabber_protos_client_src += wayland_scanner_code.process(xml)
13 | endforeach
14 |
15 | lib_grabber_protos = static_library('grabber_protos', grabber_protos_client_src + grabber_protos_headers,
16 | dependencies: [wayland_client]) # for the include directory
17 |
18 | lib_grabber_protos_dep = declare_dependency(
19 | link_with: lib_grabber_protos,
20 | sources: grabber_protos_headers,
21 | )
22 |
23 | toplevel_grabber = static_library('toplevel_grabber', 'toplevel-grabber.c',
24 | dependencies: [wayland_client, lib_grabber_protos_dep],
25 | c_args: ['-D_POSIX_C_SOURCE=200809L'])
26 |
27 | toplevel_grabber_dep = declare_dependency(
28 | link_with: toplevel_grabber,
29 | include_directories: include_directories('.')
30 | )
31 |
32 | toplevel_grabber_test = executable('tl_grabber_test', 'toplevel-grabber-test.c',
33 | dependencies: [toplevel_grabber_dep],
34 | install: false)
35 |
--------------------------------------------------------------------------------
/input-inhibitor/input_inhibitor.h:
--------------------------------------------------------------------------------
1 | /*
2 | * input_inhibitor.h -- inhibitor for grabbing key combinations
3 | *
4 | * Copyright 2020 Daniel Kondor
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | *
18 | */
19 |
20 |
21 | #ifndef INPUT_INHIBITOR_H
22 | #define INPUT_INHIBITOR_H
23 |
24 | #include
25 |
26 | #ifdef __cplusplus
27 | extern "C" {
28 | #endif
29 |
30 | /* Try to initialize inhibitor (keyboard grabber) interface. */
31 | gboolean input_inhibitor_init();
32 |
33 | /* Try to grab keyboard. */
34 | gboolean input_inhibitor_grab();
35 |
36 | /* Release an existing grab. */
37 | void input_inhibitor_ungrab();
38 |
39 | #ifdef __cplusplus
40 | }
41 | #endif
42 |
43 | #endif
44 |
45 |
--------------------------------------------------------------------------------
/NEWS:
--------------------------------------------------------------------------------
1 | 2.4.0
2 | =========
3 | * Support the Vulkan and pixman renderers for drawing strokes
4 | * Depend on Wayfire release 0.10.0 and wlroots release 0.19
5 |
6 | 2.3.0
7 | =========
8 | * Adapt to JSON API changes in Wayfire
9 | * Depend on Wayfire version >= 0.10.0 (in development) and wlroots version = 0.18
10 |
11 | 2.2.1
12 | =========
13 | * Fix a build issue
14 | * Build now requires meson version >= 1.0.0
15 |
16 | 2.2.0
17 | =========
18 | * Support wlroots 0.18 (if used by Wayfire)
19 | * Add a "rotate cube" action
20 |
21 | 2.1.3
22 | =========
23 | * Add man page
24 | * Make rebuilding Vala code the default
25 |
26 | 2.1.2
27 | =========
28 | * Bug fix: avoid crashing if a gesture is started on a popup
29 |
30 | 2.1.1
31 | =========
32 | * WM Action / Move: avoid a spurious move of the view when the action is initiated
33 |
34 | 2.1
35 | =========
36 | * Command action: allow to select an app to run graphically
37 |
38 | 2.0.2
39 | =========
40 | * Avoid a potential edge case where refocusing the original view conflicts with the Command action.
41 | * Refactor the use of GtkApplication.
42 |
43 | 2.0.1
44 | =========
45 | * Allow recording strokes with the same button that is used by the plugin
46 |
47 | 2.0
48 | =========
49 | * Refactor internal storage and clean code
50 | * Gestures can be freely reordered
51 | * Fix issues related to the display of apps and groups
52 | * Slight updates to the GUI
53 | * Add "Touchpad Gesture" action (including the "Scroll" action from Easystroke)
54 | * Add the possibility to import / export the gesture configuration
55 |
56 | 1.0
57 | =========
58 | * Original release, most features work
59 |
--------------------------------------------------------------------------------
/src/stroke_draw.h:
--------------------------------------------------------------------------------
1 | /*
2 | * stroke_draw.h
3 | *
4 | * Copyright (c) 2008-2009, Thomas Jaeger
5 | * Copyright (c) 2020-2023 Daniel Kondor
6 | *
7 | * Permission to use, copy, modify, and/or distribute this software for any
8 | * purpose with or without fee is hereby granted, provided that the above
9 | * copyright notice and this permission notice appear in all copies.
10 | *
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
16 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
17 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 | *
19 | */
20 |
21 |
22 | #ifndef STROKE_DRAW_H
23 | #define STROKE_DRAW_H
24 |
25 | #include "gesture.h"
26 | #include
27 |
28 | class StrokeDrawer {
29 | protected:
30 | static Glib::RefPtr drawEmpty_(int);
31 | static Glib::RefPtr pbEmpty;
32 |
33 | public:
34 | static Glib::RefPtr draw(const Stroke* stroke, int size, double width = 2.0);
35 | static void draw(const Stroke* stroke, Cairo::RefPtr surface, int x, int y, int w, int h, double width = 2.0);
36 | static void draw_svg(const Stroke* stroke, std::string filename);
37 | static Glib::RefPtr drawEmpty(int);
38 | };
39 |
40 | #endif
41 |
42 |
--------------------------------------------------------------------------------
/src/meson.build:
--------------------------------------------------------------------------------
1 | # resources (GUI layout)
2 | econf_res = gnome.compile_resources(
3 | 'ecres', 'resources.xml',
4 | source_dir: 'data',
5 | c_name: 'econf'
6 | )
7 |
8 | conf_data = configuration_data()
9 | conf_data.set('DATA_DIR', join_paths(datadir, 'wstroke'))
10 | configure_file(input: 'config.h.in',
11 | output: 'config.h',
12 | install: false,
13 | configuration: conf_data)
14 |
15 | # note: this is code generated by Vala, compile it separately to
16 | # silence warnings
17 | cellib = static_library('cellib', 'cellrenderertextish.vala',
18 | vala_header: 'cellrenderertextish.h',
19 | dependencies: [glib, gobject, gtk, input_inhibitor_dep])
20 |
21 | wconf_sources = ['main.cc', 'actiondb_config.cc', 'actiondb.cc', 'actions.cc',
22 | 'appchooser.cc', 'gesture.cc', 'stroke_draw.cc', 'stroke.c',
23 | 'convert_keycodes.cc', 'stroke_drawing_area.cpp', econf_res]
24 | wconf = executable('wstroke-config', wconf_sources,
25 | dependencies: [gtkmm, gdkmm, wlroots_headers, boost, protos, input_inhibitor_dep, toplevel_grabber_dep],
26 | install: true,
27 | cpp_args: ['-DACTIONDB_CONVERT_CODES', '-DWLR_USE_UNSTABLE'],
28 | link_with: cellib)
29 |
30 |
31 | wslib_sources = ['easystroke_gestures.cpp', 'input_events.cpp', 'actiondb.cc', 'actiondb_plugin.cc', 'gesture.cc', 'stroke.c']
32 | wslib = shared_module('wstroke', wslib_sources,
33 | dependencies: [wayfire, wlroots, wlserver, boost, glibmm, cairo, pixman],
34 | install: true,
35 | install_dir: wayfire.get_variable(pkgconfig: 'plugindir'),
36 | cpp_args: ['-Wno-unused-parameter', '-Wno-format-security','-DWAYFIRE_PLUGIN', '-DWLR_USE_UNSTABLE'],
37 | link_args: '-rdynamic')
38 |
39 |
--------------------------------------------------------------------------------
/src/stroke.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009, Thomas Jaeger
3 | * Copyright (c) 2023, Daniel Kondor
4 | *
5 | * Permission to use, copy, modify, and/or distribute this software for any
6 | * purpose with or without fee is hereby granted, provided that the above
7 | * copyright notice and this permission notice appear in all copies.
8 | *
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
12 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
14 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
15 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 | */
17 | #ifndef __STROKE_H__
18 | #define __STROKE_H__
19 |
20 | #ifdef __cplusplus
21 | extern "C" {
22 | #endif
23 |
24 | struct _stroke_t;
25 |
26 | typedef struct _stroke_t stroke_t;
27 |
28 | stroke_t *stroke_alloc(int n);
29 | void stroke_add_point(stroke_t *stroke, double x, double y);
30 | void stroke_finish(stroke_t *stroke);
31 | void stroke_free(stroke_t *stroke);
32 | stroke_t *stroke_copy(const stroke_t *stroke);
33 |
34 | int stroke_get_size(const stroke_t *stroke);
35 | void stroke_get_point(const stroke_t *stroke, int n, double *x, double *y);
36 | double stroke_get_time(const stroke_t *stroke, int n);
37 | double stroke_get_angle(const stroke_t *stroke, int n);
38 | double stroke_angle_difference(const stroke_t *a, const stroke_t *b, int i, int j);
39 |
40 | double stroke_compare(const stroke_t *a, const stroke_t *b, int *path_x, int *path_y);
41 |
42 | extern const double stroke_infinity;
43 |
44 | #ifdef __cplusplus
45 | }
46 | #endif
47 | #endif
48 |
--------------------------------------------------------------------------------
/src/stroke_drawing_area.h:
--------------------------------------------------------------------------------
1 | /*
2 | * stroke_drawing_area.h -- Gtk.DrawingArea adapted to record new strokes
3 | *
4 | * Copyright 2020-2023 Daniel Kondor
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | *
18 | */
19 |
20 |
21 | #ifndef STROKE_DRAWING_AREA_H
22 | #define STROKE_DRAWING_AREA_H
23 |
24 | #include
25 | #include "gesture.h"
26 |
27 | class SRArea : public Gtk::DrawingArea {
28 | public:
29 | SRArea();
30 | void clear();
31 | Stroke* get_stroke() { return &stroke; }
32 | sigc::signal stroke_recorded;
33 | virtual ~SRArea() { }
34 | protected:
35 | bool on_draw(const Cairo::RefPtr& cr) override;
36 | bool on_button_press_event(GdkEventButton* event) override;
37 | bool on_button_release_event(GdkEventButton* event) override;
38 | bool on_motion_notify_event(GdkEventMotion* event) override;
39 | bool on_configure_event(GdkEventConfigure* event) override;
40 |
41 | void draw_line(gdouble x, gdouble y);
42 |
43 | Cairo::RefPtr surface;
44 | guint current_button = 0;
45 | gdouble last_x;
46 | gdouble last_y;
47 |
48 | Stroke::PreStroke ps;
49 | Stroke stroke;
50 | };
51 |
52 |
53 | #endif
54 |
55 |
--------------------------------------------------------------------------------
/src/convert_keycodes.h:
--------------------------------------------------------------------------------
1 | /*
2 | * convert_keycodes.h
3 | *
4 | * Copyright 2020 Daniel Kondor
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | *
18 | */
19 |
20 |
21 | #include
22 | #include
23 |
24 | #ifndef CONVERT_KEYCODES_H
25 | #define CONVERT_KEYCODES_H
26 |
27 | class KeyCodes {
28 | public:
29 | /* convert from a combination of Gdk modifier constants to the
30 | * WLR modifier enum constants (take care of "virtual" modifiers
31 | * like SUPER, ALT, etc.) */
32 | static uint32_t convert_modifier(uint32_t mod);
33 | /* add back "virtual" modifiers -- calls the corresponding GDK function */
34 | static uint32_t add_virtual_modifiers(uint32_t mod);
35 | /* try to convert a keysym to a hardware keycode; returns the
36 | * keycode or zero if it was not found */
37 | static uint32_t convert_keysym(uint32_t key);
38 | /* convert a hardware keycode to a keysym (using level = 0 and
39 | * group = 0) for the purpose of displaying it to the user */
40 | static uint32_t convert_keycode(uint32_t code);
41 |
42 | static void init();
43 |
44 | static unsigned int keycode_errors;
45 | protected:
46 | static GdkKeymap* keymap;
47 | };
48 |
49 |
50 | #endif
51 |
52 |
--------------------------------------------------------------------------------
/toplevel-grabber/toplevel-grabber-test.c:
--------------------------------------------------------------------------------
1 | /*
2 | * toplevel-grabber-test.c -- test selecting a toplevel view based on
3 | * user interaction
4 | *
5 | * Copyright 2020 Daniel Kondor
6 | *
7 | * Permission to use, copy, modify, and/or distribute this software for any
8 | * purpose with or without fee is hereby granted, provided that the above
9 | * copyright notice and this permission notice appear in all copies.
10 | *
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
16 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
17 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 | *
19 | */
20 |
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | static int is_done = 0;
28 |
29 | static void tl_cb(void* data, struct tl_grabber* gr) {
30 | char* app_id = toplevel_grabber_get_app_id(gr);
31 | printf("Activated app: %s\n", app_id ? app_id : "(null)");
32 | if(app_id) free(app_id);
33 | toplevel_grabber_free(gr);
34 | is_done = 1;
35 | }
36 |
37 |
38 | int main() {
39 | struct wl_display* dpy = wl_display_connect(NULL);
40 | if(!dpy) {
41 | fprintf(stderr, "Cannot connect to display!\n");
42 | return 1;
43 | }
44 |
45 | struct tl_grabber* gr = toplevel_grabber_new(dpy, NULL, NULL);
46 | if(!gr) {
47 | fprintf(stderr, "Cannot create grabber interface!\n");
48 | return 1;
49 | }
50 | printf("Starting grabber, click to select a toplevel view\n");
51 | toplevel_grabber_set_callback(gr, tl_cb, NULL);
52 |
53 | while(!(is_done || wl_display_dispatch(dpy) == -1));
54 | if(!is_done) toplevel_grabber_free(gr);
55 |
56 | return 0;
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/src/gesture.cc:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2009, Thomas Jaeger
3 | * Copyright (c) 2020-2023, Daniel Kondor
4 | *
5 | * Permission to use, copy, modify, and/or distribute this software for any
6 | * purpose with or without fee is hereby granted, provided that the above
7 | * copyright notice and this permission notice appear in all copies.
8 | *
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
12 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
14 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
15 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 | */
17 | #include "gesture.h"
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | BOOST_CLASS_EXPORT(Stroke)
27 |
28 | Stroke::Stroke(const PreStroke &ps) : stroke(nullptr, stroke_deleter()) {
29 | if (ps.size() >= 2) {
30 | stroke_t *s = stroke_alloc(ps.size());
31 | for (const auto& t : ps)
32 | stroke_add_point(s, t.x, t.y);
33 | stroke_finish(s);
34 | stroke.reset(s);
35 | }
36 | }
37 |
38 | int Stroke::compare(const Stroke& a, const Stroke& b, double &score) {
39 | score = 0.0;
40 | if (!a.stroke || !b.stroke) {
41 | if (!a.stroke && !b.stroke) {
42 | score = 1.0;
43 | return 1;
44 | }
45 | return -1;
46 | }
47 | double cost = stroke_compare(a.stroke.get(), b.stroke.get(), nullptr, nullptr);
48 | if (cost >= stroke_infinity)
49 | return -1;
50 | score = std::max(1.0 - 2.5*cost, 0.0);
51 | return score > 0.7;
52 | }
53 |
54 | /*
55 | Stroke Stroke::trefoil() {
56 | PreStroke s;
57 | const unsigned int n = 40;
58 | s.reserve(n);
59 | for (unsigned int i = 0; i<=n; i++) {
60 | double phi = M_PI*(-4.0*i/n)-2.7;
61 | double r = exp(1.0 + sin(6.0*M_PI*i/n)) + 2.0;
62 | s.add(Point{(float)(r*cos(phi)), (float)(r*sin(phi))});
63 | }
64 | return Stroke(s);
65 | }
66 | */
67 |
68 |
--------------------------------------------------------------------------------
/src/actiondb_plugin.cc:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2009, Thomas Jaeger
3 | * Copyright (c) 2023, Daniel Kondor
4 | *
5 | * Permission to use, copy, modify, and/or distribute this software for any
6 | * purpose with or without fee is hereby granted, provided that the above
7 | * copyright notice and this permission notice appear in all copies.
8 | *
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
12 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
14 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
15 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 | */
17 |
18 | #include "gesture.h"
19 | #include "actiondb.h"
20 |
21 | template<>
22 | const std::string& ActionListDiff::get_stroke_name(unique_t id) const {
23 | auto it = added.find(id);
24 | if(it != added.end() && it->second.name != "") return it->second.name;
25 | //!! TODO: check for non-null parent ??
26 | return parent->get_stroke_name(id);
27 | }
28 |
29 | template<>
30 | std::map ActionListDiff::get_strokes() const {
31 | std::map strokes = parent ? parent->get_strokes() : std::map();
32 | for(const auto& x : deleted) strokes.erase(x);
33 | for(const auto& x : added) if(!x.second.stroke.trivial()) strokes[x.first] = &x.second.stroke;
34 | return strokes;
35 | }
36 |
37 | template<>
38 | Action* ActionListDiff::handle(const Stroke& s, Ranking* r) const {
39 | double best_score = 0.0;
40 | Action* ret = nullptr;
41 | if(r) r->stroke = &s;
42 | const auto strokes = get_strokes();
43 | for(const auto& x : strokes) {
44 | const Stroke& y = *x.second;
45 | double score;
46 | int match = Stroke::compare(s, y, score);
47 | if (match < 0)
48 | continue;
49 | bool new_best = false;
50 | if(score > best_score) {
51 | new_best = true;
52 | best_score = score;
53 | ret = get_stroke_action(x.first);
54 | if(r) r->best_stroke = &y;
55 | }
56 | if(r) {
57 | const std::string& name = get_stroke_name(x.first);
58 | r->r.insert(std::pair >
59 | (score, std::pair(name, &y)));
60 | if(new_best) r->name = name;
61 | }
62 | }
63 |
64 | if(r) {
65 | r->score = best_score;
66 | r->action = ret;
67 | }
68 | return ret;
69 | }
70 |
--------------------------------------------------------------------------------
/input-inhibitor/input_inhibitor.c:
--------------------------------------------------------------------------------
1 | /*
2 | * input_inhibitor.c -- inhibitor for grabbing key combinations
3 | *
4 | * Copyright 2020 Daniel Kondor
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | *
18 | */
19 |
20 |
21 | #include
22 | #include
23 | #include
24 |
25 | static struct zwlr_input_inhibitor_v1* grab = NULL;
26 | static struct zwlr_input_inhibit_manager_v1* inhibitor = NULL;
27 |
28 |
29 | static void _add(G_GNUC_UNUSED void *data, struct wl_registry *registry,
30 | uint32_t name, const char *interface, G_GNUC_UNUSED uint32_t version) {
31 | if(strcmp(interface, zwlr_input_inhibit_manager_v1_interface.name) == 0)
32 | inhibitor = (struct zwlr_input_inhibit_manager_v1*)wl_registry_bind(registry, name, &zwlr_input_inhibit_manager_v1_interface, 1u);
33 | }
34 |
35 | static void _remove(G_GNUC_UNUSED void *data, G_GNUC_UNUSED struct wl_registry *registry, G_GNUC_UNUSED uint32_t name) { }
36 |
37 | static struct wl_registry_listener listener = { &_add, &_remove };
38 |
39 |
40 | gboolean input_inhibitor_init() {
41 | struct wl_display* display = gdk_wayland_display_get_wl_display(gdk_display_get_default());
42 | if(!display) return FALSE;
43 |
44 | struct wl_registry* registry = wl_display_get_registry(display);
45 | if(!registry) return FALSE;
46 |
47 | wl_registry_add_listener(registry, &listener, NULL);
48 | wl_display_dispatch(display);
49 | wl_display_roundtrip(display);
50 | if (!inhibitor) return FALSE;
51 | return TRUE;
52 | }
53 |
54 | gboolean input_inhibitor_grab() {
55 | if(!inhibitor) return FALSE;
56 | if(grab) return TRUE;
57 | grab = zwlr_input_inhibit_manager_v1_get_inhibitor(inhibitor);
58 | if(!grab) return FALSE;
59 | return TRUE;
60 | }
61 |
62 | void input_inhibitor_ungrab() {
63 | if(grab) {
64 | zwlr_input_inhibitor_v1_destroy(grab);
65 | wl_display_flush(gdk_wayland_display_get_wl_display(gdk_display_get_default()));
66 | grab = NULL;
67 | }
68 | }
69 |
70 |
71 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project(
2 | 'wstroke',
3 | 'c',
4 | 'cpp',
5 | 'vala',
6 | version: '2.4.0',
7 | license: 'MIT',
8 | meson_version: '>=1.0.0',
9 | default_options: [
10 | 'cpp_std=c++17',
11 | 'c_std=c11',
12 | 'warning_level=2',
13 | 'werror=false',
14 | ],
15 | )
16 |
17 | # paths (only needed to install icon and desktop file)
18 | prefix = get_option('prefix')
19 | datadir = join_paths(prefix, get_option('datadir'))
20 | icon_dir = join_paths(datadir, 'icons')
21 | desktop_dir = join_paths(datadir, 'applications')
22 |
23 | # dependencies for loadable plugin
24 | boost = dependency('boost', modules: ['serialization'], static: false)
25 | wayfire = dependency('wayfire', version: '>=0.10.0')
26 | wlroots = dependency('wlroots-0.19')
27 | wlroots_headers = wlroots.partial_dependency(includes: true, compile_args: true)
28 | wlserver = dependency('wayland-server')
29 | glibmm = dependency('glibmm-2.4')
30 | cairo = dependency('cairo')
31 | pixman = dependency('pixman-1')
32 |
33 | # additional dependencies for GUI
34 | gtkmm = dependency('gtkmm-3.0')
35 | gdkmm = dependency('gdkmm-3.0')
36 | glib = dependency('glib-2.0')
37 | gobject = dependency('gobject-2.0')
38 | gtk = dependency('gtk+-3.0')
39 | gdk = dependency('gdk-3.0')
40 |
41 | gnome = import('gnome')
42 |
43 | # filesystem library support
44 | # note: on Ubuntu 18.04 this only works with clang++
45 | cpp = meson.get_compiler('cpp')
46 | if cpp.has_link_argument('-lc++fs')
47 | add_project_link_arguments(['-lc++fs'], language: 'cpp')
48 | elif cpp.has_link_argument('-lc++experimental')
49 | add_project_link_arguments(['-lc++experimental'], language: 'cpp')
50 | elif cpp.has_link_argument('-lstdc++fs')
51 | add_project_link_arguments(['-lstdc++fs'], language: 'cpp')
52 | endif
53 |
54 |
55 | # wayland-scanner -- needed for keyboard grabber and input inhibitor
56 | wayland_client = dependency('wayland-client')
57 | wayland_scanner = find_program('wayland-scanner')
58 |
59 | wayland_scanner_code = generator(
60 | wayland_scanner,
61 | output: '@BASENAME@-protocol.c',
62 | arguments: ['private-code', '@INPUT@', '@OUTPUT@'],
63 | )
64 |
65 | wayland_scanner_client = generator(
66 | wayland_scanner,
67 | output: '@BASENAME@-client-protocol.h',
68 | arguments: ['client-header', '@INPUT@', '@OUTPUT@'],
69 | )
70 |
71 |
72 | add_project_arguments(['--vapidir', meson.current_source_dir() + '/src'], language: 'vala')
73 | add_project_arguments(['--pkg', 'input_inhibitor'], language: 'vala')
74 |
75 | subdir('input-inhibitor')
76 | subdir('toplevel-grabber')
77 | subdir('src')
78 | subdir('example')
79 |
80 |
81 | install_data('wstroke.xml', install_dir: wayfire.get_variable(pkgconfig: 'metadatadir'))
82 | install_data('wstroke-config.desktop', install_dir: desktop_dir)
83 | install_man('wstroke-config.1')
84 | subdir('icons')
85 |
--------------------------------------------------------------------------------
/src/appchooser.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2023, Daniel Kondor
3 | *
4 | * Permission to use, copy, modify, and/or distribute this software for any
5 | * purpose with or without fee is hereby granted, provided that the above
6 | * copyright notice and this permission notice appear in all copies.
7 | *
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
11 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
13 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 | */
16 |
17 |
18 | #ifndef APPCHOOSER_H
19 | #define APPCHOOSER_H
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | class AppChooser {
28 | private:
29 | Glib::RefPtr widgets;
30 | Gtk::Dialog* dialog;
31 | Gtk::HeaderBar* header;
32 | Gtk::ScrolledWindow* sw;
33 | Gtk::SearchEntry* searchentry;
34 | Gtk::Entry* entry;
35 | Gtk::CheckButton* cb;
36 | Gtk::Button *select_ok;
37 | Glib::ustring filter_lower;
38 |
39 | GAppInfoMonitor* monitor = nullptr;
40 |
41 | class AppBox : public Gtk::Box {
42 | private:
43 | Glib::RefPtr app;
44 | Glib::ustring name_lower;
45 | public:
46 | AppBox(Glib::RefPtr& app_);
47 | Glib::RefPtr get_app() const { return app; }
48 | bool filter(const Glib::ustring& filter) const { return name_lower.find(filter) != name_lower.npos; } // filter already lowercase
49 | int compare(const AppBox& box) const { return name_lower.compare(box.name_lower); }
50 | };
51 |
52 | struct AppContent {
53 | std::vector > apps;
54 | std::unique_ptr flowbox;
55 | };
56 |
57 | std::unique_ptr apps;
58 | std::atomic apps_pending;
59 |
60 | std::thread thread;
61 | std::mutex mutex;
62 | bool thread_running = false;
63 | bool more_work = false;
64 | std::atomic exit_request{false};
65 | bool first_run = false;
66 |
67 | bool update_pending = false;
68 |
69 | void update_apps();
70 | void thread_func();
71 |
72 | friend void on_apps_changed(GAppInfoMonitor*, void* p);
73 |
74 | static int apps_sort(const Gtk::FlowBoxChild* x, const Gtk::FlowBoxChild* y);
75 | bool apps_filter(const Gtk::FlowBoxChild* x) const;
76 |
77 | public:
78 | Glib::RefPtr res_app;
79 | std::string res_cmdline;
80 | bool custom_res = false;
81 |
82 | AppChooser(Glib::RefPtr& w) : widgets(w) { }
83 | ~AppChooser();
84 |
85 | void startup();
86 |
87 | bool run(const Glib::ustring& gesture_name, const Glib::ustring& custom_command);
88 | };
89 |
90 | #endif
91 |
92 |
93 |
--------------------------------------------------------------------------------
/toplevel-grabber/toplevel-grabber.h:
--------------------------------------------------------------------------------
1 | /*
2 | * toplevel-grabber.h -- library using the wlr-foreign-toplevel
3 | * interface to get the ID of an activated toplevel view
4 | *
5 | * Copyright 2020 Daniel Kondor
6 | *
7 | * Permission to use, copy, modify, and/or distribute this software for any
8 | * purpose with or without fee is hereby granted, provided that the above
9 | * copyright notice and this permission notice appear in all copies.
10 | *
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
16 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
17 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 | *
19 | */
20 |
21 |
22 | #ifndef TOPLEVEL_GRABBER_H
23 | #define TOPLEVEL_GRABBER_H
24 |
25 | #ifdef __cplusplus
26 | extern "C" {
27 | #endif
28 |
29 | #include
30 |
31 | struct tl_grabber;
32 |
33 | /*
34 | * Create a new grabber and start listening to events about toplevels.
35 | * Parameters:
36 | * dpy -- Wayland display connectoin to use (must be given by the caller)
37 | * callback -- function to be called when a new toplevel is activated (can be NULL)
38 | * data -- user data to pass to the callback function
39 | */
40 | struct tl_grabber* toplevel_grabber_new(struct wl_display* dpy,
41 | void (*callback)(void* data, struct tl_grabber* gr), void* data);
42 |
43 | /*
44 | * Get the last activated app ID (if any). Returns a copy of the app ID,
45 | * or NULL if no app was activated or no app ID can be determined. If the
46 | * return value is non-NULL, the caller must free() it.
47 | */
48 | char* toplevel_grabber_get_app_id(struct tl_grabber* gr);
49 |
50 | /*
51 | * Reset the currently active app, i.e. toplevel_grabber_get_app_id()
52 | * will return NULL until a new toplevel is activated again.
53 | * void toplevel_grabber_reset(struct tl_grabber* gr);
54 | */
55 |
56 | /*
57 | * Set the callback function to be called when a new toplevel is activated.
58 | */
59 | void toplevel_grabber_set_callback(struct tl_grabber* gr,
60 | void (*callback)(void* data, struct tl_grabber* gr), void* data);
61 |
62 | /*
63 | * Activate any toplevel view with a matching app_id on the given seat.
64 | * If parent is true, it selects the topmost parent if multiple views
65 | * in a hierarchy have the same app ID.
66 | * This is useful to re-show the caller's view after the user has
67 | * selected another app by activating it.
68 | * Returns 0 on success or -1 if the given app ID was not found.
69 | */
70 | int toplevel_grabber_activate_app(struct tl_grabber* gr,
71 | const char* app_id, struct wl_seat* wl_seat, int parent);
72 |
73 | /* Stop listening to toplevel events and free all resources associated
74 | * with this instance.
75 | */
76 | void toplevel_grabber_free(struct tl_grabber* gr);
77 |
78 | #ifdef __cplusplus
79 | }
80 | #endif
81 |
82 | #endif
83 |
84 |
--------------------------------------------------------------------------------
/src/input_events.hpp:
--------------------------------------------------------------------------------
1 | /*
2 | * input_events.hpp -- interface to generate input events in Wayfire
3 | *
4 | * Copyright 2020-2024 Daniel Kondor
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | *
18 | */
19 |
20 |
21 | #ifndef INPUT_EVENTS_HPP
22 | #define INPUT_EVENTS_HPP
23 |
24 | extern "C" {
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | }
31 |
32 | #define WSTROKE_AXIS_HORIZONTAL wl_pointer_axis::WL_POINTER_AXIS_HORIZONTAL_SCROLL
33 | #define WSTROKE_AXIS_VERTICAL wl_pointer_axis::WL_POINTER_AXIS_VERTICAL_SCROLL
34 | #define WSTROKE_BUTTON_STATE wl_pointer_button_state
35 | #define WSTROKE_AXIS_ORIENTATION wl_pointer_axis
36 |
37 | class input_headless {
38 | public:
39 | /* init internals, create headless wlroots backend with fake
40 | * pointer and add it to the backends managed by Wayfire */
41 | void init();
42 | /* remove the headless backend created by this class and
43 | * delete it */
44 | void fini();
45 | /* emit a mouse button event */
46 | void pointer_button(uint32_t time_msec, uint32_t button, enum WSTROKE_BUTTON_STATE state);
47 | /* emit a pointer scroll event */
48 | void pointer_scroll(uint32_t time_msec, double delta, enum WSTROKE_AXIS_ORIENTATION o);
49 | /* emit a sequence of swipe events */
50 | void pointer_start_swipe(uint32_t time_msec, uint32_t fingers);
51 | void pointer_update_swipe(uint32_t time_msec, uint32_t fingers, double dx, double dy);
52 | void pointer_end_swipe(uint32_t time_msec, bool cancelled);
53 | /* emit a sequence of pinch events */
54 | void pointer_start_pinch(uint32_t time_msec, uint32_t fingers);
55 | void pointer_update_pinch(uint32_t time_msec, uint32_t fingers, double dx, double dy, double scale, double rotation);
56 | void pointer_end_pinch(uint32_t time_msec, bool cancelled);
57 | /* emit a keyboard event */
58 | void keyboard_key(uint32_t time_msec, uint32_t key, enum wl_keyboard_key_state state);
59 | /* modify the modifier state of the keyboard */
60 | void keyboard_mods(uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked);
61 | /* return if a pointer event was generated by us */
62 | bool is_own_event_btn(const wlr_pointer_button_event* ev) const { return ev && (ev->pointer == input_pointer); }
63 |
64 | ~input_headless() { fini(); }
65 |
66 | protected:
67 | void start_backend();
68 | struct wlr_backend* headless_backend = nullptr;
69 | struct wlr_pointer* input_pointer = nullptr;
70 | struct wlr_keyboard* input_keyboard = nullptr;
71 | };
72 |
73 | #endif
74 |
75 |
--------------------------------------------------------------------------------
/src/stroke_drawing_area.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * stroke_drawing_area.cpp -- Gtk.DrawingArea adapted to record new strokes
3 | *
4 | * Copyright 2020-2023 Daniel Kondor
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | *
18 | */
19 |
20 |
21 | #include "stroke_drawing_area.h"
22 | #include
23 |
24 | SRArea::SRArea() {
25 | // signal_configure_event().connect(sigc::mem_fun(*this, &SRArea::configure_event));
26 | add_events(Gdk::EventMask::BUTTON_PRESS_MASK | Gdk::EventMask::BUTTON_RELEASE_MASK | Gdk::EventMask::BUTTON_MOTION_MASK);
27 | }
28 |
29 | bool SRArea::on_configure_event(GdkEventConfigure* event) {
30 | auto win = get_window();
31 | surface = win->create_similar_surface(Cairo::Content::CONTENT_COLOR, event->width, event->height);
32 | clear();
33 | return true;
34 | }
35 |
36 | bool SRArea::on_draw(const Cairo::RefPtr& cr) {
37 | if(surface) {
38 | cr->set_source(surface, 0, 0);
39 | cr->paint();
40 | }
41 | return true;
42 | }
43 |
44 | bool SRArea::on_button_press_event(GdkEventButton* event) {
45 | if(current_button) return true;
46 | current_button = event->button;
47 | last_x = event->x;
48 | last_y = event->y;
49 | ps.clear();
50 | stroke = Stroke();
51 | ps.push_back(Stroke::Point {last_x, last_y});
52 | return true;
53 | }
54 |
55 | bool SRArea::on_button_release_event(GdkEventButton* event) {
56 | if(event->button != current_button) return true;
57 | draw_line(event->x, event->y);
58 | current_button = 0;
59 | stroke = Stroke(ps);
60 | ps.clear();
61 | stroke_recorded.emit(&stroke);
62 | return true;
63 | }
64 |
65 | bool SRArea::on_motion_notify_event(GdkEventMotion* event) {
66 | if(current_button) draw_line(event->x, event->y);
67 | return true;
68 | }
69 |
70 | void SRArea::draw_line(gdouble x, gdouble y) {
71 | if(surface && (x != last_x || y != last_y)) {
72 | auto cr = Cairo::Context::create(surface);
73 | cr->set_source_rgb(0.8, 0, 0);
74 | cr->move_to(last_x, last_y);
75 | cr->line_to(x, y);
76 | cr->stroke();
77 |
78 | int x1 = std::floor(std::min(x, last_x)) - 2;
79 | int y1 = std::floor(std::min(y, last_y)) - 2;
80 | int w = std::ceil(std::abs(x - last_x)) + 4;
81 | int h = std::ceil(std::abs(y - last_y)) + 4;
82 | queue_draw_area(x1, y1, w, h);
83 |
84 | ps.push_back(Stroke::Point {last_x, last_y});
85 |
86 | last_x = x;
87 | last_y = y;
88 | }
89 | }
90 |
91 | void SRArea::clear() {
92 | if(surface) {
93 | auto cr = Cairo::Context::create(surface);
94 | cr->set_source_rgb(1, 1, 1);
95 | cr->paint();
96 | }
97 | ps.clear();
98 | stroke = Stroke();
99 | }
100 |
101 |
--------------------------------------------------------------------------------
/input-inhibitor/wlr-input-inhibitor-unstable-v1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Copyright © 2018 Drew DeVault
5 |
6 | Permission to use, copy, modify, distribute, and sell this
7 | software and its documentation for any purpose is hereby granted
8 | without fee, provided that the above copyright notice appear in
9 | all copies and that both that copyright notice and this permission
10 | notice appear in supporting documentation, and that the name of
11 | the copyright holders not be used in advertising or publicity
12 | pertaining to distribution of the software without specific,
13 | written prior permission. The copyright holders make no
14 | representations about the suitability of this software for any
15 | purpose. It is provided "as is" without express or implied
16 | warranty.
17 |
18 | THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
19 | SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
20 | FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
21 | SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
22 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
23 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
24 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
25 | THIS SOFTWARE.
26 |
27 |
28 |
29 |
30 | Clients can use this interface to prevent input events from being sent to
31 | any surfaces but its own, which is useful for example in lock screen
32 | software. It is assumed that access to this interface will be locked down
33 | to whitelisted clients by the compositor.
34 |
35 |
36 |
37 |
38 | Activates the input inhibitor. As long as the inhibitor is active, the
39 | compositor will not send input events to other clients.
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | While this resource exists, input to clients other than the owner of the
52 | inhibitor resource will not receive input events. The client that owns
53 | this resource will receive all input events normally. The compositor will
54 | also disable all of its own input processing (such as keyboard shortcuts)
55 | while the inhibitor is active.
56 |
57 | The compositor may continue to send input events to selected clients,
58 | such as an on-screen keyboard (via the input-method protocol).
59 |
60 |
61 |
62 |
63 | Destroy the inhibitor and allow other clients to receive input.
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/convert_keycodes.cc:
--------------------------------------------------------------------------------
1 | /*
2 | * convert_keycodes.cc
3 | *
4 | * Copyright 2020 Daniel Kondor
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | *
18 | */
19 |
20 |
21 | #include "convert_keycodes.h"
22 | #include
23 | extern "C"
24 | {
25 | #include
26 | }
27 |
28 |
29 | GdkKeymap* KeyCodes::keymap = nullptr;
30 | unsigned int KeyCodes::keycode_errors = 0;
31 |
32 | void KeyCodes::init() {
33 | if(keymap) return;
34 | GdkDisplay* dpy = gdk_display_get_default();
35 | keymap = gdk_keymap_get_for_display(dpy);
36 | }
37 |
38 | static constexpr std::array, 10> modifier_match = {
39 | std::pair(GDK_SHIFT_MASK, WLR_MODIFIER_SHIFT),
40 | std::pair(GDK_LOCK_MASK, WLR_MODIFIER_CAPS),
41 | std::pair(GDK_CONTROL_MASK, WLR_MODIFIER_CTRL),
42 | std::pair(GDK_MOD1_MASK, WLR_MODIFIER_ALT),
43 | std::pair(GDK_META_MASK, WLR_MODIFIER_ALT),
44 | std::pair(GDK_MOD2_MASK, WLR_MODIFIER_MOD2),
45 | std::pair(GDK_MOD3_MASK, WLR_MODIFIER_MOD3),
46 | std::pair(GDK_MOD5_MASK, WLR_MODIFIER_MOD5),
47 | std::pair(GDK_MOD4_MASK, WLR_MODIFIER_LOGO),
48 | std::pair(GDK_SUPER_MASK, WLR_MODIFIER_LOGO)
49 | };
50 |
51 | uint32_t KeyCodes::convert_modifier(uint32_t mod) {
52 | uint32_t ret = 0;
53 | for(auto p : modifier_match) if(mod & p.first) ret |= p.second;
54 | return ret;
55 | }
56 |
57 | uint32_t KeyCodes::convert_keysym(uint32_t key) {
58 | if(!keymap) return 0;
59 | uint32_t ret = 0;
60 | GdkKeymapKey* keys = nullptr;
61 | gint n_keys = 0;
62 | if(gdk_keymap_get_entries_for_keyval(keymap, key, &keys, &n_keys) && n_keys && keys) {
63 | for(gint i = 0; i < n_keys; i++) {
64 | if(keys[i].group == 0 || keys[i].level == 0) {
65 | ret = keys[i].keycode;
66 | break;
67 | }
68 | }
69 | }
70 | if(keys) g_free(keys);
71 | if(!ret) {
72 | keycode_errors++;
73 | fprintf(stderr, "KeyCodes::convert_keysym(): could not convert %u\n", key);
74 | }
75 | return ret;
76 | }
77 |
78 | uint32_t KeyCodes::convert_keycode(uint32_t code) {
79 | if(!keymap) return 0;
80 | GdkKeymapKey key;
81 | key.keycode = code;
82 | key.level = 0;
83 | key.group = 0;
84 | return gdk_keymap_lookup_key(keymap, &key);
85 | }
86 |
87 | uint32_t KeyCodes::add_virtual_modifiers(uint32_t mod) {
88 | /* currently this only takes care of super
89 | * additional logic might be needed on e.g. Apple keyboards */
90 | if(mod & WLR_MODIFIER_LOGO) {
91 | mod ^= WLR_MODIFIER_LOGO;
92 | mod |= GDK_SUPER_MASK;
93 | }
94 | return mod;
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/wstroke.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <_short>Mouse Gestures
5 | <_long>A plugin to identify complex gestures drawn by the mouse and associate them with actions.
6 | Accessibility
7 |
8 | <_short>General
9 |
14 |
19 |
25 |
30 |
51 |
56 |
61 |
62 |
63 | <_short>Action preferences
64 |
89 |
96 |
97 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/gesture.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2009, Thomas Jaeger
3 | * Copyright (c) 2020-2023, Daniel Kondor
4 | *
5 | * Permission to use, copy, modify, and/or distribute this software for any
6 | * purpose with or without fee is hereby granted, provided that the above
7 | * copyright notice and this permission notice appear in all copies.
8 | *
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
12 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
14 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
15 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 | */
17 | #ifndef __GESTURE_H__
18 | #define __GESTURE_H__
19 |
20 | #include "stroke.h"
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | #define STROKE_SIZE 64
30 |
31 | class Stroke {
32 | friend class boost::serialization::access;
33 | public:
34 | struct Point {
35 | double x;
36 | double y;
37 | Point operator+(const Point &p) {
38 | Point sum = { x + p.x, y + p.y };
39 | return sum;
40 | }
41 | Point operator-(const Point &p) {
42 | Point sum = { x - p.x, y - p.y };
43 | return sum;
44 | }
45 | Point operator*(const double a) {
46 | Point product = { x * a, y * a };
47 | return product;
48 | }
49 | template void serialize(Archive & ar, const unsigned int version) {
50 | ar & x; ar & y;
51 | if (version == 0) {
52 | double time;
53 | ar & time;
54 | }
55 | }
56 | };
57 |
58 | using PreStroke = std::vector;
59 |
60 | private:
61 | BOOST_SERIALIZATION_SPLIT_MEMBER()
62 | template void load(Archive & ar, const unsigned int version) {
63 | if(version >= 6) {
64 | unsigned int n;
65 | ar & n;
66 | if(n) {
67 | stroke_t* s = stroke_alloc(n);
68 | for(unsigned int i = 0; i < n; i++) {
69 | double x, y;
70 | ar & x;
71 | ar & y;
72 | stroke_add_point(s, x, y);
73 | }
74 | stroke_finish(s);
75 | stroke.reset(s);
76 | }
77 | return;
78 | }
79 |
80 | std::vector ps;
81 | ar & ps;
82 | if (ps.size()) {
83 | stroke_t *s = stroke_alloc(ps.size());
84 | for (std::vector::iterator i = ps.begin(); i != ps.end(); ++i)
85 | stroke_add_point(s, i->x, i->y);
86 | stroke_finish(s);
87 | stroke.reset(s);
88 | }
89 | if (version == 0) return;
90 |
91 | int trigger;
92 | int button;
93 | unsigned int modifiers;
94 | bool timeout;
95 |
96 | ar & button;
97 | if (version >= 2) ar & trigger;
98 | if (version < 3) return;
99 | ar & timeout;
100 | if (version < 5) return;
101 | ar & modifiers;
102 |
103 | }
104 | template void save(Archive & ar, __attribute__((unused)) unsigned int version) const {
105 | unsigned int n = size();
106 | ar & n;
107 | for(unsigned int i = 0; i < n; i++) {
108 | Point p = points(i);
109 | ar & p.x;
110 | ar & p.y;
111 | }
112 | }
113 |
114 | struct stroke_deleter {
115 | void operator()(stroke_t* s) const { stroke_free(s); }
116 | };
117 |
118 | public:
119 | std::unique_ptr stroke;
120 |
121 | Stroke() : stroke(nullptr, stroke_deleter()) { }
122 | Stroke(const PreStroke &s);
123 | Stroke clone() const { Stroke s; if(stroke) s.stroke.reset(stroke_copy(stroke.get())); return s; }
124 |
125 | static Stroke trefoil();
126 | static int compare(const Stroke&, const Stroke&, double &);
127 |
128 | unsigned int size() const { return stroke ? stroke_get_size(stroke.get()) : 0; }
129 | bool trivial() const { return size() == 0 ; }
130 | Point points(int n) const { Point p; stroke_get_point(stroke.get(), n, &p.x, &p.y); return p; }
131 | double time(int n) const { return stroke_get_time(stroke.get(), n); }
132 | };
133 | BOOST_CLASS_VERSION(Stroke, 6)
134 | BOOST_CLASS_VERSION(Stroke::Point, 1)
135 |
136 | #endif
137 |
--------------------------------------------------------------------------------
/src/stroke_draw.cc:
--------------------------------------------------------------------------------
1 | /*
2 | * stroke_draw.cc
3 | *
4 | * Copyright (c) 2008-2009, Thomas Jaeger
5 | * Copyright (c) 2020-2023 Daniel Kondor
6 | *
7 | * Permission to use, copy, modify, and/or distribute this software for any
8 | * purpose with or without fee is hereby granted, provided that the above
9 | * copyright notice and this permission notice appear in all copies.
10 | *
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
16 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
17 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 | *
19 | */
20 |
21 |
22 | #include "stroke_draw.h"
23 | #include
24 |
25 | Glib::RefPtr StrokeDrawer::pbEmpty;
26 |
27 | Glib::RefPtr StrokeDrawer::drawEmpty(int size) {
28 | if (size != STROKE_SIZE)
29 | return drawEmpty_(size);
30 | if (pbEmpty)
31 | return pbEmpty;
32 | pbEmpty = drawEmpty_(size);
33 | return pbEmpty;
34 | }
35 |
36 |
37 | Glib::RefPtr StrokeDrawer::draw(const Stroke* stroke, int size, double width) {
38 | Glib::RefPtr pb = drawEmpty_(size);
39 | int w = size;
40 | int h = size;
41 | int stride = pb->get_rowstride();
42 | guint8 *row = pb->get_pixels();
43 | // This is all pretty messed up
44 | // http://www.archivum.info/gtkmm-list@gnome.org/2007-05/msg00112.html
45 | Cairo::RefPtr surface = Cairo::ImageSurface::create(row, Cairo::FORMAT_ARGB32, w, h, stride);
46 | draw(stroke, surface, 0, 0, pb->get_width(), size, width);
47 | for (int i = 0; i < w; i++) {
48 | guint8 *px = row;
49 | for (int j = 0; j < h; j++) {
50 | guint8 a = px[3];
51 | guint8 r = px[2];
52 | guint8 g = px[1];
53 | guint8 b = px[0];
54 | if (a) {
55 | px[0] = ((((guint)r) << 8) - r) / a;
56 | px[1] = ((((guint)g) << 8) - g) / a;
57 | px[2] = ((((guint)b) << 8) - b) / a;
58 | }
59 | px += 4;
60 | }
61 | row += stride;
62 | }
63 | return pb;
64 | }
65 |
66 | Glib::RefPtr StrokeDrawer::drawEmpty_(int size) {
67 | Glib::RefPtr pb = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB,true,8,size,size);
68 | pb->fill(0x00000000);
69 | return pb;
70 | }
71 |
72 |
73 | void StrokeDrawer::draw(const Stroke* stroke, Cairo::RefPtr surface, int x, int y, int w, int h, double width) {
74 | const Cairo::RefPtr ctx = Cairo::Context::create (surface);
75 | x += width; y += width; w -= 2*width; h -= 2*width;
76 | ctx->save();
77 | ctx->translate(x,y);
78 | ctx->scale(w,h);
79 | ctx->set_line_width(2.0*width/(w+h));
80 | if (stroke->size()) {
81 | ctx->set_line_cap(Cairo::LINE_CAP_ROUND);
82 | int n = stroke->size();
83 | float lambda = sqrt(3)-2.0;
84 | float sum = lambda / (1 - lambda);
85 | std::vector y(n);
86 | y[0] = stroke->points(0) * sum;
87 | for (int j = 0; j < n-1; j++)
88 | y[j+1] = (y[j] + stroke->points(j)) * lambda;
89 | std::vector z(n);
90 | z[n-1] = stroke->points(n-1) * (-sum);
91 | for (int j = n-1; j > 0; j--)
92 | z[j-1] = (z[j] - stroke->points(j)) * lambda;
93 | for (int j = 0; j < n-1; j++) {
94 | // j -> j+1
95 | ctx->set_source_rgba(0.0, stroke->time(j), 1.0-stroke->time(j), 1.0);
96 | Stroke::Point p[4];
97 | p[0] = stroke->points(j);
98 | p[3] = stroke->points(j+1);
99 | p[1] = p[0] + y[j] + z[j];
100 | p[2] = p[3] - y[j+1] - z[j+1];
101 | ctx->move_to(p[0].x, p[0].y);
102 | ctx->curve_to(p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y);
103 | ctx->stroke();
104 | }
105 | } else {
106 | ctx->set_source_rgba(0.0, 0.0, 1.0, 1.0);
107 | ctx->move_to(0.33, 0.33);
108 | ctx->line_to(0.67, 0.67);
109 | ctx->move_to(0.33, 0.67);
110 | ctx->line_to(0.67, 0.33);
111 | ctx->stroke();
112 | }
113 | ctx->restore();
114 | }
115 |
116 | void StrokeDrawer::draw_svg(const Stroke* stroke, std::string filename) {
117 | const int S = 32;
118 | const int B = 1;
119 | Cairo::RefPtr s = Cairo::SvgSurface::create(filename, S, S);
120 | draw(stroke, s, B, B, S-2*B, S-2*B);
121 | }
122 |
123 |
124 |
--------------------------------------------------------------------------------
/src/cellrenderertextish.vala:
--------------------------------------------------------------------------------
1 | /* compile with valac -c cellrenderertextish.vala --pkg gtk+-3.0 --vapidir . --pkg input_inhibitor -C -H cellrenderertextish.h */
2 |
3 | public class CellRendererTextish : Gtk.CellRendererText {
4 | public enum Mode { Text, Key, Popup, Combo }
5 | public new Mode mode;
6 | public unowned string[] items;
7 |
8 | public Gdk.Pixbuf? icon { get; set; default = null; }
9 |
10 | public signal void key_edited(string path, Gdk.ModifierType mods, uint code);
11 | public signal void combo_edited(string path, uint row);
12 |
13 | private Gtk.CellEditable? cell;
14 |
15 | public CellRendererTextish() {
16 | mode = Mode.Text;
17 | cell = null;
18 | items = null;
19 | }
20 |
21 | public CellRendererTextish.with_items(string[] items) {
22 | mode = Mode.Text;
23 | cell = null;
24 | this.items = items;
25 | }
26 |
27 | public void set_items(string[] items_) {
28 | items = items_;
29 | }
30 |
31 | public override unowned Gtk.CellEditable? start_editing(Gdk.Event? event, Gtk.Widget widget, string path, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gtk.CellRendererState flags) {
32 | cell = null;
33 | if (!editable)
34 | return cell;
35 | switch (mode) {
36 | case Mode.Text:
37 | cell = base.start_editing(event, widget, path, background_area, cell_area, flags);
38 | break;
39 | case Mode.Key:
40 | cell = new CellEditableAccel(this, path, widget);
41 | break;
42 | case Mode.Combo:
43 | cell = new CellEditableCombo(this, path, widget, items);
44 | break;
45 | case Mode.Popup:
46 | cell = new CellEditableDummy();
47 | break;
48 | }
49 | return cell;
50 | }
51 |
52 | public override void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gtk.CellRendererState flags) {
53 | Gdk.cairo_rectangle(ctx, cell_area);
54 | if(icon != null) {
55 | Gdk.cairo_set_source_pixbuf(ctx, icon, cell_area.x, cell_area.y + cell_area.height / 2 - icon.height / 2);
56 | ctx.fill();
57 | cell_area.x += icon.width + 4;
58 | }
59 | base.render(ctx, widget, background_area, cell_area, flags);
60 | }
61 |
62 | public override void get_size(Gtk.Widget widget, Gdk.Rectangle? cell_area, out int x_offset, out int y_offset, out int width, out int height) {
63 | base.get_size(widget, cell_area, out x_offset, out y_offset, out width, out height);
64 | if(icon != null) {
65 | width += icon.width;
66 | height = int.max(height, icon.height);
67 | }
68 | }
69 |
70 | public override void get_preferred_height(Gtk.Widget widget, out int minimum_size, out int natural_size) {
71 | base.get_preferred_height(widget, out minimum_size, out natural_size);
72 | if(icon != null) {
73 | minimum_size = int.max(minimum_size, icon.height);
74 | natural_size = int.max(natural_size, icon.height);
75 | }
76 | }
77 | public override void get_preferred_height_for_width(Gtk.Widget widget, int width, out int minimum_height, out int natural_height) {
78 | base.get_preferred_height_for_width(widget, width, out minimum_height, out natural_height);
79 | if(icon != null) {
80 | minimum_height = int.max(minimum_height, icon.height);
81 | natural_height = int.max(natural_height, icon.height);
82 | }
83 | }
84 | public override void get_preferred_width(Gtk.Widget widget, out int minimum_size, out int natural_size) {
85 | base.get_preferred_width(widget, out minimum_size, out natural_size);
86 | if(icon != null) {
87 | minimum_size += icon.width;
88 | natural_size += icon.width;
89 | }
90 | }
91 | public override void get_preferred_width_for_height(Gtk.Widget widget, int height, out int minimum_width, out int natural_width) {
92 | base.get_preferred_width_for_height(widget, height, out minimum_width, out natural_width);
93 | if(icon != null) {
94 | minimum_width += icon.width;
95 | natural_width += icon.width;
96 | }
97 | }
98 | }
99 |
100 | class CellEditableDummy : Gtk.EventBox, Gtk.CellEditable {
101 | public bool editing_canceled { get; set; }
102 | protected virtual void start_editing(Gdk.Event? event) {
103 | editing_done();
104 | remove_widget();
105 | }
106 | }
107 |
108 | class CellEditableAccel : Gtk.EventBox, Gtk.CellEditable {
109 | public bool editing_canceled { get; set; }
110 | new CellRendererTextish parent;
111 | new string path;
112 |
113 | public CellEditableAccel(CellRendererTextish parent, string path, Gtk.Widget widget) {
114 | this.parent = parent;
115 | this.path = path;
116 | editing_done.connect(on_editing_done);
117 | Gtk.Label label = new Gtk.Label("Key combination...");
118 | label.set_alignment(0.0f, 0.5f);
119 | add(label);
120 | override_background_color(Gtk.StateFlags.NORMAL, widget.get_style_context().get_background_color(Gtk.StateFlags.SELECTED));
121 | label.override_color(Gtk.StateFlags.NORMAL, widget.get_style_context().get_color(Gtk.StateFlags.SELECTED));
122 | show_all();
123 | }
124 |
125 | protected virtual void start_editing(Gdk.Event? event) {
126 | Gtk.grab_add(this);
127 | Gdk.keyboard_grab(get_window(), false, event != null ? event.get_time() : Gdk.CURRENT_TIME);
128 |
129 | Inhibitor.grab();
130 |
131 | /*
132 | Gdk.DeviceManager dm = get_window().get_display().get_device_manager();
133 | foreach (Gdk.Device dev in dm.list_devices(Gdk.DeviceType.SLAVE))
134 | Gtk.device_grab_add(this, dev, true);
135 | */
136 | key_press_event.connect(on_key);
137 | }
138 |
139 | bool on_key(Gdk.EventKey event) {
140 | if (event.is_modifier != 0)
141 | return true;
142 | switch (event.keyval) {
143 | case Gdk.Key.Super_L:
144 | case Gdk.Key.Super_R:
145 | case Gdk.Key.Hyper_L:
146 | case Gdk.Key.Hyper_R:
147 | return true;
148 | }
149 | Gdk.ModifierType mods = event.state; /* & Gtk.accelerator_get_default_mod_mask(); -- does not work! */
150 |
151 | editing_done();
152 | remove_widget();
153 |
154 | parent.key_edited(path, mods, event.hardware_keycode);
155 | return true;
156 | }
157 | void on_editing_done() {
158 | Gtk.grab_remove(this);
159 | Gdk.keyboard_ungrab(Gdk.CURRENT_TIME);
160 | Inhibitor.ungrab();
161 |
162 | /*
163 | Gdk.DeviceManager dm = get_window().get_display().get_device_manager();
164 | foreach (Gdk.Device dev in dm.list_devices(Gdk.DeviceType.SLAVE))
165 | Gtk.device_grab_remove(this, dev);
166 | */
167 | }
168 | }
169 |
170 |
171 | class CellEditableCombo : Gtk.ComboBoxText, Gtk.CellEditable {
172 | new CellRendererTextish parent;
173 | new string path;
174 |
175 | public CellEditableCombo(CellRendererTextish parent, string path, Gtk.Widget widget, string[] items) {
176 | this.parent = parent;
177 | this.path = path;
178 | foreach (string item in items) {
179 | append_text(item);
180 | }
181 | changed.connect(() => parent.combo_edited(path, active));
182 | }
183 |
184 | public virtual void start_editing(Gdk.Event? event) {
185 | base.start_editing(event);
186 | show_all();
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wstroke
2 |
3 | Port of [Easystroke mouse gestures](https://github.com/thjaeger/easystroke) as a plugin for [Wayfire](https://github.com/WayfireWM/wayfire). Mouse gestures are shapes drawn on the screen while holding down one of the buttons (typically the right or middle button). This plugin allows associating such gestures with various actions. See the [Wiki](https://github.com/dkondor/wstroke/wiki) for more explanations and examples.
4 |
5 | Packages are available for:
6 | - Ubuntu 24.04: https://launchpad.net/~kondor-dani/+archive/ubuntu/ppa-wstroke
7 | - Debian (testing and unstable): in the official [repository](https://packages.debian.org/testing/wstroke).
8 |
9 | ### Dependencies
10 |
11 | - [Wayfire](https://github.com/WayfireWM/wayfire), version [0.10.0](https://github.com/WayfireWM/wayfire/tree/v0.10.0) or newer (see the [wiki](https://github.com/dkondor/wstroke/wiki/Compilation-with-older-Wayfire-versions) if using older Wayfire versions).
12 | - [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) version [0.19](https://gitlab.freedesktop.org/wlroots/wlroots/-/tree/0.19?ref_type=heads).
13 | - Development libraries for GTK, GDK, glib, cairo, pixman, gtkmm, gdkmm and boost-serialization (Ubuntu packages: `libglib2.0-dev, libgtk-3-dev, libcairo2-dev, libpixman-1-dev, libgtkmm-3.0-dev, libboost-serialization-dev`)
14 | - `glib-compile-resources` (Ubuntu package: `libglib2.0-dev-bin`)
15 | - [Vala](https://vala.dev/) compiler (for building, Ubuntu package: `valac`; or use the [no_vala](https://github.com/dkondor/wstroke/tree/no_vala) branch instead)
16 | - Optional, but highly recommended: [WCM](https://github.com/WayfireWM/wcm) for basic configuration
17 | - Optionally [libinput](https://www.freedesktop.org/wiki/Software/libinput/) version [1.17](https://lists.freedesktop.org/archives/wayland-devel/2021-February/041733.html) or higher for improved touchpad support (to allow tap-and-drag for the right and middle buttons, required for drawing gestures without physical buttons)
18 |
19 | ### Building and installing
20 |
21 | ```
22 | meson build
23 | ninja -C build
24 | sudo ninja -C build install
25 | ```
26 |
27 | If you get build errors, your Wayfire version might be too old (or too new). See the [wiki](https://github.com/dkondor/wstroke/wiki/Compilation-with-older-Wayfire-versions) for information about building wstroke with older Wayfire versions.
28 |
29 |
30 | ### Running
31 |
32 | If correctly installed, it will show up as "Mouse Gestures" plugin in WCM and can be enabled from there, or with adding `wstroke` to the list of plugins (in `[core]`) in `~/.config/wayfire.ini`.
33 |
34 | ### Configuration
35 |
36 | Basic options such as the button used for gestures, or the target of gestures can be changed with WCM as the "Mouse Gestures" plugin, or by manually editing the `[wstroke]` section in `~/.config/wayfire.ini`. Note: trying to set a button will likely make WCM to show a warning (e.g. "Attempting to bind `BTN_RIGHT` without modifier"); it is safe to ignore this, since wstroke will forward button clicks when needed.
37 |
38 | Gestures can be configured by the standalone program `wstroke-config`. Recommended ways to obtain an initial configuration:
39 | - If you have Easystroke installed, `wstroke-config` will attempt to import gestures from it, by looking for any of the `actions*` files under `~/.easystroke`. Even without Easystroke installed, copying the content of this directory from a previous installation can be used to import gestures. It is recommended to check that importing is done correctly.
40 | - An example configuration file is under [example/actions-wstroke-2](example/actions-wstroke-2). This is installed automatically and will be used by `wstroke-config` as default if no other configuration exists. You can copy this file to `~/.config/wstroke` manually as well.
41 |
42 | Gestures are stored under `wstroke/actions-wstroke-2` in the directory given by the `XDG_CONFIG_HOME` environment variable (`~/.config` by default). It is recommended not to edit this file manually, but it can be copied between different computers, or backed up and restored manually.
43 |
44 | #### Focus settings ####
45 | For a better experience, it is recommended to disable the "click-to-focus" feature in Wayfire for the mouse button used for gestures. This will allow wstroke to manage focus when using this button and set the target of the gesture as requested by the user.
46 |
47 | To do this, under the "Core" tab of WCM, change the option "Mouse button to focus views" to *not* include the button used for gestures. E.g. if the right button is used, the setting here should not contain `BTN_RIGHT`, so it might look like `BTN_LEFT | BTN_MIDDLE` (note: it is best to set this by clicking on the edit button on the right of the setting and manually editing the text that corresponds to this setting). Don't be alarmed by the warning that appears ("Attempting to bind `BTN_LEFT | BTN_MIDDLE` without modifier"); this is exactly the intended behavior in this case.
48 |
49 | The same can be achieved by editing the option `focus_buttons` in the `[core]` section of `~/.config/wayfire.ini`.
50 |
51 | ### What works
52 |
53 | - Importing saved strokes from "actions" files created with Easystroke (just run `wstroke-config`).
54 | - Drawing and recognizing strokes.
55 | - Drawing strokes with all supported renderer backends (EGL, Vulkan and pixman).
56 | - Actions on the active view: close, minimize, (un)maximize, move, resize (select "WM Action" and the appropriate action).
57 | - Actions to activate another Wayfire plugin (typical desktop interactions are under "Global Action"; "Custom Plugin" can be used with giving the plugin activator name directly), only supported for some plugins, see [here](https://github.com/WayfireWM/wayfire/issues/1811).
58 | - Generating keypresses ("Key" action).
59 | - Generating mouse clicks ("Button" action).
60 | - Generating modifiers ("Ignore" action -- only works in combination with mouse clicks, not the keyboard).
61 | - Emulating touchpad "gestures" with mouse movement, such as scrolling or pinch zoom in apps that support it ("Touchpad Gesture" action; "Scroll" action from Easystroke will be converted to this).
62 | - Running commands as a gesture action.
63 | - Getting keybindings and mouse button bindings in the configuration for actions.
64 | - Recording strokes (slight change: these have to be recorded on a "canvas", cannot be drawn anywhere like with Easystroke; also, recording strokes requires using a different mouse button).
65 | - Identifying views and using application specific gestures or excluding certain apps completely; setting these in `wstroke-config` by interactively grabbing the app-id of an open view.
66 | - Option to target either the view under the mouse when starting the gesture (original Easystroke behavior) or the currently active one.
67 | - Option to change focus to the view under the mouse after a gesture.
68 | - Basic timeouts (move the mouse after clicking to have a gesture / end the gesture if not moving within a timeout).
69 |
70 | ### What does not work
71 |
72 | - SendText action (removed from settings, will be converted to Global)
73 | - Ignore action in combination with keyboard keypresses.
74 | - Individual settings (which button, timeout) for each pointing device
75 | - Advanced gestures
76 | - Touchscreen and pen / stylus support
77 | - Drawing strokes with the Vulkan renderer (`WLR_RENDERER=vulkan`) is inefficient (suggestions for improvement are welcome).
78 |
79 |
--------------------------------------------------------------------------------
/src/appchooser.cc:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2023, Daniel Kondor
3 | *
4 | * Permission to use, copy, modify, and/or distribute this software for any
5 | * purpose with or without fee is hereby granted, provided that the above
6 | * copyright notice and this permission notice appear in all copies.
7 | *
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
11 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
13 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 | */
16 |
17 |
18 | #include "appchooser.h"
19 | #include
20 |
21 | AppChooser::AppBox::AppBox(Glib::RefPtr& app_) : Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL), app(app_) {
22 | auto image = new Gtk::Image(app->get_icon(), Gtk::IconSize(Gtk::ICON_SIZE_DIALOG));
23 | image->set_pixel_size(48);
24 | this->add(*image);
25 | const std::string& name = app->get_name();
26 | auto label = new Gtk::Label(name.length() > 23 ? name.substr(0, 20) + "..." : name);
27 | this->add(*label);
28 | name_lower = Glib::ustring(app->get_name()).lowercase();
29 | }
30 |
31 | void AppChooser::update_apps() {
32 | bool tmp;
33 | mutex.lock();
34 | tmp = thread_running;
35 | if(tmp) more_work = true;
36 | mutex.unlock();
37 |
38 | if(!tmp) {
39 | if(thread.joinable()) thread.join();
40 | thread_running = true;
41 | more_work = false;
42 | thread = std::thread(&AppChooser::thread_func, this);
43 | }
44 | update_pending = false;
45 | }
46 |
47 | void AppChooser::thread_func() {
48 | while(true) {
49 | if(exit_request.load()) break;
50 |
51 | AppContent* tmp = new AppContent();
52 | tmp->apps = Gio::AppInfo::get_all();
53 | if(exit_request.load()) {
54 | delete tmp;
55 | break;
56 | }
57 |
58 | auto it = std::remove_if(tmp->apps.begin(), tmp->apps.end(), [](const Glib::RefPtr& p) { return !p->should_show(); });
59 | tmp->apps.erase(it, tmp->apps.end());
60 |
61 | tmp->flowbox.reset(new Gtk::FlowBox());
62 |
63 | for(auto& a : tmp->apps) {
64 | if(exit_request.load()) break;
65 | auto box = new AppBox(a);
66 | tmp->flowbox->add(*box);
67 | }
68 |
69 | std::lock_guard lock(mutex);
70 | tmp = apps_pending.exchange(tmp);
71 | if(tmp) delete tmp;
72 | if(!more_work) {
73 | thread_running = false;
74 | break;
75 | }
76 | more_work = false;
77 | }
78 | }
79 |
80 |
81 | void on_apps_changed(GAppInfoMonitor*, void* p) {
82 | AppChooser* chooser = (AppChooser*)p;
83 | if(chooser->update_pending) return;
84 | chooser->update_pending = true;
85 |
86 | Glib::signal_timeout().connect_seconds_once([chooser](){ chooser->update_apps(); }, 4);
87 | }
88 |
89 | void AppChooser::startup() {
90 | update_apps();
91 | monitor = g_app_info_monitor_get();
92 | g_signal_connect(monitor, "changed", G_CALLBACK(on_apps_changed), this);
93 |
94 | /* basic setup for the GUI */
95 | widgets->get_widget("dialog_appchooser", dialog);
96 | widgets->get_widget("header_appchooser", header);
97 | widgets->get_widget("entry_appchooser", entry);
98 | widgets->get_widget("checkbutton_appchooser", cb);
99 | widgets->get_widget("scrolledwindow_appchooser", sw);
100 | widgets->get_widget("appchooser_ok", select_ok);
101 | widgets->get_widget("searchentry_appchooser", searchentry);
102 |
103 | cb->signal_toggled().connect([this]() {
104 | entry->set_sensitive(cb->get_active());
105 | });
106 |
107 | searchentry->signal_search_changed().connect([this]() {
108 | filter_lower = searchentry->get_text().lowercase();
109 | if(apps) apps->flowbox->invalidate_filter();
110 | });
111 | searchentry->signal_stop_search().connect([this]() {
112 | searchentry->set_text(Glib::ustring());
113 | });
114 | }
115 |
116 | int AppChooser::apps_sort(const Gtk::FlowBoxChild* x, const Gtk::FlowBoxChild* y) {
117 | const AppBox* a = dynamic_cast(x->get_child());
118 | const AppBox* b = dynamic_cast(y->get_child());
119 | if(!(a && b)) return 0; // or throw an exception?
120 | return a->compare(*b);
121 | }
122 |
123 | bool AppChooser::apps_filter(const Gtk::FlowBoxChild* x) const {
124 | if(filter_lower.empty()) return true;
125 | const AppBox* a = dynamic_cast(x->get_child());
126 | return a && a->filter(filter_lower);
127 | }
128 |
129 | bool AppChooser::run(const Glib::ustring& gesture_name, const Glib::ustring& custom_command) {
130 | if(!apps && !apps_pending) thread.join(); // in this case, the worker thread should be running
131 |
132 | AppContent* tmp = nullptr;
133 | tmp = apps_pending.exchange(tmp);
134 | if(tmp) {
135 | sw->remove();
136 | apps.reset(tmp);
137 | apps->flowbox->signal_child_activated().connect([this](Gtk::FlowBoxChild*) {
138 | dialog->response(Gtk::RESPONSE_OK);
139 | });
140 | apps->flowbox->set_valign(Gtk::ALIGN_START);
141 | apps->flowbox->set_homogeneous(true);
142 | apps->flowbox->set_activate_on_single_click(false);
143 | apps->flowbox->set_sort_func(&apps_sort);
144 | apps->flowbox->set_filter_func([this](const Gtk::FlowBoxChild* x) { return apps_filter(x); });
145 | sw->add(*apps->flowbox);
146 | }
147 |
148 | if(!apps) return false;
149 |
150 | if(custom_command.empty()) {
151 | cb->set_active(false);
152 | entry->set_sensitive(false);
153 | }
154 | else {
155 | cb->set_active(true);
156 | entry->set_sensitive(true);
157 | }
158 | entry->set_text(custom_command);
159 |
160 | select_ok->grab_default();
161 | Glib::ustring str = Glib::ustring::compose(_("Choose app to run for gesture %1"), gesture_name);
162 | header->set_subtitle(str);
163 |
164 | dialog->show_all();
165 |
166 | auto x = dialog->run();
167 | dialog->hide();
168 |
169 | if(x == Gtk::RESPONSE_OK) {
170 | if(cb->get_active()) {
171 | res_cmdline = entry->get_text();
172 | custom_res = true;
173 | res_app.reset();
174 | return true;
175 | }
176 | else {
177 | custom_res = false;
178 | auto tmp = apps->flowbox->get_selected_children();
179 | if(tmp.size()) {
180 | auto selected = tmp.front();
181 | const AppBox* box = dynamic_cast(selected->get_child());
182 | if(box) {
183 | res_app = box->get_app();
184 | res_cmdline = res_app->get_commandline();
185 | // remove placeholders (%f, %F, %u and %U for files, and misc; note: in theory, we should properly parse %i, %c and %k)
186 | auto i = res_cmdline.begin();
187 | for(auto j = res_cmdline.begin(); j != res_cmdline.end(); ++j) {
188 | if(*j == '%') {
189 | ++j;
190 | if(j == res_cmdline.end()) break;
191 | if(*j != '%') continue;
192 | }
193 | if(j != i) *i = *j;
194 | ++i;
195 | }
196 | res_cmdline.erase(i, res_cmdline.end());
197 | return true;
198 | }
199 | }
200 | }
201 | }
202 | res_app.reset();
203 | res_cmdline = "";
204 | custom_res = false;
205 | return false;
206 | }
207 |
208 |
209 |
210 | AppChooser::~AppChooser() {
211 | if(monitor) g_object_unref(monitor);
212 | exit_request.store(true);
213 | if(thread.joinable()) thread.join();
214 | AppContent* tmp = apps_pending;
215 | if(tmp) delete tmp;
216 | sw->remove();
217 | }
218 |
219 |
220 |
--------------------------------------------------------------------------------
/src/main.cc:
--------------------------------------------------------------------------------
1 | /*
2 | * main.cc
3 | *
4 | * Copyright 2020-2023 Daniel Kondor
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | *
18 | */
19 |
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include "actions.h"
30 | #include "actiondb.h"
31 | #include "ecres.h"
32 | #include "convert_keycodes.h"
33 | #include "input_inhibitor.h"
34 | #include "config.h"
35 |
36 | static void error_dialog(const Glib::ustring &text) {
37 | Gtk::MessageDialog dialog(text, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
38 | dialog.show();
39 | dialog.run();
40 | }
41 |
42 | /* Display a dialog with an error text if the configuration cannot be read. */
43 | bool config_error_dialog(const Glib::ustring& fn, const Glib::ustring& err, Gtk::Builder* widgets) {
44 | std::unique_ptr dialog;
45 | {
46 | Gtk::Dialog* tmp;
47 | widgets->get_widget("dialog_config_error", tmp);
48 | dialog.reset(tmp);
49 | }
50 |
51 | Glib::ustring msg = Glib::ustring::compose(_("The gesture configuration file \"%1\" exists but cannot be read. The following error was encountered:"), fn);
52 | Gtk::Label* label;
53 | Gtk::TextView* tv;
54 | widgets->get_widget("label_config_error", label);
55 | widgets->get_widget("textview_config_error", tv);
56 | tv->get_buffer()->set_text(err);
57 | label->set_text(msg);
58 | dialog->show();
59 | return (dialog->run() == 1); // note: response == 1 means that user clicked on the "Overwrite" button
60 | }
61 |
62 |
63 | void startup(Gtk::Application* app, Actions** p_actions)
64 | {
65 | char* xdg_config = getenv("XDG_CONFIG_HOME");
66 | std::string home_dir = getenv("HOME");
67 | std::string old_config_dir = home_dir + "/.easystroke/";
68 | std::string config_dir = xdg_config ? std::string(xdg_config) + "/wstroke/" :
69 | home_dir + "/.config/wstroke/";
70 |
71 | // ensure that config dir exists
72 | std::error_code ec;
73 | if(std::filesystem::exists(config_dir, ec)) {
74 | if(!std::filesystem::is_directory(config_dir, ec)) {
75 | error_dialog(Glib::ustring::compose(_( "Path for config files (%1) is not a directory! "
76 | "Cannot store configuration. "
77 | "You can change the configuration directory "
78 | "using the XDG_CONFIG_HOME environment variable."
79 | ), config_dir));
80 | return; // note: not creating a main window will result in automatically exiting
81 | }
82 | }
83 | else {
84 | if(!std::filesystem::create_directories(config_dir, ec)) {
85 | error_dialog(Glib::ustring::compose(_( "Cannot create configuration directory \"%1\"! "
86 | "Cannot store the configuration. "
87 | "You can change the configuration directory "
88 | "using the XDG_CONFIG_HOME environment variable."
89 | ), config_dir));
90 | return; // note: not creating a main window will result in automatically exiting
91 | }
92 | }
93 |
94 | Glib::RefPtr widgets = Gtk::Builder::create_from_resource("/easystroke/gui.glade");
95 | auto actions = new Actions(config_dir, widgets);
96 | *p_actions = actions;
97 | ActionDB& actions_db = actions->actions;
98 | KeyCodes::init();
99 | bool config_read;
100 | std::string config_err_msg;
101 | std::string easystroke_convert_msg;
102 | std::string keycode_err_msg;
103 |
104 | for(const char* const * x = ActionDB::wstroke_actions_versions; *x; ++x) {
105 | std::string fn = config_dir + *x;
106 | try {
107 | config_read = actions_db.read(fn);
108 | }
109 | catch(std::exception& e) {
110 | fprintf(stderr, "%s\n", e.what());
111 | config_read = false;
112 | actions_db.clear();
113 |
114 | if(x == ActionDB::wstroke_actions_versions) {
115 | /* In this case, the error is with reading the current config
116 | * (which would be overwritten by us). Signal an error to the user */
117 | if(!config_error_dialog(fn, e.what(), widgets.get())) return;
118 |
119 | /* move the configuration file -- try to assign a new filename
120 | * in a naive way (we assume that there is no gain from TOCTOU
121 | * attacks here :) */
122 | std::string new_fn = fn;
123 | new_fn += ".bak";
124 | if(std::filesystem::exists(new_fn, ec)) {
125 | std::default_random_engine rng(time(0));
126 | std::uniform_int_distribution dd(1, 999999);
127 | new_fn += "-";
128 | while(true) {
129 | std::string tmp;
130 | tmp = new_fn + std::to_string(dd(rng));
131 | if(!std::filesystem::exists(tmp, ec)) {
132 | new_fn = tmp;
133 | break;
134 | }
135 | }
136 | }
137 | rename(fn.c_str(), new_fn.c_str());
138 | fprintf(stderr, "Moved unreadable config file to new location: %s\n", new_fn.c_str());
139 | config_err_msg = "Created a backup of the previous, unreadable config file here:\n" + new_fn;
140 | }
141 | }
142 | if(config_read) break;
143 | }
144 | if(!config_read) {
145 | if(std::filesystem::exists(old_config_dir, ec) && std::filesystem::is_directory(old_config_dir, ec)) {
146 | KeyCodes::keycode_errors = 0;
147 | for(const char* const * x = ActionDB::easystroke_actions_versions; *x; ++x) {
148 | std::string fn = old_config_dir + *x;
149 | try {
150 | config_read = actions_db.read(fn);
151 | }
152 | catch(std::exception& e) {
153 | fprintf(stderr, "%s\n", e.what());
154 | config_read = false;
155 | actions_db.clear();
156 | }
157 | if(config_read) {
158 | easystroke_convert_msg = "Imported gestures from Easystroke's configuration:\n" + fn;
159 | easystroke_convert_msg += "\nPlease check that all actions were interpreted correctly.";
160 | break;
161 | }
162 | }
163 | }
164 | if(!config_read) {
165 | try {
166 | config_read = actions_db.read(std::string(DATA_DIR) + "/" + ActionDB::wstroke_actions_versions[0]);
167 | }
168 | catch(std::exception& e) {
169 | fprintf(stderr, "%s\n", e.what());
170 | }
171 | }
172 | }
173 | if(KeyCodes::keycode_errors) keycode_err_msg = _("Could not convert some keycodes. "
174 | "Some Key actions have missing values");
175 |
176 | Gtk::Dialog* d = nullptr;
177 | if(!(keycode_err_msg.empty() && easystroke_convert_msg.empty() && config_err_msg.empty())) {
178 | std::string text;
179 | if(!config_err_msg.empty()) text += (config_err_msg + "\n\n");
180 | if(!easystroke_convert_msg.empty()) text += (easystroke_convert_msg + "\n\n");
181 | if(!keycode_err_msg.empty()) text += (keycode_err_msg + "\n\n");
182 | d = new Gtk::MessageDialog(text, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
183 | }
184 |
185 | if(!input_inhibitor_init())
186 | fprintf(stderr, _("Could not initialize keyboard grabber interface. Assigning key combinations might not work.\n"));
187 |
188 | actions->startup(app, d);
189 | }
190 |
191 | int main(int argc, char **argv) {
192 | Actions* actions = nullptr;
193 | auto app = Gtk::Application::create(argc, argv, "org.wstroke.config");
194 | app->signal_startup().connect([&app, &actions]() { startup(app.get(), &actions); });
195 | app->signal_activate().connect([&actions]() { if(actions) actions->get_main_win()->present(); });
196 | int ret = app->run();
197 | if(actions) {
198 | actions->exit();
199 | delete actions;
200 | }
201 | return ret;
202 | }
203 |
204 |
--------------------------------------------------------------------------------
/src/stroke.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009, Thomas Jaeger
3 | * Copyright (c) 2023, Daniel Kondor
4 | *
5 | * Permission to use, copy, modify, and/or distribute this software for any
6 | * purpose with or without fee is hereby granted, provided that the above
7 | * copyright notice and this permission notice appear in all copies.
8 | *
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
12 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
14 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
15 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 | */
17 |
18 | #define _GNU_SOURCE
19 |
20 | #include "stroke.h"
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | const double stroke_infinity = 0.2;
28 | #define EPS 0.000001
29 |
30 | struct point {
31 | double x;
32 | double y;
33 | double t;
34 | double dt;
35 | double alpha;
36 | };
37 |
38 | struct _stroke_t {
39 | int n;
40 | int capacity;
41 | struct point *p;
42 | };
43 |
44 | stroke_t *stroke_alloc(int n) {
45 | assert(n > 0);
46 | stroke_t *s = malloc(sizeof(stroke_t));
47 | s->n = 0;
48 | s->capacity = n;
49 | s->p = calloc(n, sizeof(struct point));
50 | return s;
51 | }
52 |
53 | void stroke_add_point(stroke_t *s, double x, double y) {
54 | assert(s->capacity > s->n);
55 | s->p[s->n].x = x;
56 | s->p[s->n].y = y;
57 | s->n++;
58 | }
59 |
60 | static inline double angle_difference(double alpha, double beta) {
61 | double d = alpha - beta;
62 | if (d < -1.0)
63 | d += 2.0;
64 | else if (d > 1.0)
65 | d -= 2.0;
66 | return d;
67 | }
68 |
69 | void stroke_finish(stroke_t *s) {
70 | assert(s->capacity > 0);
71 | s->capacity = -1;
72 |
73 | int n = s->n - 1;
74 | double total = 0.0;
75 | s->p[0].t = 0.0;
76 | for (int i = 0; i < n; i++) {
77 | total += hypot(s->p[i+1].x - s->p[i].x, s->p[i+1].y - s->p[i].y);
78 | s->p[i+1].t = total;
79 | }
80 | for (int i = 0; i <= n; i++)
81 | s->p[i].t /= total;
82 | double minX = s->p[0].x, minY = s->p[0].y, maxX = minX, maxY = minY;
83 | for (int i = 1; i <= n; i++) {
84 | if (s->p[i].x < minX) minX = s->p[i].x;
85 | if (s->p[i].x > maxX) maxX = s->p[i].x;
86 | if (s->p[i].y < minY) minY = s->p[i].y;
87 | if (s->p[i].y > maxY) maxY = s->p[i].y;
88 | }
89 | double scaleX = maxX - minX;
90 | double scaleY = maxY - minY;
91 | double scale = (scaleX > scaleY) ? scaleX : scaleY;
92 | if (scale < 0.001) scale = 1;
93 | for (int i = 0; i <= n; i++) {
94 | s->p[i].x = (s->p[i].x-(minX+maxX)/2)/scale + 0.5;
95 | s->p[i].y = (s->p[i].y-(minY+maxY)/2)/scale + 0.5;
96 | }
97 |
98 | for (int i = 0; i < n; i++) {
99 | s->p[i].dt = s->p[i+1].t - s->p[i].t;
100 | s->p[i].alpha = atan2(s->p[i+1].y - s->p[i].y, s->p[i+1].x - s->p[i].x)/M_PI;
101 | }
102 |
103 | }
104 |
105 | void stroke_free(stroke_t *s) {
106 | if (s)
107 | free(s->p);
108 | free(s);
109 | }
110 |
111 | stroke_t *stroke_copy(const stroke_t *stroke) {
112 | if(!stroke) return NULL;
113 | stroke_t *s = malloc(sizeof(stroke_t));
114 | if(!s) return NULL;
115 | s->p = calloc(stroke->n, sizeof(struct point));
116 | if(!(s->p)) {
117 | free(s);
118 | return NULL;
119 | }
120 | s->n = stroke->n;
121 | s->capacity = s->n;
122 | memcpy(s->p, stroke->p, s->n * sizeof(struct point));
123 | return s;
124 | }
125 |
126 |
127 | int stroke_get_size(const stroke_t *s) { return s->n; }
128 |
129 | void stroke_get_point(const stroke_t *s, int n, double *x, double *y) {
130 | assert(n < s->n);
131 | if (x)
132 | *x = s->p[n].x;
133 | if (y)
134 | *y = s->p[n].y;
135 | }
136 |
137 | double stroke_get_time(const stroke_t *s, int n) {
138 | assert(n < s->n);
139 | return s->p[n].t;
140 | }
141 |
142 | double stroke_get_angle(const stroke_t *s, int n) {
143 | assert(n+1 < s->n);
144 | return s->p[n].alpha;
145 | }
146 |
147 | inline static double sqr(double x) { return x*x; }
148 |
149 | double stroke_angle_difference(const stroke_t *a, const stroke_t *b, int i, int j) {
150 | return fabs(angle_difference(stroke_get_angle(a, i), stroke_get_angle(b, j)));
151 | }
152 |
153 | static inline void step(const stroke_t *a,
154 | const stroke_t *b,
155 | const int N,
156 | double *dist,
157 | int *prev_x,
158 | int *prev_y,
159 | const int x,
160 | const int y,
161 | const double tx,
162 | const double ty,
163 | int *k,
164 | const int x2,
165 | const int y2)
166 | {
167 | double dtx = a->p[x2].t - tx;
168 | double dty = b->p[y2].t - ty;
169 | if (dtx >= dty * 2.2 || dty >= dtx * 2.2 || dtx < EPS || dty < EPS)
170 | return;
171 | (*k)++;
172 |
173 | double d = 0.0;
174 | int i = x, j = y;
175 | double next_tx = (a->p[i+1].t - tx) / dtx;
176 | double next_ty = (b->p[j+1].t - ty) / dty;
177 | double cur_t = 0.0;
178 |
179 | for (;;) {
180 | double ad = sqr(angle_difference(a->p[i].alpha, b->p[j].alpha));
181 | double next_t = next_tx < next_ty ? next_tx : next_ty;
182 | bool done = next_t >= 1.0 - EPS;
183 | if (done)
184 | next_t = 1.0;
185 | d += (next_t - cur_t)*ad;
186 | if (done)
187 | break;
188 | cur_t = next_t;
189 | if (next_tx < next_ty)
190 | next_tx = (a->p[++i+1].t - tx) / dtx;
191 | else
192 | next_ty = (b->p[++j+1].t - ty) / dty;
193 | }
194 | double new_dist = dist[x*N+y] + d * (dtx + dty);
195 | if (new_dist != new_dist) abort();
196 |
197 | if (new_dist >= dist[x2*N+y2])
198 | return;
199 |
200 | prev_x[x2*N+y2] = x;
201 | prev_y[x2*N+y2] = y;
202 | dist[x2*N+y2] = new_dist;
203 | }
204 |
205 | /* To compare two gestures, we use dynamic programming to minimize (an
206 | * approximation) of the integral over square of the angle difference among
207 | * (roughly) all reparametrizations whose slope is always between 1/2 and 2.
208 | */
209 | double stroke_compare(const stroke_t *a, const stroke_t *b, int *path_x, int *path_y) {
210 | const int M = a->n;
211 | const int N = b->n;
212 | const int m = M - 1;
213 | const int n = N - 1;
214 |
215 | double* dist = malloc(M * N * sizeof(double));
216 | int* prev_x = malloc(M * N * sizeof(int));
217 | int* prev_y = malloc(M * N * sizeof(int));
218 | for (int i = 0; i < m; i++)
219 | for (int j = 0; j < n; j++)
220 | dist[i*N+j] = stroke_infinity;
221 | dist[M*N-1] = stroke_infinity;
222 | dist[0] = 0.0;
223 |
224 | for (int x = 0; x < m; x++) {
225 | for (int y = 0; y < n; y++) {
226 | if (dist[x*N+y] >= stroke_infinity)
227 | continue;
228 | double tx = a->p[x].t;
229 | double ty = b->p[y].t;
230 | int max_x = x;
231 | int max_y = y;
232 | int k = 0;
233 |
234 | while (k < 4) {
235 | if (a->p[max_x+1].t - tx > b->p[max_y+1].t - ty) {
236 | max_y++;
237 | if (max_y == n) {
238 | step(a, b, N, dist, prev_x, prev_y, x, y, tx, ty, &k, m, n);
239 | break;
240 | }
241 | for (int x2 = x+1; x2 <= max_x; x2++)
242 | step(a, b, N, dist, prev_x, prev_y, x, y, tx, ty, &k, x2, max_y);
243 | } else {
244 | max_x++;
245 | if (max_x == m) {
246 | step(a, b, N, dist, prev_x, prev_y, x, y, tx, ty, &k, m, n);
247 | break;
248 | }
249 | for (int y2 = y+1; y2 <= max_y; y2++)
250 | step(a, b, N, dist, prev_x, prev_y, x, y, tx, ty, &k, max_x, y2);
251 | }
252 | }
253 | }
254 | }
255 | double cost = dist[M*N-1];
256 | if (path_x && path_y) {
257 | if (cost < stroke_infinity) {
258 | int x = m;
259 | int y = n;
260 | int k = 0;
261 | while (x || y) {
262 | int old_x = x;
263 | x = prev_x[x*N+y];
264 | y = prev_y[old_x*N+y];
265 | path_x[k] = x;
266 | path_y[k] = y;
267 | k++;
268 | }
269 | } else {
270 | path_x[0] = 0;
271 | path_y[0] = 0;
272 | }
273 | }
274 |
275 | free(prev_y);
276 | free(prev_x);
277 | free(dist);
278 |
279 | return cost;
280 | }
281 |
--------------------------------------------------------------------------------
/src/actions.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2009, Thomas Jaeger
3 | * Copyright (c) 2020-2023, Daniel Kondor
4 | *
5 | * Permission to use, copy, modify, and/or distribute this software for any
6 | * purpose with or without fee is hereby granted, provided that the above
7 | * copyright notice and this permission notice appear in all copies.
8 | *
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
12 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
14 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
15 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 | */
17 | #ifndef __ACTIONS_H__
18 | #define __ACTIONS_H__
19 |
20 | #include
21 | #include
22 | #include
23 | #include "actiondb.h"
24 | #include "appchooser.h"
25 |
26 | class TreeViewMulti : public Gtk::TreeView {
27 | bool pending;
28 | Gtk::TreePath path;
29 | virtual bool on_button_press_event(GdkEventButton* event);
30 | virtual bool on_button_release_event(GdkEventButton* event);
31 | virtual void on_drag_begin(const Glib::RefPtr &context);
32 | public:
33 | TreeViewMulti();
34 | };
35 |
36 | class Actions {
37 | public:
38 | Actions(const std::string& config_dir_, Glib::RefPtr& widgets_) : chooser(widgets_), widgets(widgets_), config_dir(config_dir_) { }
39 | void startup(Gtk::Application* app, Gtk::Dialog* message_dialog = nullptr);
40 | private:
41 | void on_button_delete();
42 | void on_button_new();
43 | void on_selection_changed();
44 | void on_name_edited(const Glib::ustring& path, const Glib::ustring& new_text);
45 | void on_type_edited(const Glib::ustring& path, const Glib::ustring& new_text);
46 | void on_row_activated(Gtk::TreeRow& row);
47 | void on_cell_data_name(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter);
48 | void on_cell_data_type(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter);
49 | void save_actions();
50 | void update_actions() { actions_changed = true; }
51 | public:
52 | void on_accel_edited(const gchar *path_string, guint accel_key, GdkModifierType accel_mods);
53 | void on_combo_edited(const gchar *path_string, guint item);
54 | void on_arg_editing_started(GtkCellEditable *editable, const gchar *path);
55 | void on_text_edited(const gchar *path, const gchar *new_text);
56 | void on_cell_data_arg(GtkCellRenderer *cell, gchar *path);
57 | void on_stroke_editing(const char* path);
58 |
59 | Gtk::Window* get_main_win() { return main_win.get(); }
60 | void exit() { exiting = true; save_actions(); }
61 |
62 | ActionDB actions;
63 |
64 | private:
65 | int compare_ids(const Gtk::TreeModel::iterator &a, const Gtk::TreeModel::iterator &b);
66 | class OnStroke;
67 |
68 | void focus(stroke_id id, int col, bool edit);
69 |
70 | void on_add_app();
71 | void on_add_group();
72 | void on_apps_selection_changed();
73 | void load_app_list(const Gtk::TreeNodeChildren &ch, ActionListDiff *actions);
74 | void update_action_list();
75 | void update_row(const Gtk::TreeRow& row);
76 | void update_counts();
77 | void on_remove_app();
78 |
79 | bool select_exclude_row(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter, const std::string& name);
80 | void on_add_exclude();
81 | void on_remove_exclude();
82 |
83 | class ModelColumns : public Gtk::TreeModel::ColumnRecord {
84 | public:
85 | ModelColumns() {
86 | add(stroke); add(name); add(type); add(arg); add(cmd_save); add(id);
87 | add(name_bold); add(action_bold); add(deactivated); add(action_icon);
88 | add(cmd_path); add(custom_command);
89 | }
90 | Gtk::TreeModelColumn > stroke, action_icon;
91 | Gtk::TreeModelColumn name, type, arg, cmd_save, plugin_action_save, cmd_path;
92 | Gtk::TreeModelColumn id;
93 | Gtk::TreeModelColumn name_bold, action_bold;
94 | Gtk::TreeModelColumn deactivated, custom_command;
95 | };
96 | class Store : public Gtk::ListStore {
97 | Actions *parent;
98 | public:
99 | Store(const Gtk::TreeModelColumnRecord &columns, Actions *p) : Gtk::ListStore(columns), parent(p) {}
100 | static Glib::RefPtr create(const Gtk::TreeModelColumnRecord &columns, Actions *parent) {
101 | return Glib::RefPtr(new Store(columns, parent));
102 | }
103 | protected:
104 | bool row_draggable_vfunc(const Gtk::TreeModel::Path&) const;
105 | bool row_drop_possible_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData&) const;
106 | bool drag_data_received_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData& selection);
107 | };
108 | class AppsStore : public Gtk::TreeStore {
109 | Actions *parent;
110 | public:
111 | AppsStore(const Gtk::TreeModelColumnRecord &columns, Actions *p) : Gtk::TreeStore(columns), parent(p) {}
112 | static Glib::RefPtr create(const Gtk::TreeModelColumnRecord &columns, Actions *parent) {
113 | return Glib::RefPtr(new AppsStore(columns, parent));
114 | }
115 | protected:
116 | bool row_drop_possible_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData &selection) const;
117 | bool drag_data_received_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData& selection);
118 | };
119 | ModelColumns cols;
120 | TreeViewMulti tv;
121 | Glib::RefPtr tm;
122 |
123 | /* Special casing to store additional info about Command actions */
124 | struct CommandInfo {
125 | Glib::ustring name;
126 | Glib::RefPtr icon;
127 | };
128 | std::unordered_map command_info;
129 | void load_command_infos_r(ActionListDiff& x);
130 | void load_command_infos();
131 | /* helper for the app chooser */
132 | AppChooser chooser;
133 |
134 | Gtk::TreeView *apps_view = nullptr;
135 | Glib::RefPtr apps_model;
136 | /* helper to find a given app in apps_view / apps_model */
137 | bool get_action_item(const ActionListDiff* x, Gtk::TreeIter& it);
138 |
139 | class Single : public Gtk::TreeModel::ColumnRecord {
140 | public:
141 | Single() { add(type); }
142 | Gtk::TreeModelColumn type;
143 | };
144 | Single type;
145 |
146 | class Apps : public Gtk::TreeModel::ColumnRecord {
147 | public:
148 | Apps() { add(app); add(actions); add(count); }
149 | Gtk::TreeModelColumn app;
150 | Gtk::TreeModelColumn*> actions;
151 | Gtk::TreeModelColumn count;
152 | };
153 | Apps ca;
154 |
155 | /* exception list */
156 | Single exclude_cols;
157 | Glib::RefPtr exclude_tm;
158 | Gtk::TreeView* exclude_tv;
159 |
160 | struct Focus;
161 |
162 | Glib::RefPtr type_store;
163 |
164 | Gtk::Button *button_record, *button_delete, *button_remove_app, *button_reset_actions;
165 | Gtk::CheckButton *check_show_deleted;
166 | Gtk::Expander *expander_apps;
167 | Gtk::VPaned *vpaned_apps;
168 |
169 | int vpaned_position;
170 | bool editing_new = false;
171 | bool editing = false;
172 |
173 | ActionListDiff* action_list;
174 | Glib::RefPtr widgets;
175 |
176 | /* import / export */
177 | Gtk::Window* import_dialog;
178 | Gtk::Button* button_import_cancel;
179 | Gtk::Button* button_import_import;
180 | Gtk::FileChooserButton* import_file_chooser;
181 | Gtk::RadioButton* import_add;
182 | Gtk::InfoBar* import_info;
183 | Gtk::Label* import_info_label;
184 | void try_import();
185 | void try_export();
186 |
187 | /* main window */
188 | std::unique_ptr main_win;
189 | const std::string config_dir;
190 | Glib::RefPtr timeout; /* timeout for saving changes */
191 | bool actions_changed = false;
192 | bool exiting = false;
193 | bool save_error = false;
194 | };
195 |
196 | #endif
197 |
198 |
--------------------------------------------------------------------------------
/src/input_events.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * input_events.cpp -- interface to generate input events in Wayfire
3 | *
4 | * Copyright 2020-2024 Daniel Kondor
5 | *
6 | * Permission to use, copy, modify, and/or distribute this software for any
7 | * purpose with or without fee is hereby granted, provided that the above
8 | * copyright notice and this permission notice appear in all copies.
9 | *
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 | *
18 | */
19 |
20 | #include "input_events.hpp"
21 |
22 | #include
23 |
24 | extern "C" {
25 | #define static
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #undef static
33 | }
34 |
35 | #include
36 | #include
37 |
38 | static const struct wlr_pointer_impl ws_headless_pointer_impl = {
39 | .name = "wstroke-pointer",
40 | };
41 |
42 | static const struct wlr_keyboard_impl ws_headless_keyboard_impl = {
43 | .name = "wstroke-keyboard",
44 | .led_update = nullptr
45 | };
46 |
47 |
48 | void input_headless::init() {
49 | auto& core = wf::compositor_core_t::get();
50 | /* 1. create headless backend */
51 | headless_backend = wlr_headless_backend_create(core.ev_loop);
52 | if(!headless_backend) {
53 | LOGE("Cannot create headless wlroots backend!");
54 | return;
55 | }
56 | /* 2. add to the core backend */
57 | if(!wlr_multi_backend_add(core.backend, headless_backend)) {
58 | LOGE("Cannot add headless wlroots backend!");
59 | wlr_backend_destroy(headless_backend);
60 | headless_backend = nullptr;
61 | return;
62 | }
63 | /* 3. start the new headless backend */
64 | start_backend();
65 |
66 | /* 4. create the new input device */
67 | input_pointer = (struct wlr_pointer*)calloc(1, sizeof(struct wlr_pointer));
68 | if(!input_pointer) {
69 | LOGE("Cannot create pointer device!");
70 | fini();
71 | return;
72 | }
73 | wlr_pointer_init(input_pointer, &ws_headless_pointer_impl, ws_headless_pointer_impl.name);
74 |
75 |
76 | input_keyboard = (struct wlr_keyboard*)calloc(1, sizeof(struct wlr_keyboard));
77 | if(!input_keyboard) {
78 | LOGE("Cannot create keyboard device!");
79 | fini();
80 | return;
81 | }
82 | wlr_keyboard_init(input_keyboard, &ws_headless_keyboard_impl, ws_headless_keyboard_impl.name);
83 |
84 | wl_signal_emit_mutable(&headless_backend->events.new_input, input_keyboard);
85 | wl_signal_emit_mutable(&headless_backend->events.new_input, input_pointer);
86 | }
87 |
88 | void input_headless::start_backend() {
89 | if(!wlr_backend_start(headless_backend)) {
90 | LOGE("Cannot start headless wlroots backend!");
91 | fini();
92 | }
93 | }
94 |
95 | void input_headless::fini() {
96 | if(input_pointer) {
97 | wlr_pointer_finish(input_pointer);
98 | free(input_pointer);
99 | input_pointer = nullptr;
100 | }
101 | if(input_keyboard) {
102 | wlr_keyboard_finish(input_keyboard);
103 | free(input_keyboard);
104 | input_keyboard = nullptr;
105 | }
106 | if(headless_backend) {
107 | auto& core = wf::compositor_core_t::get();
108 | wlr_multi_backend_remove(core.backend, headless_backend);
109 | wlr_backend_destroy(headless_backend);
110 | headless_backend = nullptr;
111 | }
112 | }
113 |
114 | void input_headless::pointer_button(uint32_t time_msec, uint32_t button, enum WSTROKE_BUTTON_STATE state) {
115 | if(!(input_pointer && headless_backend)) {
116 | LOGW("No input device created!");
117 | return;
118 | }
119 | LOGD("Emitting pointer button event");
120 | wlr_pointer_button_event ev;
121 | ev.pointer = input_pointer;
122 | ev.button = button;
123 | ev.state = state;
124 | ev.time_msec = time_msec;
125 | wl_signal_emit(&(input_pointer->events.button), &ev);
126 | }
127 |
128 | void input_headless::pointer_scroll(uint32_t time_msec, double delta, enum WSTROKE_AXIS_ORIENTATION o) {
129 | if(!(input_pointer && headless_backend)) {
130 | LOGW("No input device created!");
131 | return;
132 | }
133 | LOGD("Emitting pointer scroll event");
134 | wlr_pointer_axis_event ev;
135 | ev.pointer = input_pointer;
136 | ev.time_msec = time_msec;
137 | ev.source = WL_POINTER_AXIS_SOURCE_CONTINUOUS;
138 | ev.orientation = o;
139 | ev.delta = delta;
140 | ev.delta_discrete = delta * WLR_POINTER_AXIS_DISCRETE_STEP;;
141 | wl_signal_emit(&(input_pointer->events.axis), &ev);
142 | }
143 |
144 | void input_headless::pointer_start_swipe(uint32_t time_msec, uint32_t fingers) {
145 | if(!(input_pointer && headless_backend)) {
146 | LOGW("No input device created!");
147 | return;
148 | }
149 | LOGD("Emitting pointer swipe begin event");
150 | wlr_pointer_swipe_begin_event ev;
151 | ev.pointer = input_pointer;
152 | ev.time_msec = time_msec;
153 | ev.fingers = fingers;
154 | wl_signal_emit(&(input_pointer->events.swipe_begin), &ev);
155 | }
156 |
157 | void input_headless::pointer_update_swipe(uint32_t time_msec, uint32_t fingers, double dx, double dy) {
158 | if(!(input_pointer && headless_backend)) {
159 | LOGW("No input device created!");
160 | return;
161 | }
162 | LOGD("Emitting pointer swipe update event");
163 | wlr_pointer_swipe_update_event ev;
164 | ev.pointer = input_pointer;
165 | ev.time_msec = time_msec;
166 | ev.fingers = fingers;
167 | ev.dx = dx;
168 | ev.dy = dy;
169 | wl_signal_emit(&(input_pointer->events.swipe_update), &ev);
170 | }
171 |
172 | void input_headless::pointer_end_swipe(uint32_t time_msec, bool cancelled) {
173 | if(!(input_pointer && headless_backend)) {
174 | LOGW("No input device created!");
175 | return;
176 | }
177 | LOGD("Emitting pointer swipe end event");
178 | wlr_pointer_swipe_end_event ev;
179 | ev.pointer = input_pointer;
180 | ev.time_msec = time_msec;
181 | ev.cancelled = cancelled; //!! note: conversion from C++ bool to C99/C23 bool !!
182 | wl_signal_emit(&(input_pointer->events.swipe_end), &ev);
183 | }
184 |
185 | void input_headless::pointer_start_pinch(uint32_t time_msec, uint32_t fingers) {
186 | if(!(input_pointer && headless_backend)) {
187 | LOGW("No input device created!");
188 | return;
189 | }
190 | LOGD("Emitting pointer pinch begin event");
191 | wlr_pointer_pinch_begin_event ev;
192 | ev.pointer = input_pointer;
193 | ev.time_msec = time_msec;
194 | ev.fingers = fingers;
195 | wl_signal_emit(&(input_pointer->events.pinch_begin), &ev);
196 | }
197 |
198 | void input_headless::pointer_update_pinch(uint32_t time_msec, uint32_t fingers, double dx, double dy, double scale, double rotation) {
199 | if(!(input_pointer && headless_backend)) {
200 | LOGW("No input device created!");
201 | return;
202 | }
203 | LOGD("Emitting pointer pinch update event");
204 | wlr_pointer_pinch_update_event ev;
205 | ev.pointer = input_pointer;
206 | ev.time_msec = time_msec;
207 | ev.fingers = fingers;
208 | ev.dx = dx;
209 | ev.dy = dy;
210 | ev.scale = scale;
211 | ev.rotation = rotation;
212 | wl_signal_emit(&(input_pointer->events.pinch_update), &ev);
213 | }
214 |
215 | void input_headless::pointer_end_pinch(uint32_t time_msec, bool cancelled) {
216 | if(!(input_pointer && headless_backend)) {
217 | LOGW("No input device created!");
218 | return;
219 | }
220 | LOGD("Emitting pointer pinch end event");
221 | wlr_pointer_pinch_end_event ev;
222 | ev.pointer = input_pointer;
223 | ev.time_msec = time_msec;
224 | ev.cancelled = cancelled; //!! note: conversion from C++ bool to C99/C23 bool !!
225 | wl_signal_emit(&(input_pointer->events.pinch_end), &ev);
226 | }
227 |
228 | void input_headless::keyboard_key(uint32_t time_msec, uint32_t key, enum wl_keyboard_key_state state) {
229 | if(!(input_keyboard && headless_backend)) {
230 | LOGW("No input device created!");
231 | return;
232 | }
233 | LOGD("Emitting keyboard event ", key, state == WL_KEYBOARD_KEY_STATE_PRESSED ? ", pressed" : ", released");
234 | wlr_keyboard_key_event ev;
235 | ev.keycode = key;
236 | ev.state = (decltype(ev.state))state;
237 | ev.update_state = true;
238 | ev.time_msec = time_msec;
239 | wl_signal_emit(&(input_keyboard->events.key), &ev);
240 | }
241 |
242 | void input_headless::keyboard_mods(uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked) {
243 | if(!(input_keyboard && headless_backend)) {
244 | LOGW("No input device created!");
245 | return;
246 | }
247 | LOGD("Changing keyboard modifiers");
248 | wlr_keyboard_notify_modifiers(input_keyboard, mods_depressed, mods_latched, mods_locked, 0);
249 | /* struct wlr_seat* seat = wf::get_core().get_current_seat(); -- does not work: combining with the "real" keyboard
250 | struct wlr_keyboard_modifiers modifiers;
251 | modifiers.depressed = mods_depressed;
252 | modifiers.latched = mods_latched;
253 | modifiers.locked = mods_locked;
254 | modifiers.group = 0; // ??
255 | wlr_seat_keyboard_notify_modifiers(seat, &modifiers); */
256 | }
257 |
258 |
--------------------------------------------------------------------------------
/toplevel-grabber/toplevel-grabber.c:
--------------------------------------------------------------------------------
1 | /*
2 | * toplevel-grabber.c -- library using the wlr-foreign-toplevel
3 | * interface to get the ID of an activated toplevel view
4 | *
5 | * Copyright 2020 Daniel Kondor
6 | *
7 | * Permission to use, copy, modify, and/or distribute this software for any
8 | * purpose with or without fee is hereby granted, provided that the above
9 | * copyright notice and this permission notice appear in all copies.
10 | *
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
16 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
17 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 | *
19 | */
20 |
21 |
22 | #include
23 | #include
24 | #include
25 | #include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
26 | #include
27 |
28 | typedef struct zwlr_foreign_toplevel_handle_v1 wfthandle;
29 |
30 | /* struct to hold information for one instance of the grabber */
31 | struct tl_grabber {
32 | struct zwlr_foreign_toplevel_manager_v1* manager;
33 | struct wl_list toplevels;
34 | void (*callback)(void* data, struct tl_grabber* gr);
35 | void* data;
36 | struct toplevel* active;
37 | int init_done;
38 | };
39 |
40 | /* struct to hold information about one toplevel */
41 | struct toplevel {
42 | char* app_id;
43 | wfthandle* handle;
44 | wfthandle* parent;
45 | struct tl_grabber* gr;
46 | int init_done;
47 | struct wl_list link;
48 | };
49 |
50 |
51 | #ifndef G_GNUC_UNUSED
52 | #define G_GNUC_UNUSED __attribute__((unused))
53 | #endif
54 |
55 | /* callbacks */
56 |
57 | static void title_cb(G_GNUC_UNUSED void* data, G_GNUC_UNUSED wfthandle* handle,
58 | G_GNUC_UNUSED const char* title) {
59 | /* don't care */
60 | }
61 |
62 | static void appid_cb(void* data, G_GNUC_UNUSED wfthandle* handle, const char* app_id) {
63 | if(!(app_id && data)) return;
64 | struct toplevel* tl = (struct toplevel*)data;
65 | if(tl->app_id) free(tl->app_id);
66 | tl->app_id = strdup(app_id);
67 | }
68 |
69 | void output_enter_cb(G_GNUC_UNUSED void* data, G_GNUC_UNUSED wfthandle* handle,
70 | G_GNUC_UNUSED struct wl_output* output) {
71 | /* don't care */
72 | }
73 | void output_leave_cb(G_GNUC_UNUSED void* data, G_GNUC_UNUSED wfthandle* handle,
74 | G_GNUC_UNUSED struct wl_output* output) {
75 | /* don't care */
76 | }
77 |
78 | void state_cb(void* data, G_GNUC_UNUSED wfthandle* handle, struct wl_array* state) {
79 | if(!(data && state)) return;
80 | struct toplevel* tl = (struct toplevel*)data;
81 | struct tl_grabber* gr = tl->gr;
82 | if(!gr) return;
83 | int activated = 0;
84 | int i;
85 | uint32_t* stdata = (uint32_t*)state->data;
86 | for(i = 0; i*sizeof(uint32_t) < state->size; i++) {
87 | if(stdata[i] == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) {
88 | activated = 1;
89 | break;
90 | }
91 | }
92 | if(activated) {
93 | int orig_init_done = tl->init_done;
94 | struct toplevel* new_active = NULL;
95 | while(tl) {
96 | new_active = tl;
97 | if(!tl->parent) break;
98 | tl = zwlr_foreign_toplevel_handle_v1_get_user_data(tl->parent);
99 | }
100 | if(new_active != gr->active) {
101 | gr->active = new_active;
102 | if(orig_init_done && gr->callback) gr->callback(gr->data, gr);
103 | }
104 | }
105 | }
106 |
107 | void done_cb(void* data, G_GNUC_UNUSED wfthandle* handle) {
108 | if(!data) return;
109 | struct toplevel* tl = (struct toplevel*)data;
110 | tl->init_done = 1;
111 | }
112 |
113 | void closed_cb(void* data, G_GNUC_UNUSED wfthandle* handle) {
114 | if(!data) return;
115 | struct toplevel* tl = (struct toplevel*)data;
116 | /* note: we can assume that this toplevel is not set as the parent
117 | * of any existing toplevels at this point */
118 | wl_list_remove(&(tl->link));
119 | zwlr_foreign_toplevel_handle_v1_destroy(tl->handle);
120 | if(tl->app_id) free(tl->app_id);
121 | free(tl);
122 | }
123 |
124 | void parent_cb(void* data, G_GNUC_UNUSED wfthandle* handle, wfthandle* parent) {
125 | if(!data) return;
126 | struct toplevel* tl = (struct toplevel*)data;
127 | tl->parent = parent;
128 | }
129 |
130 | struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_interface = {
131 | .title = title_cb,
132 | .app_id = appid_cb,
133 | .output_enter = output_enter_cb,
134 | .output_leave = output_leave_cb,
135 | .state = state_cb,
136 | .done = done_cb,
137 | .closed = closed_cb,
138 | .parent = parent_cb
139 | };
140 |
141 | /* register new toplevel */
142 | static void new_toplevel(void *data, G_GNUC_UNUSED struct zwlr_foreign_toplevel_manager_v1 *manager,
143 | wfthandle *handle) {
144 | if(!handle) return;
145 | if(!data) {
146 | /* if we unset the user data pointer, then we don't care anymore */
147 | zwlr_foreign_toplevel_handle_v1_destroy(handle);
148 | return;
149 | }
150 | struct tl_grabber* gr = (struct tl_grabber*)data;
151 | struct toplevel* tl = (struct toplevel*)malloc(sizeof(struct toplevel));
152 | if(!tl) {
153 | /* TODO: error message */
154 | return;
155 | }
156 | tl->app_id = NULL;
157 | tl->handle = handle;
158 | tl->parent = NULL;
159 | tl->init_done = 0;
160 | tl->gr = gr;
161 | wl_list_insert(&(gr->toplevels), &(tl->link));
162 |
163 | /* note: we cannot do anything as long as we get app_id */
164 | zwlr_foreign_toplevel_handle_v1_add_listener(handle, &toplevel_handle_interface, tl);
165 | }
166 |
167 | /* sent when toplevel management is no longer available -- this will happen after stopping */
168 | static void toplevel_manager_finished(G_GNUC_UNUSED void *data,
169 | struct zwlr_foreign_toplevel_manager_v1 *manager) {
170 | zwlr_foreign_toplevel_manager_v1_destroy(manager);
171 | }
172 |
173 | static struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_interface = {
174 | .toplevel = new_toplevel,
175 | .finished = toplevel_manager_finished,
176 | };
177 |
178 | static void registry_global_add_cb(void *data, struct wl_registry *registry,
179 | uint32_t id, const char *interface, uint32_t version) {
180 | struct tl_grabber* gr = (struct tl_grabber*)data;
181 | if(!strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name)) {
182 | uint32_t v = zwlr_foreign_toplevel_manager_v1_interface.version;
183 | if(version < v) v = version;
184 | gr->manager = wl_registry_bind(registry, id, &zwlr_foreign_toplevel_manager_v1_interface, v);
185 | if(gr->manager)
186 | zwlr_foreign_toplevel_manager_v1_add_listener(gr->manager, &toplevel_manager_interface, gr);
187 | else { /* TODO: handle error */ }
188 | }
189 | gr->init_done = 0;
190 | }
191 |
192 | static void registry_global_remove_cb(G_GNUC_UNUSED void *data,
193 | G_GNUC_UNUSED struct wl_registry *registry, G_GNUC_UNUSED uint32_t id) {
194 | /* don't care */
195 | }
196 |
197 | static const struct wl_registry_listener registry_listener = {
198 | registry_global_add_cb,
199 | registry_global_remove_cb
200 | };
201 |
202 | struct tl_grabber* toplevel_grabber_new(struct wl_display* dpy,
203 | void (*callback)(void* data, struct tl_grabber* gr), void* data) {
204 | if(!dpy) {
205 | /* TODO: get display! */
206 | return NULL;
207 | }
208 |
209 | struct tl_grabber* gr = (struct tl_grabber*)malloc(sizeof(struct tl_grabber));
210 | if(!gr) return NULL;
211 |
212 | gr->manager = NULL;
213 | wl_list_init(&(gr->toplevels));
214 | gr->callback = callback;
215 | gr->data = data;
216 | gr->active = NULL;
217 |
218 | struct wl_registry* registry = wl_display_get_registry(dpy);
219 | wl_registry_add_listener(registry, ®istry_listener, gr);
220 | do {
221 | gr->init_done = 1;
222 | wl_display_roundtrip(dpy);
223 | }
224 | while(!gr->init_done);
225 | return gr;
226 | }
227 |
228 | char* toplevel_grabber_get_app_id(struct tl_grabber* gr) {
229 | char* ret = NULL;
230 | if(gr && gr->active && gr->active->app_id) ret = strdup(gr->active->app_id);
231 | return ret;
232 | }
233 |
234 | /*
235 | void toplevel_grabber_reset(struct tl_grabber* gr) {
236 | if(gr) gr->active = NULL;
237 | }
238 | */
239 |
240 | void toplevel_grabber_set_callback(struct tl_grabber* gr,
241 | void (*callback)(void* data, struct tl_grabber* gr), void* data) {
242 | if(gr) {
243 | gr->callback = callback;
244 | gr->data = data;
245 | }
246 | }
247 |
248 | int toplevel_grabber_activate_app(struct tl_grabber* gr,
249 | const char* app_id, struct wl_seat* wl_seat, int parent) {
250 | if(!(gr && app_id)) return -1;
251 | struct toplevel* tl;
252 | wl_list_for_each(tl, &(gr->toplevels), link) {
253 | if(!strcmp(app_id, tl->app_id)) {
254 | if(parent) while(tl->parent) {
255 | struct toplevel* tmp = zwlr_foreign_toplevel_handle_v1_get_user_data(tl->parent);
256 | if(!tmp) break;
257 | tl = tmp;
258 | }
259 | zwlr_foreign_toplevel_handle_v1_activate (tl->handle, wl_seat);
260 | return 0;
261 | }
262 | }
263 | return -1;
264 | }
265 |
266 | void toplevel_grabber_free(struct tl_grabber* gr) {
267 | if(!gr) return;
268 | /* stop listening and also free all existing toplevels */
269 | /* set user data to null -- this will stop adding newly reported toplevels */
270 | zwlr_foreign_toplevel_manager_v1_set_user_data(gr->manager, NULL);
271 | /* this will send the finished signal and result in destroying manager later */
272 | zwlr_foreign_toplevel_manager_v1_stop(gr->manager);
273 | gr->manager = NULL;
274 | /* destroy all existing toplevel handles */
275 | struct toplevel* tl;
276 | struct toplevel* tmp;
277 | wl_list_for_each_safe(tl, tmp, &(gr->toplevels), link) {
278 | wl_list_remove(&(tl->link));
279 | zwlr_foreign_toplevel_handle_v1_destroy(tl->handle);
280 | if(tl->app_id) free(tl->app_id);
281 | free(tl);
282 | }
283 | }
284 |
285 |
286 |
--------------------------------------------------------------------------------
/icons/wstroke.svg:
--------------------------------------------------------------------------------
1 |
2 |
46 |
--------------------------------------------------------------------------------
/toplevel-grabber/wlr-foreign-toplevel-management-unstable-v1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Copyright © 2018 Ilia Bozhinov
5 |
6 | Permission to use, copy, modify, distribute, and sell this
7 | software and its documentation for any purpose is hereby granted
8 | without fee, provided that the above copyright notice appear in
9 | all copies and that both that copyright notice and this permission
10 | notice appear in supporting documentation, and that the name of
11 | the copyright holders not be used in advertising or publicity
12 | pertaining to distribution of the software without specific,
13 | written prior permission. The copyright holders make no
14 | representations about the suitability of this software for any
15 | purpose. It is provided "as is" without express or implied
16 | warranty.
17 |
18 | THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
19 | SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
20 | FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
21 | SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
22 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
23 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
24 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
25 | THIS SOFTWARE.
26 |
27 |
28 |
29 |
30 | The purpose of this protocol is to enable the creation of taskbars
31 | and docks by providing them with a list of opened applications and
32 | letting them request certain actions on them, like maximizing, etc.
33 |
34 | After a client binds the zwlr_foreign_toplevel_manager_v1, each opened
35 | toplevel window will be sent via the toplevel event
36 |
37 |
38 |
39 |
40 | This event is emitted whenever a new toplevel window is created. It
41 | is emitted for all toplevels, regardless of the app that has created
42 | them.
43 |
44 | All initial details of the toplevel(title, app_id, states, etc.) will
45 | be sent immediately after this event via the corresponding events in
46 | zwlr_foreign_toplevel_handle_v1.
47 |
48 |
49 |
50 |
51 |
52 |
53 | Indicates the client no longer wishes to receive events for new toplevels.
54 | However the compositor may emit further toplevel_created events, until
55 | the finished event is emitted.
56 |
57 | The client must not send any more requests after this one.
58 |
59 |
60 |
61 |
62 |
63 | This event indicates that the compositor is done sending events to the
64 | zwlr_foreign_toplevel_manager_v1. The server will destroy the object
65 | immediately after sending this request, so it will become invalid and
66 | the client should free any resources associated with it.
67 |
68 |
69 |
70 |
71 |
72 |
73 | A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
74 | window. Each app may have multiple opened toplevels.
75 |
76 | Each toplevel has a list of outputs it is visible on, conveyed to the
77 | client with the output_enter and output_leave events.
78 |
79 |
80 |
81 |
82 | This event is emitted whenever the title of the toplevel changes.
83 |
84 |
85 |
86 |
87 |
88 |
89 | This event is emitted whenever the app-id of the toplevel changes.
90 |
91 |
92 |
93 |
94 |
95 |
96 | This event is emitted whenever the toplevel becomes visible on
97 | the given output. A toplevel may be visible on multiple outputs.
98 |
99 |
100 |
101 |
102 |
103 |
104 | This event is emitted whenever the toplevel stops being visible on
105 | the given output. It is guaranteed that an entered-output event
106 | with the same output has been emitted before this event.
107 |
108 |
109 |
110 |
111 |
112 |
113 | Requests that the toplevel be maximized. If the maximized state actually
114 | changes, this will be indicated by the state event.
115 |
116 |
117 |
118 |
119 |
120 | Requests that the toplevel be unmaximized. If the maximized state actually
121 | changes, this will be indicated by the state event.
122 |
123 |
124 |
125 |
126 |
127 | Requests that the toplevel be minimized. If the minimized state actually
128 | changes, this will be indicated by the state event.
129 |
130 |
131 |
132 |
133 |
134 | Requests that the toplevel be unminimized. If the minimized state actually
135 | changes, this will be indicated by the state event.
136 |
137 |
138 |
139 |
140 |
141 | Request that this toplevel be activated on the given seat.
142 | There is no guarantee the toplevel will be actually activated.
143 |
144 |
145 |
146 |
147 |
148 |
149 | The different states that a toplevel can have. These have the same meaning
150 | as the states with the same names defined in xdg-toplevel
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | This event is emitted immediately after the zlw_foreign_toplevel_handle_v1
162 | is created and each time the toplevel state changes, either because of a
163 | compositor action or because of a request in this protocol.
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 | This event is sent after all changes in the toplevel state have been
172 | sent.
173 |
174 | This allows changes to the zwlr_foreign_toplevel_handle_v1 properties
175 | to be seen as atomic, even if they happen via multiple events.
176 |
177 |
178 |
179 |
180 |
181 | Send a request to the toplevel to close itself. The compositor would
182 | typically use a shell-specific method to carry out this request, for
183 | example by sending the xdg_toplevel.close event. However, this gives
184 | no guarantees the toplevel will actually be destroyed. If and when
185 | this happens, the zwlr_foreign_toplevel_handle_v1.closed event will
186 | be emitted.
187 |
188 |
189 |
190 |
191 |
192 | The rectangle of the surface specified in this request corresponds to
193 | the place where the app using this protocol represents the given toplevel.
194 | It can be used by the compositor as a hint for some operations, e.g
195 | minimizing. The client is however not required to set this, in which
196 | case the compositor is free to decide some default value.
197 |
198 | If the client specifies more than one rectangle, only the last one is
199 | considered.
200 |
201 | The dimensions are given in surface-local coordinates.
202 | Setting width=height=0 removes the already-set rectangle.
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
215 |
216 |
217 |
218 |
219 | This event means the toplevel has been destroyed. It is guaranteed there
220 | won't be any more events for this zwlr_foreign_toplevel_handle_v1. The
221 | toplevel itself becomes inert so any requests will be ignored except the
222 | destroy request.
223 |
224 |
225 |
226 |
227 |
228 | Destroys the zwlr_foreign_toplevel_handle_v1 object.
229 |
230 | This request should be called either when the client does not want to
231 | use the toplevel anymore or after the closed event to finalize the
232 | destruction of the object.
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 | Requests that the toplevel be fullscreened on the given output. If the
241 | fullscreen state and/or the outputs the toplevel is visible on actually
242 | change, this will be indicated by the state and output_enter/leave
243 | events.
244 |
245 | The output parameter is only a hint to the compositor. Also, if output
246 | is NULL, the compositor should decide which output the toplevel will be
247 | fullscreened on, if at all.
248 |
249 |
250 |
251 |
252 |
253 |
254 | Requests that the toplevel be unfullscreened. If the fullscreen state
255 | actually changes, this will be indicated by the state event.
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 | This event is emitted whenever the parent of the toplevel changes.
264 |
265 |
266 |
267 |
268 |
269 |
--------------------------------------------------------------------------------
/src/actiondb.cc:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2008-2009, Thomas Jaeger
3 | * Copyright (c) 2020-2023, Daniel Kondor
4 | *
5 | * Permission to use, copy, modify, and/or distribute this software for any
6 | * purpose with or without fee is hereby granted, provided that the above
7 | * copyright notice and this permission notice appear in all copies.
8 | *
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
12 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
14 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
15 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 | */
17 | #include "actiondb.h"
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 |
33 | #ifdef ACTIONDB_CONVERT_CODES
34 | #include "convert_keycodes.h"
35 |
36 | static inline uint32_t convert_modifier(uint32_t mod) {
37 | return KeyCodes::convert_modifier(mod);
38 | }
39 |
40 | static inline uint32_t convert_keysym(uint32_t key) {
41 | return KeyCodes::convert_keysym(key);
42 | }
43 |
44 | #else
45 |
46 | static inline uint32_t convert_modifier(G_GNUC_UNUSED uint32_t mod) {
47 | throw std::runtime_error("unsupported action DB version!\nrun the wstroke-config program first to convert it to the new format\n");
48 | }
49 |
50 | static inline uint32_t convert_keysym(G_GNUC_UNUSED uint32_t key) {
51 | throw std::runtime_error("unsupported action DB version!\nrun the wstroke-config program first to convert it to the new format\n");
52 | }
53 |
54 | #endif
55 |
56 |
57 | BOOST_CLASS_EXPORT(Action)
58 | BOOST_CLASS_EXPORT(Command)
59 | BOOST_CLASS_EXPORT(ModAction)
60 | BOOST_CLASS_EXPORT(SendKey)
61 | BOOST_CLASS_EXPORT(SendText)
62 | BOOST_CLASS_EXPORT(Scroll)
63 | BOOST_CLASS_EXPORT(Ignore)
64 | BOOST_CLASS_EXPORT(Button)
65 | BOOST_CLASS_EXPORT(Misc)
66 | BOOST_CLASS_EXPORT(Global)
67 | BOOST_CLASS_EXPORT(View)
68 | BOOST_CLASS_EXPORT(Plugin)
69 | BOOST_CLASS_EXPORT(Touchpad)
70 |
71 |
72 | template void Action::serialize(G_GNUC_UNUSED Archive & ar, G_GNUC_UNUSED unsigned int version) {}
73 |
74 | template void Command::serialize(Archive & ar, unsigned int version) {
75 | ar & boost::serialization::base_object(*this);
76 | ar & cmd;
77 | if(version > 0) ar & desktop_file;
78 | }
79 |
80 | template void Plugin::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) {
81 | ar & boost::serialization::base_object(*this);
82 | ar & cmd;
83 | }
84 |
85 | template void ModAction::load(Archive & ar, G_GNUC_UNUSED unsigned int version) {
86 | ar & boost::serialization::base_object(*this);
87 | ar & mods;
88 | if (version < 1) mods = convert_modifier(mods);
89 | }
90 |
91 | template void ModAction::save(Archive & ar, G_GNUC_UNUSED unsigned int version) const {
92 | ar & boost::serialization::base_object(*this);
93 | ar & mods;
94 | }
95 |
96 | template void SendKey::load(Archive & ar, const unsigned int version) {
97 | ar & boost::serialization::base_object(*this);
98 | ar & key;
99 | if (version < 2) {
100 | uint32_t code;
101 | ar & code;
102 | if (version < 1) {
103 | bool xtest;
104 | ar & xtest;
105 | }
106 | key = convert_keysym(key);
107 | }
108 | }
109 |
110 | template void SendKey::save(Archive & ar, G_GNUC_UNUSED unsigned int version) const {
111 | ar & boost::serialization::base_object(*this);
112 | ar & key;
113 | }
114 |
115 | template void SendText::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) {
116 | ar & boost::serialization::base_object(*this);
117 | ar & text;
118 | }
119 |
120 | template void Scroll::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) {
121 | ar & boost::serialization::base_object(*this);
122 | }
123 |
124 | template void Ignore::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) {
125 | ar & boost::serialization::base_object(*this);
126 | }
127 |
128 | template void Button::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) {
129 | ar & boost::serialization::base_object(*this);
130 | ar & button;
131 | }
132 |
133 | template void Misc::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) {
134 | ar & boost::serialization::base_object(*this);
135 | ar & type;
136 | }
137 |
138 | std::unique_ptr Misc::convert() const {
139 | switch(type) {
140 | case SHOWHIDE:
141 | return Global::create(Global::Type::SHOW_CONFIG);
142 | case NONE:
143 | case DISABLE:
144 | case UNMINIMIZE:
145 | default:
146 | return Global::create(Global::Type::NONE);
147 | }
148 | }
149 |
150 | template void Global::load(Archive & ar, G_GNUC_UNUSED unsigned int version) {
151 | ar & boost::serialization::base_object(*this);
152 | ar & type;
153 | /* allow later extensions to add more types that might not be supported in older versions */
154 | if((uint32_t)type >= n_actions) type = Type::NONE;
155 | }
156 |
157 | template void Global::save(Archive & ar, G_GNUC_UNUSED unsigned int version) const {
158 | ar & boost::serialization::base_object(*this);
159 | ar & type;
160 | }
161 |
162 | template void View::load(Archive & ar, G_GNUC_UNUSED unsigned int version) {
163 | ar & boost::serialization::base_object(*this);
164 | ar & type;
165 | /* allow later extensions to add more types that might not be supported in older versions */
166 | if((uint32_t)type >= n_actions) type = Type::NONE;
167 | }
168 |
169 | template void View::save(Archive & ar, G_GNUC_UNUSED unsigned int version) const {
170 | ar & boost::serialization::base_object(*this);
171 | ar & type;
172 | }
173 |
174 | template void Touchpad::load(Archive & ar, G_GNUC_UNUSED unsigned int version) {
175 | ar & boost::serialization::base_object(*this);
176 | ar & type;
177 | /* allow later extensions to add more types that might not be supported in older versions */
178 | if((uint32_t)type >= n_actions) type = Type::NONE;
179 | ar & fingers;
180 | }
181 |
182 | template void Touchpad::save(Archive & ar, G_GNUC_UNUSED unsigned int version) const {
183 | ar & boost::serialization::base_object(*this);
184 | ar & type;
185 | ar & fingers;
186 | }
187 |
188 |
189 | class StrokeSet : public std::set> {
190 | friend class boost::serialization::access;
191 | template void serialize(Archive & ar, const unsigned int version);
192 | };
193 | BOOST_CLASS_EXPORT(StrokeSet)
194 |
195 | template void StrokeSet::serialize(Archive & ar, G_GNUC_UNUSED unsigned int version) {
196 | ar & boost::serialization::base_object > >(*this);
197 | }
198 |
199 | template void StrokeInfo::load(Archive & ar, const unsigned int version) {
200 | if (version >= 4) {
201 | ar & stroke;
202 | ar & action;
203 | }
204 | else {
205 | StrokeSet strokes;
206 | ar & strokes;
207 |
208 | if(strokes.size() && *strokes.begin()) stroke = std::move(**strokes.begin());
209 |
210 | boost::shared_ptr