Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Factory:PowerPC
waynergy
waynergy-0.16+3.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File waynergy-0.16+3.obscpio of Package waynergy
07070100000000000081A400000000000000000000000164751F57000001AE000000000000000000000000000000000000001B00000000waynergy-0.16+3/.gitignore# Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf 07070100000001000081A400000000000000000000000164751F5700000426000000000000000000000000000000000000001800000000waynergy-0.16+3/LICENSEMIT License Copyright (c) 2020 r-c-f Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 07070100000002000081A400000000000000000000000164751F5700003788000000000000000000000000000000000000001A00000000waynergy-0.16+3/README.md# waynergy An implementation of a synergy client for wayland compositors. Based on the upstream uSynergy library (heavily modified for more protocol support and a bit of paranoia). ## Getting started ### Prerequisites * wayland, including wayland-scanner and the base protocols * libxkbcommon * libtls (either from libressl, or libretls) * A compositor making use of [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots), or (on an experimental basis) KDE, or (if all else fails) the willingness to run questionable networking utilities with the privileges to access /dev/uinput * [wl-clipboard](https://github.com/bugaevc/wl-clipboard) for clipboard support (*only works on wlroots, not KDE/GNOME*) ### Building/Installation #### Packages - [AUR (release)](https://aur.archlinux.org/packages/waynergy) - [AUR (git master)](https://aur.archlinux.org/packages/waynergy-git) - [Gentoo ebuilds (by nrndda)](https://github.com/nrndda/nrndda-overlay/tree/master/gui-apps/waynergy) #### Manual ``` meson build cd build ninja ninja install ``` - Some distributions may not provide the required headers in the main packages of the listed prerequisites. Be sure to check for `-dev` or `-devel` variants if this is the case. - KDE users may need to adjust the absolute path in `waynergy.desktop` to satisfy kwin's trust checks; a mismatch will prevent the server from offering the required interface. ### Running #### Permissions/Security Granting networking software written in questionable C the ability to generate arbitrary inputs isn't exactly a recipe for success if people are out to get you. Using TLS is essential in any case. __For anyone posting an issue or otherwise seeking assistance: *debug logs are key logs*__. So far people have been prudent but it pays to make this clear I think. Posting a full debug log will make any targeted network attack far less concerning than your bank login credentials ending up on a public issue page, even if they are slightly obfuscated by key mapping translations. ##### wlroots (sway et al.) There are no restrictions on privileged protocols at the moment (though that [may eventually change](https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3339)). As such it should Just Work™. ##### KDE `waynergy.desktop` must be installed, and the path must be absolute or the required interface will not be offered. ##### uinput (everyone else) First and foremost it should be kept in mind that kernel-level virtual input may have undesirable side effects because the compositor is being completely bypassed. This most notably means that virtual consoles are no longer isolated as far as input is concerned, so simple lock screens can be easily bypassed if more than one session is active. I've not tested the usability of magic SysRq, but that could also potentially be a great annoyance. With that out of the way.... waynergy must be able to open `/dev/uinput` for this to work. Doing this for the general case as part of the installation would require setuid, which I'm not inclined to go with given obvious security implications (though it *will* still work, i.e. drop privileges as expected). The alternative approach is to create an additional group specifically for uinput access, greatly reducing the risk involved, but possibly varying between systems. In my case, a 'uinput' group is created, and a udev rule is used to modify the permissions appropriately: ``` # /etc/udev/rules.d/49-input.rules, in my case KERNEL=="uinput",GROUP:="uinput",MODE:="0660" ``` From here, one *could* assign one's users to this group, but doing so would open up uinput to every program, with all the potential issues noted in the first paragraph. The safest approach is probably setgid: ``` # as root -- adjust path as needed chown :uinput waynergy chmod g+s waynergy ``` If this doesn't still doesn't seem to work (as in [#38](https://github.com/r-c-f/waynergy/issues/38)) be sure that the `uinput` module is loaded properly. This might be done by creating a file `/etc/modules-load.d/uinput.conf` with the contents of `uinput`. #### CLI See ``` waynergy -h ``` output: ``` ./waynergy: Synergy client for wayland compositors USAGE: ./waynergy [-h|--help] [-v|--version] [-b|--backend backend] [-c|--host host] [-p|--port port] [-W|--width width] [-H|--height height] [-N|--name name] [-l|--logfile file] [-L|--loglevel level] [-n|--no-clip] [-e|--enable-crypto] [-E|--disable-crypto] [-t|--enable-tofu] [--fatal-none] [--fatal-ebad] [--fatal-ebsy] [--fatal-timeout] -h|--help: Help text -v|--version: Version information -b|--backend backend: [backend] Input backend -- one of wlr, kde, uinput -c|--host host: [host] Server to connect to -p|--port port: [port] Port -W|--width width: [width] Width of screen in pixels (manual override, must be given with height) -H|--height height: [height] Height of screen in pixels (manual override, must be given with width) -N|--name name: [name] Name of client screen -l|--logfile file: [log/path] Name of logfile to use -L|--loglevel level: [log/level] Log level -- number, increasing from 0 for more verbosity up to 6, or one of 'none', 'error', 'warn', 'info', 'debug', 'debugsyn' -n|--no-clip: Don't synchronize the clipboard -e|--enable-crypto: [tls/enable] Enable TLS encryption -E|--disable-crypto: [tls/enable] Force disable TLS encryption -t|--enable-tofu: [tls/tofu] Enable trust-on-first-use for TLS certificate --fatal-none: Consider *normal* disconnect (i.e. CBYE) to be fatal --fatal-ebad: Protocol errors are fatal --fatal-ebsy: EBSY (client already exists with our name) errors are fatal --fatal-timeout: timeouts are fatal ``` For each option that is also present in the configuration file, the applicable name is given in `[]`. Also note that `SIGUSR1` triggers re-execution. Useful until proper reconnect procedures exist. ### Configuration By default, the configuration files are stored in `$XDG_CONFIG_HOME/waynergy`, which is probably at `~/.config/waynergy` in most cases. This can be overridden with the environment variable `WAYNERGY_CONF_PATH`. Aside from keymaps and hashes, most things should go in `config.ini`; the old approach of single-value-per-file is retained strictly for compatibility, and because ripping it out would be annoying for cases where that approach is really genuinely useful (i.e. xkb keymaps which have their file format). The basic global values are `port`, `host`, `name`, `width`, and `height`, which do exactly what the command line option would do. Options within sections are referred to in the form `section/property` for the purposes of this document. In addition to `config.ini`, there is now support for snippets (with `.ini` extension) under `config.ini.d/`. Any duplicate options will overwrite any read previously. #### Keymap For the time being there are two key mapping mechanisms: xkb, which works with the wlr backend, and raw, which will work on everything. xkb appears a bit more complex, but has the advantage that the targets of the keycodes will remain effectively the same across any client that can run waynergy. For raw, both the source and the target will vary, based on the underlying operating systems and layout configurations. There is also now support for using the Synergy mapped key codes rather than the raw buttons, by setting `syn_raw_key_codes` to `false`. Eventually this may make a more universal approach possible for the defaults, though I'll leave that for anyone who wants to have a go at doing it. ##### XKB By default, the existing compositor keymap is tried, falling back on a baked-in default. This has changed from prior versions which always used the baked-in default; to get the old behavior, set `wl_keyboard_map` to false. If neither default is sufficient, an xkb-formatted map should be placed in `xkb_keymap`. The easiest way to obtain this is to use the output of ``` setxkbmap -print ``` For custom keycodes, one may run into issues with Xwayland if the minimum keycode is below 8. To work around this, an offset may be provided in the configuration as `xkb_key_offset`. ###### Windows primary Unfortunately there is no existing `xkb_keycodes` section included in xkbcommon that will work with a Windows primary. To deal with this I've included one that mostly works (minus the keys I don't actually have to test on my own systems) in `doc/xkb/keycodes/win`. ###### macOS primary The same issue of keycodes applies here; see `doc/xkb/keycodes/mac` for a usable configuration. ###### Generating custom keycode sections If everything is still broken, using `waynergy-mapper -x $NAME` will generate an `xkb_keycodes` section with the given name by iterating through every possible keycode in the current keymap to find out what the server is sending. ##### Raw keymapping Because the fake input protocol used by KDE doesn't support custom keymaps, while uinput doesn't involve xkb at all, using xkb for this doesn't work; instead, one must use the `raw-keymap` section to map the remote keycodes to the local keymap using the form `remote = local`; for example, to use an OpenBSD server I must specify the following section to get arrow keys working properly: ``` [raw-keymap] 98 = 111 100 = 113 104 = 116 102 = 114 ``` Because these will vary on the source and target end, providing general-purpose mappings is more difficult, but if anyone wants to contribute some under `doc` with clearly-defined server and client targets they would be appreciated by somebody probably. The offset functionality is enabled through `raw-keymap/offset`, though it can also be disabled for explicit mappings by setting `raw-keymap/offset_on_explicit` to `false`. Generating a raw keymapping from scratch is now much more straightforward: run `waynergy-mapper -r`, which will iterate through every keycode in the local xkb map and accept input from the remote, a bit like using xev/wev but with immediately usable output. ###### Synergy ID keymapping In some cases (see recent comments on [#35](https://github.com/r-c-f/waynergy/issues/35)) Windows servers are sending duplicate button values for keys which are clearly different. In these cases using the synergy key IDs might make sense in an `id-keymap` section: ``` [id-keymap] 57218 = 199 57219 = 200 ``` with actual values based on the above process, using the `id` parameter in the server log instead of `button`. #### Button map This should not be necessary in 99.9% of situations, but if you want to change the default mouse button behavior you can include a section redefining the button map in use: ``` [button-map] 0 = 0 # No button 1 = 0x110 # BTN_LEFT 2 = 0x112 # BTN_MIDDLE 3 = 0x111 # BTN_RIGHT 4 = 0x113 # BTN_SIDE 5 = 0x114 # BTN_EXTRA ``` Given here is the default; each of 6 protocol possibilities is mapped to a value based on `/usr/include/linux/input-event-keycodes.h`. #### Screensaver `screensaver/start` should contain a command to be run when the screensaver is activated remotely, `screensaver/stop` should contain a command to terminate it when it is deactivated. #### Idle inhibition hack Due to issues with the idle inhibition protocol, idle is actually inhibited by sending a hopefully-meaningless event to the compositor: if `idle-inhibit/method` is `key`, the raw keycode given in `idle-inhibit/keycode` is sent if defined, falling back on the xkb-style key name in `idle-inhibit/keyname`, which will default to `HYPR`. If `idle-inhbibit/method` is `mouse`, then a relative move of 0,0 is sent (this is the default). For GNOME, `gnome-session-inhibit` is used instead. This should be automatically detected. Synergy upstream seems to no longer support this; if your screenlocker never triggers set `idle-inhibit/enable` to `false`. Barrier and input-leap seem to still work. #### TLS Enabled or disabled with `tls/enable`. Certificate hashes (for any given host) are stored in the directory `tls/hash/`, one hash per file named after the host. These can be obtained by running something like ``` printf "SHA256:%s\n" $(openssl x509 -outform der -in $certpath | openssl dgst -sha256 | cut -d ' ' -f 2) ``` on the host. Common values of $certpath would be - `%LocalAppData%\Barrier\SSL\Barrier.pem` - `~/.local/share/barrier/SSL/Barrier.pem` - `$XDG_DATA_HOME/barrier/SSL/Barrier.pem` - `~/.synergy/SSL/Synergy.pem` There is also the option to trust on first use by setting `tls/tofu` or running with the `--enable-tofu` option on the command line, which will allow verification that it has not changed on subsequent connections. Client certificates are now supported as well; simply place the certificate at `tls/cert`. #### wlroots wheel issues The latest version of wlroots has an issue where discrete axis events are accumulated rather than sent directly; to correct for this the discrete parameter is now a multiple of `120` rather than `1`. For sway or wayfire users on Linux this is automatically detected and worked around; otherwise, the `wlr/wheel_mult` configuration option may be used. ## Acknowledgements I would like to thank * [uSynergy](https://github.com/symless/synergy-micro-client) for the protocol library * The swaywm people, who've provided the protocols to make something like this possible * wl-clipboard, because its watch mode turns it into a clipboard manager so I I don't have to. * [mattiasgustavsson](https://github.com/mattiasgustavsson/libs) for the INI implementation. * kwin developers for supporting a fake input protocol these days * the kernel developers for supplying uinput on Linux * and __users like you__, whose contributions (even through simple reporting of issues) are instrumental in making this suck just a little bit less over time. Adding multiple backend support, for compositors I don't even use, has proven entirely worthwhile for this reason, for example. ## TODO At this point I'm content with most things here save for the following * KDE likes to hide the mouse pointer if there is no pointing device currently installed. I don't think it's too much work to plug in a mouse, but some would probably prefer a better workaround. 07070100000003000041ED00000000000000000000000464751F5700000000000000000000000000000000000000000000001400000000waynergy-0.16+3/doc07070100000004000041ED00000000000000000000000264751F5700000000000000000000000000000000000000000000002300000000waynergy-0.16+3/doc/config-example07070100000005000081A400000000000000000000000164751F57000001FA000000000000000000000000000000000000002E00000000waynergy-0.16+3/doc/config-example/config.ini#Example configuration (in INI format) host = foo.bar #port = 28400 #name = localhost #width = 1024 #height = 768 xkb_key_offset = 0 # Restart on a fatal error, rather than exit #restart_on_fatal = false [screensaver] start = pkill -SIGUSR1 swayidle #stop = pkill swaylock [idle-inhibit] method = key keyname = HYPR [tls] enable = true tofu = true [log] level = 3 mode = a path = /tmp/waynergy.log [wayland] # Timeout for display flushes (in ms), negative to block indefinitely #flush_timeout = 5000 07070100000006000081A400000000000000000000000164751F570000013B000000000000000000000000000000000000002E00000000waynergy-0.16+3/doc/config-example/xkb_keymapxkb_keymap { xkb_keycodes { include "evdev+aliases(qwerty)" }; xkb_types { include "complete" }; xkb_compat { include "complete" }; xkb_symbols { include "pc+us+inet(evdev)+capslock(super)+compose(ralt)" }; xkb_geometry { include "pc(pc105)" }; }; 07070100000007000041ED00000000000000000000000364751F5700000000000000000000000000000000000000000000001800000000waynergy-0.16+3/doc/xkb07070100000008000041ED00000000000000000000000264751F5700000000000000000000000000000000000000000000002100000000waynergy-0.16+3/doc/xkb/keycodes07070100000009000081A400000000000000000000000164751F57000004BE000000000000000000000000000000000000002500000000waynergy-0.16+3/doc/xkb/keycodes/mac//Set waynergy config `xkb_key_offset` to 7 default xkb_keycodes "mac" { minimum = 8; maximum = 255; <ESC> = 61; <FK01> = 130; <FK02> = 128; <FK03> = 107; <FK04> = 126; <FK05> = 104; <FK06> = 105; <FK07> = 106; <FK08> = 108; <FK09> = 109; <FK10> = 117; <FK11> = 111; <FK12> = 119; <TLDE> = 58; <AE01> = 26; <AE02> = 27; <AE03> = 28; <AE04> = 29; <AE05> = 31; <AE06> = 30; <AE07> = 34; <AE08> = 36; <AE09> = 33; <AE10> = 37; <AE11> = 35; <AE12> = 32; <BKSP> = 59; <TAB> = 56; <AD01> = 20; <AD02> = 21; <AD03> = 22; <AD04> = 23; <AD05> = 25; <AD06> = 24; <AD07> = 40; <AD08> = 42; <AD09> = 39; <AD10> = 43; <AD11> = 41; <AD12> = 38; <BKSL> = 50; <CAPS> = 65; <AC01> = 8; <AC02> = 9; <AC03> = 10; <AC04> = 11; <AC05> = 13; <AC06> = 12; <AC07> = 46; <AC08> = 48; <AC09> = 45; <AC10> = 49; <AC11> = 47; <RTRN> = 44; <LFSH> = 64; <AB01> = 14; <AB02> = 15; <AB03> = 16; <AB04> = 17; <AB05> = 19; <AB06> = 53; <AB07> = 54; <AB08> = 51; <AB09> = 55; <AB10> = 52; <LALT> = 66; <LCTL> = 67; <SPCE> = 57; <LWIN> = 63; <UP> = 134; <LEFT> = 131; <DOWN> = 133; <RGHT> = 132; }; 0707010000000A000081A400000000000000000000000164751F5700000A4E000000000000000000000000000000000000002500000000waynergy-0.16+3/doc/xkb/keycodes/win// Set waynergy config xkb_key_offset to 7 // // XKB keycodes for Windows primaries // // Derived from trial and error, and then the eventual realization that // these are actually just specially encoded PS/2 set 1 make scancodes: // - single byte codes are sent unaltered // - E0-profixed codes are sent as the primary byte OR'd with 0x100. // // This may be horribly wrong in some cases, feel free to send corrections // or extensions for more fancy layouts with more specialized keys default xkb_keycodes "win" { minimum= 8; maximum= 1024; <ESC> = 0x8; <FK01> = 0x42; <FK02> = 0x43; <FK03> = 0x44; <FK04> = 0x45; <FK05> = 0x46; <FK06> = 0x47; <FK07> = 0x48; <FK08> = 0x49; <FK09> = 0x4a; <FK10> = 0x4b; <FK11> = 0x4c; <FK12> = 0x4d; <PRSC> = 318; <SCLK> = 0x4d; // <PAUS> = //Doesn't seem to be sent <TLDE> = 0x30; <AE01> = 0x09; <AE02> = 0x0a; <AE03> = 0x0b; <AE04> = 0x0c; <AE05> = 0x0d; <AE06> = 0x0e; <AE07> = 0x0f; <AE08> = 0x10; <AE09> = 0x11; <AE10> = 0x12; <AE11> = 0x13; <AE12> = 0x14; <BKSP> = 0x15; <TAB> = 0x16; <AD01> = 0x17; <AD02> = 0x18; <AD03> = 0x19; <AD04> = 0x1a; <AD05> = 0x1b; <AD06> = 0x1c; <AD07> = 0x1d; <AD08> = 0x1e; <AD09> = 0x1f; <AD10> = 0x20; <AD11> = 0x21; <AD12> = 0x22; <BKSL> = 50; <CAPS> = 0x41; <AC01> = 0x25; <AC02> = 0x26; <AC03> = 0x27; <AC04> = 0x28; <AC05> = 0x29; <AC06> = 0x2a; <AC07> = 0x2b; <AC08> = 0x2c; <AC09> = 0x2d; <AC10> = 0x2e; <AC11> = 0x2f; <RTRN> = 0x23; <LFSH> = 0x31; <AB01> = 0x33; <AB02> = 0x34; <AB03> = 0x35; <AB04> = 0x36; <AB05> = 0x37; <AB06> = 0x38; <AB07> = 0x39; <AB08> = 0x3a; <AB09> = 0x3b; <AB10> = 0x3c; <RTSH> = 0x3d; <LCTL> = 0x24; <LWIN> = 354; <LALT> = 0x3f; <SPCE> = 0x40; //<LVL3> = 319; //necessary for some layouts to type accented characters <RALT> = 319; // standard US layout <RWIN> = 0x203; <MENU> = 356; <RCTL> = 292; <INS> = 345; <HOME> = 334; <PGUP> = 336; <DELE> = 346; <END> = 342; <PGDN> = 344; <LEFT> = 338; <UP> = 335; <RGHT> = 340; <DOWN> = 343; <NMLK> = 0x4c; <KPDV> = 0x13c; <KPMU> = 0x3e; <KPSU> = 0x51; <KPAD> = 0x55; <KPEN> = 0x123; <KPDL> = 0x5a; <KP1> = 0x56; <KP2> = 0x57; <KP3> = 0x58; <KP4> = 0x52; <KP5> = 0x53; <KP6> = 0x54; <KP7> = 0x4e; <KP8> = 0x4f; <KP9> = 0x50; <KP0> = 0x59; }; 0707010000000B000041ED00000000000000000000000264751F5700000000000000000000000000000000000000000000001800000000waynergy-0.16+3/include0707010000000C000081A400000000000000000000000164751F570000042A000000000000000000000000000000000000001F00000000waynergy-0.16+3/include/clip.h#pragma once #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> #include <poll.h> #include <stdint.h> #include <stdbool.h> #include "uSynergy.h" #include "os.h" #include "xmem.h" #include "fdio_full.h" #include <spawn.h> #include "log.h" #include "net.h" #define CLIP_UPDATER_FD_COUNT 8 extern int clipMonitorFd; extern struct sockaddr_un clipMonitorAddr; extern pid_t clipMonitorPid[2]; /* check if wl-clipboard is even present */ bool clipHaveWlClipboard(void); /* spawn wl-paste monitor processes */ bool clipSetupSockets(void); bool clipSpawnMonitors(void); /* convert a file descriptor to a clipboard ID */ enum uSynergyClipboardId clipIdFromFd(int fd); /* process poll data */ void clipMonitorPollProc(struct pollfd *pfd); /* run wl-copy, with given data */ bool clipWlCopy(enum uSynergyClipboardId id, const unsigned char *data, size_t len); /* write all of stdin to the clipboard monitor FIFO */ int clipWriteToSocket(char *path, char cid); 0707010000000D000081A400000000000000000000000164751F57000004B3000000000000000000000000000000000000002100000000waynergy-0.16+3/include/config.h#pragma once #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdbool.h> #include <unistd.h> #include <errno.h> #include <sys/stat.h> #include <fcntl.h> /* initialize configuration -- used to load INI */ extern bool configInitINI(void); /* read a configuration file from the proper directory, or return NULL */ extern char *configReadFile(char *name); /* read a configuration file, into an array of strings representing each line*/ extern char **configReadLines(char *name); /* read all properties from a given INI section */ extern int configReadFullSection(char *name, char ***key, char ***val); /* the following make an attempt to read a configuration file, but will allow * specifying a default if it is not present or otherwise borked */ /* read a string, *including* any newlines */ extern char *configTryStringFull(char *name, char *def); /* read a string, *trimmed* of the newline */ extern char *configTryString(char *name, char *def); extern long configTryLong(char *name, long def); /* read a bool */ extern bool configTryBool(char *name, bool def); /* Write a string to a config file */ extern bool configWriteString(char *name, const char *val, bool overwrite); 0707010000000E000081A400000000000000000000000164751F57000009B4000000000000000000000000000000000000002400000000waynergy-0.16+3/include/fdio_full.h/* fdio_full -- read/write without shortness * * Version 1.1 * * Copyright 2021 Ryan Farley <ryan.farley@gmx.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef FDIO_FULL_H_INC #define FDIO_FULL_H_INC #if defined(__GNUC__) #define SHL_UNUSED __attribute__((unused)) #else #define SHL_UNUSED #endif #include <unistd.h> #include <errno.h> #include <string.h> #include <stdbool.h> #include <limits.h> enum fdio_full_flag { FDIO_FULL_FLAG_NONE = 0, FDIO_FULL_FLAG_NB = 1, //don't block on non-blocking sockets FDIO_FULL_FLAG_INTR = 2, //bail out on EINTR }; SHL_UNUSED static bool read_full(int fd, void *buf, size_t count, enum fdio_full_flag flags) { ssize_t ret; char *pos; for (pos = buf; count;) { errno = 0; ret = read(fd, pos, count > SSIZE_MAX ? SSIZE_MAX : count); if (ret < 1) { switch (errno) { case EINTR: if (flags & FDIO_FULL_FLAG_INTR) return false; continue; case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif if (flags & FDIO_FULL_FLAG_NB) return false; continue; default: return false; } } pos += ret; count -= ret; } return true; } SHL_UNUSED static bool write_full(int fd, const void *buf, size_t count, enum fdio_full_flag flags) { ssize_t ret; const char *pos; for (pos = buf; count;) { errno = 0; ret = write(fd, pos, count > SSIZE_MAX ? SSIZE_MAX : count); if (ret < 1) { switch (errno) { case EINTR: if (flags & FDIO_FULL_FLAG_INTR) return false; continue; case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif if (flags & FDIO_FULL_FLAG_NB) return false; continue; default: return false; } } pos += ret; count -= ret; } return true; } #undef SHL_UNUSED #endif 0707010000000F000081A400000000000000000000000164751F5700009833000000000000000000000000000000000000001E00000000waynergy-0.16+3/include/ini.h/* ------------------------------------------------------------------------------ Licensing information can be found at the end of the file. ------------------------------------------------------------------------------ ini.h - v1.2 - Simple ini-file reader for C/C++. Do this: #define INI_IMPLEMENTATION before you include this file in *one* C/C++ file to create the implementation. */ #ifndef ini_h #define ini_h #define INI_GLOBAL_SECTION ( 0 ) #define INI_NOT_FOUND ( -1 ) typedef struct ini_t ini_t; ini_t* ini_create( void* memctx ); ini_t* ini_load( char const* data, void* memctx ); int ini_save( ini_t const* ini, char* data, int size ); void ini_destroy( ini_t* ini ); int ini_section_count( ini_t const* ini ); char const* ini_section_name( ini_t const* ini, int section ); int ini_property_count( ini_t const* ini, int section ); char const* ini_property_name( ini_t const* ini, int section, int property ); char const* ini_property_value( ini_t const* ini, int section, int property ); int ini_find_section( ini_t const* ini, char const* name, int name_length ); int ini_find_property( ini_t const* ini, int section, char const* name, int name_length ); int ini_section_add( ini_t* ini, char const* name, int length ); void ini_property_add( ini_t* ini, int section, char const* name, int name_length, char const* value, int value_length ); void ini_section_remove( ini_t* ini, int section ); void ini_property_remove( ini_t* ini, int section, int property ); void ini_section_name_set( ini_t* ini, int section, char const* name, int length ); void ini_property_name_set( ini_t* ini, int section, int property, char const* name, int length ); void ini_property_value_set( ini_t* ini, int section, int property, char const* value, int length ); #endif /* ini_h */ /** ini.h ===== Simple ini-file reader for C/C++. Examples -------- #### Loading an ini file and retrieving values #define INI_IMPLEMENTATION #include "ini.h" #include <stdio.h> #include <stdlib.h> int main() { FILE* fp = fopen( "test.ini", "r" ); fseek( fp, 0, SEEK_END ); int size = ftell( fp ); fseek( fp, 0, SEEK_SET ); char* data = (char*) malloc( size + 1 ); fread( data, 1, size, fp ); data[ size ] = '\0'; fclose( fp ); ini_t* ini = ini_load( data ); free( data ); int second_index = ini_find_property( ini, INI_GLOBAL_SECTION, "SecondSetting" ); char const* second = ini_property_value( ini, INI_GLOBAL_SECTION, second_index ); printf( "%s=%s\n", "SecondSetting", second ); int section = ini_find_section( ini, "MySection" ); int third_index = ini_find_property( ini, section, "ThirdSetting" ); char const* third = ini_property_value( ini, section, third_index ); printf( "%s=%s\n", "ThirdSetting", third ); ini_destroy( ini ); return 0; } ----------------------------------------------------------------------------------------------- #### Creating a new ini file #define INI_IMPLEMENTATION #include "ini.h" #include <stdio.h> #include <stdlib.h> int main() { ini_t* ini = ini_create(); ini_property_add( ini, INI_GLOBAL_SECTION, "FirstSetting", "Test" ); ini_property_add( ini, INI_GLOBAL_SECTION, "SecondSetting", "2" ); int section = ini_section_add( ini, "MySection" ); ini_property_add( ini, section, "ThirdSetting", "Three" ); int size = ini_save( ini, NULL, 0 ); // Find the size needed char* data = (char*) malloc( size ); size = ini_save( ini, data, size ); // Actually save the file ini_destroy( ini ); FILE* fp = fopen( "test.ini", "w" ); fwrite( data, 1, size, fp ); fclose( fp ); free( data ); return 0; } API Documentation ----------------- ini.h is a small library for reading classic .ini files. It is a single-header library, and does not need any .lib files or other binaries, or any build scripts. To use it, you just include ini.h to get the API declarations. To get the definitions, you must include ini.h from *one* single C or C++ file, and #define the symbol `INI_IMPLEMENTATION` before you do. ### Customization There are a few different things in ini.h which are configurable by #defines. The customizations only affect the implementation, so will only need to be defined in the file where you have the #define INI_IMPLEMENTATION. Note that if all customizations are utilized, ini.h will include no external files whatsoever, which might be useful if you need full control over what code is being built. #### Custom memory allocators To store the internal data structures, ini.h needs to do dynamic allocation by calling `malloc`. Programs might want to keep track of allocations done, or use custom defined pools to allocate memory from. ini.h allows for specifying custom memory allocation functions for `malloc` and `free`. This is done with the following code: #define INI_IMPLEMENTATION #define INI_MALLOC( ctx, size ) ( my_custom_malloc( ctx, size ) ) #define INI_FREE( ctx, ptr ) ( my_custom_free( ctx, ptr ) ) #include "ini.h" where `my_custom_malloc` and `my_custom_free` are your own memory allocation/deallocation functions. The `ctx` parameter is an optional parameter of type `void*`. When `ini_create` or `ini_load` is called, you can pass in a `memctx` parameter, which can be a pointer to anything you like, and which will be passed through as the `ctx` parameter to every `INI_MALLOC`/`INI_FREE` call. For example, if you are doing memory tracking, you can pass a pointer to your tracking data as `memctx`, and in your custom allocation/deallocation function, you can cast the `ctx` param back to the right type, and access the tracking data. If no custom allocator is defined, ini.h will default to `malloc` and `free` from the C runtime library. #### Custom C runtime function The library makes use of three additional functions from the C runtime library, and for full flexibility, it allows you to substitute them for your own. Here's an example: #define INI_IMPLEMENTATION #define INI_MEMCPY( dst, src, cnt ) ( my_memcpy_func( dst, src, cnt ) ) #define INI_STRLEN( s ) ( my_strlen_func( s ) ) #define INI_STRNICMP( s1, s2, cnt ) ( my_strnicmp_func( s1, s2, cnt ) ) #include "ini.h" If no custom function is defined, ini.h will default to the C runtime library equivalent. ini_create ---------- ini_t* ini_create( void* memctx ) Instantiates a new, empty ini structure, which can be manipulated with other API calls, to fill it with data. To save it out to an ini-file string, use `ini_save`. When no longer needed, it can be destroyed by calling `ini_destroy`. `memctx` is a pointer to user defined data which will be passed through to the custom INI_MALLOC/INI_FREE calls. It can be NULL if no user defined data is needed. ini_load -------- ini_t* ini_load( char const* data, void* memctx ) Parse the zero-terminated string `data` containing an ini-file, and create a new ini_t instance containing the data. The instance can be manipulated with other API calls to enumerate sections/properties and retrieve values. When no longer needed, it can be destroyed by calling `ini_destroy`. `memctx` is a pointer to user defined data which will be passed through to the custom INI_MALLOC/INI_FREE calls. It can be NULL if no user defined data is needed. ini_save -------- int ini_save( ini_t const* ini, char* data, int size ) Saves an ini structure as a zero-terminated ini-file string, into the specified buffer. Returns the number of bytes written, including the zero terminator. If `data` is NULL, nothing is written, but `ini_save` still returns the number of bytes it would have written. If the size of `data`, as specified in the `size` parameter, is smaller than that required, only part of the ini-file string will be written. `ini_save` still returns the number of bytes it would have written had the buffer been large enough. ini_destroy ----------- void ini_destroy( ini_t* ini ) Destroy an `ini_t` instance created by calling `ini_load` or `ini_create`, releasing the memory allocated by it. No further API calls are valid on an `ini_t` instance after calling `ini_destroy` on it. ini_section_count ----------------- int ini_section_count( ini_t const* ini ) Returns the number of sections in an ini file. There's at least one section in an ini file (the global section), but there can be many more, each specified in the file by the section name wrapped in square brackets [ ]. ini_section_name ---------------- char const* ini_section_name( ini_t const* ini, int section ) Returns the name of the section with the specified index. `section` must be non-negative and less than the value returned by `ini_section_count`, or `ini_section_name` will return NULL. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. ini_property_count ------------------ int ini_property_count( ini_t const* ini, int section ) Returns the number of properties belonging to the section with the specified index. `section` must be non-negative and less than the value returned by `ini_section_count`, or `ini_section_name` will return 0. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. Properties are declared in the ini-file on he format `name=value`. ini_property_name ----------------- char const* ini_property_name( ini_t const* ini, int section, int property ) Returns the name of the property with the specified index `property` in the section with the specified index `section`. `section` must be non-negative and less than the value returned by `ini_section_count`, and `property` must be non-negative and less than the value returned by `ini_property_count`, or `ini_property_name` will return NULL. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. ini_property_value ------------------ char const* ini_property_value( ini_t const* ini, int section, int property ) Returns the value of the property with the specified index `property` in the section with the specified index `section`. `section` must be non-negative and less than the value returned by `ini_section_count`, and `property` must be non-negative and less than the value returned by `ini_property_count`, or `ini_property_value` will return NULL. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. ini_find_section ---------------- int ini_find_section( ini_t const* ini, char const* name, int name_length ) Finds the section with the specified name, and returns its index. `name_length` specifies the number of characters in `name`, which does not have to be zero-terminated. If `name_length` is zero, the length is determined automatically, but in this case `name` has to be zero-terminated. If no section with the specified name could be found, the value `INI_NOT_FOUND` is returned. ini_find_property ----------------- int ini_find_property( ini_t const* ini, int section, char const* name, int name_length ) Finds the property with the specified name, within the section with the specified index, and returns the index of the property. `name_length` specifies the number of characters in `name`, which does not have to be zero-terminated. If `name_length` is zero, the length is determined automatically, but in this case `name` has to be zero-terminated. If no property with the specified name could be found within the specified section, the value `INI_NOT_FOUND` is returned. `section` must be non-negative and less than the value returned by `ini_section_count`, or `ini_find_property` will return `INI_NOT_FOUND`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. ini_section_add --------------- int ini_section_add( ini_t* ini, char const* name, int length ) Adds a section with the specified name, and returns the index it was added at. There is no check done to see if a section with the specified name already exists - multiple sections of the same name are allowed. `length` specifies the number of characters in `name`, which does not have to be zero-terminated. If `length` is zero, the length is determined automatically, but in this case `name` has to be zero-terminated. ini_property_add ---------------- void ini_property_add( ini_t* ini, int section, char const* name, int name_length, char const* value, int value_length ) Adds a property with the specified name and value to the specified section, and returns the index it was added at. There is no check done to see if a property with the specified name already exists - multiple properties of the same name are allowed. `name_length` and `value_length` specifies the number of characters in `name` and `value`, which does not have to be zero-terminated. If `name_length` or `value_length` is zero, the length is determined automatically, but in this case `name`/`value` has to be zero-terminated. `section` must be non-negative and less than the value returned by `ini_section_count`, or the property will not be added. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. ini_section_remove ------------------ void ini_section_remove( ini_t* ini, int section ) Removes the section with the specified index, and all properties within it. `section` must be non-negative and less than the value returned by `ini_section_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. Note that removing a section will shuffle section indices, so that section indices you may have stored will no longer indicate the same section as it did before the remove. Use the find functions to update your indices. ini_property_remove ------------------- void ini_property_remove( ini_t* ini, int section, int property ) Removes the property with the specified index from the specified section. `section` must be non-negative and less than the value returned by `ini_section_count`, and `property` must be non-negative and less than the value returned by `ini_property_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. Note that removing a property will shuffle property indices within the specified section, so that property indices you may have stored will no longer indicate the same property as it did before the remove. Use the find functions to update your indices. ini_section_name_set -------------------- void ini_section_name_set( ini_t* ini, int section, char const* name, int length ) Change the name of the section with the specified index. `section` must be non-negative and less than the value returned by `ini_section_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. `length` specifies the number of characters in `name`, which does not have to be zero-terminated. If `length` is zero, the length is determined automatically, but in this case `name` has to be zero-terminated. ini_property_name_set --------------------- void ini_property_name_set( ini_t* ini, int section, int property, char const* name, int length ) Change the name of the property with the specified index in the specified section. `section` must be non-negative and less than the value returned by `ini_section_count`, and `property` must be non-negative and less than the value returned by `ini_property_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. `length` specifies the number of characters in `name`, which does not have to be zero-terminated. If `length` is zero, the length is determined automatically, but in this case `name` has to be zero-terminated. ini_property_value_set ---------------------- void ini_property_value_set( ini_t* ini, int section, int property, char const* value, int length ) Change the value of the property with the specified index in the specified section. `section` must be non-negative and less than the value returned by `ini_section_count`, and `property` must be non-negative and less than the value returned by `ini_property_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. `length` specifies the number of characters in `value`, which does not have to be zero-terminated. If `length` is zero, the length is determined automatically, but in this case `value` has to be zero-terminated. */ /* ---------------------- IMPLEMENTATION ---------------------- */ #ifdef INI_IMPLEMENTATION #undef INI_IMPLEMENTATION #define INITIAL_CAPACITY ( 256 ) #undef _CRT_NONSTDC_NO_DEPRECATE #define _CRT_NONSTDC_NO_DEPRECATE #undef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #include <stddef.h> #ifndef INI_MALLOC #include <stdlib.h> #define INI_MALLOC( ctx, size ) ( malloc( size ) ) #define INI_FREE( ctx, ptr ) ( free( ptr ) ) #endif #ifndef INI_MEMCPY #include <string.h> #define INI_MEMCPY( dst, src, cnt ) ( memcpy( dst, src, cnt ) ) #endif #ifndef INI_STRLEN #include <string.h> #define INI_STRLEN( s ) ( strlen( s ) ) #endif #ifndef INI_STRNICMP #ifdef _WIN32 #include <string.h> #define INI_STRNICMP( s1, s2, cnt ) ( strnicmp( s1, s2, cnt ) ) #else #include <string.h> #define INI_STRNICMP( s1, s2, cnt ) ( strncasecmp( s1, s2, cnt ) ) #endif #endif struct ini_internal_section_t { char name[ 32 ]; char* name_large; }; struct ini_internal_property_t { int section; char name[ 32 ]; char* name_large; char value[ 64 ]; char* value_large; }; struct ini_t { struct ini_internal_section_t* sections; int section_capacity; int section_count; struct ini_internal_property_t* properties; int property_capacity; int property_count; void* memctx; }; static int ini_internal_property_index( ini_t const* ini, int section, int property ) { int i; int p; if( ini && section >= 0 && section < ini->section_count ) { p = 0; for( i = 0; i < ini->property_count; ++i ) { if( ini->properties[ i ].section == section ) { if( p == property ) return i; ++p; } } } return INI_NOT_FOUND; } ini_t* ini_create( void* memctx ) { ini_t* ini; ini = (ini_t*) INI_MALLOC( memctx, sizeof( ini_t ) ); ini->memctx = memctx; ini->sections = (struct ini_internal_section_t*) INI_MALLOC( ini->memctx, INITIAL_CAPACITY * sizeof( ini->sections[ 0 ] ) ); ini->section_capacity = INITIAL_CAPACITY; ini->section_count = 1; /* global section */ ini->sections[ 0 ].name[ 0 ] = '\0'; ini->sections[ 0 ].name_large = 0; ini->properties = (struct ini_internal_property_t*) INI_MALLOC( ini->memctx, INITIAL_CAPACITY * sizeof( ini->properties[ 0 ] ) ); ini->property_capacity = INITIAL_CAPACITY; ini->property_count = 0; return ini; } ini_t* ini_load( char const* data, void* memctx ) { ini_t* ini; char const* ptr; int s; char const* start; char const* start2; int l; ini = ini_create( memctx ); ptr = data; if( ptr ) { s = 0; while( *ptr ) { /* trim leading whitespace */ while( *ptr && *ptr <=' ' ) ++ptr; /* done? */ if( !*ptr ) break; /* comment */ else if( *ptr == ';' ) { while( *ptr && *ptr !='\n' ) ++ptr; } /* section */ else if( *ptr == '[' ) { ++ptr; start = ptr; while( *ptr && *ptr !=']' && *ptr != '\n' ) ++ptr; if( *ptr == ']' ) { s = ini_section_add( ini, start, (int)( ptr - start) ); ++ptr; } } /* property */ else { start = ptr; while( *ptr && *ptr !='=' && *ptr != '\n' ) ++ptr; if( *ptr == '=' ) { l = (int)( ptr - start); ++ptr; while( *ptr && *ptr <= ' ' && *ptr != '\n' ) ptr++; start2 = ptr; while( *ptr && *ptr != '\n' ) ++ptr; while( *(--ptr) <= ' ' ) (void)ptr; ptr++; ini_property_add( ini, s, start, l, start2, (int)( ptr - start2) ); } } } } return ini; } int ini_save( ini_t const* ini, char* data, int size ) { int s; int p; int i; int l; char* n; int pos; if( ini ) { pos = 0; for( s = 0; s < ini->section_count; ++s ) { n = ini->sections[ s ].name_large ? ini->sections[ s ].name_large : ini->sections[ s ].name; l = (int) INI_STRLEN( n ); if( l > 0 ) { if( data && pos < size ) data[ pos ] = '['; ++pos; for( i = 0; i < l; ++i ) { if( data && pos < size ) data[ pos ] = n[ i ]; ++pos; } if( data && pos < size ) data[ pos ] = ']'; ++pos; if( data && pos < size ) data[ pos ] = '\n'; ++pos; } for( p = 0; p < ini->property_count; ++p ) { if( ini->properties[ p ].section == s ) { n = ini->properties[ p ].name_large ? ini->properties[ p ].name_large : ini->properties[ p ].name; l = (int) INI_STRLEN( n ); for( i = 0; i < l; ++i ) { if( data && pos < size ) data[ pos ] = n[ i ]; ++pos; } if( data && pos < size ) data[ pos ] = '='; ++pos; n = ini->properties[ p ].value_large ? ini->properties[ p ].value_large : ini->properties[ p ].value; l = (int) INI_STRLEN( n ); for( i = 0; i < l; ++i ) { if( data && pos < size ) data[ pos ] = n[ i ]; ++pos; } if( data && pos < size ) data[ pos ] = '\n'; ++pos; } } if( pos > 0 ) { if( data && pos < size ) data[ pos ] = '\n'; ++pos; } } if( data && pos < size ) data[ pos ] = '\0'; ++pos; return pos; } return 0; } void ini_destroy( ini_t* ini ) { int i; if( ini ) { for( i = 0; i < ini->property_count; ++i ) { if( ini->properties[ i ].value_large ) INI_FREE( ini->memctx, ini->properties[ i ].value_large ); if( ini->properties[ i ].name_large ) INI_FREE( ini->memctx, ini->properties[ i ].name_large ); } for( i = 0; i < ini->section_count; ++i ) if( ini->sections[ i ].name_large ) INI_FREE( ini->memctx, ini->sections[ i ].name_large ); INI_FREE( ini->memctx, ini->properties ); INI_FREE( ini->memctx, ini->sections ); INI_FREE( ini->memctx, ini ); } } int ini_section_count( ini_t const* ini ) { if( ini ) return ini->section_count; return 0; } char const* ini_section_name( ini_t const* ini, int section ) { if( ini && section >= 0 && section < ini->section_count ) return ini->sections[ section ].name_large ? ini->sections[ section ].name_large : ini->sections[ section ].name; return NULL; } int ini_property_count( ini_t const* ini, int section ) { int i; int count; if( ini ) { count = 0; for( i = 0; i < ini->property_count; ++i ) { if( ini->properties[ i ].section == section ) ++count; } return count; } return 0; } char const* ini_property_name( ini_t const* ini, int section, int property ) { int p; if( ini && section >= 0 && section < ini->section_count ) { p = ini_internal_property_index( ini, section, property ); if( p != INI_NOT_FOUND ) return ini->properties[ p ].name_large ? ini->properties[ p ].name_large : ini->properties[ p ].name; } return NULL; } char const* ini_property_value( ini_t const* ini, int section, int property ) { int p; if( ini && section >= 0 && section < ini->section_count ) { p = ini_internal_property_index( ini, section, property ); if( p != INI_NOT_FOUND ) return ini->properties[ p ].value_large ? ini->properties[ p ].value_large : ini->properties[ p ].value; } return NULL; } int ini_find_section( ini_t const* ini, char const* name, int name_length ) { int i; if( ini && name ) { if( name_length <= 0 ) name_length = (int) INI_STRLEN( name ); for( i = 0; i < ini->section_count; ++i ) { char const* const other = ini->sections[ i ].name_large ? ini->sections[ i ].name_large : ini->sections[ i ].name; if( INI_STRNICMP( name, other, name_length ) == 0 ) return i; } } return INI_NOT_FOUND; } int ini_find_property( ini_t const* ini, int section, char const* name, int name_length ) { int i; int c; if( ini && name && section >= 0 && section < ini->section_count) { if( name_length <= 0 ) name_length = (int) INI_STRLEN( name ); c = 0; for( i = 0; i < ini->property_capacity; ++i ) { if( ini->properties[ i ].section == section ) { char const* const other = ini->properties[ i ].name_large ? ini->properties[ i ].name_large : ini->properties[ i ].name; if( INI_STRNICMP( name, other, name_length ) == 0 ) return c; ++c; } } } return INI_NOT_FOUND; } int ini_section_add( ini_t* ini, char const* name, int length ) { struct ini_internal_section_t* new_sections; if( ini && name ) { if( length <= 0 ) length = (int) INI_STRLEN( name ); if( ini->section_count >= ini->section_capacity ) { ini->section_capacity *= 2; new_sections = (struct ini_internal_section_t*) INI_MALLOC( ini->memctx, ini->section_capacity * sizeof( ini->sections[ 0 ] ) ); INI_MEMCPY( new_sections, ini->sections, ini->section_count * sizeof( ini->sections[ 0 ] ) ); INI_FREE( ini->memctx, ini->sections ); ini->sections = new_sections; } ini->sections[ ini->section_count ].name_large = 0; if( length + 1 >= sizeof( ini->sections[ 0 ].name ) ) { ini->sections[ ini->section_count ].name_large = (char*) INI_MALLOC( ini->memctx, (size_t) length + 1 ); INI_MEMCPY( ini->sections[ ini->section_count ].name_large, name, (size_t) length ); ini->sections[ ini->section_count ].name_large[ length ] = '\0'; } else { INI_MEMCPY( ini->sections[ ini->section_count ].name, name, (size_t) length ); ini->sections[ ini->section_count ].name[ length ] = '\0'; } return ini->section_count++; } return INI_NOT_FOUND; } void ini_property_add( ini_t* ini, int section, char const* name, int name_length, char const* value, int value_length ) { struct ini_internal_property_t* new_properties; if( ini && name && section >= 0 && section < ini->section_count ) { if( name_length <= 0 ) name_length = (int) INI_STRLEN( name ); if( value_length <= 0 ) value_length = (int) INI_STRLEN( value ); if( ini->property_count >= ini->property_capacity ) { ini->property_capacity *= 2; new_properties = (struct ini_internal_property_t*) INI_MALLOC( ini->memctx, ini->property_capacity * sizeof( ini->properties[ 0 ] ) ); INI_MEMCPY( new_properties, ini->properties, ini->property_count * sizeof( ini->properties[ 0 ] ) ); INI_FREE( ini->memctx, ini->properties ); ini->properties = new_properties; } ini->properties[ ini->property_count ].section = section; ini->properties[ ini->property_count ].name_large = 0; ini->properties[ ini->property_count ].value_large = 0; if( name_length + 1 >= sizeof( ini->properties[ 0 ].name ) ) { ini->properties[ ini->property_count ].name_large = (char*) INI_MALLOC( ini->memctx, (size_t) name_length + 1 ); INI_MEMCPY( ini->properties[ ini->property_count ].name_large, name, (size_t) name_length ); ini->properties[ ini->property_count ].name_large[ name_length ] = '\0'; } else { INI_MEMCPY( ini->properties[ ini->property_count ].name, name, (size_t) name_length ); ini->properties[ ini->property_count ].name[ name_length ] = '\0'; } if( value_length + 1 >= sizeof( ini->properties[ 0 ].value ) ) { ini->properties[ ini->property_count ].value_large = (char*) INI_MALLOC( ini->memctx, (size_t) value_length + 1 ); INI_MEMCPY( ini->properties[ ini->property_count ].value_large, value, (size_t) value_length ); ini->properties[ ini->property_count ].value_large[ value_length ] = '\0'; } else { INI_MEMCPY( ini->properties[ ini->property_count ].value, value, (size_t) value_length ); ini->properties[ ini->property_count ].value[ value_length ] = '\0'; } ++ini->property_count; } } void ini_section_remove( ini_t* ini, int section ) { int p; if( ini && section >= 0 && section < ini->section_count ) { if( ini->sections[ section ].name_large ) INI_FREE( ini->memctx, ini->sections[ section ].name_large ); for( p = ini->property_count - 1; p >= 0; --p ) { if( ini->properties[ p ].section == section ) { if( ini->properties[ p ].value_large ) INI_FREE( ini->memctx, ini->properties[ p ].value_large ); if( ini->properties[ p ].name_large ) INI_FREE( ini->memctx, ini->properties[ p ].name_large ); ini->properties[ p ] = ini->properties[ --ini->property_count ]; } } ini->sections[ section ] = ini->sections[ --ini->section_count ]; for( p = 0; p < ini->property_count; ++p ) { if( ini->properties[ p ].section == ini->section_count ) ini->properties[ p ].section = section; } } } void ini_property_remove( ini_t* ini, int section, int property ) { int p; if( ini && section >= 0 && section < ini->section_count ) { p = ini_internal_property_index( ini, section, property ); if( p != INI_NOT_FOUND ) { if( ini->properties[ p ].value_large ) INI_FREE( ini->memctx, ini->properties[ p ].value_large ); if( ini->properties[ p ].name_large ) INI_FREE( ini->memctx, ini->properties[ p ].name_large ); ini->properties[ p ] = ini->properties[ --ini->property_count ]; return; } } } void ini_section_name_set( ini_t* ini, int section, char const* name, int length ) { if( ini && name && section >= 0 && section < ini->section_count ) { if( length <= 0 ) length = (int) INI_STRLEN( name ); if( ini->sections[ section ].name_large ) INI_FREE( ini->memctx, ini->sections[ section ].name_large ); ini->sections[ section ].name_large = 0; if( length + 1 >= sizeof( ini->sections[ 0 ].name ) ) { ini->sections[ section ].name_large = (char*) INI_MALLOC( ini->memctx, (size_t) length + 1 ); INI_MEMCPY( ini->sections[ section ].name_large, name, (size_t) length ); ini->sections[ section ].name_large[ length ] = '\0'; } else { INI_MEMCPY( ini->sections[ section ].name, name, (size_t) length ); ini->sections[ section ].name[ length ] = '\0'; } } } void ini_property_name_set( ini_t* ini, int section, int property, char const* name, int length ) { int p; if( ini && name && section >= 0 && section < ini->section_count ) { if( length <= 0 ) length = (int) INI_STRLEN( name ); p = ini_internal_property_index( ini, section, property ); if( p != INI_NOT_FOUND ) { if( ini->properties[ p ].name_large ) INI_FREE( ini->memctx, ini->properties[ p ].name_large ); ini->properties[ ini->property_count ].name_large = 0; if( length + 1 >= sizeof( ini->properties[ 0 ].name ) ) { ini->properties[ p ].name_large = (char*) INI_MALLOC( ini->memctx, (size_t) length + 1 ); INI_MEMCPY( ini->properties[ p ].name_large, name, (size_t) length ); ini->properties[ p ].name_large[ length ] = '\0'; } else { INI_MEMCPY( ini->properties[ p ].name, name, (size_t) length ); ini->properties[ p ].name[ length ] = '\0'; } } } } void ini_property_value_set( ini_t* ini, int section, int property, char const* value, int length ) { int p; if( ini && value && section >= 0 && section < ini->section_count ) { if( length <= 0 ) length = (int) INI_STRLEN( value ); p = ini_internal_property_index( ini, section, property ); if( p != INI_NOT_FOUND ) { if( ini->properties[ p ].value_large ) INI_FREE( ini->memctx, ini->properties[ p ].value_large ); ini->properties[ ini->property_count ].value_large = 0; if( length + 1 >= sizeof( ini->properties[ 0 ].value ) ) { ini->properties[ p ].value_large = (char*) INI_MALLOC( ini->memctx, (size_t) length + 1 ); INI_MEMCPY( ini->properties[ p ].value_large, value, (size_t) length ); ini->properties[ p ].value_large[ length ] = '\0'; } else { INI_MEMCPY( ini->properties[ p ].value, value, (size_t) length ); ini->properties[ p ].value[ length ] = '\0'; } } } } #endif /* INI_IMPLEMENTATION */ /* contributors: Randy Gaul (copy-paste bug in ini_property_value_set) Branimir Karadzic (INI_STRNICMP bugfix) revision history: 1.2 using strnicmp for correct length compares, fixed copy-paste bug in ini_property_value_set 1.1 customization, added documentation, cleanup 1.0 first publicly released version */ /* ------------------------------------------------------------------------------ This software is available under 2 licenses - you may choose the one you like. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2015 Mattias Gustavsson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ 07070100000010000081A400000000000000000000000164751F57000006F8000000000000000000000000000000000000001E00000000waynergy-0.16+3/include/log.h#pragma once #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <stdbool.h> #include <stdarg.h> #include <errno.h> enum logLevel { LOG_NONE = 0, LOG_ERR, LOG_WARN, LOG_INFO, LOG_DBG, LOG_DBGSYN, LOG__INVALID, /* also serves as count */ }; enum logLevel logLevelFromString(const char *s); bool logInit(enum logLevel level, char *path); void logOutV(enum logLevel level, const char *fmt, va_list ap); void logOut(enum logLevel level, const char *fmt, ...); /* standard log functions */ void logErr(const char *fmt, ...); void logWarn(const char *fmt, ...); void logInfo(const char *fmt, ...); void logDbg(const char *fmt, ...); void logDbgSyn(const char *fmt, ...); #define logPErr(msg) do { \ logErr("%s: %s: %s", __func__, (msg), strerror(errno)); \ } while (0) #define logPWarn(msg) do { \ logWarn("%s: %s: %s", __func__, (msg), strerror(errno)); \ } while (0) #define logPInfo(msg) do { \ logInfo("%s: %s: %s", __func__, (msg), strerror(errno)); \ } while (0) #define logPDbg(msg) do { \ logDbg("%s: %s: %s", __func__, (msg), strerror(errno)); \ } while (0) #define logPDbgSyn(msg) do { \ logDbgSyn("%s: %s: %s", __func__, (msg), strerror(errno)); \ } while (0) void logClose(void); /* Signal-safe logging * * - Start with logOutSigStart(YOU_LEVEL) * - Use any combination of the logOutSigTYPE functions * - End with logOutSigEnd() */ void logOutSigStart(enum logLevel level); void logOutSigEnd(enum logLevel level); void LogOutSigChar(enum logLevel level, char c); void logOutSigStr(enum logLevel level, const char *str); void logOutSigI32(enum logLevel level, int32_t val); void logOutSigU32(enum logLevel level, uint32_t val); /* logOutSig retains normal behavior */ void logOutSig(enum logLevel level, const char *msg); 07070100000011000081A400000000000000000000000164751F57000000BA000000000000000000000000000000000000002400000000waynergy-0.16+3/include/meson.buildver_h = vcs_tag( command: ['git', 'describe', '--tags'], input: 'ver.h.in', output: 'ver.h', replace_string: '@VCS_DESC@' ) ver_dep = declare_dependency( sources: [ ver_h ], ) 07070100000012000081A400000000000000000000000164751F57000004FE000000000000000000000000000000000000001E00000000waynergy-0.16+3/include/net.h#pragma once #include "uSynergy.h" #include "wayland.h" #include "fdio_full.h" #include "clip.h" #include <stdlib.h> #include <stdbool.h> #include <unistd.h> #include <sys/types.h> #include <fcntl.h> #include <poll.h> #include <limits.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/stat.h> #include <netdb.h> #include <time.h> #include <signal.h> #include "sig.h" #include <sys/un.h> #define CLIP_UPDATER_FD_COUNT 8 extern int clipMonitorFd; extern struct sockaddr_un clipMonitorAddr; extern pid_t clipMonitorPid[2]; enum net_pollfd_id { POLLFD_SYN, POLLFD_WL, POLLFD_CLIP_MON, POLLFD_CLIP_UPDATER, POLLFD_COUNT = POLLFD_CLIP_UPDATER + CLIP_UPDATER_FD_COUNT }; #define POLLFD_COUNT POLLFD_CLIP_UPDATER + CLIP_UPDATER_FD_COUNT extern struct pollfd netPollFd[POLLFD_COUNT]; struct synNetContext { uSynergyContext *syn_ctx; bool tls; bool tls_tofu; struct tls *tls_ctx; char *tls_hash; char *host; char *port; int fd; }; bool synNetInit(struct synNetContext *net_ctx, uSynergyContext *syn_ctx, const char *host, const char *port, bool tls, bool tofu); void netPollInit(void); void netPoll(struct synNetContext *snet_ctx, struct wlContext *wl_ctx); bool synNetDisconnect(struct synNetContext *snet_ctx); 07070100000013000081A400000000000000000000000164751F5700000276000000000000000000000000000000000000001D00000000waynergy-0.16+3/include/os.h#pragma once #include <stdbool.h> #include <sys/mman.h> #include <sys/types.h> extern char *osConfigPathOverride; extern int osGetAnonFd(void); extern char *osGetRuntimePath(char *name); extern char *osGetHomeConfigPath(char *name); /* check if a file exists */ extern bool osFileExists(const char *path); /* create parents of a given path if they don't already exist */ extern bool osMakeParentDir(const char *path, mode_t mode); /* drop setuid and setgid privileges; aborts on failure, as it should. */ extern void osDropPriv(void); /* determine the name of the other end of a socket */ extern char *osGetPeerProcName(int fd); 07070100000014000081A400000000000000000000000164751F57000004DA000000000000000000000000000000000000001E00000000waynergy-0.16+3/include/sig.h#pragma once #include <signal.h> #include <string.h> #include <stdbool.h> #include <sys/wait.h> #include "wayland.h" #include "log.h" #include "net.h" enum sigExitStatus { SES_SUCCESS = EXIT_SUCCESS, /* everything normal */ SES_FAILURE = EXIT_FAILURE, /* general failure */ SES_ERROR_SYN, /* synergy protocol error */ SES_ERROR_WL, /* wayland error. DO NOT attempt cleanup functions related to wayland. */ }; extern volatile sig_atomic_t sigDoExit; extern volatile sig_atomic_t sigDoRestart; void Exit(enum sigExitStatus status); void Restart(enum sigExitStatus status); /* exit or restart, for situations like wayland protocol errors beyond our * control that don't necessarily mean the compositor no longer exists or the * session actually ended */ void ExitOrRestart(enum sigExitStatus status); void sigHandleInit(char **argv); void sigWaitSIGCHLD(bool state); static inline bool sigHandleCheck(void) { return sigDoExit || sigDoRestart; } static inline void sigHandleRun(void) { if (sigDoExit) { logInfo("Exit signal %s received, exiting...", strsignal(sigDoExit)); Exit(SES_SUCCESS); } if (sigDoRestart) { logInfo("Restart signal %s received, restarting...", strsignal(sigDoRestart)); Restart(SES_SUCCESS); } } 07070100000015000081A400000000000000000000000164751F57000039D3000000000000000000000000000000000000001F00000000waynergy-0.16+3/include/sopt.h/* sopt -- simple option parsing * * Version 1.9 * * Copyright 2021 Ryan Farley <ryan.farley@gmx.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef SOPTH_INCLUDE #define SOPTH_INCLUDE #if defined(__GNUC__) #define SHL_UNUSED __attribute__((unused)) #else #define SHL_UNUSED #endif #include <stdio.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <float.h> #include <limits.h> #include <inttypes.h> #include <errno.h> enum sopt_argtype { SOPT_ARGTYPE_NULL, SOPT_ARGTYPE_STR, SOPT_ARGTYPE_CHAR, SOPT_ARGTYPE_SCHAR, SOPT_ARGTYPE_UCHAR, SOPT_ARGTYPE_SHORT, SOPT_ARGTYPE_USHORT, SOPT_ARGTYPE_INT, SOPT_ARGTYPE_UINT, SOPT_ARGTYPE_LONG, SOPT_ARGTYPE_ULONG, SOPT_ARGTYPE_LONGLONG, SOPT_ARGTYPE_ULONGLONG, SOPT_ARGTYPE_FLOAT, SOPT_ARGTYPE_DBL, SOPT_ARGTYPE_LONGDBL, }; /* By setting SOPT_INVAL to '?', and terminating with it, we ensure that -- * should a simple search through the option array yield no match, the final * element will have the value '?'*/ #define SOPT_INVAL '?' #define SOPT_AFTER -1 struct sopt { /* Option ID. Should be unique. Indicates the following, based on * value: * alphanumeric: * a short option, i.e. triggered with -o for 'o' * SOPT_INVAL: * signals end of option array * SOPT_AFTER: * specifies a non-option parameter documented in * usage text * other: * identifies long option. Ideally, should be > UCHAR_MAX * for this purpose, to ensure no collisions with * potential short options. */ int val; /* Long option name, if not null. i.e. --long-option would be "long-option" */ const char *name; /* type of parameter, if not null */ enum sopt_argtype argtype; /* Parameter, if not null. Or name of unparsed argument, if SOPT_AFTER */ const char *arg; /* Description for usage text */ const char *desc; }; union sopt_arg { char *str; signed char sc; unsigned char uc; char c; short s; unsigned short us; int i; unsigned int ui; long l; unsigned long ul; long long ll; unsigned long long ull; float f; double d; long double ld; }; /* Initializer macros, for static option array definitions. Use like: * struct sopt optspec[] = { * SOPT...(...), * SOPT...(...), * ... * SOPT_INIT_END * }; */ /* Define a simple option like -o (takes no parameter) */ #define SOPT_INIT(opt, desc) { (opt), NULL, SOPT_ARGTYPE_NULL, NULL, (desc) } /* Same as above, but with a long option name given */ #define SOPT_INITL(opt, name, desc) { (opt), (name), SOPT_ARGTYPE_NULL, NULL, (desc) } /* Define an option with an argument, i.e. -o foo */ #define SOPT_INIT_ARG(opt, type, param, desc) { (opt), NULL, (type), (param), (desc)} /* Same as above, but with long option name given */ #define SOPT_INIT_ARGL(opt, name, type, param, desc) {(opt), (name), (type), (param), (desc)} /* Define an unparsed argument, i.e. after -- */ #define SOPT_INIT_AFTER(str, desc) {SOPT_AFTER, NULL, SOPT_ARGTYPE_NULL, (str), (desc)} /* Terminate the option array. Must be last element. */ #define SOPT_INIT_END {SOPT_INVAL, NULL, SOPT_ARGTYPE_NULL, NULL, NULL} #define SOPT_VALID(opt) ((opt)->val != SOPT_INVAL) /*simple helper -- print out usage example*/ SHL_UNUSED static inline void sopt_usage_printopt(struct sopt *opt) { bool shortopt, longopt; shortopt = (((unsigned char)opt->val) == opt->val) && isalnum(opt->val); longopt = opt->name; if (!(shortopt || longopt)) { return; /*borked, yo*/ } if (shortopt) { fprintf(stderr, "-%c", opt->val); } if (shortopt && longopt) { fprintf(stderr, "|"); } if (longopt) { fprintf(stderr, "--%s", opt->name); } if (opt->arg) { fprintf(stderr, " %s", opt->arg); } } /*print out usage message * Formatted as such: * $name: $desc * * USAGE: $name [-o|--opt] -- afteropt * -o|--opt: * option description here */ SHL_UNUSED static void sopt_usage(struct sopt *optspec, const char *name, const char *desc) { struct sopt *opt; bool afteropt = false; if (!(name && desc && optspec)) return; fprintf(stderr, "%s: %s\n\nUSAGE: %s", name, desc, name); for (opt = optspec; SOPT_VALID(opt); ++opt) { if (opt->val == SOPT_AFTER) { afteropt = true; continue; } fprintf(stderr, " ["); sopt_usage_printopt(opt); fprintf(stderr, "]"); } if (afteropt) { fprintf(stderr, " --"); for (opt = optspec; SOPT_VALID(opt); ++opt) { if (opt->val == SOPT_AFTER) fprintf(stderr, " %s", opt->arg); } } fprintf(stderr, "\n\t"); /* now we get to the descriptions */ for (opt = optspec; SOPT_VALID(opt); ++opt) { if (opt->val == SOPT_AFTER) continue; sopt_usage_printopt(opt); fprintf(stderr, ":\n\t\t%s\n\t", opt->desc); } if (afteropt) { for (opt = optspec; SOPT_VALID(opt); ++opt) { if (opt->val == SOPT_AFTER) fprintf(stderr, "%s:\n\t\t%s\n\t", opt->arg, opt->desc); } } /*make it prettier*/ fprintf(stderr, "\n"); } /*print out usage message, but with static storage of parameters. * If 'set' is true, other parameters are stored, and the function returns. * If 'set' is false, other parameters are ignored, and sopt_usage() is called * with stored values used */ SHL_UNUSED static void sopt_usage_static(struct sopt *opt, const char *name, const char *desc, bool set) { static const char *name_s, *desc_s; static struct sopt *opt_s; if (set) { name_s = name; desc_s = desc; opt_s = opt; } else { sopt_usage(opt_s, name_s, desc_s); } } /* for convenience, set the static usage values for future use */ SHL_UNUSED static inline void sopt_usage_set(struct sopt *opt, const char *name, const char *desc) { sopt_usage_static(opt, name, desc, true); } /* for convenience, call sopt_usage_static with stored parameters */ SHL_UNUSED static inline void sopt_usage_s(void) { sopt_usage_static(NULL, NULL, NULL, false); } SHL_UNUSED static bool sopt_argconv_int(const char *s, intmax_t *out) { char *endptr; errno = 0; *out = strtoimax(s, &endptr, 0); if (endptr == s) return false; if (errno) return false; return true; } SHL_UNUSED static bool sopt_argconv_uint(const char *s, uintmax_t *out) { char *endptr; /* we do not want any negative values here */ for (;*s && isspace(*s); ++s); if (!*s) return false; if (*s == '-') return false; errno = 0; *out = strtoumax(s, &endptr, 0); if (endptr == s) return false; if (errno) return false; return true; } SHL_UNUSED static bool sopt_argconv_ldbl(const char *s, long double *out) { char *endptr; errno = 0; *out = strtold(s, &endptr); if (endptr == s) return false; if (errno) return false; return true; } SHL_UNUSED static void sopt_perror(struct sopt *opt, const char *msg) { fprintf(stderr, "Error parsing argument "); if (isalnum(opt->val)) { fprintf(stderr, "-%c", opt->val); if (opt->name) { fprintf(stderr, "/"); } } if (opt->name) { fprintf(stderr, "--%s", opt->name); } fprintf(stderr, ": %s\n", msg); } /* replacement for getopt() * argc: * argc, obviously * argv: * argv, obviously * opt: * array of possible option structures * cpos: * Stores the current position in a combined option, i.e. -abcd. * *cpos MUST BE ZERO ON FIRST CALL * optind: * Current position in the argv array. At end of processing, will point * to first non-parsed argument. * *optind MUST BE ZERO ON FIRST CALL * arg: * Pointer to an sopt_arg union to contain the parsed argument. * * RETURNS: * '?' if unknown or invalid input given, * opt->val for the found option otherwise. */ SHL_UNUSED static int sopt_getopt(int argc, char **argv, struct sopt *opt, int *cpos, int *optind, union sopt_arg *arg) { char *arg_str; intmax_t arg_int; bool arg_int_valid; uintmax_t arg_uint; bool arg_uint_valid; long double arg_float; bool arg_float_valid; if (!(opt && cpos &&argv && optind && arg && argc)) return -1; /* handle the case of combined options */ if (*cpos) goto shortopt; /*otherwise proceed normally*/ if (++*optind >= argc) return -1; if (argv[*optind][0] != '-') return -1; if (argv[*optind][1] == '-') { /*end of options*/ if (!argv[*optind][2]) { ++*optind; //optind points at next non-option return -1; } /*or a long option*/ for (; SOPT_VALID(opt); ++opt) { /*don't want to be passing NULL to strcmp, now do we?*/ if (opt->name) { if (!strcmp(opt->name, argv[*optind] + 2)) break; } } } else { shortopt: /* if we're not in a combined option, start at first option * character */ if (!*cpos) *cpos = 1; /* find our shortopt */ for (; SOPT_VALID(opt); ++opt) { if (opt->val == argv[*optind][*cpos]) break; } /* check if we're in a combined option */ if (argv[*optind][++*cpos]) { /* make sure that we're not expecting a param */ if (opt->arg) return SOPT_INVAL; } else { *cpos = 0; } } if (opt->arg) { if (!(arg_str = argv[++*optind])) { sopt_perror(opt, "Missing required argument"); return SOPT_INVAL; } arg_int_valid = sopt_argconv_int(arg_str, &arg_int); arg_uint_valid = sopt_argconv_uint(arg_str, &arg_uint); arg_float_valid = sopt_argconv_ldbl(arg_str, &arg_float); switch (opt->argtype) { case SOPT_ARGTYPE_STR: arg->str = arg_str; break; /* Signed integers */ #if CHAR_MIN /* because character types are implementation-defined with regard to sign */ case SOPT_ARGTYPE_CHAR: #endif case SOPT_ARGTYPE_SCHAR: if (!arg_int_valid) { sopt_perror(opt, "Argument is not an integer"); return SOPT_INVAL; } if (!(arg_int >= SCHAR_MIN && arg_int <= SCHAR_MAX)) { sopt_perror(opt, "Argument out of range"); return SOPT_INVAL; } arg->sc = arg_int; break; case SOPT_ARGTYPE_SHORT: if (!arg_int_valid) { sopt_perror(opt, "Argument is not an integer"); return SOPT_INVAL; } if (!(arg_int >= SHRT_MIN && arg_int <= SHRT_MAX)) { sopt_perror(opt, "Argument out of range"); return SOPT_INVAL; } arg->s = arg_int; break; case SOPT_ARGTYPE_INT: if (!arg_int_valid) { sopt_perror(opt, "Argument is not an integer"); return SOPT_INVAL; } if (!(arg_int >= INT_MIN && arg_int <= INT_MAX)) { sopt_perror(opt, "Argument out of range"); return SOPT_INVAL; } arg->i = arg_int; break; case SOPT_ARGTYPE_LONG: if (!arg_int_valid) { sopt_perror(opt, "Argument is not an integer"); return SOPT_INVAL; } if (!(arg_int >= LONG_MIN && arg_int <= LONG_MAX)) { sopt_perror(opt, "Argument out of range"); return SOPT_INVAL; } arg->l = arg_int; break; case SOPT_ARGTYPE_LONGLONG: if (!arg_int_valid) { sopt_perror(opt, "Argument is not an integer"); return SOPT_INVAL; } if (!(arg_int >= LLONG_MIN && arg_int <= LLONG_MAX)) { sopt_perror(opt, "Argument out of range"); return SOPT_INVAL; } arg->ll = arg_int; break; /* Unsigned integers */ #if !CHAR_MIN /* because implementation-defined char signedness */ case SOPT_ARGTYPE_CHAR: #endif case SOPT_ARGTYPE_UCHAR: if (!arg_uint_valid) { sopt_perror(opt, "Argument is not an unsigned integer"); return SOPT_INVAL; } if (arg_uint > UCHAR_MAX) { sopt_perror(opt, "Argument out of range"); return SOPT_INVAL; } arg->uc = arg_uint; break; case SOPT_ARGTYPE_USHORT: if (!arg_uint_valid) { sopt_perror(opt, "Argument is not an unsigned integer"); return SOPT_INVAL; } if (arg_uint > USHRT_MAX) { sopt_perror(opt, "Argument out of range"); return SOPT_INVAL; } arg->us = arg_uint; break; case SOPT_ARGTYPE_UINT: if (!arg_uint_valid) { sopt_perror(opt, "Argument is not an unsigned integer"); return SOPT_INVAL; } if (arg_uint > UINT_MAX) { sopt_perror(opt, "Argument out of range"); return SOPT_INVAL; } arg->ui = arg_uint; break; case SOPT_ARGTYPE_ULONG: if (!arg_uint_valid) { sopt_perror(opt, "Argument is not an unsigned integer"); return SOPT_INVAL; } if (arg_uint > ULONG_MAX) { sopt_perror(opt, "Argument out of range"); return SOPT_INVAL; } arg->ul = arg_uint; break; case SOPT_ARGTYPE_ULONGLONG: if (!arg_uint_valid) { sopt_perror(opt, "Argument is not an unsigned integer"); return SOPT_INVAL; } if (arg_uint > ULLONG_MAX) { sopt_perror(opt, "Argument out of range"); return SOPT_INVAL; } arg->ull = arg_uint; break; /* Floats */ case SOPT_ARGTYPE_FLOAT: if (!arg_float_valid) { sopt_perror(opt, "Argument is not a valid floating-point number"); return SOPT_INVAL; } arg->f = arg_float; break; case SOPT_ARGTYPE_DBL: if (!arg_float_valid) { sopt_perror(opt, "Argument is not a valid floating-point number"); return SOPT_INVAL; } arg->d = arg_float; break; case SOPT_ARGTYPE_LONGDBL: if (!arg_float_valid) { sopt_perror(opt, "Argument is not a valid floating-point number"); return SOPT_INVAL; } arg->ld = arg_float; break; default: return SOPT_INVAL; } } return opt->val; } /* A replacement for getopt() that allows the use of static allocation. * NOT THREAD SAFE * * Parameters are same as sopt_getopt(), with new semantics: * * opt: * If NULL, nothing is done save for reinitialization to a clean state * If different from the last call, reset only optind and cpos * cpos: * If NULL, a static allocation is used. Reset whenever opt changes. * optind: * If NULL, a static allocation is used. Reset whenever opt changes. */ SHL_UNUSED static int sopt_getopt_s(int argc, char **argv, struct sopt *opt, int *cpos, int *optind, union sopt_arg *arg) { static struct sopt *opt_last = NULL; static int cpos_s = 0; static int optind_s = 0; if (!cpos) cpos = &cpos_s; if (!optind) optind = &optind_s; if (opt != opt_last) { *cpos = 0; *optind = 0; opt_last = opt; } if (!opt) return SOPT_INVAL; return sopt_getopt(argc, argv, opt, cpos, optind, arg); } #undef SHL_UNUSED #endif 07070100000016000081A400000000000000000000000164751F5700001205000000000000000000000000000000000000001E00000000waynergy-0.16+3/include/ssb.h/* simple string builder * * Version 1.3 * * Copyright 2023 Ryan Farley <ryan.farley@gmx.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef SSB_H_INC #define SSB_H_INC #if defined(__GNUC__) #define SHL_UNUSED __attribute__((unused)) #else #define SHL_UNUSED #endif #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <stdbool.h> #include <errno.h> /* defines the length used for ssb_readfile, if SSB_GROW_EXACT is used */ #if !defined SSB_READ_LEN #define SSB_READ_LEN 4096 #endif enum ssb_grow { /* grow by a factor of 1.5 */ SSB_GROW_1_5 = 0, /* grow by a factor of 2.0 */ SSB_GROW_2_0, /* grow by exactly the amount needed each time */ SSB_GROW_EXACT }; struct ssb { enum ssb_grow grow; char *buf; size_t size; size_t pos; }; SHL_UNUSED static bool ssb_truncate(struct ssb *s, size_t newsize) { char *realloc_buf; if (!(realloc_buf = realloc(s->buf, newsize + 1))) { return false; } s->buf = realloc_buf; if (s->pos >= newsize) { s->buf[newsize] = '\0'; } s->size = newsize + 1; return true; } SHL_UNUSED static bool ssb_grow_min(struct ssb *s, size_t min) { size_t newsize; newsize = min += s->size; switch (s->grow) { case SSB_GROW_1_5: newsize = s->size + (s->size / 2); break; case SSB_GROW_2_0: newsize = s->size * 2; break; default: break; } return ssb_truncate(s, (newsize >= min ? newsize : min)); } SHL_UNUSED static int ssb_vprintf(struct ssb *s, const char *fmt, va_list ap) { va_list sizeap; int size; if (!s) { errno = EINVAL; return -1; } va_copy(sizeap, ap); if ((size = vsnprintf(s->buf + s->pos, s->size - s->pos, fmt, sizeap)) >= s->size - s->pos) { if (!ssb_grow_min(s, size)) { return -1; } vsnprintf(s->buf + s->pos, s->size - s->pos, fmt, ap); } va_end(sizeap); s->pos += size; return 0; } SHL_UNUSED static int ssb_printf(struct ssb *s, const char *fmt, ...) { int ret; va_list ap; va_start(ap, fmt); ret = ssb_vprintf(s, fmt, ap); va_end(ap); return ret; } SHL_UNUSED static void ssb_rewind(struct ssb *s) { s->pos = 0; if (s->buf) { *(s->buf) = '\0'; } } SHL_UNUSED static void ssb_free(struct ssb *s) { s->pos = 0; if (s->buf) { free(s->buf); } s->size = 0; s->buf = NULL; } /* add a single character to the buffer */ SHL_UNUSED static bool ssb_addc(struct ssb *s, unsigned char c) { if (s->size - s->pos < 2) { if (!ssb_grow_min(s, 1)) { return false; } } s->buf[s->pos++] = c; s->buf[s->pos] = '\0'; return true; } /* analogous to getdelim -- returns true if anything is successfully read */ SHL_UNUSED static bool ssb_getdelim(struct ssb *s, int delim, FILE *f) { int c; size_t old_pos = s->pos; while ((c = getc(f)) != EOF) { if (!ssb_addc(s, c)) { return false; } if (c == delim) { return true; } } return s->pos - old_pos; } /* a bit like getline -- return true if anything is successfully read */ SHL_UNUSED static bool ssb_getline(struct ssb *s, FILE *f) { return ssb_getdelim(s, '\n', f); } /* read an entire file into a buffer */ SHL_UNUSED static bool ssb_readfile(struct ssb *s, FILE *f) { size_t read_count = 0; do { s->pos += read_count; if (s->size - s->pos <= 2) { if (!ssb_grow_min(s, SSB_READ_LEN)) { return false; } } } while ((read_count = fread(s->buf + s->pos, 1, s->size - s->pos - 1, f))); /* ideally the result would be NUL terminated, but we cannot be sure */ s->buf[s->pos] = '\0'; return feof(f); } /* x variants -- can never fail! */ SHL_UNUSED static void ssb_xvprintf(struct ssb *s, const char *fmt, va_list ap) { if (ssb_vprintf(s, fmt, ap)) abort(); } SHL_UNUSED static void ssb_xprintf(struct ssb *s, const char *fmt, ...) { va_list ap; va_start(ap, fmt); ssb_xvprintf(s, fmt, ap); va_end(ap); } SHL_UNUSED static void ssb_xtruncate(struct ssb *s, size_t newsize) { if (!ssb_truncate(s, newsize)) abort(); } #undef SHL_UNUSED #endif 07070100000017000081A400000000000000000000000164751F5700000402000000000000000000000000000000000000001E00000000waynergy-0.16+3/include/ssp.h#pragma once /* safe stream parsing -- because the existing network code here is a total * clusterfuck */ #include <stdint.h> #include <stdio.h> #include <stdbool.h> struct sspBuf { const unsigned char *data; size_t pos; size_t len; }; extern bool sspSeek(struct sspBuf *buf, size_t len); extern bool sspNetInt(struct sspBuf *buf, void *res, size_t len); extern bool sspMemMove(void *dest, struct sspBuf *buf, size_t len); static inline bool sspChar(struct sspBuf *buf, char *res) { return sspNetInt(buf, res, 1); } static inline bool sspUChar(struct sspBuf *buf, unsigned char *res) { return sspNetInt(buf, res, 1); } static inline bool sspNet16(struct sspBuf *buf, int16_t *res) { return sspNetInt(buf, res, 2); } static inline bool sspNetU16(struct sspBuf *buf, uint16_t *res) { return sspNetInt(buf, res, 2); } static inline bool sspNet32(struct sspBuf *buf, int32_t *res) { return sspNetInt(buf, res, 4); } static inline bool sspNetU32(struct sspBuf *buf, uint32_t *res) { return sspNetInt(buf, res, 4); } 07070100000018000081A400000000000000000000000164751F570000442F000000000000000000000000000000000000002300000000waynergy-0.16+3/include/uSynergy.h#pragma once /* uSynergy client -- Interface for the embedded Synergy client library Heavily modified from the original version Copyright (C) 2012-2016 Symless Ltd. Copyright (c) 2012 Alex Evans This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include <stdio.h> #include <stdint.h> #include <stdbool.h> #include "log.h" #ifdef __cplusplus extern "C" { #endif //--------------------------------------------------------------------------------------------------------------------- // Configuration //--------------------------------------------------------------------------------------------------------------------- /** @brief Determine endianness **/ #if defined(USYNERGY_LITTLE_ENDIAN) && defined(USYNERGY_BIG_ENDIAN) /* Ambiguous: both endians specified */ #error "Can't define both USYNERGY_LITTLE_ENDIAN and USYNERGY_BIG_ENDIAN" #elif !defined(USYNERGY_LITTLE_ENDIAN) && !defined(USYNERGY_BIG_ENDIAN) /* Attempt to auto detect */ #if defined(__LITTLE_ENDIAN__) || defined(LITTLE_ENDIAN) #define USYNERGY_LITTLE_ENDIAN #elif defined(__BIG_ENDIAN__) || defined(BIG_ENDIAN) || (_BYTE_ORDER == _BIG_ENDIAN) #define USYNERGY_BIG_ENDIAN #else #error "Can't detect endian-nes, please defined either USYNERGY_LITTLE_ENDIAN or USYNERGY_BIG_ENDIAN"; #endif #else /* User-specified endian-nes, nothing to do for us */ #endif //--------------------------------------------------------------------------------------------------------------------- // Types and Constants //--------------------------------------------------------------------------------------------------------------------- /** @brief User context type The uSynergyCookie type is an opaque type that is used by uSynergy to communicate to the client. It is passed along to callback functions as context. **/ //typedef struct { int ignored; } * uSynergyCookie; typedef void *uSynergyCookie; /** @brief Clipboard types **/ enum uSynergyClipboardFormat { USYNERGY_CLIPBOARD_FORMAT_TEXT = 0, /* Text format, UTF-8, newline is LF */ USYNERGY_CLIPBOARD_FORMAT_BITMAP = 1, /* Bitmap format, BMP 24/32bpp, BI_RGB */ USYNERGY_CLIPBOARD_FORMAT_HTML = 2, /* HTML format, HTML fragment, UTF-8, newline is LF */ }; /** @brief Constants and limits **/ #define USYNERGY_NUM_JOYSTICKS 4 /* Maximum number of supported joysticks */ #define USYNERGY_PROTOCOL_MAJOR 1 /* Major protocol version */ #define USYNERGY_PROTOCOL_MINOR 6 /* Minor protocol version */ #define USYNERGY_IDLE_TIMEOUT 10000 /* Timeout in milliseconds before reconnecting */ #define USYNERGY_TRACE_BUFFER_SIZE 1024 /* Maximum length of traced message */ #define USYNERGY_REPLY_BUFFER_SIZE 1024 /* Maximum size of a reply packet */ #define USYNERGY_RECEIVE_BUFFER_SIZE 0xFFFF /* Maximum size of an incoming packet */ /** @brief Keyboard constants **/ #define USYNERGY_MODIFIER_SHIFT 0x0001 /* Shift key modifier */ #define USYNERGY_MODIFIER_CTRL 0x0002 /* Ctrl key modifier */ #define USYNERGY_MODIFIER_ALT 0x0004 /* Alt key modifier */ #define USYNERGY_MODIFIER_META 0x0008 /* Meta key modifier */ #define USYNERGY_MODIFIER_WIN 0x0010 /* Windows key modifier */ #define USYNERGY_MODIFIER_ALT_GR 0x0020 /* AltGr key modifier */ #define USYNERGY_MODIFIER_LEVEL5LOCK 0x0040 /* Level5Lock key modifier */ #define USYNERGY_MODIFIER_CAPSLOCK 0x1000 /* CapsLock key modifier */ #define USYNERGY_MODIFIER_NUMLOCK 0x2000 /* NumLock key modifier */ #define USYNERGY_MODIFIER_SCROLLOCK 0x4000 /* ScrollLock key modifier */ /** @brif Mouse button identifiers **/ enum uSynergyMouseButton { USYNERGY_MOUSE_BUTTON_NONE = 0, USYNERGY_MOUSE_BUTTON_LEFT, USYNERGY_MOUSE_BUTTON_MIDDLE, USYNERGY_MOUSE_BUTTON_RIGHT, USYNERGY_MOUSE_BUTTON__COUNT, }; /** @brief Error codes **/ enum uSynergyError { USYNERGY_ERROR__INIT = -1, USYNERGY_ERROR_NONE = 0, USYNERGY_ERROR_EBSY, USYNERGY_ERROR_EBAD, USYNERGY_ERROR_TIMEOUT, USYNERGY_ERROR__COUNT }; //--------------------------------------------------------------------------------------------------------------------- // Functions and Callbacks //--------------------------------------------------------------------------------------------------------------------- /** @brief Connect function This function is called when uSynergy needs to connect to the host. It doesn't imply a network implementation or destination address, that must all be handled on the user side. The function should return true if a connection was established or false if it could not connect. When network errors occur (e.g. uSynergySend or uSynergyReceive fail) then the connect call will be called again so the implementation of the function must close any old connections and clean up resources before retrying. @param cookie Cookie supplied in the Synergy context **/ typedef bool (*uSynergyConnectFunc)(uSynergyCookie cookie); /** @brief Send function This function is called when uSynergy needs to send something over the default connection. It should return true if sending succeeded and false otherwise. This function should block until the send operation is completed. @param cookie Cookie supplied in the Synergy context @param buffer Address of buffer to send @param length Length of buffer to send **/ typedef bool (*uSynergySendFunc)(uSynergyCookie cookie, const uint8_t *buffer, int length); /** @brief Receive function This function is called when uSynergy needs to receive data from the default connection. It should return true if receiving data succeeded and false otherwise. This function should block until data has been received and wait for data to become available. If @a outLength is set to 0 upon completion it is assumed that the connection is alive, but still in a connecting state and needs time to settle. @param cookie Cookie supplied in the Synergy context @param buffer Address of buffer to receive data into @param maxLength Maximum amount of bytes to write into the receive buffer @param outLength Address of integer that receives the actual amount of bytes written into @a buffer **/ typedef bool (*uSynergyReceiveFunc)(uSynergyCookie cookie, uint8_t *buffer, int maxLength, int* outLength); /** @brief Thread sleep function This function is called when uSynergy wants to suspend operation for a while before retrying an operation. It is mostly used when a socket times out or disconnect occurs to prevent uSynergy from continuously hammering a network connection in case the network is down. @param cookie Cookie supplied in the Synergy context @param timeMs Time to sleep the current thread (in milliseconds) **/ typedef void (*uSynergySleepFunc)(uSynergyCookie cookie, int timeMs); /** @brief Get time function This function is called when uSynergy needs to know the current time. This is used to determine when timeouts have occured. The time base should be a cyclic millisecond time value. @returns Time value in milliseconds **/ typedef uint32_t (*uSynergyGetTimeFunc)(); /** @brief Screen active callback This callback is called when Synergy makes the screen active or inactive. This callback is usually sent when the mouse enters or leaves the screen. @param cookie Cookie supplied in the Synergy context @param active Activation flag, 1 if the screen has become active, 0 if the screen has become inactive **/ typedef void (*uSynergyScreenActiveCallback)(uSynergyCookie cookie, bool active); /** @brief Screensaver callback **/ typedef void (*uSynergyScreensaverCallback)(uSynergyCookie cookie, bool state); /** @brief Mouse wheel callback **/ typedef void (*uSynergyMouseWheelCallback)(uSynergyCookie cookie, int16_t x, int16_t y); /** @brief Mouse button callback **/ typedef void (*uSynergyMouseButtonCallback)(uSynergyCookie cookie, enum uSynergyMouseButton button); /** @brief Mouse movement callback **/ typedef void (*uSynergyMouseMoveCallback)(uSynergyCookie cookie, bool rel, int16_t x, int16_t y); /** @brief Key event callback This callback is called when a key is pressed or released. @param cookie Cookie supplied in the Synergy context @param key Key code of key that was pressed or released @param id Synergy key ID code of the key that was pressed or released @param modifiers Status of modifier keys (alt, shift, etc.) @param down Down or up status, 1 is key is pressed down, 0 if key is released (up) @param repeat Repeat flag, 1 if the key is down because the key is repeating, 0 if the key is initially pressed by the user **/ typedef void (*uSynergyKeyboardCallback)(uSynergyCookie cookie, uint16_t key, uint16_t id, uint16_t modifiers, bool down, bool repeat); /** @brief Joystick event callback This callback is called when a joystick stick or button changes. It is possible that multiple callbacks are fired when different sticks or buttons change as these are individual messages in the packet stream. Each callback will contain all the valid state for the different axes and buttons. The last callback received will represent the most current joystick state. @param cookie Cookie supplied in the Synergy context @param joyNum Joystick number, always in the range [0 ... USYNERGY_NUM_JOYSTICKS> @param buttons Button pressed mask @param leftStickX Left stick X position, in range [-127 ... 127] @param leftStickY Left stick Y position, in range [-127 ... 127] @param rightStickX Right stick X position, in range [-127 ... 127] @param rightStickY Right stick Y position, in range [-127 ... 127] **/ typedef void (*uSynergyJoystickCallback)(uSynergyCookie cookie, uint8_t joyNum, uint16_t buttons, int8_t leftStickX, int8_t leftStickY, int8_t rightStickX, int8_t rightStickY); enum uSynergyClipboardId { SYNERGY_CLIPBOARD_CLIPBOARD = 0, SYNERGY_CLIPBOARD_SELECTION = 1 }; /** @brief Clipboard event callback This callback is called when something is placed on the clipboard. Multiple callbacks may be fired for multiple clipboard formats if they are supported. The data provided is read-only and may not be modified by the application. @param cookie Cookie supplied in the Synergy context @param id Clipboard identifier @param format Clipboard format @param data Memory area containing the clipboard raw data @param size Size of clipboard data **/ typedef void (*uSynergyClipboardCallback)(uSynergyCookie cookie, enum uSynergyClipboardId id, enum uSynergyClipboardFormat format, const uint8_t *data, uint32_t size); #define SYN_DATA_START 1 #define SYN_DATA_CHUNK 2 #define SYN_DATA_END 3 //--------------------------------------------------------------------------------------------------------------------- // Context //--------------------------------------------------------------------------------------------------------------------- /** @brief uSynergy context **/ typedef struct uSynergyContext { /* Mandatory configuration data, filled in by client */ uSynergyConnectFunc m_connectFunc; /* Connect function */ uSynergySendFunc m_sendFunc; /* Send data function */ uSynergyReceiveFunc m_receiveFunc; /* Receive data function */ uSynergySleepFunc m_sleepFunc; /* Thread sleep function */ uSynergyGetTimeFunc m_getTimeFunc; /* Get current time function */ const char* m_clientName; /* Name of Synergy Screen / Client */ uint16_t m_clientWidth; /* Width of screen */ uint16_t m_clientHeight; /* Height of screen */ /* Optional configuration data, filled in by client */ bool m_useRawKeyCodes; /* determine which key codes are sent to events */ bool m_errorIsFatal[USYNERGY_ERROR__COUNT]; /* determines whether or not a given error code is fatal (i.e. we just give up rather than reconnect*/ uSynergyCookie m_cookie; /* Cookie pointer passed to callback functions (can be NULL) */ uSynergyScreenActiveCallback m_screenActiveCallback; /* Callback for entering and leaving screen */ uSynergyScreensaverCallback m_screensaverCallback; uSynergyMouseWheelCallback m_mouseWheelCallback; uSynergyMouseButtonCallback m_mouseButtonUpCallback; uSynergyMouseButtonCallback m_mouseButtonDownCallback; uSynergyMouseMoveCallback m_mouseMoveCallback; uSynergyKeyboardCallback m_keyboardCallback; /* Callback for keyboard events */ uSynergyJoystickCallback m_joystickCallback; /* Callback for joystick events */ uSynergyClipboardCallback m_clipboardCallback; /* Callback for clipboard events */ /* State data, used internall by client, initialized by uSynergyInit() */ enum uSynergyError m_lastError; /* last error code which may have triggered a lost connection */ const char* m_implementation; /*implementation of the protocol -- usually "Synergy" or "Barrier" */ bool m_connected; /* Is our socket connected? */ bool m_hasReceivedHello; /* Have we received a 'Hello' from the server? */ bool m_infoCurrent; /* whether we've gotten an acknowledge from our information message */ bool m_resChanged; /* whether we've had a screen resolution change or not */ bool m_isCaptured; /* Is Synergy active (i.e. this client is receiving input messages?) */ uint32_t m_lastMessageTime; /* Time at which last message was received */ uint32_t m_sequenceNumber; /* Packet sequence number */ uint8_t m_receiveBuffer[USYNERGY_RECEIVE_BUFFER_SIZE]; /* Receive buffer */ int m_receiveOfs; /* Receive buffer offset */ uint8_t m_replyBuffer[USYNERGY_REPLY_BUFFER_SIZE]; /* Reply buffer */ uint8_t* m_replyCur; /* Write offset into reply buffer */ int8_t m_joystickSticks[USYNERGY_NUM_JOYSTICKS][4]; /* Joystick stick position in 2 axes for 2 sticks */ uint16_t m_joystickButtons[USYNERGY_NUM_JOYSTICKS]; /* Joystick button state */ unsigned char* m_clipBuf[2]; /* buffers for clipboard data */ size_t m_clipLen[2]; /* allocated length of clipboard buffers */ size_t m_clipPos[2]; /* actual length of clipboard buffers */ size_t m_clipPosExpect[2]; /* expected length of clipboard data */ bool m_clipInStream[2]; /* whether or not we are currently in a clipboard data stream */ bool m_clipGrabbed[2]; /* whether or not we're grabbed -- i.e. obligated to send data on focus loss */ } uSynergyContext; //--------------------------------------------------------------------------------------------------------------------- // Interface //--------------------------------------------------------------------------------------------------------------------- /** @brief Initialize uSynergy context This function initializes @a context for use. Call this function directly after creating the context, before filling in any configuration data in it. Not calling this function will cause undefined behavior. @param context Context to be initialized **/ extern void uSynergyInit(uSynergyContext *context); /** @brief Update uSynergy This function updates uSynergy and does the bulk of the work. It does connection management, receiving data, reconnecting after errors or timeouts and so on. It assumes that networking operations are blocking and it can suspend the current thread if it needs to wait. It is best practice to call uSynergyUpdate from a background thread so it is responsive. Because uSynergy relies mostly on blocking calls it will mostly stay in thread sleep state waiting for system mutexes and won't eat much memory. uSynergyUpdate doesn't do any memory allocations or have any side effects beyond those of the callbacks it calls. @param context Context to be updated **/ extern void uSynergyUpdate(uSynergyContext *context); /** @brief Update clipboard data This function sets new clipboard data and prepares it to be sent to the server on screen deactivation. Currently there is only support for plaintext, but HTML and image data could be supported with some effort. @param context Context to send clipboard data to @param id Clipboard to send data to @param len Length of clipboard data @param text Text to set to the clipboard **/ extern void uSynergyUpdateClipBuf(uSynergyContext *context, enum uSynergyClipboardId id, uint32_t len, const char *data); /** @brief Update screen resolution This will send an update to the server on screen resolution change. @param context Synergy context @param width Width in pixels @param height Height in pixels **/ extern void uSynergyUpdateRes(uSynergyContext *context, int16_t width, int16_t height); #ifdef __cplusplus }; #endif 07070100000019000081A400000000000000000000000164751F5700000037000000000000000000000000000000000000002100000000waynergy-0.16+3/include/ver.h.in#pragma once #define WAYNERGY_VERSION_STR "@VCS_DESC@" 0707010000001A000081A400000000000000000000000164751F57000013C0000000000000000000000000000000000000002200000000waynergy-0.16+3/include/wayland.h#pragma once #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <poll.h> #include <sys/mman.h> #include <xkbcommon/xkbcommon.h> #include "os.h" #include "xmem.h" #include "config.h" #include <unistd.h> #include <wayland-client.h> #include <wayland-client-protocol.h> #include "fake-input-client-protocol.h" #include "wlr-virtual-pointer-unstable-v1-client-protocol.h" #include "virtual-keyboard-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #include "idle-client-protocol.h" #include "uSynergy.h" struct wlOutput { uint32_t wl_name; struct wl_output *wl_output; struct zxdg_output_v1 *xdg_output; int32_t x; int32_t y; int width; int height; int32_t scale; bool complete; bool have_log_size; bool have_log_pos; char *name; char *desc; struct wlOutput *next; }; struct wlIdle { /*module specific state */ void *state; /*wayland context */ struct wlContext *wl_ctx; /* actual functions */ void (*inhibit_start)(struct wlIdle *); void (*inhibit_stop)(struct wlIdle *); }; extern bool wlIdleInitKde(struct wlContext *ctx); extern bool wlIdleInitGnome(struct wlContext *ctx); #define WL_INPUT_BUTTON_COUNT 8 struct wlInput { /* module-specific state */ void *state; /* key state information*/ int *key_press_state; size_t key_press_state_len; // keyboard layout handling struct xkb_context *xkb_ctx; struct xkb_keymap *xkb_map; struct xkb_state *xkb_state; /* raw keymap -- distinct from xkb */ size_t key_count; int *raw_keymap; /* id-based keymap -- uses synergy abstract keycodes */ size_t id_count; int *id_keymap; /* whether or not a given id entry should be used */ bool *id_keymap_valid; /* mouse button map */ int button_map[WL_INPUT_BUTTON_COUNT]; /* wayland context */ struct wlContext *wl_ctx; /* actual functions */ void (*mouse_rel_motion)(struct wlInput *, int, int); void (*mouse_motion)(struct wlInput *, int, int); void (*mouse_button)(struct wlInput *, int, int); void (*mouse_wheel)(struct wlInput *, signed short dx, signed short dy); void (*key)(struct wlInput *, int, int); bool (*key_map)(struct wlInput *, char *); }; /* uinput must open device fds before privileges are dropped, so this is * necessary */ extern bool wlInputInitWlr(struct wlContext *ctx); extern bool wlInputInitKde(struct wlContext *ctx); extern bool wlInputInitUinput(struct wlContext *ctx); struct wlContext { char *comp_name; struct wl_registry *registry; struct wl_display *display; struct wl_seat *seat; uint32_t seat_caps; struct wl_keyboard *kb; char *kb_map; struct wlInput input; /* /dev/uinput file descriptors, for mouse or keyboard * or -1 to disable */ int uinput_fd[2]; /* objects offered by the compositor that backends might use */ struct zwp_virtual_keyboard_manager_v1 *keyboard_manager; struct zwlr_virtual_pointer_manager_v1 *pointer_manager; struct org_kde_kwin_fake_input *fake_input; /* output stuff */ struct zxdg_output_manager_v1 *output_manager; struct wlOutput *outputs; /* idle stuff */ struct org_kde_kwin_idle *idle_manager; struct wlIdle idle; //state int width; int height; time_t epoch; long timeout; //callbacks void (*on_output_update)(struct wlContext *ctx); }; /* flush the display with proper error checking */ extern void wlDisplayFlush(struct wlContext *ctx); /* (re)set the keyboard layout according to the configuration * probably not useful outside wlSetup*/ extern int wlKeySetConfigLayout(struct wlContext *ctx); /* load button map */ extern void wlLoadButtonMap(struct wlContext *ctx); /* set up the wayland context */ extern bool wlSetup(struct wlContext *context, int width, int height, char *backend); /* obtain a monotonic timestamp */ extern uint32_t wlTS(struct wlContext *context); /* update screen resolution */ extern void wlResUpdate(struct wlContext *context, int width, int height); /* close wayland connection */ extern void wlClose(struct wlContext *context); /* retrieve the wayland connection file descriptor, for polling purposes */ extern int wlPrepareFd(struct wlContext *context); /* process IO indicated by poll() */ extern void wlPollProc(struct wlContext *context, short revents); /* mouse-related functions */ extern void wlMouseRelativeMotion(struct wlContext *context, int dx, int dy); extern void wlMouseMotion(struct wlContext *context, int x, int y); extern void wlMouseButton(struct wlContext *context, int button, int state); extern void wlMouseWheel(struct wlContext *context, signed short dx, signed short dy); /* keyboard-related functions */ /* send a raw keycode, no mapping is performed */ extern void wlKeyRaw(struct wlContext *context, int key, int state); /* send a keycode or id, mapping as needed. Prefers the id value. */ extern void wlKey(struct wlContext *context, int key, int id, int state); /* release all currently-pressed keys, usually on exiting the screen */ extern void wlKeyReleaseAll(struct wlContext *context); /* enable or disable idle inhibition */ extern void wlIdleInhibit(struct wlContext *context, bool on); 0707010000001B000081A400000000000000000000000164751F5700000DF2000000000000000000000000000000000000001F00000000waynergy-0.16+3/include/xmem.h/* xmem -- memory operations that can only fail catastrophically * * Version 1.5 * * Copyright 2021 Ryan Farley <ryan.farley@gmx.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef XMEM_H_INC #define XMEM_H_INC #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <stdint.h> #if defined(__GNUC__) #define XMEM_UNUSED __attribute__((unused)) #else #define XMEM_UNUSED #endif XMEM_UNUSED static void *xmalloc(size_t len) { void *ret; if (!(ret = malloc(len))) abort(); return ret; } XMEM_UNUSED static void *xcalloc(size_t nmemb, size_t size) { void *ret; if (!(ret = calloc(nmemb, size))) abort(); return ret; } XMEM_UNUSED static void *xrealloc(void *ptr, size_t len) { if (!(ptr = realloc(ptr, len))) abort(); return ptr; } /* xreallocarray: safely reallocate an array * * Like reallocarray() present on some platforms, will catch any possible * overflows */ XMEM_UNUSED static void *xreallocarray(void *ptr, size_t nmemb, size_t size) { if (size && nmemb > SIZE_MAX / size) { abort(); } return xrealloc(ptr, nmemb * size); } XMEM_UNUSED static char *xstrdup(const char *str) { char *ret; size_t len; if (!str) return NULL; len = strlen(str) + 1; ret = xmalloc(len); return memcpy(ret, str, len); } /* x*asnprintf: print to a buffer, allocating/growing as needed * * If *size is 0, *strp is allocated anew; otherwise, it is grown to the * needed size. The current length of the string in the buffer is returned, * to allow for manual trimming if desired. */ XMEM_UNUSED static size_t xvasnprintf(char **strp, size_t *size, const char *fmt, va_list ap) { va_list testap; int tsize; if (!*size) { *strp = NULL; } va_copy(testap, ap); if ((tsize = vsnprintf(*strp, *size, fmt, testap)) == -1) abort(); va_end(testap); if (tsize >= *size) { *size = tsize + 1; *strp = xrealloc(*strp, *size); vsnprintf(*strp, *size, fmt, ap); } return tsize; } XMEM_UNUSED static size_t xasnprintf(char **strp, size_t *size, const char *fmt, ...) { va_list ap; size_t ret; va_start(ap, fmt); ret = xvasnprintf(strp, size, fmt, ap); va_end(ap); return ret; } /* x*asprintf: print to a newly-allocated buffer * * A bit like the asprintf family on some platforms, but will work anywhere */ XMEM_UNUSED static void xvasprintf(char **strp, const char *fmt, va_list ap) { size_t s = 0; xvasnprintf(strp, &s, fmt, ap); } XMEM_UNUSED static void xasprintf(char **strp, const char *fmt, ...) { va_list ap; size_t s = 0; va_start(ap, fmt); xvasnprintf(strp, &s, fmt, ap); va_end(ap); } /* strfreev: free a NULL-terminated array of strings * * NULL-terminated array being a bit like argv in main(). */ XMEM_UNUSED static void strfreev(char **strv) { if (strv) { size_t i; for (i = 0; strv[i]; ++i) { free(strv[i]); } free(strv); } } #undef XMEM_UNUSED #endif 0707010000001C000081A400000000000000000000000164751F57000005ED000000000000000000000000000000000000001C00000000waynergy-0.16+3/meson.buildproject('waynergy', 'c', version: '0.0.16' ) src_c = files( 'src/wl_idle.c', 'src/wl_idle_gnome.c', 'src/wl_idle_kde.c', 'src/wl_input.c', 'src/wl_input_wlr.c', 'src/wl_input_kde.c', 'src/wl_input_uinput.c', 'src/main.c', 'src/clip.c', 'src/config.c', 'src/net.c', 'src/os.c', 'src/sig.c', 'src/ssp.c', 'src/wayland.c', 'src/uSynergy.c', 'src/log.c' ) wayland_client = dependency('wayland-client') xkbcommon = dependency('xkbcommon') libtls = dependency('libtls') if host_machine.system() == 'linux' add_project_arguments('-D_GNU_SOURCE ', language: 'c') endif if host_machine.endian() == 'big' add_project_arguments('-DUSYNERGY_BIG_ENDIAN', language: 'c') else add_project_arguments('-DUSYNERGY_LITTLE_ENDIAN', language: 'c') endif subdir('protocol') subdir('include') executable( 'waynergy', src_c, install: true, dependencies : [ client_protos, libtls, wayland_client, xkbcommon, ver_dep, ], include_directories: [include_directories('include')], ) executable( 'waynergy-clip-update', 'src/clip-update.c', install: true, include_directories: [include_directories('include')], ) executable( 'waynergy-mapper', 'src/mapper.c', install: true, dependencies : [ client_protos, wayland_client, xkbcommon, ], include_directories: [include_directories('include')], ) install_data( 'waynergy.desktop', install_dir: get_option('datadir') / 'applications', rename: meson.project_name() + '.desktop', ) 0707010000001D000041ED00000000000000000000000264751F5700000000000000000000000000000000000000000000001900000000waynergy-0.16+3/protocol0707010000001E000081A400000000000000000000000164751F5700000F67000000000000000000000000000000000000002800000000waynergy-0.16+3/protocol/fake-input.xml<?xml version="1.0" encoding="UTF-8"?> <protocol name="fake_input"> <copyright><![CDATA[ SPDX-FileCopyrightText: 2015 Martin Gräßlin SPDX-License-Identifier: LGPL-2.1-or-later ]]></copyright> <interface name="org_kde_kwin_fake_input" version="4"> <description summary="Fake input manager"> This interface allows other processes to provide fake input events. Purpose is on the one hand side to provide testing facilities like XTest on X11. But also to support use case like kdeconnect's mouse pad interface. A compositor should not trust the input received from this interface. Clients should not expect that the compositor honors the requests from this interface. </description> <request name="authenticate"> <description summary="Information why the client wants to use the interface"> A client should use this request to tell the compositor why it wants to use this interface. The compositor might use the information to decide whether it wants to grant the request. The data might also be passed to the user to decide whether the application should get granted access to this very privileged interface. </description> <arg name="application" type="string" summary="user visible name of the application"/> <arg name="reason" type="string" summary="reason why the application wants to use this interface"/> </request> <request name="pointer_motion"> <arg name="delta_x" type="fixed"/> <arg name="delta_y" type="fixed"/> </request> <request name="button"> <arg name="button" type="uint"/> <arg name="state" type="uint"/> </request> <request name="axis"> <arg name="axis" type="uint"/> <arg name="value" type="fixed"/> </request> <request name="touch_down" since="2"> <description summary="touch down event"> A client should use this request to send touch down event at specific coordinates. </description> <arg name="id" type="uint" summary="unique id for touch down event"/> <arg name="x" type="fixed" summary="x coordinate for touch down event"/> <arg name="y" type="fixed" summary="y coordinate for touch down event"/> </request> <request name="touch_motion" since="2"> <description summary="touch motion event"> A client should use this request to send touch motion to specific position. </description> <arg name="id" type="uint" summary="unique id for touch motion event"/> <arg name="x" type="fixed" summary="x coordinate for touch motion event"/> <arg name="y" type="fixed" summary="y coordinate for touch motion event"/> </request> <request name="touch_up" since="2"> <description summary="touch up event"> A client should use this request to send touch up event. </description> <arg name="id" type="uint" summary="unique id for touch up event"/> </request> <request name="touch_cancel" since="2"> <description summary="touch cancel event"> A client should use this request to cancel the current touch event. </description> </request> <request name="touch_frame" since="2"> <description summary="touch frame event"> A client should use this request to send touch frame event. </description> </request> <request name="pointer_motion_absolute" since="3"> <arg name="x" type="fixed"/> <arg name="y" type="fixed"/> </request> <request name="keyboard_key" since="4"> <arg name="button" type="uint"/> <arg name="state" type="uint"/> </request> </interface> </protocol> 0707010000001F000081A400000000000000000000000164751F5700000972000000000000000000000000000000000000002200000000waynergy-0.16+3/protocol/idle.xml<?xml version="1.0" encoding="UTF-8"?> <protocol name="idle"> <copyright><![CDATA[ Copyright (C) 2015 Martin Gräßlin This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. ]]></copyright> <interface name="org_kde_kwin_idle" version="1"> <description summary="User idle time manager"> This interface allows to monitor user idle time on a given seat. The interface allows to register timers which trigger after no user activity was registered on the seat for a given interval. It notifies when user activity resumes. This is useful for applications wanting to perform actions when the user is not interacting with the system, e.g. chat applications setting the user as away, power management features to dim screen, etc.. </description> <request name="get_idle_timeout"> <arg name="id" type="new_id" interface="org_kde_kwin_idle_timeout"/> <arg name="seat" type="object" interface="wl_seat"/> <arg name="timeout" type="uint" summary="The idle timeout in msec"/> </request> </interface> <interface name="org_kde_kwin_idle_timeout" version="1"> <request name="release" type="destructor"> <description summary="release the timeout object"/> </request> <request name="simulate_user_activity"> <description summary="Simulates user activity for this timeout, behaves just like real user activity on the seat"/> </request> <event name="idle"> <description summary="Triggered when there has not been any user activity in the requested idle time interval"/> </event> <event name="resumed"> <description summary="Triggered on the first user activity after an idle event"/> </event> </interface> </protocol> 07070100000020000081A400000000000000000000000164751F57000019F5000000000000000000000000000000000000004400000000waynergy-0.16+3/protocol/keyboard-shortcuts-inhibit-unstable-v1.xml<?xml version="1.0" encoding="UTF-8"?> <protocol name="keyboard_shortcuts_inhibit_unstable_v1"> <copyright> Copyright © 2017 Red Hat Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. </copyright> <description summary="Protocol for inhibiting the compositor keyboard shortcuts"> This protocol specifies a way for a client to request the compositor to ignore its own keyboard shortcuts for a given seat, so that all key events from that seat get forwarded to a surface. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. </description> <interface name="zwp_keyboard_shortcuts_inhibit_manager_v1" version="1"> <description summary="context object for keyboard grab_manager"> A global interface used for inhibiting the compositor keyboard shortcuts. </description> <request name="destroy" type="destructor"> <description summary="destroy the keyboard shortcuts inhibitor object"> Destroy the keyboard shortcuts inhibitor manager. </description> </request> <request name="inhibit_shortcuts"> <description summary="create a new keyboard shortcuts inhibitor object"> Create a new keyboard shortcuts inhibitor object associated with the given surface for the given seat. If shortcuts are already inhibited for the specified seat and surface, a protocol error "already_inhibited" is raised by the compositor. </description> <arg name="id" type="new_id" interface="zwp_keyboard_shortcuts_inhibitor_v1"/> <arg name="surface" type="object" interface="wl_surface" summary="the surface that inhibits the keyboard shortcuts behavior"/> <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat for which keyboard shortcuts should be disabled"/> </request> <enum name="error"> <entry name="already_inhibited" value="0" summary="the shortcuts are already inhibited for this surface"/> </enum> </interface> <interface name="zwp_keyboard_shortcuts_inhibitor_v1" version="1"> <description summary="context object for keyboard shortcuts inhibitor"> A keyboard shortcuts inhibitor instructs the compositor to ignore its own keyboard shortcuts when the associated surface has keyboard focus. As a result, when the surface has keyboard focus on the given seat, it will receive all key events originating from the specified seat, even those which would normally be caught by the compositor for its own shortcuts. The Wayland compositor is however under no obligation to disable all of its shortcuts, and may keep some special key combo for its own use, including but not limited to one allowing the user to forcibly restore normal keyboard events routing in the case of an unwilling client. The compositor may also use the same key combo to reactivate an existing shortcut inhibitor that was previously deactivated on user request. When the compositor restores its own keyboard shortcuts, an "inactive" event is emitted to notify the client that the keyboard shortcuts inhibitor is not effectively active for the surface and seat any more, and the client should not expect to receive all keyboard events. When the keyboard shortcuts inhibitor is inactive, the client has no way to forcibly reactivate the keyboard shortcuts inhibitor. The user can chose to re-enable a previously deactivated keyboard shortcuts inhibitor using any mechanism the compositor may offer, in which case the compositor will send an "active" event to notify the client. If the surface is destroyed, unmapped, or loses the seat's keyboard focus, the keyboard shortcuts inhibitor becomes irrelevant and the compositor will restore its own keyboard shortcuts but no "inactive" event is emitted in this case. </description> <request name="destroy" type="destructor"> <description summary="destroy the keyboard shortcuts inhibitor object"> Remove the keyboard shortcuts inhibitor from the associated wl_surface. </description> </request> <event name="active"> <description summary="shortcuts are inhibited"> This event indicates that the shortcut inhibitor is active. The compositor sends this event every time compositor shortcuts are inhibited on behalf of the surface. When active, the client may receive input events normally reserved by the compositor (see zwp_keyboard_shortcuts_inhibitor_v1). This occurs typically when the initial request "inhibit_shortcuts" first becomes active or when the user instructs the compositor to re-enable and existing shortcuts inhibitor using any mechanism offered by the compositor. </description> </event> <event name="inactive"> <description summary="shortcuts are restored"> This event indicates that the shortcuts inhibitor is inactive, normal shortcuts processing is restored by the compositor. </description> </event> </interface> </protocol> 07070100000021000081A400000000000000000000000164751F5700000433000000000000000000000000000000000000002500000000waynergy-0.16+3/protocol/meson.buildwayland_scanner = find_program('wayland-scanner') wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', arguments: ['private-code', '@INPUT@', '@OUTPUT@'], ) wayland_scanner_client = generator( wayland_scanner, output: '@BASENAME@-client-protocol.h', arguments: ['client-header', '@INPUT@', '@OUTPUT@'], ) client_protocols = [ ['idle.xml'], ['fake-input.xml'], ['keyboard-shortcuts-inhibit-unstable-v1.xml'], ['virtual-keyboard-unstable-v1.xml'], ['wlr-virtual-pointer-unstable-v1.xml'], ['xdg-output-unstable-v1.xml'], ['xdg-shell.xml'], ] client_protos_src = [] client_protos_headers = [] foreach p : client_protocols xml = join_paths(p) client_protos_src += wayland_scanner_code.process(xml) client_protos_headers += wayland_scanner_client.process(xml) endforeach lib_client_protos = static_library( 'client_protos', client_protos_src + client_protos_headers, dependencies: [wayland_client] ) client_protos = declare_dependency( link_with: lib_client_protos, sources: client_protos_headers, ) 07070100000022000081A400000000000000000000000164751F5700001316000000000000000000000000000000000000003A00000000waynergy-0.16+3/protocol/virtual-keyboard-unstable-v1.xml<?xml version="1.0" encoding="UTF-8"?> <protocol name="virtual_keyboard_unstable_v1"> <copyright> Copyright © 2008-2011 Kristian Høgsberg Copyright © 2010-2013 Intel Corporation Copyright © 2012-2013 Collabora, Ltd. Copyright © 2018 Purism SPC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. </copyright> <interface name="zwp_virtual_keyboard_v1" version="1"> <description summary="virtual keyboard"> The virtual keyboard provides an application with requests which emulate the behaviour of a physical keyboard. This interface can be used by clients on its own to provide raw input events, or it can accompany the input method protocol. </description> <request name="keymap"> <description summary="keyboard mapping"> Provide a file descriptor to the compositor which can be memory-mapped to provide a keyboard mapping description. Format carries a value from the keymap_format enumeration. </description> <arg name="format" type="uint" summary="keymap format"/> <arg name="fd" type="fd" summary="keymap file descriptor"/> <arg name="size" type="uint" summary="keymap size, in bytes"/> </request> <enum name="error"> <entry name="no_keymap" value="0" summary="No keymap was set"/> </enum> <request name="key"> <description summary="key event"> A key was pressed or released. The time argument is a timestamp with millisecond granularity, with an undefined base. All requests regarding a single object must share the same clock. Keymap must be set before issuing this request. State carries a value from the key_state enumeration. </description> <arg name="time" type="uint" summary="timestamp with millisecond granularity"/> <arg name="key" type="uint" summary="key that produced the event"/> <arg name="state" type="uint" summary="physical state of the key"/> </request> <request name="modifiers"> <description summary="modifier and group state"> Notifies the compositor that the modifier and/or group state has changed, and it should update state. The client should use wl_keyboard.modifiers event to synchronize its internal state with seat state. Keymap must be set before issuing this request. </description> <arg name="mods_depressed" type="uint" summary="depressed modifiers"/> <arg name="mods_latched" type="uint" summary="latched modifiers"/> <arg name="mods_locked" type="uint" summary="locked modifiers"/> <arg name="group" type="uint" summary="keyboard layout"/> </request> <request name="destroy" type="destructor" since="1"> <description summary="destroy the virtual keyboard keyboard object"/> </request> </interface> <interface name="zwp_virtual_keyboard_manager_v1" version="1"> <description summary="virtual keyboard manager"> A virtual keyboard manager allows an application to provide keyboard input events as if they came from a physical keyboard. </description> <enum name="error"> <entry name="unauthorized" value="0" summary="client not authorized to use the interface"/> </enum> <request name="create_virtual_keyboard"> <description summary="Create a new virtual keyboard"> Creates a new virtual keyboard associated to a seat. If the compositor enables a keyboard to perform arbitrary actions, it should present an error when an untrusted client requests a new keyboard. </description> <arg name="seat" type="object" interface="wl_seat"/> <arg name="id" type="new_id" interface="zwp_virtual_keyboard_v1"/> </request> </interface> </protocol> 07070100000023000081A400000000000000000000000164751F5700001AEF000000000000000000000000000000000000003D00000000waynergy-0.16+3/protocol/wlr-virtual-pointer-unstable-v1.xml<?xml version="1.0" encoding="UTF-8"?> <protocol name="wlr_virtual_pointer_unstable_v1"> <copyright> Copyright © 2019 Josef Gajdusek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. </copyright> <interface name="zwlr_virtual_pointer_v1" version="2"> <description summary="virtual pointer"> This protocol allows clients to emulate a physical pointer device. The requests are mostly mirror opposites of those specified in wl_pointer. </description> <enum name="error"> <entry name="invalid_axis" value="0" summary="client sent invalid axis enumeration value" /> <entry name="invalid_axis_source" value="1" summary="client sent invalid axis source enumeration value" /> </enum> <request name="motion"> <description summary="pointer relative motion event"> The pointer has moved by a relative amount to the previous request. Values are in the global compositor space. </description> <arg name="time" type="uint" summary="timestamp with millisecond granularity"/> <arg name="dx" type="fixed" summary="displacement on the x-axis"/> <arg name="dy" type="fixed" summary="displacement on the y-axis"/> </request> <request name="motion_absolute"> <description summary="pointer absolute motion event"> The pointer has moved in an absolute coordinate frame. Value of x can range from 0 to x_extent, value of y can range from 0 to y_extent. </description> <arg name="time" type="uint" summary="timestamp with millisecond granularity"/> <arg name="x" type="uint" summary="position on the x-axis"/> <arg name="y" type="uint" summary="position on the y-axis"/> <arg name="x_extent" type="uint" summary="extent of the x-axis"/> <arg name="y_extent" type="uint" summary="extent of the y-axis"/> </request> <request name="button"> <description summary="button event"> A button was pressed or released. </description> <arg name="time" type="uint" summary="timestamp with millisecond granularity"/> <arg name="button" type="uint" summary="button that produced the event"/> <arg name="state" type="uint" enum="wl_pointer.button_state" summary="physical state of the button"/> </request> <request name="axis"> <description summary="axis event"> Scroll and other axis requests. </description> <arg name="time" type="uint" summary="timestamp with millisecond granularity"/> <arg name="axis" type="uint" enum="wl_pointer.axis" summary="axis type"/> <arg name="value" type="fixed" summary="length of vector in touchpad coordinates"/> </request> <request name="frame"> <description summary="end of a pointer event sequence"> Indicates the set of events that logically belong together. </description> </request> <request name="axis_source"> <description summary="axis source event"> Source information for scroll and other axis. </description> <arg name="axis_source" type="uint" enum="wl_pointer.axis_source" summary="source of the axis event"/> </request> <request name="axis_stop"> <description summary="axis stop event"> Stop notification for scroll and other axes. </description> <arg name="time" type="uint" summary="timestamp with millisecond granularity"/> <arg name="axis" type="uint" enum="wl_pointer.axis" summary="the axis stopped with this event"/> </request> <request name="axis_discrete"> <description summary="axis click event"> Discrete step information for scroll and other axes. This event allows the client to extend data normally sent using the axis event with discrete value. </description> <arg name="time" type="uint" summary="timestamp with millisecond granularity"/> <arg name="axis" type="uint" enum="wl_pointer.axis" summary="axis type"/> <arg name="value" type="fixed" summary="length of vector in touchpad coordinates"/> <arg name="discrete" type="int" summary="number of steps"/> </request> <request name="destroy" type="destructor" since="1"> <description summary="destroy the virtual pointer object"/> </request> </interface> <interface name="zwlr_virtual_pointer_manager_v1" version="2"> <description summary="virtual pointer manager"> This object allows clients to create individual virtual pointer objects. </description> <request name="create_virtual_pointer"> <description summary="Create a new virtual pointer"> Creates a new virtual pointer. The optional seat is a suggestion to the compositor. </description> <arg name="seat" type="object" interface="wl_seat" allow-null="true"/> <arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/> </request> <request name="destroy" type="destructor" since="1"> <description summary="destroy the virtual pointer manager"/> </request> <!-- Version 2 additions --> <request name="create_virtual_pointer_with_output" since="2"> <description summary="Create a new virtual pointer"> Creates a new virtual pointer. The seat and the output arguments are optional. If the seat argument is set, the compositor should assign the input device to the requested seat. If the output argument is set, the compositor should map the input device to the requested output. </description> <arg name="seat" type="object" interface="wl_seat" allow-null="true"/> <arg name="output" type="object" interface="wl_output" allow-null="true"/> <arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/> </request> </interface> </protocol> 07070100000024000081A400000000000000000000000164751F570000254A000000000000000000000000000000000000003400000000waynergy-0.16+3/protocol/xdg-output-unstable-v1.xml<?xml version="1.0" encoding="UTF-8"?> <protocol name="xdg_output_unstable_v1"> <copyright> Copyright © 2017 Red Hat Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. </copyright> <description summary="Protocol to describe output regions"> This protocol aims at describing outputs in a way which is more in line with the concept of an output on desktop oriented systems. Some information are more specific to the concept of an output for a desktop oriented system and may not make sense in other applications, such as IVI systems for example. Typically, the global compositor space on a desktop system is made of a contiguous or overlapping set of rectangular regions. Some of the information provided in this protocol might be identical to their counterparts already available from wl_output, in which case the information provided by this protocol should be preferred to their equivalent in wl_output. The goal is to move the desktop specific concepts (such as output location within the global compositor space, the connector name and types, etc.) out of the core wl_output protocol. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. </description> <interface name="zxdg_output_manager_v1" version="3"> <description summary="manage xdg_output objects"> A global factory interface for xdg_output objects. </description> <request name="destroy" type="destructor"> <description summary="destroy the xdg_output_manager object"> Using this request a client can tell the server that it is not going to use the xdg_output_manager object anymore. Any objects already created through this instance are not affected. </description> </request> <request name="get_xdg_output"> <description summary="create an xdg output from a wl_output"> This creates a new xdg_output object for the given wl_output. </description> <arg name="id" type="new_id" interface="zxdg_output_v1"/> <arg name="output" type="object" interface="wl_output"/> </request> </interface> <interface name="zxdg_output_v1" version="3"> <description summary="compositor logical output region"> An xdg_output describes part of the compositor geometry. This typically corresponds to a monitor that displays part of the compositor space. For objects version 3 onwards, after all xdg_output properties have been sent (when the object is created and when properties are updated), a wl_output.done event is sent. This allows changes to the output properties to be seen as atomic, even if they happen via multiple events. </description> <request name="destroy" type="destructor"> <description summary="destroy the xdg_output object"> Using this request a client can tell the server that it is not going to use the xdg_output object anymore. </description> </request> <event name="logical_position"> <description summary="position of the output within the global compositor space"> The position event describes the location of the wl_output within the global compositor space. The logical_position event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output) and whenever the location of the output changes within the global compositor space. </description> <arg name="x" type="int" summary="x position within the global compositor space"/> <arg name="y" type="int" summary="y position within the global compositor space"/> </event> <event name="logical_size"> <description summary="size of the output in the global compositor space"> The logical_size event describes the size of the output in the global compositor space. For example, a surface without any buffer scale, transformation nor rotation set, with the size matching the logical_size will have the same size as the corresponding output when displayed. Most regular Wayland clients should not pay attention to the logical size and would rather rely on xdg_shell interfaces. Some clients such as Xwayland, however, need this to configure their surfaces in the global compositor space as the compositor may apply a different scale from what is advertised by the output scaling property (to achieve fractional scaling, for example). For example, for a wl_output mode 3840×2160 and a scale factor 2: - A compositor not scaling the surface buffers will advertise a logical size of 3840×2160, - A compositor automatically scaling the surface buffers will advertise a logical size of 1920×1080, - A compositor using a fractional scale of 1.5 will advertise a logical size to 2560×1620. For example, for a wl_output mode 1920×1080 and a 90 degree rotation, the compositor will advertise a logical size of 1080x1920. The logical_size event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output) and whenever the logical size of the output changes, either as a result of a change in the applied scale or because of a change in the corresponding output mode(see wl_output.mode) or transform (see wl_output.transform). </description> <arg name="width" type="int" summary="width in global compositor space"/> <arg name="height" type="int" summary="height in global compositor space"/> </event> <event name="done"> <description summary="all information about the output have been sent"> This event is sent after all other properties of an xdg_output have been sent. This allows changes to the xdg_output properties to be seen as atomic, even if they happen via multiple events. For objects version 3 onwards, this event is deprecated. Compositors are not required to send it anymore and must send wl_output.done instead. </description> </event> <!-- Version 2 additions --> <event name="name" since="2"> <description summary="name of this output"> Many compositors will assign names to their outputs, show them to the user, allow them to be configured by name, etc. The client may wish to know this name as well to offer the user similar behaviors. The naming convention is compositor defined, but limited to alphanumeric characters and dashes (-). Each name is unique among all wl_output globals, but if a wl_output global is destroyed the same name may be reused later. The names will also remain consistent across sessions with the same hardware and software configuration. Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do not assume that the name is a reflection of an underlying DRM connector, X11 connection, etc. The name event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output). This event is only sent once per xdg_output, and the name does not change over the lifetime of the wl_output global. </description> <arg name="name" type="string" summary="output name"/> </event> <event name="description" since="2"> <description summary="human-readable description of this output"> Many compositors can produce human-readable descriptions of their outputs. The client may wish to know this description as well, to communicate the user for various purposes. The description is a UTF-8 string with no convention defined for its contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 output via :1'. The description event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output) and whenever the description changes. The description is optional, and may not be sent at all. For objects of version 2 and lower, this event is only sent once per xdg_output, and the description does not change over the lifetime of the wl_output global. </description> <arg name="description" type="string" summary="output description"/> </event> </interface> </protocol> 07070100000025000081A400000000000000000000000164751F570000E45B000000000000000000000000000000000000002700000000waynergy-0.16+3/protocol/xdg-shell.xml<?xml version="1.0" encoding="UTF-8"?> <protocol name="xdg_shell"> <copyright> Copyright © 2008-2013 Kristian Høgsberg Copyright © 2013 Rafael Antognolli Copyright © 2013 Jasper St. Pierre Copyright © 2010-2013 Intel Corporation Copyright © 2015-2017 Samsung Electronics Co., Ltd Copyright © 2015-2017 Red Hat Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. </copyright> <interface name="xdg_wm_base" version="5"> <description summary="create desktop-style surfaces"> The xdg_wm_base interface is exposed as a global object enabling clients to turn their wl_surfaces into windows in a desktop environment. It defines the basic functionality needed for clients and the compositor to create windows that can be dragged, resized, maximized, etc, as well as creating transient windows such as popup menus. </description> <enum name="error"> <entry name="role" value="0" summary="given wl_surface has another role"/> <entry name="defunct_surfaces" value="1" summary="xdg_wm_base was destroyed before children"/> <entry name="not_the_topmost_popup" value="2" summary="the client tried to map or destroy a non-topmost popup"/> <entry name="invalid_popup_parent" value="3" summary="the client specified an invalid popup parent surface"/> <entry name="invalid_surface_state" value="4" summary="the client provided an invalid surface state"/> <entry name="invalid_positioner" value="5" summary="the client provided an invalid positioner"/> </enum> <request name="destroy" type="destructor"> <description summary="destroy xdg_wm_base"> Destroy this xdg_wm_base object. Destroying a bound xdg_wm_base object while there are surfaces still alive created by this xdg_wm_base object instance is illegal and will result in a protocol error. </description> </request> <request name="create_positioner"> <description summary="create a positioner object"> Create a positioner object. A positioner object is used to position surfaces relative to some parent surface. See the interface description and xdg_surface.get_popup for details. </description> <arg name="id" type="new_id" interface="xdg_positioner"/> </request> <request name="get_xdg_surface"> <description summary="create a shell surface from a surface"> This creates an xdg_surface for the given surface. While xdg_surface itself is not a role, the corresponding surface may only be assigned a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is illegal to create an xdg_surface for a wl_surface which already has an assigned role and this will result in a protocol error. This creates an xdg_surface for the given surface. An xdg_surface is used as basis to define a role to a given surface, such as xdg_toplevel or xdg_popup. It also manages functionality shared between xdg_surface based surface roles. See the documentation of xdg_surface for more details about what an xdg_surface is and how it is used. </description> <arg name="id" type="new_id" interface="xdg_surface"/> <arg name="surface" type="object" interface="wl_surface"/> </request> <request name="pong"> <description summary="respond to a ping event"> A client must respond to a ping event with a pong request or the client may be deemed unresponsive. See xdg_wm_base.ping. </description> <arg name="serial" type="uint" summary="serial of the ping event"/> </request> <event name="ping"> <description summary="check if the client is alive"> The ping event asks the client if it's still alive. Pass the serial specified in the event back to the compositor by sending a "pong" request back with the specified serial. See xdg_wm_base.pong. Compositors can use this to determine if the client is still alive. It's unspecified what will happen if the client doesn't respond to the ping request, or in what timeframe. Clients should try to respond in a reasonable amount of time. A compositor is free to ping in any way it wants, but a client must always respond to any xdg_wm_base object it created. </description> <arg name="serial" type="uint" summary="pass this to the pong request"/> </event> </interface> <interface name="xdg_positioner" version="5"> <description summary="child surface positioner"> The xdg_positioner provides a collection of rules for the placement of a child surface relative to a parent surface. Rules can be defined to ensure the child surface remains within the visible area's borders, and to specify how the child surface changes its position, such as sliding along an axis, or flipping around a rectangle. These positioner-created rules are constrained by the requirement that a child surface must intersect with or be at least partially adjacent to its parent surface. See the various requests for details about possible rules. At the time of the request, the compositor makes a copy of the rules specified by the xdg_positioner. Thus, after the request is complete the xdg_positioner object can be destroyed or reused; further changes to the object will have no effect on previous usages. For an xdg_positioner object to be considered complete, it must have a non-zero size set by set_size, and a non-zero anchor rectangle set by set_anchor_rect. Passing an incomplete xdg_positioner object when positioning a surface raises an error. </description> <enum name="error"> <entry name="invalid_input" value="0" summary="invalid input provided"/> </enum> <request name="destroy" type="destructor"> <description summary="destroy the xdg_positioner object"> Notify the compositor that the xdg_positioner will no longer be used. </description> </request> <request name="set_size"> <description summary="set the size of the to-be positioned rectangle"> Set the size of the surface that is to be positioned with the positioner object. The size is in surface-local coordinates and corresponds to the window geometry. See xdg_surface.set_window_geometry. If a zero or negative size is set the invalid_input error is raised. </description> <arg name="width" type="int" summary="width of positioned rectangle"/> <arg name="height" type="int" summary="height of positioned rectangle"/> </request> <request name="set_anchor_rect"> <description summary="set the anchor rectangle within the parent surface"> Specify the anchor rectangle within the parent surface that the child surface will be placed relative to. The rectangle is relative to the window geometry as defined by xdg_surface.set_window_geometry of the parent surface. When the xdg_positioner object is used to position a child surface, the anchor rectangle may not extend outside the window geometry of the positioned child's parent surface. If a negative size is set the invalid_input error is raised. </description> <arg name="x" type="int" summary="x position of anchor rectangle"/> <arg name="y" type="int" summary="y position of anchor rectangle"/> <arg name="width" type="int" summary="width of anchor rectangle"/> <arg name="height" type="int" summary="height of anchor rectangle"/> </request> <enum name="anchor"> <entry name="none" value="0"/> <entry name="top" value="1"/> <entry name="bottom" value="2"/> <entry name="left" value="3"/> <entry name="right" value="4"/> <entry name="top_left" value="5"/> <entry name="bottom_left" value="6"/> <entry name="top_right" value="7"/> <entry name="bottom_right" value="8"/> </enum> <request name="set_anchor"> <description summary="set anchor rectangle anchor"> Defines the anchor point for the anchor rectangle. The specified anchor is used derive an anchor point that the child surface will be positioned relative to. If a corner anchor is set (e.g. 'top_left' or 'bottom_right'), the anchor point will be at the specified corner; otherwise, the derived anchor point will be centered on the specified edge, or in the center of the anchor rectangle if no edge is specified. </description> <arg name="anchor" type="uint" enum="anchor" summary="anchor"/> </request> <enum name="gravity"> <entry name="none" value="0"/> <entry name="top" value="1"/> <entry name="bottom" value="2"/> <entry name="left" value="3"/> <entry name="right" value="4"/> <entry name="top_left" value="5"/> <entry name="bottom_left" value="6"/> <entry name="top_right" value="7"/> <entry name="bottom_right" value="8"/> </enum> <request name="set_gravity"> <description summary="set child surface gravity"> Defines in what direction a surface should be positioned, relative to the anchor point of the parent surface. If a corner gravity is specified (e.g. 'bottom_right' or 'top_left'), then the child surface will be placed towards the specified gravity; otherwise, the child surface will be centered over the anchor point on any axis that had no gravity specified. </description> <arg name="gravity" type="uint" enum="gravity" summary="gravity direction"/> </request> <enum name="constraint_adjustment" bitfield="true"> <description summary="constraint adjustments"> The constraint adjustment value define ways the compositor will adjust the position of the surface, if the unadjusted position would result in the surface being partly constrained. Whether a surface is considered 'constrained' is left to the compositor to determine. For example, the surface may be partly outside the compositor's defined 'work area', thus necessitating the child surface's position be adjusted until it is entirely inside the work area. The adjustments can be combined, according to a defined precedence: 1) Flip, 2) Slide, 3) Resize. </description> <entry name="none" value="0"> <description summary="don't move the child surface when constrained"> Don't alter the surface position even if it is constrained on some axis, for example partially outside the edge of an output. </description> </entry> <entry name="slide_x" value="1"> <description summary="move along the x axis until unconstrained"> Slide the surface along the x axis until it is no longer constrained. First try to slide towards the direction of the gravity on the x axis until either the edge in the opposite direction of the gravity is unconstrained or the edge in the direction of the gravity is constrained. Then try to slide towards the opposite direction of the gravity on the x axis until either the edge in the direction of the gravity is unconstrained or the edge in the opposite direction of the gravity is constrained. </description> </entry> <entry name="slide_y" value="2"> <description summary="move along the y axis until unconstrained"> Slide the surface along the y axis until it is no longer constrained. First try to slide towards the direction of the gravity on the y axis until either the edge in the opposite direction of the gravity is unconstrained or the edge in the direction of the gravity is constrained. Then try to slide towards the opposite direction of the gravity on the y axis until either the edge in the direction of the gravity is unconstrained or the edge in the opposite direction of the gravity is constrained. </description> </entry> <entry name="flip_x" value="4"> <description summary="invert the anchor and gravity on the x axis"> Invert the anchor and gravity on the x axis if the surface is constrained on the x axis. For example, if the left edge of the surface is constrained, the gravity is 'left' and the anchor is 'left', change the gravity to 'right' and the anchor to 'right'. If the adjusted position also ends up being constrained, the resulting position of the flip_x adjustment will be the one before the adjustment. </description> </entry> <entry name="flip_y" value="8"> <description summary="invert the anchor and gravity on the y axis"> Invert the anchor and gravity on the y axis if the surface is constrained on the y axis. For example, if the bottom edge of the surface is constrained, the gravity is 'bottom' and the anchor is 'bottom', change the gravity to 'top' and the anchor to 'top'. The adjusted position is calculated given the original anchor rectangle and offset, but with the new flipped anchor and gravity values. If the adjusted position also ends up being constrained, the resulting position of the flip_y adjustment will be the one before the adjustment. </description> </entry> <entry name="resize_x" value="16"> <description summary="horizontally resize the surface"> Resize the surface horizontally so that it is completely unconstrained. </description> </entry> <entry name="resize_y" value="32"> <description summary="vertically resize the surface"> Resize the surface vertically so that it is completely unconstrained. </description> </entry> </enum> <request name="set_constraint_adjustment"> <description summary="set the adjustment to be done when constrained"> Specify how the window should be positioned if the originally intended position caused the surface to be constrained, meaning at least partially outside positioning boundaries set by the compositor. The adjustment is set by constructing a bitmask describing the adjustment to be made when the surface is constrained on that axis. If no bit for one axis is set, the compositor will assume that the child surface should not change its position on that axis when constrained. If more than one bit for one axis is set, the order of how adjustments are applied is specified in the corresponding adjustment descriptions. The default adjustment is none. </description> <arg name="constraint_adjustment" type="uint" summary="bit mask of constraint adjustments"/> </request> <request name="set_offset"> <description summary="set surface position offset"> Specify the surface position offset relative to the position of the anchor on the anchor rectangle and the anchor on the surface. For example if the anchor of the anchor rectangle is at (x, y), the surface has the gravity bottom|right, and the offset is (ox, oy), the calculated surface position will be (x + ox, y + oy). The offset position of the surface is the one used for constraint testing. See set_constraint_adjustment. An example use case is placing a popup menu on top of a user interface element, while aligning the user interface element of the parent surface with some user interface element placed somewhere in the popup surface. </description> <arg name="x" type="int" summary="surface position x offset"/> <arg name="y" type="int" summary="surface position y offset"/> </request> <!-- Version 3 additions --> <request name="set_reactive" since="3"> <description summary="continuously reconstrain the surface"> When set reactive, the surface is reconstrained if the conditions used for constraining changed, e.g. the parent window moved. If the conditions changed and the popup was reconstrained, an xdg_popup.configure event is sent with updated geometry, followed by an xdg_surface.configure event. </description> </request> <request name="set_parent_size" since="3"> <description summary=""> Set the parent window geometry the compositor should use when positioning the popup. The compositor may use this information to determine the future state the popup should be constrained using. If this doesn't match the dimension of the parent the popup is eventually positioned against, the behavior is undefined. The arguments are given in the surface-local coordinate space. </description> <arg name="parent_width" type="int" summary="future window geometry width of parent"/> <arg name="parent_height" type="int" summary="future window geometry height of parent"/> </request> <request name="set_parent_configure" since="3"> <description summary="set parent configure this is a response to"> Set the serial of an xdg_surface.configure event this positioner will be used in response to. The compositor may use this information together with set_parent_size to determine what future state the popup should be constrained using. </description> <arg name="serial" type="uint" summary="serial of parent configure event"/> </request> </interface> <interface name="xdg_surface" version="5"> <description summary="desktop user interface surface base interface"> An interface that may be implemented by a wl_surface, for implementations that provide a desktop-style user interface. It provides a base set of functionality required to construct user interface elements requiring management by the compositor, such as toplevel windows, menus, etc. The types of functionality are split into xdg_surface roles. Creating an xdg_surface does not set the role for a wl_surface. In order to map an xdg_surface, the client must create a role-specific object using, e.g., get_toplevel, get_popup. The wl_surface for any given xdg_surface can have at most one role, and may not be assigned any role not based on xdg_surface. A role must be assigned before any other requests are made to the xdg_surface object. The client must call wl_surface.commit on the corresponding wl_surface for the xdg_surface state to take effect. Creating an xdg_surface from a wl_surface which has a buffer attached or committed is a client error, and any attempts by a client to attach or manipulate a buffer prior to the first xdg_surface.configure call must also be treated as errors. After creating a role-specific object and setting it up, the client must perform an initial commit without any buffer attached. The compositor will reply with an xdg_surface.configure event. The client must acknowledge it and is then allowed to attach a buffer to map the surface. Mapping an xdg_surface-based role surface is defined as making it possible for the surface to be shown by the compositor. Note that a mapped surface is not guaranteed to be visible once it is mapped. For an xdg_surface to be mapped by the compositor, the following conditions must be met: (1) the client has assigned an xdg_surface-based role to the surface (2) the client has set and committed the xdg_surface state and the role-dependent state to the surface (3) the client has committed a buffer to the surface A newly-unmapped surface is considered to have met condition (1) out of the 3 required conditions for mapping a surface if its role surface has not been destroyed, i.e. the client must perform the initial commit again before attaching a buffer. </description> <enum name="error"> <entry name="not_constructed" value="1"/> <entry name="already_constructed" value="2"/> <entry name="unconfigured_buffer" value="3"/> <entry name="invalid_serial" value="4"/> </enum> <request name="destroy" type="destructor"> <description summary="destroy the xdg_surface"> Destroy the xdg_surface object. An xdg_surface must only be destroyed after its role object has been destroyed. </description> </request> <request name="get_toplevel"> <description summary="assign the xdg_toplevel surface role"> This creates an xdg_toplevel object for the given xdg_surface and gives the associated wl_surface the xdg_toplevel role. See the documentation of xdg_toplevel for more details about what an xdg_toplevel is and how it is used. </description> <arg name="id" type="new_id" interface="xdg_toplevel"/> </request> <request name="get_popup"> <description summary="assign the xdg_popup surface role"> This creates an xdg_popup object for the given xdg_surface and gives the associated wl_surface the xdg_popup role. If null is passed as a parent, a parent surface must be specified using some other protocol, before committing the initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. </description> <arg name="id" type="new_id" interface="xdg_popup"/> <arg name="parent" type="object" interface="xdg_surface" allow-null="true"/> <arg name="positioner" type="object" interface="xdg_positioner"/> </request> <request name="set_window_geometry"> <description summary="set the new window geometry"> The window geometry of a surface is its "visible bounds" from the user's perspective. Client-side decorations often have invisible portions like drop-shadows which should be ignored for the purposes of aligning, placing and constraining windows. The window geometry is double buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. When maintaining a position, the compositor should treat the (x, y) coordinate of the window geometry as the top left corner of the window. A client changing the (x, y) window geometry coordinate should in general not alter the position of the window. Once the window geometry of the surface is set, it is not possible to unset it, and it will remain the same until set_window_geometry is called again, even if a new subsurface or buffer is attached. If never set, the value is the full bounds of the surface, including any subsurfaces. This updates dynamically on every commit. This unset is meant for extremely simple clients. The arguments are given in the surface-local coordinate space of the wl_surface associated with this xdg_surface. The width and height must be greater than zero. Setting an invalid size will raise an error. When applied, the effective window geometry will be the set window geometry clamped to the bounding rectangle of the combined geometry of the surface of the xdg_surface and the associated subsurfaces. </description> <arg name="x" type="int"/> <arg name="y" type="int"/> <arg name="width" type="int"/> <arg name="height" type="int"/> </request> <request name="ack_configure"> <description summary="ack a configure event"> When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. For instance, for toplevel surfaces the compositor might use this information to move a surface to the top left only when the client has drawn itself for the maximized or fullscreen state. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. A client is not required to commit immediately after sending an ack_configure request - it may even ack_configure several times before its next surface commit. A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. Sending an ack_configure request consumes the serial number sent with the request, as well as serial numbers sent by all configure events sent on this xdg_surface prior to the configure event referenced by the committed serial. It is an error to issue multiple ack_configure requests referencing a serial from the same configure event, or to issue an ack_configure request referencing a serial from a configure event issued before the event identified by the last ack_configure request for the same xdg_surface. Doing so will raise an invalid_serial error. </description> <arg name="serial" type="uint" summary="the serial from the configure event"/> </request> <event name="configure"> <description summary="suggest a surface change"> The configure event marks the end of a configure sequence. A configure sequence is a set of one or more events configuring the state of the xdg_surface, including the final xdg_surface.configure event. Where applicable, xdg_surface surface roles will during a configure sequence extend this event as a latched state sent as events before the xdg_surface.configure event. Such events should be considered to make up a set of atomically applied configuration states, where the xdg_surface.configure commits the accumulated state. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. If the client receives multiple configure events before it can respond to one, it is free to discard all but the last event it received. </description> <arg name="serial" type="uint" summary="serial of the configure event"/> </event> </interface> <interface name="xdg_toplevel" version="5"> <description summary="toplevel surface"> This interface defines an xdg_surface role which allows a surface to, among other things, set window-like properties such as maximize, fullscreen, and minimize, set application-specific metadata like title and id, and well as trigger user interactive operations such as interactive resize and move. Unmapping an xdg_toplevel means that the surface cannot be shown by the compositor until it is explicitly mapped again. All active operations (e.g., move, resize) are canceled and all attributes (e.g. title, state, stacking, ...) are discarded for an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to the state it had right after xdg_surface.get_toplevel. The client can re-map the toplevel by perfoming a commit without any buffer attached, waiting for a configure event and handling it as usual (see xdg_surface description). Attaching a null buffer to a toplevel unmaps the surface. </description> <request name="destroy" type="destructor"> <description summary="destroy the xdg_toplevel"> This request destroys the role surface and unmaps the surface; see "Unmapping" behavior in interface section for details. </description> </request> <enum name="error"> <entry name="invalid_resize_edge" value="0" summary="provided value is not a valid variant of the resize_edge enum"/> <entry name="invalid_parent" value="1" summary="invalid parent toplevel"/> </enum> <request name="set_parent"> <description summary="set the parent of this surface"> Set the "parent" of this surface. This surface should be stacked above the parent surface and all other ancestor surfaces. Parent surfaces should be set on dialogs, toolboxes, or other "auxiliary" surfaces, so that the parent is raised when the dialog is raised. Setting a null parent for a child surface unsets its parent. Setting a null parent for a surface which currently has no parent is a no-op. Only mapped surfaces can have child surfaces. Setting a parent which is not mapped is equivalent to setting a null parent. If a surface becomes unmapped, its children's parent is set to the parent of the now-unmapped surface. If the now-unmapped surface has no parent, its children's parent is unset. If the now-unmapped surface becomes mapped again, its parent-child relationship is not restored. The parent toplevel must not be one of the child toplevel's descendants, and the parent must be different from the child toplevel, otherwise the invalid_parent protocol error is raised. </description> <arg name="parent" type="object" interface="xdg_toplevel" allow-null="true"/> </request> <request name="set_title"> <description summary="set surface title"> Set a short title for the surface. This string may be used to identify the surface in a task bar, window list, or other user interface elements provided by the compositor. The string must be encoded in UTF-8. </description> <arg name="title" type="string"/> </request> <request name="set_app_id"> <description summary="set application ID"> Set an application identifier for the surface. The app ID identifies the general class of applications to which the surface belongs. The compositor can use this to group multiple surfaces together, or to determine how to launch a new application. For D-Bus activatable applications, the app ID is used as the D-Bus service name. The compositor shell will try to group application surfaces together by their app ID. As a best practice, it is suggested to select app ID's that match the basename of the application's .desktop file. For example, "org.freedesktop.FooViewer" where the .desktop file is "org.freedesktop.FooViewer.desktop". Like other properties, a set_app_id request can be sent after the xdg_toplevel has been mapped to update the property. See the desktop-entry specification [0] for more details on application identifiers and how they relate to well-known D-Bus names and .desktop files. [0] http://standards.freedesktop.org/desktop-entry-spec/ </description> <arg name="app_id" type="string"/> </request> <request name="show_window_menu"> <description summary="show the window menu"> Clients implementing client-side decorations might want to show a context menu when right-clicking on the decorations, giving the user a menu that they can use to maximize or minimize the window. This request asks the compositor to pop up such a window menu at the given position, relative to the local surface coordinates of the parent surface. There are no guarantees as to what menu items the window menu contains. This request must be used in response to some sort of user action like a button press, key press, or touch down event. </description> <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/> <arg name="serial" type="uint" summary="the serial of the user event"/> <arg name="x" type="int" summary="the x position to pop up the window menu at"/> <arg name="y" type="int" summary="the y position to pop up the window menu at"/> </request> <request name="move"> <description summary="start an interactive move"> Start an interactive, user-driven move of the surface. This request must be used in response to some sort of user action like a button press, key press, or touch down event. The passed serial is used to determine the type of interactive move (touch, pointer, etc). The server may ignore move requests depending on the state of the surface (e.g. fullscreen or maximized), or if the passed serial is no longer valid. If triggered, the surface will lose the focus of the device (wl_pointer, wl_touch, etc) used for the move. It is up to the compositor to visually indicate that the move is taking place, such as updating a pointer cursor, during the move. There is no guarantee that the device focus will return when the move is completed. </description> <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/> <arg name="serial" type="uint" summary="the serial of the user event"/> </request> <enum name="resize_edge"> <description summary="edge values for resizing"> These values are used to indicate which edge of a surface is being dragged in a resize operation. </description> <entry name="none" value="0"/> <entry name="top" value="1"/> <entry name="bottom" value="2"/> <entry name="left" value="4"/> <entry name="top_left" value="5"/> <entry name="bottom_left" value="6"/> <entry name="right" value="8"/> <entry name="top_right" value="9"/> <entry name="bottom_right" value="10"/> </enum> <request name="resize"> <description summary="start an interactive resize"> Start a user-driven, interactive resize of the surface. This request must be used in response to some sort of user action like a button press, key press, or touch down event. The passed serial is used to determine the type of interactive resize (touch, pointer, etc). The server may ignore resize requests depending on the state of the surface (e.g. fullscreen or maximized). If triggered, the client will receive configure events with the "resize" state enum value and the expected sizes. See the "resize" enum value for more details about what is required. The client must also acknowledge configure events using "ack_configure". After the resize is completed, the client will receive another "configure" event without the resize state. If triggered, the surface also will lose the focus of the device (wl_pointer, wl_touch, etc) used for the resize. It is up to the compositor to visually indicate that the resize is taking place, such as updating a pointer cursor, during the resize. There is no guarantee that the device focus will return when the resize is completed. The edges parameter specifies how the surface should be resized, and is one of the values of the resize_edge enum. Values not matching a variant of the enum will cause a protocol error. The compositor may use this information to update the surface position for example when dragging the top left corner. The compositor may also use this information to adapt its behavior, e.g. choose an appropriate cursor image. </description> <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/> <arg name="serial" type="uint" summary="the serial of the user event"/> <arg name="edges" type="uint" enum="resize_edge" summary="which edge or corner is being dragged"/> </request> <enum name="state"> <description summary="types of state on the surface"> The different state values used on the surface. This is designed for state values like maximized, fullscreen. It is paired with the configure event to ensure that both the client and the compositor setting the state can be synchronized. States set in this way are double-buffered. They will get applied on the next commit. </description> <entry name="maximized" value="1" summary="the surface is maximized"> <description summary="the surface is maximized"> The surface is maximized. The window geometry specified in the configure event must be obeyed by the client. The client should draw without shadow or other decoration outside of the window geometry. </description> </entry> <entry name="fullscreen" value="2" summary="the surface is fullscreen"> <description summary="the surface is fullscreen"> The surface is fullscreen. The window geometry specified in the configure event is a maximum; the client cannot resize beyond it. For a surface to cover the whole fullscreened area, the geometry dimensions must be obeyed by the client. For more details, see xdg_toplevel.set_fullscreen. </description> </entry> <entry name="resizing" value="3" summary="the surface is being resized"> <description summary="the surface is being resized"> The surface is being resized. The window geometry specified in the configure event is a maximum; the client cannot resize beyond it. Clients that have aspect ratio or cell sizing configuration can use a smaller size, however. </description> </entry> <entry name="activated" value="4" summary="the surface is now activated"> <description summary="the surface is now activated"> Client window decorations should be painted as if the window is active. Do not assume this means that the window actually has keyboard or pointer focus. </description> </entry> <entry name="tiled_left" value="5" since="2"> <description summary="the surface’s left edge is tiled"> The window is currently in a tiled layout and the left edge is considered to be adjacent to another part of the tiling grid. </description> </entry> <entry name="tiled_right" value="6" since="2"> <description summary="the surface’s right edge is tiled"> The window is currently in a tiled layout and the right edge is considered to be adjacent to another part of the tiling grid. </description> </entry> <entry name="tiled_top" value="7" since="2"> <description summary="the surface’s top edge is tiled"> The window is currently in a tiled layout and the top edge is considered to be adjacent to another part of the tiling grid. </description> </entry> <entry name="tiled_bottom" value="8" since="2"> <description summary="the surface’s bottom edge is tiled"> The window is currently in a tiled layout and the bottom edge is considered to be adjacent to another part of the tiling grid. </description> </entry> </enum> <request name="set_max_size"> <description summary="set the maximum size"> Set a maximum size for the window. The client can specify a maximum size so that the compositor does not try to configure the window beyond this size. The width and height arguments are in window geometry coordinates. See xdg_surface.set_window_geometry. Values set in this way are double-buffered. They will get applied on the next commit. The compositor can use this information to allow or disallow different states like maximize or fullscreen and draw accurate animations. Similarly, a tiling window manager may use this information to place and resize client windows in a more effective way. The client should not rely on the compositor to obey the maximum size. The compositor may decide to ignore the values set by the client and request a larger size. If never set, or a value of zero in the request, means that the client has no expected maximum size in the given dimension. As a result, a client wishing to reset the maximum size to an unspecified state can use zero for width and height in the request. Requesting a maximum size to be smaller than the minimum size of a surface is illegal and will result in a protocol error. The width and height must be greater than or equal to zero. Using strictly negative values for width and height will result in a protocol error. </description> <arg name="width" type="int"/> <arg name="height" type="int"/> </request> <request name="set_min_size"> <description summary="set the minimum size"> Set a minimum size for the window. The client can specify a minimum size so that the compositor does not try to configure the window below this size. The width and height arguments are in window geometry coordinates. See xdg_surface.set_window_geometry. Values set in this way are double-buffered. They will get applied on the next commit. The compositor can use this information to allow or disallow different states like maximize or fullscreen and draw accurate animations. Similarly, a tiling window manager may use this information to place and resize client windows in a more effective way. The client should not rely on the compositor to obey the minimum size. The compositor may decide to ignore the values set by the client and request a smaller size. If never set, or a value of zero in the request, means that the client has no expected minimum size in the given dimension. As a result, a client wishing to reset the minimum size to an unspecified state can use zero for width and height in the request. Requesting a minimum size to be larger than the maximum size of a surface is illegal and will result in a protocol error. The width and height must be greater than or equal to zero. Using strictly negative values for width and height will result in a protocol error. </description> <arg name="width" type="int"/> <arg name="height" type="int"/> </request> <request name="set_maximized"> <description summary="maximize the window"> Maximize the surface. After requesting that the surface should be maximized, the compositor will respond by emitting a configure event. Whether this configure actually sets the window maximized is subject to compositor policies. The client must then update its content, drawing in the configured state. The client must also acknowledge the configure when committing the new content (see ack_configure). It is up to the compositor to decide how and where to maximize the surface, for example which output and what region of the screen should be used. If the surface was already maximized, the compositor will still emit a configure event with the "maximized" state. If the surface is in a fullscreen state, this request has no direct effect. It may alter the state the surface is returned to when unmaximized unless overridden by the compositor. </description> </request> <request name="unset_maximized"> <description summary="unmaximize the window"> Unmaximize the surface. After requesting that the surface should be unmaximized, the compositor will respond by emitting a configure event. Whether this actually un-maximizes the window is subject to compositor policies. If available and applicable, the compositor will include the window geometry dimensions the window had prior to being maximized in the configure event. The client must then update its content, drawing it in the configured state. The client must also acknowledge the configure when committing the new content (see ack_configure). It is up to the compositor to position the surface after it was unmaximized; usually the position the surface had before maximizing, if applicable. If the surface was already not maximized, the compositor will still emit a configure event without the "maximized" state. If the surface is in a fullscreen state, this request has no direct effect. It may alter the state the surface is returned to when unmaximized unless overridden by the compositor. </description> </request> <request name="set_fullscreen"> <description summary="set the window as fullscreen on an output"> Make the surface fullscreen. After requesting that the surface should be fullscreened, the compositor will respond by emitting a configure event. Whether the client is actually put into a fullscreen state is subject to compositor policies. The client must also acknowledge the configure when committing the new content (see ack_configure). The output passed by the request indicates the client's preference as to which display it should be set fullscreen on. If this value is NULL, it's up to the compositor to choose which display will be used to map this surface. If the surface doesn't cover the whole output, the compositor will position the surface in the center of the output and compensate with with border fill covering the rest of the output. The content of the border fill is undefined, but should be assumed to be in some way that attempts to blend into the surrounding area (e.g. solid black). If the fullscreened surface is not opaque, the compositor must make sure that other screen content not part of the same surface tree (made up of subsurfaces, popups or similarly coupled surfaces) are not visible below the fullscreened surface. </description> <arg name="output" type="object" interface="wl_output" allow-null="true"/> </request> <request name="unset_fullscreen"> <description summary="unset the window as fullscreen"> Make the surface no longer fullscreen. After requesting that the surface should be unfullscreened, the compositor will respond by emitting a configure event. Whether this actually removes the fullscreen state of the client is subject to compositor policies. Making a surface unfullscreen sets states for the surface based on the following: * the state(s) it may have had before becoming fullscreen * any state(s) decided by the compositor * any state(s) requested by the client while the surface was fullscreen The compositor may include the previous window geometry dimensions in the configure event, if applicable. The client must also acknowledge the configure when committing the new content (see ack_configure). </description> </request> <request name="set_minimized"> <description summary="set the window as minimized"> Request that the compositor minimize your surface. There is no way to know if the surface is currently minimized, nor is there any way to unset minimization on this surface. If you are looking to throttle redrawing when minimized, please instead use the wl_surface.frame event for this, as this will also work with live previews on windows in Alt-Tab, Expose or similar compositor features. </description> </request> <event name="configure"> <description summary="suggest a surface change"> This configure event asks the client to resize its toplevel surface or to change its state. The configured state should not be applied immediately. See xdg_surface.configure for details. The width and height arguments specify a hint to the window about how its surface should be resized in window geometry coordinates. See set_window_geometry. If the width or height arguments are zero, it means the client should decide its own window dimension. This may happen when the compositor needs to configure the state of the surface but doesn't have any information about any previous or expected dimension. The states listed in the event specify how the width/height arguments should be interpreted, and possibly how it should be drawn. Clients must send an ack_configure in response to this event. See xdg_surface.configure and xdg_surface.ack_configure for details. </description> <arg name="width" type="int"/> <arg name="height" type="int"/> <arg name="states" type="array"/> </event> <event name="close"> <description summary="surface wants to be closed"> The close event is sent by the compositor when the user wants the surface to be closed. This should be equivalent to the user clicking the close button in client-side decorations, if your application has any. This is only a request that the user intends to close the window. The client may choose to ignore this request, or show a dialog to ask the user to save their data, etc. </description> </event> <!-- Version 4 additions --> <event name="configure_bounds" since="4"> <description summary="recommended window geometry bounds"> The configure_bounds event may be sent prior to a xdg_toplevel.configure event to communicate the bounds a window geometry size is recommended to constrain to. The passed width and height are in surface coordinate space. If width and height are 0, it means bounds is unknown and equivalent to as if no configure_bounds event was ever sent for this surface. The bounds can for example correspond to the size of a monitor excluding any panels or other shell components, so that a surface isn't created in a way that it cannot fit. The bounds may change at any point, and in such a case, a new xdg_toplevel.configure_bounds will be sent, followed by xdg_toplevel.configure and xdg_surface.configure. </description> <arg name="width" type="int"/> <arg name="height" type="int"/> </event> <!-- Version 5 additions --> <enum name="wm_capabilities" since="5"> <entry name="window_menu" value="1" summary="show_window_menu is available"/> <entry name="maximize" value="2" summary="set_maximized and unset_maximized are available"/> <entry name="fullscreen" value="3" summary="set_fullscreen and unset_fullscreen are available"/> <entry name="minimize" value="4" summary="set_minimized is available"/> </enum> <event name="wm_capabilities" since="5"> <description summary="compositor capabilities"> This event advertises the capabilities supported by the compositor. If a capability isn't supported, clients should hide or disable the UI elements that expose this functionality. For instance, if the compositor doesn't advertise support for minimized toplevels, a button triggering the set_minimized request should not be displayed. The compositor will ignore requests it doesn't support. For instance, a compositor which doesn't advertise support for minimized will ignore set_minimized requests. Compositors must send this event once before the first xdg_surface.configure event. When the capabilities change, compositors must send this event again and then send an xdg_surface.configure event. The configured state should not be applied immediately. See xdg_surface.configure for details. The capabilities are sent as an array of 32-bit unsigned integers in native endianness. </description> <arg name="capabilities" type="array" summary="array of 32-bit capabilities"/> </event> </interface> <interface name="xdg_popup" version="5"> <description summary="short-lived, popup surfaces for menus"> A popup surface is a short-lived, temporary surface. It can be used to implement for example menus, popovers, tooltips and other similar user interface concepts. A popup can be made to take an explicit grab. See xdg_popup.grab for details. When the popup is dismissed, a popup_done event will be sent out, and at the same time the surface will be unmapped. See the xdg_popup.popup_done event for details. Explicitly destroying the xdg_popup object will also dismiss the popup and unmap the surface. Clients that want to dismiss the popup when another surface of their own is clicked should dismiss the popup using the destroy request. A newly created xdg_popup will be stacked on top of all previously created xdg_popup surfaces associated with the same xdg_toplevel. The parent of an xdg_popup must be mapped (see the xdg_surface description) before the xdg_popup itself. The client must call wl_surface.commit on the corresponding wl_surface for the xdg_popup state to take effect. </description> <enum name="error"> <entry name="invalid_grab" value="0" summary="tried to grab after being mapped"/> </enum> <request name="destroy" type="destructor"> <description summary="remove xdg_popup interface"> This destroys the popup. Explicitly destroying the xdg_popup object will also dismiss the popup, and unmap the surface. If this xdg_popup is not the "topmost" popup, a protocol error will be sent. </description> </request> <request name="grab"> <description summary="make the popup take an explicit grab"> This request makes the created popup take an explicit grab. An explicit grab will be dismissed when the user dismisses the popup, or when the client destroys the xdg_popup. This can be done by the user clicking outside the surface, using the keyboard, or even locking the screen through closing the lid or a timeout. If the compositor denies the grab, the popup will be immediately dismissed. This request must be used in response to some sort of user action like a button press, key press, or touch down event. The serial number of the event should be passed as 'serial'. The parent of a grabbing popup must either be an xdg_toplevel surface or another xdg_popup with an explicit grab. If the parent is another xdg_popup it means that the popups are nested, with this popup now being the topmost popup. Nested popups must be destroyed in the reverse order they were created in, e.g. the only popup you are allowed to destroy at all times is the topmost one. When compositors choose to dismiss a popup, they may dismiss every nested grabbing popup as well. When a compositor dismisses popups, it will follow the same dismissing order as required from the client. If the topmost grabbing popup is destroyed, the grab will be returned to the parent of the popup, if that parent previously had an explicit grab. If the parent is a grabbing popup which has already been dismissed, this popup will be immediately dismissed. If the parent is a popup that did not take an explicit grab, an error will be raised. During a popup grab, the client owning the grab will receive pointer and touch events for all their surfaces as normal (similar to an "owner-events" grab in X11 parlance), while the top most grabbing popup will always have keyboard focus. </description> <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/> <arg name="serial" type="uint" summary="the serial of the user event"/> </request> <event name="configure"> <description summary="configure the popup surface"> This event asks the popup surface to configure itself given the configuration. The configured state should not be applied immediately. See xdg_surface.configure for details. The x and y arguments represent the position the popup was placed at given the xdg_positioner rule, relative to the upper left corner of the window geometry of the parent surface. For version 2 or older, the configure event for an xdg_popup is only ever sent once for the initial configuration. Starting with version 3, it may be sent again if the popup is setup with an xdg_positioner with set_reactive requested, or in response to xdg_popup.reposition requests. </description> <arg name="x" type="int" summary="x position relative to parent surface window geometry"/> <arg name="y" type="int" summary="y position relative to parent surface window geometry"/> <arg name="width" type="int" summary="window geometry width"/> <arg name="height" type="int" summary="window geometry height"/> </event> <event name="popup_done"> <description summary="popup interaction is done"> The popup_done event is sent out when a popup is dismissed by the compositor. The client should destroy the xdg_popup object at this point. </description> </event> <!-- Version 3 additions --> <request name="reposition" since="3"> <description summary="recalculate the popup's location"> Reposition an already-mapped popup. The popup will be placed given the details in the passed xdg_positioner object, and a xdg_popup.repositioned followed by xdg_popup.configure and xdg_surface.configure will be emitted in response. Any parameters set by the previous positioner will be discarded. The passed token will be sent in the corresponding xdg_popup.repositioned event. The new popup position will not take effect until the corresponding configure event is acknowledged by the client. See xdg_popup.repositioned for details. The token itself is opaque, and has no other special meaning. If multiple reposition requests are sent, the compositor may skip all but the last one. If the popup is repositioned in response to a configure event for its parent, the client should send an xdg_positioner.set_parent_configure and possibly an xdg_positioner.set_parent_size request to allow the compositor to properly constrain the popup. If the popup is repositioned together with a parent that is being resized, but not in response to a configure event, the client should send an xdg_positioner.set_parent_size request. </description> <arg name="positioner" type="object" interface="xdg_positioner"/> <arg name="token" type="uint" summary="reposition request token"/> </request> <event name="repositioned" since="3"> <description summary="signal the completion of a repositioned request"> The repositioned event is sent as part of a popup configuration sequence, together with xdg_popup.configure and lastly xdg_surface.configure to notify the completion of a reposition request. The repositioned event is to notify about the completion of a xdg_popup.reposition request. The token argument is the token passed in the xdg_popup.reposition request. Immediately after this event is emitted, xdg_popup.configure and xdg_surface.configure will be sent with the updated size and position, as well as a new configure serial. The client should optionally update the content of the popup, but must acknowledge the new popup configuration for the new position to take effect. See xdg_surface.ack_configure for details. </description> <arg name="token" type="uint" summary="reposition request token"/> </event> </interface> </protocol> 07070100000026000041ED00000000000000000000000264751F5700000000000000000000000000000000000000000000001400000000waynergy-0.16+3/src07070100000027000081A400000000000000000000000164751F57000003E7000000000000000000000000000000000000002200000000waynergy-0.16+3/src/clip-update.c#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <stdbool.h> #include <sys/socket.h> #include <sys/un.h> #include "ssb.h" #include "xmem.h" #include "fdio_full.h" int main(int argc, char **argv) { char *path = argv[2]; char cid = argv[1][0]; struct ssb s = {0}; struct sockaddr_un sa = {0}; int sock; if (!ssb_readfile(&s, stdin)) { return EXIT_FAILURE; } /* write out the clipboard sequence -- char for ID, size_t for size, then data */ strncpy(sa.sun_path, path, sizeof(sa.sun_path) - 1); sa.sun_family = AF_UNIX; if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { return EXIT_FAILURE; } if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) == -1) { return EXIT_FAILURE; } if (!write_full(sock, &cid, 1, 0)) { return EXIT_FAILURE; } if (!write_full(sock, &(s.pos), sizeof(s.pos), 0)) { return EXIT_FAILURE; } if (!write_full(sock, s.buf, s.pos, 0)) { return EXIT_FAILURE; } shutdown(sock, SHUT_RDWR); close(sock); return EXIT_SUCCESS; } 07070100000028000081A400000000000000000000000164751F5700001356000000000000000000000000000000000000001B00000000waynergy-0.16+3/src/clip.c#include "clip.h" #include "wayland.h" #include "net.h" extern uSynergyContext synContext; extern char **environ; int clipMonitorFd; struct sockaddr_un clipMonitorAddr; pid_t clipMonitorPid[2]; /* check for wl-clipboard's presence */ bool clipHaveWlClipboard(void) { bool ret = true; pid_t pid; int status; char *argv_0[] = { "wl-paste", "-v", NULL }; char *argv_1[] = { "wl-copy", "-v", NULL }; char **argv[] = {argv_0, argv_1}; sigWaitSIGCHLD(true); for (int i = 0; i < 2; ++i) { if (posix_spawnp(&pid, argv[i][0], NULL, NULL, argv[i], environ)) { ret = false; goto done; } if (waitpid(pid, &status, 0) != pid) { ret = false; goto done; } if (status) { ret = false; goto done; } logDbg("Found %s", argv[i][0]); } done: sigWaitSIGCHLD(false); return ret; } /* set up sockets */ bool clipSetupSockets() { char *path; path = osGetRuntimePath("waynergy-clip-sock"); strncpy(clipMonitorAddr.sun_path, path, sizeof(clipMonitorAddr.sun_path) - 1); free(path); unlink(clipMonitorAddr.sun_path); clipMonitorAddr.sun_family = AF_UNIX; if ((clipMonitorFd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { logPErr("socket"); return false; } if (bind(clipMonitorFd, (struct sockaddr *)&clipMonitorAddr, sizeof(clipMonitorAddr)) == -1) { logPErr("bind"); return false; } if (listen(clipMonitorFd, 8) == -1) { logPErr("listen"); return false; } for (int i = POLLFD_CLIP_UPDATER; i < POLLFD_COUNT; ++i) { netPollFd[i].fd = -1; } return true; } /* spawn wl-paste watchers */ bool clipSpawnMonitors(void) { char *argv_0[] = { "wl-paste", "-n", "-w", "waynergy-clip-update", "c", clipMonitorAddr.sun_path, NULL }; char *argv_1[] = { "wl-paste", "-n", "--primary", "-w", "waynergy-clip-update", "p", clipMonitorAddr.sun_path, NULL }; char **argv[] = { argv_0, argv_1 }; /* kill the other crap on our socket */ char *killcmd; int killret; xasprintf(&killcmd, "pkill -f 'wlpaste.*%s'", clipMonitorAddr.sun_path); if ((killret = system(killcmd))) { logWarn("Could not kill lingering wlpaste instances: %d\n", killret); } free(killcmd); for (int i = 0; i < 2; ++i) { if (posix_spawnp(clipMonitorPid + i,"wl-paste",NULL,NULL,argv[i],environ)) { logPErr("spawn"); return false; } } return true; } /* process poll data */ void clipMonitorPollProc(struct pollfd *pfd) { size_t len; char c_id, *buf = NULL; enum uSynergyClipboardId id; if (pfd->revents & POLLIN) { if (pfd->fd == clipMonitorFd) { for (int i = POLLFD_CLIP_UPDATER; i < POLLFD_COUNT; ++i) { if (netPollFd[i].fd == -1) { logDbg("Accepting"); netPollFd[i].fd = accept(clipMonitorFd, NULL, NULL); if (netPollFd[i].fd == -1) { logPErr("accept"); switch (errno) { case ECONNABORTED: case EINTR: break; default: logErr("clipboard update socket is broken"); close(clipMonitorFd); clipMonitorFd = -1; netPollFd[POLLFD_CLIP_MON].fd = -1; break; } } return; } } logErr("No free updater file descriptors -- doing nothing"); } else { if (!read_full(pfd->fd, &c_id, 1, 0)) { logPErr("Could not read clipboard ID"); goto done; } if (!read_full(pfd->fd, &len, sizeof(len), 0)) { logPErr("Could not read clipboard data length"); goto done; } buf = xmalloc(len); if (!read_full(pfd->fd, buf, len, 0)) { logPErr("Could not read clipboard data"); goto done; } if (c_id == 'p') { id = SYNERGY_CLIPBOARD_SELECTION; } else if (c_id == 'c') { id = SYNERGY_CLIPBOARD_CLIPBOARD; } else { logErr("Unknown clipboard ID %c", c_id); goto done; } logDbg("Clipboard data read for %c: %zd bytes", c_id, len); uSynergyUpdateClipBuf(&synContext, id , len, buf); done: shutdown(pfd->fd, SHUT_RDWR); close(pfd->fd); pfd->fd = -1; free(buf); } } return; } /* set wayland clipboard with wl-copy */ bool clipWlCopy(enum uSynergyClipboardId id, const unsigned char *data, size_t len) { pid_t pid; posix_spawn_file_actions_t fa; char *argv_0[] = { "wl-copy", "-f", NULL}; char *argv_1[] = { "wl-copy", "-f", "--primary", NULL }; char **argv[] = {argv_0, argv_1}; /* create the pipe we will use to communicate with it */ int fd[2]; errno = 0; if (pipe(fd) == -1) { perror("pipe"); return false; } posix_spawn_file_actions_init(&fa); posix_spawn_file_actions_adddup2(&fa, fd[0], STDIN_FILENO); posix_spawn_file_actions_addclose(&fa, fd[1]); /* now we can spawn */ errno = 0; if (posix_spawnp(&pid, "wl-copy", &fa, NULL, argv[id], environ)) { logPErr("wl-copy spawn"); close(fd[0]); close(fd[1]); posix_spawn_file_actions_destroy(&fa); return false; } posix_spawn_file_actions_destroy(&fa); close(fd[0]); /* write to child process */ write_full(fd[1], data, len, 0); close(fd[1]); return true; } 07070100000029000081A400000000000000000000000164751F5700001FFD000000000000000000000000000000000000001D00000000waynergy-0.16+3/src/config.c#include "config.h" #include "os.h" #include "xmem.h" #include "fdio_full.h" #include "log.h" #include "ssb.h" #include <stdbool.h> #include <dirent.h> #include <sys/stat.h> #define INI_IMPLEMENTATION #define INI_MALLOC(ctx, size) xcalloc(1, size) #define INI_FREE(ctx, ptr) free(ptr) #include "ini.h" static ini_t *config_ini = NULL; static char *read_file_dumb(char *path) { FILE *f; struct ssb s = {0}; if (!path) return false; if (!(f = fopen(path, "r"))) { return false; } if (!(ssb_readfile(&s, f) && ssb_truncate(&s, s.pos))) { ssb_free(&s); } return s.buf; } static char *try_read_ini(char *name) { char *section_buf = NULL; char *prop_buf = name; const char *val = NULL; int section = INI_GLOBAL_SECTION; int prop; size_t i; if (!config_ini) return NULL; for (i = 0; name[i]; ++i) { if (name[i] == '/') { section_buf = xstrdup(name); section_buf[i] = '\0'; prop_buf += i + 1; break; } } if (section_buf) { if ((section = ini_find_section(config_ini, section_buf, strlen(section_buf))) == INI_NOT_FOUND) { logDbg("Section %s not found in INI", section_buf); goto done; } } if ((prop = ini_find_property(config_ini, section, prop_buf, strlen(prop_buf))) == INI_NOT_FOUND) { logDbg("Property %s not found in INI", prop_buf); goto done; } if (!(val = ini_property_value(config_ini, section, prop))) { logDbg("Could not retrieve INI value"); goto done; } if (val) { logDbg("Got value from INI: %s: %s", name, val); } done: if (section_buf) free(section_buf); return val ? xstrdup(val) : NULL; } static int read_full_section_dir(char *name, char ***key, char ***val) { int ret = 0, count = 0; DIR *dir = NULL; struct dirent *ent; char *dir_path = NULL; char *path = NULL; struct stat sbuf; dir_path = osGetHomeConfigPath(name); if (!(dir = opendir(dir_path))) { ret = -1; goto done; } *key = xmalloc(sizeof((*key)[0])); *val = xmalloc(sizeof((*val)[0])); while ((ent = readdir(dir))) { xasprintf(&path, "%s/%s", dir_path, ent->d_name); if (stat(path, &sbuf) == -1) { ret = -1; goto done; } if (!S_ISREG(sbuf.st_mode)) { free(path); path = NULL; continue; } *key = xreallocarray(*key, ++count + 1, sizeof((*key)[0])); *val = xreallocarray(*val, count + 1, sizeof((*val)[0])); (*key)[count - 1] = strdup(ent->d_name); (*val)[count - 1] = read_file_dumb(path); free(path); path = NULL; } (*key)[count] = NULL; (*val)[count] = NULL; ret = count; goto done; done: if (dir) closedir(dir); free(dir_path); return ret; } static int read_full_section_ini(char *name, char ***key, char ***val) { int count, i, section; if (!config_ini) { return -1; } if (!name) { section = INI_GLOBAL_SECTION; } else { section = ini_find_section(config_ini, name, strlen(name)); } if (section == INI_NOT_FOUND) { logDbg("Could not find section %s", name); return -1; } count = ini_property_count(config_ini, section); *key = xcalloc(count + 1, sizeof((*key)[0])); *val = xcalloc(count + 1, sizeof((*val)[0])); for (i = 0; i < count; ++i) { (*key)[i] = strdup(ini_property_name(config_ini, section, i)); (*val)[i] = strdup(ini_property_value(config_ini, section, i)); } return count; } int configReadFullSection(char *name, char ***key, char ***val) { int count; *val = NULL; *key = NULL; if ((count = read_full_section_ini(name, key, val)) != -1) { return count; } return read_full_section_dir(name, key, val); } char *configReadFile(char *name) { char *buf, *path; //We try the INI approach first if ((buf = try_read_ini(name))) { logDbg("Using INI value for %s: %s", name, buf); return buf; } //and proceed from there. if (!(path = osGetHomeConfigPath(name))) return NULL; buf = read_file_dumb(path); free(path); return buf; } char **configReadLines(char *name) { char *path, *buf = NULL; size_t len = 24; //allocated; size_t pos = 0; size_t n; //line length -- probably don't need it char **line; FILE *f; /* try INI first */ if ((buf = try_read_ini(name))) { logDbg("Using INI single-line value for %s: %s", name, buf); line = xcalloc(sizeof(*line), 2); line[0] = buf; return line; } if (!(path = osGetHomeConfigPath(name))) return NULL; if (!(f = fopen(path, "r"))) { free(path); return NULL; } line = xcalloc(sizeof(*line), len); for (pos = 0; ;++pos) { if (pos == len) { len *= 3; len /= 2; line = xrealloc(line, sizeof(*line) * len); memset(line + pos, 0, sizeof(*line) * (len - pos)); } n = 0; if (getline(line + pos, &n, f) == -1) { break; } } line[pos] = NULL; free(path); return xrealloc(line, sizeof(*line) * (pos + 1)); } /* combine ini files, overwriting conflicting values */ static void ini_cat(ini_t *dst, ini_t *src) { int sec_src, sec_dst, prop_src, prop_dst; const char *sec_name, *prop_name; for (sec_src = 0; sec_src < ini_section_count(src); ++sec_src) { sec_name = ini_section_name(src, sec_src); if ((sec_dst = ini_find_section(dst, sec_name, 0)) == INI_NOT_FOUND) { sec_dst = ini_section_add(dst, sec_name, 0); } for (prop_src = 0; prop_src < ini_property_count(src, sec_src); ++prop_src) { prop_name = ini_property_name(src, sec_src, prop_src); if ((prop_dst = ini_find_property(dst, sec_dst, prop_name, 0)) == INI_NOT_FOUND) { ini_property_add(dst, sec_dst, prop_name, 0, ini_property_value(src, sec_src, prop_src), 0); } else { ini_property_value_set(dst, sec_dst, prop_dst, ini_property_value(src, sec_src, prop_src), 0); } } } } /* read all INI files from config.ini.d, combining them into a destination * * slightly hackish because it's treating 'config.ini.d' as a section*/ static bool ini_d_load(ini_t *dst) { bool ret = true; char *suffix; char **name = NULL, **txt = NULL; int count, i; ini_t *src; if ((count = read_full_section_dir("config.ini.d", &name, &txt)) == -1) { ret = false; goto done; } for (i = 0; i < count; ++i) { /* we only want INI files */ if ((suffix = strstr(name[i], ".ini")) && *(suffix + 4) == '\0') { if ((src = ini_load(txt[i], NULL))) { ini_cat(dst, src); ini_destroy(src); } } } done: strfreev(name); strfreev(txt); return ret; } bool configInitINI(void) { char *buf; bool ret = true; if ((buf = configReadFile("config.ini"))) { if (!(config_ini = ini_load(buf, NULL))) { logErr("Could not load INI configuration"); ret = false; goto done; } } /* we need to have an empty INI to merge stuff into */ if (!config_ini) { if (!(config_ini = ini_create(NULL))) { logErr("Could not create new INI structure"); ret = false; goto done; } } if (!ini_d_load(config_ini)) { logWarn("Could not read ini.d configurations"); } done: free(buf); return ret; } char *configTryStringFull(char *name, char *def) { char *output = configReadFile(name); return output ? output : (def ? xstrdup(def) : NULL); } char *configTryString(char *name, char *def) { char *output = configReadFile(name); if (!output) return xstrdup(def); /* remove the newline */ for (char *c = output; *c; ++c) { if (*c == '\n') { //terminate it. *c = '\0'; break; } } return output; } long configTryLong(char *name, long def) { char *s; long out; if ((s = configReadFile(name))) { errno = 0; out = strtol(s, NULL, 0); free(s); if (!errno) return out; } return def; } bool configTryBool(char *name, bool def) { char *s; bool out; if ((s = configReadFile(name))) { out = (bool)( strstr(s, "yes") || strstr(s, "true") || strstr(s, "on")); free(s); return out; } return def; } bool configWriteString(char *name, const char *val, bool overwrite) { int fd, oflag; char *path; int ret; if (!(path = osGetHomeConfigPath(name))) { return false; } if (!osMakeParentDir(path, S_IRWXU)) { logPErr("Could not create parent directory structure"); free(path); return false; } errno = 0; oflag = O_WRONLY | O_CREAT | (overwrite ? O_EXCL : 0); if ((fd = open(path, oflag, S_IRWXU)) == -1) { logPErr("Could not create config file"); free(path); return false; } ret = write_full(fd, val, strlen(val), 0); close(fd); return ret; } 0707010000002A000081A400000000000000000000000164751F5700001693000000000000000000000000000000000000001A00000000waynergy-0.16+3/src/log.c#include "log.h" #include "config.h" #include "xmem.h" #include <time.h> #include <stdint.h> #include <inttypes.h> #include <assert.h> #include "fdio_full.h" static enum logLevel log_level = LOG_NONE; static FILE *log_file; static int log_file_fd; static struct timespec log_start = {0}; static void log_print_ts(FILE *out) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); ts.tv_sec -= log_start.tv_sec; ts.tv_nsec -= log_start.tv_nsec; if (ts.tv_nsec < 0) { --ts.tv_sec; ts.tv_nsec += 1000000000; } fprintf(out, "%" PRIdMAX ".%09ld", (intmax_t)ts.tv_sec, ts.tv_nsec); } static char *log_level_str[] = { "NONE", "ERROR", "WARN", "INFO", "DEBUG", "DEBUGSYN", NULL, }; enum logLevel logLevelFromString(const char *s) { enum logLevel ll; char *endptr; for (ll = LOG_NONE; ll < LOG__INVALID; ++ll) { if (!strcasecmp(log_level_str[ll], s)) { return ll; } } /* try numeric value */ errno = 0; ll = strtol(s, &endptr, 0); if (endptr == s) { fprintf(stderr, "loglevel: invalid string, and no digits\n"); ll = LOG__INVALID; } else if (errno) { perror("loglevel: strtol error"); ll = LOG__INVALID; } else if (ll >= LOG__INVALID) { fprintf(stderr, "loglevel: invalid numerical value\n"); ll = LOG__INVALID; } return ll; } static char *log_level_get_str(enum logLevel level) { assert(level < (sizeof(log_level_str)/sizeof(*log_level_str))); return log_level_str[level]; } static void log_out_v_(FILE *f, enum logLevel level, const char *fmt, va_list ap) { va_list aq; if (level > log_level) return; log_print_ts(f); fprintf(f, ": [%s] ", log_level_get_str(level)); va_copy(aq, ap); vfprintf(f, fmt, aq); putc('\n', f); fflush(f); va_end(aq); } void logOutV(enum logLevel level, const char *fmt, va_list ap) { log_out_v_(stderr, level, fmt, ap); if (log_file) log_out_v_(log_file, level, fmt, ap); } void logOut(enum logLevel level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); logOutV(level, fmt, ap); va_end(ap); } void logErr(const char *fmt, ...) { va_list ap; va_start(ap, fmt); logOutV(LOG_ERR, fmt, ap); va_end(ap); } void logWarn(const char *fmt, ...) { va_list ap; va_start(ap, fmt); logOutV(LOG_WARN, fmt, ap); va_end(ap); } void logInfo(const char *fmt, ...) { va_list ap; va_start(ap, fmt); logOutV(LOG_INFO, fmt, ap); va_end(ap); } void logDbg(const char *fmt, ...) { va_list ap; va_start(ap, fmt); logOutV(LOG_DBG, fmt, ap); va_end(ap); } void logDbgSyn(const char *fmt, ...) { va_list ap; va_start(ap, fmt); logOutV(LOG_DBGSYN, fmt, ap); va_end(ap); } bool logInit(enum logLevel level, char *path) { log_level = level; clock_gettime(CLOCK_MONOTONIC, &log_start); #if !defined(WAYNERGY_TEST) char *mode = configTryString("log/mode", "w"); if (path) { if (!(log_file = fopen(path, mode))) { logErr("Could not open extra logfile at path %s", path); free(mode); return false; } log_file_fd = fileno(log_file); } free(mode); #endif logInfo("Log initialized at level %d", level); return true; } void logClose(void) { if (log_file) fclose(log_file); } /* signal-safe logging */ #define INT32_BUFLEN 12 static char *uint32_to_str(uint32_t in, char *out) { int i; int digits; if (!in) { strcpy(out, "0"); return out; } for (i = INT32_BUFLEN - 2; in; --i) { out[i] = '0' + (in % 10); in /= 10; } /* shift back by number of unused digits */ digits = INT32_BUFLEN - 2 - i; memmove(out, out + i + 1, digits); out[digits] = 0; return out; } static char *int32_to_str(int32_t in, char out[static INT32_BUFLEN]) { if (in == INT_MIN) { strcpy(out, "INT_MIN"); return out; } else if (in < 0) { in *= -1; uint32_to_str(in, out + 1); out[0] = '-'; } else { uint32_to_str(in, out); } return out; } static void log_out_ss(enum logLevel level, const char *str) { if (level > log_level) return; write_full(STDERR_FILENO, str, strlen(str), 0); if (log_file) { write_full(log_file_fd, str, strlen(str), 0); } } static void log_print_ts_ss(enum logLevel level, int out_fd) { char buf[INT32_BUFLEN]; char zero = '0'; char dot = '.'; struct timespec ts; int i, numlen; if (level > log_level) return; clock_gettime(CLOCK_MONOTONIC, &ts); ts.tv_sec -= log_start.tv_sec; ts.tv_nsec -= log_start.tv_nsec; if (ts.tv_nsec < 0 ) { --ts.tv_sec; ts.tv_nsec += 1000000000; } uint32_to_str(ts.tv_sec, buf); write_full(out_fd, buf, strlen(buf), 0); write_full(out_fd, &dot, 1, 0); uint32_to_str(ts.tv_nsec, buf); numlen = strlen(buf); for (i = 0; i < 9 - numlen; ++i ) { write_full(out_fd, &zero, 1, 0); } write_full(out_fd, buf, strlen(buf), 0); } void logOutSigStart(enum logLevel level) { log_print_ts_ss(level, STDERR_FILENO); if (log_file) { log_print_ts_ss(level, log_file_fd); } log_out_ss(level, ": ["); log_out_ss(level, log_level_get_str(level)); log_out_ss(level, "] "); } void logOutSigChar(enum logLevel level, char c) { char buf[2] = {c}; log_out_ss(level, buf); } void logOutSigEnd(enum logLevel level) { logOutSigChar(level, '\n'); } void logOutSigStr(enum logLevel level, const char *str) { log_out_ss(level, str); } void logOutSig(enum logLevel level, const char *msg) { logOutSigStart(level); logOutSigStr(level, msg); logOutSigEnd(level); } void logOutSigI32(enum logLevel level, int32_t val) { char buf[INT32_BUFLEN]; int32_to_str(val, buf); log_out_ss(level, buf); } void logOutSigU32(enum logLevel level, uint32_t val) { char buf[INT32_BUFLEN]; uint32_to_str(val, buf); log_out_ss(level, buf); } 0707010000002B000081A400000000000000000000000164751F5700002782000000000000000000000000000000000000001B00000000waynergy-0.16+3/src/main.c#include <stdio.h> #include <stdarg.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> #include <poll.h> #include "xmem.h" #include "fdio_full.h" #include "sopt.h" #include "uSynergy.h" #include "net.h" #include "wayland.h" #include "config.h" #include "clip.h" #include "log.h" #include "sig.h" #include "ver.h" static struct sopt optspec[] = { SOPT_INITL('h', "help", "Help text"), SOPT_INITL('v', "version", "Version information"), SOPT_INIT_ARGL('b', "backend", SOPT_ARGTYPE_STR, "backend", "[backend] Input backend -- one of wlr, kde, uinput"), SOPT_INIT_ARGL('c', "host", SOPT_ARGTYPE_STR, "host", "[host] Server to connect to"), SOPT_INIT_ARGL('p', "port", SOPT_ARGTYPE_STR, "port", "[port] Port"), SOPT_INIT_ARGL('W', "width", SOPT_ARGTYPE_SHORT, "width", "[width] Width of screen in pixels (manual override, must be given with height)"), SOPT_INIT_ARGL('H', "height", SOPT_ARGTYPE_SHORT, "height", "[height] Height of screen in pixels (manual override, must be given with width)"), SOPT_INIT_ARGL('N', "name", SOPT_ARGTYPE_STR, "name", "[name] Name of client screen"), SOPT_INIT_ARGL('l', "logfile", SOPT_ARGTYPE_STR, "file", "[log/path] Name of logfile to use"), SOPT_INIT_ARGL('L', "loglevel", SOPT_ARGTYPE_STR, "level", "[log/level] Log level -- number, increasing from 0 for more verbosity up to 6, or one of 'none', 'error', 'warn', 'info', 'debug', 'debugsyn'"), SOPT_INITL('n', "no-clip", "Don't synchronize the clipboard"), SOPT_INITL('e', "enable-crypto", "[tls/enable] Enable TLS encryption"), SOPT_INITL('E', "disable-crypto", "[tls/enable] Force disable TLS encryption"), SOPT_INITL('t', "enable-tofu", "[tls/tofu] Enable trust-on-first-use for TLS certificate"), SOPT_INITL(CHAR_MAX + USYNERGY_ERROR_NONE, "fatal-none", "Consider *normal* disconnect (i.e. CBYE) to be fatal"), SOPT_INITL(CHAR_MAX + USYNERGY_ERROR_EBAD, "fatal-ebad", "Protocol errors are fatal"), SOPT_INITL(CHAR_MAX + USYNERGY_ERROR_EBSY, "fatal-ebsy", "EBSY (client already exists with our name) errors are fatal"), SOPT_INITL(CHAR_MAX + USYNERGY_ERROR_TIMEOUT, "fatal-timeout", "timeouts are fatal"), SOPT_INIT_END }; uSynergyContext synContext; struct wlContext wlContext = {0}; struct synNetContext synNetContext; static void syn_mouse_wheel_cb(uSynergyCookie cookie, int16_t x, int16_t y) { wlMouseWheel(&wlContext, x, y); } static void syn_mouse_button_down_cb(uSynergyCookie cookie, enum uSynergyMouseButton button) { wlMouseButton(&wlContext, button, 1); } static void syn_mouse_button_up_cb(uSynergyCookie cookie, enum uSynergyMouseButton button) { wlMouseButton(&wlContext, button, 0); } static void syn_mouse_move_cb(uSynergyCookie cookie, bool rel, int16_t x, int16_t y) { if (rel) { wlMouseRelativeMotion(&wlContext, x, y); } else { wlMouseMotion(&wlContext, x, y); } } static void syn_key_cb(uSynergyCookie cookie, uint16_t key, uint16_t id, uint16_t mod, bool down, bool repeat) { if (!repeat) wlKey(&wlContext, key, id, down); } static void syn_clip_cb(uSynergyCookie cookie, enum uSynergyClipboardId id, uint32_t format, const uint8_t *data, uint32_t size) { //XXX: Only text makes any sense to process here. if (format == 0) { clipWlCopy(id, data, size); } } static void syn_screensaver_cb(uSynergyCookie cookie, bool state) { size_t i; int ret; wlIdleInhibit(&wlContext, !state); char **cmd = configReadLines(state ? "screensaver/start" : "screensaver/stop"); if (!cmd) return; for (i = 0; cmd[i]; ++i) { ret = system(cmd[i]); if (ret) { logWarn("Screensaver callback state %s command #%zd (%s) failed with code %d", state ? "start" : "stop", i, cmd[i], ret); } } strfreev(cmd); } void wl_output_update_cb(struct wlContext *context) { struct wlOutput *output = context->outputs; int b, l, t, r; b = 0; l = 0; t = 0; r = 0; int width = 0; int height = 0; for(; output; output = output->next) { if (b > output->y) b = output->y; if (l > output->x) l = output->x; if (t < (output->y + output->height)) t = (output->y + output->height); if (r < (output->x + output->width)) r = (output->x + output->width); } width = r - l; height = t - b; logInfo("Geometry updated: %dx%d", width, height); uSynergyUpdateRes(&synContext, width, height); wlResUpdate(&wlContext, width, height); } static void syn_active_cb(uSynergyCookie cookie, bool active) { if (!active) { wlKeyReleaseAll(&wlContext); } } static void uinput_fd_open(int res[static 2]) { if ((res[0] = open("/dev/uinput", O_WRONLY | O_CLOEXEC)) == -1) { /* can't use normal logs yet, still privileged */ perror("uinput fd open failed (this is normal if not using uinput backend)"); return; } if ((res[1] = open("/dev/uinput", O_WRONLY | O_CLOEXEC)) == -1) { perror("uinput fd open failed (this is normal if not using uinput backend)"); close(res[0]); res[0] = -1; } } int main(int argc, char **argv) { int ret = EXIT_SUCCESS; int opt; union sopt_arg soptarg = {0}; char *port = NULL; char *name = NULL; char *host = NULL; char *backend = NULL; char hostname[_POSIX_HOST_NAME_MAX] = {0}; char *log_path = NULL; enum logLevel log_level; bool man_geom = false; bool use_clipboard = true; bool enable_crypto = false; bool enable_tofu = false; /* deal with privileged operations first */ uinput_fd_open(wlContext.uinput_fd); /* and drop said privileges */ osDropPriv(); /* we default to name being hostname, so get it*/ if (gethostname(hostname, _POSIX_HOST_NAME_MAX - 1) == -1) { perror("gethostname"); goto error; } uSynergyInit(&synContext); /* figure out whether we need to override the configuration path */ osConfigPathOverride = getenv("WAYNERGY_CONF_PATH"); /*Intialize INI configuration*/ configInitINI(); /* Load defaults for everything */ port = configTryString("port", "24800"); host = configTryString("host", "localhost"); name = configTryString("name", hostname); backend = configTryString("backend", NULL); enable_crypto = configTryBool("tls/enable", false); enable_tofu = configTryBool("tls/tofu", false); synContext.m_clientWidth = configTryLong("width", 0); synContext.m_clientHeight = configTryLong("height", 0); log_path = configTryString("log/path", NULL); log_level = configTryLong("log/level", LOG_WARN); sopt_usage_set(optspec, argv[0], "Synergy client for wayland compositors"); while ((opt = sopt_getopt_s(argc, argv, optspec, NULL, NULL, &soptarg)) != -1) { switch (opt) { case 'h': sopt_usage_s(); goto done; case 'v': fprintf(stderr, "%s version %s\n", argv[0], WAYNERGY_VERSION_STR); goto done; case 'b': backend = xstrdup(soptarg.str); break; case 'c': free(host); host = xstrdup(soptarg.str); break; case 'p': free(port); port = xstrdup(soptarg.str); break; case 'W': synContext.m_clientWidth = soptarg.s; break; case 'H': synContext.m_clientHeight = soptarg.s; break; case 'N': free(name); name = xstrdup(soptarg.str); break; case 'L': if ((log_level = logLevelFromString(soptarg.str)) == LOG__INVALID) { goto opterror; } break; case 'l': log_path = xstrdup(soptarg.str); break; case 'n': use_clipboard = false; break; case 'e': enable_crypto = true; break; case 'E': enable_crypto = false; break; case 't': enable_tofu = true; break; case CHAR_MAX + USYNERGY_ERROR_NONE: case CHAR_MAX + USYNERGY_ERROR_EBAD: case CHAR_MAX + USYNERGY_ERROR_EBSY: case CHAR_MAX + USYNERGY_ERROR_TIMEOUT: synContext.m_errorIsFatal[opt - CHAR_MAX] = true; break; opterror: default: sopt_usage_s(); goto error; } } /* set up logging */ logInit(log_level, log_path); logInfo("%s version %s", argv[0], WAYNERGY_VERSION_STR); /* now we decide if we should use manual geom */ if (synContext.m_clientWidth || synContext.m_clientHeight) { if (!(synContext.m_clientWidth && synContext.m_clientHeight)) { logErr("Must specify both manual dimensions"); sopt_usage_s(); goto error; } logInfo("Using manual dimensions: %dx%d", synContext.m_clientWidth, synContext.m_clientHeight); man_geom = true; } /* set up signal handler */ sigHandleInit(argv); /* we can't override const, so set hostname here*/ synContext.m_clientName = name; if (!synNetInit(&synNetContext, &synContext, host, port, enable_crypto, enable_tofu)) { logErr("Could not initialize network code"); goto error; } /* key code type */ synContext.m_useRawKeyCodes = configTryBool("syn_raw_key_codes", true); /* populate events */ synContext.m_mouseMoveCallback = syn_mouse_move_cb; synContext.m_mouseButtonDownCallback = syn_mouse_button_down_cb; synContext.m_mouseButtonUpCallback = syn_mouse_button_up_cb; synContext.m_mouseWheelCallback = syn_mouse_wheel_cb; synContext.m_keyboardCallback = syn_key_cb; synContext.m_screensaverCallback = syn_screensaver_cb; synContext.m_screenActiveCallback = syn_active_cb; /* wayland context events */ wlContext.on_output_update = man_geom ? NULL : wl_output_update_cb; /* set up clipboard */ if (clipHaveWlClipboard() && use_clipboard) { synContext.m_clipboardCallback = syn_clip_cb; if (!clipSetupSockets()) goto error; if(!clipSpawnMonitors()) goto error; } else if (!use_clipboard) { logInfo("Clipboard sync disabled by command line"); } else { logWarn("wl-clipboard not found, no clipboard synchronization support"); } /* setup wayland */ if (!wlSetup(&wlContext, synContext.m_clientWidth, synContext.m_clientHeight, backend)) goto error; wlIdleInhibit(&wlContext, true); /* initialize main loop */ netPollInit(); /* and actual main loop */ while(1) { /* no matter what handling signals is a good idea */ sigHandleRun(); if (!synContext.m_connected) { /* always try updating first so we initially connect */ uSynergyUpdate(&synContext); } else { netPoll(&synNetContext, &wlContext); } } error: ret = EXIT_FAILURE; done: free(log_path); free(host); free(name); free(port); free(backend); return ret; } 0707010000002C000081A400000000000000000000000164751F57000040B3000000000000000000000000000000000000001D00000000waynergy-0.16+3/src/mapper.c/* mapper -- easily create keyboard maps for waynergy consumption */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <time.h> #include <fcntl.h> #include <unistd.h> #include <wayland-client.h> #include <wayland-client-protocol.h> #include <xkbcommon/xkbcommon.h> #include "xmem.h" #include "fdio_full.h" #include "xdg-shell-client-protocol.h" #include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h" #include "sopt.h" #include "ssb.h" static struct sopt optspec[] = { SOPT_INITL('h', "help", "Help text"), SOPT_INITL('r', "raw", "Use raw keymap section output (default)"), SOPT_INITL('s', "skipped", "Include skipped keys in XKB output"), SOPT_INIT_ARGL('x', "xkb", SOPT_ARGTYPE_STR, "name", "Use xkb keycode format for output, with given name"), SOPT_INIT_ARGL('o', "out", SOPT_ARGTYPE_STR, "path", "Output to given file (defaults to stdout)"), SOPT_INIT_ARGL('l', "keylim", SOPT_ARGTYPE_LONG, "limit", "Maximum local key limit"), SOPT_INIT_END }; #define BTN_LEFT 0x110 #define BTN_RIGHT 0x111 #define BTN_MIDDLE 0x112 /* configuration stuff */ static FILE *out_file = NULL; static char *xkb_out_name = NULL; /* if NULL, we can assume they want raw */ static uint32_t raw_keymap_max_limit = 0xFFFFFFFF; static bool xkb_skipped = false; /* Keyboard handling */ static struct xkb_context *xkb_ctx; static struct xkb_keymap *xkb_keymap; static uint32_t *raw_keymap; static bool *raw_keymap_set; static uint32_t raw_keymap_min; static uint32_t raw_keymap_max; static uint32_t raw_keymap_pos; static int started; static char *get_syms_str(void) { char sym_name[64]; struct ssb s = {0}; const xkb_keysym_t *syms_out; int syms_count, i, levels, level, layouts, layout; layouts = xkb_keymap_num_layouts_for_key(xkb_keymap, raw_keymap_pos); for (layout = 0; layout < layouts; ++layout) { levels = xkb_keymap_num_levels_for_key(xkb_keymap, raw_keymap_pos, layout); for (level = 0; level < levels; ++level) { if ((syms_count = xkb_keymap_key_get_syms_by_level(xkb_keymap, raw_keymap_pos, layout, level, &syms_out))) { for (i = 0; i < syms_count; ++i) { xkb_keysym_get_name(syms_out[i], sym_name, sizeof(sym_name)); ssb_xprintf(&s, "%s ", sym_name); } } } } return s.buf; } static void raw_keymap_print(void) { uint32_t i; fprintf(out_file, "[raw-keymap]\n"); for (i = raw_keymap_min; i < raw_keymap_max; ++i) { if (raw_keymap[i] != i) { fprintf(out_file, "%u = %u\n", raw_keymap[i], i); } } exit(0); } static void xkb_keycodes_print(char *sec_name) { uint32_t i; uint32_t mapped_min = 0xFFFFFFFF; uint32_t mapped_max = 0; uint32_t count = 0; const char *name; for (i = raw_keymap_min; i < raw_keymap_max; ++i) { if (raw_keymap_set[i] || xkb_skipped) { ++count; if (raw_keymap[i] < mapped_min) { mapped_min = raw_keymap[i]; } if (raw_keymap[i] > mapped_max) { mapped_max = raw_keymap[i]; } } } if (!count) { fprintf(stderr, "No keycodes defined!\n"); exit(1); } fprintf(out_file, "xkb_keycodes \"%s\" {\n", sec_name); fprintf(out_file, "\tminimum = %u;\n", mapped_min); fprintf(out_file, "\tmaximum = %u;\n", mapped_max); for (i = raw_keymap_min; i < raw_keymap_max; ++i) { if (raw_keymap_set[i] || xkb_skipped) { if ((name = xkb_keymap_key_get_name(xkb_keymap, i))) { fprintf(out_file, "\t<%s> = %u;\n", name, raw_keymap[i]); } } } fprintf(out_file, "};\n"); exit(0); } static void print_output(void) { if (xkb_out_name) { fprintf(stderr, "Printing xkb_keycodes section....\n"); xkb_keycodes_print(xkb_out_name); } else { fprintf(stderr, "Printing [raw-keymap] section....\n"); raw_keymap_print(); } } static void raw_keymap_next(void) { ++raw_keymap_pos; if (raw_keymap_pos > raw_keymap_max) { print_output(); } } static void raw_keymap_prev(void) { if (raw_keymap_pos > raw_keymap_min) { raw_keymap_set[raw_keymap_pos] = false; --raw_keymap_pos; } } static void raw_keymap_store(uint32_t val) { fprintf(stderr, " keycode %u\n", val); raw_keymap[raw_keymap_pos] = val; raw_keymap_set[raw_keymap_pos] = true; raw_keymap_next(); } static void raw_keymap_init(void) { uint32_t i; raw_keymap = xcalloc(raw_keymap_max + 1, sizeof(*raw_keymap)); raw_keymap_set = xcalloc(raw_keymap_max + 1, sizeof(*raw_keymap_set)); /* start by assuming that each key simply maps to itself */ for (i = 0; i < raw_keymap_max; ++i) { raw_keymap[i] = i; } /* and start our position at the minimum keycode */ raw_keymap_pos = raw_keymap_min; } static void raw_keymap_prompt(void) { const char *key_name; char *sym_name; /* for L1 */ /* skip invalid keycodes */ while (!(key_name = xkb_keymap_key_get_name(xkb_keymap, raw_keymap_pos))) { raw_keymap_next(); } sym_name = get_syms_str(); fprintf(stderr,"Key %u (name: %s) (syms: %s)?\n", raw_keymap_pos, key_name, sym_name); free(sym_name); } /* Shared memory support code */ static void randname(char *buf) { struct timespec ts; long r; int i; clock_gettime(CLOCK_REALTIME, &ts); r = ts.tv_nsec; for (i = 0; i < 6; ++i) { buf[i] = 'A'+(r&15)+(r&16)*2; r >>= 5; } } static int create_shm(size_t size) { int retries = 100; int fd; int ret; char name[] = "/wl_shm-XXXXXX"; do { randname(name + sizeof(name) - 7); --retries; if ((fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600)) >= 0) { shm_unlink(name); break; } } while (retries > 0 && errno == EEXIST); if (fd == -1) { return -1; } do { ret = ftruncate(fd, size); } while (ret < 0 && errno == EINTR); if (ret < 0) { close(fd); return -1; } return fd; } static struct wl_seat *wl_seat; static struct wl_registry *wl_registry; static struct wl_display *wl_display; static struct wl_keyboard *wl_keyboard; static struct wl_pointer *wl_pointer; static struct wl_shm *wl_shm; static struct wl_compositor *wl_compositor; static struct xdg_wm_base *xdg_wm_base; static struct wl_surface *wl_surface; static struct xdg_surface *xdg_surface; static struct xdg_toplevel *xdg_toplevel; static struct zwp_keyboard_shortcuts_inhibit_manager_v1 *keyboard_shortcuts_inhibit_manager; static struct zwp_keyboard_shortcuts_inhibitor_v1 *keyboard_shortcuts_inhibitor; /*keyboard shortcut inhibitor listener */ static void inhibitor_active(void *data, struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor) { fprintf(stderr, "Shortcuts inhibited\n"); } static void inhibitor_inactive(void *data, struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor) { fprintf(stderr, "Shortcuts not inhibited\n"); } static struct zwp_keyboard_shortcuts_inhibitor_v1_listener keyboard_shortcuts_inhibitor_listener = { .active = inhibitor_active, .inactive = inhibitor_inactive, }; /* buffer listener */ static void buffer_release(void *data, struct wl_buffer *buffer) { wl_buffer_destroy(buffer); } static struct wl_buffer_listener buffer_listener = { .release = buffer_release, }; /* actually draw */ static struct wl_buffer *draw(void) { int width = 640, height = 480; int stride = width * 4; int size = stride * height; int fd; uint32_t *data; struct wl_shm_pool *pool; struct wl_buffer *buffer; if ((fd = create_shm(size)) == -1 ) { return NULL; } if ((data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { close(fd); return NULL; } pool = wl_shm_create_pool(wl_shm, fd, size); buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888); wl_shm_pool_destroy(pool); close(fd); /*just draw white for now */ memset(data, 0, size); munmap(data, size); wl_buffer_add_listener(buffer, &buffer_listener, NULL); return buffer; } /* xdg-surface listener */ static void xdg_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct wl_buffer *buffer; xdg_surface_ack_configure(surface, serial); if (!(buffer = draw())) { fprintf(stderr, "Could not draw buffer\n"); exit(1); } wl_surface_attach(wl_surface, buffer, 0, 0); wl_surface_commit(wl_surface); } static struct xdg_surface_listener xdg_surface_listener = { .configure = xdg_surface_configure, }; /* xdg_wm_base */ static void wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { xdg_wm_base_pong(xdg_wm_base, serial); } static struct xdg_wm_base_listener wm_base_listener = { .ping = wm_base_ping, }; /* keyboard */ /* for debugging purposes.... */ static void keymap_print(char *buf, size_t len) { char *s = xcalloc(len + 1, 1); memcpy(s, buf, len + 1); fprintf(stderr, "\n***\n%s\n***\n", s); free(s); } static void keyboard_keymap(void *data, struct wl_keyboard *wl_kb, uint32_t format, int32_t fd, uint32_t size) { char *buf; if (xkb_keymap) { fprintf(stderr, "keymap changed, exiting\n"); exit(1); } if ((buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { fprintf(stderr, "could not map keyboard map fd\n"); exit(1); } if (!(xkb_keymap = xkb_keymap_new_from_buffer(xkb_ctx, buf, size - 1, format, XKB_KEYMAP_COMPILE_NO_FLAGS))) { fprintf(stderr, "could not compile keymap:\n"); keymap_print(buf, size); exit(1); } munmap(buf, size); close(fd); /* set up the raw keymap for our consumption */ raw_keymap_min = xkb_keymap_min_keycode(xkb_keymap); raw_keymap_max = xkb_keymap_max_keycode(xkb_keymap); if (raw_keymap_max_limit < raw_keymap_max) { raw_keymap_max = raw_keymap_max_limit; fprintf(stderr, "limiting maximum local keycode to %u\n", raw_keymap_max); } raw_keymap_init(); } static void keyboard_enter(void *data, struct wl_keyboard *wl_kb, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { } static void keyboard_leave(void *data, struct wl_keyboard *wl_kb, uint32_t serial, struct wl_surface *surface) { } static void keyboard_key(void *data, struct wl_keyboard *wl_kb, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { key += 8; if (started && !state) { raw_keymap_store(key); raw_keymap_prompt(); } } static void keyboard_mod(void *data, struct wl_keyboard *wl_kb, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { } static void keyboard_rep(void *data, struct wl_keyboard *wl_kb, int32_t rate, int32_t delay) { } static struct wl_keyboard_listener keyboard_listener = { .keymap = keyboard_keymap, .enter = keyboard_enter, .leave = keyboard_leave, .key = keyboard_key, .modifiers = keyboard_mod, .repeat_info = keyboard_rep, }; /* pointer */ static void pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y) { } static void pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { } static void pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) { } static void pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { if (state) { return; } if (!started) { started = 1; fprintf(stderr, "Starting keymapping\n"); fprintf(stderr, "To skip, left click\n"); fprintf(stderr, "To exit and print, right click\n"); fprintf(stderr, "To undo, middle click\n"); } else { switch (button) { case BTN_LEFT: raw_keymap_next(); break; case BTN_MIDDLE: raw_keymap_prev(); break; case BTN_RIGHT: print_output(); break; } } raw_keymap_prompt(); } static void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t val) { } static void pointer_frame(void *data, struct wl_pointer *pointer) { } static void pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source) { } static void pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis) { } static void pointer_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete) { } static struct wl_pointer_listener pointer_listener = { .enter = pointer_enter, .leave = pointer_leave, .motion = pointer_motion, .button = pointer_button, .axis = pointer_axis, .frame = pointer_frame, .axis_source = pointer_axis_source, .axis_stop = pointer_axis_stop, .axis_discrete = pointer_axis_discrete, }; /* seat */ static void seat_capabilities(void *data, struct wl_seat *seat, uint32_t caps) { if (caps & (WL_SEAT_CAPABILITY_KEYBOARD | WL_SEAT_CAPABILITY_POINTER)) { wl_keyboard = wl_seat_get_keyboard(seat); wl_keyboard_add_listener(wl_keyboard, &keyboard_listener, NULL); wl_pointer = wl_seat_get_pointer(wl_seat); wl_pointer_add_listener(wl_pointer, &pointer_listener, NULL); fprintf(stderr, "To start, click input window\n"); } else { fprintf(stderr, "seat does not have input\n"); exit(1); } } static void seat_name(void *data, struct wl_seat *seat, const char *name) { } static struct wl_seat_listener seat_listener = { .capabilities = seat_capabilities, .name = seat_name, }; /* registry */ static void registry_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (strcmp(interface, wl_seat_interface.name) == 0) { wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, version); wl_seat_add_listener(wl_seat, &seat_listener, NULL); } else if (strcmp(interface, wl_shm_interface.name) == 0) { wl_shm = wl_registry_bind(registry, name, &wl_shm_interface, version); } else if (strcmp(interface, wl_compositor_interface.name) == 0) { wl_compositor = wl_registry_bind(registry, name, &wl_compositor_interface, version); } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, version); xdg_wm_base_add_listener(xdg_wm_base, &wm_base_listener, NULL); } else if (strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name) == 0) { keyboard_shortcuts_inhibit_manager = wl_registry_bind(registry, name, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, version); } } static void registry_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static struct wl_registry_listener registry_listener = { .global = registry_global, .global_remove = registry_global_remove, }; int main(int argc, char **argv) { union sopt_arg soptarg = {0}; int opt; char *out_file_path = NULL; sopt_usage_set(optspec, argv[0], "Interactively create keymaps/keycode sections for waynergy configuration"); while ((opt = sopt_getopt_s(argc, argv, optspec, NULL, NULL, &soptarg)) != -1) { switch (opt) { case 'h': sopt_usage_s(); return 0; case 's': xkb_skipped = true; break; case 'o': out_file_path = xstrdup(soptarg.str); break; case 'r': xkb_out_name = NULL; break; case 'x': xkb_out_name = xstrdup(soptarg.str); break; case 'l': raw_keymap_max_limit = soptarg.l; break; default: sopt_usage_s(); return 1; } } if (out_file_path) { if (!(out_file = fopen(out_file_path, "w+"))) { perror("could not open output file"); return 1; } fprintf(stderr, "Printing output to %s\n", out_file_path); } else { fprintf(stderr, "Printing output to stdout\n"); out_file = stdout; } if (!(xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS))) { fprintf(stderr, "could not create xkb context\n"); exit(1); } if (!(wl_display = wl_display_connect(NULL))) { fprintf(stderr, "could not connect to wayland socket\n"); return 1; } wl_registry = wl_display_get_registry(wl_display); wl_registry_add_listener(wl_registry, ®istry_listener, NULL); wl_display_dispatch(wl_display); wl_display_roundtrip(wl_display); wl_surface = wl_compositor_create_surface(wl_compositor); xdg_surface = xdg_wm_base_get_xdg_surface(xdg_wm_base, wl_surface); xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL); xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); xdg_toplevel_set_title(xdg_toplevel, argv[0]); wl_surface_commit(wl_surface); if (keyboard_shortcuts_inhibit_manager) { keyboard_shortcuts_inhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(keyboard_shortcuts_inhibit_manager, wl_surface, wl_seat); zwp_keyboard_shortcuts_inhibitor_v1_add_listener(keyboard_shortcuts_inhibitor, &keyboard_shortcuts_inhibitor_listener, NULL); } else { fprintf(stderr, "WARNING: could not inhibit compositor keyboard shortcuts, keyboard input might trigger unexpected behavior\n"); } while (wl_display_dispatch(wl_display) != -1); return 0; } 0707010000002D000081A400000000000000000000000164751F5700002277000000000000000000000000000000000000001A00000000waynergy-0.16+3/src/net.c#include "uSynergy.h" #include "wayland.h" #include "fdio_full.h" #include "clip.h" #include "net.h" #include "os.h" #include <stdlib.h> #include <stdbool.h> #include <unistd.h> #include <sys/types.h> #include <fcntl.h> #include <poll.h> #include <limits.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/stat.h> #include <netdb.h> #include <time.h> #include <tls.h> #include <assert.h> static char *load_cert_hash(const char *host) { char *ret, *path; xasprintf(&path, "tls/hash/%s", host); ret = configTryString(path, NULL); free(path); return ret; } static bool store_cert_hash(const char *host, const char *hash) { bool ret; char *path; xasprintf(&path, "tls/hash/%s", host); ret = configWriteString(path, hash, 0); free(path); return ret; } static bool syn_connect_setup(struct synNetContext *snet_ctx, struct addrinfo *ai) { struct tls_config *cfg; const char *peer_hash; char *cert_path; if ((snet_ctx->fd = socket(ai->ai_family, ai->ai_socktype | SOCK_CLOEXEC, ai->ai_protocol)) == -1) { logPErr("socket"); return false; } if (connect(snet_ctx->fd, ai->ai_addr, ai->ai_addrlen)) { logPErr("connect"); return false; } if (snet_ctx->tls) { if (!(snet_ctx->tls_ctx = tls_client())) { logErr("Could not create tls client context"); return false; } if (!(cfg = tls_config_new())) { logErr("Could not create tls configuration structure"); return false; } /* figure out certificate hash business */ if (!(snet_ctx->tls_hash = load_cert_hash(snet_ctx->host))) { if (!snet_ctx->tls_tofu) { logErr("No certificate hash available"); return false; } /* if we are trusting on first use we just defer this * until a successful handshake */ } /* set client certificate */ cert_path = osGetHomeConfigPath("tls/cert"); if (osFileExists(cert_path)) { if (tls_config_set_key_file(cfg, cert_path)) { logErr("Could not load client key: %s", tls_error(snet_ctx->tls_ctx)); tls_config_free(cfg); free(cert_path); return false; } if (tls_config_set_cert_file(cfg, cert_path)) { logErr("Could not load client certificate: %s", tls_error(snet_ctx->tls_ctx)); tls_config_free(cfg); free(cert_path); return false; } } free(cert_path); /* we operate on hashes instead -- this is fine for now */ tls_config_insecure_noverifycert(cfg); tls_config_insecure_noverifyname(cfg); if (tls_configure(snet_ctx->tls_ctx, cfg)) { logErr("Could not configure TLS context: %s", tls_error(snet_ctx->tls_ctx)); tls_config_free(cfg); return false; } tls_config_free(cfg); if (tls_connect_socket(snet_ctx->tls_ctx, snet_ctx->fd, snet_ctx->host)) { logErr("tls_connect error: %s", tls_error(snet_ctx->tls_ctx)); return false; } if (tls_handshake(snet_ctx->tls_ctx)) { logErr("tls_handshake error: %s", tls_error(snet_ctx->tls_ctx)); return false; } if (!(peer_hash = tls_peer_cert_hash(snet_ctx->tls_ctx))) { logErr("Server provided no certificate"); return false; } if (!snet_ctx->tls_hash) { logInfo("Trust-on-first-use enabled, saving hash %s", tls_peer_cert_hash(snet_ctx->tls_ctx)); snet_ctx->tls_hash = xstrdup(peer_hash); if (!store_cert_hash(snet_ctx->host, peer_hash)) { logErr("Could not save certificate hash"); /* we don't want the connection to proceed if * we can't save the hash -- otherwise, 'trust * on first use' just becomes 'trust on every * use' and a total waste of time */ return false; } } if (strcasecmp(snet_ctx->tls_hash, peer_hash)) { logErr("CERTIFICATE HASH MISMATCH: %s (client) != %s (server)", snet_ctx->tls_hash, peer_hash); return false; } } return true; } static bool syn_connect(uSynergyCookie cookie) { bool ret = false; int gai_ret; struct addrinfo *hostinfo, *h; struct synNetContext *snet_ctx = cookie; uSynergyContext *syn_ctx = snet_ctx->syn_ctx; logInfo("Going to connect to %s at port %s", snet_ctx->host, snet_ctx->port); struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM }; if ((gai_ret = getaddrinfo(snet_ctx->host, snet_ctx->port, &hints, &hostinfo))) { if (gai_ret == EAI_SYSTEM) { logPErr("getaddrinfo system error"); return false; } else { logErr("getaddrinfo failed: %s", gai_strerror(gai_ret)); } return false; } synNetDisconnect(snet_ctx); for (h = hostinfo; h; h = h->ai_next) { /* catch connection timeouts */ alarm(USYNERGY_IDLE_TIMEOUT/1000); ret = syn_connect_setup(snet_ctx, h); alarm(0); if (ret) { syn_ctx->m_lastMessageTime = syn_ctx->m_getTimeFunc(); break; } else { /* it didn't work, so we aren't strictly connected... * but this prevents fd and memory leakage by cleaning * up after the partial failure */ synNetDisconnect(snet_ctx); } } freeaddrinfo(hostinfo); return ret; } static bool tls_write_full(struct tls *ctx, const unsigned char *buf, size_t len) { while (len) { ssize_t ret; ret = tls_write(ctx, buf, len); if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) { continue; } if (ret == -1) { logErr("tls_write failed: %s", tls_error(ctx)); return false; } buf += ret; len -= ret; } return true; } static bool syn_send(uSynergyCookie cookie, const uint8_t *buf, int len) { struct synNetContext *snet_ctx = cookie; return snet_ctx->tls_ctx ? tls_write_full(snet_ctx->tls_ctx, buf, len) : write_full(snet_ctx->fd, buf, len, 0); } struct pollfd netPollFd[POLLFD_COUNT]; void netPollInit(void) { for (int i = 0; i < POLLFD_COUNT; ++i) { netPollFd[i].events = POLLIN; netPollFd[i].fd = -1; } } void netPoll(struct synNetContext *snet_ctx, struct wlContext *wl_ctx) { int ret; uSynergyContext *syn_ctx = snet_ctx->syn_ctx; if (snet_ctx->fd == -1) { logErr("INVALID FILE DESCRIPTOR for synergy context"); } int wlfd = wlPrepareFd(wl_ctx); netPollFd[POLLFD_SYN].fd = snet_ctx->fd; netPollFd[POLLFD_WL].fd = wlfd; netPollFd[POLLFD_CLIP_MON].fd = clipMonitorFd; int nfd = syn_ctx->m_connected ? POLLFD_COUNT : 1; while ((ret = poll(netPollFd, nfd, USYNERGY_IDLE_TIMEOUT)) > 0) { sigHandleRun(); if (netPollFd[POLLFD_SYN].revents & POLLIN) { uSynergyUpdate(syn_ctx); } if ((syn_ctx->m_getTimeFunc() - syn_ctx->m_lastMessageTime) > USYNERGY_IDLE_TIMEOUT) { logErr("Synergy timeout encountered -- disconnecting"); synNetDisconnect(snet_ctx); return; } sigHandleRun(); /* ignore everything else until synergy is ready */ if (syn_ctx->m_connected) { wlPollProc(wl_ctx, netPollFd[POLLFD_WL].revents); sigHandleRun(); clipMonitorPollProc(&netPollFd[POLLFD_CLIP_MON]); sigHandleRun(); for (int i = POLLFD_CLIP_UPDATER; i < POLLFD_COUNT; ++i) { clipMonitorPollProc(netPollFd + i); sigHandleRun(); } } nfd = syn_ctx->m_connected ? POLLFD_COUNT : 1; } if (!ret) { logErr("Poll timeout encountered -- disconnecting synergy"); synNetDisconnect(snet_ctx); } sigHandleRun(); } static bool syn_recv(uSynergyCookie cookie, uint8_t *buf, int max_len, int *out_len) { struct synNetContext *snet_ctx = cookie; alarm(USYNERGY_IDLE_TIMEOUT/1000); if (snet_ctx->tls_ctx) { do { *out_len = tls_read(snet_ctx->tls_ctx, buf, max_len); } while (*out_len == TLS_WANT_POLLIN || *out_len == TLS_WANT_POLLOUT); } else { *out_len = read(snet_ctx->fd, buf, max_len); } alarm(0); if (*out_len < 1) { logErr("Synergy receive timed out"); snet_ctx->syn_ctx->m_lastError = USYNERGY_ERROR_TIMEOUT; return false; } return true; } static void syn_sleep(uSynergyCookie cookie, int ms) { usleep(ms * 1000); } static uint32_t syn_get_time(void) { uint32_t ms; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); ms = ts.tv_sec * 1000; ms += ts.tv_nsec / 1000000; return ms; } bool synNetInit(struct synNetContext *snet_ctx, uSynergyContext *context, const char *host, const char *port, bool tls, bool tofu) { snet_ctx->host = xstrdup(host); snet_ctx->port = xstrdup(port); snet_ctx->syn_ctx = context; snet_ctx->fd = -1; snet_ctx->tls = tls; snet_ctx->tls_tofu = tofu; context->m_connectFunc = syn_connect; context->m_sendFunc = syn_send; context->m_receiveFunc = syn_recv; context->m_sleepFunc = syn_sleep; context->m_getTimeFunc = syn_get_time; context->m_cookie = snet_ctx; return true; } bool synNetDisconnect(struct synNetContext *snet_ctx) { if (snet_ctx->fd == -1) return false; if (snet_ctx->tls_ctx) { if (tls_close(snet_ctx->tls_ctx)) { logErr("tls_close error: %s", snet_ctx->tls_ctx); } free(snet_ctx->tls_hash); snet_ctx->tls_hash = NULL; tls_free(snet_ctx->tls_ctx); snet_ctx->tls_ctx = NULL; } shutdown(snet_ctx->fd, SHUT_RDWR); close(snet_ctx->fd); snet_ctx->fd = -1; snet_ctx->syn_ctx->m_connected = false; return true; } 0707010000002E000081A400000000000000000000000164751F570000118D000000000000000000000000000000000000001900000000waynergy-0.16+3/src/os.c#include <sys/mman.h> #include <stdbool.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <grp.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> #include "ssb.h" #include "xmem.h" #include "log.h" #ifdef __FreeBSD__ #include <sys/param.h> #endif bool osFileExists(const char *path) { struct stat buf; if (stat(path, &buf)) { return false; } if (!S_ISREG(buf.st_mode)) { logWarn("%s is not a regular file as expected", path); return false; } return true; } bool osMakeParentDir(const char *path, mode_t mode) { char *c; char parent_path[strlen(path) + 1]; if (!strchr(path, '/')) return true; strcpy(parent_path, path); for (c = strchr(parent_path + 1, '/'); c; c = strchr(c + 1, '/')) { *c = '\0'; if (mkdir(parent_path, mode)) { if (errno != EEXIST) { *c = '/'; return false; } } *c = '/'; } return true; } int osGetAnonFd(void) { #if defined(__linux__) || ((defined(__FreeBSD__) && (__FreeBSD_version >= 1300048))) return memfd_create("waynergy-anon-fd", MFD_CLOEXEC); #endif return fileno(tmpfile()); } char *osGetRuntimePath(char *name) { char *res; char *base; if (!(base = getenv("XDG_RUNTIME_DIR"))) { base = "/tmp"; } xasprintf(&res, "%s/%s", base, name); return res; } char *osConfigPathOverride; char *osGetHomeConfigPath(char *name) { char *res; char *env; if (osConfigPathOverride) { xasprintf(&res, "%s/%s", osConfigPathOverride, name); } else if ((env = getenv("XDG_CONFIG_HOME"))) { xasprintf(&res,"%s/waynergy/%s", env, name); } else { if (!(env = getenv("HOME"))) return NULL; xasprintf(&res, "%s/.config/waynergy/%s",env, name); } return res; } void osDropPriv(void) { uid_t new_uid, old_uid; gid_t new_gid, old_gid; new_uid = getuid(); old_uid = geteuid(); new_gid = getgid(); old_gid = getegid(); if (!old_uid) { fprintf(stderr, "Running as root, dropping ancillary groups\n"); if (setgroups(1, &new_gid)) { /* if we're privileged we have not initialized the * log yet */ perror("Could not drop ancillary groups"); abort(); } } /* POSIX calls this permanent, and it seems to be the case on the BSDs * and Linux. */ if (new_gid != old_gid) { fprintf(stderr, "Dropping gid from %d to %d\n", old_gid, new_gid); if (setregid(new_gid, new_gid)) { perror("Could not set group IDs"); abort(); } } if (new_uid != old_uid) { fprintf(stderr, "Dropping uid from %d to %d\n", old_uid, new_uid); if (setreuid(new_uid, new_uid)) { perror("Could not set user IDs"); abort(); } } } #ifdef __linux__ char *osGetPeerProcName(int fd) { struct ucred uc; socklen_t len = sizeof(uc); char *lf = NULL; char *path = NULL; FILE *f = NULL; struct ssb s = {0}; if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) == -1) { logPErr("GetPeerProcName: getsockopt() failure"); return NULL; } xasprintf(&path, "/proc/%d/comm", uc.pid); if (!(f = fopen(path, "r"))) { logPErr("Could not open file"); goto done; } if (!ssb_readfile(&s, f)) { logPErr("Could not read process name"); ssb_free(&s); goto done; } /* strip the new line */ lf = strchr(s.buf, '\n'); ssb_xtruncate(&s, lf - s.buf); done: free(path); if (f) { fclose(f); } return s.buf; } #elif defined(__FreeBSD__) #include <sys/param.h> #include <sys/user.h> #include <sys/ucred.h> #include <sys/sysctl.h> char *osGetPeerProcName(int fd) { char *name = NULL; struct xucred cred = {0}; socklen_t slen = sizeof(cred); size_t len = sizeof(cred); struct kinfo_proc *kip = NULL; int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID}; if (getsockopt(fd, SOL_LOCAL, LOCAL_PEERCRED, &cred, &slen) == -1) { logPErr("GetPeerProcName: getsockopt() failure"); goto done; } logDbg("Got peer pid of %d", cred.cr_pid); mib[3] = cred.cr_pid; if (sysctl(mib, nitems(mib), NULL, &len, NULL, 0) == -1) { logPErr("sysctl failed to get size"); goto done; } kip = xmalloc(len); if (sysctl(mib, nitems(mib), kip, &len, NULL, 0) == -1) { logPErr("sysctl failed to get proc info"); goto done; } if ((len != sizeof(*kip)) || (kip->ki_structsize != sizeof(*kip)) || (kip->ki_pid != cred.cr_pid)) { logErr("returned procinfo is unusable"); goto done; } name = xstrdup(kip->ki_comm); done: free(kip); return name; } #else char *osGetPeerProcName(int fd) { logErr("osGetPeerProcName not implemented for this platform"); return NULL; } #endif 0707010000002F000081A400000000000000000000000164751F5700000D34000000000000000000000000000000000000001A00000000waynergy-0.16+3/src/sig.c#include <sys/wait.h> #include "sig.h" #include "clip.h" #include "wayland.h" volatile sig_atomic_t sigDoExit = 0; volatile sig_atomic_t sigDoRestart = 0; extern struct wlContext wlContext; extern uSynergyContext synContext; extern struct synNetContext synNetContext; static char **argv_reexec; static void cleanup(enum sigExitStatus status) { /* stop clipboard monitors */ for (int i = 0; i < 2; ++i) { if (clipMonitorPid[i] != -1) { kill(clipMonitorPid[i], SIGTERM); } } /*close stuff*/ synNetDisconnect(&synNetContext); if (status != SES_ERROR_WL) { /* this stuff will crash and burn if we are exiting because of * a wayland error */ wlIdleInhibit(&wlContext, false); wlClose(&wlContext); } logClose(); /*unmask any caught signals*/ } void Exit(enum sigExitStatus status) { cleanup(status); exit(status); } void Restart(enum sigExitStatus status) { cleanup(status); errno = 0; execvp(argv_reexec[0], argv_reexec); logPErr("reexec"); exit(EXIT_FAILURE); } void ExitOrRestart(enum sigExitStatus status) { if (configTryBool("restart_on_fatal", false)) { logErr("Restarting on status %d", status); Restart(status); } Exit(status); } static void sig_handle(int sig, siginfo_t *si, void *context) { int level; switch (sig) { case SIGALRM: logOutSig(LOG_ERR, "Alarm timeout encountered -- probably disconnecting"); break; case SIGTERM: case SIGINT: case SIGQUIT: if (sigDoExit) { logOutSig(LOG_ERR, "received unhandled quit request, aborting"); abort(); } sigDoExit = sig; break; case SIGPIPE: logOutSig(LOG_WARN, "Broken pipe, restarting"); case SIGUSR1: sigDoRestart = true; break; case SIGCHLD: if (si && si->si_code == CLD_EXITED) { level = si->si_status ? LOG_WARN : LOG_DBG; logOutSigStart(level); logOutSigStr(level, "Child died: PID "); logOutSigI32(level, si->si_pid); logOutSigStr(level, ", Status "); logOutSigI32(level, si->si_status); logOutSigEnd(level); } else { logOutSig(LOG_DBG, "SIGCHLD sent without exit"); } break; default: logOutSig(LOG_ERR, "Unhandled signal"); } } void sigWaitSIGCHLD(bool state) { struct sigaction sa = {0}; static struct sigaction sa_old; static bool we_set_wait; if (state || !we_set_wait) { sa.sa_sigaction = sig_handle; sa.sa_flags = SA_SIGINFO | SA_RESTART | (state ? 0 : SA_NOCLDWAIT); sigaction(SIGCHLD, &sa, &sa_old); } else { //use old sigaction, in case weird flags exist. sa_old.sa_flags &= ~SA_NOCLDWAIT; sigaction(SIGCHLD, &sa_old, NULL); } logDbg("%srequiring wait() on SIGCHLD", (state ? "" : "not ")); } void sigHandleInit(char **argv) { struct sigaction sa; sigset_t set; argv_reexec = argv; /* set up signal handler */ sa.sa_sigaction = sig_handle; sigemptyset(&sa.sa_mask); //alarm should trigger EINTR sa.sa_flags = SA_SIGINFO; sigaction(SIGALRM, &sa, NULL); //others can restart sa.sa_flags |= SA_RESTART; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); sigaction(SIGUSR1, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); //don't zombify sa.sa_flags |= SA_NOCLDWAIT; sigaction(SIGCHLD, &sa, NULL); sigemptyset(&set); sigaddset(&set, SIGUSR1); sigaddset(&set, SIGTERM); sigaddset(&set, SIGINT); sigaddset(&set, SIGQUIT); sigaddset(&set, SIGALRM); sigprocmask(SIG_UNBLOCK, &set, NULL); } 07070100000030000081A400000000000000000000000164751F5700000350000000000000000000000000000000000000001A00000000waynergy-0.16+3/src/ssp.c#include "ssp.h" #include <string.h> bool sspSeek(struct sspBuf *buf, size_t len) { if (!buf) { return false; } if (buf->pos + len > buf->len) { return false; } buf->pos += len; return true; } bool sspNetInt(struct sspBuf *buf, void *res, size_t len) { ssize_t i; unsigned char *res_b = res; if (!res) { return sspSeek(buf, len); } if (!buf) { return false; } if (buf->pos + len > buf->len) { return false; } for (i = 0; i < len; ++i) { #ifdef USYNERGY_LITTLE_ENDIAN res_b[len - i - 1] = buf->data[buf->pos++]; #else res_b[i] = buf->data[buf->pos++]; #endif } return true; } bool sspMemMove(void *dest, struct sspBuf *buf, size_t len) { if (!(buf && dest)) { return false; } if (buf->pos + len > buf->len) { return false; } memmove(dest, buf->data + buf->pos, len); buf->pos += len; return true; } 07070100000031000081A400000000000000000000000164751F5700007162000000000000000000000000000000000000001F00000000waynergy-0.16+3/src/uSynergy.c/* uSynergy client -- Implementation for the embedded Synergy client library Heavily modified from the original version Copyright (c) 2012 Alex Evans This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include "uSynergy.h" #include <stdio.h> #include <string.h> #include "sig.h" #include "xmem.h" #include "ssp.h" #include <stdlib.h> #include "log.h" #include <inttypes.h> //--------------------------------------------------------------------------------------------------------------------- // Internal helpers //--------------------------------------------------------------------------------------------------------------------- #define PARSE_ERROR() do { logErr("Parsing Error: %s %s:%d", __func__, __FILE__, __LINE__); return; } while (0) #define REPLY_ERROR() do { logErr("Error in constructing reply: %s %s:%d", __func__, __FILE__, __LINE__); return; } while (0) /** @brief Read 32 bit integer in network byte order and convert to native byte order **/ static int32_t sNetToNative32(const unsigned char *value) { #ifdef USYNERGY_LITTLE_ENDIAN return value[3] | (value[2] << 8) | (value[1] << 16) | (value[0] << 24); #else return value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); #endif } /** @brief Add string to reply packet **/ static bool sAddString(uSynergyContext *context, const char *string) { size_t len = strlen(string); if (context->m_replyCur - context->m_replyBuffer + len > USYNERGY_REPLY_BUFFER_SIZE) return false; memcpy(context->m_replyCur, string, len); context->m_replyCur += len; return true; } static bool sAddBin(uSynergyContext *context, const unsigned char *val, size_t len) { if (context->m_replyCur - context->m_replyBuffer + len > USYNERGY_REPLY_BUFFER_SIZE) return false; memcpy(context->m_replyCur, val, len); context->m_replyCur += len; return true; } /** @brief Add uint8 to reply packet **/ static bool sAddUInt8(uSynergyContext *context, uint8_t value) { if (context->m_replyCur - context->m_replyBuffer + sizeof(value) > USYNERGY_REPLY_BUFFER_SIZE) return false; *context->m_replyCur++ = value; return true; } /** @brief Add uint16 to reply packet **/ static bool sAddUInt16(uSynergyContext *context, uint16_t value) { if (context->m_replyCur - context->m_replyBuffer + sizeof(value) > USYNERGY_REPLY_BUFFER_SIZE) return false; uint8_t *reply = context->m_replyCur; *reply++ = (uint8_t)(value >> 8); *reply++ = (uint8_t)value; context->m_replyCur = reply; return true; } /** @brief Add uint32 to reply packet **/ static bool sAddUInt32(uSynergyContext *context, uint32_t value) { if (context->m_replyCur - context->m_replyBuffer + sizeof(value) > USYNERGY_REPLY_BUFFER_SIZE) return false; uint8_t *reply = context->m_replyCur; *reply++ = (uint8_t)(value >> 24); *reply++ = (uint8_t)(value >> 16); *reply++ = (uint8_t)(value >> 8); *reply++ = (uint8_t)value; context->m_replyCur = reply; return true; } /** @brief Mark context as being disconnected **/ static void sSetDisconnected(uSynergyContext *context, enum uSynergyError err) { context->m_connected = false; context->m_hasReceivedHello = false; context->m_isCaptured = false; context->m_receiveOfs = 0; context->m_replyCur = context->m_replyBuffer + 4; context->m_sequenceNumber = 0; context->m_lastError = err; } /** @brief Send reply packet **/ static bool sSendReply(uSynergyContext *context) { // Set header size uint8_t *reply_buf = context->m_replyBuffer; uint32_t reply_len = (uint32_t)(context->m_replyCur - reply_buf); /* Total size of reply */ uint32_t body_len = reply_len - 4; /* Size of body */ bool ret; reply_buf[0] = (uint8_t)(body_len >> 24); reply_buf[1] = (uint8_t)(body_len >> 16); reply_buf[2] = (uint8_t)(body_len >> 8); reply_buf[3] = (uint8_t)body_len; // Send reply ret = context->m_sendFunc(context->m_cookie, context->m_replyBuffer, reply_len); // Reset reply buffer write pointer context->m_replyCur = context->m_replyBuffer+4; return ret; } /* mouse callbacks */ static void sSendMouseWheelCallback(uSynergyContext *context, int16_t x, int16_t y) { if (!context->m_mouseWheelCallback) return; if (context->m_resChanged || !context->m_infoCurrent) return; context->m_mouseWheelCallback(context->m_cookie, x, y); } static void sSendMouseButtonDownCallback(uSynergyContext *context, enum uSynergyMouseButton button) { if (!context->m_mouseButtonDownCallback) return; context->m_mouseButtonDownCallback(context->m_cookie, button); } static void sSendMouseButtonUpCallback(uSynergyContext *context, enum uSynergyMouseButton button) { if (!context->m_mouseButtonUpCallback) return; context->m_mouseButtonUpCallback(context->m_cookie, button); } static void sSendMouseMoveCallback(uSynergyContext *context, bool rel, int16_t x, int16_t y) { if (!context->m_mouseMoveCallback) return; context->m_mouseMoveCallback(context->m_cookie, rel, x, y); } /** @brief Send screensaver callback **/ static void sSendScreensaverCallback(uSynergyContext *context, bool state) { if (!context->m_screensaverCallback) return; context->m_screensaverCallback(context->m_cookie, state); } /** @brief Send keyboard callback when a key has been pressed or released **/ static void sSendKeyboardCallback(uSynergyContext *context, uint16_t key, uint16_t id, uint16_t modifiers, bool down, bool repeat) { // Skip if no callback is installed if (context->m_keyboardCallback == 0L) return; // Send callback context->m_keyboardCallback(context->m_cookie, key, id, modifiers, down, repeat); } /** @brief Send joystick callback **/ static void sSendJoystickCallback(uSynergyContext *context, uint8_t joyNum) { int8_t *sticks; // Skip if no callback is installed if (context->m_joystickCallback == 0L) return; // Send callback sticks = context->m_joystickSticks[joyNum]; context->m_joystickCallback(context->m_cookie, joyNum, context->m_joystickButtons[joyNum], sticks[0], sticks[1], sticks[2], sticks[3]); } /** @brief Send clipboard data **/ void uSynergySendClipboard(uSynergyContext *context, int id, uint32_t len, const unsigned char *text) { char buffer[128]; int pos; uint32_t chunk_len; // Calculate maximum size that will fit in a reply packet uint32_t overhead_size = 4 + /* Message size */ 4 + /* Message ID */ 1 + /* Clipboard index */ 4 + /* Sequence number */ 4 + /* Rest of message size (because it's a Synergy string from here on) */ 4 + /* Number of clipboard formats */ 4 + /* Clipboard format */ 4; /* Clipboard data length */ uint32_t max_length = USYNERGY_REPLY_BUFFER_SIZE - overhead_size; unsigned char chunk[max_length]; // Assemble start packet. sprintf(buffer, "%" PRIu32, len); if (!(sAddString(context, "DCLP") && sAddUInt8(context, id) && /* Clipboard index */ sAddUInt32(context, context->m_sequenceNumber) && sAddUInt8(context, SYN_DATA_START) && sAddUInt32(context, strlen(buffer)) && /* Rest of message size: mark, string size of message */ sAddString(context, buffer))) { REPLY_ERROR(); } sSendReply(context); // Now we do the chunks. for (pos = 0; pos < len; pos += chunk_len) { chunk_len = ((len - pos) > max_length) ? max_length : len - pos; memmove(chunk, text + pos, chunk_len); if (!(sAddString(context, "DCLP") && sAddUInt8(context, id) && sAddUInt32(context, context->m_sequenceNumber) && sAddUInt8(context, SYN_DATA_CHUNK) && sAddUInt32(context, chunk_len) && sAddBin(context, chunk, chunk_len))) { REPLY_ERROR(); } sSendReply(context); } //And then we're done if (!(sAddString(context, "DCLP") && sAddUInt8(context, id) && sAddUInt32(context, context->m_sequenceNumber) && sAddUInt8(context, SYN_DATA_END) && sAddUInt32(context, 0))) { REPLY_ERROR(); } sSendReply(context); } /** @brief Check if the given message contains a valid welcome message, to allow for barrier compatibility **/ static char *sImplementations[] = { "Barrier", "Synergy", NULL }; static char *sIsWelcome(struct sspBuf *msg) { char **i; for (i = sImplementations; *i; ++i) { if (strlen(*i) > msg->len) continue; if (memcmp(msg->data, *i, strlen(*i)) == 0) { sspSeek(msg, strlen(*i)); return *i; } } return NULL; } /** @brief Parse a single client message, update state, send callbacks and send replies **/ static void sProcessMessage(uSynergyContext *context, struct sspBuf *msg) { // We have a packet! const char *imp; char pkt_id[5] = {0}; if ((imp = sIsWelcome(msg))) { // Welcome message // kMsgHello = "Synergy%2i%2i" // kMsgHelloBack = "Synergy%2i%2i%s" uint16_t server_major, server_minor; if (!(sspNetU16(msg, &server_major) && sspNetU16(msg, &server_minor))) { PARSE_ERROR(); } logInfo("Server is %s %" PRIu16 ".%" PRIu16, imp, server_major, server_minor); // Initialize position in reply buffer -- discards leftovers from // failed send attempts, ensures no protocol errors on initialization context->m_replyCur = context->m_replyBuffer+4; if(!(sAddString(context, imp) && sAddUInt16(context, USYNERGY_PROTOCOL_MAJOR) && sAddUInt16(context, USYNERGY_PROTOCOL_MINOR) && sAddUInt32(context, (uint32_t)strlen(context->m_clientName)) && sAddString(context, context->m_clientName))) { REPLY_ERROR(); } if (!sSendReply(context)) { // Send reply failed, let's try to reconnect logErr("SendReply failed, trying to reconnect in a second"); context->m_connected = false; context->m_sleepFunc(context->m_cookie, 1000); } else { // Let's assume we're connected logInfo("Connected as client \"%s\"", context->m_clientName); context->m_hasReceivedHello = true; context->m_implementation = imp; } return; } if (!sspMemMove(pkt_id, msg, 4)) { PARSE_ERROR(); } else if (!strcmp(pkt_id, "QINF")) { // Screen info. Reply with DINF // kMsgQInfo = "QINF" // kMsgDInfo = "DINF%2i%2i%2i%2i%2i%2i%2i" uint16_t x = 0, y = 0, warp = 0; if (!(sAddString(context, "DINF") && sAddUInt16(context, x) && sAddUInt16(context, y) && sAddUInt16(context, context->m_clientWidth) && sAddUInt16(context, context->m_clientHeight) && sAddUInt16(context, warp) && sAddUInt16(context, 0) && // mx? sAddUInt16(context, 0))) { // my? REPLY_ERROR(); } sSendReply(context); context->m_infoCurrent = false; return; } else if (!strcmp(pkt_id, "CIAK")) { // kMsgCInfoAck = "CIAK" context->m_infoCurrent = true; return; } else if (!strcmp(pkt_id, "CROP")) { // Do nothing? // kMsgCResetOptions = "CROP" return; } else if (!strcmp(pkt_id, "CINN")) { // Screen enter. Reply with CNOP // kMsgCEnter = "CINN%2i%2i%4i%2i" // Obtain the Synergy sequence number if (!(sspNet16(msg, NULL) && sspNet16(msg, NULL) && sspNetU32(msg, &context->m_sequenceNumber))) { PARSE_ERROR(); } context->m_isCaptured = true; // Call callback if (context->m_screenActiveCallback != 0L) context->m_screenActiveCallback(context->m_cookie, true); } else if (!strcmp(pkt_id, "COUT")) { // Screen leave // kMsgCLeave = "COUT" context->m_isCaptured = false; // Send clipboard data for (int id = 0; id < 2; ++id) { if (context->m_clipGrabbed[id]) { uSynergySendClipboard(context, id, context->m_clipPos[id], context->m_clipBuf[id]); context->m_clipGrabbed[id] = false; } } // Call callback if (context->m_screenActiveCallback != 0L) context->m_screenActiveCallback(context->m_cookie, false); } else if (!strcmp(pkt_id, "CSEC")) { //Screensaver state char active; if (!sspChar(msg, &active)) { PARSE_ERROR(); } sSendScreensaverCallback(context, active); } else if (!strcmp(pkt_id, "DMDN")) { // Mouse down // kMsgDMouseDown = "DMDN%1i" char btn; if (!sspChar(msg, &btn)) { PARSE_ERROR(); } //logDbgSyn("DMDN: btn %hhd", btn); sSendMouseButtonDownCallback(context, btn); } else if (!strcmp(pkt_id, "DMUP")) { // Mouse up // kMsgDMouseUp = "DMUP%1i" char btn; if (!sspChar(msg, &btn)) { PARSE_ERROR(); } //logDbgSyn("DMUP: btn %hhd", btn); sSendMouseButtonUpCallback(context, btn); } else if (!strcmp(pkt_id, "DMMV")) { // Mouse move. Reply with CNOP // kMsgDMouseMove = "DMMV%2i%2i" int16_t x, y; if (!(sspNet16(msg, &x) && sspNet16(msg, &y))) { PARSE_ERROR(); } //logDbgSyn("DKMV: x %" PRId16 ", y %" PRId16, x, y); sSendMouseMoveCallback(context, false, x, y); } else if (!strcmp(pkt_id, "DMRM")) { //Relative mouse move. int16_t x, y; if (!(sspNet16(msg, &x) && sspNet16(msg, &y))) { PARSE_ERROR(); } //logDbgSyn("DKRM: x %" PRId16 ", y %" PRId16, x, y); sSendMouseMoveCallback(context, true, x, y); } else if (!strcmp(pkt_id, "DMWM")) { // Mouse wheel // kMsgDMouseWheel = "DMWM%2i%2i" // kMsgDMouseWheel1_0 = "DMWM%2i" int16_t x, y; if (!(sspNet16(msg, &x) && sspNet16(msg, &y))) { PARSE_ERROR(); } //logDbgSyn("DKWM: x %" PRId16 ", y %" PRId16, x, y); sSendMouseWheelCallback(context, x, y); } else if (!strcmp(pkt_id, "DKDN")) { // Key down // kMsgDKeyDown = "DKDN%2i%2i%2i" // kMsgDKeyDown1_0 = "DKDN%2i%2i" uint16_t id, mod, key; if (!(sspNetU16(msg, &id) && sspNetU16(msg, &mod) && sspNetU16(msg, &key))) { PARSE_ERROR(); } logDbgSyn("DKDN: id %" PRIu16 ", mod %" PRIx16 ", key %" PRIu16, id, mod, key); sSendKeyboardCallback(context, context->m_useRawKeyCodes ? key : id, id, mod, true, false); } else if (!strcmp(pkt_id, "DKRP")) { // Key repeat // kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i" // kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i" uint16_t id, mod, count, key; if (!(sspNetU16(msg, &id) && sspNetU16(msg, &mod) && sspNetU16(msg, &count) && sspNetU16(msg, &key))) { PARSE_ERROR(); } logDbgSyn("DKRP: id %" PRIu16 ", mod %" PRIx16 ", count %" PRIu16 ", key %" PRIu16, id, mod, count, key); sSendKeyboardCallback(context, context->m_useRawKeyCodes ? key : id, id, mod, true, true); } else if (!strcmp(pkt_id, "DKUP")) { // Key up // kMsgDKeyUp = "DKUP%2i%2i%2i" // kMsgDKeyUp1_0 = "DKUP%2i%2i" uint16_t id, mod, key; if (!(sspNetU16(msg, &id) && sspNetU16(msg, &mod) && sspNetU16(msg, &key))) { PARSE_ERROR(); } logDbgSyn("DKUP: id %" PRIu16 ", mod %" PRIx16 ", key %" PRIu16, id, mod, key); sSendKeyboardCallback(context, context->m_useRawKeyCodes ? key : id, id, mod, false, false); } else if (!strcmp(pkt_id, "DGBT")) { // Joystick buttons // kMsgDGameButtons = "DGBT%1i%2i"; uint8_t joy_num; uint16_t state; if (!(sspUChar(msg, &joy_num) && sspNetU16(msg, &state))) { PARSE_ERROR(); } if (joy_num<USYNERGY_NUM_JOYSTICKS) { context->m_joystickButtons[joy_num] = state; sSendJoystickCallback(context, joy_num); } } else if (!strcmp(pkt_id, "DGST")) { // Joystick sticks // kMsgDGameSticks = "DGST%1i%1i%1i%1i%1i"; uint8_t joy_num; int8_t state[4]; if (!(sspUChar(msg, &joy_num) && sspMemMove(state, msg, sizeof(state)))) { PARSE_ERROR(); } if (joy_num<USYNERGY_NUM_JOYSTICKS) { // Copy stick state, then send callback memcpy(context->m_joystickSticks[joy_num], state, sizeof(state)); sSendJoystickCallback(context, joy_num); } } else if (!strcmp(pkt_id, "DSOP")) { // Set options // kMsgDSetOptions = "DSOP%4I" } else if (!strcmp(pkt_id, "CALV")) { // Keepalive, reply with CALV and then CNOP // kMsgCKeepAlive = "CALV" logDbg("Got CALV"); if (!sAddString(context, "CALV")) { REPLY_ERROR(); } sSendReply(context); // now reply with CNOP } else if (!strcmp(pkt_id, "CCLP")) { // Clipboard grab // CCLP%1i%4i // // 1 uint32: size // 4 char: identifier ("CCLP") // 1 uint8_t: clipboard ID // 1 uint32_t: sequence number unsigned char id; uint32_t seq; if (!(sspUChar(msg, &id) && sspNetU32(msg, &seq))) { PARSE_ERROR(); } /* XXX: I think the sequence number is always zero on receive?*/ context->m_clipGrabbed[id] = false; } else if (!strcmp(pkt_id, "DCLP")) { // Clipboard message // kMsgDClipboard = "DCLP%1i%4i%s" // // The clipboard message contains: // 1 uint32: The size of the message // 4 chars: The identifier ("DCLP") // 1 uint8: The clipboard index // 1 uint32: The sequence number. It's zero, because this message is always coming from the server? // 1 uint32: The total size of the remaining 'string' (as per the Synergy %s string format (which is 1 uint32 for size followed by a char buffer (not necessarily null terminated)). // 1 uint32: The number of formats present in the message // And then 'number of formats' times the following: // 1 uint32: The format of the clipboard data // 1 uint32: The size n of the clipboard data // n uint8: The clipboard data unsigned char id, mark; uint32_t seq, len; if (!(sspUChar(msg, &id) && sspNetU32(msg, &seq) && sspUChar(msg, &mark) && sspNetU32(msg, &len))) { PARSE_ERROR(); } if (mark == SYN_DATA_START) { context->m_clipGrabbed[id] = false; context->m_clipInStream[id] = true; context->m_clipPos[id] = 0; char expected_len[len + 1]; sspMemMove(expected_len, msg, len); expected_len[len] = '\0'; context->m_clipPosExpect[id] = atoi(expected_len); if (context->m_clipPosExpect[id] > context->m_clipLen[id]) { context->m_clipBuf[id] = xrealloc(context->m_clipBuf[id], context->m_clipPosExpect[id]); } } else if (mark == SYN_DATA_CHUNK && context->m_clipInStream[id]) { if ((context->m_clipPos[id] + len) > context->m_clipPosExpect[id]) { logErr("Packet too long!"); return; } sspMemMove(context->m_clipBuf[id] + context->m_clipPos[id], msg, len); context->m_clipPos[id] += len; } else if (mark == SYN_DATA_END && context->m_clipInStream[id]) { struct sspBuf clipmsg = { .data = context->m_clipBuf[id], .pos = 0, .len = context->m_clipPosExpect[id] }; uint32_t num_formats, format, size; if (!sspNetU32(&clipmsg, &num_formats)) { PARSE_ERROR(); } for (; num_formats; num_formats--) { // Parse clipboard format header if (!(sspNetU32(&clipmsg, &format) && sspNetU32(&clipmsg, &size))) { PARSE_ERROR(); } // Call callback if (context->m_clipboardCallback) { //First check size against buffer if (clipmsg.pos + size > clipmsg.len) { PARSE_ERROR(); } context->m_clipboardCallback(context->m_cookie, id, format, clipmsg.data + clipmsg.pos, size); } if (!sspSeek(&clipmsg, size)) { PARSE_ERROR(); } } context->m_clipInStream[id] = false; } } else if (!strcmp(pkt_id, "CBYE")) { logInfo("Server disconnected"); sSetDisconnected(context, USYNERGY_ERROR_NONE); return; } else if (!strcmp(pkt_id, "EBAD")) { logErr("Protocol error"); sSetDisconnected(context, USYNERGY_ERROR_EBAD); return; } else if (!strcmp(pkt_id, "EBSY")) { logErr("Other screen already connected with our name"); sSetDisconnected(context, USYNERGY_ERROR_EBSY); return; } else { // Unknown packet, could be any of these // kMsgCNoop = "CNOP" // kMsgCClose = "CBYE" // kMsgCClipboard = "CCLP%1i%4i" // kMsgCScreenSaver = "CSEC%1i" // kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i" // kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i" // kMsgDMouseRelMove = "DMRM%2i%2i" // kMsgEIncompatible = "EICV%2i%2i" // kMsgEBusy = "EBSY" // kMsgEUnknown = "EUNK" // kMsgEBad = "EBAD" logWarn("Unknown packet '%s'", pkt_id); return; } // Reply with CNOP maybe? if(!sAddString(context, "CNOP")) { REPLY_ERROR(); } sSendReply(context); } #undef USYNERGY_IS_PACKET /** @brief Update a connected context **/ static void sUpdateContext(uSynergyContext *context) { /* Receive data (blocking) */ int receive_size = USYNERGY_RECEIVE_BUFFER_SIZE - context->m_receiveOfs; int num_received = 0; uint32_t packlen = 0; if (context->m_receiveFunc(context->m_cookie, context->m_receiveBuffer + context->m_receiveOfs, receive_size, &num_received) == false) { /* Receive failed, let's try to reconnect */ logErr("Receive failed (%d bytes asked, %d bytes received), trying to reconnect in a second", receive_size, num_received); /* The *only* way this can occur normally is with a timeout so that's what we assume*/ sSetDisconnected(context, USYNERGY_ERROR_TIMEOUT); context->m_sleepFunc(context->m_cookie, 1000); return; } context->m_receiveOfs += num_received; /* If we didn't receive any data then we're probably still polling to get connected and therefore not getting any data back. To avoid overloading the system with a Synergy thread that would hammer on polling, we let it rest for a bit if there's no data. */ if (num_received == 0) context->m_sleepFunc(context->m_cookie, 500); /* Check for timeouts */ if (context->m_hasReceivedHello) { uint32_t cur_time = context->m_getTimeFunc(); if (num_received == 0) { /* Timeout after 2 secs of inactivity (we received no CALV) */ if ((cur_time - context->m_lastMessageTime) > USYNERGY_IDLE_TIMEOUT) sSetDisconnected(context, USYNERGY_ERROR_TIMEOUT); } else context->m_lastMessageTime = cur_time; } /* Eat packets */ for (;;) { /* Grab packet length and bail out if the packet goes beyond the end of the buffer */ packlen = sNetToNative32(context->m_receiveBuffer); if (packlen+4 > context->m_receiveOfs) break; /* Process message */ struct sspBuf msg = { .data = context->m_receiveBuffer + 4, .pos = 0, .len = packlen }; sProcessMessage(context, &msg); /* if we've lost the connection, don't bother with further * processing */ if (!context->m_connected) return; /* Move packet to front of buffer */ memmove(context->m_receiveBuffer, context->m_receiveBuffer+packlen+4, context->m_receiveOfs-packlen-4); context->m_receiveOfs -= packlen+4; } /* Throw away over-sized packets */ if (packlen > USYNERGY_RECEIVE_BUFFER_SIZE) { /* Oversized packet, ditch tail end */ logWarn("Oversized packet: '%c%c%c%c' (length %d)", context->m_receiveBuffer[4], context->m_receiveBuffer[5], context->m_receiveBuffer[6], context->m_receiveBuffer[7], packlen); num_received = context->m_receiveOfs-4; // 4 bytes for the size field while (num_received != packlen) { int buffer_left = packlen - num_received; int to_receive = buffer_left < USYNERGY_RECEIVE_BUFFER_SIZE ? buffer_left : USYNERGY_RECEIVE_BUFFER_SIZE; int ditch_received = 0; if (context->m_receiveFunc(context->m_cookie, context->m_receiveBuffer, to_receive, &ditch_received) == false) { /* Receive failed, let's try to reconnect */ logErr("Receive failed, trying to reconnect in a second"); /* this only happens with timeout*/ sSetDisconnected(context, USYNERGY_ERROR_TIMEOUT); context->m_sleepFunc(context->m_cookie, 1000); break; } else { num_received += ditch_received; } } context->m_receiveOfs = 0; } } //--------------------------------------------------------------------------------------------------------------------- // Public interface //--------------------------------------------------------------------------------------------------------------------- /** @brief Initialize uSynergy context **/ void uSynergyInit(uSynergyContext *context) { /* Zero memory */ memset(context, 0, sizeof(uSynergyContext)); /* Initialize to default state */ sSetDisconnected(context, USYNERGY_ERROR__INIT); } /** @brief Update uSynergy **/ void uSynergyUpdate(uSynergyContext *context) { if (context->m_connected) { /* Update context, receive data, call callbacks */ sUpdateContext(context); } else { /* Try to connect */ if (context->m_lastError > 0) { if (context->m_errorIsFatal[context->m_lastError]) { logErr("Last error received (code %d) is configured as fatal, exiting", context->m_lastError); Exit(SES_ERROR_SYN); } } if (context->m_connectFunc(context->m_cookie)) { context->m_connected = true; } else { logErr("Connection attempt failed, trying to reconnect in a second"); context->m_sleepFunc(context->m_cookie, 1000); } } } /* check all formats in the clipboard message buffer */ static bool uSynergyClipBufContains(uSynergyContext *context, enum uSynergyClipboardId id, uint32_t len, const char *data) { uint32_t formats, flen; struct sspBuf buf = { .data = context->m_clipBuf[id], .pos = 0, .len = context->m_clipLen[id] }; if (!buf.data) return false; if (context->m_clipInStream[id]) return false; if (!sspNetU32(&buf, &formats)) { logErr("Clipboard parse error: %d, %d", buf.pos, buf.len); return false; } for (int i = 0; i < formats; ++i) { //skip the format, only check the raw data if (!(sspNetU32(&buf, NULL) && sspNetU32(&buf, &flen))) { logErr("Clipboard format parse error"); return false; } if (flen == len) { if (!memcmp(data, buf.data + buf.pos, len)) { return true; } } } return false; } /* generic functions to add values to raw buffer */ static uint8_t *buf_add_int32(uint8_t *buf, uint32_t val) { buf[0] = (val >> 24) & 0xFF; buf[1] = (val >> 16) & 0xFF; buf[2] = (val >> 8) & 0xFF; buf[3] = val & 0xFF; return buf + 4; } /* Update clipboard buffer from local clipboard */ void uSynergyUpdateClipBuf(uSynergyContext *context, enum uSynergyClipboardId id, uint32_t len, const char *data) { /* to prevent feedback loops, check to make sure the data is actually * different from what we've already got */ if (uSynergyClipBufContains(context, id, len, data)) return; /* grab the clipboard, initialize the buffer */ context->m_clipInStream[id] = false; context->m_clipGrabbed[id] = true; context->m_clipPos[id] = len + 4 + 4 + 4; //format count, format ID, size, data if (context->m_clipLen[id] < context->m_clipPos[id]) { context->m_clipLen[id] = context->m_clipPos[id]; context->m_clipBuf[id] = xrealloc(context->m_clipBuf[id], context->m_clipLen[id]); } /*populate buffer*/ uint8_t *buf = context->m_clipBuf[id]; buf = buf_add_int32(buf, 1); //formats buf = buf_add_int32(buf, USYNERGY_CLIPBOARD_FORMAT_TEXT); //type, text only for now buf = buf_add_int32(buf, len); //length of actual data memmove(buf, data, len); /* send CCLP -- CCLP%1i%4i */ if (!(sAddString(context, "CCLP") && sAddUInt8(context, id) && sAddUInt32(context, context->m_sequenceNumber))) { REPLY_ERROR(); } sSendReply(context); } /* Update resolution */ void uSynergyUpdateRes(uSynergyContext *context, int16_t width, int16_t height) { context->m_clientWidth = width; context->m_clientHeight = height; if (context->m_connected) { logDbg("Sending DINF to update screen resolution"); /* send information update */ uint16_t x = 0, y = 0, warp = 0; sAddString(context, "DINF"); sAddUInt16(context, x); sAddUInt16(context, y); sAddUInt16(context, context->m_clientWidth); sAddUInt16(context, context->m_clientHeight); sAddUInt16(context, warp); sAddUInt16(context, 0); // mx? sAddUInt16(context, 0); // my? sSendReply(context); context->m_infoCurrent = false; } } 07070100000032000081A400000000000000000000000164751F5700004305000000000000000000000000000000000000001E00000000waynergy-0.16+3/src/wayland.c#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <time.h> #include <poll.h> #include <wayland-client.h> #include <wayland-client-protocol.h> #include <sys/mman.h> #include "config.h" #include "os.h" #include "ssb.h" #include "xmem.h" #include "wayland.h" #include <stdbool.h> #include "log.h" #include "sig.h" static char *display_strerror(int error) { switch (error) { case WL_DISPLAY_ERROR_INVALID_OBJECT: return "server couldn't find object"; case WL_DISPLAY_ERROR_INVALID_METHOD: return "method doesn't exist on specified interface or malformed request"; case WL_DISPLAY_ERROR_NO_MEMORY: return "server is out of memory"; case WL_DISPLAY_ERROR_IMPLEMENTATION: return "compositor implementation error"; } return strerror(error); } static void wl_log_handler(const char *fmt, va_list ap) { logOutV(LOG_ERR, fmt, ap); if (configTryBool("wayland/log_fatal", true)) { logErr("Logged wayland errors set to fatal"); ExitOrRestart(SES_ERROR_WL); } } static bool wl_display_flush_base(struct wlContext *ctx) { int error; if ((error = wl_display_get_error(ctx->display))) { logErr("Wayland display error %d: %s", error, display_strerror(error)); ExitOrRestart(SES_ERROR_WL); } if (wl_display_flush(ctx->display) == -1) { if (errno == EAGAIN) { return false; } else { if ((error = wl_display_get_error(ctx->display))) { logErr("Wayland display error %d: %s", error, display_strerror(error)); } else { logPErr("No wayland display error, but flush failed"); } ExitOrRestart(SES_ERROR_WL); } } return true; } static bool wl_display_flush_block(struct wlContext *ctx) { struct pollfd pfd = {0}; int pret; pfd.fd = wlPrepareFd(ctx); pfd.events = POLLOUT; pret = poll(&pfd, 1, ctx->timeout); if (pret == 1) { if (pfd.revents & POLLOUT) { if (wl_display_flush_base(ctx)) { return true; } logErr("blocking display flush failed"); } else { logErr("blocking display flush socket unwritable"); } } else if (pret == 0) { logErr("blocking display flush timed out"); } else if (pret == -1) { logPErr("blocking display poll failed"); } return false; } void wlDisplayFlush(struct wlContext *ctx) { if (!wl_display_flush_base(ctx)) { if (!wl_display_flush_block(ctx)) { ExitOrRestart(SES_ERROR_WL); } } } void wlOutputAppend(struct wlOutput **outputs, struct wl_output *output, struct zxdg_output_v1 *xdg_output, uint32_t wl_name) { struct wlOutput *l; struct wlOutput *n = xmalloc(sizeof(*n)); memset(n, 0, sizeof(*n)); n->wl_output = output; n->xdg_output = xdg_output; n->wl_name = wl_name; if (!*outputs) { *outputs = n; } else { for (l = *outputs; l->next; l = l->next); l->next = n; } } struct wlOutput *wlOutputGet(struct wlOutput *outputs, struct wl_output *wl_output) { struct wlOutput *l = NULL; for (l = outputs; l; l = l->next) { if (l->wl_output == wl_output) { break; } } return l; } struct wlOutput *wlOutputGetXdg(struct wlOutput *outputs, struct zxdg_output_v1 *xdg_output) { struct wlOutput *l = NULL; for (l = outputs; l; l = l->next) { if (l->xdg_output == xdg_output) break; } return l; } struct wlOutput *wlOutputGetWlName(struct wlOutput *outputs, uint32_t wl_name) { struct wlOutput *l = NULL; for (l = outputs; l; l = l->next) { if (l->wl_name == wl_name) break; } return l; } void wlOutputRemove(struct wlOutput **outputs, struct wlOutput *output) { struct wlOutput *prev = NULL; if (!outputs) return; if (*outputs != output) { for (prev = *outputs; prev; prev = prev->next) { if (prev->next == output) break; } if (!prev) { logErr("Tried to remove unknown output"); return; } prev->next = prev->next->next; } else { *outputs = NULL; } free(output->name); free(output->desc); if (output->xdg_output) { zxdg_output_v1_destroy(output->xdg_output); } if (output->wl_output) { wl_output_destroy(output->wl_output); } free(output); } static void output_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) { struct wlContext *ctx = data; struct wlOutput *output = wlOutputGet(ctx->outputs, wl_output); if (!output) { logErr("Output not found"); return; } logDbg("Mutating output..."); if (output->have_log_pos) { logDbg("Except not really, because the logical position outweighs this"); return; } output->complete = false; output->x = x; output->y = y; logDbg("Got output at position %d,%d", x, y); } static void output_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { struct wlContext *ctx = data; struct wlOutput *output = wlOutputGet(ctx->outputs, wl_output); bool preferred = flags & WL_OUTPUT_MODE_PREFERRED; bool current = flags & WL_OUTPUT_MODE_CURRENT; logDbg("Got %smode: %dx%d@%d%s", current ? "current " : "", width, height, refresh, preferred ? "*" : ""); if (!output) { logErr("Output not found in list"); return; } if (current) { if (!preferred) { logInfo("Not using preferred mode on output -- check config"); } logDbg("Mutating output..."); if (output->have_log_size) { logDbg("Except not really, because logical size outweighs this"); return; } output->complete = false; output->width = width; output->height = height; } } static void output_scale(void *data, struct wl_output *wl_output, int32_t factor) { struct wlContext *ctx = data; struct wlOutput *output = wlOutputGet(ctx->outputs, wl_output); logDbg("Got scale factor for output: %d", factor); if (!output) { logErr("Output not found in list"); return; } logDbg("Mutating output..."); output->complete = false; output->scale = factor; } static void output_done(void *data, struct wl_output *wl_output) { struct wlContext *ctx = data; struct wlOutput *output = wlOutputGet(ctx->outputs, wl_output); if (!output) { logErr("Output not found in list"); return; } output->complete = true; if (output->name) { logInfo("Output name: %s", output->name); } if (output->desc) { logInfo("Output description: %s", output->desc); } logInfo("Output updated: %dx%d at %d, %d (scale: %d)", output->width, output->height, output->x, output->y, output->scale); /* fire event if all outputs are complete. */ bool complete = true; for (output = ctx->outputs; output; output = output->next) { complete = complete && output->complete; } if (complete) { logDbg("All outputs updated, triggering event"); if (ctx->on_output_update) ctx->on_output_update(ctx); } } static void xdg_output_pos(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { logDbg("Got xdg output position: %d, %d", x, y); struct wlContext *ctx = data; struct wlOutput *output = wlOutputGetXdg(ctx->outputs, xdg_output); if (!output) { logErr("Could not find xdg output"); return; } logDbg("Mutating output from xdg_output event"); output->complete = false; output->have_log_pos = true; output->x = x; output->y = y; } static void xdg_output_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { logDbg("Got xdg output size: %dx%d", width, height); struct wlContext *ctx = data; struct wlOutput *output = wlOutputGetXdg(ctx->outputs, xdg_output); if (!output) { logErr("Could not find xdg output"); return; } logDbg("Mutating output from xdg_output event"); output->complete = false; output->have_log_size = true; output->width = width; output->height = height; } static void xdg_output_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { logDbg("Got xdg output name: %s", name); struct wlContext *ctx = data; struct wlOutput *output = wlOutputGetXdg(ctx->outputs, xdg_output); if (!output) { logErr("Could not find xdg output"); return; } logDbg("Mutating output from xdg_output event"); output->complete = false; if (output->name) { free(output->name); } output->name = xstrdup(name); } static void xdg_output_desc(void *data, struct zxdg_output_v1 *xdg_output, const char *desc) { logDbg("Got xdg output desc: %s", desc); struct wlContext *ctx = data; struct wlOutput *output = wlOutputGetXdg(ctx->outputs, xdg_output); if (!output) { logErr("Could not find xdg output"); return; } logDbg("Mutating output from xdg_output event"); output->complete = false; if (output->desc) { free(output->desc); } output->desc = xstrdup(desc); } static struct zxdg_output_v1_listener xdg_output_listener = { .logical_position = xdg_output_pos, .logical_size = xdg_output_size, .name = xdg_output_name, .description = xdg_output_desc, }; static struct wl_output_listener output_listener = { .geometry = output_geometry, .mode = output_mode, .done = output_done, .scale = output_scale }; static void keyboard_keymap(void *data, struct wl_keyboard *wl_kb, uint32_t format, int32_t fd, uint32_t size) { struct wlContext *ctx = data; char *buf = NULL; char *map = NULL; if (!configTryBool("wl_keyboard_map", true)) { goto cleanup; } if ((map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { logPDbg("Could not map keymap from fd"); goto cleanup; } buf = xcalloc(size + 1, 1); memcpy(buf, map, size); free(ctx->kb_map); ctx->kb_map = buf; buf = NULL; logDbg("Current keymap updated"); free(buf); munmap(map, size); cleanup: close(fd); } static void keyboard_enter(void *data, struct wl_keyboard *wl_kb, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { } static void keyboard_leave(void *data, struct wl_keyboard *wl_kb, uint32_t serial, struct wl_surface *surface) { } static void keyboard_key(void *data, struct wl_keyboard *wl_kb, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { } static void keyboard_mod(void *data, struct wl_keyboard *wl_kb, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { } static void keyboard_rep(void *data, struct wl_keyboard *wl_kb, int32_t rate, int32_t delay) { } static struct wl_keyboard_listener keyboard_listener = { .keymap = keyboard_keymap, .enter = keyboard_enter, .leave = keyboard_leave, .key = keyboard_key, .modifiers = keyboard_mod, .repeat_info = keyboard_rep, }; static void seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t caps) { struct wlContext *ctx = data; ctx->seat_caps = caps; if (caps & WL_SEAT_CAPABILITY_POINTER) { logDbg("Seat has pointer"); } if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { logDbg("Seat has keyboard"); ctx->kb = wl_seat_get_keyboard(wl_seat); wl_keyboard_add_listener(ctx->kb, &keyboard_listener, ctx); wl_display_dispatch(ctx->display); wl_display_roundtrip(ctx->display); } } static void seat_name(void *data, struct wl_seat *seat, const char *name) { logDbg("Seat name is %s", name); } static struct wl_seat_listener seat_listener = { .capabilities = seat_capabilities, .name = seat_name, }; static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct wlContext *ctx = data; struct wl_output *wl_output; struct zxdg_output_v1 *xdg_output; if (strcmp(interface, wl_seat_interface.name) == 0) { ctx->seat = wl_registry_bind(registry, name, &wl_seat_interface, version); wl_seat_add_listener(ctx->seat, &seat_listener, ctx); } else if (strcmp(interface, zwlr_virtual_pointer_manager_v1_interface.name) == 0) { ctx->pointer_manager = wl_registry_bind(registry, name, &zwlr_virtual_pointer_manager_v1_interface, 1); } else if (strcmp(interface, zwp_virtual_keyboard_manager_v1_interface.name) == 0) { ctx->keyboard_manager = wl_registry_bind(registry, name, &zwp_virtual_keyboard_manager_v1_interface, 1); } else if (strcmp(interface, org_kde_kwin_fake_input_interface.name) == 0) { ctx->fake_input = wl_registry_bind(registry, name, &org_kde_kwin_fake_input_interface, 4); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) ==0) { if (version < 3) { logInfo("xdg-output too old (version %d)", version); return; } ctx->output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 3); if (ctx->outputs) { for (struct wlOutput *output = ctx->outputs; output; output = output->next) { if (!output->xdg_output) { output->xdg_output = zxdg_output_manager_v1_get_xdg_output(ctx->output_manager, output->wl_output); zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, ctx); } } } } else if (strcmp(interface, wl_output_interface.name) == 0) { wl_output = wl_registry_bind(registry, name, &wl_output_interface, 2); wl_output_add_listener(wl_output, &output_listener, ctx); if (ctx->output_manager) { xdg_output = zxdg_output_manager_v1_get_xdg_output(ctx->output_manager, wl_output); zxdg_output_v1_add_listener(xdg_output, &xdg_output_listener, ctx); } else { xdg_output = NULL; } wlOutputAppend(&ctx->outputs, wl_output, xdg_output, name); } else if (strcmp(interface, org_kde_kwin_idle_interface.name) == 0) { logDbg("Got idle manager"); ctx->idle_manager = wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, version); } } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct wlContext *ctx = data; /* possible objects */ struct wlOutput *output; /* for now we only handle the case of outputs going away */ output = wlOutputGetWlName(ctx->outputs, name); if (output) { logInfo("Lost output %s", output->name ? output->name : ""); wlOutputRemove(&ctx->outputs, output); ctx->on_output_update(ctx); } } static const struct wl_registry_listener registry_listener = { .global = handle_global, .global_remove = handle_global_remove, }; uint32_t wlTS(struct wlContext *ctx) { struct timespec ts; if (ctx->epoch == -1) { ctx->epoch = time(NULL); } clock_gettime(CLOCK_MONOTONIC, &ts); ts.tv_sec -= ctx->epoch; return (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000); } void wlClose(struct wlContext *ctx) { return; } bool wlSetup(struct wlContext *ctx, int width, int height, char *backend) { int fd; bool input_init = false; wl_log_set_handler_client(&wl_log_handler); ctx->timeout = configTryLong("wayland/flush_timeout", 5000); ctx->width = width; ctx->height = height; ctx->display = wl_display_connect(NULL); if (!ctx->display) { logErr("Could not connect to display"); return false; } ctx->registry = wl_display_get_registry(ctx->display); wl_registry_add_listener(ctx->registry, ®istry_listener, ctx); wl_display_dispatch(ctx->display); wl_display_roundtrip(ctx->display); /* figure out which compositor we are using */ fd = wl_display_get_fd(ctx->display); ctx->comp_name = osGetPeerProcName(fd); logInfo("Compositor seems to be %s", ctx->comp_name); if (backend) { if (!strcmp(backend, "wlr")) { input_init = wlInputInitWlr(ctx); } else if (!strcmp(backend, "kde")) { input_init = wlInputInitKde(ctx); } else if (!strcmp(backend, "uinput")) { input_init = wlInputInitUinput(ctx); } if (!input_init) { logErr("Input backend %s not supported", backend); return false; } } else { /* try them all */ if (wlInputInitWlr(ctx)) { logInfo("Using wlroots protocols for virtual input"); } else if (wlInputInitKde(ctx)) { logInfo("Using kde protocols for virtual input"); } else if (wlInputInitUinput(ctx)) { logInfo("Using uinput for virtual input"); } else { logErr("Virtual input not supported by compositor"); return false; } } /* if these have been used, they'll be -1 in the wlContext and saved * in the input state structure; otherwise, they should be closed */ if (ctx->uinput_fd[0] != -1) close(ctx->uinput_fd[0]); if (ctx->uinput_fd[1] != -1) close(ctx->uinput_fd[1]); if(wlKeySetConfigLayout(ctx)) { logErr("Could not configure virtual keyboard"); return false; } /* initiailize idle inhibition */ if (configTryBool("idle-inhibit/enable", true)) { if (wlIdleInitKde(ctx)) { logInfo("Using KDE idle inhibition protocol"); } else if (wlIdleInitGnome(ctx)) { logInfo("Using GNOME idle inhibition through gnome-session-inhibit"); } else { logInfo("No idle inhibition support"); } } else { logInfo("Idle inhibition explicitly disabled"); } /* set FD_CLOEXEC */ int flags = fcntl(fd, F_GETFD); flags |= FD_CLOEXEC; fcntl(fd, F_SETFD, flags); return true; } void wlResUpdate(struct wlContext *ctx, int width, int height) { ctx->width = width; ctx->height = height; } int wlPrepareFd(struct wlContext *ctx) { int fd; fd = wl_display_get_fd(ctx->display); // while (wl_display_prepare_read(display) != 0) { // wl_display_dispatch(display); // } // wl_display_flush(display); return fd; } void wlPollProc(struct wlContext *ctx, short revents) { if (revents & POLLIN) { // wl_display_cancel_read(display); wl_display_dispatch(ctx->display); } if (revents & POLLHUP) { logErr("Lost wayland connection"); Exit(SES_ERROR_WL); } } 07070100000033000081A400000000000000000000000164751F57000001C5000000000000000000000000000000000000001E00000000waynergy-0.16+3/src/wl_idle.c#include "wayland.h" void wlIdleInhibit(struct wlContext *ctx, bool on) { logDbg("Got idle inhibit request: %s", on ? "on" : "off"); if (on) { if (!ctx->idle.inhibit_start) { logDbg("No idle inhibition support, ignoring request"); return; } ctx->idle.inhibit_start(&ctx->idle); } else { if (!ctx->idle.inhibit_stop) { logDbg("No idle inhibition support, ignoring request"); return; } ctx->idle.inhibit_stop(&ctx->idle); } } 07070100000034000081A400000000000000000000000164751F5700000563000000000000000000000000000000000000002400000000waynergy-0.16+3/src/wl_idle_gnome.c#include "wayland.h" #include <signal.h> #include <spawn.h> static void inhibit_stop(struct wlIdle *idle) { pid_t *inhibitor = idle->state; if (*inhibitor == -1) { logDbg("gnome-session-inhibit not running"); return; } logDbg("Stopping gnome-session-inhibit"); kill(*inhibitor, SIGTERM); *inhibitor = -1; } static void inhibit_start(struct wlIdle *idle) { pid_t *inhibitor = idle->state; char *argv[] = { "gnome-session-inhibit", "--inhibit", "idle", "--inhibit-only", NULL, }; if (*inhibitor != -1) { inhibit_stop(idle); } logDbg("Starting gnome-session-inhibit"); if (posix_spawnp(inhibitor, argv[0], NULL, NULL, argv, environ)) { *inhibitor = -1; logPErr("Could not spawn gnome-session-inhibit"); } } bool wlIdleInitGnome(struct wlContext *ctx) { if (strcmp(ctx->comp_name, "gnome-shell")) { logDbg("gnome inhibitor only works with 'gnome-shell', we have '%s'", ctx->comp_name); return false; } pid_t *inhibitor = xmalloc(sizeof(*inhibitor)); *inhibitor = -1; ctx->idle.wl_ctx = ctx; ctx->idle.state = inhibitor; ctx->idle.inhibit_start = inhibit_start; ctx->idle.inhibit_stop = inhibit_stop; return true; } 07070100000035000081A400000000000000000000000164751F5700000B3D000000000000000000000000000000000000002200000000waynergy-0.16+3/src/wl_idle_kde.c#include "wayland.h" struct kde_state { struct org_kde_kwin_idle_timeout_listener listener; struct org_kde_kwin_idle_timeout *timeout; xkb_keycode_t key; int key_raw; long idle_time; }; static void on_idle_mouse(void *data, struct org_kde_kwin_idle_timeout *timeout) { struct wlIdle *idle = data; logDbg("Got idle event, responding with zero mouse move"); wlMouseRelativeMotion(idle->wl_ctx, 0, 0); } static void on_idle_key(void *data, struct org_kde_kwin_idle_timeout *timeout) { struct wlIdle *idle = data; struct kde_state *kde = idle->state; //Second try at this -- press a key we do not care about logDbg("Got idle event, responding with keypress"); if (kde->key_raw != -1) { wlKeyRaw(idle->wl_ctx, kde->key_raw, true); wlKeyRaw(idle->wl_ctx, kde->key_raw, false); } else { wlKey(idle->wl_ctx, kde->key, 0, true); wlKey(idle->wl_ctx, kde->key, 0, false); } } static void on_resumed(void *data, struct org_kde_kwin_idle_timeout *timeout) { logDbg("Got resume event"); } static void inhibit_start(struct wlIdle *idle) { struct kde_state *kde = idle->state; kde->timeout = org_kde_kwin_idle_get_idle_timeout(idle->wl_ctx->idle_manager, idle->wl_ctx->seat, kde->idle_time * 1000); if (!kde->timeout) { logErr("Could not get idle timeout"); return; } org_kde_kwin_idle_timeout_add_listener(kde->timeout, &kde->listener, idle); wlDisplayFlush(idle->wl_ctx); } static void inhibit_stop(struct wlIdle *idle) { struct kde_state *kde = idle->state; if (!kde->timeout) { logDbg("Idle already not inhibited"); return; } org_kde_kwin_idle_timeout_release(kde->timeout); wlDisplayFlush(idle->wl_ctx); kde->timeout = NULL; } bool wlIdleInitKde(struct wlContext *ctx) { char *idle_method; char *idle_keyname; if (!ctx->idle_manager) { logWarn("KDE idle inhibit selected, but no idle manager support"); return false; } struct kde_state *kde = xcalloc(1, sizeof(*kde)); kde->listener.resumed = on_resumed; kde->idle_time = configTryLong("idle-inhibit/interval", 30); idle_method = configTryString("idle-inhibit/method", "mouse"); if (!strcmp(idle_method, "mouse")) { kde->listener.idle = on_idle_mouse; } else if (!strcmp(idle_method, "key")) { kde->listener.idle = on_idle_key; /* first try a raw keycode for idle, because in case * of uinput xkb map might be rather useless */ kde->key_raw = configTryLong("idle-inhibit/keycode", -1); idle_keyname = configTryString("idle-inhibit/keyname", "HYPR"); kde->key = xkb_keymap_key_by_name(ctx->input.xkb_map, idle_keyname); free(idle_keyname); } else { logErr("Unknown idle inhibition method %s, initialization failed", idle_method); free(idle_method); free(kde); return false; } free(idle_method); ctx->idle.wl_ctx = ctx; ctx->idle.state = kde; ctx->idle.inhibit_start = inhibit_start; ctx->idle.inhibit_stop = inhibit_stop; return true; } 07070100000036000081A400000000000000000000000164751F5700002388000000000000000000000000000000000000001F00000000waynergy-0.16+3/src/wl_input.c#include "wayland.h" #include <assert.h> #include <stdbool.h> #include "log.h" #include "fdio_full.h" #include <xkbcommon/xkbcommon.h> /* handle button maps */ void wlLoadButtonMap(struct wlContext *ctx) { int i; char *key; int default_map[] = { 0, 0x110, /*BTN_LEFT*/ 0x112, /*BTN_MIDDLE*/ 0x111, /*BTN_RIGHT*/ 0x113, /*BTN_SIDE*/ 0x114, /*BTN_EXTRA*/ 0x113, /*BTN_SIDE*/ 0x114, /*BTN_EXTRA*/ }; static_assert(sizeof(default_map)/sizeof(*default_map) == WL_INPUT_BUTTON_COUNT, "button map size mismatch"); for (i = 0; i < WL_INPUT_BUTTON_COUNT; ++i) { xasprintf(&key, "button-map/%d", i); ctx->input.button_map[i] = configTryLong(key, default_map[i]); logDbg("Set button mapping: %d -> %d", i, ctx->input.button_map[i]); free(key); } }; /* Code to track keyboard state for modifier masks * because the synergy protocol is less than ideal at sending us modifiers */ static bool local_mod_init(struct wlContext *wl_ctx, char *keymap_str) { wl_ctx->input.xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!wl_ctx->input.xkb_ctx) { return false; } wl_ctx->input.xkb_map = xkb_keymap_new_from_string(wl_ctx->input.xkb_ctx, keymap_str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!wl_ctx->input.xkb_map) { xkb_context_unref(wl_ctx->input.xkb_ctx); return false; } wl_ctx->input.xkb_state = xkb_state_new(wl_ctx->input.xkb_map); if (!wl_ctx->input.xkb_state) { xkb_map_unref(wl_ctx->input.xkb_map); xkb_context_unref(wl_ctx->input.xkb_ctx); return false; } return true; } /* and code to handle raw mapping of keys */ static void load_raw_keymap(struct wlContext *ctx) { bool offset_on_explicit; char **key, **val, *endstr; int i, count, offset, lkey, rkey; key = NULL; val = NULL; if (ctx->input.raw_keymap) { free(ctx->input.raw_keymap); } /* start with the xkb maximum */ ctx->input.key_count = xkb_keymap_max_keycode(ctx->input.xkb_map) + 1; logDbg("max key: %zu", ctx->input.key_count); if ((count = configReadFullSection("raw-keymap", &key, &val)) != -1) { /* slightly inefficient approach, but it will actually work * First pass -- just find the *real* maximum raw keycode */ for (i = 0; i < count; ++i) { errno = 0; lkey = strtol(key[i], &endstr, 0); if (errno || endstr == key[i]) continue; if (lkey >= ctx->input.key_count) { ctx->input.key_count = lkey + 1; logDbg("max key update: %zu", ctx->input.key_count); } } } /* initialize everything */ ctx->input.raw_keymap = xcalloc(ctx->input.key_count, sizeof(*ctx->input.raw_keymap)); offset = configTryLong("raw-keymap/offset", 0); offset += configTryLong("xkb_key_offset", 0); logDbg("Initial raw key offset: %d", offset); for (i = 0; i < ctx->input.key_count; ++i) { ctx->input.raw_keymap[i] = i + offset; } /* and second pass -- store any actually mappings, apply offset */ offset_on_explicit = configTryBool("raw-keymap/offset_on_explicit", true); for (i = 0; i < count; ++i) { errno = 0; lkey = strtol(key[i], &endstr, 0); if (errno || endstr == key[i]) continue; errno = 0; rkey = strtol(val[i], &endstr, 0); if (errno || endstr == val[i]) continue; ctx->input.raw_keymap[lkey] = rkey + (offset_on_explicit ? offset : 0); logDbg("set raw key map: %d = %d", lkey, ctx->input.raw_keymap[lkey]); if (rkey >= ctx->input.key_press_state_len) { ctx->input.key_press_state_len = rkey + 1; logDbg("Set maximum raw keycode to %d", rkey + 1); } } strfreev(key); strfreev(val); } static void load_id_keymap(struct wlContext *ctx) { char **key, **val, *endstr; int i, count,lkey, rkey; key = NULL; val = NULL; if (ctx->input.id_keymap) { free(ctx->input.id_keymap); } /* start with the known synergy maximum */ ctx->input.id_count = 0xF000; logDbg("max key: %zu", ctx->input.id_count); if ((count = configReadFullSection("id-keymap", &key, &val)) != -1) { /* slightly inefficient approach, but it will actually work * First pass -- just find the *real* maximum id keycode */ for (i = 0; i < count; ++i) { errno = 0; lkey = strtol(key[i], &endstr, 0); if (errno || endstr == key[i]) continue; if (lkey >= ctx->input.id_count) { ctx->input.id_count = lkey + 1; logDbg("max id update: %zu", ctx->input.id_count); } } } /* initialize everything */ ctx->input.id_keymap = xcalloc(ctx->input.id_count, sizeof(*ctx->input.id_keymap)); /* and set everything as invalid initially, to trigger raw key map */ if (ctx->input.id_keymap_valid) { free(ctx->input.id_keymap_valid); } ctx->input.id_keymap_valid = xcalloc(ctx->input.id_count, sizeof(*ctx->input.id_keymap_valid)); /* and second pass -- store any actually mappings, set valid */ for (i = 0; i < count; ++i) { errno = 0; lkey = strtol(key[i], &endstr, 0); if (errno || endstr == key[i]) continue; errno = 0; rkey = strtol(val[i], &endstr, 0); if (errno || endstr == val[i]) continue; ctx->input.id_keymap[lkey] = rkey; ctx->input.id_keymap_valid[lkey] = true; logDbg("set id key map: %d = %d", lkey, ctx->input.id_keymap[lkey]); if (rkey >= ctx->input.key_press_state_len) { ctx->input.key_press_state_len = rkey + 1; logDbg("Set maximum raw keycode to %d", rkey + 1); } } strfreev(key); strfreev(val); } int wlKeySetConfigLayout(struct wlContext *ctx) { int ret = 0; /* ensure that we've given everything a chance to give us a proper default */ if (!ctx->kb_map) { wl_display_dispatch(ctx->display); wl_display_roundtrip(ctx->display); } char *default_map = ctx->kb_map; logDbg("Will default to map %s", default_map); char *keymap_str = configTryStringFull("xkb_keymap", default_map); local_mod_init(ctx, keymap_str); ret = !ctx->input.key_map(&ctx->input, keymap_str); ctx->input.key_press_state_len = 0; load_raw_keymap(ctx); load_id_keymap(ctx); ctx->input.key_press_state = xcalloc(ctx->input.key_press_state_len, sizeof(*ctx->input.key_press_state)); free(keymap_str); return ret; } void wlKeyRaw(struct wlContext *ctx, int key, int state) { size_t i; /* keep track of raw keystate size */ if (key >= ctx->input.key_press_state_len) { logDbg("Resizing key press state array from %zu to %zu", ctx->input.key_press_state_len, key + 1); ctx->input.key_press_state = xreallocarray (ctx->input.key_press_state, key + 1, sizeof(*ctx->input.key_press_state)); for (i = ctx->input.key_press_state_len; i < (key + 1); ++i) { ctx->input.key_press_state[i] = 0; } ctx->input.key_press_state_len = key + 1; } if (!ctx->input.key_press_state[key] && !state) { logDbg("Superfluous release of raw key %d", key); return; } if (key > xkb_keymap_max_keycode(ctx->input.xkb_map)) { logDbg("keycode greater than xkb maximum, mod not tracked"); } else { xkb_state_update_key(ctx->input.xkb_state, key, state); xkb_mod_mask_t depressed = xkb_state_serialize_mods(ctx->input.xkb_state, XKB_STATE_MODS_DEPRESSED); xkb_mod_mask_t latched = xkb_state_serialize_mods(ctx->input.xkb_state, XKB_STATE_MODS_LATCHED); xkb_mod_mask_t locked = xkb_state_serialize_mods(ctx->input.xkb_state, XKB_STATE_MODS_LOCKED); xkb_layout_index_t group = xkb_state_serialize_layout(ctx->input.xkb_state, XKB_STATE_LAYOUT_EFFECTIVE); logDbg("Modifiers: depressed: %x latched: %x locked: %x group: %x", depressed, latched, locked, group); } logDbg("Keycode: %d, state %d", key, state); ctx->input.key_press_state[key] += state ? 1 : -1; ctx->input.key(&ctx->input, key, state); } void wlKey(struct wlContext *ctx, int key, int id, int state) { int oldkey = key; if ((id < ctx->input.id_count) && ctx->input.id_keymap_valid[id]) { key = ctx->input.id_keymap[id]; logDbg("Key %d remapped to %d by id %d", oldkey, key, id); } else { if (key >= ctx->input.key_count) { logWarn("Key %d outside configured keymap, dropping", key); return; } key = ctx->input.raw_keymap[key]; if (key != oldkey) { logDbg("Key %d remapped to %d", oldkey, key); } } if (key == -1) { logDbg("Dropping key mapped to -1"); return; } wlKeyRaw(ctx, key, state); } void wlKeyReleaseAll(struct wlContext *ctx) { size_t i; for (i = 0; i < ctx->input.key_press_state_len; ++i) { while (ctx->input.key_press_state[i]) { logDbg("Release all: key %zd, pressed %d times", i, ctx->input.key_press_state[i]); wlKeyRaw(ctx, i, 0); } } } void wlMouseRelativeMotion(struct wlContext *ctx, int dx, int dy) { ctx->input.mouse_rel_motion(&ctx->input, dx, dy); } void wlMouseMotion(struct wlContext *ctx, int x, int y) { ctx->input.mouse_motion(&ctx->input, x, y); } void wlMouseButton(struct wlContext *ctx, int button, int state) { if (button >= WL_INPUT_BUTTON_COUNT) { logWarn("Mouse button %d exceeds maximum %d, dropping", button, WL_INPUT_BUTTON_COUNT); return; } logDbg("Mouse button: %d (mapped to %d), state: %d", button, ctx->input.button_map[button], state); ctx->input.mouse_button(&ctx->input, ctx->input.button_map[button], state); } void wlMouseWheel(struct wlContext *ctx, signed short dx, signed short dy) { ctx->input.mouse_wheel(&ctx->input, dx, dy); } 07070100000037000081A400000000000000000000000164751F57000008FB000000000000000000000000000000000000002300000000waynergy-0.16+3/src/wl_input_kde.c#include "wayland.h" static bool key_map(struct wlInput *input, char *keymap_str) { /* XXX: this is blatantly inadequate */ logWarn("KDE does not support xkb keymaps -- use raw-keymap instead"); return true; } static void key(struct wlInput *input, int key, int state) { struct org_kde_kwin_fake_input *fake = input->state; org_kde_kwin_fake_input_keyboard_key(fake, key - 8, state); wlDisplayFlush(input->wl_ctx); } static void mouse_rel_motion(struct wlInput *input, int dx, int dy) { struct org_kde_kwin_fake_input *fake = input->state; org_kde_kwin_fake_input_pointer_motion(fake, wl_fixed_from_int(dx), wl_fixed_from_int(dy)); wlDisplayFlush(input->wl_ctx); } static void mouse_motion(struct wlInput *input, int x, int y) { struct org_kde_kwin_fake_input *fake = input->state; org_kde_kwin_fake_input_pointer_motion_absolute(fake, wl_fixed_from_int(x), wl_fixed_from_int(y)); wlDisplayFlush(input->wl_ctx); } static void mouse_button(struct wlInput *input, int button, int state) { struct org_kde_kwin_fake_input *fake = input->state; org_kde_kwin_fake_input_button(fake, button, state); wlDisplayFlush(input->wl_ctx); } static void mouse_wheel(struct wlInput *input, signed short dx, signed short dy) { struct org_kde_kwin_fake_input *fake = input->state; if (dx < 0) { org_kde_kwin_fake_input_axis(fake, 1, wl_fixed_from_int(15)); }else if (dx > 0) { org_kde_kwin_fake_input_axis(fake, 1, wl_fixed_from_int(-15)); } if (dy < 0) { org_kde_kwin_fake_input_axis(fake, 0, wl_fixed_from_int(15)); } else if (dy > 0) { org_kde_kwin_fake_input_axis(fake, 0, wl_fixed_from_int(-15)); } wlDisplayFlush(input->wl_ctx); } bool wlInputInitKde(struct wlContext *ctx) { logDbg("Trying KDE fake input protocol for input"); if (!(ctx->fake_input)) { logDbg("Fake input not supported"); return false; } org_kde_kwin_fake_input_authenticate(ctx->fake_input, "waynergy", "control keyboard and mouse with Synergy/Barrier server"); ctx->input = (struct wlInput) { .state = ctx->fake_input, .wl_ctx = ctx, .mouse_motion = mouse_motion, .mouse_rel_motion = mouse_rel_motion, .mouse_button = mouse_button, .mouse_wheel = mouse_wheel, .key = key, .key_map = key_map, }; wlLoadButtonMap(ctx); logInfo("Using KDE fake input protocol"); return true; } 07070100000038000081A400000000000000000000000164751F57000014C9000000000000000000000000000000000000002600000000waynergy-0.16+3/src/wl_input_uinput.c/* uinput-based input handling as a last resort */ #include "wayland.h" #include "log.h" #include "fdio_full.h" #if defined(__linux__) #include <linux/uinput.h> #elif defined(__FreeBSD__) #include <dev/evdev/uinput.h> #endif #if !defined(UINPUT_VERSION) || (UINPUT_VERSION < 5) bool wlInputInitUinput(struct wlContext *ctx) { logDbg("uinput unavailable or too old on this platform"); return false; } #else struct state_uinput { int key_fd; int mouse_fd; }; #define UINPUT_KEY_MAX 256 static void emit(int fd, int type, int code, int val) { struct input_event ie = { .type = type, .code = code, .value = val, }; if (!write_full(fd, &ie, sizeof(ie), 0)) { logPErr("could not send uinput event"); } } static void mouse_rel_motion(struct wlInput *input, int dx, int dy) { struct state_uinput *ui = input->state; emit(ui->mouse_fd, EV_REL, REL_X, dx); emit(ui->mouse_fd, EV_REL, REL_Y, dy); emit(ui->mouse_fd, EV_SYN, SYN_REPORT, 0); } static void mouse_motion(struct wlInput *input, int x, int y) { struct state_uinput *ui = input->state; emit(ui->mouse_fd, EV_ABS, ABS_X, x); emit(ui->mouse_fd, EV_ABS, ABS_Y, y); emit(ui->mouse_fd, EV_SYN, SYN_REPORT, 0); } static void mouse_button(struct wlInput *input, int button, int state) { struct state_uinput *ui = input->state; emit(ui->mouse_fd, EV_KEY, button, state); emit(ui->mouse_fd, EV_SYN, SYN_REPORT, 0); } static void mouse_wheel(struct wlInput *input, signed short dx, signed short dy) { struct state_uinput *ui = input->state; if (dx < 0) { emit(ui->mouse_fd, EV_REL, REL_HWHEEL, -1); } else if (dx > 0) { emit(ui->mouse_fd, EV_REL, REL_HWHEEL, 1); } if (dy < 0) { emit(ui->mouse_fd, EV_REL, REL_WHEEL, -1); } else if (dy > 0) { emit(ui->mouse_fd, EV_REL, REL_WHEEL, 1); } emit(ui->mouse_fd, EV_SYN, SYN_REPORT, 0); } static void key(struct wlInput *input, int code, int state) { struct state_uinput *ui = input->state; code -= 8; if (code > UINPUT_KEY_MAX) { logErr("Keycode %d is unsupported by uinput (max %d), dropping", code, UINPUT_KEY_MAX); return; } emit(ui->key_fd, EV_KEY, code, state); emit(ui->key_fd, EV_SYN, SYN_REPORT, 0); } static bool key_map(struct wlInput *input, char *map) { logWarn("uinput does not support xkb keymaps -- use raw-keymap instead"); return true; } #define TRY_IOCTL(fd, req, ...) \ do { \ if (ioctl(fd, req, __VA_ARGS__) == -1) { \ logPDbg("ioctl " #req " failed"); \ return false; \ } \ } while (0) #define TRY_IOCTL0(fd, req) \ do { \ if (ioctl(fd, req) == -1) { \ logPDbg("ioctl " #req " failed"); \ return false; \ } \ } while (0) static bool init_key(struct state_uinput *ui) { int i; struct uinput_setup usetup = { .id = { .bustype = BUS_VIRTUAL, }, .name = "waynergy keyboard", }; TRY_IOCTL(ui->key_fd, UI_SET_EVBIT, EV_SYN); TRY_IOCTL(ui->key_fd, UI_SET_EVBIT, EV_KEY); for (i = 0; i <= UINPUT_KEY_MAX; ++i) { TRY_IOCTL(ui->key_fd, UI_SET_KEYBIT, i); } TRY_IOCTL(ui->key_fd, UI_DEV_SETUP, &usetup); TRY_IOCTL0(ui->key_fd, UI_DEV_CREATE); return true; } static bool init_mouse(struct wlContext *ctx, struct state_uinput *ui, int max_x, int max_y) { int i; struct uinput_setup usetup = { .id = { .bustype = BUS_VIRTUAL, }, .name = "waynergy mouse", }; struct uinput_abs_setup x = { .code = ABS_X, .absinfo = { .maximum = max_x, }, }; struct uinput_abs_setup y = { .code = ABS_Y, .absinfo = { .maximum = max_y, }, }; TRY_IOCTL(ui->mouse_fd, UI_SET_EVBIT, EV_SYN); TRY_IOCTL(ui->mouse_fd, UI_SET_EVBIT, EV_KEY); for (i = 0; i < WL_INPUT_BUTTON_COUNT; ++i) { TRY_IOCTL(ui->mouse_fd, UI_SET_KEYBIT, ctx->input.button_map[i]); } TRY_IOCTL(ui->mouse_fd, UI_SET_EVBIT, EV_REL); TRY_IOCTL(ui->mouse_fd, UI_SET_RELBIT, REL_X); TRY_IOCTL(ui->mouse_fd, UI_SET_RELBIT, REL_Y); TRY_IOCTL(ui->mouse_fd, UI_SET_RELBIT, REL_WHEEL); TRY_IOCTL(ui->mouse_fd, UI_SET_RELBIT, REL_HWHEEL); TRY_IOCTL(ui->mouse_fd, UI_SET_EVBIT, EV_ABS); TRY_IOCTL(ui->mouse_fd, UI_SET_ABSBIT, ABS_X); TRY_IOCTL(ui->mouse_fd, UI_SET_ABSBIT, ABS_Y); TRY_IOCTL(ui->mouse_fd, UI_DEV_SETUP, &usetup); TRY_IOCTL(ui->mouse_fd, UI_ABS_SETUP, &x); TRY_IOCTL(ui->mouse_fd, UI_ABS_SETUP, &y); TRY_IOCTL0(ui->mouse_fd, UI_DEV_CREATE); return true; } bool wlInputInitUinput(struct wlContext *ctx) { struct state_uinput *ui; if (ctx->uinput_fd[0] == -1 || ctx->uinput_fd[1] == -1) { logDbg("Invalid uinput fds"); return false; } ui = xmalloc(sizeof(*ui)); ui->key_fd = ctx->uinput_fd[0]; ui->mouse_fd = ctx->uinput_fd[1]; /* we've consumed these */ ctx->uinput_fd[0] = -1; ctx->uinput_fd[1] = -1; /* because we need to know the button map ahead of time, we need * to initialize this first */ ctx->input = (struct wlInput) { .state = ui, .wl_ctx = ctx, .mouse_motion = mouse_motion, .mouse_rel_motion = mouse_rel_motion, .mouse_button = mouse_button, .mouse_wheel = mouse_wheel, .key = key, .key_map = key_map, }; wlLoadButtonMap(ctx); if (!init_key(ui)) goto error; if (!init_mouse(ctx, ui, ctx->width, ctx->height)) goto error; logInfo("Using uinput"); return true; error: if (ui->key_fd != -1) close(ui->key_fd); if (ui->mouse_fd != -1) close(ui->mouse_fd); free(ui); return false; } #endif /* !defined(__linux__) */ 07070100000039000081A400000000000000000000000164751F5700001148000000000000000000000000000000000000002300000000waynergy-0.16+3/src/wl_input_wlr.c#include "wayland.h" #include <stdbool.h> #include "log.h" #include "fdio_full.h" #include "config.h" #include <xkbcommon/xkbcommon.h> #include <spawn.h> #include <ctype.h> extern char **environ; struct state_wlr { struct zwlr_virtual_pointer_v1 *pointer; int wheel_mult; struct zwp_virtual_keyboard_v1 *keyboard; }; /* create a layout file descriptor */ static bool key_map(struct wlInput *input, char *keymap_str) { logDbg("Setting virtual keymap"); struct state_wlr *wlr = input->state; int fd; if ((fd = osGetAnonFd()) == -1) { return false; } size_t keymap_size = strlen(keymap_str) + 1; if (!write_full(fd, keymap_str, keymap_size, 0)) { return false; } zwp_virtual_keyboard_v1_keymap(wlr->keyboard, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, fd, keymap_size); return true; } static void key(struct wlInput *input, int key, int state) { struct state_wlr *wlr = input->state; xkb_mod_mask_t depressed = xkb_state_serialize_mods(input->xkb_state, XKB_STATE_MODS_DEPRESSED); xkb_mod_mask_t latched = xkb_state_serialize_mods(input->xkb_state, XKB_STATE_MODS_LATCHED); xkb_mod_mask_t locked = xkb_state_serialize_mods(input->xkb_state, XKB_STATE_MODS_LOCKED); xkb_layout_index_t group = xkb_state_serialize_layout(input->xkb_state, XKB_STATE_LAYOUT_EFFECTIVE); zwp_virtual_keyboard_v1_key(wlr->keyboard, wlTS(input->wl_ctx), key - 8, state); zwp_virtual_keyboard_v1_modifiers(wlr->keyboard, depressed, latched, locked, group); wlDisplayFlush(input->wl_ctx); } static void mouse_rel_motion(struct wlInput *input, int dx, int dy) { struct state_wlr *wlr = input->state; zwlr_virtual_pointer_v1_motion(wlr->pointer, wlTS(input->wl_ctx), wl_fixed_from_int(dx), wl_fixed_from_int(dy)); zwlr_virtual_pointer_v1_frame(wlr->pointer); wlDisplayFlush(input->wl_ctx); } static void mouse_motion(struct wlInput *input, int x, int y) { struct state_wlr *wlr = input->state; zwlr_virtual_pointer_v1_motion_absolute(wlr->pointer, wlTS(input->wl_ctx), x, y, input->wl_ctx->width, input->wl_ctx->height); zwlr_virtual_pointer_v1_frame(wlr->pointer); wlDisplayFlush(input->wl_ctx); } static void mouse_button(struct wlInput *input, int button, int state) { struct state_wlr *wlr = input->state; zwlr_virtual_pointer_v1_button(wlr->pointer, wlTS(input->wl_ctx), button, state); zwlr_virtual_pointer_v1_frame(wlr->pointer); wlDisplayFlush(input->wl_ctx); } static void mouse_wheel(struct wlInput *input, signed short dx, signed short dy) { struct state_wlr *wlr = input->state; //we are a wheel, after all zwlr_virtual_pointer_v1_axis_source(wlr->pointer, 0); if (dx < 0) { zwlr_virtual_pointer_v1_axis_discrete(wlr->pointer, wlTS(input->wl_ctx), 1, wl_fixed_from_int(15), wlr->wheel_mult); }else if (dx > 0) { zwlr_virtual_pointer_v1_axis_discrete(wlr->pointer, wlTS(input->wl_ctx), 1, wl_fixed_from_int(-15), -1 * wlr->wheel_mult); } if (dy < 0) { zwlr_virtual_pointer_v1_axis_discrete(wlr->pointer, wlTS(input->wl_ctx), 0, wl_fixed_from_int(15), wlr->wheel_mult); } else if (dy > 0) { zwlr_virtual_pointer_v1_axis_discrete(wlr->pointer, wlTS(input->wl_ctx), 0, wl_fixed_from_int(-15), -1 * wlr->wheel_mult); } zwlr_virtual_pointer_v1_frame(wlr->pointer); wlDisplayFlush(input->wl_ctx); } bool wlInputInitWlr(struct wlContext *ctx) { int wheel_mult_default; struct state_wlr *wlr; if (!(ctx->pointer_manager && ctx->keyboard_manager)) { return false; } wlr = xmalloc(sizeof(*wlr)); wlr->pointer = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(ctx->pointer_manager, ctx->seat); wlr->keyboard = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(ctx->keyboard_manager, ctx->seat); /* some wlroots versions behaved weirdly with discrete inputs, * accumulating them and only issuing a client event when they've reached * 120. This seems to have been fixed in v0.16.0, so the detection * logic shouldn't be needed anymore, but we'll leave it configurable * just in case */ wheel_mult_default = 1; wlr->wheel_mult = configTryLong("wlr/wheel_mult", wheel_mult_default); logDbg("Using wheel_mult value of %d", wlr->wheel_mult); ctx->input = (struct wlInput) { .state = wlr, .wl_ctx = ctx, .mouse_rel_motion = mouse_rel_motion, .mouse_motion = mouse_motion, .mouse_button = mouse_button, .mouse_wheel = mouse_wheel, .key = key, .key_map = key_map, }; wlLoadButtonMap(ctx); logInfo("Using wlroots virtual input protocols"); return true; } 0707010000003A000041ED00000000000000000000000364751F5700000000000000000000000000000000000000000000001500000000waynergy-0.16+3/test0707010000003B000041ED00000000000000000000000264751F5700000000000000000000000000000000000000000000001C00000000waynergy-0.16+3/test/config0707010000003C000081A400000000000000000000000164751F5700000401000000000000000000000000000000000000001E00000000waynergy-0.16+3/test/config.c#include "../include/os.h" #include "../include/log.h" #include "../include/config.h" #include <sys/socket.h> #include <sys/un.h> #include <sys/wait.h> #include <unistd.h> #include <string.h> int main(int argc, char **argv) { int stat; char **lines; char *str; long l; bool b; osConfigPathOverride="./config"; logInit(LOG_DBG, NULL); if (!configInitINI()) { logErr("Could not initialize config.ini"); return 1; } if (!(str = configTryString("str", NULL))) { logErr("Could not read string"); return 1; } if (strcmp("string", str)) { logErr("String value %s != string", str); return 1; } if ((l = configTryLong("long", 0)) != 1234) { logErr("Long value %d != 1234", l); return 1; } if (!(b = configTryBool("bool", false))) { logErr("Bool value incorrect"); return 1; } if (!(lines = configReadLines("str"))) { logErr("Could not read lines for 'str'"); return 1; } if (strcmp(lines[0], "string")) { logErr("Incorrect value for line: %s", lines[0]); return 1; } return 0; } 0707010000003D000081A400000000000000000000000164751F5700000026000000000000000000000000000000000000002700000000waynergy-0.16+3/test/config/config.inistr = string long = 1234 bool = true 0707010000003E000081A400000000000000000000000164751F57000008B5000000000000000000000000000000000000001A00000000waynergy-0.16+3/test/os.c#include "../include/os.h" #include "../include/log.h" #include <sys/socket.h> #include <sys/un.h> #include <sys/wait.h> #include <unistd.h> #include <string.h> bool get_anon_fd(void) { char w[] = "This is a test of I/O"; char r[sizeof(w)] = {0}; int fd; ssize_t len; if ((fd = osGetAnonFd()) == -1) { return false; } if ((len = write(fd, w, sizeof(w))) != sizeof(w)) { logErr("Only wrote %zd bytes to anon fd", len); logPErr("write"); return false; } if (lseek(fd, 0, SEEK_SET) == -1) { logPErr("lseek"); return false; } if ((len = read(fd, r, sizeof(r) - 1)) != sizeof(r) - 1) { logErr("Only read %zd bytes from anon fd", len); logPErr("read"); return false; } if (strncmp(r, w, sizeof(w))) { logErr("R/W mismatch: wrote '%s', read back '%s'", w, r); return false; } return (osGetAnonFd() != -1); } bool get_peer_proc_name(char *orig_name) { pid_t host, child; int sock[2]; int child_stat; char *name; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) == -1) { logPErr("socketpair"); return false; } host = getpid(); child = fork(); if (child == -1) { logPErr("fork"); return false; } if (child) { /* host side */ logDbg("Child pid %d, host pid %d", child, host); close(sock[0]); if (waitpid(child, &child_stat, 0) != child) { logPErr("Could not wait on child"); return false; } switch (child_stat) { case 0: break; case 1: logPErr("Child could not get valid name"); return false; case 2: logPErr("Child name mismatch"); return false; default: logErr("Unknown exit code %d", child_stat); return false; } } else { /* child side */ close(sock[1]); if (!(name = osGetPeerProcName(sock[0]))) { exit(1); } logDbg("Got peer name: %s\n", name); if (strcmp(name, orig_name)) { exit(2); } exit(0); } return true; } int main(int argc, char **argv) { char *orig_name; /* strip out leading components */ orig_name = strrchr(argv[0], '/'); if (orig_name) { ++orig_name; } if (!(*orig_name)) { logErr("invalid process name"); return 1; } logInit(LOG_DBG, NULL); bool stat = true; stat = stat && get_anon_fd(); stat = stat && get_peer_proc_name(orig_name); return !stat; } 0707010000003F000081A400000000000000000000000164751F570000014C000000000000000000000000000000000000001C00000000waynergy-0.16+3/test/run.sh#!/bin/sh cc -D_GNU_SOURCE -DWAYNERGY_TEST -g -I../include os.c ../src/os.c ../src/log.c if ./a.out; then echo "os.c: passed" else echo "os.c: failed" fi cc -D_GNU_SOURCE -DWAYNERGY_TEST -g -I../include config.c ../src/os.c ../src/log.c ../src/config.c if ./a.out; then echo "config.c: passed" else echo "config.c: failed" fi 07070100000040000081A400000000000000000000000164751F5700000121000000000000000000000000000000000000002100000000waynergy-0.16+3/waynergy.desktop[Desktop Entry] Comment=A Barrier/Synergy client for Wayland compositors Exec=/usr/bin/waynergy Icon=system-run Name=Waynergy StartupNotify=true Terminal=true TerminalOptions=\s--noclose Type=Application X-KDE-Wayland-Interfaces=org_kde_kwin_fake_input X-Desktop-File-Install-Version=0.26 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!705 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor