text:
import postcss from 'postcss';
import { parse } from 'postcss-values-parser';

// TODO: To support Tailwind need to cover all variants of class names including device eg md:. margin, width, flex, height
// TODO: Test with example repos
// TODO: Check webComponents option works
// TODO: Check works with tailwind

module.exports = (opts = {}) => {
  opts = opts || {};
  const pf = "fgp-";
  const flexGapNotSupported = opts.flexGapNotSupported ? opts.flexGapNotSupported + " " : "";

  function getFlex(decl, obj) {
    if (decl.prop === "display" && decl.value === "flex" || decl.prop === "display" && decl.value === "inline-flex") {
      obj.hasFlex = true;
    }
  }

  function getWidth(decl, obj) {
    if (decl.prop === "width" || decl.prop === "max-width" || decl.prop === "min-width") {
      obj.hasWidth = true;
    }

    if (decl.prop === "height" || decl.prop === "max-height" || decl.prop === "min-height") {
      obj.hasHeight = true;
    }
  }

  function getGap(decl, obj) {
    if (decl.prop === "gap" || decl.prop === "column-gap" || decl.prop === "row-gap") {
      obj.hasGap = true;

      if (decl.prop === "row-gap") {
        obj.gapValues[0] = decl.value;
      }

      if (decl.prop === "column-gap") {
        obj.gapValues[1] = decl.value;
      }

      if (decl.prop === "gap") {
        obj.gapValues = postcss.list.space(decl.value);

        if (obj.gapValues.length === 1) {
          obj.gapValues.push(obj.gapValues[0]);
        }
      }
    }
  }

  function getMargin(decl, obj) {
    if (decl.prop === "margin" || decl.prop === "margin-left" || decl.prop === "margin-top") {
      var value;
      obj.hasMargin = true;

      if (decl.prop === "margin-top") {
        if (decl.value === "0") {
          value = '0px';
        }

        obj.marginValues[0] = value;
      }

      if (decl.prop === "margin") {
        obj.marginValues = postcss.list.space(decl.value);

        if (decl.prop === "margin-left") {
          if (decl.value === "0") {
            value = '0px';
          }

          obj.marginValues[3] = value;
        }

        if (obj.marginValues[0] === "0") {
          obj.marginValues[0] = '0px';
        }

        if (obj.marginValues[1] === "0") {
          obj.marginValues[1] = '0px';
        }

        switch (obj.marginValues.length) {
          case 1:
            obj.marginValues.push(obj.marginValues[0]);
          // falls through

          case 2:
            obj.marginValues.push(obj.marginValues[0]);
          // falls through

          case 3:
            obj.marginValues.push(obj.marginValues[1]);
        }
      }
    }
  }

  function getRules(decl, obj, root) {
    var fileName = root.source.input.file;
    obj.rules.orig = decl.parent;
    var selector; // These are needed to specifiy global scope for CSS modules

    var cssModule = "";
    var cssModuleEnd = "";

    if (fileName && fileName.endsWith(".module.css") && opts.flexGapNotSupported) {
      cssModule = ":global(";
      cssModuleEnd = ") ";
    } // if (obj.hasGap && obj.hasFlex) {

    selector = {
      container: `${cssModule}${flexGapNotSupported}${cssModuleEnd}${obj.rules.orig.selector}`,
      item: `${cssModule}${flexGapNotSupported}${cssModuleEnd}${obj.rules.orig.selector.split(",").map(item => `${item} > *`).join(",")}`,
      reset: `${cssModule}${flexGapNotSupported}${cssModuleEnd}${obj.rules.orig.selector.split(",").map(item => `${item} > * > *`).join(",")}`
    };

    if (opts.webComponents) {
      selector.item = `${cssModule}${flexGapNotSupported}${cssModuleEnd}${obj.rules.orig.selector.split(",").map(item => `${item} > *`).join(",")},
${cssModule}${flexGapNotSupported}${cssModuleEnd}${obj.rules.orig.selector.split(",").map(item => `${item} > ::slotted(*)`).join(",")}`;
    } // }
    // if ((opts.tailwindCSS && /^.gap(?=\b|[0-9])/gmi.test(obj.rules.orig.selector) && !obj.hasFlex) || (obj.hasWidth || obj.hasHeight) || (opts.tailwindCSS && /^.-?m(y-[0-9]|x-[0-9]|-px|-[0-9].?[0-9]?)/gmi.test(obj.rules.orig.selector) && !obj.hasFlex)) {
    // 	selector = {
    // 		container: `${cssModule}${flexGapNotSupported}${cssModuleEnd}.flex${obj.rules.orig.selector}, ${cssModule}${flexGapNotSupported}${cssModuleEnd}.inline-flex${obj.rules.orig.selector}`,
    // 		item: `${cssModule}${flexGapNotSupported}${cssModuleEnd}.flex${obj.rules.orig.selector} > *, ${cssModule}${flexGapNotSupported}${cssModuleEnd}.inline-flex${obj.rules.orig.selector} > *`,
    // 		reset: `${cssModule}${flexGapNotSupported}${cssModuleEnd}.flex${obj.rules.orig.selector} > * > *, ${cssModule}${flexGapNotSupported}${cssModuleEnd}.inline-flex${obj.rules.orig.selector} > * > *`,
    // 		slotted: `${cssModule}${flexGapNotSupported}${cssModuleEnd}.flex${obj.rules.orig.selector} > ::slotted(*), ${cssModule}${flexGapNotSupported}${cssModuleEnd}.inline-flex${obj.rules.orig.selector} > ::slotted(*)`
    // 	};
    // }

    obj.rules.container = postcss.rule({
      selector: selector.container
    });
    obj.rules.item = postcss.rule({
      selector: selector.item
    });
    obj.rules.reset = postcss.rule({
      selector: selector.reset
    });
    obj.rules.container.prepend(postcss.comment({
      text: 'added by fgp'
    }));
    obj.rules.item.prepend(postcss.comment({
      text: 'added by fgp'
    }));
    obj.rules.reset.prepend(postcss.comment({
      text: 'added by fgp'
    }));
  }

  function addRootSelector(root, rule) {
    var fileName = root.source.input.file; // This avoids adding :root selector to module files used by Next.js

    if (!(fileName && (fileName.endsWith(".module.css") || fileName.endsWith(".module.scss") || fileName.endsWith(".module.sass") || fileName.endsWith(".module.pcss")))) {
      const rootRule = postcss.rule({
        selector: ":root"
      });
      rule.before(rootRule);
      rootRule.prepend(postcss.comment({
        text: 'added by fgp'
      }));
      rootRule.append(`--has-fgp: initial;
				--element-has-fgp: initial;
				--parent-has-fgp: initial;`);
      rootRule.walk(i => {
        i.raws.before = "\n\t";
      });
    }
  }

  function addWidth(rule, obj) {
    if (obj.hasWidth || obj.hasHeight) {
      rule.walkDecls(decl => {
        var value = parse(decl.value).nodes[0];
        let prop = decl.prop;

        if (decl.value === 0) {
          decl.value = "0px";
        }

        let {
          orig,
          container
        } = obj.rules;
        orig.before(container);
        let axis = prop === "width" ? "column" : "row";

        if (decl.prop === "width" || decl.prop === "height" || decl.prop === "max-height" || decl.prop === "min-height" || decl.prop === "max-width" || decl.prop === "min-width") {
          // Percentages
          if (value.unit === "%") {
            container.append(`--${pf}${prop}: var(--element-has-fgp) calc(${decl.value} + var(--${pf}gap-${axis}, 0%));`); // var precentageToDecimal = value.value / 100
            // Sort of a guess for forumla when width is a percentage
            // container.append(
            // 	`--${pf}${prop}: var(--element-has-fgp) calc((${decl.value} - ${precentageToDecimal}%) + (1.0${value.value} * var(--${pf}gap-${axis}, 0%)));`
            // );
            // container.append(
            // 	`--${pf}${prop}-percentages-decimal: ${value.number / 100} !important;`
            // );
            // reset.append(
            // 	`--${pf}${prop}-percentages-decimal: initial;`
            // );
          } // Pixels, Ems
          else {
              container.append(`--${pf}${prop}: var(--element-has-fgp) calc(${decl.value} + var(--${pf}gap-${axis}, 0%));`); // 			container.append(
              // 				`--${pf}gap-percentage-to-pixels-column: calc(-1 * ${decl.value} * var(${pf}gap_percentage-decimal-${axis})) !important;
              // ${pf}gap-percentage-to-pixels-row: calc(-1 * ${decl.value} * var(${pf}gap-percentage-decimal-${axis})) !important;`
              // 			);
              // 			reset.append(
              // 				`--${pf}gap-percentage-to-pixels-column: initial;
              // ${pf}gap-percentage-to-pixels-row: initial;`
              // 			);
            }

          decl.value = `var(--fgp-${prop}, ${decl.value})`;
          container.walk(i => {
            i.raws.before = "\n\t";
          }); // reset.walk(i => { i.raws.before = "\n\t"; });
          // decl.remove()
        }
      });
    }
  }

  function rewriteFlex(rule, obj) {
    rule.walkDecls(decl => {
      if (decl.prop === "display" && decl.value === "flex" || decl.prop === "display" && decl.value === "inline-flex") {
        obj.rules.container.append(`--has-fgp: ;`);
        obj.rules.container.append(`--element-has-fgp: ;`);
        obj.rules.item.append(`--parent-has-fgp: !important;`);
        obj.rules.item.append(`--element-has-fgp: initial;`);
        obj.rules.reset.append(`--parent-has-fgp: initial;`);
      }
    });
  }

  function rewriteMargin(rule, obj) {
    let {
      orig,
      container,
      item,
      reset
    } = obj.rules; // 1. Replace existing margin-left and margin-top

    orig.walkDecls(decl => {
      if (decl.prop === "margin-top" || decl.prop === "margin-left") {
        // don't do this is margin is auto because cannot calc with auto
        if (decl.value !== "auto") {
          var value = decl.value;

          if (value === "0") {
            value = "0px";
          }

          decl.before(`--${pf}${decl.prop}: initial;`);
          decl.before(`--orig-${decl.prop}: ${value};`);
          decl.value = `var(--${pf}${decl.prop}, var(--orig-${decl.prop}))`;
          item.append(`--orig-${decl.prop}: initial;`);
        }
      }

      if (decl.prop === "margin") {
        // TODO: Need to catch when value is auto as can't work with calc
        decl.before(`--${pf}margin-top: initial;`);
        decl.before(`--${pf}margin-left: initial;`);
        decl.before(`--orig-margin-top: ${obj.marginValues[0]};`);
        decl.before(`--orig-margin-right: ${obj.marginValues[1]};`);
        decl.before(`--orig-margin-bottom: ${obj.marginValues[2]};`);
        decl.before(`--orig-margin-left: ${obj.marginValues[3]};`);
        decl.value = `var(--${pf}margin-top, var(--orig-margin-top)) var(--orig-margin-right) var(--orig-margin-bottom) var(--${pf}margin-left, var(--orig-margin-left))`;
        item.append(`--orig-margin-top: initial;`);
        item.append(`--orig-margin-right: initial;`);
        item.append(`--orig-margin-bottom: initial;`);
        item.append(`--orig-margin-left: initial;`);
      }
    }); // 2. Add margin when gap present

    const properties = [["row", "top"], ["column", "left"]]; // Disable gap when element has display flex
    // TODO: Needs modifying to work on gap shorthand

    orig.walkDecls(decl => {
      if (decl.prop === "gap" || decl.prop === "row-gap" || decl.prop === "column-gap") {
        // don't do this is margin is auto because cannot calc with auto
        decl.before(`--${pf}${decl.prop}: var(--has-fgp, ${decl.value})`);
        decl.value = `var(--${pf}${decl.prop}, 0px)`;
      }
    });

    if (obj.hasFlex || obj.hasGap) {
      container.append(`pointer-events: var(--has-fgp) none;`);
      item.append(`pointer-events: var(--parent-has-fgp) auto;`);
    }

    properties.forEach((property, index) => {
      var axis = property[0];
      var side = property[1];
      var value = obj.gapValues[index];
      var gapNumber = axis === "row" ? 0 : 1;
      var marginNumber = axis === "row" ? 0 : 3;

      if (value === "0") {
        value = "0px";
      } // Only add if gap is not null

      if (obj.gapValues[gapNumber] !== null) {
        // Don't add margin if rule already contains margin
        if (!obj.marginValues[marginNumber] && obj.marginValues[marginNumber] !== 0) {
          orig.append(`margin-${side}: var(--${pf}margin-${side}, var(--orig-margin-${side}));`);
        }

        if (parse(value).nodes[0].unit === "%") {
          var unitlessPercentage = parse(value).nodes[0].value / 100; // formula: (parent - self) / (100 - 1 + percentageAsDecimal) * 100

          if (side !== "top") {
            container.append(`--${pf}gap-${axis}: ${value};
						--${pf}-parent-gap-as-decimal: ${unitlessPercentage};
						`);
            container.append(`--${pf}margin-${side}: calc(
						(var(--${pf}parent-gap-${axis}, 0px) - var(--${pf}gap-${axis}) / (100 - (1 + ${unitlessPercentage})) * 100)
						+ var(--${pf}orig-margin-${side}, 0px)
						) !important`);
          }
        } else {
          // formula: (parent - self)
          container.append(`--${pf}gap-${axis}: ${value};`);
          container.append(`--${pf}margin-${side}: var(--has-fgp) calc(var(--${pf}parent-gap-${axis}, 0px) / (1 + var(--${pf}-parent-gap-as-decimal, 0)) - var(--${pf}gap-${axis}) + var(--orig-margin-${side}, 0px)) !important;`);
        }

        if (parse(value).nodes[0].unit === "%") {
          if (side !== "top") {
            item.append(`--${pf}parent-gap-${axis}: ${value};
					--${pf}margin-${side}: var(--parent-has-fgp) calc(var(--${pf}gap-${axis}) / (1 + ${unitlessPercentage}) + var(--orig-margin-${side}, 0px));`);
          }
        } else {
          item.append(`--${pf}parent-gap-${axis}: ${value};
					--${pf}margin-${side}: var(--parent-has-fgp) calc(var(--${pf}gap-${axis}) + var(--orig-margin-${side}, 0px));`);
        } // Add margin to items

        item.append(`margin-${side}: var(--${pf}margin-${side});`); // // Reset fgp margins
        // reset.append(`--${pf}margin-${side}: initial`)
        // // Reset fgp parent gap

        reset.append(`--${pf}parent-gap-${axis}: initial`);
      }
    });
  }

  return {
    postcssPlugin: 'postcss-gap',

    Once(root) {
      var rootAdded = false;
      root.walkRules(rule => {
        if (rule.name !== 'import' && !rootAdded) {
          addRootSelector(root, rule);
          rootAdded = true;
        }
      });
      root.walkRules(rule => {
        // To check if rule original or added by plugin
        var origRule = true;
        rule.walkDecls(decl => {
          if (decl.prop.startsWith("--fgp") || decl.prop.startsWith("--has")) {
            origRule = false;
          }
        });
        rule.walkComments(comment => {
          if (comment.text === "added by fgp") {
            origRule = false;
          }
        });
        var shouldPolyfill = false;

        if (Array.isArray(opts.only) || opts.only === true) {
          shouldPolyfill = false;
        }

        if (origRule) {
          var obj = {
            rules: {},
            gapValues: [null, null],
            marginValues: [null, null, null, null],
            hasGap: false,
            hasFlex: false,
            hasMargin: false,
            shouldPolyfill: shouldPolyfill
          };
          rule.walkDecls(decl => {
            getRules(decl, obj, root);
            getFlex(decl, obj);
            getGap(decl, obj);
            getMargin(decl, obj);
            getWidth(decl, obj);
          });

          if (Array.isArray(opts.only) || opts.only === true) {
            if (obj.hasFlex && obj.hasGap) {
              obj.shouldPolyfill = true;
            }
          } else {
            if (obj.hasFlex || obj.hasMargin || obj.hasGap || obj.hasWidth || obj.hasHeight) {
              obj.shouldPolyfill = true;
            }
          }

          if (Array.isArray(opts.only)) {
            if (opts.only.includes(rule.selector) || opts.only.some(item => {
              if (item instanceof RegExp) {
                return item.test(rule.selector);
              }
            })) {
              obj.shouldPolyfill = true;
            }
          } // If rule contains comment /* apply fgp */ then polyfill it

          rule.walkComments(comment => {
            if (comment.text === "apply fgp") {
              comment.remove();
              obj.shouldPolyfill = true;
            }
          });

          if (obj.shouldPolyfill) {
            addWidth(rule, obj);
            rewriteFlex(rule, obj); // addMargin(rule, obj)

            rewriteMargin(rule, obj);

            if (obj.hasFlex || obj.hasGap || obj.hasMargin || !(obj.hasWidth || obj.hasHeight)) {
              if (!(obj.hasMargin && !obj.hasFlex && !obj.hasGap)) {
                obj.rules.orig.before(obj.rules.container);
                obj.rules.container.before(obj.rules.item);
                obj.rules.item.before(obj.rules.reset);
              }

              if (obj.hasFlex) {
                obj.rules.item.before(obj.rules.reset);
              }

              if (obj.hasMargin && !obj.hasFlex && !obj.hasGap) {
                obj.rules.orig.before(obj.rules.item);
              } // Clean

              obj.rules.orig.walk(i => {
                return i.raws.before = "\n\t";
              });
              obj.rules.container.walk(i => {
                i.raws.before = "\n\t";
              });
              obj.rules.item.walk(i => {
                i.raws.before = "\n\t";
              });
              obj.rules.reset.walk(i => {
                i.raws.before = "\n\t";
              });
            }
          }
        }
      });
    }

  };
};

module.exports.postcss = true;
//# sourceMappingURL=index.mjs.map


text:
// tailwind.config.js
module.exports = {
	purge: [],
	darkMode: false, // or 'media' or 'class'
	theme: {
		extend: {},
	},
	variants: {
		extend: {},
	},
	plugins: [],
}

repo_name: gavinmcfarland/flex-gap-polyfill

repo_language: CSS

mime_type: text/plain


text:
module.exports = {
	plugins: {
		'tailwindcss': {},
		'flex-gap-polyfill': {}
	}
};


text:
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.

The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.


text:
import '../styles/globals.css'
import 'tailwindcss/tailwind.css'

function MyApp({ Component, pageProps }) {
	return <Component {...pageProps} />
}

export default MyApp


text:
import Head from 'next/head'
import styles from '../styles/Home.module.css'

export default function Home() {
	return (

				<title>Create Next App</title>
				<link rel="icon" href="/favicon.ico" />

					Welcome to Next.js!

					Get started by editing{' '}
					pages/index.js

				<div className={`${styles.grid} flex gap-8 mt-12`}>

Documentation →

Find in-depth information about Next.js features and API.

Learn →

Learn about Next.js in an interactive course with quizzes!

Examples →

Discover and deploy boilerplate example Next.js projects.

Deploy →

							Instantly deploy your Next.js site to a public URL with Vercel.

					Powered by{' '}

	)
}


text:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction

export default (req, res) => {
  res.status(200).json({ name: 'John Doe' })
}


text:
html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

a {
  color: inherit;
  text-decoration: none;
}

* {
  box-sizing: border-box;
}


text:
.container {
  min-height: 100vh;
  padding: 0 0.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.main {
  padding: 5rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.footer {
  width: 100%;
  height: 100px;
  border-top: 1px solid #eaeaea;
  display: flex;
  justify-content: center;
  align-items: center;
}

.footer img {
  margin-left: 0.5rem;
}

.footer a {
  display: flex;
  justify-content: center;
  align-items: center;
}

.title a {
  color: #0070f3;
  text-decoration: none;
}

.title a:hover,
.title a:focus,
.title a:active {
  text-decoration: underline;
}

.title {
  margin: 0;
  line-height: 1.15;
  font-size: 4rem;
}

.title,
.description {
  text-align: center;
}

.description {
  line-height: 1.5;
  font-size: 1.5rem;
}

.code {
  background: #fafafa;
  border-radius: 5px;
  padding: 0.75rem;
  font-size: 1.1rem;
  font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
    Bitstream Vera Sans Mono, Courier New, monospace;
}

.grid {
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  max-width: 800px;
}

.card {
  /* margin: 1rem; */
  flex-basis: 45%;
  padding: 1.5rem;
  text-align: left;
  color: inherit;
  text-decoration: none;
  border: 1px solid #eaeaea;
  border-radius: 10px;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.card:hover,
.card:focus,
.card:active {
  color: #0070f3;
  border-color: #0070f3;
}

.card h3 {
  margin: 0 0 1rem 0;
  font-size: 1.5rem;
}

.card p {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.5;
}

.logo {
  height: 1em;
}

@media (max-width: 600px) {
  .grid {
    width: 100%;
    flex-direction: column;
  }
}


type: opencollective

deprecated: core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.

autoprefixer: bin/autoprefixer

acorn: bin/acorn

type: github

ansi-html: bin/ansi-html

type: tidelift

type: patreon

type: consulting

cssesc: bin/cssesc

semver: bin/semver

he: bin/he

jsesc: bin/jsesc

miller-rabin: bin/miller-rabin

mustache: bin/mustache

deprecated: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.

deprecated: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.


text:
export default {
  // Target: https://go.nuxtjs.dev/config-target
  target: 'static',

  // Global page headers: https://go.nuxtjs.dev/config-head
  head: {
    title: 'nuxtjs-2',
    htmlAttrs: {
      lang: 'en'
    },
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },

  // Global CSS: https://go.nuxtjs.dev/config-css
  css: [
  ],

  // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
  plugins: [
  ],

  // Auto import components: https://go.nuxtjs.dev/config-components
  components: true,

  // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
  buildModules: [
    '@nuxt/postcss8'
  ],

  // Modules: https://go.nuxtjs.dev/config-modules
  modules: [
  ],

  // Build Configuration: https://go.nuxtjs.dev/config-build
  build: {
    postcss: {
      plugins: {
        'flex-gap-polyfill': {},
      }
    }
  }
}


text:
# nuxtjs

As of 27 Apr 2020 Nuxt.js still uses PostCSS 7 and is therefore incompatible with the latest verison of `flex-gap-polyfill`.

For now a work around is the use https://github.com/nuxt/postcss8.


text:
# ASSETS

**This directory is not required, you can delete it if you don't want to use it.**

This directory contains your un-compiled assets such as LESS, SASS, or JavaScript.

More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked).


text:
<template>

      <Logo />

        nuxtjs-2

          Documentation

          GitHub

</template>


text:
# PAGES

This directory contains your Application Views and Routes.
The framework reads all the `*.vue` files inside this directory and creates the router of your application.

More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing).


text:
# PLUGINS

**This directory is not required, you can delete it if you don't want to use it.**

This directory contains Javascript plugins that you want to run before mounting the root Vue.js application.

More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins).


text:
# STATIC

**This directory is not required, you can delete it if you don't want to use it.**

This directory contains your static files.
Each file inside this directory is mapped to `/`.
Thus you'd want to delete this README.md before deploying to production.

Example: `/static/robots.txt` is mapped as `/robots.txt`.

More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static).


text:
<template>
  <svg class="NuxtLogo" width="245" height="180" viewBox="0 0 452 342" xmlns="http://www.w3.org/2000/svg">
    <path
      d="M139 330l-1-2c-2-4-2-8-1-13H29L189 31l67 121 22-16-67-121c-1-2-9-14-22-14-6 0-15 2-22 15L5 303c-1 3-8 16-2 27 4 6 10 12 24 12h136c-14 0-21-6-24-12z"
      fill="#00C58E"
    />
    <path
      d="M447 304L317 70c-2-2-9-15-22-15-6 0-15 3-22 15l-17 28v54l39-67 129 230h-49a23 23 0 0 1-2 14l-1 1c-6 11-21 12-23 12h76c3 0 17-1 24-12 3-5 5-14-2-26z"
      fill="#108775"
    />
    <path
      d="M376 330v-1l1-2c1-4 2-8 1-12l-4-12-102-178-15-27h-1l-15 27-102 178-4 12a24 24 0 0 0 2 15c4 6 10 12 24 12h190c3 0 18-1 25-12zM256 152l93 163H163l93-163z"
      fill="#2F495E"
    />
  </svg>
</template>

<style>
.NuxtLogo {
  animation: 1s appear;
  margin: auto;
}

@keyframes appear {
  0% {
    opacity: 0;
  }
}
</style>


text:
# COMPONENTS

**This directory is not required, you can delete it if you don't want to use it.**

The components directory contains your Vue.js Components.

_Nuxt.js doesn't supercharge these components._


text:
# STORE

**This directory is not required, you can delete it if you don't want to use it.**

This directory contains your Vuex Store files.
Vuex Store option is implemented in the Nuxt.js framework.

Creating a file in this directory automatically activates the option in the framework.

More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).


text:
# MIDDLEWARE

**This directory is not required, you can delete it if you don't want to use it.**

This directory contains your application middleware.
Middleware let you define custom functions that can be run before rendering either a page or a group of pages.

More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware).


text:
<template>
  <div>
    <Nuxt />
  </div>
</template>

<style>
html {
  font-family:
    'Source Sans Pro',
    -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    Roboto,
    'Helvetica Neue',
    Arial,
    sans-serif;
  font-size: 16px;
  word-spacing: 1px;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  box-sizing: border-box;
}

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
}

.button--green {
  display: inline-block;
  border-radius: 4px;
  border: 1px solid #3b8070;
  color: #3b8070;
  text-decoration: none;
  padding: 10px 30px;
}

.button--green:hover {
  color: #fff;
  background-color: #3b8070;
}

.button--grey {
  display: inline-block;
  border-radius: 4px;
  border: 1px solid #35495e;
  color: #35495e;
  text-decoration: none;
  padding: 10px 30px;
  margin-left: 15px;
}

.button--grey:hover {
  color: #fff;
  background-color: #35495e;
}
</style>


text:
# LAYOUTS

**This directory is not required, you can delete it if you don't want to use it.**

This directory contains your Application Layouts.

More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts).


text:
module.exports = {
	plugins: [
		'flex-gap-polyfill'
	]
};


text:
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.

The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.


text:
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp


text:
import Head from 'next/head'
import styles from '../styles/Home.module.css'

export default function Home() {
  return (

        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />

          Welcome to Next.js!

          Get started by editing{' '}
          pages/index.js

Documentation →

Find in-depth information about Next.js features and API.

Learn →

Learn about Next.js in an interactive course with quizzes!

Examples →

Discover and deploy boilerplate example Next.js projects.

Deploy →

              Instantly deploy your Next.js site to a public URL with Vercel.

          Powered by{' '}

  )
}


text:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction

export default (req, res) => {
  res.status(200).json({ name: 'John Doe' })
}


text:
html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

a {
  color: inherit;
  text-decoration: none;
}

* {
  box-sizing: border-box;
}


text:
.container {
  min-height: 100vh;
  padding: 0 0.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.main {
  padding: 5rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.footer {
  width: 100%;
  height: 100px;
  border-top: 1px solid #eaeaea;
  display: flex;
  justify-content: center;
  align-items: center;
}

.footer img {
  margin-left: 0.5rem;
}

.footer a {
  display: flex;
  justify-content: center;
  align-items: center;
}

.title a {
  color: #0070f3;
  text-decoration: none;
}

.title a:hover,
.title a:focus,
.title a:active {
  text-decoration: underline;
}

.title {
  margin: 0;
  line-height: 1.15;
  font-size: 4rem;
}

.title,
.description {
  text-align: center;
}

.description {
  line-height: 1.5;
  font-size: 1.5rem;
}

.code {
  background: #fafafa;
  border-radius: 5px;
  padding: 0.75rem;
  font-size: 1.1rem;
  font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
    Bitstream Vera Sans Mono, Courier New, monospace;
}

.grid {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  max-width: 800px;
  margin-top: 3rem;
	gap: 2rem;
}

.card {
  /* margin: 1rem; */
  flex-basis: 45%;
  padding: 1.5rem;
  text-align: left;
  color: inherit;
  text-decoration: none;
  border: 1px solid #eaeaea;
  border-radius: 10px;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.card:hover,
.card:focus,
.card:active {
  color: #0070f3;
  border-color: #0070f3;
}

.card h3 {
  margin: 0 0 1rem 0;
  font-size: 1.5rem;
}

.card p {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.5;
}

.logo {
  height: 1em;
}

@media (max-width: 600px) {
  .grid {
    width: 100%;
    flex-direction: column;
  }
}


text:
# Mac Media Key Forwarder [![Build Status](https://travis-ci.org/milgra/macmediakeyforwarder.svg?branch=master)](https://travis-ci.org/milgra/macmediakeyforwarder#)

Mac Media Key Forwarder for [iTunes](https://www.apple.com/itunes/) and [Spotify](http://www.spotify.com).

## For the latest version and bugfixes use quentinlesceller's fork : https://github.com/quentinlesceller/macmediakeyforwarder/releases

Forwards media keys to iTunes or Spotify directly.

You can prioritize which app you would like to control or you can go with the default behaviour which controls the running app.

The app runs in the menu bar.

Download the compiled application from my [Releases](https://github.com/milgra/macmediakeyforwarder/releases).

If you want even more control over what you want to control you should try [beardedspice](http://beardedspice.github.io).

**Issues you should know about**

The app listens on the event tap for key events. This causes problems in some rare cases, like
- when changing search engine in Safari's preferences window
- when trying to allow third-party kernel extensions

In these cases simply pause Mac Media Key Forwarder from it's menu.

**Installation & MacOS Mojave Compatibility**

The app itself is compatible with Mojave, but you need to add it as trusted application in order to make it function properly.

You can do this with these steps:
1. Go to **System Preferences** > **Security & Privacy**
2. Open the **Privacy** tab, and look for **Accessibility** in the left list
3. Turn on the checkbox for **MacMediaKeyForwarder.app** in the right list
2. Look for **Automation** in the left list
3. Turn on the checkbox for **iTunes.app** and **Spotifiy.app** under **MacMediaKeyForwarder.app** in the right list
4. Run the app again

![Security Setting](security_a.png)

![Security Setting](security_b.png)

**Other milgra utilities you might be interested in**

[mac audio keepalive](https://github.com/milgra/macaudiokeepalive)

[airpods sound quality fixer](https://github.com/milgra/airpodssoundqualityfixer)

[fat fingers keyboard for iphone](https://github.com/milgra/fatfingerskeyboard)

---

**Contributors :**
* Michael Dorner ([@michaeldorner](http://github.com/michaeldorner))
* Matt Chaput ([@mchaput](http://github.com/mchaput))
* Ben Kropf ([@ben-kropf](http://github.com/ben-kropf))
* Alejandro Iván ([@alejandroivan](http://github.com/alejandroivan))
* Sungho Lee ([@sh1217sh](http://github.com/sh1217sh))
* Björn Büschke ([@maciboy](http://github.com/maciboy))
* Sergei Solovev ([@e1ectron](http://github.com/e1ectron))
* Munkácsi Márk ([@munkacsimark](http://github.com/munkacsimark))
* Irvin Lim ([@irvinlim](https://github.com/irvinlim))
* Simon Seku ([@SimonSeku](https://github.com/SimonSeku))
* Dave Nicolson ([@dnicolson](https://github.com/dnicolson))
* teemue ([@teemue](https://github.com/teemue))
* takamu ([@takamu](https://github.com/takamu))
* Alex ([@sashoism](https://github.com/sashoism))
* Sebastiaan Pasma ([@spasma](https://github.com/spasma))
* WiktorBuczko ([@WiktorBuczko](https://github.com/WiktorBuczko))
* Andy White ([@arcwhite](https://github.com/arcwhite))
* xjbeta ([@xjbeta](https://github.com/xjbeta))
* Jules Coynel ([@jcoynel](https://github.com/jcoynel))

Thank you!!!

---

*What's new in version 3.1 :*
- Ability to hide the menu icon
- French translation

*What's new in version 3.0 :*
- Catalina compatibility

*What's new in version 2.8 :*
- Polish localization
- Fixed broken Japanese, Finnish, Dutch localization

*What's new in version 2.7 :*
- Dutch localization

*What's new in version 2.6 :*
- Enabled undocking status bar item

*What's new in version 2.5 :*
- Finnish, Japanese localization
- Modified Accessibility Instructions

*What's new in version 2.3 :*
- Korean, Danish, Russian and Hungarian localization is linked back to the project ( they got lost somewhere :( )

*What's new in version 2.2 :*
- MacOS Mojave 10.14.2 fix, showing notification pop-up if tap cannot be created

*What's new in version 2.1 :*
- app brings up permission popups if permission is not granted for Accessibility and Automation Target

What's new in version 2.0 :
- app renamed to Mac Media Key Forwarder
- Hungarian localization
- updated icon
- Open At Login state is checked every time the menu is opened so it shows an updated state
- added installation steps to readme because increased MacOS security made it more confusing
- added event-tap related issues to readme because it can cause head scratches in some special cases

What's new in version 1.9 :
- added open at login menu option
- German localization update
- Korean localization update

What's new in version 1.8 :
- added pause menu option
- added pause automatically menu option : if no music player is running macOS default behavior is used and keys are forwarded to currently active media player
- Russian localization
- German localization
- Spanish localization
- fixed headphone button issue
- added macOS Sierra compatibility if you want explicit music player control there

What's new in version 1.7 :
- fast forward/rewind is possible when iTunes is selected explicitly
- Korean localization
- rumors say that it works with TouchBar

What's new in version 1.6 :
- increased compatibility with external keyboards

What's new in version 1.5 :
- now you can explicitly prioritize iTunes or Spotify
- play button now starts up iTunes or Spotify if they are not running aaaand explicitly selected

What's new in version 1.4 :
- memory leak fixed

What's new in version 1.3 :
- previousTrack replaced with backTrack in case of iTunes for a better experience

What's new in version 1.2 :
- new icon
- source code is super tight now
- developer id signed, its a trusted app now

---

repo_name: milgra/macmediakeyforwarder

repo_language: Objective-C

mime_type: text/x-objective-c


text:
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 46;
	objects = {

/* Begin PBXBuildFile section */
		3333F8972198687500816A9F /* GBLaunchAtLogin.m in Sources */ = {isa = PBXBuildFile; fileRef = 3333F8942198687500816A9F /* GBLaunchAtLogin.m */; };
		336F744D1E07E136009BCC1B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 336F744C1E07E136009BCC1B /* AppDelegate.m */; };
		336F74501E07E136009BCC1B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 336F744F1E07E136009BCC1B /* main.m */; };
		336F74521E07E136009BCC1B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 336F74511E07E136009BCC1B /* Assets.xcassets */; };
		336F74551E07E136009BCC1B /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 336F74531E07E136009BCC1B /* MainMenu.xib */; };
		336F74651E07E4F6009BCC1B /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 336F74641E07E4F6009BCC1B /* CoreAudio.framework */; };
		33CD48BF1F866DB7000C454F /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33CD48BE1F866DB7000C454F /* ScriptingBridge.framework */; };
		33CD48CB1F867394000C454F /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 33CD48C71F8671B6000C454F /* icon.png */; };
		33CD48CC1F867408000C454F /* icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 33CD48C91F86724F000C454F /* icon@2x.png */; };
		33E2CC3E21B1361E002F8094 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33E2CC3D21B1361E002F8094 /* CoreServices.framework */; };
		A5999C4C1FF3BB89009905E1 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A5999C4E1FF3BB89009905E1 /* Localizable.strings */; };
		AA3E49F621AAA0C500EEA78B /* appicon.png in Resources */ = {isa = PBXBuildFile; fileRef = AA3E49F521AAA0C500EEA78B /* appicon.png */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
		112AF55920263497006937BF /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
		2C9625F520295D9F00F0C47E /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
		330630112313ED7500ACFBFE /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
		330630122313ED8100ACFBFE /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
		330630132313F6D400ACFBFE /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
		330630142313F6DE00ACFBFE /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
		3319A9E81E085353005DB79C /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = SOURCE_ROOT; };
		3319A9E91E085353005DB79C /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; };
		3333F8922198687500816A9F /* GBLaunchAtLogin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GBLaunchAtLogin.h; sourceTree = "<group>"; };
		3333F8932198687500816A9F /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
		3333F8942198687500816A9F /* GBLaunchAtLogin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GBLaunchAtLogin.m; sourceTree = "<group>"; };
		3333F8952198687500816A9F /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
		336F74481E07E136009BCC1B /* MacMediaKeyForwarder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MacMediaKeyForwarder.app; sourceTree = BUILT_PRODUCTS_DIR; };
		336F744B1E07E136009BCC1B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
		336F744C1E07E136009BCC1B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
		336F744F1E07E136009BCC1B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
		336F74511E07E136009BCC1B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		336F74541E07E136009BCC1B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
		336F74561E07E136009BCC1B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		336F74641E07E4F6009BCC1B /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
		337268BF1F8BA311007284E3 /* Spotify.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Spotify.h; sourceTree = "<group>"; };
		338A08AF2028840D001F1ED3 /* manualtest.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = manualtest.txt; sourceTree = "<group>"; };
		3397086D21EB6B7B009F3857 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
		3397086E21EB6B8F009F3857 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
		3397086F21EB6B9C009F3857 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = "<group>"; };
		33B952841FD6E8C5003EE4CD /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = SOURCE_ROOT; };
		33CD48BE1F866DB7000C454F /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = System/Library/Frameworks/ScriptingBridge.framework; sourceTree = SDKROOT; };
		33CD48C11F866DC6000C454F /* iTunes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTunes.h; sourceTree = "<group>"; };
		33CD48C71F8671B6000C454F /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = "<group>"; };
		33CD48C91F86724F000C454F /* icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon@2x.png"; sourceTree = "<group>"; };
		33E2CC3D21B1361E002F8094 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
		84B350D923526D6100255450 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
		A523261E2887DBBC001EA9B4 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
		A5999C4D1FF3BB89009905E1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
		A5999C4F1FF3BB98009905E1 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
		AA3E49F521AAA0C500EEA78B /* appicon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = appicon.png; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		336F74451E07E136009BCC1B /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				33E2CC3E21B1361E002F8094 /* CoreServices.framework in Frameworks */,
				33CD48BF1F866DB7000C454F /* ScriptingBridge.framework in Frameworks */,
				336F74651E07E4F6009BCC1B /* CoreAudio.framework in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		3333F8912198687500816A9F /* GBLaunchAtLogin */ = {
			isa = PBXGroup;
			children = (
				3333F8922198687500816A9F /* GBLaunchAtLogin.h */,
				3333F8932198687500816A9F /* LICENSE */,
				3333F8942198687500816A9F /* GBLaunchAtLogin.m */,
				3333F8952198687500816A9F /* README.md */,
			);
			name = GBLaunchAtLogin;
			path = MacMediaKeyForwarder/Frameworks/GBLaunchAtLogin;
			sourceTree = "<group>";
		};
		336F743F1E07E136009BCC1B = {
			isa = PBXGroup;
			children = (
				336F744A1E07E136009BCC1B /* MacMediaKeyForwarder */,
				336F74491E07E136009BCC1B /* Products */,
				336F74631E07E4F6009BCC1B /* Frameworks */,
			);
			sourceTree = "<group>";
		};
		336F74491E07E136009BCC1B /* Products */ = {
			isa = PBXGroup;
			children = (
				336F74481E07E136009BCC1B /* MacMediaKeyForwarder.app */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		336F744A1E07E136009BCC1B /* MacMediaKeyForwarder */ = {
			isa = PBXGroup;
			children = (
				33CD48C11F866DC6000C454F /* iTunes.h */,
				337268BF1F8BA311007284E3 /* Spotify.h */,
				336F744B1E07E136009BCC1B /* AppDelegate.h */,
				336F744C1E07E136009BCC1B /* AppDelegate.m */,
				336F744E1E07E136009BCC1B /* Supporting Files */,
			);
			path = MacMediaKeyForwarder;
			sourceTree = "<group>";
		};
		336F744E1E07E136009BCC1B /* Supporting Files */ = {
			isa = PBXGroup;
			children = (
				33B952841FD6E8C5003EE4CD /* .travis.yml */,
				AA3E49F521AAA0C500EEA78B /* appicon.png */,
				33CD48C71F8671B6000C454F /* icon.png */,
				33CD48C91F86724F000C454F /* icon@2x.png */,
				336F74511E07E136009BCC1B /* Assets.xcassets */,
				336F74531E07E136009BCC1B /* MainMenu.xib */,
				336F74561E07E136009BCC1B /* Info.plist */,
				3319A9E81E085353005DB79C /* LICENSE */,
				3319A9E91E085353005DB79C /* README.md */,
				338A08AF2028840D001F1ED3 /* manualtest.txt */,
				336F744F1E07E136009BCC1B /* main.m */,
				A5999C4E1FF3BB89009905E1 /* Localizable.strings */,
			);
			name = "Supporting Files";
			sourceTree = "<group>";
		};
		336F74631E07E4F6009BCC1B /* Frameworks */ = {
			isa = PBXGroup;
			children = (
				33E2CC3D21B1361E002F8094 /* CoreServices.framework */,
				3333F8912198687500816A9F /* GBLaunchAtLogin */,
				33CD48BE1F866DB7000C454F /* ScriptingBridge.framework */,
				336F74641E07E4F6009BCC1B /* CoreAudio.framework */,
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		336F74471E07E136009BCC1B /* MacMediaKeyForwarder */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 336F74591E07E136009BCC1B /* Build configuration list for PBXNativeTarget "MacMediaKeyForwarder" */;
			buildPhases = (
				336F74441E07E136009BCC1B /* Sources */,
				336F74451E07E136009BCC1B /* Frameworks */,
				336F74461E07E136009BCC1B /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = MacMediaKeyForwarder;
			productName = "High Sierra Media Key Enabler";
			productReference = 336F74481E07E136009BCC1B /* MacMediaKeyForwarder.app */;
			productType = "com.apple.product-type.application";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		336F74401E07E136009BCC1B /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastUpgradeCheck = 1010;
				ORGANIZATIONNAME = "Milan Toth";
				TargetAttributes = {
					336F74471E07E136009BCC1B = {
						CreatedOnToolsVersion = 8.2;
						ProvisioningStyle = Manual;
						SystemCapabilities = {
							com.apple.Sandbox = {
								enabled = 0;
							};
						};
					};
				};
			};
			buildConfigurationList = 336F74431E07E136009BCC1B /* Build configuration list for PBXProject "MacMediaKeyForwarder" */;
			compatibilityVersion = "Xcode 3.2";
			developmentRegion = English;
			hasScannedForEncodings = 0;
			knownRegions = (
				English,
				en,
				Base,
				ko,
				de,
				es,
				hu,
				ru,
				da,
				ja,
				fi,
				pl,
				nl,
				fr,
				"zh-Hant",
			);
			mainGroup = 336F743F1E07E136009BCC1B;
			productRefGroup = 336F74491E07E136009BCC1B /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				336F74471E07E136009BCC1B /* MacMediaKeyForwarder */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		336F74461E07E136009BCC1B /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				336F74521E07E136009BCC1B /* Assets.xcassets in Resources */,
				A5999C4C1FF3BB89009905E1 /* Localizable.strings in Resources */,
				33CD48CC1F867408000C454F /* icon@2x.png in Resources */,
				336F74551E07E136009BCC1B /* MainMenu.xib in Resources */,
				AA3E49F621AAA0C500EEA78B /* appicon.png in Resources */,
				33CD48CB1F867394000C454F /* icon.png in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		336F74441E07E136009BCC1B /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				3333F8972198687500816A9F /* GBLaunchAtLogin.m in Sources */,
				336F74501E07E136009BCC1B /* main.m in Sources */,
				336F744D1E07E136009BCC1B /* AppDelegate.m in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXVariantGroup section */
		336F74531E07E136009BCC1B /* MainMenu.xib */ = {
			isa = PBXVariantGroup;
			children = (
				336F74541E07E136009BCC1B /* Base */,
			);
			name = MainMenu.xib;
			sourceTree = "<group>";
		};
		A5999C4E1FF3BB89009905E1 /* Localizable.strings */ = {
			isa = PBXVariantGroup;
			children = (
				A5999C4D1FF3BB89009905E1 /* en */,
				A5999C4F1FF3BB98009905E1 /* ko */,
				112AF55920263497006937BF /* de */,
				2C9625F520295D9F00F0C47E /* es */,
				3397086D21EB6B7B009F3857 /* hu */,
				3397086E21EB6B8F009F3857 /* ru */,
				3397086F21EB6B9C009F3857 /* da */,
				330630112313ED7500ACFBFE /* ja */,
				330630122313ED8100ACFBFE /* fi */,
				330630132313F6D400ACFBFE /* pl */,
				330630142313F6DE00ACFBFE /* nl */,
				84B350D923526D6100255450 /* fr */,
				A523261E2887DBBC001EA9B4 /* zh-Hant */,
			);
			name = Localizable.strings;
			sourceTree = "<group>";
		};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
		336F74571E07E136009BCC1B /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				CODE_SIGN_IDENTITY = "-";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				MACOSX_DEPLOYMENT_TARGET = 10.12;
				MTL_ENABLE_DEBUG_INFO = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = macosx;
			};
			name = Debug;
		};
		336F74581E07E136009BCC1B /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				CODE_SIGN_IDENTITY = "-";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				MACOSX_DEPLOYMENT_TARGET = 10.12;
				MTL_ENABLE_DEBUG_INFO = NO;
				SDKROOT = macosx;
			};
			name = Release;
		};
		336F745A1E07E136009BCC1B /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CLANG_ENABLE_OBJC_ARC = YES;
				CODE_SIGN_IDENTITY = "-";
				CODE_SIGN_STYLE = Manual;
				COMBINE_HIDPI_IMAGES = YES;
				DEVELOPMENT_TEAM = "";
				INFOPLIST_FILE = MacMediaKeyForwarder/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
				MACOSX_DEPLOYMENT_TARGET = 10.12;
				MARKETING_VERSION = 3.1;
				PRODUCT_BUNDLE_IDENTIFIER = com.milgra.hsmke;
				PRODUCT_NAME = "$(TARGET_NAME)";
				PROVISIONING_PROFILE_SPECIFIER = "";
			};
			name = Debug;
		};
		336F745B1E07E136009BCC1B /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CLANG_ENABLE_OBJC_ARC = YES;
				CODE_SIGN_IDENTITY = "-";
				CODE_SIGN_STYLE = Manual;
				COMBINE_HIDPI_IMAGES = YES;
				DEVELOPMENT_TEAM = "";
				INFOPLIST_FILE = MacMediaKeyForwarder/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
				MACOSX_DEPLOYMENT_TARGET = 10.12;
				MARKETING_VERSION = 3.1;
				PRODUCT_BUNDLE_IDENTIFIER = com.milgra.hsmke;
				PRODUCT_NAME = "$(TARGET_NAME)";
				PROVISIONING_PROFILE_SPECIFIER = "";
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		336F74431E07E136009BCC1B /* Build configuration list for PBXProject "MacMediaKeyForwarder" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				336F74571E07E136009BCC1B /* Debug */,
				336F74581E07E136009BCC1B /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		336F74591E07E136009BCC1B /* Build configuration list for PBXNativeTarget "MacMediaKeyForwarder" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				336F745A1E07E136009BCC1B /* Debug */,
				336F745B1E07E136009BCC1B /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */
	};
	rootObject = 336F74401E07E136009BCC1B /* Project object */;
}


text:
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:/Users/milgra/Desktop/Projects/github/highsierramediakeyenabler/MacMediaKeyForwarder.xcodeproj">
   </FileRef>
</Workspace>


text:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>IDEDidComputeMac32BitWarning</key>
	<true/>
</dict>
</plist>


text:
#import "AppDelegate.h"
#import "GBLaunchAtLogin.h"
#import "iTunes.h"
#import "Spotify.h"
#import <CoreServices/CoreServices.h>
#import <ScriptingBridge/ScriptingBridge.h>

typedef NS_ENUM(NSInteger, MediaKeysPrioritize)
{
    // Normal behavior (without priority; send events to iTunes and Spotify if both are open)
    MediaKeysPrioritizeNone,
    // If both apps are open, prioritize iTunes over Spotify
    MediaKeysPrioritizeITunes,
    // If both apps are open, prioritize Spotify over iTunes
    MediaKeysPrioritizeSpotify
};

typedef NS_ENUM(NSInteger, PauseState)
{
    // pause app
    PauseStateNone,
    // pause app
    PauseStatePause,
    // pause app automatically when iTunes and Spotify is not running
    PauseStateAutomatic,
};

typedef NS_ENUM(NSInteger, KeyHoldState)
{
    KeyHoldStateNone,
    KeyHoldStateWaiting,
    KeyHoldStateHolding
};

static NSString *kUserDefaultsPriorityOptionKey = @"user_priority_option";
static NSString *kUserDefaultsPauseOptionKey = @"user_pause_option";
static NSString *kUserDefaultsHideFromMenuBarOptionKey = @"user_hide_from_menu_bar_option";

PauseState pauseState;
KeyHoldState keyHoldStatus;
MediaKeysPrioritize mediaKeysPriority;

@interface AppDelegate ()
{
    NSStatusItem* statusItem;
    CFMachPortRef eventPort;
    CFRunLoopSourceRef eventPortSource;
    NSMutableArray *priorityOptionItems;
    NSMutableArray *pauseOptionItems;
    NSMenuItem *startupItem;
    NSMenuItem *hideFromMenuBarItem;
}

@end

@implementation AppDelegate

static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
{
    @autoreleasepool
    {
        AppDelegate *self = (__bridge id)refcon;

        if(type == kCGEventTapDisabledByTimeout)
        {
            CGEventTapEnable(self->eventPort, TRUE);
            return event;
        }

        if(type == kCGEventTapDisabledByUserInput)
        {
            return event;
        }

        if(type != NX_SYSDEFINED )
        {
            return event;
        }

        NSEvent *nsEvent = nil;
        @try
        {
            nsEvent = [NSEvent eventWithCGEvent:event];
        }
        @catch (NSException * e)
        {
            return event;
        }

        if([nsEvent subtype] != 8)
        {
            return event;
        }

        int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16);

        if (keyCode != NX_KEYTYPE_PLAY &&
            keyCode != NX_KEYTYPE_FAST &&
            keyCode != NX_KEYTYPE_REWIND &&
            keyCode != NX_KEYTYPE_PREVIOUS &&
            keyCode != NX_KEYTYPE_NEXT)
        {
            return event;
        }

        iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:[self iTunesBundleIdentifier]];
        SpotifyApplication *spotify = [SBApplication applicationWithBundleIdentifier:@"com.spotify.client"];

        if ( pauseState == PauseStatePause )
        {
            return event;
        }

        if ( pauseState == PauseStateAutomatic )
        {
            if (![spotify isRunning ] && ![iTunes isRunning ] )
            {
                return event;
            }
        }

        int keyFlags = ([nsEvent data1] & 0x0000FFFF);
        BOOL keyIsPressed = (((keyFlags & 0xFF00) >> 8)) == 0xA;

        if (keyIsPressed)
        {
            switch ( mediaKeysPriority )
            {
                case MediaKeysPrioritizeITunes:
                {
                    switch (keyCode)
                    {
                        case NX_KEYTYPE_PLAY:
                        {
                            [iTunes playpause];
                            break;
                        }
                        default:
                        {
                            if (keyHoldStatus == KeyHoldStateNone)
                            {
                                keyHoldStatus = KeyHoldStateWaiting;
                            }
                            else if (keyHoldStatus == KeyHoldStateWaiting)
                            {
                                keyHoldStatus = KeyHoldStateHolding;
                                switch (keyCode)
                                {
                                    case NX_KEYTYPE_NEXT:
                                    case NX_KEYTYPE_FAST:
                                    {
                                        [iTunes fastForward];
                                        break;
                                    }
                                    case NX_KEYTYPE_PREVIOUS:
                                    case NX_KEYTYPE_REWIND:
                                    {
                                        [iTunes rewind];
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    break;
                }
                case MediaKeysPrioritizeSpotify:
                {
                    switch (keyCode)
                    {
                        case NX_KEYTYPE_PLAY:
                        {
                            [spotify playpause];
                            break;
                        }
                        case NX_KEYTYPE_NEXT:
                        case NX_KEYTYPE_FAST:
                        {
                            [spotify nextTrack];
                            break;
                        };
                        case NX_KEYTYPE_PREVIOUS:
                        case NX_KEYTYPE_REWIND:
                        {
                            [spotify previousTrack];
                            break;
                        }
                    }
                    break;
                }
                case MediaKeysPrioritizeNone:
                {
                    switch (keyCode)
                    {
                        case NX_KEYTYPE_PLAY:
                        {
                            if ( [spotify isRunning ] ) [spotify playpause];
                            if ( [iTunes isRunning ] ) [iTunes playpause];
                            break;
                        }
                        case NX_KEYTYPE_NEXT:
                        case NX_KEYTYPE_FAST:
                        {
                            if ( [spotify isRunning ] ) [spotify nextTrack];
                            if ( [iTunes isRunning ] ) [iTunes nextTrack];
                            break;
                        }
                        case NX_KEYTYPE_PREVIOUS:
                        case NX_KEYTYPE_REWIND:
                        {
                            if ( [spotify isRunning ] ) [spotify previousTrack];
                            if ( [iTunes isRunning ] ) [iTunes backTrack];
                            break;
                        }
                    }
                    break;
                }
            }
        }
        else
        {
            switch (keyHoldStatus)
            {
                case KeyHoldStateWaiting:
                {
                    if (mediaKeysPriority == MediaKeysPrioritizeITunes)
                    {
                        switch (keyCode)
                        {
                            case NX_KEYTYPE_NEXT:
                            case NX_KEYTYPE_FAST:
                            {
                                [iTunes nextTrack];
                                break;
                            }
                            case NX_KEYTYPE_PREVIOUS:
                            case NX_KEYTYPE_REWIND:
                            {
                                [iTunes backTrack];
                                break;
                            }
                        }
                    }
                    break;
                }
                case KeyHoldStateHolding:
                {
                    // Stop fast forwarding / rewinding

                    if (mediaKeysPriority == MediaKeysPrioritizeITunes)
                    {
                        [iTunes resume];
                    }
                    break;
                }
                case KeyHoldStateNone:
                {
                    break;
                }
            }
            keyHoldStatus = KeyHoldStateNone;
        }

        // stop propagation

        return NULL;
    }
}

- (NSString *)iTunesBundleIdentifier {
    if ( @available(macOS 10.15, *) )
    {
        return @"com.apple.music";
    }
    else
    {
        return @"com.apple.iTunes";
    }
}

- (void)applicationDidBecomeActive:(NSNotification *)notification
{

}

- ( void ) applicationDidFinishLaunching : ( NSNotification*) theNotification
{
    // init containers

    priorityOptionItems = [[NSMutableArray alloc] init];
    pauseOptionItems = [[NSMutableArray alloc] init];

    // init states

    pauseState = PauseStateNone;
    keyHoldStatus = KeyHoldStateNone;
    mediaKeysPriority = MediaKeysPrioritizeNone;

    NSNumber *option = [[NSUserDefaults standardUserDefaults] objectForKey:kUserDefaultsPriorityOptionKey];
    if ( option )
    {
        mediaKeysPriority = [option integerValue];
    }

    option = [[NSUserDefaults standardUserDefaults] objectForKey:kUserDefaultsPauseOptionKey];
    if ( option )
    {
        pauseState = [option integerValue];
    }

    // Version string

    NSDictionary *bundleInfo = [[NSBundle mainBundle] infoDictionary];
    NSString *versionString = [NSString stringWithFormat:@"Version %@ (build %@)",
                               bundleInfo[@"CFBundleShortVersionString"],
                               bundleInfo[@"CFBundleVersion"] ];

    NSMenu *menu = [ [ NSMenu alloc ] init ];
    [ menu setDelegate : self ];
    [ menu addItemWithTitle : versionString action : nil keyEquivalent : @"" ];
    [ menu addItem : [ NSMenuItem separatorItem ] ]; // A thin grey line

    [pauseOptionItems addObject:[ menu addItemWithTitle: NSLocalizedString(@"Pause", @"Pause") action : @selector(manualPause) keyEquivalent : @"" ]];
    [pauseOptionItems addObject:[ menu addItemWithTitle: NSLocalizedString(@"Pause if no player is running", @"Pause if no player is running") action : @selector(autoPause) keyEquivalent : @"" ]];

    [ menu addItem : [ NSMenuItem separatorItem ] ]; // A thin grey line

    [priorityOptionItems addObject:[ menu addItemWithTitle: NSLocalizedString(@"Send events to both players", @"Send events to both players") action : @selector(prioritizeNone) keyEquivalent : @"" ]];
    [priorityOptionItems addObject:[ menu addItemWithTitle: NSLocalizedString(@"Prioritize iTunes", @"Prioritize iTunes") action : @selector(prioritizeITunes) keyEquivalent : @"" ]];
    [priorityOptionItems addObject:[ menu addItemWithTitle: NSLocalizedString(@"Prioritize Spotify", @"Prioritize Spotify") action : @selector(prioritizeSpotify) keyEquivalent : @"" ]];

    [ menu addItem : [ NSMenuItem separatorItem ] ]; // A thin grey line

    startupItem = [ menu addItemWithTitle:NSLocalizedString(@"Open at login", @"Open at login") action:@selector(toggleStartupItem) keyEquivalent:@""];
    hideFromMenuBarItem = [ menu addItemWithTitle:NSLocalizedString(@"Hide from menu bar", @"Hide from menu bar") action:@selector(hideFromMenuBar) keyEquivalent:@""];
    [ menu addItem : [ NSMenuItem separatorItem ] ]; // A thin grey line

    [ menu addItem : [ NSMenuItem separatorItem ] ]; // A thin grey line

    [ menu addItemWithTitle : NSLocalizedString(@"Donate if you like the app", @"Donate if you like the app") action : @selector(support) keyEquivalent : @"" ];
    [ menu addItemWithTitle : NSLocalizedString(@"Check for updates", @"Check for updates") action : @selector(update) keyEquivalent : @"" ];
    [ menu addItemWithTitle : NSLocalizedString(@"Quit", @"Quit") action : @selector(terminate) keyEquivalent : @"" ];

    NSImage* image = [ NSImage imageNamed : @"icon" ];
    [ image setTemplate : YES ];

    statusItem = [ [ NSStatusBar systemStatusBar ] statusItemWithLength : NSVariableStatusItemLength ];
    [ statusItem setToolTip : @"Mac Media Key Forwarder" ];
    [ statusItem setMenu : menu ];
    [ statusItem setImage : image ];
    [ statusItem setBehavior : NSStatusItemBehaviorRemovalAllowed ];
    if ([self shouldHideFromMenuBar]) {
        [ statusItem setVisible : NO ];
    } else {
        [ statusItem setVisible : YES ];
    }

    [self updateStartupItemState];
    [self updatePauseState];
    [self updateOptionState];

    eventPort = CGEventTapCreate( kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, CGEventMaskBit(NX_SYSDEFINED), tapEventCallback, (__bridge void * _Nullable)(self));
    if ( eventPort == NULL )
    {
    	eventPort = CGEventTapCreate( kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, NX_SYSDEFINEDMASK, tapEventCallback, (__bridge void * _Nullable)(self));
	}

    if ( eventPort != NULL )
    {

		// Check if permission is granted to send AppleEvents to the running app target, and prompt if not set
		if ( @available(macOS 10.14, *) )
		{

			iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:[self iTunesBundleIdentifier]];
			SpotifyApplication *spotify = [SBApplication applicationWithBundleIdentifier:@"com.spotify.client"];

			if ( spotify != nil )
			{
				NSAppleEventDescriptor *targetAppEventDescriptor = [NSAppleEventDescriptor descriptorWithBundleIdentifier:@"com.spotify.client"];
				AEDeterminePermissionToAutomateTarget([targetAppEventDescriptor aeDesc], typeWildCard, typeWildCard, true);
			}

			if ( iTunes != nil )
			{
				NSAppleEventDescriptor *targetAppEventDescriptor = [NSAppleEventDescriptor descriptorWithBundleIdentifier:[self iTunesBundleIdentifier]];
				AEDeterminePermissionToAutomateTarget([targetAppEventDescriptor aeDesc], typeWildCard, typeWildCard, true);
			}
		}

		eventPortSource = CFMachPortCreateRunLoopSource( kCFAllocatorSystemDefault, eventPort, 0 );

		[self startEventSession];

	}
	else
	{

		NSAlert *alert = [[NSAlert alloc] init];
		[alert setMessageText:@"Error"];
		[alert setInformativeText:@"Cannot start event listening. Please add Mac Media Key Forwarder to the \"Security & Privacy\" pane in System Preferences. Check \"Accessibility\" and \"Automation\" under the \"Privacy\" tab."];
		[alert addButtonWithTitle:@"Ok"];
		[alert runModal];

		exit(0);

	}

}

- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
{
    if ([self shouldHideFromMenuBar]) {
        [self setHideFromMenuBar:NO];
        [statusItem setVisible: YES];
    }

    return YES;
}

- ( void ) startEventSession
{
    if (pauseState != PauseStatePause && !CFRunLoopContainsSource(CFRunLoopGetCurrent(), eventPortSource, kCFRunLoopCommonModes)) {
        CFRunLoopAddSource( CFRunLoopGetCurrent(), eventPortSource, kCFRunLoopCommonModes );
        CFRunLoopRun();
    }
}

- ( void ) stopEventSession
{
    if (CFRunLoopContainsSource(CFRunLoopGetCurrent(), eventPortSource, kCFRunLoopCommonModes)) {
        CFRunLoopRemoveSource( CFRunLoopGetCurrent(), eventPortSource, kCFRunLoopCommonModes );
        CFRunLoopStop(CFRunLoopGetCurrent());
    }
}

- ( void ) terminate
{
    [ NSApp terminate : nil ];
}

- ( void ) support
{
    [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString: @"https://paypal.me/milgra"]];
}

- ( void ) update
{
    [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString: @"http://milgra.com/mac-media-key-forwarder.html"]];
}

#pragma mark - App priorization

- (void)prioritizeNone
{
    mediaKeysPriority = MediaKeysPrioritizeNone;
    [[NSUserDefaults standardUserDefaults] setObject:@(mediaKeysPriority) forKey:kUserDefaultsPriorityOptionKey];
    [self updateOptionState];
}

- (void)prioritizeITunes
{
    mediaKeysPriority = MediaKeysPrioritizeITunes;
    [[NSUserDefaults standardUserDefaults] setObject:@(mediaKeysPriority) forKey:kUserDefaultsPriorityOptionKey];
    [self updateOptionState];
}

- (void)prioritizeSpotify
{
    mediaKeysPriority = MediaKeysPrioritizeSpotify;
    [[NSUserDefaults standardUserDefaults] setObject:@(mediaKeysPriority) forKey:kUserDefaultsPriorityOptionKey];
    [self updateOptionState];
}

- (void)manualPause
{
    if ( pauseState != PauseStatePause )
    {
        pauseState = PauseStatePause;
        [self stopEventSession];
    }
    else
    {
        pauseState = PauseStateNone;
        [self startEventSession];
    }

    [[NSUserDefaults standardUserDefaults] setObject:@(pauseState) forKey:kUserDefaultsPauseOptionKey];
    [self updatePauseState];
}

- (void)autoPause
{
    if ( pauseState != PauseStateAutomatic )
    {
        pauseState = PauseStateAutomatic;
    }
    else
    {
        pauseState = PauseStateNone;
    }
    [[NSUserDefaults standardUserDefaults] setObject:@(pauseState) forKey:kUserDefaultsPauseOptionKey];
    [self updatePauseState];

    [self startEventSession];
}

#pragma mark - Startup Item
- (void)toggleStartupItem {
    if ( [GBLaunchAtLogin isLoginItem] ) {
        [GBLaunchAtLogin removeAppFromLoginItems];
    }
    else {
        [GBLaunchAtLogin addAppAsLoginItem];
    }

    [self updateStartupItemState];
}

- (void)hideFromMenuBar
{
    [self setHideFromMenuBar:YES];

    if ([GBLaunchAtLogin isLoginItem] == NO) {
        [GBLaunchAtLogin addAppAsLoginItem];
    }

    [statusItem setVisible: NO];
}

- (void)setHideFromMenuBar:(BOOL)hidden
{
    [[NSUserDefaults standardUserDefaults] setBool:hidden forKey:kUserDefaultsHideFromMenuBarOptionKey];
}

- (BOOL)shouldHideFromMenuBar
{
    return [[NSUserDefaults standardUserDefaults] boolForKey:kUserDefaultsHideFromMenuBarOptionKey];
}

#pragma mark - UI refresh

- (void)updateOptionState
{
    // Verify if a choice was selected, otherwise mark "None" as the default

    NSNumber *option = [[NSUserDefaults standardUserDefaults] valueForKey:kUserDefaultsPriorityOptionKey];
    if ( option )
    {
        mediaKeysPriority = [option integerValue];
    }

    // Mark with a tick the selected item from priority options

    for ( NSUInteger index = 0, num = priorityOptionItems.count; index < num; index++ )
    {
        NSMenuItem *item = priorityOptionItems[index];
        [item setState:( index == mediaKeysPriority ? NSControlStateValueOn : NSControlStateValueOff )];
    }
}

- (void)updatePauseState
{
    NSMenuItem *item0 = pauseOptionItems[0];
    NSMenuItem *item1 = pauseOptionItems[1];

    [item0 setState: pauseState == PauseStatePause ? NSControlStateValueOn : NSControlStateValueOff];
    [item1 setState: pauseState == PauseStateAutomatic ? NSControlStateValueOn : NSControlStateValueOff];
}

- (void)updateStartupItemState {
    [startupItem setState: [GBLaunchAtLogin isLoginItem] ? NSControlStateValueOn : NSControlStateValueOff];
}

- (void)menuWillOpen:(NSMenu *)menu
{
    [self updateStartupItemState];
}

@end


text:
    #import <Cocoa/Cocoa.h>

    @interface AppDelegate : NSObject <NSApplicationDelegate,NSMenuDelegate>

    @end


text:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>$(MARKETING_VERSION)</string>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSMinimumSystemVersion</key>
	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
	<key>LSUIElement</key>
	<true/>
	<key>NSAppleEventsUsageDescription</key>
	<string></string>
	<key>NSHumanReadableCopyright</key>
	<string>2017. Milan Toth. No rights reserved.</string>
	<key>NSMainNibFile</key>
	<string>MainMenu</string>
	<key>NSPrincipalClass</key>
	<string>NSApplication</string>
</dict>
</plist>


text:
Start with clean install ( delete app and com.milgra.hmske.plist from ~/Library/Preferences )

Default state :
    - check menu :
        - pause items should be disabled
        - send events to both players should be selected
    - check if app controls itunes/spotify individually and both running.
    - app shouldn't start music players automatically.

iTunes state :
    - select Prioritize iTunes from the menu
    - check if app starts up iTunes automatically
    - check if play/pause/prev/next is working
    - check if fast forward/rewind is working
    - restart app and check if prioritize iTunes remains selected

Spotify state :
    - select Prioritize Spotify from the menu
    - check if app starts up Spotify automatically
    - check if play/pause/prev/next is working
    - check if fast forward/rewind is working
    - restart app and check if prioritize Spotify remains selected

Pause state :
    - select pause from the menu
    - app shouldn't forward events to player
    - macOS default media key behaviour should happen ( try it with a youtube video in safari )

Auto-Pause state :
    - select pause if no player is running from the menu
    - close iTunes and Spotify
        - app shouldn't forward events to player
        - macOS default media key behaviour should happen
    - open iTunes or Spotify
        - app should behave based on the selected priority option


text:
/*
 * Spotify.h
 */

#import <AppKit/AppKit.h>
#import <ScriptingBridge/ScriptingBridge.h>

@class SpotifyApplication, SpotifyTrack, SpotifyApplication;

enum SpotifyEPlS {
	SpotifyEPlSStopped = 'kPSS',
	SpotifyEPlSPlaying = 'kPSP',
	SpotifyEPlSPaused = 'kPSp'
};
typedef enum SpotifyEPlS SpotifyEPlS;

/*
 * Spotify Suite
 */

// The Spotify application.
@interface SpotifyApplication : SBApplication

@property (copy, readonly) SpotifyTrack *currentTrack;  // The current playing track.
@property NSInteger soundVolume;  // The sound output volume (0 = minimum, 100 = maximum)
@property (readonly) SpotifyEPlS playerState;  // Is Spotify stopped, paused, or playing?
@property double playerPosition;  // The player’s position within the currently playing track in seconds.
@property (readonly) BOOL repeatingEnabled;  // Is repeating enabled in the current playback context?
@property BOOL repeating;  // Is repeating on or off?
@property (readonly) BOOL shufflingEnabled;  // Is shuffling enabled in the current playback context?
@property BOOL shuffling;  // Is shuffling on or off?

- (void) nextTrack;  // Skip to the next track.
- (void) previousTrack;  // Skip to the previous track.
- (void) playpause;  // Toggle play/pause.
- (void) pause;  // Pause playback.
- (void) play;  // Resume playback.
- (void) playTrack:(NSString *)x inContext:(NSString *)inContext;  // Start playback of a track in the given context.

@end

// A Spotify track.
@interface SpotifyTrack : SBObject

@property (copy, readonly) NSString *artist;  // The artist of the track.
@property (copy, readonly) NSString *album;  // The album of the track.
@property (readonly) NSInteger discNumber;  // The disc number of the track.
@property (readonly) NSInteger duration;  // The length of the track in seconds.
@property (readonly) NSInteger playedCount;  // The number of times this track has been played.
@property (readonly) NSInteger trackNumber;  // The index of the track in its album.
@property (readonly) BOOL starred;  // Is the track starred?
@property (readonly) NSInteger popularity;  // How popular is this track? 0-100
- (NSString *) id;  // The ID of the item.
@property (copy, readonly) NSString *name;  // The name of the track.
@property (copy, readonly) NSString *artworkUrl;  // The URL of the track%apos;s album cover.
@property (copy, readonly) NSImage *artwork;  // The property is deprecated and will never be set. Use the 'artwork url' instead.
@property (copy, readonly) NSString *albumArtist;  // That album artist of the track.
@property (copy) NSString *spotifyUrl;  // The URL of the track.

@end

/*
 * Standard Suite
 */

// The application's top level scripting object.
@interface SpotifyApplication (StandardSuite)

@property (copy, readonly) NSString *name;  // The name of the application.
@property (readonly) BOOL frontmost;  // Is this the frontmost (active) application?
@property (copy, readonly) NSString *version;  // The version of the application.

@end


text:
//
//  main.m
//
//  Created by Milan Toth on 2016. 12. 19..
//  Copyright © 2016. Milan Toth. All rights reserved.
//

#import <Cocoa/Cocoa.h>

int main(int argc, const char * argv[]) {
    return NSApplicationMain(argc, argv);
}